skynet 支持两种集群模式。master/slave和cluster。
为什么要cluster
除非你的业务本来就是偏重 IO 的,也就是你根本不打算利用单台硬件的多核心优势来增强计算力,抹平本机和网络的差异是没有意义的。无论硬件怎样发展,你都不可能看到主板上的总线带宽和 TCP 网络的带宽工作在同一数量级的那一天,因为这是物理基本规律决定的。
当你的业务需要高计算力,把 actor 放在一台机器上才可以正常的发挥 CPU 能力去合作;如果你的系统又需要分布式扩展,那么一定是有很多组独立无关的业务可以平行处理。这两类工作必须由构架系统的人自己想清楚,规划好怎么部署这些 actor ,而不可能随手把 actor 扔在分布式系统中,随便挑台硬件运行就够了。
恰巧网络游戏服务就是这种业务类型。多组服务器、多个游戏场景之间交互很弱,但其中的个体又需要很强的计算力。这就是 skynet 切合的应用场景。
同一进程下的服务通讯和跨网络的通讯到底有什么不同
1.进程内的内存是共享的,skynet 是用 lua 沙盒来隔离服务状态,但是可以通过 C 库来绕过沙盒直接沟通。如果一个服务生产了大量数据,想传给您一个服务消费,在同一进程下,是不必经过序列化过程,而只需要通过消息传递内存地址指针即可。这个优化存在 O(1) 和 O(n) 的性能差别,不可以无视。
2.同一进程内的服务从底层角度来说,是同生共死的。Lua 的沙盒可以确保业务错误能够被正确捕获,而非常规代码不可控的错误,比如断电、网络中断,不会破坏掉系统的一部分而另一部分正常工作。所以,如果两个 actor 你确定在同一进程内,那么你可以像写常规程序那样有一个共识:如果我这个 actor 可以正常工作,那么对端协作的另一个 actor 也一样在正常工作。就等同于,我这个函数在运行,我当然可以放心的调用进程内的另一个函数,你不会担心调用函数不存在,也不会担心它永远不返回或是收不到你的调用。这也是为什么我们不必为同一进程内的服务间 RPC 设计超时的机制。不用考虑对方不相应你的情况,可以极大的简化编写程序的人的心智负担。比如,常规程序中,就没有(非 IO 处理的)程序库的 API 会在调用接口上提供一个超时参数。
3.同一进程内所有服务间的通讯公平共享了同一内存总线的带宽。这个带宽很大,和 CPU 的处理速度是匹配的。可以基本不考虑正常业务下的服务过载问题。也就是说,大部分情况下,一个服务能生产数据的速度不太会超过另一个服务能消费数据的速度。这种情况会造成消费数据的服务过载,是我们使用 skynet 框架这几年来 bug 出现最多的类型。而跨越网络时,不仅会因为生产速度和消费速度不匹配造成过载,更会因为传递数据的带宽和生产速度不匹配而过载。如果让开发者时刻去考虑,这些数据是投递到本地、那些数据是投递到网络,那么已经违背了抹平本地和网络差异这点设计初衷。
master/slave 模式(局域网)
当单台机器的处理能力达到极限后,可以考虑通过内置的 master/slave 机制来扩展。 master与每个slave相连,每个slave又两两互连。master同时会充当一个中心节点的作用,用来协调各个slave的工作。
每个 skynet 进程都是一个 slave 节点。但其中一个 slave 节点可以通过配置 standalone 来多启动一个 cmaster 服务,用来协调 slave 组网。对于每个 slave 节点,都内置一个 harbor 服务用于和其它 slave 节点通讯。在 master/slave 模式中,节点内的消息通讯和节点间的通讯是透明的。skynet 核心会根据目的地址的 harbor id 来决定是直接投递消息,还是把消息转发给 harbor 服务。
这种模式的缺点也非常明显:它被设计为对单台物理机计算能力不足情况下的补充。所以忽略了系统一部分故障的处理机制,而把整个网络视为一体。即,整个网络中任意一个节点都必须正常工作,节点间的联系也不可断开。这就好比你一台物理机上如果插了多块 CPU ,任意一个损坏都会导致整台机器不能正常工作一样。所以,不要把这个模式用于跨机房的组网。所有 slave 节点都应该在同一局域网内(最好在同一交换机下)。不应该把系统设计成可以任意上线或下线 slave 的模式。slave 的组网机制也限制了这一点。如果一个 slave 意外退出网络,这个 harbor id 就被废弃,不可再使用。这样是为了防止网络中其它服务还持有这个断开的 slave 上的服务地址;而一个新的进程以相同的 harbor id 接入时,是无法保证旧地址和新地址不重复的。
master/slave最大的缺点在于不能很好的处理某个节点异常断开的情况。底层的 harbor 假设机器间是可靠连接,不会断开。而一旦内部网络不健康,很可能会导致整个系统无法正常工作。它的设计目的并不是为了提供弹性扩展的分布式方案,而是为了突破单机性能上限的问题。
cluster 模式
skynet 提供了更具弹性的集群方案。它可以和 master/slave 共存。也就是说,你可以部署多组 master/slave 网络,然后再用 cluster 将它们联系起来。
它的工作原理是这样的:
在每个 skynet 节点(单个进程)内,启动一个叫 clusterd 的服务。所有需要跨进程的消息投递都先把消息投递到这个服务上,再由它来转发到网络。
Cluster 是去中心化的,所以需要在每台机器上都放置一份配置文件(通常是相同的)。通过调用 cluster.reload 可以让本进程重新加载配置。如果你修改了每个节点名字对应的地址,那么 reload 之后的请求都会发到新的地址。而之前没有收到回应的请求还是会在老地址上等待。如果你老的地址已经无效(通常是主动关闭了进程)那么请求方会收到一个错误。
这种做法要求明确本地服务的调用和远程调用的区别。虽然远程调用的性能可能略低,但由于不像底层 harbor 那样把本地、远程服务的区别透明化,反倒不容易出问题。且 tcp 连接使用了更健壮的 socketchannel ,一旦连接断开,发起 rpc 的一方会收到异常,也可以重试(自动重连)。
Toda cuestion tiene dos puntos de vista: El equivocado y el nuestro.