Skip to content

NGX_CC v2 的新功能

aimingoo edited this page Aug 15, 2015 · 2 revisions

NGX_CC v2 的新功能

除了一些小的BUG修正之外,ngx_cc v2.0.0主要实现了如下四个方面的特性:

  • 动态节点迁移
  • 结点失效检查
  • 更加友好的多通道支持
  • 实现与N4C框架的集成

这些特性主要用于构建一个高性能的通信集群,确保在不停机的前提下应对系统在线变化。

其它的变更细节参见:https://github.com/aimingoo/ngx_cc#history

如何实现动态节点迁移

缺省情况下,clients将自己的"ip:port"登记到super的列表中,然而一旦super node出了问题,例如说它要update,那么怎么办呢?

在v1.0.0中只提供了简单的心跳机制来解决类似问题。在上面这个例子中,由于clients总是用心跳来将自己报告(reportHubPort)给super,所以一旦super 恢复,那么至多在一个心跳周期中,就能收集到全部clients的报告,并重新得到clients列表。进一步的,ngx_cc.cc()在'clients'方向上的通讯也就恢复了。

然而,自super失效开始,直到所有clients完成report之前,所有的通讯都丢失了。而且,一旦super不可恢复,那么clients将没有任何可用的super。

v2.x中的“动态节点迁移”就是用来解决这个问题的。

从super迁出clients

一旦我们认为super需要shutdown,那么我们将所有的clients迁移到另一台服务器上去即可。需要的时候,我们还可以将它迁回来。而且,有了这样的迁移能力,我们也就可以将任意一组clients迁到某个其它的结点之下(作为clients),这样一来,事实上我们就可以得到“任意层级的”super/clients集群。例如node1 ~ 8以node0为root,我们可以将node2 ~ 8迁移给node1作为clients,那么

  • node0将只有剩下一个client,即node1;
  • node1有了7个clients;
  • 整个的集群从二层变成了三层。

ngx_cc.transfer()用于实现这种“从super通知所有clients”的迁移:

function ngx_cc.transfer(super, channels, clients)

其中super用于指定新super的'host:port',而clients指从当前super中迁出的clients的ip列表(使用","分隔,用"*"表示全部clients)。你也可以指定channels,这样也可以将clients分批迁出到不同的super。

在clients上面迁移/切换super的

然而,可能在你打算做这件事之前,super就彻底挂掉了。你想做的事情,不过是将clients的supert host:port改改,避免发向super的消息漏掉。

好在ngx_cc也样提供route.transfer()。它与ngx_cc.transfer()的不同在于,route.transfer()是从客户端上发起调用的,它不通知super,而仅仅是维护自己的super配置。事实上,如果是从super来起发起ngx_cc.transfer(),super也会负责向所有指定的clients发一次请求,以便它们能invoke各自的route.transfer()。

换言之,route.transfer()是ngx_cc.transfer()中的一个步骤,但只有当super失效时,才有必要从clients自行发起调用。

route.transfer()的接口与ngx_cc.transfer()并不相同:

function route.transfer(super)
function route.transfer(host, port)

你可以用'host:port'形式的字符串来指定你试图迁移到的新的super,或者分别指定host与port参数(当port缺省时,它默认为'80')。

如果没有super,那么该结点被视为root

ngx_cx没有严格意义上的“没有super”。这样情况下,应该设置super为当前的masterHost:hubPort。

尽管masterHost通常是127.0.0.1,但由于当前nginx结点可能存在内部通讯与外部通讯地址不一致的情况,所以我们不能确切地说masterHost一定就是这个值(localhost/127.0.0.1)。这取决于你在nginx.conf中的实际配置。

hubPort也与nginx.conf中的配置有关,该值是指listen配置的值(不是per_worker配置)。

所以将super指向masterHost:hubPort指的就是当前结点,也就意味着没有super。这种情况下,该结点也被视为root,而route.isRoot()调用也就将返回true。

由于ngx_cc存在多通道。所以在不同通道上的route.isRoot()返回值不一定是相同的。其中的检查方法之一,就是调用invoke?getServiceStat。如下示例,是在控制台上检测demo通道上的服务信息:

> curl -s 'http://localhost/demo/invoke?getServiceStat&selfOnly'
{"clients":{},"routePort":"8013","service":"127.0.0.1:80","ports":"8010,8012,8011,8013","super":"127.0.0.1:80"}

当service与super信息一致时,这个结点就是root了。

  1. 如果去掉selfOnly参数,则将深度遍历所有clients,返回整个集群的信息。

  2. 需要该通道加载了module/invoke.lua模块。

