美文网首页
初探分布式之旅——几年前的一封邮件

初探分布式之旅——几年前的一封邮件

作者: Mr_Child | 来源:发表于2017-04-28 12:37 被阅读0次
    几年前的一封邮件,贴简书里留作纪念
    

    大家好,我这个邮件是想分享下我最近对于分布式设计的感受。
    分布式,要谈论这么一个题目,首先需要抛出来的两个问题就是:
    一,什么是分布式?
    二,我们需要分布式么?
    对于一,我们可能脑海中会出现大量的专业术语,但这里我们只涉及狭义范围,只讨论如何处理互联网海量请求的分布式服务。分布式,三个字中我认为重点就是一个分字,简单地说就是怎么把一个系统分成几个可以重复部署的组件,通过简单的重复部署这些组件,就能应对不断增长的容量需求。
    对于二,
    最近我通过观察我们公司大家的项目以及代码,我觉得大多数后台开发可能在大量的tornado框架cgi开发工作中,因为重复的无状态http设计,而渐渐失去了复杂系统设计的“状态连续性”概念。
    我们最常见的读cgi设计是这样的,用户发起一个http请求,你的cgi处理用户的请求参数,分析参数后以一定格式向缓存或者数据库取得需要返回的数据,包装取回的数据然后返回;最常见的写cgi设计也差不多,用户发起一个http请求,你的cgi处理用户的请求内容,分析内容后,向缓存或者数据库写入,然后返回成功或者失败。
    接下来的场景一般是这样的:你部署很多个处理这种cgi的进程,在前端经dns再经nginx进行负载均衡。
    对于这样的cgi设计来说,它是没有状态的,一个任意的用户发起请求后,由哪一个进程进行处理往往是不可预估的(也不需要预估)。
    在这样的cgi中,我们往往脱离了对内存的精确控制,我们设计的cgi,流进来的数据往往在程序内存中只存在了个瞬间,然后要么进了缓存,要么进了数据库,流出去的数据也同理,在给传输给用户之间,也只在你的程序内存空间中存在了一瞬。
    然后我们的性能提高就是通过使用更多的缓存(可以是memcache或者redis),增加缓存的命中率来提高,也就是说通过把数据更快的从别的地方读入你的程序内存空间来提高我们cgi的性能。
    是的,我们一般都这么做,这样的简单无状态设计,让我们的程序都能高度简单化,也不存在其他复杂的问题。
    但是,这样的设计真的是万金油么?我们为什么每次都需要把数据从别的地方读入我们的程序空间呢?为什么数据就不能一直存在于我们的程序空间中,来等待随时的读取?缓存是我们提升性能的利器,但为什么我们需要一个外部的缓存,难道不能让我们的程序本身就是缓存么?
    最后一个问题的答案是,当然可以。而且我们在关键的性能相关的场合理应这么设计,作为后端开发,应该追求的是容量与性能的平衡,这其中的法宝就是对内存的精确使用,在内存中,对数据进行“连续性”的状态变迁。
    一旦我们进行这样的设计,可以把系统的响应缩短到最快(因为至少可以去掉读取数据的外部依赖环节,比如缓存或者数据库)。那么,哪些场景需要这样的设计呢,比如活跃用户的消息通知(包括IM以及所有其他项目的用户消息通知功能),比如相同用户访问频率非常高的其他实时性强的功能等(热门列表,然后需要根据不同用户的访问记录去重并且记录访问)。
    那应该怎么做呢?假设我们拥有一台性能无限的机器,并且它永不当机,那么我们只需要在相同机器中创建一块无限大的内存空间,然后这块空间就可以容纳无限的用户,接收无限的用户数据,任何的变更都在这台机器的这块内存空间中处理,我们只需要在这台机器中写一个CGI程序即可完成我们需求。
    一台性能无限的机器显然是不存在的,但我们拥有多台性能有限的机器,而且在一定的时间段内,我们的用户数也是有限的。假设我们某个关键服务,服务每天活跃的100万用户,而每个用户的用户数据在内存中的体积大概在80K左右,那么一台16G内存的机器可以容纳40万左右的用户数,如果能充分利用资源,那么三台机器可以完成这个需求。
    显然我们需要一个符合以下这些需求的系统:
    1.利用上三台机器的资源,容纳下活跃用户。
    2.当活跃用户增长到500万的时候,系统可以通过简单地增加到五倍的机器数量来容纳下增加的用户。
    3.当我想请求一个确定的用户的数据的时候,系统能把请求准确地转发到该用户所在的机器上。
    回顾下刚才讲第一点,对于分布式简单概念的阐述,我们可以知道,这里,这个能满足需求的系统,就一定是一个分布式的系统。
    接下来的问题,就是讨论针对这么一个需求,怎么做分布式。
    我们假设我们有三台机,为了讨论方便,我们把代表一定的内存、CPU资源的空间作为一个节点,
    那么,假设我们有三个节点。看起来是这样的:

    AFA19BF2-FD23-4AA5-B70A-89B331B91FEC.png

    为了达到需求3的要求,我们还需要一个路由节点,这个路由节点来负责接入的逻辑,路由节点本身是不需要状态的,它用来承担短链接或者长链接的接入,用来决定输入的数据(某一个用户的请求)应该路由到哪一个确切的M节点。现在系统看起来是这样的:

    215AFEDA-9E05-4C60-90BD-3364B398EEF2.png

    对于R1来说,确定哪一个用户的请求到哪一台机器的策略,一般使用一致性哈希算法(注1)来解决,这是一个常见的算法,该算法的优点简单的说就是当M1到M3这三台机发生增加或者减少时,一致性哈希算法可以使得机器间数据迁移的影响降低到最小。
    说到机器的增加或者减少,这里就要引入一个问题,如何进行节点的识别感知?
    R1要跟M1 M2 M3通讯,那么它势必要知道系统中存在M1 M2 M3三个节点,而且在系统运行的过程中,R1还需要感知到M节点的加入或者退出。
    这里可以使用业界常用并且也久经考验的工具——zookeeper(注2)。zookeeper简单地说,就是负责存储数据(这里是节点信息,存什么取决了你的需要)并且把这些数据的变化通知给它的关注者们。
    zookeeper的概念非常类似linux的目录概念。基本的使用是这样的,首先你搭建一个zookeeper集群。
    对于M节点来说,当M节点启动的时候,通过zookeeper的Create方法,去注册自己的机器名到某个目录下,然后设置其数据(暂时设置为该M机器的端口号),这个Create方法需要带EPHEMERAL这个flag作为参数,这个flag的作用时,当M节点连接着zookeeper时创建的zookeeper节点存在,当M节点断开zookeeper连接时(可能是故障退出后者正常退出),M节点注册的zookeeper节点会自动删除。
    那么m1在zookpeer中注册了节点:/mycluster/m1,节点上的数据为m1的端口号。m2、m3同理。
    以上的描述很类似于,文件与文件内容的概念。
    对于R节点来说,在R节点的启动的时候,通过zookeeper的children方法带watch参数,来注册对目录 /mycluster 的监听,这样,当该目录下的节点变化时(M节点加入退出时),R节点就可以即时监听到变化,从而可以改变它对M节点们的路由策略了。
    那么加入了zookeeper之后,我们的分布式系统,就已经基本成型了。当接入压力比较大的时候,R节点也是可以多个的,R节点之间的负载平衡我们常用的方式是通过DNS+Nginx来负载,那么现在系统看起来是这样的:

    3354099A-8609-47D1-8003-08721EB59D32.png

    一切看起来都初具规模……等一等,那么万一有一个M节点当机了,怎么办呢?
    这里我们可以引入常见的主从方案来解决这个问题,并且通过zookeeper来自动完成主从的选择。
    我们引入 M11 M22 M33三个节点,然后让M1跟M11去zookeeper中注册一样的名字,这里我们设定该名字为V1,V1(V for virtual)就是代表M11跟M1这两个实际节点的虚拟节点,在zookeeper中可以增加Sequence Flag作为create的参数,这样两次创建同样的zookeeper节点名字就会带上序号,像这样:
    /mycluster/v10000000000 (假设是M1)
    /mycluster/v10000000001 (假设是M11)
    然后这次我们把M11 M1机器的机器地址跟端口作为这两个zookeeper节点的数据。
    当R节点发现这两个节点的时候,可以通过比较序号大小来决定谁是Master谁是Slave(我们可以简单的采取序号最小的为master的做法),从而确定不同的读写策略,而且现在同样的数据R节点可以采用简单的策略(双写)来确保Master与Slave的数据一致(如果需要更加严格的保证还需要其他的策略,此文不赘述)。
    然后,当M1 down的时候,它会退出/mycluster 目录,然后R节点会发现现在序号最小的v节点是v10000000001(即M11)了,那么现在R节点可以采取把它作为master的策略来分发数据了,也就成功实现了主从的自动切换(不间断服务)。
    好了,现在系统看起来像是这样:

    4F1B7859-7CE5-49ED-A848-E21D8864A15F.png

    当然不要忘了M节点们依然需要数据落地策略,只不过此时的数据落地就不需要是实时的(可以只由master节点进行或者只由slave节点进行)。
    本文基本上不会太涉及细节并且只描述了较为单一的场景,如果有兴趣有启发的可以自己多查资料或者与我讨论。

    注解:

    注1.一致性哈希(https://zh.wikipedia.org/wiki/%E4%B8%80%E8%87%B4%E5%93%88%E5%B8%8C
    注2. zookeeper(https://zookeeper.apache.org/

    相关文章

      网友评论

          本文标题:初探分布式之旅——几年前的一封邮件

          本文链接:https://www.haomeiwen.com/subject/ivykkttx.html