作者: H.E. | 您可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明
网址: http://www.javabloger.com/article/mq-kestrel-redis-for-java.html
豆瓣读书 向你推荐有关 JMSNoSQL分布式存储性能、 类别的图书。

Kestrel是twitter的开发团队用scala语言写的开源消息中间件,可以将消息持久存储到磁盘上,也可以将消息存储于内存中,但是不论保存磁盘还是内存中都可以设置消息存储的超期时间长短。
原先Kestrel是由Ruby写的Starling项目,但是后来 twitter的开发人员尝试用scala重新实现,并且可以支持Memcached的部分协议,例如:GET、SET、FLUSH_ALL、 STATS。对于Kestrel 服务器端而言如果有N个接收端连接在Kestrel服务器上,那么每个接收端会平均或者随机的收到不同的消息,并且发送端发过了消息接收端就算不接收,等 到接收端再上去接收的时候还能收到消息,因为Kestrel支持消息持久化。
Kestrel 不存在主从 和 集群的概念,只存在分布式的说法,这有点类似memcached 通过客户端组成一个环状,对于Kestrel 服务器端而言,如果服务器端收到消息了,但是里面有消息没有收下来就挂了,再重启的时候接收端还能收到之前的消息。

Redis是一个key/value存储系统,大多数开发者把他当做类似Memcached的缓存系统使用,
Redis和MemCached区别的是:
    1.会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件
    2.对于我来说更重要的是Redis还实现了master-slave(主从)数据同步的功能。
    3.Redis不仅支持传统的存储的value类型,还可以支持list(链表)、set(集合)和zset(有序集合),
    4.Redis还支持多种方式的排序,并且可以按照数据存储的范围来访问,有点像查询(select),
    5.支持消息发布和订阅功能。

Redis也有我们对他不满意之处,例如:
        1.对于同一个队列或者通道来说,有N个接收端连接在Redis服务器上,每个接收端会收到相同的消息。
        2.发过了 你不接收 等接收端再上去消息就没有了,默认的配置是写在内存中,重启后不保存。
这2点上跟 Kestrel 是相反的,但通过一些奇巧淫技将Redis消息视为缓存进行持久化,保证redis服务器重启后数据还在,并且通过Redis客户端动态设置机器的主从备 份,只要有两台或以上的Redis做集群可以对数据永远有保证,除非服务器集群环境全挂,这样的话Redis还算是对数据提供了可靠性。
但动态指定主从关系的话此时又会面临一致性的话题,在客户端指定Master的时候,为避免相互竞争或者重复指定,我们通过Jgroups工具作为Redis for Java客户端之间的通讯工具,在通讯过程中采用了Paxos算法,由客户端先选出一个Leader,再由这个选出来的Leader进行动态指定Redis集群中的master机器,这样可以提高一致性,避免竞争和冲突。

回到一开始说的Kestrel, 经过一番测试比较使用 xmemcached、spymemcached 等客户端对Kestrel进行收发操作,发现效果都比较烂,很遗憾没有找到让人满意的Kestrel for Java的客户端,所以我们一致认为使用Kestrel 这货将来的杯具估计产生在客户端(java),也许Twitter使用的是ruby,是不是对ruby反而支持的很好?对Java支持的不够给 力?Kestrel 接收端 需要不断的循环服务器 才能及时的收到消息, 这样节点数一多的话网络开销会不会很大?
在测试的过程中,我们使用xmemcached作为客户端,貌似不太靠谱,开10个线程,循环1000次,表示鸭梨很大,出现以下异常信息,producer和consumer都有,而且同时冒出来。
发送端:
Exception in thread "main" java.util.concurrent.TimeoutException: Timed out(1000) waiting for operation
接收端:
Exception in thread "main" java.util.concurrent.TimeoutException: Timed out waiting for operation
随后google之,作者提供了解答:http://code.google.com/p/xmemcached/issues/detail?id=108
作者给出的解释是:“这主要是因为xmc的操作都是异步的,同步等待有个超时时间,默认的1秒在高并发或者存取大数据的时候通常是不够的。”晕啊,表示不能接受这样的解释。

