Redis
五种数据结构
- String字符串
- hash哈希
- list列表
- set集合
- zset有序集合
String
存储对象
-
SET KEY Value(json格式数据) -
MSET user:1:name "XXX" user:1:age 25 MGET user:1:name user:1:age
计数器
INCR KEY
Hash(KEY-VALUE)
对象缓存
-
HMSET KEY Field-Value [Field-Value...] HMGET KEY Field [Field....]
购物车
-
添加商品
HSET KEY Field Value#存储键值 HSET car:userId productId num -
增加数量
HINCRBY KEY Field num#为field的值加上num
HINCRBY ar:userId productId num
-
查询商品总数
HLEN KEY#返回key中field数量 HLEN car:userId -
删除商品
HDEL KEY Field#删除指定field HDEL car:userId productId -
获取所有商品
HGETALL KEY#返回key中所有键值 HGETALL car:userId
优点
- 同类数据归类整合储存,方便数据管理
- 相比string操作消耗内存与cpu更小
- 相比string储存更节省空间
缺点
- 过期功能不能使用在field上,只能用在key
- Redis集群架构下不适合大规模使用
List
常用数据结构
- Stack(栈):LPUSH+LPOP ->FILO 左加+左取。 先进来的元素最后才能拿到
- Queue(队列):LPUSH+RPOP。 左加+右取。 先进先出
- Blocking MQ(阻塞队列):LPUSH+BRPOP。
消息流
-
放消息
LPUSH KEY Value LPUSH msg:1888 10088
- 取消息
```shell
LRANGE KEY 开始位置 结束为止
LRANGE msg:1888 0 5
Set
抽奖(举例)
-
点击参与抽奖加入集合
SADD KEY Value#添加元素 -
查看参与抽奖所有用户
SMEMBERS KEY#获取key中所有元素 -
筹集count名中奖者
SRANDMEMBER KEY [COUNT] # 从集合中随机选出count元素,元素不从key中删除 SPOP KEY [COUNT]#从集合中随机选出count元素,元素不从key中删除
点赞
- 点赞
SADD KEY Value#添加元素 - 取消点赞
SREM KEY Value#删除元素 - 检查用户是否点赞
```shell
SISMEMBER KEY Value#判断value是否存在与key中
- 获取点赞的用户列表
SMEMBERS KEY #获取key中所有元素 - 获取点赞用户数
SCARD KEY e#获取元素个数
集合操作
-
交集
SINTER KEY1 KEY2 KEY3 ... -
并集
SUNION KEY1 KEY2.... -
差集(以第一个集合为准,后面所有集合的并集)
SDIFF KEY1 KEY2 ....
缓存雪崩
指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方法
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队
- 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存
缓存穿透
指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方法
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
缓存击穿
指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
解决方法
- 设置热点数据永远不过期
- 加互斥锁,互斥锁
缓存预热
就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存更新
- 先删缓存,再更新数据库
- 先更新数据库,再删缓存
- 先更新数据库,再更新缓存
- read/write through
- 写回,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级
缓存淘汰策略
- 当内存不足以容纳新写入数据时,新写入操作会报错。
- 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key
- 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key
- 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
- 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
- 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
哨兵机制
哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:
- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
分布式锁
synchronized重锁针对的是单个JVM,若项目复制在多台tomcat下,也会出现相同操作。解决这样的办法就是使用分布式锁,因为redis是单线程处理程序。
setnx:只有key不存在才会进行set,若存在则不做任何操作
程序问题
- 若抢到锁的线程抛出异常:将业务代码用try-finally,在finally中释放锁
- 抢到锁的服务宕机:设置超时时间
- 在高并发情况下,业务执行时间大于超时时间,导致后来的线程获得锁,前者线程执行完释放了后来的锁:自己释放自己的锁,加个标识,开个小线程给锁续命。
Redisson