redis 使用历程
为什么使用redis缓存?
答:之前是没有使用redis的,直接用Java代码写类缓存功能,有些系统参数方面也是采用直接查询数据库。此中间还出现过一些其他问题,后面才选择redis。
先说下不使用缓存会造成哪些影响:
1、在项目中,为避免不断查询数据库,给数据库造成压力,需要将生效的用户充值地址加入到缓存中,刚开始直接使用ArrayList,但是在测试阶段就发现,在不断将新的地址加入到ArrayList中,会出现添加异常情况,同时导致应用假死情况,后来经排查发现ArrayList非线程安全,然后采用CopyOnWriteArrayList(该list适合读多写少的情况),这个很好的解决了问题。虽然CopyOnWriteArrayList解决了并发问题,但是在性能上不理想,每次有新的地址添加到list中都会创建新的列表,并将之前的列表通过 Arrays.copyOf(elements, len + 1);
效率低下,且如果缓存的用户地址太多(比如几万、十几万时),在每次发生新增是会造成内存使用短期飙升的情况。
2、同时在项目中还有些系统配置参数,在业务处理中会根据配置参数执行对应的逻辑(比如:是否允许提现、每日最大提现额度,定时任务是否执行等),但是这些系统参数基本上不怎么变动,在不断的查询数据库也是一种浪费。
3、对一些用户访问量大的数据,直接访问数据库,在高并发情况下,可能会导致项目挂掉。
4、自己写的本地缓存方案可能存在一些潜在的未知问题。
基于上述情况,开始加入缓存技术,并就缓存方案进行选型。
缓存方案选型
目前市面上主流的缓存方案有两种:redis、memcache。我为什么放弃memcache而选择redis呢?
1、从支持的数据类型方面:memcache只支持简单数据类型,需要客户端自己处理复杂对象;而redis支持的类型,除String、List、Hash、Set、SortedSet外,还支持pub/sub(不支持持久化,如果消费者当时不在现场,则消息丢失,可靠的操作还是选择消息队列)、Transaction、HyperLogLog(非精确的去重统计,伯努利实验及抛硬币)、bloomFilter(避免击穿)等功能。
2、从持久化方面:memcached不支持持久化,redis支持持久化(可通过aof,rdb)
3、从线程角度:redis是单线程(但单机就可支持10万的并发量,且不需要考虑上下文切换等问题,如果并发量太大,可以考虑多机部署方案【主从复制、哨兵模式、集群模式等】),memcache多线程。
4、综上,我选择redis作为项目的缓存方案。
使用redis常见问题
1、缓存集中到期问题(缓存设置的时候,设置了固定的到期时长,如果同一段时间集中加入缓存,则会存在集中过期的情况,在刚好过期时吗,如果有大量请求进来,则可能会存在短暂卡顿,或者雪崩现象,甚至导致访问数据库瘫痪)
解决方案:
1、在某些基本不怎么变动的缓存,设置永不过期(必要时手动更新)
2、对需要设置过期时间的, 过期时间大小加个随机数,将集中过期变的分散些,降低缓存雪崩概率。
2、缓存穿透&缓存击穿
先说【缓存穿透】,指的是请求查询缓存及数据库均不存在的数据。比如查询用户id(一般都是正数递增的),你查询负数或者其他字符串肯定不会存在,且请求会不断打到db上。避免方案是对请求数据的合法性进行校验(权限校验、参数校验),还有就是采用布隆过滤器(原理是采用bitmap位图,缓存的key会议bit的方式存入布隆过滤器上, 如果在布隆过滤器上能找到,则可能存在,否则肯定不存在),然后在进行缓存查询、数据库查询。
缓存击穿:在key失效瞬间(比如过期失效),大量请求打到数据库上。
3、分布式锁
说白了就是通过setnx(key不存在则设值)命令向缓存中设值。如果设值成功则获取锁,为避免加锁后忘记释放,通过expire设值锁失效时间;若设值不成功,则加锁失败。但是有可能在加锁后,还没来得及设值过期时间,程序挂掉,需要采用setIfAbsent(K key, V value, long timeout, TimeUnit unit)
记得是这个方法。
但是:虽然解决了锁过期无法释放的问题,但设置的过期时间不能保证我当前获取锁的线程执行完成了,假如业务线比较长,在锁过期后还没有正常执行完成,又有可能引发其他问题。
4、假如在线上环境中有上亿个key,如何找出以某些字符串开头key的值,为什么不能使用keys *?
首先说下为什么不能使用keys * ,因为redis是单线程。使用keys 时会导致其他线程阻塞,导致其他请求进来时需要等待,直到key【且key没有limit,会全部遍历】 执行完成,如果刚好在执行key命令时,大量请求进来,那就GG了。推荐方案:采用scan方式,该命令可无阻塞的获取指定模式的key列表。该命令的缺点是可能会存在一定的重复(为什么会存在重复呢?因为redis底层采用hashMap进行的实现,也就是说数据结构是数组加链表,如果在查询过程中存在扩容或者锁容时,就会出现重复),需要业务系统进行去重。
5、redis异步消息
可以采用list数据结构进行处理。lpush 加入消息、然后rpop获取消息,如果list为空,则阻塞。或者rpush加入消息,lpop获取消息。这样保证了消息的先进先出;如果lpush、lpop或rpush、rpop则是先进后出。
6、redis延时队列
可通过sortedSet数据结构,通过zadd添加消息,每次添加时对score自增(建议方案是采用时间戳,但是别神经病调整系统时间,不然就把自己找地埋了吧),消费时使用zrangebyscore(读取数据后,业务方根据业务逻辑处理)
7、Pipeline操作
redis的管道命令,可以批量将多个请求发送给服务端,中间不需要等待请求的回复,只需要最后一并读取结果就可以了。但是在集群模式下,不能直接使用Pipeline,否则可能会报错。
原因:集群模式下,各集群节点是分配了不同的slot槽位,而请求的key在hash及crc16计算后可能存在不同的节点上,所以直接使用会报错。可以根据redis集群的槽位分配请求,对批量数据进行先计算分配,然后再发起请求。
8、过期策略
定期清理:定期随机抽取过期的key进行清理
惰性清理:被访问时,如果发现已过期则清理,且不返回数据给调用方。
9、redis持久化
RDB模式:
原理:是redis会单独创建(fork)一个与当前进程一模一样的子进程来进行持久化,这个子线程的所有数据(变量。环境变量,程序程序计数器等)都和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的io操作,这就确保了极高的性能
触发机制:shutdown时,如果没有开启aof,会触发配置文件中默认的快照配置;执行命令save或者bgsave save是只管保存,其他不管,全部阻塞 bgsave: redis会在后台异步进行快照操作,同时可以响应客户端的请求。
AOF
原理:将Reids的操作日志以追加的方式写入文件,读操作是不记录的(配置文件中appendonly默认不开启)
优势:优化数据丢失问题,rdb会丢失最后一次快照后的数据,aof丢失不会超过2秒的数据
触发机制:
no:表示等操作系统进行数据缓存同步到磁盘(快,持久化没保证)
always:同步持久化,每次发生数据变更时,立即记录到磁盘(慢,安全)
everysec:表示每秒同步一次(默认值,很快,但可能会丢失一秒以内的数据)
auto-aof-rewrite-percentage 100 当AOF文件增长到一定大小的时候Redis能够调用 bgrewriteaof对日志文件进行重写 。当AOF文件大小的增长率大于该配置项时自动开启重写(这里指超过原大小的100%)
auto-aof-rewrite-min-size 64mb:当AOF文件增长到一定大小的时候Redis能够调用 bgrewriteaof对日志文件进行重写 。当AOF文件大小大于该配置项时自动开启重写
混合持久化:4.0版本默认关闭,5.0 版本默认开启。通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用;注意:同时开启aof跟rdb,aof优先(数据更加全面)
9、同步机制
第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog
10、集群方案
1、主从模式
2.6版本之后,slave默认只读(缺点:主机挂掉后不能更新数据)
2、哨兵模式(高可用)
在master挂掉时,将slave提升为master。中间会有几秒钟的不可用状态,因为在进行选举。
哨兵原理:
主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,小兵节点就会将其进行主管下线
客观下线:哨兵节点在对主节点进行主管下线后,会听过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的数量达到一定数值,则对该主节点进行客观下线
哨兵定时任务:
每10秒通过想主从节点发送info命令获取最新的主从结构;发现slave节点,确定主从关系
每2秒通过发布订阅功能获取其他哨兵节点的信息
没秒通过向其他节点发送平命令进行心跳检测,判断是否下线
哨兵选举:raft算法(基本思路是先到先得,一般情况谁先完成客观下线谁就会成为领导者)
新节点选举:
1、过滤掉不健康的从节点
2、选择优先级最高的从节点(由replica-priority指定);如果优先级无法区分
3、选择复制偏移量最大的从节点;如果仍无法区分
4、选择runid最小的从节点
建议:哨兵节点大于一个(应该是奇数),一方面增加哨兵节点的冗余,冰面哨兵本身成为高可用的瓶颈,另一方面减少对下线的误判,同时也应该部署在不同的物理机上
3、集群模式(高扩展)
手动搭建步骤:配置文件开启集群配置;meet个节点;设置slot槽;设置主从;
脚本执行:
5.0之前需要安装环境并执行redis-trib.rb;
5.0之后可用下面命令执行:
/usr/local/bin/redis-cli --cluster create 192.168.0.104:7000 192.168.0.104:7001 192.168.0.104:7002 192.168.0.104:7003 192.168.0.104:7004 192.168.0.104:7005 --cluster-replicas 1
集群的扩容与缩容,注意slot分配
原创不易、转载请注明来源