jedisCluster.incr,key值+1并返回,将 key 中储存的数字值增一,没有的先设为0再+1并返回,如果 key不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作
jedisCluster.expire,设置过期时间
jedisCluster.llen,列表长度
jedisCluster.hincrBy,把对象属性+对应数值
jedisCluster.ttl,当key不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。否则,以秒为单位,返回 key的剩余生存时间
jedisCluster.setex,为指定的key设置值及其过期时间。如果 key 已经存在, SETEX 命令将会替换旧的值
jedis.eval,执行lua脚本,redis服务器内置lua解释器
hdel 被成功删除字段的数量,可以和hset结合作为请求锁
加解锁最好使用lua脚本,能保证原子性,通过设置key标记值,再设置过期时间的方式没有原子性,一旦中间出问题没有设置成功过期时间,锁有可能永久不会释放了,详情参考LockImpl
1.lpush,从左往右添加元素,在key 对应 list的头部添加字符串元素 rpop右取
2.rpush,从右到左添加元素,在key 对应 list 的尾部添加字符串元素 lpop左取
redis5.0以上支持string hash list set zset geo hyperloglog stream等数据类型
redis模式:主从、哨兵、集群
使用场景:
String:
计数器(incr、decr)、分布式锁(setnx)、存储对象(对象转换为json字符串)
hash:
购物车,以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素,使用hincrby添加/减少商品个数,hgetall获取列表,hdel删除商品,hlen获取商品数量
较多fields(比如表很多字段,只挑选有用的一些字段存在redis)/序列化消耗较大/只需要获取少量fields的场景,大JSON对象序列化/反序列化比较耗费资源,尤其是在仅需要少量fields时,使用hash结构,可以减少性能和网络传输损耗
比如最好不要存储对象,因为存取需要序列化、反序列化比较耗时,只存有用字段,hmset/hmget很有用
list:
1.简单发布/订阅。list类型的lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能,可以实现简单的点对点消息队列。但是一般不建议使用,因为当前PH-MQ、MQCP的功能已经很完善了
2. 时序相关的列表缓存
通过lrange等命令可以批量获取list内容,可用于缓存用户发表的朋友圈列表、评论等时序相关数据,但是针对删除、更新等操作支持不佳
set:
黑名单/白名单/好友/关注集合,全量操作成本较高,redis提供了一些很实用的命令用于直接操作这些集合,如:
a. sinter命令可以获得并集
b. sismember命令可以判断集合中是否存在某值
c. scard命令可以获取sets数量
zset:
1. 排行榜,支持score传入票数等排行依据进行排序
2. 延时队列(带有延时功能的消息队列),比如用户下单30分钟后未付款自动关闭订单或者用户下单后延时短信提醒
score作为时间戳,自动按照时间最近的进行排序,启一个线程持续poll并设置park时间,完成延迟队列的设计,可参考Executors.newScheduledThreadPool中的DelayedWorkQueue
3. 滑动窗口限流(指定时间T内,只允许发生N次。我们可以将这个指定时间T,看成一个滑动时间窗口(定宽))
score作为时间戳,可统计最近一段时间内内的成员数量,实现滑动窗口限流
Geo:
1. 查找附近的人,需要传入坐标信息
获取方圆20km的10个人,按距离排序:georadiusbymember keyname ireader 20 km count 10 asc
HyperLogLog:
1. 带有一定精度损失的去重统计计数
海量数据处理,存在精度缺失问题,最高损失0.81%精度,适用场景:
a. 统计注册 IP 数
b. 统计每日访问 IP 数
c. 统计页面实时 UV 数
d. 统计在线用户数
e. 统计用户每天搜索不同词条的个数
不适用需要精准判断的场景,如黑名单,以免误伤
Redis 内存淘汰算法
随机
TTL(从设置了过期时间的 Keys 中获取最早过期的 一批 Keys,然后淘汰这些 Keys)
LRU(Least Recently Used,通过 Key 的最后访问时间来判定哪些 Key 更适合被淘汰)
LFU(Least Frequently Used,最不经常使用,访问次数)
Redis驱逐策略(基于淘汰算法)
noeviction:达到内存限额后返回错误,客户尝试可以导致更多内存使用的命令(大部分写命令,但DEL和一些例外)
allkeys-lru:为了给新增加的数据腾出空间,驱逐键先试图移除一部分最近使用较少的
volatile-lru:为了给新增加的数据腾出空间,驱逐键先试图移除一部分最近使用较少的,但只限于有过期设置的驱逐key
allkeys-random: 为了给新增加的数据腾出空间,驱逐任意key
volatile-random: 为了给新增加的数据腾出空间,驱逐任意key,但只限于有过期设置的驱逐key
volatile-ttl: 在设置了过期时间的key空间中,具有更早过期时间的key优先移除
Redis使用规范
禁止使用较长key,可以使用md5后的值
禁用keys、flushall、flushdb等命令
必须设置缓存过期时间,不能把Redis当做持久化存储的数据库(不绝对,长期热点数据比如配置信息可以不过期)
禁止存储大value,可以使用不同key分片存储在不同集群节点
使用批处理操作提高效率,原生的可以使用mget、mset,非原生可以使用pipeline(多个命令一起提交,不支持事务,主要是减少网络延迟)
一个应用对应一个Redis实例,避免相互覆盖数据
常见问题及解决方案
缓存失效(过期了,正常业务场景)
缓存击穿,缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。设置热点数据(比如配置)不过期,设置数据中过期时间大于实际过期时间,加锁读取数据库
对该key的请求进行拦截
缓存穿透
缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。设置排队锁排队访问
缓存雪崩
缓存在同一时间内大量键值过期(失效),需从数据库中获取数据,大量并发请求访问数据库,影响系统稳定。设置排队锁,在并发访问时,排队访问。缓存超时时间设置时,增加一个随机时间长度,避免所有缓存在同一时间失效。建立缓存备份机制(例如:运用本地缓存+分布式缓存互为备份,Redis自动备份)。定时任务更新缓存并续期
缓存污染(多发生在本地缓存,没有深拷贝,改变了对象引用值)
缓存一致性方案
弱一致性方案
先更新数据库,再更新缓存
这套方案,大家是普遍反对的。为什么呢?有如下两点原因。原因一(线程安全角度)同时有请求A和请求B进行更新操作,那么会出现(1)线程A更新了数据库(2)线程B更新了数据库(3)线程B更新了缓存(4)线程A更新了缓存这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。原因二(业务场景角度)有如下两点:(1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。(2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适
先删除缓存,再更新数据库(不推荐)
该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:(1)请求A进行写操作,删除缓存(2)请求B查询发现缓存不存在(3)请求B去数据库查询得到旧值(4)请求B将旧值写入缓存(5)请求A将新值写入数据库上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据
先更新数据库,再删除缓存(推荐)
先更新数据库,再延迟删除缓存
监听binlog更新redis(不推荐,容易出问题)
强一致性方案
通过加锁
Redis 最佳实践:
Redis 作为一款高性能内存型缓存数据库,广泛用于互联网应用场景中,本文总结了 Redis 的最佳实践。
- Redis 部署模式
Redis 部署时常见的模式有独享模式与共享模式(集群)。
独享模式:每个应用独享一个 Redis 实例,可以使用原有的 Redis。缓存使用量 < 1G,并发访问 > 1000qps,key 的数量大于 3000 个。优点是对缓存性能有较好的控制,缺点是成本高。
共享模式(集群):将多个应用共享一个 Redis 集群实例,与其他系统共用 Redis 实例。缓存使用量 1G,并发访问 > 1000qps,key 的数量小于 3000 个。优点是成本低,同时对于缓存的增长有更好的支撑性。
- 连接池的设置
连接池的设置可以有效地提升 Redis 实例的性能。常用的连接池参数设置如下:
-
连接池的最大连接数,过少会导致竞争、阻塞,过多会浪费资源。建议一般设置为 10 个并发连接在 Redis 操作均开销 2ms 的情况下,可以支撑最大 5000qps,建议设置在 20 以下。
-
连接池的最小空闲连接数。建议设置为 5 以下。
-
连接池资源耗尽时,连接尝试分配阻塞时间,超时即抛出异常,建议设为 1~2 秒。
-
与 Redis TCP 建立连接的超时时间,同时代表与 Redis TCP 连接上的读超时时间,建议一般设置为 3 秒。
如果业务量大,需要增大redis连接数,取redis的时候注意捕获异常,有可能连接数不够抛异常,需要保证从其他渠道能拿到数据,保证业务正常进行
使用分布式锁的场景:比如集群环境机器从redis队列取请求消费并计数,不用锁的话可能会造成计数数值读取不一致问题
设计缓存的时候要考虑二级缓存(一级redis,二级数据库,因为一级redis肯定是有个阈值的),请求上来的时候一级达到阈值就放到二级,取任务的时候补充数据
通过messageId+messageType作为数据库唯-键去替代redis分布式锁
a.落message b.判断是否uk冲突 c.如果是uk冲突,查出来做业务处理;如果不是uk冲突需要告诉消息中间件重发。简单化处理可以先抓异常,然后查库,查不到数据通知中间件重发,查到数据正常业务处理
java幂等方案 将所有的reqId存储至redis,3天后过期,每个请求到来后先去redis查询。此方案在生产使用,验证过每天百万级的数据量占用内存也还好