乐趣区

关于微服务:有状态的服务其实可以做更多的事情

简介

对于初学者,心里对“有状态服务”的了解可能比拟含糊,然而从面向对象编程思维的角度去了解兴许会清朗很多。面向对象编程思维提倡的是用编程语言去形容世间万物,所以面向对象编程的语言都会提供形容对象的容器以及对象行为的表达方式。举一个很简略的栗子,在 c# 或者 java 中,表白对象的容器就是 class,对象的行为通过一系列的接口或者函数来表白。更进一步,对象形象进去之后,大多数对象都有本人的外部状态,体现到代码上也就是常见的类的属性。

面向对象编程的根本思维实质上是对事实世界的一种形象,万物皆可形象。

依据业务把对象形象进去之后,每一个实例化的对象其实都能够有本人的状态,比方:在最常见的游戏场景中,每一个玩家都是“玩家 ” 这类对象的一个实例,每一个玩家都有本人的名字,性别,等级,HP 等属性,这些属性实质上就是玩家的状态,随着工夫的推移,每个玩家的 HP,等级等属性会随之变动,这些变动其实就是这个玩家状态的变动。对应到有状态的服务也是如此,之所以称之为有状态,是因为服务外部的对象状态会随着业务有着对应的变动,而这些变动只产生在这个服务外部,在外界看来,这个服务如同是有状态的。

有状态的服务实质上是一些有状态对象的汇合,这些对象状态的变动只产生在以后服务过程中。

劣势和劣势

有状态服务之所以被称为有状态,一个很大的起因是它能够追溯状态的变动过程,也就是说一个有状态的服务保留着状态变动的记录,并能够依据这些历史记录复原到指定的状态,这在很多场景下十分有用。举一个很简略的栗子:咱们平时玩的斗地主游戏,三个玩家,当有一个玩家因为网络起因掉线,通过一段时间,这个玩家又从新上线,须要依据某些记录来复原玩家掉线期间零碎主动出牌的记录,这些出牌记录在这个业务中其实就是这个玩家的状态变动记录。在有状态的服务中,很容易做到这一点。

其实理论开发中很多场景不须要记录每个状态的变动,只保留最新状态即可,不单单是因为保留每个状态的变动须要大量的存储和架构设计,更因为是很多业务基本不须要这些状态变动记录,业务须要的只是最新的状态,所以大部分有状态的服务只保留着最新的状态。

有状态的服务在设计难度上比无状态的服务要大很多,不仅仅是因为开发设计人员须要更好的形象能力,更多的是一致性的设计问题。古代的分布式系统,都是由多个服务器组成一个集群来对外提供服务,当一个对象在服务器 A 产生之后,如果申请被调配到了服务器 B 上,这种状况下有状态的服务毫无意义,为什么呢?当一个雷同的业务对象存在于不同的服务器上的时候,实质上就违反了事实世界的规定,你能说一个人,即出世在中国,又出世在美国吗?所以有状态的服务对于一致性问题有着人造的要求,这种思维和微服务设计现实不约而同,举个栗子:一个用户信息的服务,对外提供查问批改能力,但凡用户信息的业务必须通过这个服务来实现。同理,一个对象状态的查问批改以及这个对象的行为,必须由这个对象的服务来实现。

有状态的服务要求雷同业务对象的申请必须被路由到同一个服务过程。

因而,有状态的服务对于同一个对象的横向扩容是做不到的,就算是做的到,多个雷同对象之间的状态同步工作也必然会破费更多的资源。在很多场景下,有状态的服务要留神热点问题,例如最常见的秒杀,这里并非是说有状态服务不适宜大并发的场景,反而在高并发的场景下,有状态的服务往往体现的比无状态服务更加杰出。

Actor 模型

在泛滥的并发模型中,最适宜有状态服务设计的莫过于 Actor 模型了,如果你对 actor 模型还不相熟,能够撸一遍菜菜之前的文章:

分布式高并发下 Actor 模型如此优良

actor 模型天生就具备了一致性这种特点,让咱们在对业务进行形象的时候,不用思考一致性的问题,而且每一个申请都是异步模式,在对象外部批改对象的状态不用加锁,这在传统的架构中是做不到的。

