JGroup 使用分享
JGroup是当前被广泛使用的可靠组间通信的工具之一。例如OSCache以及JBossTreeCache都是用的是JGroup。
JGroup功能十分强大,通过配置各种参数就可以充分利用它所提供的各项功能。JGroup最大的特点就是支持协议栈的可配置性,它本是实现了基本的Java的协议栈实现,也就是最基本的消息广播的基础,同时支持附加协议栈的配置,消息的传递就是在这些协议栈之间相互传递,封装,检查,丢弃,重发。JGroup可以基于TCP协议来实现消息广播,也可以通过UDP方式来广播消息,利弊不言而喻,TCP可靠,但是代价大,性能没有UDP来的好,UDP速度快,代价小,但是消息的丢失率以及无序性有着很大的限制。但是JGroup在UDP方式的基础上,增加了协议栈的配置,通过配置上层的协议,可以保证消息的重发,大包体的分解(同时保证消息包体顺序),组内机器的状态检测等功能。
这里大致描述一下使用中的配置参数的内容,可以在配置中正确合理的配置好JGroup的功能,使之在分布式环境中起到最合适的作用。
当我们启动一个JGroup的组的时候,其实建立了一个通道,可以通过配置文件为这个通道配置相应的协议栈,那么在这个通道上传递的消息也就会在配置的协议栈中传递交互。但是配置多协议的时候也需要注意,当配置的协议越多,那么功能也越强大,支持的传输特性也越多,但是其损失的可能就是性能,所以和平时的网络编程一样,UDP和TCP各有优缺点,只是根据使用的场景需求不同来合理的使用不同的协议栈叠加,达到最佳的效果。
先贴一段JGroup的范例配置:
"UDP(mcast_addr=224.0.0.35;mcast_port=45566;ip_ttl=32;" +
"mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" +
"PING(timeout=2000;num_initial_members=3):" +
"MERGE2(min_interval=5000;max_interval=10000):" +
"FD_SOCK:" +
"VERIFY_SUSPECT(timeout=1500):" +
"pbcast.STABLE(desired_avg_gossip=20000):" +
"pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800):" +
"UNICAST(timeout=5000;min_wait_time=2000):" +
"FRAG(frag_size=4096;down_thread=false;up_thread=false):" +
"pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;" +
"shun=false;print_local_addr=true)"
UDP:使用IP多波协议来广播组内消息并且使用的是UDP包作为消息发送给独立的Member。
PING:是用多波协议来查找初始化的在线Member。
MERGE2:允许将子组合并到一个组。
FD_SOCK:基于Socket的错误检测(通过Member之间的环形检测来实现)。当发现有member失败就产生消息通知。
VERIFY_SUSPECT:双重检测member是否确实Dead,多了Suspect的状态。
Pbcast.STABLE:删除被全部member查看到的消息(作用就是如果在分布式的情况下,其中某些member没有看到,那么这条消息就不会被删除,会重发,保证消息广播的全局性)
Pbcast.NAKACK:保证消息的可靠性和FIFO(消息的顺序传递)。
UNICAST:类似于NAKACK的功能。
FRAG:大消息的拆分和组装。
Pbcast.GMS:组成员协议,响应加入离开组的消息以及新建view的消息。
UDP配置:
UDP广播模式可以配置成两种方式:使用UDP和常规IP多波传输。配置类似于范例中的配置。所有的组内成员都运行在一个host上,分布在一个LAN内。但是这个需要确认的就是IP的多波传递是否允许穿越多个子网,通常情况下是不允许穿越多个子网的,因此就需要确保多个组内的member在一个局域网中,并且可以相互间通信。
另一种模式就是使用UDP但是不使用IP多波传递。UDP和PING的配置都是基于IP的多波传递的,因此消息传递以及组内成员的初始化检测都是要依靠于IP多波。因此这里提供了另外一种方式就是设定一个每一个member都能够访问到的机器,在这台机器上面设置一个GossipRouter,类似于路由一样,每一个member的注册都直接在这台机器上的服务上管理。
需要配置的就是设置ip_mcast为false。同时需要配置gossip_host gossip_port gossip_refresh这三个参数(地址,端口,刷新一次GossipRouter的时间)。
然后就在那台服务器上运行GossipRouter程序:
java org.jgroups.stack.GossipRouter -port 5555 -bindaddress localhost
配置如下:
"UDP(ip_mcast=false;mcast_addr=224.0.0.35;mcast_port=45566;ip_ttl=32;" +
"mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" +
"PING(gossip_host=localhost;gossip_port=5555;gossip_refresh=15000;" +
"timeout=2000;num_initial_members=3):" +
当如果需要在WAN中使用JGroup的时候就需要配置TCP来替换UDP了。
"TCP(start_port=7800):" +
"TCPPING(initial_hosts=localhost[7800];port_range=5;timeout=
"num_initial_members=3;up_thread=true;down_thread=true):" +
"VERIFY_SUSPECT(timeout=1500;down_thread=false;up_thread=false):"
"pbcast.STABLE(desired_avg_gossip=20000;down_thread=false;up_
"pbcast.NAKACK(down_thread=true;up_thread=true;gc_lag=100;retransmit_
"pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;"
"print_local_addr=false;down_thread=true;up_thread=true)";
FD 协议栈(Failure Detection)
FD的作用就是探测组内的成员是否还活着。当组内的成员被怀疑可能死掉了,那么SUSPECT消息就会传播到集群的每一个节点上。不过这个协议没有将crashed的member踢出去的功能,这个工作由GMS来做,它只是通知的功能。
FD是基于心跳消息检测来实现的,如果回复消息在timeout的规定时间内没有收到,那么就会宣布那个node被suspected,然后会被GMS给踢出去。一共三个参数作为配置项:
Timeout,max_tries,shun。前面两个不说了,最后一个十分重要。例如:一个组内有a,b,c,d四个成员,当d由于负荷过高没有在timeout的时间内作出响应,导致被踢出组的时候,a,b,c的组员view中只有abc三个member,而d的view却还有abcd四个成员,此时如果D再发消息给组内的成员,组内成员将会拒绝接受。那么如果设置shun为true的时候,D就重新加入组内在下面两种情况:1.ABC接受到了D发过来的are-you-alive的检测消息。2.D自己收到了一个view消息,view内不包含D。(类似于重连机制)。这点很重要特别是在分布式的环境中,当某些服务器的压力可能较高,配置的超时时间又不确定是否可以满足高负荷响应。记得要在GMS里面的shun也配置一样的情况。
FD_ALL也是基于简单的心跳协议,只不过是每个节点会向每一个member周期的发送心跳包,同时将会保存每个member的状态表。
FD_SOCK是基于环形的TCP Socket互联,相互监测来实现FD的功能的,每一个member都有自己的邻居,使得member组成一个环形的校验链。但是有几个缺点,第一当使用多网卡机器的时候,2.2.8以前的版本就可能造成随机绑定ip的问题,以后可以指定bind_addr这个参数,明确邻居的ip。另外的缺点也就是我们在实施过程中发现的问题,由于TCP的异常产生未必都能被检测到,同时例如网络问题等因素,往往这种FD很难保证真实的挂起出错的node,加之他是ring的检测方式,会导致明明某个member已经dead了,但是由于没有检测到,导致后面再加入的member或者自己重启,都无法正常使JGroup正常工作,不断地在重复join/leave的操作。(所以不建议使用这种模式)
Group Membership
他关注新member的加入,member离开的请求,处理crashed的member suspect消息等操作。