1.Redis 的技术特性和演进
Redis是一款开源的、网络化的、基于内存的、可进行数据持久化KEY-VALUE的存储系统。Redis通过KEY映射VALUE的方式来建立字典来保存数据,支持多类型存储包括STRING、LIST,SET,SORT SET和HASH等,可以在这些数据类型上做很多原子性操作。Redis将数据存储在内存里面,而且它发送给Redis的命令请求不需要经过典型的查询分析器(PARSER)或查询优化器(OPTIMIZER)处理,所以Redis对自身存储的数据执行随机读写的速度是非常快速的。
随着互联网时代的推进、大数据时代的到来,Redis的版本也经过翻天覆地的变化。从键的过期时间逐步细化、实现增量复制到推出分布式集群Redis CLUSTER,LUA脚本的功能不断增强,再到4.0版本支持MODULE,由此可见,Redis的发展更多的拥抱了数据处理的大潮。在这种背景下,如何应对当下互联网金融账务核心应用的高并发、海量数据快速计算、数据结果急速响应的需求,Redis的使用提供了较为出色的解决方案,通过将数据和请求分布到不同的节点,实现水平扩展和负载均衡,进而提供高并发数、数据吞吐量和快速响应的能力。
Redis集群方案目前主流有三种,主从、SENTINEL、CLUSTER,因SENTINEL是基于主从的延伸,在此我们仅对哨兵和CLUSTER进行分析。
一、SENTINEL集群
SENTINEL是REDIS2.6版本中推出高可用(HA)解决方案,当用Redis做MASTER-SLAVE的高可用方案时,MASTER不能提供服务,Redis自身不能实现自动主从切换,因此SENTINEL承担了监视器的功能。它能监控多个MASTER-SALVE集群,并能够选举多个SALVE中的一个作为新的MASTER,其他的SALVE节点会将它所追随的MASTER地址改为被提升为MASTER的SALVE的新地址,拓扑图如下。
SENTINEL集群数据同步原理还是延续主从模式:
(1)当从节点向主节点发送SYNC命令;
(2)主节点接收命令后执行BGSAVE命令保存快照,并创建持久化文件,同时将创建持久化文件期间的命令缓存;
(3)主节点执行完BGSAVE命令后,将持久化文件发送至从节点;(4)从节点接收持久化文件后开始接收主节点的命令缓存;
(5)以上步骤均完成后,主节点每执行一个写入操作,均发送从节点。具体流程图如下:
二、CLUSTER集群
CLUSTER集群是REDIS3.0版本中推出的特性,是基于Redis的一个分布式实现。它引入了哈希槽的概念,支持动态添加或删除节点,可线性扩展至1000个节点,多个Redis节点之间数据共享,部分节点不可达时集群仍可用,数据通过异步复制,不保证数据的强一致性,具备自动FAILOVER的能力,客户端直接连接Redis SERVER,免去PROXY性能的消耗。
Redis CLUSTER是一种去中心化的结构,拓扑图如下图所示。所有节点之间互相连接,通过GOSSIP协议来发布广播消息,每间隔时间内互相发送PING/PONG心跳包来检测其他节点状态,来保持信息同步,客户端直接连接任意Redis SERVER,并由Redis CLUSTER路由转发客户端请求到真正请求数据的节点。
CLUSTER集群数据同步原理还是延续主从模式,在此就不再重复的描述,在此着重说明一下CLUSTER集群的路由原理。当操作某个KEY时,Redis CLUSTER节点并不是采取代理的模式直接寻找到这个KEY所在的节点并执行命令,而是将客户端重定向到存储这个KEY的节点,通过对KEY的操作,客户端会记录路由地址,最终客户端获得每个节点负责的KEY最新信息。在此同样以流程图来简单描述。
所以在一般情况下,对于给定的操作,客户端会直接连接正确的节点并执行命令。与单点Redis不一样的是,CLUSTER集群引入了哈希槽概念,而且不是一致性哈希来实现,共有16384个哈希槽。每个MASTER节点只负责一部分哈希槽,每个KEY通过HASH_SOLT=CRC16(KEY)384来计算属于哪一个节点。
三、总结
我们先看下两种集群方式在技术指标上的区别
针对技术指标的侧重点,SENTINEL模式更偏向于高可用的CACHE、存储场景;CLUSTER模式用户高可用的需求场景,更偏向于数据量较高的高可用CACHE、存储场景。因此,在互联网金融账务核心系统中,更偏向于选择REDIS CLUSTER。
2.Redis在互联网金融账务核心系统的一些应用场景
互联网金融账务核心系统是一种特殊的账务系统,与传统金融的账务核心相比较,它具备数据的强一致性和业务耦合程度,具备数据传输的合规性,更具备某些场景下极高的访问密集度。以下列举Redis在互联网金融账务核心系统的一些典型的应用场景。
一、SESSION共享
SESSION是一种记录客户状态的机制,不同于COOKIE保存在客户端浏览器中,SESSION是保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是SESSION,当客户端和服务器需要再次交互时,只需要从该SESSION中查找该客户的状态就可以了。
如下图所示,用户请求首先会到达负载均衡,负载均衡会根据路由策略将请求分发到后端的服务实例,这就会出现第一次的请求交给实例M,下次的请求可能会是实例N,如果不做SESSION共享,到达实例N的请求会返回至客户端进行处理。
SESSION在多个服务和服务器之间共享,可多站点单点登录,也可实现单点登录的踢出功能。
二、活动抢券
活动抢券是互联网金融公司最常用的营销手段,对于互联网金融公司来说,本业务场景带来了几个需要解决的问题,系统的高并发压力;大量用户抢同一张优惠券带来数据一致性问题;券不能超发、重复发;由于券涉及到资金,也带资金安全的问题。由此可见,本场景主要的技术挑战是高并发、高响应、一致性、安全性等。
我们引用了电力行业的一种措施“削峰填谷”,顾名思义,就是在流量洪峰到来之前在系统负载较低的时候进行冷热数据的交换,和券数据的预热,用来分担领券活动后的高并发流量。而领券销售活动的业务特点是定时的洪峰流量对关联系统造成的巨大的并发访问的冲击。在此,从业务场景来说,如下图所示。
(1)提前把访问量蓄水,预热活跃用户,降低活动开始后的登录和风控压力。
(2)对Redis进行全局数据化处理,基于Redis内存高读写高QPS的特性,解决热点数据的高并发问题。热点数据均存储在内存中,采取Redis CLUSTER特性,进行多分片部署,通过高并发读写特性,提升系统吞吐量。下图为热数据的结构和场景描述。
(3)分布式锁的应用,在抢券过程中,可能出现多个用户同时在抢同一张券,或同一用户抢多张券的问题。当用户点击过快时,可能同一用户的两个线程同时通过了抢券资格的校验,在这种情况下该用户可以同时抢两张券。针对这个问题,我们通过对券号码、用户账号两个维度进行加分布式锁来解决。根据REDIS的锁服务设计,锁在数据结构中使用REDIS最简单的KEY,核心在于建立锁和释放锁,并保证绝对的安全。最简单的方法是用GET来检查锁,当获取不到信息,用SET来设置锁。这种方式最简单,但应用场景较为局限,不能保证独占锁,因为在高并发场景中,GET和SET之间的毫秒级延迟,不能确保其他线程去进行GET和SET操作。
针对这种风险,我们的锁设计考虑使用SET NX PX来实现,如下:SET KEY 唯一随机数值 NX PX 固定毫秒。设置KEY的值,仅在不存在时生效,并设置存活期为一个固定毫秒。和KEY关联的VALUE值在所有客户端和所有加锁请求中必须是唯一的。使用唯一随机值是为了更安全的释放锁,删除KEY值要增加逻辑且前置条件为KEY值存在和VALUE值是线程对应的那个唯一值。具体加锁实现方式如下。
三、支付渠道限额
本场景是支付路由场景下支付渠道限额的设计,是为了通过执行渠道路由支付时,避免资金不足时缺乏渠道控制,造成大量交易失败,导致处理异常。以下为路由示例图。
例如渠道A路由比例为70%、渠道B路由比例为30%,当支付请求过多时,渠道A的资金配额用尽会导致经过渠道A的支付交易失败,因此需要对配额进行计算,恢复配额金额。基于交易数据的强一致性和准确性,我们采取Redis命令的原子特性,即一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。具体的运算规范如下。
3.Redis使用中遇到的一些问题
在海量数据、高并发场景中,如何使用好Redis,约束KEY规范、合理确定VALUE值大小;合理设定参数大小,如TIMEOUT、MASMEMORY、MAXCILENT;合理分配缓存失效时间;合理加锁控制分布式单线程数据库查询写缓存操作等。在此,阐述在海量数据、高并发场景中遇到的一些问题。
一、大VALUE问题
背景:某时间点业务监控大量告警,经过排查,业务系统连接Redis报大量的TOO MANY CLUSTER REDIRECTIONS。
经过排查,发现业务系统连接REDIS异常,同时网络吞吐量异常汹涌。经过查看源码发现,当发生JEDISCONNECTIONEXCEPTION或者JEDISREDIRECTIONEXCEPTION时,会抛出此类异常,那我们着重分析下两种异常的场景。JEDISCONNECTIONEXCEPTION顾名思义,连接REDIS错误,连接节点1时候FAILED,尝试连接节点2仍旧FAILED,客户端会推断整个集群FAILD抛出异常,中断当前连接。JEDISREDIRECTIONEXCEPTION的产生由于两个异常产生,一个是MOVED导致,另一个是ASK导致,由此推断,MOVED是节点发生迁移引发的哈希槽不能识别,而且ASK则是某一个槽被设置为IMPORTING状态时,节点在接收到ASKING命令后才会接收关于这个槽的命令请求。
对Redis的内部排查,内部节点连接没有异常,CLUSTER INFO状态也是OK。基于以上分析得出结论,可能导致异常的场景为主从节点发生切换、迁移,网络因素导致更新槽位信息失败。验证了我们发现Redis主节点的网卡流量巨大,已经达到千兆网卡的最高值。于是我们查看了Redis慢查询日志,发现频繁调用某个KEY,而这个KEY存在大VALUE的问题,所以造成以上故障。
解决方式为业务逻辑进行修改,修改后解决问题。
二、缓存雪崩
背景:某时间点业务监控大量告警,链路耗时增加,数据库CPU飙升。
我们的缓存数据交换逻辑图如下
经过排查,发现数据库CPU飙升因为查询量激增,按照我们的数据交换逻辑来看,当客户端请求在Redis命中直接返回,如果MISS才去数据库查询,查询结果返回的同时将数据回写至Redis。数据库查询量在业务没有激增的情况下激增明显是异常现象,我们又排查了Redis的MONITOR记录,发现所有的查询都是MISS,存在阶段时间内请求击穿的现象,所以造成了缓存雪崩。
以上证据标明Redis本身是没有问题,那有问题的只是代码逻辑出现了异常。于是进行业务代码的排查,发现设置KEY失效时间时候没有进行校验,导致所有的KEY在同一时间失效,进而请求全部转向数据库,导致缓存的雪崩。
解决方式有两种:
(1)在缓存失效后,通过队列或者锁机制来控制访问数据库和缓存的线程数量,这也是一种简单的流控方式;
(2)超时时间增加校验标签,二次判断是否超时,如超时进行数据交换,由REDIS主动超时变为变动超时。
三、BGREWRITEAOF
背景:收到告警,REDIS服务器磁盘空间不足。
众所周知,Redis是内存数据库,数据均是缓存在内存中,唯一涉及磁盘的那就是持久化文件。顺着思路看,果不其然,存放持久化文件的磁盘已逼近水线,且持久化文件的大小是占用内存大小的三倍。官网上对BGREWRITEAOF是这样解释的,Redis BGREWRITEAOF命令用于异步执行一个AOF(APPENDONLY FILE) 文件重写操作。重写会创建一个当前 AOF 文件的体积优化版本。即使 BGREWRITEAOF 执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 BGREWRITEAOF 成功之前不会被修改。
简单来说,Redis的AOF机制类似于MYSQL的BINLOG,它会将所写的命令按照一定的频率(NO、ALWAYS、EVERY SECONDS)写入到日志文件中,用于REDIS重启后快速加载数据,恢复数据。写入的命令是存在重复的,这就造成持久化文件与实际占用内存大小的不匹配。BGREWRITEAOF的作用就是将重复的命令进行合并,以减少持久化日志的尺寸,减少内存的占用,快速恢复数据。举一个重写例子。
解决方法:Redis2.4版本后提供了自动重写的功能,大大改善了老版本中采取定时任务实现重写的方式。(1)AUTO_AOFREWRITE_PERC设置是持久化文件大小超过基准百分多少触发BGREWRITEAOF;(2)AUTO_AOFREWRITE_MIN_SIZE设置当前持久化文件大于多少字节后触发BGREWRITEAOF。
4.Redis的监控
目前Redis的监控方案大都采取遍历INFO结果进行数据的聚合、展示。一些核心指标如内存碎片比、分配内存总量、消耗内存峰值、命中数、未命中数、连接用户数、接收总连接数、每秒执行命令数、阻塞用户数等。
具体获取数值实现方式大体有两种:
(1)通过脚本方式获取数值;
(2)通过客户端方式连接获取数值。
展示方式也大体有两种:
(1)采取GRAFANA方式展示;
(2)通过JS页面方式展示。具体如下图
5.总结
综上即为Redis在互联网金融系统中的应用,包括集群的选型、业务场景的应用和使用中遇到的一些问题。同时,我们也在尝试将Redis技术应用到更多的场景中,也在进行Redis CLUSTER在多机房架构下进行大二层的组网,以探索更多的实践领域。