基于 actor 模型,零碎设计的难点在于形象业务模型,一旦业务模型稳固,咱们齐全能够用内存形式来保留对象状态(也能够定时去长久化),内存形式比用其余网络存储(例如 redis)要快上几个量级,菜菜也有一篇文章大家能够去撸一下:

高并发下为什么更喜爱过程内缓存

,既满足了一致性,又能够利用过程内对象状态来应答高并发业务场景,何乐而不为呢?

有不少同学问过我,Actor 模型要避免出现热点问题,就算有内存状态为其减速,那并发数还是超过 actor 的解决能力怎么办呢? 其实和传统做法相似,所有的高并发零碎设计无非就是“分”一个字,无论是简略的负载平衡,还是简单的分库分表策略,都是分治的一种体现。一台服务器不够,我就上十台,百台 …..

所有的高并发零碎设计都是基于分治思维,把每一台服务器的能力施展到极致,难度最大的还是其中的调度算法。

用 actor 模型来应答高并发,咱们能够采纳读写拆散的思维,主 actor 负责写申请,并利用某种通信机制把状态的变动告诉到多个从 actor,从 actor 负责对外的读申请,这个 DB 的读写拆散思维统一,其中最难的当属 actor 的状态同步问题了,解决问题的形式千百种,总有一种适宜你,欢送你留言写下你认为最好的解决方案。

案例(玩家信息服务)

因为菜菜是 c#出身,对 c# 的 Actor 服务框架 Orleans 比拟相熟,这里就以 Orleans 为例,其余语言的 coder 不要怪罪,Orleans 是一个十分优良的 Actor 模型框架,而且反对最新的 netcore 3.0 版本,地址为:https://github.com/dotnet/orl… 有趣味的同学能够去看一下,而且分布式事物曾经出正式版,十分给力。其余语言的也十分杰出

java:https://github.com/akka/akka

golang:https://github.com/AsynkronIT…

  1. 首先咱们定义玩家的状态信息
   // 玩家的信息,其实也就是玩家的状态信息
    public class Player {
        /// <summary>
        /// 玩家 id, 同时也是玩家这个服务的主键
        /// </summary>
        public long Id {get; set;}
        /// <summary>
        /// 玩家姓名
        /// </summary>
        public string Name {get; set;}
        /// <summary>
        /// 玩家等级
        /// </summary>
        public int Level {get; set;}
    }
  1. 接下来定义玩家的服务接口
    /// <summary>
    /// 玩家的服务接口
    /// </summary>
    interface IPlayerService: Orleans.IGrainWithIntegerKey
    {
        // 获取玩家名称
        Task<string> GetName();
        // 获取玩家等级
        Task<int> GetLevel();
        // 设置玩家等级,这个操作会扭转玩家的状态
        Task<int> SetLevel(int newLevel);
    }
  1. 接下来实现玩家服务的接口
 public class PlayerService : Grain, IPlayerService
    {
        // 这里能够用玩家的信息来代表玩家的状态信息,而且这个状态信息又充当了过程内缓存的作用
        Player playerInfo;
        public async Task<int> GetLevel()
        {return (await LoadPlayer()).Level;
        }

        public async Task<string> GetName()
        {return (await LoadPlayer()).Name;
        }

        public async Task<int> SetLevel(int newLevel)
        {var playerInfo =await LoadPlayer();
            if (playerInfo != null)
            {
                // 先进行数据库的更新,而后在更新缓存的状态, 过程内缓存更新失败的几率简直为 0
                playerInfo.Level = newLevel;                
            }
            return 1;
        }

        private async Task< Player> LoadPlayer()
        {if (playerInfo == null)
            {var id = this.GetPrimaryKeyLong();
                // 这里模仿的信息,实在环境齐全能够从长久化设施进行读取
                playerInfo= new Player() { Id = id, Name = "玩家姓名", Level = 1};
            }
            return playerInfo;
        }
    }

以上只是一个简略案例,有状态的服务还有更多的设计方案,以上只供参考

更多精彩文章

  • 分布式大并发系列
  • 架构设计系列
  • 趣学算法和数据结构系列
  • 设计模式系列

退出移动版