Redis简介
Redis是一个高性能的key-value非关系型数据库,并且是开源免费的。Redis数据库有以下特点:
1、Redis支持数据的持久化;
2、提供list,set,zset,hash等数据结构的存储;
3、Redis支持数据的备份,针对redis有优良的容灾方案;
4、redis是单线程模式,线程安全。
5,性能极高,读取速度为110000次/s,写速度为81000次/s。
6,丰富的数据类型(String, List,Hash, Set zSet)。
7,遵守原子性,事物提交要么都成功,要么都失败。
8,支持事务,通过MULTI和EXEC指令包裹代码块提交事物。
9,list的双链表特性方便实现基于内存存储的消息队列。
高效的性能、丰富的数据类型以及可持久化的特点,目前大多项目都选择使用redis做缓存存储。Redis就像是项目的备忘录,把常用并不容易变更的数据记录下来。在需要用到的时候方便查阅。项目中一般把快码,资源路径,功能编码,角色权限列表等比较固定的信息存储到redis。当遇到需要提高并发量的技术难题时,可以把用户经常加载的请求、耗时的请求缓存在redis里面,可以显著提高应用的并发量。
课题研究
一,redis容灾策略介绍
目前项目大多采用的是单节点redis服务器部署,当该节点出现宕机时应用就会异常。由于redis能够持久化,所以解决办法通常是重启redis服务器。但是问题来了,如果是生产环境出现该问题,会造成无法弥补的经济损失和名誉损失。所以需要通过容灾手段去避免该情况的出现。
方案一:master---slave 主从服务器集群:
以一个Master节点为主,多个Slave节点为辅助。Master节点负责写入数据,多个Slave节点从master节点做备份之后对外提供读取权限。这样实现了数据备份,其次读写分离减轻了master的负担。
但是一旦master出现故障,整个应用可能就面临崩溃。这时我们技术人员就不得不上线排查问题。此时技术人员就迫切想要一个可以实现全自动化无需人工参与的技术方案。
方案二:Redis Sentinel服务器集群:
Redis Sentinel可以创建一个无需人为干预就可以进行故障转移的Redis环境。Redis Sentinel类似于一组守护进程,时刻监听着redis组的运行情况,当其中一个Sentinel检测到master无法使用时,会将其广播给其他Sentinel,当所有Sentinel都确定master无法再使用时,就会执行故障转移。
Redis Sentinel主要作用有如下功能:
1,监控:Sentinel会不断的检查master和slave是否像预期那样正常运行
2,通知:当出现问题会通过api通知给管理员。
3,自动故障转移:当Sentinel一致确定master异常,Sentinel组中会选举出来一个sentinel作为领导去把其中最稳定的slave提升为master。
4,客户端动态感知master地址:故障转移完成后,客户端连接sentinel会自动切换成新的master的地址。
Sentinel自己本身也有个选举方式,选举方式为投票。每个确认了master“主观不可用”的sentinel节点,都会向周围广播自己的参选请求。所有Sentinel节点通过广播都确认“主观不可用”即可定义为“客观不可用”。同时每个节点都只会投票给第一个接受到的参选请求。当一个节点超过一半同意则确定为Leader。如果本回合持续了足够长的时间还未选出Leader,则开启下一个回合。所以建议sentinel节点必须是奇数个。
当选举出一个Sentinel作为领导节点后,Sentinel会去主导Slave节点晋升。步骤如下:
1,slave优先级设置,保存在redis.conf配置文件。
2,复制偏移量大小,最接近master的节点作为新的master。
3,选举具有最小Run ID(运行最久)的Slave作为新的Master。
4,选举成功后会让原来master降级为slave,并与其他slave节点一起随从新master节点
二,Redis实现消息队列方案
上文提到过redis支持list数据格式。针对list格式的支持,redis非常友好。Redis允许往数组的左边或者右边放入数据,也可以移除最左或者最右的元素。这样很好满足传统的队列的规则“先进先出,后进先出”。同时redis是单线程的,所以是线程安全的,作为队列不会重复脏读。如图是一个先进先出的指令demo。
Redis的list支持头尾增加数据可以看出数据结构的原型是双向链表。双向链表对于首尾的增删是非常快的。一个列表的容量可达2的32次方-1个。并且该list结构在双向链表的基础上做了存储优化,美称为quickList。引用专业人士的描述为: “quickList 是 zipList 和 linkedList 的混合体。它将 linkedList 按段切分,每一段使用 zipList 来压缩存储,多个 zipList 之间使用双向指针串接起来。因为链表的附加空间相对太高,prev 和 next 指针就要占去16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率”。
原理掌握了之后,接下来介绍一下该队列如何在项目上使用。由于公司政策不方便把代码贴上来,只能口述描述一下具体思路:
1,通过自定义注解去配置队列,实现afterPropertiesSet接口去解析注解去监听匹配的队列。
2,执行处理队列任务时,需要根据队列的数量自动生成相应数量的监听线程。确保每一个队列都被一个专门的线程监听。
3,在监听线程内去监听所需要监听的队列。如果该队列有值则进行pop操作,并触发一个处理该值的业务事件。
4,通过SmartLifecycle去触发以上动作,这样就能完成一个基于redis的消息队列了。