Redis
视频地址:
https://www.bilibili.com/video/BV1cr4y1671t?p=10&spm_id_from=pageDriver&vd_source=2d21b75bf8d9e3a7a8e0f7e21a965fe2
redis 是单线程的
cmd 链接Redis:redis-cli.exe -h 127.0.0.1 -p 6379 -a 123456
进入到redis安装目录
-h: 指定ip地址
-p:指定端口
-a:指定密码
1.数据类型
1.1. 基本类型
- String
- Hash
- List
- Set
- SortedSet
1.2. 特殊类型
- GEO
- BitMap
- HyperLog
1.3. 常用命令
用":"划分层级,例如set com:li:name lisi;set com:li:age 12;
keys 查找符合的key:keys * 查所有,keys “com:li:*”
del 删除指定的key:del key
exists 判断key是否存在:exists key
expire 给一个可以添加过期时间:expire key 60(秒数)
ttl 查看一个可以剩余有效期:ttl key;返回-1:永久有效,-2:key不存在
1.4. String
底层都是字节数组形式存储,不能超过512M
set 添加/修改 一个String类型的键值对:set key value
get 根据key获取String类型的value:get key
mset 批量添加多个String类型的键值对:mset key1 value1 key2 value2
mget 根据多个key获取多个String类型的value:mget key1 key2
incr 让一个整形的key自增1:set age 18; incr age
incrby 让一个整形的key自增并指定步长:incrby age 2;incrby age -1
incrbyfloat 让一个浮点类型的数字自增并指定步长:set score 88.5;incrbyfloat score 0.3
setnx 添加一个String类型的键值对,前提是这个key不存在,否则不执行:setnx key value; set key value nx;
setex 添加一个String类型的键值对,并且指定有效期:setex key 10 value;set key value ex 10;
1.5. Hash
也叫散列,其value是一个无序字典,类似于java中的HashMap结构
hset key field value 添加或者修改hash类型key的field的值:hset com:li:user:1 name li
hget key field 获取一个hash类型key的field的值:hget com:li:user:1 name
hmset 批量添加多个hash类型key的field的值:hmset com:li:user:2 name lilei age 19 sex 1
hmget 批量获取多个hash 类型key的field的值:hmget com:li:user:2 name age sex
hgetall 获取一个hash类型的key中的所有的field和value:hgetall com:li:user:2
hkeys 获取一个hash类型的key中的所有field:hkeys com:li:user:2
hvals 获取一个hash类型的key中的所有的value:hvals com:li:user:2
hincrby 让一个hash类型key的字段值自增并指定步长:hincrby com:li:user:2 age 1
hsetnx 添加一个hash类型的key的field值,前提是这个field不存在,否则不执行:hsetnx com:li:user:2 age 11
1.6. List
与java中的linkedList类似,可以看作是一个双向链表。支持双向检索
特点:有序,元素可以重复,插入和删除快,查询速度一般。
lpush key element 向列表左侧插入一个或多个元素:lpush list a1 a2 a3 a4 a5
lpop key 移除并返回列表左侧的第一个元素,没有则返回null:lpop list
rpush key element 想列表右侧插入一个或多个元素:rpush list b1 b2 b3 b4 b5
rpop key 移除并返回列表右侧的第一个元素,没有则返回null:rpop list
lrange key star end 返回一段角标范围内的所有元素:lrange list 0 -1
blpop brpop 和 lpop rpop类似,只不过在没有元素的时候等待指定时间,而不是直接返回null:blpop list 10(秒数)
1.7. Set
与java中的HashSet类似,
特点:无序,元素不可重复,查找快,支持交集并集差集等功能
sadd key member 向set中添加一个或多个元素:sadd s1 a b c
srem key member 移除set中的指定元素:srem s1 a
scard key 返回set中元素的个数:scard s1
sismember key member 判断一个元素是否存在于set中:sismember s1 a
smembers 获取set中的所有元素:smembers s1
sinter key1 key2 求两个集合的交集
sdiff key1 key2 求两个集合的差集
sunion key1 key2 求两个集合的并集
1.8. SortedSet
是一个可排序的set集合,用于排行榜这样的功能
特点:可排序,元素不重复,查询速度快
zadd key score member 添加一个或多个元素到SortedSet中,如果已经存在则更新其score值:zadd zset 85 jack 89 lucy 82 rose 95 tom 78 jerry 92 amy 76 miles
zrem key member 删除SortedSet中的一个指定元素:zrem zset tom
zscore key member 获取SortedSet中指定元素的score值:zscore zset amy
zrank key member 获取SortedSet指定元素的排名:zrank zset rose;zrevrank zset rose
zcard key 获取SortedSet中元素个数:zcard zset
zcount key min max 统计score值在给定范围内的元素个数:zcount zset 0 80
zincrby key increment member 让SortedSet中的指定元素自增,步长为指定的increment值
zrange key min max 按score排序后,获取指定排名范围内的元素:zrange zset 0 2;zrevrange zset 0 2
zrangebyscore key min max 按照score排序后,获取指定score范围内的元素:zrangebyscore zset 0 80
zdiff,zinter,zunion 求差集,交集,并集
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加rev即可
1.9. GEO数据结构
GEO是Geolocation的简写形式,代表地址坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据。
cmd 进入redis 使用 help commond 查询用法。例如:help geoadd
- geoadd 添加一个地理空间信息,包含:经度,纬度,值
- geodist 计算制定的两个点之间的距离并返回
- geohash 将制定的member 的坐标转为hash字符串形式并返回
- geopos 返回指定的member的坐标
- georadius 指定圆心,半径,找到该圆内包含的所有member,并按照与圆心之间的距离排序后返回。6.2以后已废弃
- geosearch 在制定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或者矩形。6.2新功能
- geosearchstore 与geosearch功能一致,不过可以把结果存储到一个指定的key。6.2新功能
1.10. BitMap(位图)
redis中是利用string类型数据解耦实现bitmap,因此最大上线是512M
cmd 进入redis 使用 help commond 查询用法。例如:help geoadd
- setbit 向指定位置存放一个0/1
- getbit 获取指定位置的bit值
- bitcount 统计bitMap中值为1 的bit位的数量
- bitfield 操作(查询,修改,自增)BitMap中bit数组中的指定位置的值
- bitfield_ro 获取BitMap中bit数组,并以十进制形式返回
- bitop 将多个BitMap的结果做位运算(与,或,异或)
- bitpos 查找bit数组中指定范围内第一个0/1出现的位置
1.11. HyperLogLog 用法
概念
- UV(Unique Visitor):独立访客量,指访问人数,多次访问算一人
- PV(Page View):页面访问量,多次点击算多次。往往用来衡量网站的流量
Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。
redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。
- pfadd 添加一个key value,重复元素只会添加一次
- pfcount 得到一个key的value数量
- pfmerge 合并多个可以的数据
1.12. Redis的消息队列
list
- 利用redis存储,不受限于JVM内存上线
- 基于redis的持久化机制,数据安全性有保证
- 可以满足消息有序性
- 无法避免消息丢失
- 只支持单消费者
利用list数据类型的blpop brpop,当取值为空时会阻塞等待
PubSub(发布订阅)
是redis2.0版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个channel,生产者向对应channel发送消息后,所有订阅者都能收到相关消息。
- subscribe channel 订阅一个或多个频道
- publish channel msg 向一个频道发送消息
- psubscribe pattern 订阅与pattern格式所匹配的所有频道
采用发布订阅模型,支持多生产,多消费
缺点:不支持数据持久化,无法避免消息丢失,消息堆积有上限,超出时数据丢失
stream
stream是redis5.0引入的一种新数据类型,可以实现一个功能非常完善的消息队列
- Redis 的java客户端
Jedis:这是 Java 语言操作 Redis 最常用的客户端之一。它提供了丰富的 API 来执行各种 Redis 命令。
例如,使用 Jedis 可以方便地进行字符串的设置和获取操作。
Lettuce:也是一个用于 Java 的 Redis 客户端,它基于 Netty 实现了非阻塞和反应式编程模型。
在高并发场景下表现出色。
Redis-py:适用于 Python 语言的 Redis 客户端。
可以轻松实现 Python 与 Redis 的交互,比如存储和读取列表数据。
Redisson:一个功能强大的 Java 客户端,提供了分布式对象和服务的支持。
常用于实现分布式锁等功能。
2.1.# Jedis
这是 Java 语言操作 Redis 最常用的客户端之一。它提供了丰富的 API 来执行各种 Redis 命令。 例如,使用 Jedis 可以方便地进行字符串的设置和获取操作
看代码com.li.redis.jedis.JedisConfig; test.java.com.li.redis.JedisTest;
2.2.# SpringDataRedis
Lettuce也是一个用于 Java 的 Redis 客户端,它基于 Netty 实现了非阻塞和反应式编程模型。
在高并发场景下表现出色。
注意:这里有两个分类,redisTemplate,stringRedisTemplate,区别在于存入对象的序列化的问题,推荐使用stringRedisTemplate手动序列化编写工具类
看代码 com.li.redis.springDataRedis.RedisConfig; test.java.com.li.redis.SpringDataRedisTest
2.3.Redisson:
一个功能强大的 Java 客户端,提供了分布式对象和服务的支持。
常用于实现分布式锁等功能。
- Redis常见问题
3.1. Redis实现session共享
用户登录一般是使用session存储用户信息,在集群环境下各个Tomcat中存储的session不能及时共享。
处理方法:用户登录之后 生成 TOKEN 代替session,TOKEN作为key,存放用户信息保存在redis中。用户请求服务器携带TOKEN,在拦截器中做校验,校验通过刷新TOKEN有效期,校验失败返回登录
3.2. Redis缓存更新策略
三种策略:
- 内存淘汰
- 过期淘汰
- 主动更新
- cache aside 缓存调用者在更新数据库的同时完成对缓存的更新
- 一致性良好
- 实现难度一般般
- read/write through 缓存与数据库集成为一个服务,服务保证两者的一致性,对外暴露API接口。调用者无需知道自己操作的是数据库还是缓存,不用关心一致性
- 一致性优秀
- 实现复杂
- 性能一般
- write back 缓存调用者的CRUD都针对缓存完成。由独立线程异步的将缓存数据写入到数据库中,实现最终一致性
- 一致性差
- 性能好
- 实现复杂
- cache aside 缓存调用者在更新数据库的同时完成对缓存的更新
低一致性需求:使用Redis自带的内存淘汰机制
高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作
- 缓存命中直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
3.3. Redis缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会直接到达数据库,当这些请求并发时会造成数据库崩溃。
解决方法:
- 缓存空对象,当数据库返回空对象的时候,向缓存中存入一个空的对象。这样短时间内请求再次过来就不会到达数据库
- 实现简单,维护方便
- 额外的内存消耗
- 可能造成短期的不一致,尽量设置较短的过期时间
- 实现简单,维护方便
- 布隆过滤器
- 内存占用少,没有多余的key
- 实现复杂,存在误判可能
- 内存占用少,没有多余的key
- 增加ID复杂度,避免被猜测ID规律
- 做好数据的基础格式校验
- 加强用户权限检验
- 做好热点参数的限流
3.4. Redis缓存雪崩
缓存雪崩是指在同一时间段大量的缓存key同时失效或者Redis宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
- 给不同的key设置不同的过期时间
- 利用Redis集群提高服务可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存
3.5. Redis缓存击穿
缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案:
- 互斥锁,查询缓存未命中时,获取互斥锁,查询数据库,写入缓存,释放锁。此时别的线程需要等待重试,直到缓存命中
- 没有额外的内存消耗
- 保证一致性
- 实现简单
- 线程需要等待,性能受影响
- 可能有死锁风险
- 逻辑过期,对热点key添加逻辑过期时间,就是不设置redis的过期时间,在value中添加一个过期时间的字段:expire:
152141223,当获取到缓存时,需要判断这个过期时间是否过期,如果过期,获取互斥锁,开启一个新的线程去查数据库写入新的缓存,然后释放锁。在这个线程的同时主线程直接返回过期的数据。其他并行线程也同样返回过期的数据。- 线程无序等待,性能较好
- 不保证一致性
- 有额外内存消耗
- 实现复杂
- 线程无序等待,性能较好
注意互斥锁并非Synchronized,这里使用 redis的String类型的 setnx 方法,如果key存在则不执行。
3.6. Redis全局ID生成器
全局ID生成器是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:
- 唯一性
- 高可用
- 高性能
- 递增型
- 安全性
为了增加ID的安全性,我们不可以直接用redis的自增的数值,需要拼接一些其他信息
看代码:com.li.redis.util.RedisIdWorker
3.7. Redis秒杀场景商品超卖
-
悲观锁,认为线程一定会发生安全问题,因此在操作数据之前先获取锁,确保线程串行执行
- 简单粗暴,性能一般
- Synchronize,Lock,互斥锁
-
乐观锁,认为线程安全问题不一定会发生,因此不加锁,只在更新数据时去判断是否有其他线程对数据做了修改。
- 性能好,但是存在成功率低的问题
- 添加版本号,修改前先验证 sql: set stock = stock - 1 , version = version + 1 where id = 10 and version = version
- CAS 方法,修改前先验证 sql: set stock = stock - 1 where id = 10 and stock = stock 因为是减库存的场景,只需要库存>0 就可以。所以这个sql可以改成 stock >0
3.8. 一人一单 需求
需要对已存在订单查询是否买入过,此时会出现线程安全问题。对查询操作需要加上 悲观锁 synchronize。
只锁定查询操作也不行,当查询通过,添加还没执行,别的线程也查询通过,还是会造成线程安全问题,所以synchronize 需要包含 下单操作。
具体要看:
https://www.bilibili.com/video/BV1cr4y1671t?p=54&spm_id_from=pageDriver&vd_source=2d21b75bf8d9e3a7a8e0f7e21a965fe2
P54 16:00 包含锁代码块优化,事务失效
整理结果:
方法通过下单用id加锁,synchronized(userId.intern())
先查询是否下单过
下单
Redis加下单记录
- Redis 分布式缓存
4.1. Redis分布式锁
集群服务下,一人一单并发问题
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
特性:
- 多进程可见
- 互斥
- 高可用
- 高性能
- 安全性
支持的方式:
- mysql
- redis
- zkooper
Redis:使用 redis的String类型的 setnx 方法,如果key存在则不执行。同时添加过期时间防止死锁。SET LOCK VALUE EX 10 NX
防止锁误删除:释放锁的时候需要验证锁是否属于自己。例如:检验锁内的value。加锁时可以存入线程ID,释放锁时验证当前线程ID是否和锁内一直
Redis分布式锁的缺点:
- 不可重入
- 不可重试
- 超时释放,不可预知的释放时间
- 主从一致性,主从同步需要时间,有可能锁失效
看REDISSON.md
- Redis持久化
5.1.# RDB持久化
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单的说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
快照文件称为RDB文件,默认是保存在当前运行目录。
cmd 登录redis 执行命令:
- save 由Redis主进程来执行RDB,会阻塞所有命令。不推荐
- bgsave 后台保存数据,异步执行,避免主进程收到影响。推荐
Redis内部有触发RDB的机制,可以在redis.conf文件中找到
- save 900 1 :900秒内,如果至少有一个key被修改,则执行bgsave。save “” 表示不执行
- rdbcompression yes :是否压缩文件,建议不开启,压缩会消耗CPU
- dbfilename dump.rdb :RDB文件名称
- dir ./ 文件保存的路径目录
基本流程:
- fork主进程得到一个子进程,共享内存空间
- 子进程读取内存数据写入到新的RDB文件
- 用新的RDB文件替换旧的RDB文件。
缺点:RDB执行间隔之间的数据有丢失的风险,fork子进程,压缩,写出RDB文件都比较耗时
5.2.# AOF持久化
AOF全称Append Only File。Redis处理的每一个写命令都会记录在AOF文件,可以看作是命令日志文件。
AOF默认是关闭的,需要修改redis.conf 配置文件来开启AOF
- appendonly yes :是否开启AOF,默认是no
- appendfilename :AOF文件的名称
- appendfsync
- always :每执行一次写命令,立即记录到AOF文件
- everysec :每隔1秒写入,是默认方案
- no :由操作系统决定何时写入
- auto-aof-rewrite-percentage 100 :AOF文件比上次文件增长超过100%触发重写
- auto-aof-rewrite-min-size 64mb :AOF文件体积最小多大以上才触发重写
cmd 登录redis 执行命令:bgrewriteaof,可以让AOF文件执行重写功能,用最少的命令达到相同的效果
RDB AOF
持久化方式 定时对内存做快照 录每一次执行的命令
数据完整性 不完整,两次备份之间会丢失 相对完整,取决于刷盘策略
文件大小 会有压缩,文件体积小 记录命令,文件体积很大
宕机恢复速度 很快 慢
数据恢复优先级 低 高
系统占用资源 高 低
使用场景 可以容忍数分钟的数据丢失,追求更快的启动速度 对数据安全性要求较高
- Redis主从集群
6.1.# 搭建Redis主从集群
本地创建3个redis实例
C:\Program Files\Redis-x64-3.2.100-6379
C:\Program Files\Redis-x64-3.2.100-6380
C:\Program Files\Redis-x64-3.2.100-6381
slaveof 指定master节点。注意:在5.0以后新增命令replicaof,与salveof效果一致。
cmd 分别登录 6380 6381 2个redis,执行slaveof 127.0.0.1 6379 ,返回OK。表示6380 6381 作为slave 已经链接上master 6379,分别在实例中 执行 info
replication 查看主从关系
此时在 6379 上set key ,在 6380 6381 中都可以查到,但是无法set key 。因为slave节点是只读的。
6.2.# 数据同步原理
主从第一次同步是全量同步:
-
第一阶段
- slave请求同步,第一次请求携带的replid 和 master的replid是不一样的。
- master判断是第一次同步,
- 返回master数据版本信息,
- slave保存版本信息
-
第二阶段
- master执行bgsave,生成RDB文件
- 同时记录RDB期间的所有写命令,记录到repl_baklog文件中
- 发送RDB文件
- slave清空本地数据,加载RDB文件
- master执行bgsave,生成RDB文件
-
第三阶段
- 发送repl_baklog中的命令给slave
- slave执行接收到的命令
Replication id:简称replid,是数据集的标记,ID一致则说明是同一个数据集。每一个master都有唯一的replid,slave则会继承master节点的replid。
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因此slave做数据同步,必须想master声明自己的replid和offset,master才可以判断到底需要同步哪些数据。
增量同步:
- 第一阶段
- slave重启之后,请求同步,携带replid和offset
- master判断replid是否一致,
- 不是第一次,回复continue
- 第二阶段
- master去repl_baklog中获取offset后的数据
- master发送offset后端命令,
- slave执行命令
注意:repl_baklog大小有上限,写满后会覆盖最早的数据,如果slave断开的时间过久,导致尚未备份的输被覆盖,则无法基于log做增量同步,只能再次做全量同步。
数据同步优化:
- 在master中配置 repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO
- Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
- 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障回复,尽可能避免全量同步
- 限制一个master上的slave节点数,如果实在太多slave,则可以采用主-从-从链式结构,减少master压力
注意:主从集群要搭配哨兵模式使用
- Redis哨兵
7.1.# 哨兵的作用和原理
哨兵机制用来实现主从集群的自动故障恢复。主要作用:
- 监控:sentinel会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- 通知:sentinel从当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis客户端
服务状态监控:sentinel基于心跳机制检测服务状态,每隔1秒向集群的每个实例发送ping命令
- 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
- 可观下线:若超过指定数量的sentinel都认为该实例主观下线,则该实例可观下线。这个值最好超过sentinel实例数量的一半
选举新的master依据:
- 首先判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点
- 判断slave节点的slave-priority值,越小优先级越高,如果是0则用不参与选举
- 判断slave节点的offset值,越大说明数据越新,优先级越高(主要依据)
- 判断slave节点的运行ID大小,越小优先级越高
如果实现故障转移:
- sentinel向选中的slave 发送slaveof no one命令,让该节点称为master
- sentinel向其他slave 发送slaveof 新masterIP 新master端口 命令,让这些slave成为新master的从节点,开始从新的master上同步数据
- 最后sentinel修改故障节点的配置文件,将故障节点标记为slave,当故障节点恢复后会自动的成为新的master的slave节点
7.2.# 搭建哨兵集群
新建哨兵配置文件:sentinel.conf
port 16381 指定端口 sentinel monitor mymaster 127.0.0.1 6380 2 指定哨兵集群名称:mymaster,redis master IP 端口,以及至少有几个哨兵主观下线才会客观下线
sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 dir “C:\Program
Files\Redis-x64-3.2.100-6381” 配置文件目录
port 16381
sentinel monitor mymaster 127.0.0.1 6380 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "C:\\Program Files\\Redis-x64-3.2.100-6381"
把sentinel.conf 放到 redis 目录下,执行
redis-server.exe sentinel.conf --sentinel
创建多个sentinel.conf文件,指定不同的端口,可以创建多个sentinel服务。
RedisTemplate的哨兵模式
spring:
redis:
sentinel:
master: mymaster
nodes:
- 127.0.0.1:16379
- 127.0.0.1:16380
- 127.0.0.1:16381
这里配置的是哨兵的节点,由哨兵去寻找redis集群。
添加sentinel配置类,或者在启动类中加入bean,配置主从读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
// return new LettuceClientConfigurationBuilderCustomizer() {
// @Override
// public void customize(LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) {
// clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
// }
// };
/**
* lambda 写法
*/
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
ReadFrom是配置Redis读取策略的一个枚举类。
- MASTER 从主节点读取
- MASTER_PREFERRED 优先从master节点读取,master不可用才读取replica(slave)
- REPLICA 从replica(slave)节点读取
- REPLICA_PREFERRED 优先从replica(slave)节点读取,所有的slave都不可用才读取master
- Redis分片集群
主从和哨兵可以解决高可用,高并发读的问题。但是依然有两个问题没有解决:
- 海量数据存储问题
- 高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
- 集群中有多个master,每个master保存不同的数据
- 每个master都可以有多个slave节点
- master之间通过ping监测彼此健康状态
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
8.1.# 搭建分片集群
准备多个redis,修改配置文件
#绑定IP
bind 0.0.0.0
#redis端口
port 6380
#开启集群功能
cluster-enabled yes
#集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file "C:\\Program Files\\Redis\\6380\\nodes.conf"
#节点心跳失败的超时时间
cluster-node-timeout 5000
#持久化文件存放目录
dir "C:\\Program Files\\Redis\\6380\\"
#注册的实例IP
replica-announce-ip 127.0.0.1
#日志存放目录
logfile ""
#保护模式
protected-mode no
这里准备6 个redis ,端口分别是 :作为master:6380,6381,6382,作为slave:6390,6391,6392
启动6个redis,此时的6个redis之间是没有关系的,现在来创建集群
redis5.0版本以后,集群管理集成到了redis-cli中,执行一下命令
redis-cli --cluster help 查看帮助文档
创建集群:执行命令
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6390 127.0.0.1:
6391 127.0.0.1:6392
–cluster-replicas:集群的副本数量,我们这里是3个master,3个slave,一主一副,那么副本数量就是1
这里IP的判断依据,一主一副加起来等于2,主副比例是1:1,现在有6个节点,3主3副,那么前三个就是主,后三个就是副
登录任意节点redis,输入 cluster nodes 查看所有节点状态
8.2.# 散列插槽
Redis会把每一个master节点映射到0-16383共16384个插槽上,数据key不是与节点绑定,而是与插槽绑定。Redis会根据key的有效部分计算插槽值,分两种情况:
- key中包含{},且{}中至少包含1个字符,{}中的部分是有效部分
- key中不包含{},整个key都是有效部分
例如:可以是num,那么就根据num计算,如果是{item}num,则根据item计算。
计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是插槽值。
注意:集群模式下登录redis 一定更要加 -c 例如:redis-cli -c -p 6380
set num 1; 会放到 6380 中
set a 1; 会放到 6382 中
如果要将同一类数据固定保存在同一个redis实例中,就需要让这一类数据使用相同的有效部分,例如可以都可以用{item}为前缀
8.3.# 集群伸缩
–cluster help 查看帮助
- add-node :添加新节点
添加6393到集群中
redis-cli --cluster add-node 127.0.0.1:6393(新节点的IP端口) 127.0.0.1:6380(已存在的一个节点的IP端口)
- reshard :重新分配插槽
此时新加入的节点 没有分配插槽,执行下列语句,分配插槽
redis-cli --cluster reshard 127.0.0.1:6393
会提示:要分配多少插槽? 输入你要分配的数字 6000
会提示:谁接受插槽? 输入6393 的ID
会提示:从哪里拷贝插槽? 输入你要拷贝的那个节点的ID ,这里输入 6380 的ID
会提示:从哪里拷贝插槽? 输入done
会提示:是否要从这里拷贝? 输入yes 就开始拷贝了
注意:6380节点只有5460个插槽,我要拷贝6000个,执行操作后,发现6393节点有5460个插槽,6380节点没有插槽了
- del-node :删除节点
redis-cli --cluster del-node 127.0.0.1:6380(已存在的一个节点的IP端口) ID (要删除的节点的ID)
有插槽的节点是不能被删除的
8.4.# 故障转移
redis自动具备主从的故障切换
当一个master失去链接之后,会被标记为疑似宕机状态,然后选择一个slave成为新的master,最后确定下线
手动故障转移,切换master:6382是master,登录6392 执行cluster failover
8.5.# RedisTemplate访问分片集群
和哨兵模式的使用方法相同,主要区别就是这里的配置
spring:
redis:
cluster:
nodes:
# 这里需要配置所有的集群节点
- 127.0.0.1:6380
- 127.0.0.1:6381
- 127.0.0.1:6382
- 127.0.0.1:6390
- 127.0.0.1:6391
- 127.0.0.1:6392
然后配置读写分离bean。
- Redis最佳实践
9.1.# redis键值设计
优雅的key结构
- 遵循基本格式:[业务名称]:[数据名]:[ID]
- 长度不要超过44字节
- 不包含特殊字符
拒绝BigKey
- 建议:
- 单个key的value小于10KB
- 对于集合类型的key,建议元素数量小于1000
- 缺点:
- 网络阻塞
- 数据倾斜
- Redis阻塞,Redis单线程的
- CPU压力过大
- 找到BigKey
- redis-cli --bigkeys
- 如何处理?
- 重新设计
- 如何删除?
- 直接删除会很耗时,尤其是集合类型,会先删除子元素,最后删除key
- Redis4.0以后提供了异步删除命令:unlink key
9.2.# 持久化配置
- 用redis做缓存时尽量不要开启持久化功能
- 建议关闭RDB持久化功能,使用AOF持久化
- 利用脚本定期在slave做RDB,实现数据备份
- 设置合理的rewrite阈值
部署建议:
- redis实例的物理机要预留足够内存
- 单个redis实例内存上线不要太大,例如4G或8G。减少主从同步的压力
- 不要与CPU密集型应用部署一起。例如:ES
- 不要与高硬盘负载应用一起部署。例如:数据库、消息队列
慢查询:
- 设置slowlog-log-slower-than:慢查询阈值,单位是微妙。
- 有些命令的问题,比如:keys * 。可以考虑禁用
9.3.# 集群还是主从
在redis的默认配置中,如果发现任意一个插槽不可用,则整个集群都会停止服务。
cluster-require-full-coverage yes
为保证高可用特性,这里建议改为false
集群带宽问题
集群节点中是靠不断的互相ping来确定集群中的其他节点的状态。每次ping都携带的信息至少包括,插槽信息,集群状态信息。避免大集群。
建议不适用集群
注意:单体的redis(主从redis哨兵机制)已经能达到万级别的QPS,并且也具备很强的高可用特性。
- Docker Redis
拉取镜像:
docker pull redis:5.0.14
因为 docker 安装运行 redis容器,是没有配置文件的,需要自己手动创建一个 redis.conf 文件 ,我这边有一份 redis默认配置文件,直接复制就能用,适用于 Redis版本小于6,因为redis6增加了用户名密码验证
docker 容器启动
docker run --name redis5.0.14 -p 6379:6379 -v /www/redis/redis.conf:/usr/local/etc/redis/redis.conf -d redis:5.0.14
–name 是 容器别名,
-p 将 宿主机 1968端口映射到 容器内6379 端口,
-v 将宿主机目录 /www/redis/redis.conf 文件 挂载到容器内/usr/local/etc/redis/redis.conf, 这样 redis启动使用的配置就是 /www/redis/redis.conf 这个文件 ,访问宿主机 1968端口的时候会映射到容器,
-d 后台运行。

2559

被折叠的 条评论
为什么被折叠?



