1. 背景
1.1 redis 的 noSql 属性
- redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用;
- redis 不仅支持简单的 key-value 类型的数据,同时还提供 list、set、hash 等数据结构的存储;
redis 支持数据的备份、集群等高可用功能,redis可支持 16 个数据库;
1.2 特性与局限
特性
- 性能极高,能读的速度是 110000 次/秒,写的速度是 81000 次/秒;
- 单个 key 可存入 512M 大小;
- 丰富的数据类型,支持类型包括:String、List、Set 及 Ordered Set;
- 原子性;
- 丰富的特性:支持 publish/subscribe,通知,key 过期等特性,可以做消息队列,如聊天室、IM;
局限性
在远程服务器上执行命令:redis-cli -h 127.0.0.1 -p 6379 -a 密码;
- 启动 redis 服务器:redis-server redis.windows.conf
- 启动 redis 客户端:redis-cli
- redis 服务相关命令
- 安装服务:redis-server —service-install redis.windows.conf
- 卸载服务:redis-server —service-uninstall
- 开启服务:redis-server —service-start
- 停止服务:redis-server —service-stop
- redis 关闭
- del key
- dump key //序列化给定 key 并返回被序列化的值
- exists key
- expire key seconds(miliseconds) //为给定 key 设置过期时间
- pttl(key) //以毫秒(秒)为单位,返回给定 key 的剩余生存时间,若为 -1 表示永久有效,-2 表示无效
- persist key //移除 key 的过期时间,key 将持久保持
- keys pattern //查找所有符合给定模式的 key,pattern 通配符,* 表示所有,?表示一个字符
- random key //从当前数据库中随机返回一个 key
- rename key newkey //重命名 key 10.move key db//从当前数据库移动到给定的数据库 db 中 11.type key ```
- 应用 expire key seconds
- 限时优惠活动信息;
- 网站数据缓存(对于一些需要定时更新的数据,如:积分排行榜);
- 手机验证码;
- 限制网站访客访问频率(如:一分钟最多访问 10 次);
key 命名建议:
应用场景
- string 通常用于保存单个字符串或 json 字符串数据;
- string 二进制安全,可把一个图片文件作为字符串存储;
- 计数器:常规 key-value 缓存应用,如统计粉丝数; ``` 赋值
- set key_name value
- setnx key value //只有在 key 不存在时设置 value【解决分布式锁 方案之一】
- mset key value[key value …] //同时设置一个或多个 key-value 对 取值
- get key_name
- getrange key start end //类似于 subString
- getbit key offset //对 key 所存储的字符串值,获取指定偏移量上的位(bit)
- mget key1 [key2 ..]
- getset key_name value //设置指定 key 的值,并返回 key 的旧值,当 key 不存在时,返回 nil
- strlen key 删除
- del key_name 自增/自减
- incr key_name //将 key 增 1,若 key 不存在,则 key 会被初始化为 0,然后再执行此操作
- incrby key_name 增量值 //将 key 加上指定增量值
- decr key_name
- decrby key_name 减值
字符串拼接
append key_name value //为指定的 key 追加至末尾,若不存在,为其赋值
```
3.3.2 hash
- 应用场景
- hash 常用于存储一个对象,例如用户信息数据,key 为用户 ID,value 为用户对象的姓名、年龄、生日等信息,value 以序列化方式存储;
- 缺点:增加序列化/反序列化开销,且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需对并发进行保护,引入 CAS 等复杂问题;
- 为什么不用 string 存储一个对象
- 用户 ID + 对应属性名称作为唯一标识,虽然省去序列化开销和并发问题,但用户 ID 重复存储,存在内存浪费 ``` 赋值
- hash 常用于存储一个对象,例如用户信息数据,key 为用户 ID,value 为用户对象的姓名、年龄、生日等信息,value 以序列化方式存储;
- hset key field value
- hmset key field value[field1 value1 …]
- hsetnx kkey field value //只有在字段 field 不存在时,设置 hash 表字段的值 取值
- hget key field
- hmget field[field1 ..]
- hgetall key //返回 hash 表中所有的字段和值
- hkeys key //获取所有 hash 表中的字段
- hlen key //获取 hash 表中字段的数量
- hexists key field //查看 hash 表 key 中,指定的字段是否存在 删除
- hdel key field1[field2..] //删除一个或多个 hash 表字段 自增/自减
- hincrby key field value
- hincrbyfloat key field increment
```
3.3.3 list
- 用来存储多个 有序 的 字符串。在
Redis
中,可以对列表的 两端 进行 插入(push
)和 弹出(pop
)操作,还可以获取 指定范围 的 元素列表、获取 指定索引下标 的 元素 等; - 内部编码
- ziplist(压缩列表):当元素 个数较少 且 没有大元素,目的是 减少内存的使用;
- linkedlist(链表): 当元素个数超过
512
个 或 某个元素超过64
字节;
- 应用场景
- 阻塞式消息队列、文章列表(对数据量大的集合进行删减)等; | 命令组合 | 对应数据结构 | | :—- | :—- | | lpush + lpop | Stack(栈) | | lpush + rpop | Queue(队列) | | lpush + ltrim | Capped Collection(有限集合) | | lpush + brpop | Message Queue(消息队列) |
添加
1. rpush key value [value ...] //从右边插入元素
2. lpush key value [value ...]
3. linsert key before|after pivot value //从列表中找到第一个等于 pivot 的元素,在其前或者后插入一个新的元素value,eg:linsert listkey before b redis
查询
1. lrange key start stop //获取指定范围内的元素列表,与 python 用法一致,eg:lrange 0 -1 可从左到右获取列表所有元素;
2. lindex key index //获取 list 指定索引下标的元素
3. llen key //获取列表长度
删除
1. lpop key //从列表左侧弹出元素
2. rpop key
3. lrem key count value //从等于value的元素删除count个元素,count>0时删右,count<0时删左,count=0删除所有;
4. ltrim listkey start end //裁剪列表,获得 start 与 end 之间的
修改
1. lset key index newValue //修改指定索引下标的元素
阻塞
1)列表为空
a. 如果timeout不为0,则客户端要等到timeout秒后返回,如果timeout=0,则客户端一直阻塞;
b. 期间若有数据添加进去,客户端立即返回;
2)列表不为空
a. 如果是多个键,那么brpop会从左至右遍历键,一旦有一个能弹出元素,客户端立即返回;
b. 如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值;
1. blpop key [key ...] timeout
2. brpop key [key ...] timeout
3.3.4 set
- 支持 集合内 的 增删改查,同时还支持 多个集合 取 交集、并集、差集;
- 集合类型 的 内部编码 有两种:
- intset(整数集合):集合中的元素都是 整数 且 元素个数 小于
set-max-intset-entries
配置(默认512
个)时,Redis
会选用intset
来作为 集合 的 内部实现,从而 减少内存 的使用; - hashtable(哈希表):当集合类型 无法满足
intset
的条件时,Redis
会使用hashtable
作为集合的 内部实现;
- intset(整数集合):集合中的元素都是 整数 且 元素个数 小于
- 应用场景:标签功能,如电商人群分类,针对不同 tag 类型的人群做针对性推荐; ``` 集合内的操作命令
- sadd key element [element …] //添加元素
- srem key element [element …] //删除元素
- scard key //统计元素个数,时间复杂度为o(1)
- sismember key element //判断元素是否存在,是的话返回1,否则返回0
- srandmember key [count] //随机返回具有指定个数的元素,默认 count 为 1
- spop key //从集合随机弹出元素,且此元素会从集合中删除
- smembers key //获取集合中的所有元素,且返回结果无序
集合间的操作命令
- sinter key [key …] //求多个集合的交集
- suinon key [key …] //求多个集合的并集
- sdiff key [key …] //求多个集合的差集
- sinterstore destination key [key …] //保存交集结果
- suionstore destination key [key …] //保存并集结果
- sdiffstore destination key [key …] //保存差集结果
```
3.3.5 sorted set(zset)
- 与 set 不同的是每个元素都会关联一个 double 类型的分数,redis 通过分数来为集合中的成员进行从小到大的排序;
- 有序集合的成员是唯一的,但 score 可以重复;
- 集合通过 hash 表实现,所以添加、删除、查找的复杂度都是 o(1);
- 集合最大可存储 40 多亿个成员;
- 应用场景:排行榜 ``` 赋值
- zadd key score1 member1 [score2 member2] //向有序集合添加一个或多个成员,或者更新已存在成员的分数
取值
- zcard key //获取有序集合的成员数
- zcount key min max //计算在有序集合中指定区间分数的成员数
- zrank key member //返回有序集合中指定成员的索引
- zrange key start stop[with scores] //通过索引区间返回有序集合中指定区间内的成员[低到高]
- zrevrange key start stop[with scores]
删除
- del key //移除集合
- zrem key member[member …]
- zremrangebyrank key start sop //移除有序集合中给定的排名区间的所有成员(低到高排序)
- zremrangebyscore key min max //移除有序集合中给定的分数区间的所有成员 ```
4. redis 主从复制
作用
命令slaveof。
优点:无需重启。缺点:不便于管理// 命令行使用 slaveof ip port // 使用命令后自身数据会被清空,但取消slave只是停止复制,并不清空复制代码
修改配置。
优点:统一配置。缺点:需要重启// 配置文件中配置 slaveof ip port slave-read-only yes //只允许从节点进行读操作复制代码
4.2 方式
4.2.1 全量复制
概念
- 用于初次复制或其它无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作,当数据量较大时,会对主从节点和网络造成很大的开销
- 过程
- Redis内部会发出一个同步命令,刚开始是Psync命令,Psync ? -1表示要求master主机同步数据;
- 主机会向从机发送run_id和offset,因为slave并没有对应的 offset,所以是全量复制;
- 从机slave会保存主机master的基本信息;
- 主节点收到全量复制的命令后,执行bgsave(异步执行),在后台生成RDB文件(快照),并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令;
- 主机发送RDB文件给从机;
- 发送缓冲区数据;
- 刷新旧的数据。从节点在载入主节点的数据之前要先将老数据清除;
- 加载RDB文件将数据库状态更新至主节点执行bgsave时的数据库状态和缓冲区数据的加载。
开销
概念
- 用于部分复制是Redis 2.8以后出现的,用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销,需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制
- 过程
- 读写分离
- 复制数据存在延迟(如果从节点发生阻塞)
- 从节点可能发生故障
- 主从配置不一致
- 例如maxmemory不一致,可能会造成丢失数据
- 例如数据结构优化参数不一致:造成主从内存不一致
规避全量复制
- 第一次全量复制不可避免,所以分片的maxmemory减小,同时选择在低峰(夜间)时,做全量复制。
- 复制积压缓冲区不足
增大复制缓冲区配置rel_backlog_size例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。
复制风暴
master节点重启,master节点生成一份rdb文件,但是要给所有从节点发送rdb文件。对cpu,内存,带宽都造成很大的压力5. redis 集群
5.1 介绍
5.2 分布式方案
5.2.1 客户端方案
客户端 就已经决定数据会被 存储 到哪个
redis
节点或者从哪个redis
节点 读取数据。其主要思想是采用 哈希算法 将Redis
数据的key
进行散列,通过hash
函数,特定的key
会 映射 到特定的Redis
节点上。
客户端分区方案 的代表为Redis Sharding
,Redis Sharding
是Redis Cluster
出来之前,业界普遍使用的Redis
多实例集群 方法。Java
的Redis
客户端驱动库Jedis
,支持Redis Sharding
功能,即ShardedJedis
以及 结合缓存池 的ShardedJedisPool
。5.2.2 代理分区方案
客户端 发送请求到一个 代理组件,代理 解析 客户端 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。
- 优点:简化 客户端 的分布式逻辑,客户端 透明接入,切换成本低,代理的 转发 和 存储 分离。
- 缺点:多了一层 代理层,加重了 架构部署复杂度 和 性能损耗。
代理分区 主流实现的有方案有 Twemproxy
和 Codis
。
5.2.3 查询路由方案
客户端随机地 请求任意一个 Redis
实例,然后由 Redis
将请求 转发 给 正确 的 Redis
节点。Redis Cluster
实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis
节点 转发 到另一个 Redis
节点,而是在 客户端 的帮助下直接 重定向( redirected
)到正确的 Redis
节点;
- 优点
- 无中心节点,数据按照 槽 存储分布在多个
Redis
实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。
- 无中心节点,数据按照 槽 存储分布在多个
缺点
key
批量操作 支持有限- 类似
mset
、mget
操作,目前只支持对具有相同slot
值的key
执行 批量操作。对于 映射为不同slot
值的key
由于执行mget
、mget
等操作可能存在于多个节点上,因此不被支持;
- 类似
key
事务操作 支持有限- 只支持 多
key
在 同一节点上 的 事务操作,当多个key
分布在 不同 的节点上时 无法 使用事务功能;
- 只支持 多
key
作为 数据分区 的最小粒度- 不能将一个 大的键值 对象如
hash
、list
等映射到 不同的节点;
- 不能将一个 大的键值 对象如
- 不支持 多数据库空间
- 单机 下的
Redis
可以支持16
个数据库(db0 ~ db15
),集群模式 下只能使用 一个 数据库空间,即db0
;
- 单机 下的
复制结构 只支持一层
- 从节点 只能复制 主节点,不支持 嵌套树状复制 结构;
5.5 搭建【实践】
6. Java 连接 redis 数据库【实战】
6.1 RedisUtil 工具类
6.2 SpringData 整合 redis
7. 其他
7.1 redis 发布与订阅功能
- 从节点 只能复制 主节点,不支持 嵌套树状复制 结构;
订阅
- subscribe channel [channel …] //订阅给定的一个或多个频道的信息
- psubscribe pattern [pattern …] //订阅一个或多个符合给定模式的频道
- 发布
- publish channel message //将信息发送到指定的频道
- 退订
- unsubscribe [channel [channel …]]
- punsubscribe [pattern [pattern …]]
-
7.2 redis 多数据库
redis 下,数据库由一个整数索引标识,而不是一个数据库名称。默认情况下,一个客户端连接到数据库 0,范围 0~15 整数;
select 2 //数据库切换 move key 名称 数据库 //移动数据 flushdb //清除当前数据库的所有 key flushall //清除所有 redis 数据库 key
7.3 redis 事务
redis 事务可以一次执行多个命令;
- redis 会将一个事务中的所有命令序列化,然后按顺序执行;
- 执行中不会被其他命令插入,不许出现加塞行为;
- 一个事务从开始到执行经历的三个阶段:开始事务、命令入队、执行事务;
- 事务的错误处理
- 【执行阶段】若执行的某个命令报出了错误(业务逻辑问题),则只有报错的命令不会被执行,而其它命令都会执行,不会回滚;
- 【组队阶段】队列中的某个命令出现了报告错误执行时整个所有队列都会被取消;
应用场景:商品秒杀活动、转账活动等;
discard //取消事务,放弃执行事务块内的所有命令 exec //执行所有事务块内的命令 multi //标记一个事务块的开始 unwatch //取消 watch 命令对所有 key 的监视 watch key[key ...] //监视一个或多个 key,若在事务执行之前这个 key 被其他命令更改则事务被打断
7.4 redis 内存管理
内存设置:建议内存空间不超过 1G 256-512M;
- 为数据设置超时时间;
-
7.5 redis 数据淘汰策略
若无淘汰策略或找不到适合淘汰的 key 时,直接返回 out of memory 错误
- 最大缓存配置: maxmemory 512G
6 种数据淘汰策略
- volatile-lru:从一设置过期时间的数据集中挑选最近最少使用的数据淘汰;
- volatile-lfu:从已设置过期的 keys 中,删除一段时间内使用次数最少的;
- volatile-ttl:从已设置过期时间的数据集中挑选最近将要过期的数据淘汰;
- volatile-random:从已设置过期时间的数据集中随机选择数据淘汰;
- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰;
- allkeys-lfu:从所以 keys 中删除一段时间内使用次数最少的;
- allkeys-random:从数据集中随机选择数据淘汰;
- no-enviction:默认配置,不采用任何淘汰策略;
7.6 redis 持久化
RDB(快照)
- 默认持久化方式,将内存中的数据已快照的方式写入二进制文件中,默认文件名:dump.rdb
- 特点:在指定时间间隔后执行
- 优点:保存和还原数据极快,适用于灾难备份
- 缺点:不适合小内存机器,RDB 机制符合要求就会照快照
- 快照条件
- 服务器正常关闭时: redis-cli shutdown
- key 满足一定条件,会进行快照,如 save 900 1 //每 900 秒至少 1 个 key 发生变化,产生快照
AOF
- 适用于不能产生任何修改丢失的场景
- 实现:将收到的每一个写命令通过 write 函数追加到文件中(默认为 appendonly.aof),当 redis 重启时会通过重新执行文件中保存的写命令在内存中重建整个数据库的内容
- 3 种方式(默认:每秒 fsync 一次)
- appendonly yes //启用 aof 持久化方式
- appendfsync always //收到写命令就立即写入磁盘,最慢,但保证完全的持久化
- appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
- appendfsync no //完全依赖 os,性能最好,持久化没保证
- 缺点:冗余命令导致持久化文件过大
7.7 redis 缓存
缓存穿透
缓存穿透
- 查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义,在流量大时,可能导致 DB 挂掉;
- 解决
- 布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被 这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力;
- 如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),对空结果进行限时缓存,设置很短的过期时间;
- 缓存击穿
- 缓存在某个时间点过期时,恰好在此节点对这个 Key 有大量并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮;
缓存雪崩
实时同步
- 适用:对强一致性要求较高
- 实现:查询时缓存有限,若无则经数据库查询后保存到缓存;更新缓存时先更新数据库,再将缓存设置过期
- 使用
- @Cacheable:查询时使用,注意 Long 类型需转换为 String 类型,否则会抛出异常
- @CachePut:更新时使用,使用此注解,一定会从 DB 上查询数据
- @CacheEvict:删除时使用
- @Caching:组合用法
- 异步队列
- 阿里工具 canal
- 采用 UDF 自定义函数的方式
参考
深入剖析Redis系列(三) - Redis集群模式搭建与原理详解
缓存穿透,缓存击穿,缓存雪崩解决方案分析
Redis主从复制的原理