Hike News

tns 中的CAP理论实践

cap

C:一致性
A:可用性
P:分区容忍性

Architecture in tns

集群采用无中心化设计,按节点ID排序并顺时针组成一个环,如图C1,节点按固定频率将其知道的cluster list、cluster node status和service list同步给下一个节点,并记录被同步节点的健康状态。

C1

故障检测

tns采用增量故障检测算法来检测集群故障。

C2

一个Up节点单次故障不会被立即标记为Down,而是被标记为Down_1,如果Down_1节点下次检测仍是故障,则会被标记为Down_2,如果Down_2节点下次检测仍是故障,则会被标记为Down,此后不会在对该节点执行故障检测。如下图:

Cluster 状态机

一致性

cluster视角

在tns中,不可变约束包括cluster node列表、cluster node健康状态、service node 列表。

tns 针对以上不可变约束满足最终一致性

增加node(cluster、service)

假设数据同步周期为T,在某个cluster node上操作增加一个node(cluster、service),在最长同步周期(T)后数据会被同步到下个节点,以此类推,假设集群节点数为N,最终一致时间最长为(N-1)T

移除node(service)

tns目前只支持移除service node,对于cluster node的移除功能暂不支持。

Service 状态机

对于移除service node,cluster 需要经历四个阶段:Leaving、Tombstone_1、Tombstone、Remove

其中Tombstone_1、Tombstone、Remove阶段由后台任务周期执行触发

  • Leaving阶段

    • 被移除的service node会被立即变更状态为Leaving,并取消对应的ping任务
    • 在周期T内,状态会被传输到下个节点
    • 最终在(N-1)T内,状态会被传输到所有节点
  • Tombstone_1阶段

    • 检查service node状态
      • 若状态为Leaving,且已停留了足够长时间,将状态变更为Tombstone_1
  • Tombstone阶段

    • 检查service node状态
      • 若状态为Tombstone_1,且已停留了足够长时间,将状态变更为Tombstone
  • Remove阶段

    • 检查service node状态
      • 若状态为Tombstone,且已停留了足够长时间,直接移除
  • 处于Leaving状态的节点仍会同步给其它节点;处于Tombstone_1、Tombstone状态的节点不会同步给其它节点;这三种状态均不接受状态更新;

为什么这么设计?

保证Leaving状态广播到整个集群;保证在真正移除前,集群所有节点处于Tombstone_1或Tombstone状态(保证移除后,该service node不会再被同步回来)。

假设处于Leaving状态的service node,未能广播给整个集群,会出现部分cluster node执行了移除操作,最终导致移除后被那些没能收到Leaving请求的节点将数据又同步回来,导致下线service node失败;另外,处于Tombstone状态的service node,节点一旦被执行移除,其上一个节点待移除数据可能处于Leaving甚至是UP状态,数据可能会被同步回来,最终导致集群出现错误,所以必须保证,在集群某节点在执行真正移除前,其余节点至少处于Tombstone_1或Tombstone状态。

详细推导过程见一致性详解

client视角

目前版本客户端不考虑一致性问题,未来可能会增加单调读一致性,但需求不大

tns-client会定时从cluster同步数据,在这个周期内,可能会出现数据不一致。例如某时刻一个service node被移除或已经down 掉,并未及时被tns-client同步过来,可能会导致client使用一个错误的service node来执行业务,出现错误,在tns-client中提供了brokenNode接口来主动移除故障节点

可用性

cluster视角

tns中,集群节点数量N>0即可写。

client视角

tns中,集群节点数量N>0即可读,同时因为tns是一个最终一致性的系统,节点的down机,会在(N-1)T内广播到整个集群,同时tns-client定时从某个cluster node同步数据也是定时操作,所以同步时某个节点可能不可用,此种情况可以采取两种措施:

  1. 换个节点立即重试
  2. 等待下一个同步周期(选择到一个健康节点)

目前tns-client采用方法2

分区容忍性

一般认为在同一个机房不会出现分区,在跨机房场景中会出现分区现象;同时在同机房内节点的上下线也被认为是特殊的分区。如图C3:
C3