随后目光开始关注Redis,Redis采用长连接,事件监听,不需要轮询,接收端等待消息,只接收字符串类型的消息,对消息的数据没有保证,不接收就没 有了服务器端不保存,也不支持Queue的消息模式,就像JMS中的Topic消息那样一个发送端对应N个接收端每个接收端收到同样的消息。
但我们将publish/subscribe和rpush/rpop 4个协议混合使用就可以完全满足我们的要求,客户端不轮训可以保证及时性和消息持久化的问题,而且还是Queue的模式。思路上通了后,做了压力测试,收 发100w个消息开1000个线程,8秒以内搞定收发,表示毫无压力,体积小,部署简单比JMS方便,而且无单点,支持可散列。

在压力测试的过程中发现使用Redis Java客户端需要注意的两个问题:1.接收端莫名其妙的异常退出,这是由于没有设置连接空闲的超时导致的,就和MySQL的8小时问题一样,在 Redis客户端设置一下config.setMaxIdle(num) 就好了。2.发送端由于发送大量请求会崩溃,出现超时错误,例如:JedisConnectionException: java.net.SocketTimeoutException: Read timed out,后来使用JedisPool和JedisPoolConfig实例,进行池化,一切都ok了,否则难以支持大数据量的高并发。

在系统中采用MQ是为了进行解耦合,用在2个环节上:
    1.系统中有大量数据并发写入,并且可以容忍一定的延时,那么就可以先存储缓存中,然后再将数据批量写入,降低DAL和数据库层每次通讯的开销。
    2.我们自己开发的分布式并行计算的定时器称为“任务工厂”(支持失败转发),任务工厂从数据库或者缓存中拿用户的信息,将用户信息和业务逻辑框架作为消 息,发送给各个不同的处理业务逻辑的服务器进行处理,那样1000w个用户的需要定时批量操作的话,任务工厂集群越大处理的速度就越快,而且如果有一个任 务工厂的节点坏掉了或者又添加了一台新节点,轮询完毕后 重新计算一次后,每台机器又将平摊被分载的计算。

这次在实施的项目中,我们还专门设计了一个“失败队列”,他的作用是 处理具体业务逻辑的服务器 收到消息后将处理失败的请求放入“失败队列”中,往往消息在发送过来后进行处理,但是由于种种原因处理失败,但是下次还会再进来处理,这样的情况如果量大 而且每次都这样的话对系统显然很不利,所以从原有的队列中移除这个用户请求,转移到“失败队列”中由 专门处理“失败队列”的任务工厂会去定时处理,如果这个账号轮训处理5次再失败,移除失败队列,存入数据库,进行管理员人工干预。

如果你真正的理解了MQ的工作原理,可以采用一些网络通信工具例如:netty、mina、grizzly  ,加上一些基于Key/Value的内存存储产品,例如memcached、redis ,自己可以是建造一个符合自己要求高效的轻量级MQ系统,如果说你可以失去一部分性能而换取可靠性的系统而言JMS依旧是首选产品。

感谢作者 庄晓丹 的回复,内容如下:
———————————————————————————————————————-
1、需要轮询,kestrel就是一个简单的push/pull模型,消费者只能通过pull轮询来获取消息。这个轮询的代价我认为很低的,取决于队列名称的大小,一般也就十几个字节一次请求。
2、超时的原因很多,通常来说跟你传输的消息大小、网络状况、kestrel的服务器状况都有关系,数据越大,在网 络传输上耗费的时间越多,相应的越容易出现超时的情况。kestrel是scala写的,启动的时候要注意下jvm参数,如堆大小、gc算法之类,减少 gc暂停带来的影响。服务器如果gc暂停,或者磁盘做刷入,都有可能导致响应超时,简单的做法就是将操作的超时时间加大,请注意,这个超时加大,不代表每 个操作都要用这么多时间(可以统计平均看看),而是代表可以容许的最大超时时间。

kestrel整体来讲,只能作为一个轻量级的mq方案,用在一些相对简单的场景。———————————————————————————————————————-

 
–end–