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(不推荐,容易出问题)
强一致性方案
通过加锁