tns不满足跨机房的分区容忍性,如果跨机房部署,出现分区情况,在没有人为增加、移除节点的情况下没有问题(tns中节点的增加、移除操作均为人工操作),所以这样部署问题也不大,只要在操作前检查下集群状态即可,操作后检查下结果是否已经被广播到整个集群。

一致性详解

cluster中有三个关键时间或周期

  1. cluster间同步数据的周期 T1
  2. service节点Leaving、Tombstone_1、Tombstone状态保留最短时间 T2
  3. 后台执行service node移除的周期 T3

cluster 之移除service node

前文已经介绍,下线一个service node需要处理好如下两件事情:

保证Leaving状态广播到整个集群;
保证在真正移除前,集群所有节点处于Tombstone_1或Tombstone状态(保证移除后,该service node不会再被同步回来

接下来看看三个周期需要满足什么条件?

若要保证Leaving状态广播到整个集群,只需保证每个节点都能将Leaving状态同步给下个节点,即在Leaving状态被转换成Tombstone_1前,至少发生过一次同步操作,推导出 T2 > T1

若要保证真正移除前,集群所有节点处于Tombstone_1、或Tombstone状态,只需保证,集群最早执行移除操作的时间 (TR) > 集群最晚将节点状态转成Tombstone_1的时间 (TT)

根据集群最终一致性性质,不难推算出,集群最晚收到Leaving的时间为 (N - 1)T1,从Leaving到Tombstone_1最长时间为 T2 + T3,所以 TT = (N - 1)T1 + T2 + T3

同理,TR = T2 + 2T3

进而可推算出:

TR > TT

=> T2 + 2T3 > (N - 1)T1 + T2 + T3

=> T3 > (N - 1)T1

综上,cluster三个周期需满足:

  • T2 > T1
  • T3 > (N - 1)T1

回过头来,假设没有Tombstone_1阶段是否可行?

根据上文推算过程,可得出

TT = (N - 1)T1 + T2 + T3

TR = T2 + T3

显然,无法满足 TR > TT,所以 Tombstone_1阶段是必要的

集群节点数上限

根据上边的推算,T3 > (N - 1)T1,目前T3 = 10分钟,T1 = 5秒,可得 N不能超过121,对于tns负载特点,120完全足够,一般三个节点即能满足大多数系统需求

最终一致时间

接下来看下集群最终一致时间到底要多长

提前须知

  • 对于增加节点(cluster、service),初始状态均为Joining,只有UP状态的节点才会同步给客户端
  • 对于增加cluster node,待增加的node状态只能被其上一个节点修改

增加cluster node

根据cluster 特性可知,一旦待添加的节点,被其上一个节点修改状态为UP,然后最终使集群达到最终一致(UP),需要时间 (N - 1)T1

那么待添加节点状态变为UP状态需要多长时间呢?

假如,操作的节点正好是待添加节点的上一个节点,那么由Joining - > UP ,至多需要T1时间,从而最终UP一致时间最长为:T1 + (N - 1)T1 = NT1

假如,操作的节点正好是待添加节点的下一个节点,那么由Joining - > UP ,至多需要NT1时间,从而最终UP一致时间最长为:NT1 + (N - 1)T1 = (2N - 1)T1

增加service node

根据cluster特性可知,Joining状态的service node同步到整个集群需要(N - 1)T1时间,然后Joining - > UP 由各自的cluster node负责ping检测,其中检测启动时间为Random( pingFrequency ),从而最终UP一致时间最长为:(N - 1)T1 + pingFrequency

下线service node

根据cluster特性可知,Leaving状态的service node同步到整个集群需要(N - 1)T1时间,一旦集群达到Leaving一致状态,客户端即获取不到该service node,所以对于客户端而言,最终一致时间最长为:(N - 1)T1

tns两种使用模式和灰度发布

tns客户端tnsclient支持不同的使用模式,包括load balance、master\slave模式,接下来介绍不同模式的作用和设计原理

load balance

在集群模式中,一个请求过来后要通过某种策略将请求分配到后台某个服务器上,这个策略我们可以称为负载均衡

tns采用加权随机的方法实现负载均衡

举例:服务serviceA下面有3个实例,对不同的实例分配不同的vNodes个数(权重),假如:a1:2;a2:4;a3:4,那么客户端会将请求的2/10分配到a1节点,将请求的4/10分配到a2节点或a3节点,从而实现了负载均衡

在tnsclient中使用LoadbalanceTSNodeIndexBuilderRandomTSNodeSelector

master\slave

在集群模式中,我们希望将请求分发到主节点(master),然后当master down后,将请求分发到某slave节点

大多数分布式系统master/slave由分布式系统本身实现,即系统自身包含一个监控组件,当监控组件检测到master不可用后自动提升某slave为master,典型代表为zookeeper(leader、flower)

tns自身并不提供master\slave功能,通过tnsclient在调用某个服务时实现。同样基于vNodes机制,tnsclient将获取到的某服务的所有实例,根据vNodes进行自然排序,vNodes小的节点优先,若vNodes相同,再根据id进行排序

同上面例子,如果采用master\slave模式,a1 vnodes最小,排在第一位,作为master;假如a1 down掉,那么a2或a3被提升为master,此时a2和a3 vnodes相同,所以id小的会被提升为master

在tnsclient中使用MaterSlaveTSNodeIndexBuilderMasterSlaveTSNodeSelector

灰度发布

所谓灰度发布,就是对要发布的程序先小批量上线,一旦出现问题,不至于影响到所有用户。

tns实现方式天然具有灰度发布的特性。基于vNodes,并且采用load balance模式,我们只需将发布的新节点的vNodes设置比较小(权重低),那么线上的流量只会有一小部分流到这个新节点,从而实现灰度发布的效果

tns cluster简介

tns(thrift name server)是我在700Bike开发的一个thrift rpc分布式组件,可以实现高可靠、负载均衡、动态水平扩展等.

相比haproxy、zookeeper等有什么优势?我们知道网络程序唯一保证可靠的方式就是心跳包,同haproxy方式有什么区别,可以阅读wiki why

使用方式wiki上都有,这里简单说说tns cluster的特性,及设计结构。

cluster结构图

cluster结构图

tns cluster 采用无中心化设计,也就是cluster中每个node都是均等的,在任一节点执行命令都等效,集群组件类似于redis,在节点上执行meet 即可,满足传递性,例如: 1 meet 2;2 meet 3 等效于1 meet 3; 1 meet 2

每个node均有一个ID,ID唯一,根据hostname+port生成,在集群中cluster按ID排序,组成一个环,如上图绿色环,其中ID较小的会负责检查比其稍大的ID的节点健康状态,并将自己知道的cluster list及健康状态和service list(不包含健康状态)传输到对方,实现集群信息同步,假如2节点Down了,1会标记2节点状态为down,并将自己的信息以后同步给3,因为2一旦down掉,是不可自动恢复的,只能手动恢复(重启2,并执行meet 重新上线).
如上,这种做法,一个节点有且仅有一个节点会同步信息给它,并且它也只同步信息给一个节点,集群增加节点不会增加单个node的压力。service list只会同步列表,不会同步service节点状态,避免1节点状态传递到6延迟比较高,每个tns节点自行维护service 状态,这样客户端不论从那个cluster node中同步service列表均比较实时。

tns内部结构图

tns

如图,在nameserver中添加三个rpc server节点,service名称定义为drpc,每增加一个节点时,可以指定ping的周期,nameserver会定时调用drpc的ping方法,ping方法返回vNodes,含义为虚拟节点,用于客户端对请求负载均衡,另外nameserver也根据每次ping返回的vNodes值来判断service server是否可用,若vNodes<0,nameserver会标记service node为down,nameserver只会同步UP状态的service节点列表给客户端,增加或下线一个节点,一个周期后(客户端设定周期)也会被客户端同步到。

单个的vNodes没什么含义,在一个service下有多个node时才有含义,例如上图中drpc包含三个节点,并且每个节点vNodes分别为7、2、1,客户端负载均衡后,其中7/10的流量会流向node1,1/10会流向node3,从而实现负载均衡,客户端默认提供一个随机选择器,大家可以按自己的意愿自己实现。

线上不论是tns node还是service node均可随时增加或减少,从而实现水平动态可扩展。

详细使用帮助文档,朋友们可以参考thriftnameserver 的WIKI。


tns交流群

jerry 于北京
2016-1-11

rocketmq 命令行自动补全工具

rocketmq_completion是为rocketmq开发的命令行自动补全工具,主要方便用户使用rocketmq时,减少命令行交互的成本及出错的概率!

安装

rocketmq_completion只有一个脚本,借助Linux中complete及compgen技术实现

  • 从github上download到本地或服务器任何目录下,例如:/your_path/rocketmq_completion/rocketmq_completion
  • 在本机bash_completion.d目录下,建立对应软连接(需要root权限),注:不同Linux发行版目录地址不同,用户可根据自己系统版本 google
1
2
cd /etc/bash_completion.d/
ln -s /your_path/rocketmq_completion/rocketmq_completion
  • 在bash_profile下填加一行
1
source /etc/bash_completion.d/rocketmq_completion
  • 重新打开一个新窗口,检查completion是否起作用
1
complete -p
  • 输出中出现以下内容表示成功
1
complete -F _mqadmin mqadmin

用法

1
2
./mqadmin[tab]
./mqadmin clusterList [-tab][--tab]

参数部分 -表示必选参数,–表示可选参数

关于升级

  • 目前版本只实现了mqadmin脚本的自动补全
  • 未来对其它脚本的补全会持续更新,实现方式是在rocketmq_completion脚本下添加_command方法并complete -F _command command方式来实现,以上用法中的步骤用户无需重复操作,即可实现对新增命令的支持

关于作者

坚持走技术路线的码农一枚

彻底摆脱配置文件 八(使用maven-zkcu-plugin灵活控制zkconfigutil)

惯例,我们从使用上开始。

准备

首先,下载并构建maven-zkcu-plugin

这是一个maven工程,采用maven构建并install本地即可

详解

接下来我们主要看效果:
我有一个配置类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@TypeZkConfigurable
public class StaticClass {
@FieldZkConfigurable(dynamicUpdate = true)
public static String a = "hello world a";
@FieldZkConfigurable
public static String b = "hello world b";
@TypeZkConfigurable
public static class InClass{
@FieldZkConfigurable(dynamicUpdate = true)
public static String c = "hello world c";
}
}

接下来看下关键的,如何使用maven-zkcu-plugin,实现彻底摆脱配置文件,并又能灵活控制配置功能开关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugin>
<groupId>com.jerry</groupId>
<artifactId>maven-zkcu-plugin</artifactId>
<version>0.1.1</version>
<configuration>
<zk>127.0.0.1:2181</zk>
<mainClass>com.jerry.testplugin.TestStatic</mainClass>
<zkConfigurableClass>
<parm>com.jerry.testplugin.StaticClass</parm>
<parm>com.jerry.testplugin.StaticClass$InClass</parm>
</zkConfigurableClass>
</configuration>
</plugin>

这里有几个参数,其中zk代表配置项保存在哪个zookeeper上;mainClass代表你的程序启动的主函数;zkConfigurableClass代表所有工程里需要配置的类,也就是添加了@TypeZkConfigurable注解的类

看看如何使用,在你工程pom文件所在目录下,执行

1
mvn clean compile zkcu:zkcu jar:jar

即可,强调下,zkcu必须在compile阶段之后执行,ok,现在你的服务已经有了zkconfigutil的功能。

有些时候,可能想关掉zkconfigutil,那么执行

1
mvn clean package

也就是去掉zkcu:zkcu即可。

特别强调下,zkcu这个plugin只能用户手动执行,无法被绑到某个maven生命周期上,这样做的目的是可能由用户控制zkconfigutil的开关。

总结

zkconfigutil采用注解的方式,实现了一个服务的配置项zookeeper化,增加dynamicUpdate = true,即可实现服务内部参数的动态更新,采用maven plugin方式,可以灵活在构建工程时控制是否使用zkconfigutil功能。 另外,如果不想将一些参数直接写在zkcu-plugin下面,也可以采用-D参数形式,为该plugin提供参数。


jerry于2015-04-17

北京

彻底摆脱配置文件 七(基于linux USER2信号检查当前管理的配置项信息)

基于linux USER2信号检查zkconfigutil当前管理的配置信息

当zkconfigutil管理的配置项过多时,我们也不确定是否有漏配置的,这个功能可以理解为配置项检查。

基于linux signal实现,使用起来比较方便,向kill -9或kill -15一样,使用这个功能只需执行kill -12 或kill -s USER2 即可,相关信息会以info级别打印到log中。

看下效果:

ubuntu下没有找到图片处理程序,兄弟们扫一眼也能看出来,看SonHelper这四行,可以看到目前监控了四个配置项,所在的类名均为com.jerry.zkconfigutil.Demo 配置想项分别为字段F1、F2、F3、F4,用到的resolve均为Reflectesolve,并且F1~F3需要动态更新,F4不需要动态更新


jerry 于 2014-08-13 03:36

广州出差宾馆

彻底摆脱配置文件 六(使用zkconfigutil和eclipse zookeeper插件配置脚本)

对一个脚本的配置和普通配置项没什么两样。直接上个图再解释!

隆重为大家介绍我一直使用的eclipse zookeeper插件,博客地址:http://www.taobaotest.com/blogs/qa?bid=15305,请分享者的著作权!!!

首先我们有个配置项名字为“F1”,在没修改前内容为:

从这个截图上能清楚看到eclipse的zookeeper插件我们选择的是Text窗口,也就是字符串,内容为F321,接下来我们就直接利用这个eclipse zookeeper插件修改F1字段的值,将其修改为一个文件的内容,还是直接上图:

从这张截图大家看到,我这里利用eclipse zookeeper插件选择File窗口(这里可以直接import文件,或export文件),直接import一个awk脚本,ok,ctrl+s保存,看到F1字段的值直接变成了文件的内容。

通过以上简单介绍,不知大家有没有感觉,假如我们有一个分布式服务,每台机器上都需要有一个配置文件,修改起来要修改N遍,通过这种方式,我们即摆脱了配置文件,也实现了一处修改影响所有的效果。

注意:zookeeper不是数据库,不应该进行过频繁的更新操作,然后zookeeper本身有对node大小的限制,默认是1M? 如果你的文件太大了,需要修改这项配置,不然会报错!


JERRY 于2014.7.7 16:21

彻底摆脱配置文件 五(基于javaagent实现zkconfigutil对程序零侵入)

之前使用zkconfigutil大致是两步,对要配置的pojo添加注解,然后需要zkconfigutil.register(class)。这样对程序造成了一定的侵入。今天为大家带来这个1.0.1版本,也是第一个正式的版本,这个版本添加对-javaagent的支持,来完全解决侵入问题,大神别急,待小弟慢慢道来!!!

先来效果:

我的工程中有个Demo是整个工程的配置项,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@TypeZkConfigurable
public final class Demo {
@FieldZkConfigurable(dynamicUpdate = true)
public static String F1 = "F1";
@FieldZkConfigurable(dynamicUpdate = true)
public static String F2 = "F2";
@FieldZkConfigurable(dynamicUpdate = true)
public static Boolean F3 = false;
@FieldZkConfigurable
public static Boolean F4 = true;
}

这个配置项F1字段dynamicUpdate = true,代表这个字段需要动态更新,即zookeeper上的值变化后F1需要做出相应修改。F4字段采用采用默认dynamicUpdate false,也就是不需要动态更新。

如何使用Demo:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws InterruptedException {
while (true) {
System.out.println("Demo.F1 = " + Demo.F1);
loop();
}
}
public static void loop() throws InterruptedException {
Thread.sleep(2000L);
}

这段代码不需多解释,2s打印一次F1(这个字段可是动态更新的哦!)

ok代码已经写完了,是不是对您的代码完全没有侵入呢?

看启动:

1
-javaagent:/home/jerry/ZKCUAgent.jar=zk@10.31.44.38:2181#class@com.jerry.zkconfigutil.Demo

这里主要用了javaagent,在jvm参数中添加上述参数,首先-javaagent指定我们的agent的jar,这里在我的home目录下,然后=号用来指定agent的agentOps,也就是参数,格式为zk@z1,z2#class@c1,c2,c3。

ok,带上javaagent参数后直接启动,现在我们的Demo已经实现了zookeeper化配置。

欣赏下吧!
启动程序运行如下:

修改zookeeper上F1字段的值为F321,运行如下:

怎么样,使用起立是不是非常easy,并且实用,通过jvm的javaagent参数进行控制,对服务实现零侵入。与zookeeper的eclipse插件是个完美的组合!!!

这是小弟发布的第一个正式版本,在osc的git仓库和github上都有完整代码及测试Demo,如果你有更好的想法真切希望你加入!!!


JERRY 于2014.07.05 14:22

彻底摆脱配置文件 四(基于反射的通用resolve详解)

之前虽然提供了生成通用resolve的template,改善了代码开发的代价,但生成的resolve过多,导致代码长度过度增长,使用ReflectResolve便可解决这个问题。

目前resolve从使用方式上有两种情况:

1
2
3
4
5
6
7
@FieldZkConfigurable(resolve = DemoResolve.DemoF1Resolve.class, dynamicUpdate = true)
public static String F1 = "F1"
@FieldZkConfigurable(dynamicUpdate = true)
public static String F2 = "F2";
@FieldZkConfigurable(dynamicUpdate = true)
public static Boolean F3 = false;

第一种,明确指定resolve,采用这种方式会比较灵活,一对一对应字段F1,逻辑可以自己方便控制,甚至可以加上一些逻辑而不是简单的赋值。

第二种,没有指定resolve,采用默认的ReflectResolve,这种方式减少代码的编写,及缓解代码的膨胀,这个resolve,采用反射机制,所以其有一定的局限性,但基本可以满足90%的需求,目前支持字段类型为:String、Long、Integer、Float、Double、Short、Boolean、及自定义类型VisualType(VisualType的子类),从本人测试的效果看,同样非常稳定。

第二种方式在使用上大大简化了开发者的开发工作,如果您的配置不需要自动动态更新,dynamicUpdate=true都可以去掉,最后只剩下一个简单的注解,即可实现属性的zookeeper配置。

1
2
@FieldZkConfigurable
public static Boolean F4 = true;

为了满足大多数用户的需求,除了java中可以使用valueOf方法反序列化的类型之外,还提供了VisualType类型,从名字上看大家应该明白了,就是“可视化类型”,只要您的属性类型继承自VisualType,并重写valueOf和toString方法也可通过ReflectResolve实现zookeeper配置当然也可动态更新。

最新代码可以从项目主页获取,欢迎大家试用,并提供宝贵意见,当然也非常愿意您的加入!!!


jerry 于 2014-04-02 13:08

彻底摆脱配置文件 三(zkconfigutil通用resolve自动生成)

系列文章见本人博客

cast_resolve_template.xml文件在工程template下,地址:cast_resolve_template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final class DemoF3Resolve extends AbstractResolve {
@Override
public String resolve() {
// TODO Auto-generated method stub
return Demo.F3.toString();
}
@Override
public void dResolve(String src) {
// TODO Auto-generated method stub
Demo.F3 = Demo.F3.getClass().cast(src);
}
}

有了这个模板,只要需要配置的属性可以string表示,就可用该模板生成通用resolve,生成后无需再修改


jerry 于 2014-03-17

© 2017 jerry's blog All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero