Parameter Server 资料汇总
parameter server 介绍
链接:https://www.zhihu.com/question/26998075/answer/40577680
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
后面有看到微软研究院 project Adam的论文,大体思路比较相似,但论文中细节比较丰富,也会互补的一些信息描述下。
概念:
参数服务器是个编程框架,用于方便分布式并行程序的编写,其中重点是对大规模参数的分布式存储和协同的支持。
工业界需要训练大型的机器学习模型,一些广泛使用的特定的模型在规模上的两个特点:
1. 参数很大,超过单个机器的容纳能力(比如大型Logistic Regression和神经网络)
2. 训练数据巨大,需要分布式并行提速(大数据)
这种需求下,当前类似MapReduce的框架并不能很好适合。
因此需要自己实现分布式并行程序,其实在Hadoop出来之前,对于大规模数据的处理,都需要自己写分布式的程序(MPI)。 之后这方面的工作流程被Google的工程师总结和抽象成MapReduce框架,大一统了。
参数服务器就类似于MapReduce,是大规模机器学习在不断使用过程中,抽象出来的框架之一。重点支持的就是参数的分布式,毕竟巨大的模型其实就是巨大的参数。
Parameter Server(Mli)
—————————-
架构:
集群中的节点可以分为计算节点和参数服务节点两种。其中,计算节点负责对分配到自己本地的训练数据(块)计算学习,并更新对应的参数;参数服务节点采用分布式存储的方式,各自存储全局参数的一部分,并作为服务方接受计算节点的参数查询和更新请求。
简而言之吧,计算节点负责干活和更新参数,参数服务节点则负责存储参数。
冗余和恢复:
类似MapReduce,每个参数在参数服务器的集群中都在多个不同节点上备份(3个也是极好的),这样当出现节点失效时,冗余的参数依旧能够保证服务的有效性。当有新的节点插入时,把原先失效节点的参数从冗余参数那边复制过来,失效节点的接班人就加入队伍了。
并行计算:
并行计算这部分主要在计算节点上进行。 类似于MapReduce,分配任务时,会将数据拆分给每个worker节点。
参数服务器在开始学习前,也会把大规模的训练数据拆分到每个计算节点上。单个计算节点就对本地数据进行学习就可以了。学习完毕再把参数的更新梯度上传给对应的参数服务节点进行更新。
详细的流程:
1.
分发训练数据 -> 节点1
节点2
节点3
…
节点i
…
节点N
2.
节点i 学习过程:
遍历本地的训练数据,统计所有需要的参数(key)
向分布式的参数服务器查询需要的参数(注意,本地数据对应到的参数只是全局参数的一小部分)
得到查询到的参数值,用于模型的本地训练
一轮训练完毕,得到所有对应参数的更新,将更新上传给参数服务器
3.
参数服务器更新参数过程:
参数服务器得到计算节点传过来的局部更新,汇总后更新本地数据
一些细节还是看李沐的文章吧,这个模型相对其他的一些参数服务器的框架是最简单直观的。
Project Adam
——————
微软研究院的一个专门用于deep learning的参数服务器,很厉害的样子。用了很多trick来提升性能和容纳更大的模型和数据,当然精度也随之上升了很多。
大体的架构和Parameter Server(Mli)的比较相似,但论文里面包含了比较丰富的细节,可以跟之前的内容互补一下。
相似点
- 同样拆分为计算节点(worker)和参数服务节点(parameter server),参数分布式存储
- 均采用冗余,每个参数会保存3份,当某参数对应的服务节点失效,服务会转到该参数对应的部分节点上(2选1)
其他点
Parameter Server 构成
参数服务器中,分布式存储参数的部分,包含两类节点:
Parameter Server(PS): 这类节点负责具体参数的分布式存储
Parameter Server Controller(PSC): 这类节点负责路由和冗余容灾和失效恢复等操作
换句话说,PSC是PS的管理者。 Adam中节点控制的灵活性全在PSC中。
路由机制
project Adam中使用了路由来确定每个参数的存储位置。
路由信息由PSC(其实是多个一致的节点来支撑整个集群的工作)。
计算节点在每次访问和更新参数前,先向PSC询问最新的路由信息,然后将参数按目标位置拆分发送。
而当PS中出现节点变化(节点失效或者添加,类似MapReduce,PSC也会定期PING每个PS来确认节点有效状态),PSC会修改路由,并提供最新的路由信息给计算节点。
冗余机制
前面说到,每个参数会在PS集群中有三个副本,存储在不同的节点上来实现冗余。
其中一个节点会被选为primary,来提供针对某个参数的服务。
当这个节点失效时,另外两个副本中会被挑选出一个作为新的primary,来继续此参数的服务。 这是容灾的第一步
恢复机制
前面说到,冗余机制是容灾第一步。
长久之计是将目前状态恢复到失效之前(目前该参数只有两个副本,需要恢复到3个),这就需要PSC的控制,将失效节点上的参数拆分到其他active的节点上备份(如此就恢复成了三个副本)
当然,如果有新节点的加入,也需要在PSC注册登记,之后PSC可以分配部分参数过去并修改路由。
有趣的trick
- 单机上,使用指针传递来避免内存复制
- 集群中通信,单个机器在传递多个进程的信息传递请求前,会通过相同的发送目标节点来归纳这些消息,之后一并发送请求。
- 无锁写入。这个也许会带来写冲突,但是概率比较低,而且深度神经网络天生可以忍受噪音
更多的信息可以搜索 project Adam,论文里面细节比较容易懂,还有一段录像讲解。
一个简单的实现:SwiftSnails
—————————————————
这是我写一个小的参数服务器,比较简单,代码也比较少。 没有用MPI,自己实现路由分发啥的,有种解放前小米加步枪的感觉。里面有个Word2Vec的分布式实现作为Demo,效率还是不错的。 有兴趣的可以看看 。
项目地址: SwiftSnails by Superjom
Reference:
Parameter Server for Distributed Machine Learning
http://parameterserver.org/
Project Adam: Building an Efficient and Scalable Deep Learning Training System
Parameter Server 优点:
链接:https://www.zhihu.com/question/26998075/answer/82547092
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1. Efficient communication:
由于是异步的通信,因此,不需要停下来等一些机器执行完一个iteration(除非有必要),这大大减少了延时。为机器学习任务做了一些优化(后续会细讲),能够大大减少网络流量和开销;
2. Flexible consistency models:
宽松的一致性要求进一步减少了同步的成本和延时。parameter server 允许算法设计者根据自身的情况来做算法收敛速度和系统性能之间的trade-off。
3. Elastic Scalability:
使用了一个分布式hash表使得新的server节点可以随时动态的插入到集合中;因此,新增一个节点不需要重新运行系统。
4. Fault Tolerance and Durability:
我们都知道,节点故障是不可避免的,特别是在大规模商用服务器集群中。从非灾难性机器故障中恢复,只需要1秒,而且不需要中断计算。Vector clocks 保证了经历故障之后还是能运行良好;
5. Ease of Use
全局共享的参数可以被表示成各种形式:vector,matrices 或者相应的sparse类型,这大大方便了机器学习算法的开发。并且提供的线性代数的数据类型都具有高性能的多线程库。
Parameter Server 详解
本博客仅为作者记录笔记之用,不免有很多细节不对之处。
还望各位看官能够见谅,欢迎批评指正。
更多相关博客请猛戳:http://blog.csdn.net/cyh_24
如需转载,请附上本文链接:http://blog.csdn.net/cyh_24/article/details/50545780
MXNet 是李沐和陈天奇等各路英雄豪杰打造的开源深度学习框架(最近不能更火了),其中最吸引我的是它的分布式训练的特性;而提供支持其分布式训练特性的正是当年李少帅和 Alex Smola 等人开发的 parameter server.
本博客以“Scaling Distributed Machine Learning with the Parameter Server” 为主,从易用性、通信高效性、可扩展性等角度介绍 parameter server .
Background
在机器学习和深度学习领域,分布式的优化已经成了一种先决条件。因为单机已经解决不了目前快速增长的数据和参数了。
现实中,训练数据的数量可能达到1TB到1PB之间,而训练过程中的参数可能会达到109到1012。而往往这些模型的参数需要被所有的worker节点频繁的访问,这就会带来很多问题和挑战:
- 访问这些巨量的参数,需要大量的网络带宽支持;
- 很多机器学习算法都是连续型的,只有上一次迭代完成(各个worker都完成)之后,才能进行下一次迭代,这就导致了如果机器之间性能差距大(木桶理论),就会造成性能的极大损失;
- 在分布式中,容错能力是非常重要的。很多情况下,算法都是部署到云环境中的(这种环境下,机器是不可靠的,并且job也是有可能被抢占的);
Related Work
对于机器学习分布式优化,有很多大公司在做了,包括:Amazon,Baidu,Facebook,Google,Microsoft 和 Yahoo。也有一些开源的项目,比如:YahooLDA 和 Petuum 和Graphlab。 总结一下:
李少帅的这个ParameterServer 属于第三代的parameter server。
第一代 parameter server:缺少灵活性和性能 —— 仅使用memcached(key, value) 键值对存储作为同步机制。 YahooLDA 通过改进这个机制,增加了一个专门的服务器,提供用户能够自定义的更新操作(set, get, update)。
第二代 parameter server:用bounded delay模型来改进YahooLDA,但是却进一步限制了worker线程模型。
第三代 parameter server 能够解决这些局限性。
首先来比较一下parameter server 跟通用的分布式系统之间的差别吧。
通用的分布式系统通常都是:每次迭代都强制同步,通常在几十个节点上,它们的性能可以表现的很好,但是在大规模集群中,这样的每次迭代强制同步的机制会因为木桶效应变得很慢。
Mahout 基于 Hadoop,MLI 基于 Spark,它们(Spark与MLI)采用的都是 Iterative MapReduce 的架构。它们能够保持迭代之间的状态,并且执行策略也更加优化了。但是,由于这两种方法都采用同步迭代的通信方式,使得它们很容易因为个别机器的低性能导致全局性能的降低。
直观感受一下,当其中一个节点运行时间过长会发生什么:
为了解决这个问题,Graphlab 采用图形抽象的方式进行异步调度通信。但是它缺少了以 MapReduce 为基础架构的弹性扩展性,并且它使用粗粒度的snapshots来进行恢复,这两点都会阻碍到可扩展性。parameter server 正是吸取Graphlab异步机制的优势,并且解决了其在可扩展性方面的劣势。
看看异步迭代是如何提高性能的:
Parameter Server 优势
说完了其他的分布式系统的缺点,该回到本博客的主题了(夸ps),parameter server 有哪些features?
1. Efficient communication:
由于是异步的通信,因此,不需要停下来等一些机器执行完一个iteration(除非有必要),这大大减少了延时。为机器学习任务做了一些优化(后续会细讲),能够大大减少网络流量和开销;2. Flexible consistency models:
宽松的一致性要求进一步减少了同步的成本和延时。parameter server 允许算法设计者根据自身的情况来做算法收敛速度和系统性能之间的trade-off。3. Elastic Scalability:
使用了一个分布式hash表使得新的server节点可以随时动态的插入到集合中;因此,新增一个节点不需要重新运行系统。4. Fault Tolerance and Durability:
我们都知道,节点故障是不可避免的,特别是在大规模商用服务器集群中。从非灾难性机器故障中恢复,只需要1秒,而且不需要中断计算。Vector clocks 保证了经历故障之后还是能运行良好;5. Ease of Use
全局共享的参数可以被表示成各种形式:vector,matrices 或者相应的sparse类型,这大大方便了机器学习算法的开发。并且提供的线性代数的数据类型都具有高性能的多线程库。
Parameter Server 系统架构
在parameter server中,每个 server 实际上都只负责分到的部分参数(servers共同维持一个全局的共享参数),而每个 work 也只分到部分数据和处理任务;
上图中,每个子节点都只维护自己分配到的参数(图中的黑色),自己部分更新之后,将计算结果(例如:梯度)传回到主节点,进行全局的更新(比如平均操作之类的),主节点再向子节点传送新的参数;
servers 与 workers 之间的通信如下:
server 节点可以跟其他 server 节点通信,每个server负责自己分到的参数,server group 共同维持所有参数的更新。
server manager node 负责维护一些元数据的一致性,比如各个节点的状态,参数的分配情况等;
worker 节点之间没有通信,只跟自己对应的server进行通信。每个worker group有一个task scheduler,负责向worker分配任务,并且监控worker的运行情况。当有新的worker加入或者退出,task scheduler 负责重新分配任务。
(key, value),Range Push and Pull
parameter server 中,参数都是可以被表示成(key, value)的集合,比如一个最小化损失函数的问题,key就是feature ID,而value就是它的权值。对于稀疏参数,不存在的key,就可以认为是0.
把参数表示成k-v, 形式更自然, 易于理,更易于编程解;
workers 跟 servers 之间通过 push 跟 pull 来通信。worker 通过 push 将计算好的梯度发送到server,然后通过 pull 从server更新参数。为了提高计算性能和带宽效率,parameter server 允许用户使用 Range Push 跟 Range Pull 操作;
假设 R 是需要push或pull的 key 的range,那么可以进行如下操作:
1 2 |
w.push(R, dest) w.pull(R, dest) |
- 1
- 2
意思应该很明显吧,就是发送和接送特定Range中的w.
Asynchronous Tasks and Dependency
体会一下Asynchronous Task 跟 Synchronous Task 的区别:
如果 iter1 需要在 iter0 computation,push 跟 pull 都完成后才能开始,那么就是Synchronous,反之就是Asynchronous.
Asynchronous Task:能够提高系统的效率(因为节省了很多等待的过程),但是,它的缺点就是容易降低算法的收敛速率;
所以,系统性能跟算法收敛速率之间是存在一个trade-off的,你需要同时考虑:
- 算法对于参数非一致性的敏感度;
- 训练数据特征之间的关联度;
- 硬盘的存储容量;
考虑到用户使用的时候会有不同的情况,parameter server 为用户提供了多种任务依赖方式:
Sequential: 这里其实是 synchronous task,任务之间是有顺序的,只有上一个任务完成,才能开始下一个任务;
Eventual: 跟 sequential 相反,所有任务之间没有顺序,各自独立完成自己的任务,
Bounded Delay: 这是sequential 跟 eventual 之间的trade-off,可以设置一个 τ 作为最大的延时时间。也就是说,只有 >τ 之前的任务都被完成了,才能开始一个新的任务;极端的情况:
τ=0,情况就是 Sequential;
τ=∞,情况就是 Eventual;
看一个bounded delay 的 PGD (proximal gradient descent)算法的系统运行流程:
注意:τ 不是越大越好的,具体要取多大,得看具体情况,贴一张李少帅做的实验(Ad click prediction):
Implementation
servers 使用 一致性hash 来存储参数k-v键值对。用链式复制方式来提高系统容错性。使用 range based communication 和 range based vector clocks 来进一步优化系统;
Vector Clock
parameter server 使用 vector clock 来记录每个节点中参数的时间戳,能够用来跟踪状态或避免数据的重复发送。但是,假设有n个节点,m个参数,那么vector clock的空间复杂度就是O(n∗m). 显然,当有几千个节点和几十亿的参数时,对于内存和带宽来说都是不可实现的。
好在,parameter server 在push跟pull的时候,都是rang-based,这就带来了一个好处:这个range里面的参数共享的是同一个时间戳,这显然可以大大降低了空间复杂度。
每次从一个range里再提取一个range,最多会生成3个新的 vector clocks(一分为三) . 假设k是算法中产生的所有的range,那么空间复杂度就变成了O(k∗m),因为k 远小于参数个数,所以空间复杂度大大降低了;
Messages
一条 message 包括:时间戳,len(range)对k-v.
这是parameter server 中最基本的通信格式,不仅仅是共享的参数才有,task 的message也是这样的格式,只要把这里的(key, value) 改成 (task ID, 参数/返回值)。
由于机器学习问题通常都需要很高的网络带宽,因此信息的压缩是必须的。
key的压缩: 因为训练数据通常在分配之后都不会发生改变,因此worker没有必要每次都发送相同的key,只需要接收方在第一次接收的时候缓存起来就行了。第二次,worker不再需要同时发送key和value,只需要发送value 和 key list的hash就行。这样瞬间减少了一般的通信量。
value的压缩: 假设参数时稀疏的,那么就会有大量的0存在。因此,为了进一步压缩,我们只需要发送非0值。parameter server使用 Snappy 快速压缩库来压缩数据、高效去除0值。
key 的压缩和 value 的压缩可以同时进行。
用户自定义过滤:
对于机器学习优化问题比如梯度下降来说,并不是每次计算的梯度对于最终优化都是有价值的,用户可以通过自定义的规则过滤一些不必要的传送,再进一步压缩带宽cost:
1. 发送很小的梯度值是低效的:
因此可以自定义设置,只在梯度值较大的时候发送;
2. 更新接近最优情况的值是低效的:
因此,只在非最优的情况下发送,可通过KKT来判断;
Replication and Consistency
parameter server 在数据一致性上,使用的是传统的一致性哈希算法,参数key与server node id被插入到一个hash ring中。
具体的一致性hash算法不是本文的重点,这里不过多介绍了,不清楚的同学建议看看其他文献熟悉一下。
只要知道它的作用是在分布式系统中,动态增加和移除节点的同时还能保证系统存储与key分配的性能效率;
从上图可以看出,每个节点都复制了它逆时钟方向的k个节点中的key。图中,k=2,S1 赋值了 S2 和 S3 内的key。
两种方式保证slave跟master之间的数据一致性:
1.默认的复制方式: Chain replication (强一致性, 可靠):
a. 更新:只能发生在数据头节点,然后更新逐步后移,直到更新到达尾节点,并由尾节点向客户确认更新成功;
b. 查询:为保证强一致性,客户查询只能在尾节点进行;
2.Replication after Aggregation:
两个worker 节点分别向server传送x和y。server 首先通过一定方式(如:f(x+y) )进行aggregate,然后再进行复制操作;
当有n个worker的时候,复制只需要k/n的带宽。通常来说,k(复制次数)是一个很小的常数,而n的值大概是几百到几千;
Server Management
要想实现系统的容错以及动态的扩展系统规模,必须要求系统能够支持动态添加和移除节点。
当有一个 server节点添加 进来的时候会发生什么呢?
1. server manager 会对新的节点分配一些range 的key,这会造成其他server节点的key的变化;
2. 新节点会获取数据做为训练用,另外会复制k份到slave。
3. server manager 将节点的变化情况广播出去。接收方可能会移除不再属于自己的数据,并且将未完成的任务提交给新来的节点;当有一个 worker节点(W)添加 进来的时候会发生什么呢? 跟server差不多,相对更简单一些:
1. task scheduler 为W分配数据;
2. 这个 worker 节点通过网络或者文件系统得到分配到的训练数据。接着,W会从服务器pull参数;
3. task scheduler 会广播节点的变化情况,可能会使一些节点释放一部分训练数据;
参考文献
【1】Mu Li. Scaling Distributed Machine Learning with the Parameter Server.
【2】CMU. http://parameterserver.org/
【3】Joseph E.Gonzalez. Emerging Systems For Large-scale Machine Learning.
首先还是要声明一下,这个文章是我在入职阿里云1个月以来,对于分布式计算的一点肤浅的认识,可能有些地方不够妥善,还请看官可以指出不足的地方,共同进步。
一.背景
随着互联网的发展,数据量的增大,很多对于数据的处理工作(例如一些推荐系统、广告推送等)都迁移到了云端,也就是分布式计算系统上。衍生了很多牛逼的分布式计算的计算模型,比较著名的就是MapReduce、MPI、BSP等。后来也产生了一些分布式计算系统,大家耳熟能详的Hadoop就是基于MapReduce实现的。
本文的主人公是Parameter Server,其实也不算是新宠了,这个模型已经被提出好几年了,只不过在国内还不是特别热。不过最近一些云服务巨头们开始了对于PS的深入开发和研究。
引用一位算法大神的话简单描述下什么事Parameter Server:总结是一种计算模型SSP+一种分布式设计看板模式Client+Server(partitioned table)+基于算法的调度策略(Scheduler)。可能有些同学还不太理解这句话,没关系,下面通过一个实例来介绍一下PS。
二.场景
因为我在学习PS的过程中是对照Map Reduce来学习的。所以也通过一个机器学习算法的并行计算的实例,来比较Map Reduce和PS。为了更好地突出PS的优势,这里用到的算法是一个梯度逼近最佳结果的一种算法-逻辑回归(Logical Regression)。
- 为了更好地帮大家理解这些内容,我也罗列了一些必须的知识储备:
- 1.逻辑回归算法-最好fork里面的代码看一下
2.随机梯度下降SGD
3.李沐大神实现的一个PS开源库,上面有一个论文,一定要读
4.并行逻辑回归-等会会借用里面的内容来讲
5.ps开源代码网站
三.Work Flow
首先还是要补充几句,Map-Reduce在实现并行算法的过程中有它的优势,但是也有很大的弊端,它在处理梯度问题上没有很好的效率。这一点PS通过client+server的模式很好的解决了这个问题。
1.Map-Reduce处理LR
首先来看下Map-Reduce是如何解决逻辑回归(下文统一称为LR)的。首先是map的过程,将很大的数据切割成key-value的形式,我们在这里假设所有的数据都是稠密的。比如说你有100行数据,切割成5份,那么每一个worker就处理其中的20行数据。Reduce主要是负责统一worker的计算结果。下面具体到LR的算法实现来讲解下Map-Reduce的过程。
先来看看整体的流程图:
第一步:首先是进行map阶段对于长尾数据的分割,我们假设数据是稠密非稀疏的。逻辑回归的并行计算的数据分割,可以按行分、按列分或者行列一起分。分好的数据通过key-value的形式传到每一个worker中,对应上图的map phase阶段的worker。当然,map里也包含LR的计算逻辑,逻辑请大家看上面的资料自己学习下。分割图如下:
第二步:利用随机梯度(SGD)方法逼近最优解,在凸函数中LR是可以无限接近最优模型的,可以通过限定循环次数和收敛条件来实现。这其中就有一个问题,认真研究LR的同学可能会发现,如果我们使用SGD的话,因为worker之间虽然有一定的通信机制,但是并不是实时同步的,所以每一个worker并不知道对方的梯度是多少,形象的描述一下就是我们可以把SGD看成一个下坡问题。
每个worker都在往终点方向下山(收敛模型),但是它们彼此间并不能实时协作,也就是说A不知道B爬到哪里,C不知道A爬到哪里。传入一个路径,我就接着向下爬一点,可能会走重复的路径。所以说Map-Reduce的SGD是一种范围的梯度。每个worker不一定一直往下走,可能走走停停甚至往后走一点,但是因为数据量巨大总是可以走到终点的。 但是这样就会浪费了很多效率,这也就是Parameter Server重点解决的问题。
第三步:负责reduce的服务器统一出一个模型输出。
2.Parameter Server的一些机制
下面我们看下Parameter Server是怎么解决这个问题。首先看下PS的总体架构,PS是由client和server组成的,client对应于上文的worker,负责计算。server是负责统一所有的client它们的参数,server间是联通的。
如下图:
总体来看,PS的优势是通过server来协同client的输出,如上一节的下山问题,PS可以协同每一个client按照一个方向直线下山,从而提高了效率。而这其中也有很多的技术细节需要考虑。
1).并行化设计
PS可以运用很多并行化的思想从而提高效率。
(1)首先在client端,计算和上传数据是采用的多线程机制,计算和数据传输在不同的线程中进行从而增加了效率。同时server并不是等待所有参数都上传完成,才向下分发的。如果一个client_a计算比较慢,server可以暂时不采用client_a的数据,而采用历史数据。
(2)数据上传也可以用树状结构代替直接上传,在client和server之间增加一层树状结构可以提高数据传输效率,节约server的处理资源。可以从下图的左边,变为右边。
2).pull和push机制
首先,是在client端应该上传怎样的数据,因为每个client节点都会不停的接受和反馈数据给server,那么到底应该push怎样的数据上去呢?这个一般来讲是选择步长最长的参数,也就是最大的梯度值的参数push上去。
3).server端的异构形式
因为每个client只处理一部分参数,server端需要将这些参数拼接起来,所以server端是一个异构的组成形式。
3.Parameter Server处理LR
上面讲了很多PS的机制,这里具体说一下PS怎么实现LR。因为LR的输出是一个线性的回归模型。输出的结果是下面的这种式子:
z=w1*x1+w2*x2…..+w10*x2+….
我们要求的是里面的w1,w2,w3….这些参数,在PS中每个client计算的是其中的某些△w。通过server将这些△w同步上去,然后再push下去继续迭代计算。这样的好处是对于梯度问题,每个client可以沿着一个方向走。
后话:我的理解还很浅,具体实现还有非常多的技术细节要敲定,部署在集群上也会出现各种问题,如:log怎么输出,有的client挂了怎么办等等。建议有空可以看下李沐的开源项目的代码,还有上面提到的一些文档。