Flash----一种VirtualActor模式的分布式有状态系统原型

首先, 这个Flash不是咱们在浏览器用的Flash这种技术, 而是:python

动做缓慢, 车速极快------闪电(Flash).c++

 

18年的某一个周末, 即兴用Python写了一个Virtual Actor模式的demo, 起了一个名字叫Flash, 是由于速度快如闪电------作framework快, 经过framework写逻辑快.git

因此大言不惭, 叫Flash, https://github.com/egmkang/flash. 第一个版本是asyncio写的, 可是编写的过程当中发现一旦少写一点东西(async/await), bug会很难找. 这一点和C#是不太同样的, 因此第一个版本能够跑以后, 花了一点时间把asyncio的代码换成了gevent.github

 

这边主要来讲说当时的想法, 之后将来若是要作相似的东西, 该如何选择. (README里面的东西可能和实现没多少关系......懒, 因此也不打算更改README, 错了就错了)golang

 

当时为了实现一个去中心化, 能够横向扩容, 能够故障迁移有状态framework. (很显然我对无状态的东西一点兴趣都没有:-D)promise

 

全部有几个关键点, 这边简单介绍一下(由于代码不必定能跑起来, 可是思想能够):浏览器

1) RPC缓存

 这边没有使用第三方RPC库, 而是选择本身实现看了一个. 在Python这种语言里面, 实现一个RPC仍是比较简单的, 所须要的例如future/promise, 序列化库, 协程, 仍是就是Python是动态语言, 因此造一个Proxy对象比较简单(C#里面是DispatchProxy).安全

   future/promise选择了gevent.event.AsyncResult.服务器

   序列化库选择了pickle, 序列化这边作法其实是有一点问题的, 第一个就是pickle效率较低, 数据比较大; 第二个就是RpcRequest/RpcResponse协议的设计不对, 由于Python的args是没有通过序列化直接塞到RpcRequest里面的, 因此没看出来有啥问题, 可是若是是其余语言这样就行不通了. 因此比较科学的作法仍是brpc那种, 包分红三部分: 包头, meta, data. 其中meta用来形容data数据和请求的元数据, 这样的话, args数据就不会被encode两次. python里面能够这么搞不表明其余语言也能够这么搞.

   Proxy对象的话, 是本身造的. 在rpc_proxy.py里面, 经过重写__call__元方法, 实现比较复杂的功能. C#的DispatchProxy也能实现这种功能, 并且功能更强大, 类型仍是安全的, Python里面作不到类型安全. 不支持动态代码生成的语言作这个都不太好作, 例如golang/c++等.

   哦, 还有就是网络库里面必定要注意sendsendall这两种东西的区别, 对于用户来说sendall代码容易编写, 可是用send实现就须要注意一下返回值, 不然可能发了一半数据, 而后对面收到的流是断的.

2) 服务发现

   元数据存在etcd里面. 

   每一个进程拉起来的时候, 经过uuid生成一个惟一的id, 当作ServerID, 而后组成一个MachineInfo, 而后就开启了一个update_machine_member_info的死循环, 去etcd里面不停的去更新本身的信息(有一个5s的CD).

   而后再开启一个get_members_info死循环去不停的刀etcd里面pull最新的membership信息, 而后再保存到内存中.

   这样在MemberShipManager里面就能够不停的add_machine, remove_machine.

   这样作的话, 只须要通过几个TTL, 集群的全部节点就能感知到成员的变动; 成员和etcd失联, 那么就应该本身退出(Flash里面没作).

3) 对象的定位

   上面说了, 集群内的节点对其余节点的感知其实是靠定时pull etcd信息来得到的, 那么新加入的节点, 就不能立马提供服务, 不然集群元数据是不一致的. 例如5s间隔去pull, 那么3个interval以后, 其余节点大几率是能感知到节点的变动. 因此等一段时间再路由新的请求到新增服务器, 能够作到更好的一致性.

   而后, MachineInfo内有服务器的负载信息, 那么:

   0> 先到进程内缓存区寻找对象的位置, 看看最近是否有人请求过, 若是目标服务器健康(保持心跳), 那么直接返回

   1> 先去到etcd里面查询对象的位置是否存在, 若是存在, 而且machine健康, 那么直接返回(并缓存)

   2> 对对象上分布式锁(经过etcd), 而后再作步骤1>, 还未找到对象的位置, 那么获取到能够提供相应服务的machine列表, 经过负载权重, 随机出来一个新的服务器, 而后保存etcd, 保存进程缓存, 返回

   很明显, 对象的定位是经过客户端侧+带权重的Random来作的. 这只是一种选择, 完成一个功能有不少选择.

4) 故障迁移

   对象的定位有一个检测目标服务器是否健康的过程, 实际上就是目标服务器是否最近向etcd更新过本身的心跳, 若是更新过那么认为健康, 不然就是不健康.

   那么, 当目标服务器不健康的时候, 就会触发对象的再定位, 从而实现故障的迁移.

5) 可重入性

   互联网的服务不存在这个问题, 是由于互联网的无状态服务, 不存在排队等候处理请求的过程.

   可是在有状态服务里面, 每每会对同一个用户(或者其余单位)的请求进行排队. 那么试想一下, 排队处理A的请求, A又调用了B, B又调用了A. A的请求没有返回以前是不能处理其余的请求的, 因此这时候就死锁了. 因此有状态的Actor服务必需要处理这种状况.

   这时候须要引入一点点代码, 来看看RpcRequest的数据结构, 里面吗包含了一个request_id, 可是在request_id以前有一个host. 实际上就是这俩数据, 决定了rpc请求的可重入性.

class RpcRequest:
    def __init__(self):
        self.clear()

    def clear(self):
        self.host = ""
        if _global_id_generator is not None:
            self.request_id = _global_id_generator.NextId()
        else:
            self.request_id = 0
        self.entity_type = 0
        self.entity_id = 0
        self.method = ""
        self.args = ()
        self.kwargs = dict()

   思考一下, Actor请求的一个请求是谁发出的? 确定是外界系统产生的第一个请求, 那么这个请求没有完成以前, 是不能处理其余请求的. 而中间的请求实际上都不是源头. 因此咱们只须要在源头上面标记惟一ID, 中间传染的路径上面都用源头的惟一ID, 因此系统里面有一个ActorContext的概念, 就是在保存这个信息. Dispatch的过程也就变得比较简单:

    if entity.context().running is False:
        gevent.spawn(lambda: _dispatch_entity_method_loop(entity))

    if entity.context().host == request.host and entity.context().request_id <= request.request_id:
        gevent.spawn(lambda: _dispatch_entity_method_anyway(entity, conn, request, response, method))
        return
    entity.context().send_message((conn, request, response, method))

   若是对象的loop不在运行就拉起来, 若是如今正在处理的请求和当前须要被Dispatch的请求源自一个请求, 那么直接开启一个协程去处理, 不然就塞进MailBox等候处理.

   从而实现了可重入性.

 

   Flash, 麻雀虽小五脏俱全, 实现不是很精良, 可是做为一个原型, 其目的已经达到. 能够对其实现进行反思, 组合出来更合理的分布式有状态服务系统.

   世人都说Python的性能差, 可是这个原型系统一秒能够跨进程进行1.5~2.2Wqps, 已经很是优秀了. 有没有算过本身的系统到底要承载多少请求, Python真的就是系统的瓶颈?

   

参考:

0) Flash (https://github.com/egmkang/flash)

1) Orleans (https://dotnet.github.io/orleans/)

2) gevent (http://www.gevent.org/)

相关文章
相关标签/搜索