结点失效检查

在集群中,并不是一个request从node得到的返回是fail,就能表明它失效的。集群中的结点或整个集群的失效的原因及其检测都是很复杂的,而ngx_cc简化了这一过程。

ngx_cc v2.x提出了两种(或者说三种)失效检查方法。这两种都是基于经验 性的、常用的规则:

  1. 如果结点在一定时间(Y seconds)内失效了多次(X times),则视为该结点失效。

  2. 如果结点在连续失效了多次(X times),则视为该结点失效;其中,所谓连续,是指两次失效之间的时间间隔不超过设定值(maxContinuousInterval)。

当ngx_cc在workers/clients方向上通讯时,如果某次请求失效,则ngx_cc会主动地向当前routePort结点('master'方向)报告;而rotePort中会invoke这个报告,如果满足上述失效检查的两条规则之一,则目标结点被确认为失效。负责响应这个报告的是module/invlid.lua模块(ngx_cc.lua只负责报告失效,而如何响应是invlid.lua的事)。

ngx_cc中并没有在super/master方向失效时发报告。对于前者,是因为ngx_cc默认认为如果super失效,则在一定时间后会恢复(ngx_cc v1的心跳模块),或者应当由集群运维来决定如何迁移到新的super结点(ngx_cc v2的transfer功能)。所以当super方向发生持续的15次失效时,就会将失效信息打入nginx error log,而不再做其它特殊的处理。

对于后者,由于ngx_cc的失效处理依赖于master/routerPort有效。所以当master方向也失效时,就只能记nginx error log了。

最后,如果在self/remote方向上出现失效,ngx_cc不做任何特殊处理。正常情况下,如果目标调用返回错误信息,则应该由调用者来决定如何处理。——换言之,它们并不被视为ngx_cc的集群结点。

关于连续失效(Continuous Invalid)

在一个支持并发请求的集群中,如何理解“连续失效(Continuous Invalid)”呢?按上述的规则:

两次失效之间的时间间隔不超过设定值(maxContinuousInterval),即为连续失效

很有可能是不合理的。例如对于1分钟1次的心跳,那上述的连续失效规则就失效了。——换言之,任何时长间隔的“连续”在这个问题上都是不靠谱的。

所以ngx_cc提供了另外一种机制来识别“连续失效”。即,如果发送的失效报告中带有特定参数,则该次report视为连续的。

在系统缺省环境中,这个url参数名为't',并作为isContinuous参数传入Valider:invalid()调用

这时,来发起报告的源中,就可以自行决定“怎样才算是一次连续失效”。于是,在ngx_cc中就专门有一个invalid对象,用于记录当前方向是否“连续”(以及连续失效次数),并在调用invalidWorker/invalidClient时将该值作为url参数传过去。

采用这一策略的另一原因在于:workers/clients方向上的实际上是随机发生在多个worker processes/ports之一上的,需要使用isContinuous这样的标志来在并行环境中做协调。

更加友好的多通道支持

v2.x的另一个“较小”的改进是对共享词典的使用。在ngx_cc v1中,每创建一个通道就需要声明一个共享词典。这在多通道环境中是相当不友好的,而且也导致无法动态地创建通道。

在v2.x中,这个共享词典被统一成为一个。不过你仍然需要在nginx.conf中声明它,缺省情况下名为:ngxcc_dict。你可以在创建通道之前修改这个缺省定义:

ngx_cc = require('ngx_cc')
ngx_cc.dict = 'your_shared_dict_name'
route = ngx_cc:new('channel_name')
..

或直接使用在创建通道时带上dict配置:

ngx_cc = require('ngx_cc')
route = ngx_cc:new('channel_name', {
	dict = 'your_shared_dict_name'
})

无论哪种方式(包括使用默认共享词典),你都可以在一个词典上创建/使用多个通道。

实现与N4C框架的集成

这是v2中的一大进步。尽管N4C目前仍然是一个较小和较简单的框架,但是它为ngx_cc提供了标准化的、一致性的,用于构建集群应用的界面和规范。

在ngx_cc v2中,下列功能/特性是基于N4C框架来实现的:

  • 定制headers
  • 在当前会话中并发请求(location_capture/cosoket/3rd)

而反过来,N4C也依赖ngx_cc才能实现它的4C(a Controllable & Computable Communication Cluster)设计目标。

有关于N4C参见:https://github.com/aimingoo/ngx_4c