比尔云BierYun--阿里云最新优惠活动
阿里云优惠码丨阿里云代金券

基于Redis实现分布式消息队列(3)

基于Redis实现分布式消息队列(3)http://www.bieryun.com/1985.html

1、Redis是什么鬼?

Redis是一个简单的,高效的,分布式的,基于内存的缓存工具。
假设好服务器后,通过网络连接(类似数据库),提供Key-Value式缓存服务。

简单,是Redis突出的特色。
简单可以保证核心功能的稳定和优异。

2、性能

性能方面:Redis是足够高效的。
和Memecached对比,在数据量较小大情况下,Redis性能更优秀。
数据量大到一定程度的时候,Memecached性能稍好。

简单结论:但总体上讲Redis性能已经足够好。

原则:Value大小不要超过1390Byte。

经实验得知:
List操作和字符串操作性能相当,略差,几乎可以忽略。
使用Jedis自带pool,“每次从pool中取用完放回“ 和 “重用单个连接“ 相比,平均用时是3倍。这部分需要继续研究底层机制,采用更合理的实验方法进一步获得数据。
使用Jedis自带pool,性能上是满足当前访问量需要的,等有时间了再进一步深入。

3、数据类型

Redis支持5种数据类型:字符串、Map、List、Set、Sorted Set。
List特别适合用于实现队列。提供的操作包括:
从左侧(或右侧)放入一个元素,从右侧(或左侧)取出一个元素,读取某个范围的元素,删除某个范围的元素。

Sorted Set中元素是唯一的,可以通过名字找。
Map可以高效地通过key找。
假如我们需要实现finishTash(taskId),需要通过名字在队列中找元素,上面两个可能会用到。

4、原子操作

实现分布式队列首要问题是:不能出现并发问题。

Redis是底层是单线程的,命令执行是原子操作,支持事务,契合了我们的需求。

Redis直接提供的命令都是原子操作,包括lpush、rpop、blpush、brpop等。

Redis支持事务。通过类似 begin…[cancel]…commit的语法,提供begin…commit之间的命令为原子操作的功能,之间命令对数据的改变对其他操作是不可见的。类似关系型数据库中的存储过程,同时提供了最高级别的事务隔离级别。

Redis支持脚本,每个脚本的执行是原子性的。

做了一下并发测试:
写了个小程序,随机对List做push或pop操作,push的比pop的稍多。
记录每次处理的详细信息到数据库。
最后把List中数据都pop出来,详细记录每次pop详细信息。
统计push和pop是否相等,统计针对每条数据是否都有push和pop。
500并发,没有出现并发问题。

5、集群

实现分布式队列另一个重要问题是:不能出现单点故障。

Redis支持Master-Slave数据复制,从服务器设置 slave-of master-ip:port 即可。
集群功能可以由客户端提供。
客户端使用哨兵,可自动切换主服务器。

由于队列操作都是写操作,从服务器主要目的是备份数据,保证数据安全。

如果想基于 sharding 做多master集群,可以结合 zookeeper 自己做。

Redis 3.0支持集群了,还没细看,应该是个好消息,等大家都用起来,没什么问题的话,可以考虑试试看。

如果 master 宕掉,怎么办?
“哨兵”会选出一个新的master来。产生过程中,消息队列暂停服务。
最极端的情况,所有Redis都停了,当消息队列发现Redis停止响应时,对业务系统的请求应抛出异常,停止队列服务。
这样会影响业务,业务系统下订单、审批等操作会失败。如果可以接受,这是一种方案。
Redis整个集群宕掉,这种情况很少发生,如果真发生了,业务系统停止服务也是可以理解的。

如果想要在Redis整个集群宕掉的情况下,消息队列仍继续提供服务。
方法是这样的:
启用备用存储机制,可以是zookeeper、可以是关系型数据库、可以是另外可用的Memecached等。
本地内存存储是不可取的,首先,同步多个客户端虚拟机内存数据太复杂,相当于自己实现了一个Redis,其次,保证内存数据存储安全太复杂。
备用存储机制相当于实现了另外一个版本的消息队列,逻辑一致,底层存储不同。这个实现可以性能低一些,保证最基本的原则即可。
想要保证不出现并发问题,由于消息队列程序同时运行在多个虚拟机中,对象锁、方法锁无效。需要有一个独立于虚拟机的锁机制,zookeeper是个好选择。
将关系型数据库设置为最高级别的事务隔离级别,太傻了。除了zk有其他好办法吗?

Redis集群整个宕掉的同时Zookeeper也全军覆没怎么办?
这个问题是没有尽头的,提供了第二备用存储、第三备用存储、第四备用存储、…,理论上也会同时宕掉,那时候怎么办?
有钱任性的土豪可以继续,预算有限的情况,能做到哪步就做到哪步。

6、持久化

分布式队列的应用场景和缓存的应用场景是不一样的。

如果有没来得及持久化的数据怎么办?
从业务系统的角度,已经成功发送给消息队列了。
消息队列也以为Redis妥妥地收好了。
可Redis还没写到日记里,更没有及时通知小伙伴,挂了。可能是断电了,可能是进程被kill了。

后果会怎样?
已经执行过的任务会再次执行一遍。
已经放到队列中的任务,消失了。
标记为已经完成的任务,状态变为“进行中”了,然后又被执行了一遍。
后果不可接受。

分布式队列不允许丢数据。
从业务角度,哪怕丢1条数据也是无法接受的。
从运维角度,Redis丢数据后,如果可以及时发现并补救,也是可以接受的。

从架构角度,队列保存在Redis中,业务数据(包括任务状态)保存在关系型数据库中。
任务状态是从业务角度确定的,消息队列不应该干涉。如果业务状态没有统一的规范和定义,从业务数据比对任务队列是否全面正确,就只能交给业务开发方来做。
从分工上来看,任务队列的目的是管理任务执行的状态,业务系统把这个职责交给了任务队列,业务系统自身的任务状态维护未必准确。
结论:任务队列不能推卸责任,不能丢数据是核心功能,不能打折扣。

采用 Master-Slave 数据复制模式,配置bgsave,追加存储到aof。

在从服务器上配置bgsave,不影响master性能。

队列操作都是写操作,master任务繁重,能让slave分担的持久化工作,就不要master做。

rdb和aof两种方法都用上,多重保险。
appendfsync设为always。// 单节点测性能,连续100000次算平均时间,和per second比对,性能损失不大。
性能会有些许损失,但任务执行为异步操作,无需用户同步等待,为了保证数据安全,这样是值得的。

当运维需要重启Master服务器的时候,采取这样的顺序:
1. 通过 cli shutdown 停止master服务器, master交代完后事后,关掉自己。这时候“哨兵”会找一个新的master出来。
万万不可以直接kill或者直接打开防火墙中断master和slave之间的连接。
master 对外防火墙,停止对外服务,Master 自动切换到其他服务器上, 原 Master 继续持久化 aof,发送到原来各从服务器。
2. 在原 master 上进行运维操作。
3. 启动原 master,这时候它已经是从服务器了。耐心等待它从新 master 获取最新数据。观察 redis 日志输出,确认数据安全。
4. 对新的 master 重复1-3的操作。
5. 将以上操作写成脚本,自动化执行,避免人为错误。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

强烈推荐

高性能SSD云服务器ECS抗攻击,高可用云数据库RDS