欢迎访问个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
B站视频: Redis入门到精通,深入剖析Redis缓存技术,Java企业级解决方案必看的redis教程
Redis官网
Redis中文网
https://redis.com.cn/
http://www.redis.cn/
https://www.redis.net.cn/
2007年10月30日, 北京奥运会门票面向公众第二阶段预售启动. 上午官方网站1h内的浏览量达到了800万次, 票务中心呼入量超过了380万人次. 由于瞬间访问数量过大, 技术系统应对不畅, 造成很多申购者无法及时提交申请.
问题现象
罪魁祸首-关系型数据库
解决思路
NoSQL, 即not-only SQL(泛指非关系型数据库), 作为关系型数据库的补充.
作用: 应对基于海量用户和海量数据前提下的数据处理问题.
特征:
常见NoSQL数据库
Redis(Remote Dictionary Server), 是用C语言开发的一个开源的高性能键值对(key-value)数据库.
特征:
Linux版本(适用于企业级开发)
Windows版本(适合零基础学习)
添加: set key value
获取: get key
退出客户端: quit/exit/
业务数据的特殊性, 作为缓存使用
附加功能, 系统功能优化或升级
redis自身是一个Map, 其中所有的数据都是采用key:value的形式存储, 最大存储512M数据.
数据类型指的是存储的数据的类型, 也就是value部分的类型, key部分永远都是字符串类型.
存储的数据: 单个数据, 最简单的数据存储类型, 也是最常用的数据存储类型.
存储数据的格式: 一个存储空间保存一个数据.
存储的内容: 通常使用字符串, 如果字符串以整数的形式展示, 可以作为数字操作使用.
添加/修改数据: set key value
获取数据: get key
删除数据: del key
添加/修改多个数据: mset key1 value1 key2 value2...
获取多个数据: mget key1 key2 ...
获取数据字符个数(字符串长度) : strlen key
追加信息到原始信息后面: append key value
(如果key存在就追加, 不存在则创建)
设置数据指定的生命周期: setex key seconds value
, psetex key millseconds value
查看指定key的剩余有效期: ttl key
大型企业级应用中, 分表操作是基本操作, 使用多张表存储同类型数据, 但是对应的主键id必须保证统一性, 不能重复. oracle数据库具有sequence设定, 可以解决该问题, 但是MySql数据库并没有类似的机制, 如何解决.
解决方案
设置数值数据增加指定范围的值
incr key
incrby key increment
incrbyfloat key increment
设置数值数据减少指定范围的值
decr key
decrby key decrement
string 作为数值操作
Tips
主页高频访问信息显示控制, 例如新浪微博大V主页显示关注数, 粉丝数与微博数量.
解决方案
(1) 在redis中为大V用户设定用户信息, 以用户主键和属性值
作为key, 后台设定定时刷新策略即可.
关注数量: user:id:3506728370:focus
>> 83
粉丝数量: user:id:3506728370:fans
>> 12210947
微博数量: user:id:3506728370:blogs
>> 6164
(2) 在redis中以json格式
存储大V用户信息, 定时刷新.
user:id:3506728370
{id:3506728370 ,fans:12210947 ,blogs: 6164, focus: 83}
Tips
redis应用于各种结构型和非结构型高热度数据访问加速.
数据库中的热点数据key命名惯例: 表名:主键名:主键值:字段属性名
对象数据的存储如果发生比较频繁的更新操作, 如果使用上面的表名:主键名:主键值:字段属性名
存string类型会显得比较笨重. 可以使用hash存储.
hash类型, 底层使用哈希表结构实现数据存储. 结构如下:
添加/修改数据: hset key field value
获取数据: hget key field
, hgetall key
删除数据: hdel key field [field2]...
添加/修改多个数据: hmset key field1 value1 field2 value2...
获取多个数据: hmget key field1 field2...
获取哈希表字段的数量: hlen key
获取哈希表中是否存在指定的字段: hexists key field
获取哈希表中所有字段名或字段名: hkeys key
, hvals key
设置指定字段的数值数据增加指定范围的值: hincrby key field increment
, hincrbyfloat key field increment
nil
.hgetall
操作可以获取全部属性, 如果内部field字段过多, 遍历整体数据效率就会很低, 有可能造成数据访问瓶颈.以上仅仅是将数据存储到了redis中, 并没有起到加速的作用, 商品信息还需要二次查询数据库.
# 003 表示购物车
127.0.0.1:6379> hmset 003 g01:nums 100 g01:info {...}
OK
127.0.0.1:6379> hgetall 003
1) "g01:nums"
2) "100"
3) "g01:info"
4) "{...}"
127.0.0.1:6379> hmset 003 g02:nums 200 g02:info {...}
OK
127.0.0.1:6379> hgetall 003
1) "g01:nums"
2) "200"
3) "g01:info"
4) "{...}"
5) "g02:nums"
6) "200"
7) "g02:info"
8) "{...}"
127.0.0.1:6379>
双11活动日, 销售手机充值卡的商家对移动, 联通, 电信的30元, 50元, 100元商品推出抢购活动, 每种商品抢购上限1000张.
解决方案:
p01表示商家ID, c30, c50, c100表示商品ID
127.0.0.1:6379> hmset p01 c30 1000 c50 1000 c100 1000
OK
127.0.0.1:6379> hgetall p01
1) "c30"
2) "1000"
3) "c50"
4) "1000"
5) "c100"
6) "1000"
127.0.0.1:6379> hincrby p01 c50 -1
(integer) 999
127.0.0.1:6379> hincrby p01 c100 -20
(integer) 980
127.0.0.1:6379> hgetall p01
1) "c30"
2) "1000"
3) "c50"
4) "999"
5) "c100"
6) "980"
127.0.0.1:6379>
存储多个数据, 并对数据进行存储空间的顺序进行区分;
一个存储空间保存多个数据, 且通过数据可以体现进入属性;
应用于具有操作先后顺序的数据控制.
添加/修改操作: lpush key value1 [value2]...
, rpush key value1 [value2]...
获取数据: lrange key start stop
, lindex key index
, llen key
获取并移除数据: lpop key
, rpop key
规定时间内获取并移除数据 (阻塞): blpop key1 [key2] timeout
, brpop key1 [key2] timeout
移除指定数据: lrem key count value
lrange key 0 -1
微信朋友圈点赞, 要求按照点赞顺序显示点赞好友信息.
解决方案:
list
的数据具有顺序的特征对信息进行管理能够存储大量的数据, 高效的内部存储机制, 提供更高的查询效率.
与hash存储结构完全相同, 仅存储键, 不存储值(nil), 并且不允许键重复.
添加数据: sadd key member1 [member2]
获取全部数据: smembers key
删除数据: srem key member1 [member2]
获取集合长度: scard key
判断集合中是否包含指定数据: sismember key member
随机获取集合中指定数量的数据: srandmember key [count]
随机获取集合中的某个数据并将该数据移除集合: spop key
求两个集合的交,并,差集:
sinter key1 [key2]
sunion key1 [key2]
sdiff key1 [key2]
求两个集合的交,并,差集并存储到指定集合中:
sinterstore destination key1 [key2]
sunionstore destination key1 [key2]
sdiffstore destination key1 [key2]
将指定数据从原始集合中移动到目标集合中: smove source destination member
每位用户首次使用今日头条时会设置3项爱好的内容, 但是后期为了增加用户的活跃度, 兴趣点, 必须让用户对其他信息类别逐渐产生兴趣, 增加用户留存度, 如何实现?
业务分析:
Tips:
redis应用于随机推荐类信息检索, 例如热点歌单推荐, 热点新闻推荐, 应用app推荐, 大v推荐等.
分析:
使用set的获取交集, 并集, 差集的方案实现.
Tips:
集团公司共有12000名员工, 内部OA系统中具有700多个角色, 3000多个业务操作, 23000多种数据, 每位员工具有一个或多个角色, 如何快速进行业务操作的权限校验?
解决方案:
127.0.0.1:6379> sadd rid:001 getall
(integer) 1
127.0.0.1:6379> sadd rid:001 getById
(integer) 1
127.0.0.1:6379> sadd rid:002 getall
(integer) 1
127.0.0.1:6379> sadd rid:002 insert
(integer) 1
127.0.0.1:6379> sunionstore uid:007 rid:001 rid:002
(integer) 3
127.0.0.1:6379> smembers uid:007 # redis提供基础数据
1) "insert"
2) "getById"
3) "getall"
127.0.0.1:6379> sismember uid:007 insert # redis提供校验结果
(integer) 1
校验工作: redis提供基础数据还是提供校验结果? 推荐使用redis提供基础数据, 因为校验使用redis会造成业务与redis耦合, 侵入性增强了.
Tips:
redis应用于同类型不重复数据的合并操作
公司对旗下新的网站做推广, 统计网站的PV(访问量), UV(独立访客), IP(独立IP).
PV: 网站被访问次数, 可通过刷新页面提高访问量.
UV: 网站被不同用户访问的次数, 可通过cookie统计访问量, 相同用户切换IP地址, UV不变.
IP: 网站被不同IP地址访问的总次数, 可通过IP地址统计访问量, 相同IP不同用户访问, IP不变.
解决方案:
127.0.0.1:6379> sadd ips 1.2.3.4
(integer) 1
127.0.0.1:6379> sadd ips 2.3.4.5
(integer) 1
127.0.0.1:6379> sadd ips 2.3.4.5
(integer) 0
127.0.0.1:6379> scard ips
(integer) 2
Tips:
redis应用于同类型数据的快速去重, 进行数据过滤.
系统黑名单, 白名单.
解决方案:
Tips:
redis应用于基于黑名单与白名单设定的服务控制.
数据排序有利于数据的有效展示, 需要提供一种可以根据自身特征进行排序的方式.
新的存储模型, 在set的存储结构基础上添加可排序字段, 用来保存可排序的数据.
score不是真正的数据, 而是辅助排序的定义, 真正的业务数据在key中.
添加数据: zadd key score1 member1 [score2 member2]
获取全部数据:
zrange key start stop [withscore]
zrevrange key start stop [withscore]
删除数据: zrem key member [member2...]
127.0.0.1:6379> zadd scores 94 zhangsan 100 lisi 60 wangwu 47 zhaoliu
(integer) 4
127.0.0.1:6379> zrange scores 0 -1
1) "zhaoliu"
2) "wangwu"
3) "zhangsan"
4) "lisi"
127.0.0.1:6379> zrange scores 0 -1 withscores
1) "zhaoliu"
2) "47"
3) "wangwu"
4) "60"
5) "zhangsan"
6) "94"
7) "lisi"
8) "100"
按条件获取数据:
zrangebyscore key min max [withscore] [limit]
zrevrangebyscore key max min [withscores]
按条件删除数据:
zremrangebyrank key start stop
zremrangebyscore key min max
127.0.0.1:6379> zrangebyscore scores 50 99 limit 0 3 withscores
1) "wangwu"
2) "60"
3) "zhangsan"
4) "94"
127.0.0.1:6379> zremrangebyrank scores 0 1
(integer) 2
127.0.0.1:6379> zrange scores 0 -1 withscores
1) "zhangsan"
2) "94"
3) "lisi"
4) "100"
注意:
获取集合数据总量:
zcard key
zcount key min max
集合交, 并集操作:
zinterstore destination numkeys key [key...]
zunionstore destination numkeys key [key...]
127.0.0.1:6379> zadd s1 50 aa 60 bb 70 cc
(integer) 3
127.0.0.1:6379> zadd s2 60 aa 40 bb 90 dd
(integer) 3
127.0.0.1:6379> zadd s3 70 aa 20 bb 100 dd
(integer) 3
127.0.0.1:6379> zinterstore ss 3 s1 s2 s3
(integer) 2
127.0.0.1:6379> zrange ss 0 -1 withscores ## 求交集后, 重复的member分数会求和
1) "bb"
2) "120"
3) "aa"
4) "180"
score保存的数据存储空间是64位.
score保存的数据也可以是一个双精度的double值, 基于双精度浮点数的特征, 可能会丢失精度, 使用时要慎重.
zset底层存储还是基于set结构的, 因此数据不能重复, 如果重复添加相同的数据, score值将被反复覆盖, 保留最后一次修改的结果
127.0.0.1:6379> zrange test 0 -1 withscores 1) "aa" 2) "11" 127.0.0.1:6379> zadd test 22 aa (integer) 0 127.0.0.1:6379> zrange test 0 -1 withscores 1) "aa" 2) "22"
票选广东十大杰出青年, 各类综艺选秀投票.
各类资源网站TOP10(电影, 歌曲,文档, 游戏等)
聊天室活跃度统计.
游戏好友亲密度.
分析:
解决方案:
获取数据对应的索引(排名):
zrank key member
zrevrank key member
score值获取与修改:
zscore key member
zincrby key increment member
127.0.0.1:6379> zadd movies 143 aa 97 bb 201 cc
(integer) 3
127.0.0.1:6379> zrank movies bb
(integer) 0
127.0.0.1:6379> zrevrank movies bb
(integer) 2
127.0.0.1:6379> zscore movies bb
"97"
Tips:
redis应用于计数器组合排序功能对应的排名
基础服务 + 增值服务类网站会设定各位会员的试用 , 让用户充分体验会员优势. 例如观影试用VIP, 游戏VIP体验, 云盘下载体验VIP, 数据查看体验VIP. 当VIP体验到期后, 如果有效管理此类信息, 即便对于正式VIP用户也存在对应的管理方式.
网站会定期开启投票, 讨论, 限时进行, 预期作废. 如何有效管理此类过期信息.
解决方案:
对于基于时间线限定的任务处理, 将处理时间记录位score值, 利用排序功能区分处理的先后顺序.
记录下一个要处理的时间, 当到期后处理对应的任务, 移除redis中的记录, 并记录下一个要处理的时间.
当新任务加入时, 判定并更新当前下一个要处理的任务时间
为提升zset的性能, 通常将任务根据特征存储成若干个zset, 例如1小时内, 1天内, 周内, 月内, 季内, 年度等, 操作时逐级提升, 将即将操作的若干个任务纳入到1小时内处理的队列中.
获取当前系统时间 time
127.0.0.1:6379> zadd ts 1509802345 uid:001
(integer) 1
127.0.0.1:6379> zadd ts 1509802390 uid:007
(integer) 1
127.0.0.1:6379> zadd ts 1509802365 uid:088
(integer) 1
127.0.0.1:6379> zrange ts 0 -1 withscores
1) "uid:001"
2) "1509802345"
3) "uid:088"
4) "1509802365"
5) "uid:007"
6) "1509802390"
127.0.0.1:6379> time
1) "1646835417"
2) "261374"
Tips:
redis应用于定时任务执行顺序管理或任务过期管理.
任务/消息权重设定应用.
当任务或者消息待处理, 形成了任务队列或消息队列时, 对于高优先级的任务要保障对其优先处理, 如何实现任务权重管理呢?
解决方案:
对于带有权重的任务, 优先处理权重高的任务, 采用score记录权重即可.
多条件任务权重设定. 如果权重条件过多时, 需要对排序score值进行处理, 保障score值能够兼容2条件或多条件, 例如外贸订单优先于国内订单, 总裁订单优先于员工订单, 经理订单优先于员工订单.
因score长度受限, 需要对数据进行截断处理, 尤其是时间设置为小时或分钟级即可.(折算后)
先设定订单类别, 后设定订单发起角色类别, 整体score长度必须是统一的, 不足位补0. 第一排序规则首位不得是0.
127.0.0.1:6379> zadd tt 102004 order:id:1
(integer) 1
127.0.0.1:6379> zadd tt 101008 order:id:2
(integer) 1
127.0.0.1:6379> zrange tt 0 -1 withscores
1) "order:id:2"
2) "101008"
3) "order:id:1"
4) "102004"
Tips:
redis应用于即时任务/消息队列执行管理.
人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术, 百度自研用户评价语义识别服务, 免费开放企业试用, 同时训练百度自己的模型, 现对试用用户的使用行为进行限速, 限制每个用户每分钟最多发起10次调用.
解决方案:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8x1zqxTO-1647738351020)(C:\Users\18482\AppData\Roaming\Typora\typora-user-images\image-20220310214111500.png)]
127.0.0.1:6379> get 415 # 415为用户ID
(nil)
127.0.0.1:6379> setex 415 60 1
OK
127.0.0.1:6379> get 415
"1"
127.0.0.1:6379> incr 415
(integer) 2
127.0.0.1:6379> get 415
"2"
127.0.0.1:6379> incr 415
(integer) 3
方案改进:
nil
127.0.0.1:6379> get 415
(nil)
127.0.0.1:6379> setex 415 60 9223372036854775797 # 设置上限-10
OK
127.0.0.1:6379> get 415
"9223372036854775797"
127.0.0.1:6379> incr 415
(integer) 9223372036854775798
127.0.0.1:6379> incr 415
(integer) 9223372036854775799
127.0.0.1:6379> incr 415
(integer) 9223372036854775800
127.0.0.1:6379> incr 415
(integer) 9223372036854775801
127.0.0.1:6379> incr 415
(integer) 9223372036854775802
127.0.0.1:6379> incr 415
(integer) 9223372036854775803
127.0.0.1:6379> incr 415
(integer) 9223372036854775804
127.0.0.1:6379> incr 415
(integer) 9223372036854775805
127.0.0.1:6379> incr 415
(integer) 9223372036854775806
127.0.0.1:6379> incr 415
(integer) 9223372036854775807
127.0.0.1:6379> incr 415
(error) ERR increment or decrement would overflow # 10次后, 超过上限, 抛出异常
使用微信的过程中, 当微信接收消息后, 会默认将最近接收的消息置顶, 当多个好友及关注的订阅号同时发送消息时, 该排序会不停的进行交替, 同时还可以将重要的会话设置为置顶. 一旦用户离线后, 再次打开微信时, 消息该按照什么顺序展示呢?
解决方案
list
的数据具有顺序的特征对消息进行管理, 将list
结构作为栈使用.127.0.0.1:6379> lrem id:100 1 200
(integer) 0
127.0.0.1:6379> lpush id:100 200
(integer) 1
127.0.0.1:6379> lrem id:100 1 300
(integer) 0
127.0.0.1:6379> lpush id:100 300
(integer) 2
127.0.0.1:6379> lrem id:100 1 400
(integer) 0
127.0.0.1:6379> lpush id:100 400
(integer) 3
127.0.0.1:6379> lrem id:100 1 200
(integer) 1
127.0.0.1:6379> lpush id:100 200
(integer) 3
127.0.0.1:6379> lrem id:100 1 300
(integer) 1
127.0.0.1:6379> lpush id:100 300
(integer) 3
127.0.0.1:6379> lrange id:100 0 -1 # 最后消息展示顺序, 400 200 300
1) "300"
2) "200"
3) "400"
基本操作
删除指定key : del key
获取key是否存在: exists key
获取key的类型: type key
查询key : keys pattern
( * 匹配任意数量的任意符号, ? 匹配一个任意符号 []匹配一个指定符号)
为key改名: rename key newkey
, renamenx key newkey
对所有key排序(list列表): sort [desc]
其他key通用操作: help @generic
扩展操作(时效性控制):
为指定key设置有效期
expire key seconds
pexpire key millseconds
expireat key timestamp
pexpireat key millseconds-timestamp
获取key的有效时间
ttl key
pttl key
切换key从时效性转换为永久性
persist key
db基本操作指令
切换数据库: select index
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 15
OK
其他操作
quit
ping
echo message
数据移动 : move key db
127.0.0.1:6379> set name crysw
OK
127.0.0.1:6379> get name
"crysw"
127.0.0.1:6379> move name 1 # 如果1库有name, 会移动失败
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> get name
"crysw"
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> set name panda
OK
127.0.0.1:6379> move name 1 # 返回0,移动失败
(integer) 0
数据清除(慎用)
dbsize # 当前db中的key数量
flushdb
flushall
操作redis的java客户端, Jedis的api使用和redis的操作指令完全一样.
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
@Test
public void testJedis() {
// 1.连接redis
Jedis jedis = new Jedis(host, port);
System.out.println(jedis);
jedis.auth(password);
// 2. 操作redis
jedis.set("name", " crysw");
System.out.println("name=" + jedis.get("name"));
System.out.println("ping=" + jedis.ping());
// 关闭连接
jedis.close();
}
JedisConfig配置类
@Configuration
@Slf4j
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private int maxWaitMillSeconds;
@Bean
public JedisPool jedisPool(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillSeconds);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
log.info("JedisPool连接成功: {}:{}",host,port);
return jedisPool;
}
}
Jedis工具类
/**
* 类描述:Jedis工具类
* @author crys
* @date 2022/1/20 21:48
* @version 1.0
*/
@Component
public class JedisUtils {
@Autowired
private JedisPool jedisPool;
@Value("${spring.redis.password}")
private String password;
/**
* 获取Jedis资源
* @return
*/
public Jedis getJedis(){
Jedis jedis = jedisPool.getResource();
jedis.auth(password);
return jedis;
}
/**
* 关闭jedis连接
* @param jedis
*/
public void close(Jedis jedis){
if (jedis!=null) jedis.close();
}
// 为什么要封装工具类? Redis有很多指令, Jedis操作它需要针对业务场景做方法封装
public int calcTimeHour(int hours){
int seconds = hours*60*60;
return seconds;
}
}
远程连接, 使用RedisDesktopManager, 目前已经收费.
免费的redis可视化软件下载
推荐一款新的免费客户端: https://gitee.com/qishibo/AnotherRedisDesktopManager
默认不允许远程连接, 需要修改信息才允许进行连接.
# 注释掉, 允许本机以外的其他机器访问redis服务
#bind 127.0.0.1 ::1
# 设置数据库密码
requirepass 123456
人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术, 百度自研用户评价语义识别服务, 免费开放企业试用, 同时训练百度自己的模型, 现对试用用户的使用行为进行限速, 限制每个用户每分钟最多发起10次调用.
案例要求:
(1) 设定A, B ,C 三个用户
(2) A用户限制10次/分调用, B用户限制30次/分调用, C用户不限制.
需求分析:
代码实现
@Service
public class BaiduServiceImpl implements BaiduService {
@Autowired
private JedisUtils jedisUtils;
// 业务操作
@Override
public void business(String level,long num) {
System.out.println("用户"+level+": 业务操作执行第"+num+"次");
}
// 控制单元
@Override
public void control(String level, int num) {
Jedis jedis = jedisUtils.getJedis();
String key = "compid:" + level;
String value = jedis.get(key);
try {
// 判断该值是否存在
if (value==null) {
// 不存在, 创建该值
jedis.setex(key, 20,String.valueOf(Long.MAX_VALUE-10));
}else {
// 存在, 自增, 调用业务
Long incr = jedis.incr(key);
business(level,num-(Long.MAX_VALUE-incr));
}
} catch (JedisDataException e) {
System.out.println("用户"+level+"使用已经达到次数上限,请升级会员级别");
}finally {
jedis.close();
}
}
}
测试
@Test
public void testBaiduService() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (;;){
baiduService.control("level1",10);
try {
SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
for (;;){
baiduService.control("level2",30);
try {
SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
下载安装包
wget http://download.redis.io/releases/redis-4.0.0.tar.gz
解压
tar -xvf redis-4.0.0.tar.gz
编译
make
安装
make install
指定端口启动Redis
./bin/redis-server --port=6379
查看redis.conf , 过滤掉注释信息(#)和空行 , 并输出到redis-6379.conf
cat redis.conf | grep -v "#" | grep -v "^$" > redis-6379.conf
指定配置文件启动Redis
./bin/redis-server redis-6379.conf
启动Redis客户端
./bin/redis-cli
基本配置
daemonize yes
以守护线程方式启动, 该方式下Redis将以服务的形式存在, 日志将不再打印到命令窗口中.
port 6379
设置当前服务启动端口号
dir /usr/local/redis/data
设定当前服务文件保存位置, 包含日志文件, 持久化文件等.
logfile "6379.log"
设定日志文件名, 便于查阅.
完整的配置项, 请移步到redis.conf文件查看.
什么是持久化
利用永久性存储介质将数据进行保存, 在特定的时间将保存的数据进行恢复的工作机制称为持久化
.
为什么要进行持久化
防止数据的意外丢失, 确保数据安全性.
持久化过程保存什么
RDB启动方式----save指令相关配置
即时保存数据, 使用save
指令手动执行一次保存操作.
dbfilename dump.rdb
说明: 设置本地数据库文件名, 默认值为 dump.rdb ; 通常设置为 dump-端口号.rdb
dir
说明: 设置存储.rdb文件
的路径, 通常设置成存储空间较大的目录中, 目录名称为 data.
rdbcompression yes
说明: 设置存储至本地数据库时是否压缩数据, 默认为yes, 采用LZF压缩. 通常默认为开启状态, 如果设置为no, 可以节省CPU运行时间, 但会使存储的文件变大.
rdbchecksum yes
说明: 设置是否进行RDB文件格式校验, 校验过程在写文件和读文件过程均进行; 通常默认为开启状态, 如果设置为no, 可以节约读写过程约10%的时间消耗, 但是存在一定的数据损坏风险.
注意: save指令的执行会阻塞当前redis服务器, 直到当前RDB过程完成为止, 有可能造成长时间阻塞, 线上环境不建议使用.
数据量过大, 单线程执行方式造成效率过低, 选择后台执行的bgsave
方式, 手动启动后台保存操作, 但不是立即执行.
redis操作者(用户)发起指令, redis服务器控制指令执行, 做到即时发起, 合理的时间执行, 达到保存数据的效果.
RDB启动方式----bgsave指令相关配置
dbfilename dump.rdb
dir
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes
说明: 后台存储过程中如果出现错误现象, 是否停止保存操作, 通常默认为开启状态.
注意: bgsave命令是针对save阻塞问题做的优化, Redis内部所有涉及到RDB操作都采用bgsave的方式, save命令可以放弃使用.
RDB相关配置
反复执行保存指令, 忘记了怎么办? 不知道数据产生了多少变化, 如何保存? Redis配置文件可以配置自动执行.
save second changes
满足限定时间范围内key的变化数量达到指定数量即进行持久化. second表示监控时间范围, changes表示监控key的变化量.
# save 3600 1
# save 300 100
# save 60 10000
注意:
(1) save配置要根据实际业务情况进行设置, 频度过高或过低都会出现性能问题, 结果可能是灾难性的.
(2) save配置中对于second与changes设置通常具有互补对应关系, 尽量不要设置成包含性关系.
(3) save配置启动后执行的是bgsave操作
全量复制 , 在主从复制中详细介绍
服务器运行过程中重启
debug reload
关闭服务器时指定保存数据
shutdown save
RDB优点
RDB缺点
针对RDB存储的弊端, 可以从下面的思路解决:
改记录数据为记录数据产生的过程
.always
每次写入操作均同步到AOF文件中, 数据零误差, 性能较低
, 不建议使用.
everysec
每秒缓冲区中的指令同步到AOF文件中, 数据准确性较高, 性能较高
. 建议使用, 也是默认配置.
在系统突然宕机的场景下会丢失1s
内的数据.
no
由操作系统控制每次同步到AOF文件的周期, 整体过程不可控.
redis.conf配置文件中配置appendonly yes|no
, 表示是否开启AOF持久化功能, 默认为不开启状态.
redis.conf配置文件中配置appendfsync always|everysec|no
, AOF写数据策略.
appendfilename filename
配置AOF持久化文件名, 默认为appendonly.aof , 建议配置为 appendonly-端口号.aof.
dir
配置AOF持久化文件保存路径, 与RDB持久化文件保持一致即可.
AOF写数据遇到的问题
随着命令不断写入AOF, 文件会越来越大, 为了解决这个问题, Redis引入了AOF重写机制压缩文件体积, AOF文件重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程. 简单说就是将对同一个数据的若干条命令执行结果转化成最终结果数据对应的指令进行记录.
AOF重写作用
AOF重写规则
进程内已超时的数据不再写入文件.
忽略无效指令, 重写时使用进程内数据直接生成, 这样新的AOF文件只保留最终数据的写入命令.
如: del key1, hdel key2 , srem key3, set key4 value4 等…
对同一数据的多条写命令合并为一条命令
如: lpush list1 a, lpush list1 b, lpush list1 c 可以转化为 lpush list1 a b c
为防止数据量过大造成客户端缓冲区溢出, 对list, set, hash, zset等类型, 每条指令最多写入64个元素.
AOF重写方式
bgrewriteaof
auto-aof-rewrite-percentage percentage
auto-aof-rewrite-min-size size
什么是事务?
Redis执行指令过程中, 多条连续执行的指令被干扰, 打断, 插队.
Redis事务就是一个命令执行的队列, 将一系列预定义命令包装成一个整体(一个队列). 当执行时, 一次性按照添加顺序依次执行, 中间不会被打断或者干扰.
开启事务
multi
设定事务的开始位置, 此指令执行后, 后续的所有指令均加入到事务中.
执行事务
exec
设定事务的结束位置, 同时执行事务. 与multi
成对出现, 成对使用.
注意: 加入事务的命令暂时进入到任务队列中, 并没有立即执行, 只有执行exec命令才开始执行.
取消事务
discard
终止当前事务的定义, 发生在multi之后, exec之前.
注意: 已经执行完毕的命令对应的数据不会自动回滚, 需要程序员自己在代码中实现回滚.
基于特定条件的事务执行
天猫双11热卖过程中, 对已经售罄的货物追加补货, 4个业务员都有权限进行补货, 补货的操作可能是一系列的操作, 牵扯到多个连续操作, 如何保障不会重复操作?
业务分析:
解决方案
对key添加监视锁, 在执行exec前如果key发生了变化, 终止事务执行.
watch key1 [key2...]
取消对所有key的监视
unwatch
Tips: redis应用基于状态控制的批量任务执行.
天猫双11热卖过程中, 对已经售罄的货物追加补货, 且补货完成. 客户购买热情高涨, 3s内将所有商品购买完毕, 本次补货已经将全部库存清空, 如何避免最后一件商品不被多人同时购买? [超卖问题]
业务分析
解决方案
使用setnx设置一个公共锁 (分布式锁的使用)
setnx lock-key value
利用setnx命令的返回值特征, 返回0设置失败, 返回1则设置成功.
对于返回设置成功的, 拥有控制权, 进行下一步的具体业务操作.
对于返回设置失败的, 不具有控制权, 排队等待.
操作完毕通过del操作释放锁.
127.0.0.1:6379> set num 10 # 初始化数据
OK
127.0.0.1:6379> setnx lock-num 1 # 当前用户获取锁
(integer) 1
127.0.0.1:6379> incrby num -1 # 当前用户操作数据
(integer) 9
127.0.0.1:6379> del dock-num # 当前用户释放锁
注意: 上述方案是一种设计概念, 依赖规范保障, 具有风险性.
Tips: redis应用基于分布式锁对应的场景控制.
依赖分布式锁的机制, 某个用户操作时对应客户端宕机了, 并且此时已经获取到锁, 没有释放, 如何解决?
业务分析
解决方案
使用 expire 为锁 key 添加时间限定, 超时没有释放, 就主动失效, 放弃锁. (分布式锁)
expire lock-key second
pexpire lock-key millseconds
## 客户端1模拟获取锁, 然后宕机了, 锁没有释放
127.0.0.1:6379> set name crysw # 数据初始化
OK
127.0.0.1:6379> set lock-name 1 # 设置锁
OK
127.0.0.1:6379> expire lock-name 30 # 设置锁的有效期
(integer) 1
127.0.0.1:6379> set name pathd
OK
127.0.0.1:6379> ## 到这里宕机了!!!!! 锁还没有释放呢,咋办呢? 别急, 上面设置了锁的有效期.
## 客户端2模拟获取上面的锁, 一直获取失败, 直到锁失效后, 才成功设置锁
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> ttl lock-name ## 查看锁的剩余时间, 已经失效, 下面重新设置锁
(integer) -2
127.0.0.1:6379> setnx lock-name 1
(integer) 1
127.0.0.1:6379>
由于操作通常都是微妙或毫秒级, 因此该锁定时间不宜设置过大. 具体时间需要根据业务逻辑测试后确认.
<<
网络平均延迟, 通常为2个数量级, 取其中单个耗时较长即可.Redis是一种内存级数据库, 所有数据均存放在内存中, 内存中的数据可以通过TTL
指令获取其状态.
数据删除策略的目标
在内存占用与CPU占用之间寻找一种平衡, 顾此失彼都会造成整体Redis性能的下降, 甚至引发服务器宕机或内存泄漏.
以上两种方案都走极端, 有没有折中方案呢? 那就是定期删除
serverCron()
-> databasesCron()
-> activeExpireCycle()
activeExpireCycle()
对每个expire[*]逐一进行检测, 每次执行 250ms/server.hzactiveExpireCycle()
进入expires[*]执行.activeExpireCycle()
执行时间到期, 下次从current_db继续向下执行.定期删除
当新数据进入redis时, 如果内存不足怎么办 ?
Redis使用内存存储数据, 在执行每一个命令前, 会调用freeMemoryIfNeeded()
检测内存是否充足. 如果内存不满足新加入数据的最低存储要求, redis要临时删除一些数据为当前指令清理存储空间. 清理数据的策略称为逐出算法
.
注意: 逐出数据的过程不是100%能够清理出可使用的内存空间, 如果不成功则反复执行, 当对所有数据尝试完毕后, 如果不能达到内存清理的要求, 将出现错误信息.
(error)OOM command not allowed when used memory > 'maxmamory'
影响数据逐出的相关配置
最大可使用内存 maxmemory
, 占用物理内存的比例, 默认值为0 , 表示不限制. 生产环境中根据需求设定, 通常设置在50%以上.
每次选取待删除数据的个数 maxmemory-samples
, 选取数据时并不会全库扫描, 导致严重的性能消耗, 降低读写性能. 因此采用随机获取数据的方式作为检测删除数据.
删除策略 maxmemory-policy
, 达到最大内存后, 对被挑选出来的数据进行删除的策略.
检测易失数据(可能会过期的数据集 server.db[i].expires)
检测全库数据( 所有数据集 server.db[i].dict )
放弃数据驱逐
maxmemory-policy volatile-lru
数据逐出策略配置依据
使用info命令输出监控信息, 查询缓存hit和miss的次数, 根据业务需求调优redis配置.
服务器端设定
设置服务器以守护进程的方式运行
daemonize yes|no
绑定主机地址, 如果绑定了, 其他客户端只能通过绑定的ip访问服务.
bind 127.0.0.1
设置服务器端口号
port 6379
设置数据库数量
databases 16
日志配置
设置服务器以指定日志记录级别
loglevel debug | verbose | notice | warning
日志记录文件名
logfile 端口号.log
注意: 日志级别开发中设置为verbose, 生产环境设置为notice, 简化日志输出量, 降低日志IO的频度.
客户端配置
设置同一时间最大客户端连接数, 默认无限制. 当客户端连接到达上限, Redis会关闭新的连接.
maxclients 0
客户端闲置等待最大时长, 达到最大值后关闭连接, 如需关闭该功能, 设置为0.
timeout 300
多服务器快捷配置
导入并加载指定配置文件信息, 用于快速创建redis公共配置较多的redis实例配置文件, 便于维护.
include /path/server-端口号.conf
基础操作
获取指定key对应偏移量上的bit值
getbit key offset
设置指定key对应偏移量上的bit值, value只能是1或0.
setbit key offset value
扩展操作
对指定key按位进行交, 并, 非, 异或操作, 并将结果保存到destKey中. and, or, not , xor
bitop or destkey key1 [key2...]
统计指定key中1的数量
bitcount key [start end]
业务场景
电影网站
业务分析
维护bit位…
统计独立UV
Hyperloglog 用于做基数统计
{1, 3, 5, 7, 5, 7, 8} > 基数集: {1,3,5,7,8} 基数: 5
{1, 1, 1, 1, 1, 1, 7, 1} > 基数集: {1,7} 基数: 2
HyperLogLog的基本操作
添加数据
pfadd key element [element...]
统计数据
pfcount key [key...]
合并数据
pfmerge destkey sourcekey [sourcekey...]
相关说明
用于地理位置计算.
基本操作
添加坐标点
geoadd key longitude latitude member [longitude latitude member...]
获取坐标点
geopos key member [member...]
计算坐标点距离
互联网三高
架构: 高并发, 高性能, 高可用.
业务可用性目标5个9, 即 99.999%, 服务器年宕机时长低于315s, 约5.25分钟. 如何计算可用性比例呢? 比如:
服务宕机实际年宕机时长866467s , 可用性=(1*365*24*60*60 - 866467) * 100% / 1*365*24*60*60 = 97.252%
主从复制
主从复制, 即将master中的数据即时, 有效的复制到slave中.
特征: 一个master可以拥有多个slave, 一个slave只对应一个master.
职责:
作用:
主从复制的过程大体可以分为三个阶段:
主从连接(slave 连接 master)
方式一: 客户端发送命令 slaveof
方式二: 启动服务器参数 ./redis-server --slaveof
方式三: 服务器配置 slaveof
方式四: 集群配置
# 三主三从
./src/redis-cli --cluster create -a 123456 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
主从断开连接
slaveof no one
授权访问
master配置文件设置密码 requirepass
master客户端发送命令设置密码
config set requirepass
config get requirepass
slave客户端发送命令设置密码 auth
slave配置文件设置密码 masterauth
启动客户端设置密码 redis-cli -a
数据同步阶段master说明
如果master数据量巨大, 数据同步阶段应该避开流量高峰期, 避免造成master阻塞, 影响业务正常执行.
复制缓冲区大小设定不合理, 会导致数据溢出. 如进行全量复制周期太长, 进行部分复制时发现数据已经存在丢失的情况, 必须进行第二次全量复制, 致使slave陷入死循环状态. 如何解决呢? 修改复制缓冲区大小.
repl-backlog-size 1mb
数据同步阶段slave说明
为避免slave进行全量复制, 部分复制时服务器响应阻塞或数据不同步, 建议关闭此期间的对外服务.
slave-serve-stale-data yes|no
数据同步阶段, master发送给slave信息可以理解master是slave的一个客户端, 主动向slave发送命令.
多个slave同时对master请求数据同步, master发送的RDB文件增多, 会对带宽造成巨大冲击, 以放master带宽不足, 数据同步需要根据业务需求, 适量错峰.
slave过多时, 建议调整拓扑结构, 由一主多从结构变为树状结构, 中间的节点既是master, 也是slave. 注意使用树状结构时, 由于层级深度, 导致深度越高的slave与最顶层master数据同步延迟较大, 数据一致性变差, 应谨慎选择.
命令传播
.命令传播阶段的部分复制
命令传播阶段出现了断网现象
部分部分的三个核心要素
服务器运行ID (runid)
概念: 服务器运行ID是每一台服务器每次运行的身份识别码, 一台服务器多次运行可以生成多个运行ID.
组成: 运行ID由40位字符组成, 是一个随机的十六进制字符.
作用: 运行ID被用于在服务器间进行传输, 识别身份; 如果想两次操作均对同一台服务器进行, 必须每次操作携带对应的运行ID , 用于识别对方.
实现方式: 运行ID在每台服务器启动时自动生成的, master在首次连接slave时, 会将自己的运行ID发给slave, slave保存运行ID, 通过info server命令可以查看.
复制缓冲区
概念: 复制积压缓冲区, 是一个先进先出(FIFO)的队列, 用于存储服务器执行过的命令, 每次传播命令, master都会将传播的命令记录下来, 并存储在复制缓冲区.
复制缓冲区默认数据存储空间大小是1M, 由于存储空间大小是固定的, 当入队元素的数量大于队列长度时, 最先入队的元素会被弹出, 而新元素会被放入队列.
由来: 每台服务器启动时, 如果开启有AOF或被连接成为master节点, 即创建复制缓冲区.
作用: 用于保存master收到的所有指令 (仅影响数据变更的指令, 例如set)
数据来源: 当master接收到主客户端的指令时, 除了将指令执行, 会将该指令存储到缓冲区中.
主从服务器复制偏移量 (offset)
心跳阶段注意事项:
当slave多数掉线, 或延迟过高时, master为保障数据稳定性, 将拒绝所有信息同步操作
min-slaves-to-write 2
min-slaves-max-lag 8
slave数量少于2个, 或者所有slave的延迟都大于等于10s时, 强制关闭master写功能, 停止数据同步.
slave数量由slave发送replconf ack命令做确认
slave延迟由slave发送replconf ack命令做确认
伴随着系统的运行, master的数据量会越来越大, 一旦master重启, runid将发生变化, 会导致全部slave的全量复制操作.
内部优化调整方案
(1) master内部创建master_repid变量, 使用runid相同的策略生成, 长度41位, 并发送给所有slave.
(2) 在master关闭时执行命令 shutdown save, 进行RDB持久化, 将runid与offset保存到RDB文件中.
(3) master重启后加载RDB文件, 恢复数据. 重启后, 将RDB文件中保存的repl-id 与 repl-offset加载到内存中.
作用: 本机保存上次runid, 重启后恢复该值, 使所有slave认为还是之前的master.
问题现象: 网络环境不佳, 出现网络中断, slave不提供服务.
问题原因: 复制缓冲区大小, 断网后slave的offset越界, 触发全量复制.
最终结果: slave反复进行全量复制.
解决方案: 修改复制缓冲区大小. repl-backlog-size
建议设置如下:
问题现象: master的CPU占用过高或slave频繁断开连接.
问题原因:
最终结果: master各种资源 (输出缓冲区, 带宽, 连接等) 被严重占用.
解决方案: 通过设置合理的超时时间, 确认是否被释放 slave.
repl-timeout
该参数定义了超时时间的阈值 (默认60s), 超过该值, 释放slave.
问题现象: slave与master连接断开
问题原因:
解决方案: 提高ping指令发送的频度.
repl-ping-slave-period
超时时间repl-time的时间至少是ping指令频度的5到10倍, 否则slave很容易判定超时.
问题现象: 多个slave获取相同数据不同步
问题原因: 网络信息不同步, 数据发送有延迟
解决方案:
优化主从间的网络环境, 通常放置在同一个机房部署, 如使用阿里云等云服务器时要注意此现象.
监控主从节点延迟(通过offset)判断, 如果slave延迟过大, 暂时屏蔽程序对该slave的数据访问.
slave-server-stale-data yes|no
开启后仅响应info, slaveof等少数命令 (慎用, 除非对数据一致性要求很高).
发生场景: 将宕机的master下线, 找一个slave作为master, 通知所有的slave连接新的master, 启动新的master与slave, 可能触发全量复制*N + 部分复制*N .
哨兵(sentinel) 是一个分布式系统, 用于对主从结构中的每台服务器进行监控
, 当出现故障时通过投票机制选择
新的master, 并将所有slave连接到新的master.
哨兵的作用:
(1) 监控
(2) 通知(提醒) , 当被监控的服务器出现问题时, 向其他 (哨兵间, 客户端) 发送通知.
(3) 自动故障转移, 断开master与slave连接, 选取一个slave作为master, 将其他slave连接到新的master, 并告知客户端新的服务器地址.
注意: 哨兵也是一台redis服务器, 只是不提供数据服务, 通常哨兵配置数量为单数.
配置哨兵
[root@centos7-01 redis_cluster]# cat sentinel/sentinel-27000.conf
port 27000
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/usr/local/redis_cluster/data/sentinel-27000.log"
dir "/usr/local/redis_cluster/data"
sentinel monitor mymaster 127.0.0.1 7000 2 # 监听的master主机, 达到2个哨兵认为master不行了,就重新选举
redis-sentinel sentinel-端口号.conf
[root@centos7-01 redis_cluster]# ./src/redis-sentinel sentinel/sentinel-27000.conf
[root@centos7-01 redis_cluster]# ./src/redis-sentinel sentinel/sentinel-27001.conf
[root@centos7-01 redis_cluster]# ./src/redis-sentinel sentinel/sentinel-27002.conf
[root@centos7-01 redis_cluster]# ps -ef | grep redis-sentinel # 查看哨兵进程
root 2799 1 0 08:41 ? 00:02:23 /usr/local/redis_cluster/src/redis-sentinel *:27000 [sentinel]
root 2805 1 0 08:41 ? 00:02:25 /usr/local/redis_cluster/src/redis-sentinel *:27001 [sentinel]
root 2811 1 0 08:41 ? 00:02:37 /usr/local/redis_cluster/src/redis-sentinel *:27002 [sentinel]
也可以将多个哨兵的启动命令写到一个shell脚本中, 比如: startSentinel.sh; 然后 sh startSentinel.sh就可以完成哨兵的启动.
/usr/local/redis_cluster/src/redis-sentinel sentinel/sentinel-27000.conf
/usr/local/redis_cluster/src/redis-sentinel sentinel/sentinel-27001.conf
/usr/local/redis_cluster/src/redis-sentinel sentinel/sentinel-27002.conf
哨兵进行主从切换
过程中经历三个阶段
用于同步各个节点的状态信息.
发现问题, 竞选负责人, 优选新master, 新master上任, 其他slave切换master, 原master作为slave故障恢复后连接.
master发送故障
主事人(哨兵)挑选新的master
哨兵leader从服务器列表中挑选可用的master.
发送指令(sentinel)
现状问题: 业务发展过程中遇到的峰值瓶颈
使用集群的方式可以快速解决上述问题…
集群作用
分散单台服务器的访问压力, 实现负载均衡.
分散单台服务器的存储压力, 实现可扩展性.
降低单台服务器宕机带来的业务灾难.
数据存储设计
集群内部通讯设计
各个数据库相互通信, 保存各个库中槽的编号数据.
一次命中, 直接返回.
一次未命中, 告知具体位置.
将解压包中的脚本src
目录复制到指定集群目录
cp -r -p src /usr/local/redis_cluster2/
将解压包中的配置文件输出到指定目录
cat redis.conf > /usr/local/redis_cluster2/redis-6379.conf
配置文件中开启集群模式
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000
dir /usr/local/redis_cluster2/data
cluster配置涵义介绍
cluster-enabled yes|no
设置加入cluster, 成为其中的节点cluster-config-file
cluster配置文件名, 该文件属于自动生成, 仅用于快速查找文件并查询文件内容cluster-node-timeout
节点服务响应超时时间, 用于判断该节点是否下线或切换为从节点cluster-migration-barrier
master连接的slave最小数量.复制多份配置
sed "s/6379/6380/g" redis-6379.conf > redis-6380.conf
sed "s/6379/6381/g" redis-6379.conf > redis-6381.conf
sed "s/6379/6382/g" redis-6379.conf > redis-6382.conf
sed "s/6379/6383/g" redis-6379.conf > redis-6383.conf
sed "s/6379/6384/g" redis-6379.conf > redis-6384.conf
启动集群(6个节点)
./src/redis-server /usr/local/redis_cluster2/redis-6379.conf
./src/redis-server /usr/local/redis_cluster2/redis-6380.conf
./src/redis-server /usr/local/redis_cluster2/redis-6381.conf
./src/redis-server /usr/local/redis_cluster2/redis-6382.conf
./src/redis-server /usr/local/redis_cluster2/redis-6383.conf
./src/redis-server /usr/local/redis_cluster2/redis-6384.conf
查看集群进程
[root@centos7-01 redis_cluster2]# ps -ef | grep redis
root 4234 3582 0 16:52 pts/0 00:00:03 ./src/redis-server *:6379 [cluster]
root 4242 3655 0 16:52 pts/1 00:00:03 ./src/redis-server *:6380 [cluster]
root 4248 3695 0 16:52 pts/2 00:00:03 ./src/redis-server *:6381 [cluster]
root 4258 4071 0 16:53 pts/3 00:00:03 ./src/redis-server *:6382 [cluster]
root 4432 4273 0 16:54 pts/4 00:00:02 ./src/redis-server *:6383 [cluster]
root 4442 4358 0 16:55 pts/6 00:00:02 ./src/redis-server *:6384 [cluster]
root 4587 4318 0 17:10 pts/5 00:00:00 grep --color=auto redis
通过redis-cli客户端命令来创建集群节点 (-a 是指定密码, requirepass没有配置,可以省略)
# 1 表示 1个master连接一个slave
./src/redis-cli --cluster create -a 123456 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1
或者通过redis-trib.rb创建集群节点( 需要具有ruby环境, ruby -v查看版本)
./src/redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
查看启动集群日志
[root@centos7-01 redis_cluster2]# ./src/redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:6383 to 127.0.0.1:6379
Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6382 to 127.0.0.1:6381
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master # 三主三从 M S
M: ac5d557520b285ea526416b09a87f199cf9c134a 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
M: a8bf9e21ea60fdf318913c988bb2019ef0d07c37 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
M: 622ff152ca0d0786b09a82615cc8bfb92df8c2ad 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
S: 20a55e126d518c828e502d0a85a6eceaa420e0b3 127.0.0.1:6382
replicates 622ff152ca0d0786b09a82615cc8bfb92df8c2ad
S: 466bef9cb5cbbd04696fbc11eed96a1de1cc4c58 127.0.0.1:6383
replicates ac5d557520b285ea526416b09a87f199cf9c134a
S: c40dd82542d3d0e1aa6c442f0055646f5deb8e14 127.0.0.1:6384
replicates a8bf9e21ea60fdf318913c988bb2019ef0d07c37
Can I set the above configuration? (type 'yes' to accept): yes # 覆写配置文件内容
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: ac5d557520b285ea526416b09a87f199cf9c134a 127.0.0.1:6379 # master 6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 466bef9cb5cbbd04696fbc11eed96a1de1cc4c58 127.0.0.1:6383 # slave 6383
slots: (0 slots) slave
replicates ac5d557520b285ea526416b09a87f199cf9c134a
S: 20a55e126d518c828e502d0a85a6eceaa420e0b3 127.0.0.1:6382 # slave 6382
slots: (0 slots) slave
replicates 622ff152ca0d0786b09a82615cc8bfb92df8c2ad
S: c40dd82542d3d0e1aa6c442f0055646f5deb8e14 127.0.0.1:6384 # slave 6384
slots: (0 slots) slave
replicates a8bf9e21ea60fdf318913c988bb2019ef0d07c37
M: a8bf9e21ea60fdf318913c988bb2019ef0d07c37 127.0.0.1:6380 # master 6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: 622ff152ca0d0786b09a82615cc8bfb92df8c2ad 127.0.0.1:6381 # master 6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered. # 16384个槽点全部分配
[root@centos7-01 redis_cluster2]#
进入data目录查看生成的集群节点文件
[root@centos7-01 redis_cluster2]# cd data
[root@centos7-01 data]# ll
total 28
-rw-r--r--. 1 root root 175 Mar 19 17:10 dump.rdb
-rw-r--r--. 1 root root 781 Mar 19 17:10 nodes-6379.conf
-rw-r--r--. 1 root root 781 Mar 19 17:10 nodes-6380.conf
-rw-r--r--. 1 root root 781 Mar 19 17:10 nodes-6381.conf
-rw-r--r--. 1 root root 781 Mar 19 17:10 nodes-6382.conf
-rw-r--r--. 1 root root 781 Mar 19 17:10 nodes-6383.conf
-rw-r--r--. 1 root root 781 Mar 19 17:10 nodes-6384.conf
例如, 查看节点 nodes-6379.conf 的内容
## 查看节点的槽点slots分配
[root@centos7-01 data]# cat nodes-6379.conf
ac5d557520b285ea526416b09a87f199cf9c134a 127.0.0.1:6379@16379 myself,master - 0 1647681030000 1 connected 0-5460 # slots
466bef9cb5cbbd04696fbc11eed96a1de1cc4c58 127.0.0.1:6383@16383 slave ac5d557520b285ea526416b09a87f199cf9c134a 0 1647681031000 1 connected
20a55e126d518c828e502d0a85a6eceaa420e0b3 127.0.0.1:6382@16382 slave 622ff152ca0d0786b09a82615cc8bfb92df8c2ad 0 1647681031553 3 connected
c40dd82542d3d0e1aa6c442f0055646f5deb8e14 127.0.0.1:6384@16384 slave a8bf9e21ea60fdf318913c988bb2019ef0d07c37 0 1647681031247 2 connected
a8bf9e21ea60fdf318913c988bb2019ef0d07c37 127.0.0.1:6380@16380 master - 0 1647681031553 2 connected 5461-10922
622ff152ca0d0786b09a82615cc8bfb92df8c2ad 127.0.0.1:6381@16381 master - 0 1647681030220 3 connected 10923-16383
vars currentEpoch 6 lastVoteEpoch 0
查看dump.rdb文件内容 , 因为该文件是二进制, 需要借助命令redis-check-rdb
[root@centos7-01 data]# ../src/redis-check-rdb dump.rdb
[offset 0] Checking RDB file dump.rdb
[offset 26] AUX FIELD redis-ver = '6.2.6'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1647681029'
[offset 67] AUX FIELD used-mem = '2716936'
[offset 85] AUX FIELD repl-stream-db = '0'
[offset 135] AUX FIELD repl-id = '0575a59a98fed22a85ff99b2c0b1a5164b71d57f'
[offset 150] AUX FIELD repl-offset = '0'
[offset 166] AUX FIELD aof-preamble = '0'
[offset 175] Checksum OK
[offset 175] \o/ RDB looks OK! \o/
[info] 0 keys read
[info] 0 expires
[info] 0 already expired
集群数据共享操作 , 集群下连接客户端 ./src/redis-cli -c -p 端口. 需要注意-c不能少.
[root@centos7-01 redis_cluster2]# ./src/redis-cli
127.0.0.1:6379> # 默认连接上6379客户端
127.0.0.1:6379> set name crysw # set操作失败, 说要去到 6380
(error) MOVED 5798 127.0.0.1:6380
127.0.0.1:6379>
127.0.0.1:6379> quit
[root@centos7-01 redis_cluster2]# ./src/redis-cli -c -p 6380 # 集群连接到6380客户端
127.0.0.1:6380> set name crysw # 设置操作ok
OK
127.0.0.1:6380> get name
"crysw"
127.0.0.1:6380> quit
[root@centos7-01 redis_cluster2]# ./src/redis-cli -c -p 6379 # 再次回到6379客户端
127.0.0.1:6379> get name # get操作, 可以读取到6380的数据, 实现了数据共享.
-> Redirected to slot [5798] located at 127.0.0.1:6380
"crysw"
127.0.0.1:6380>
查看cluster节点信息
[root@centos7-01 redis_cluster2]# ./src/redis-cli -c -p 6379
127.0.0.1:6379>
127.0.0.1:6379> cluster nodes
ac5d557520b285ea526416b09a87f199cf9c134a 127.0.0.1:6379@16379 myself,master - 0 1647682485000 1 connected 0-5460
466bef9cb5cbbd04696fbc11eed96a1de1cc4c58 127.0.0.1:6383@16383 slave ac5d557520b285ea526416b09a87f199cf9c134a 0 1647682486000 1 connected
20a55e126d518c828e502d0a85a6eceaa420e0b3 127.0.0.1:6382@16382 slave 622ff152ca0d0786b09a82615cc8bfb92df8c2ad 0 1647682485000 3 connected
c40dd82542d3d0e1aa6c442f0055646f5deb8e14 127.0.0.1:6384@16384 slave a8bf9e21ea60fdf318913c988bb2019ef0d07c37 0 1647682486865 2 connected
a8bf9e21ea60fdf318913c988bb2019ef0d07c37 127.0.0.1:6380@16380 master - 0 1647682486559 2 connected 5461-10922
622ff152ca0d0786b09a82615cc8bfb92df8c2ad 127.0.0.1:6381@16381 master - 0 1647682486559 3 connected 10923-16383
127.0.0.1:6379>
cluster nodes
查看集群节点信息.
cluster replicate
进入一个从节点redis, 切换其主节点.
cluster meet ip:port
发现一个新节点, 新增主节点.
cluster forget
忽略一个没有slot的节点.
cluster failover
手动故障转移.
服务器启动后迅速宕机, 问题排查:
请求数量较高.
主从之间的数据吞吐量较大, 数据同步操作频度较高.
解决方案:
前置准备工作:
准备工作:
实时:
总结:
缓存预热就是系统启动前, 提前将相关的缓存数据直接加载到缓存系统. 避免在用户请求的时候, 先查询数据库, 然后再将写入缓存的问题. 用户直接查询事先被预热的缓存数据.
数据库服务器崩溃, 以下几种:
问题排查
较短的时间
内, 缓存中较多
的key集中过期
.解决方案(道), 设计角度
解决方案(术), 实施角度
总结
缓存雪崩就是瞬间过期数据量太大, 导致对数据库服务器造成压力, 如果能够有效避免过期时间集中, 可以有效解决雪崩现象的出现(约40%), 配合其他策略一起使用, 并监控服务器的运行数据, 根据运行记录做快速调整.
系统平稳运行过程中, 数据库连接瞬间激增, Redis服务器无大量key过期, Redis内存平稳, Redis服务器CPU正常, 热点key过期, 数据库服务器压力激增, 导致数据库崩溃.
问题排查
某个key过期
, 该key访问量巨大.问题分析: 单个key高热数据, 短时过期.
解决方案(术)
同时淘汰
就行.总结
缓存击穿, 就是单个key高热数据过期的瞬间, 数据访问量较大, 未命中Redis后, 发起了大量对统一数据的数据库访问, 导致对数据库服务器造成压力, 应对策略应该在业务数据分析与预防方面进行, 配合运行监控测试与即时调整策略, 毕竟单个key的过期监控难度较高, 配合雪崩处理策略即可.
系统平稳运行过程中, 应用服务器流量随时间增量较大, Redis服务器命中率随时间逐步降低, Redis内存平稳, CPU占用激增, 数据库服务器压力激增, 导致数据库崩溃.
问题排查: Redis中出现大面积未命中, 尤其是非正常URL
访问.
问题分析:
解决方案(术)
缓存null, 对查询结果为null的数据进行缓存(长期使用, 定期清理), 设定短时限, 例如30-60s, 最高5min.
白名单策略
实施监控redis命中率, 业务正常范围时会有一个波动值, null数据的占比.
根据倍数不同, 启动不同的排查流程. 然后使用黑名单进行防控运营.
key加密, 问题出现后临时启动防灾业务key, 对key进行业务层传输加密服务, 设定校验程序, 对请求的key校验.
性能指标: Performance
内存指标: Memory
基本活动指标: Basic activity
持久性指标: Persistence
准备工作:**
日常例行统计数据访问记录, 统计访问频度较高的热点数据.
利用LRU数据删除策略, 构建数据留存队列. (storm与kafka配合)
准备工作:
实时:
总结:
缓存预热就是系统启动前, 提前将相关的缓存数据直接加载到缓存系统. 避免在用户请求的时候, 先查询数据库, 然后再将写入缓存的问题. 用户直接查询事先被预热的缓存数据.
数据库服务器崩溃, 以下几种:
问题排查
较短的时间
内, 缓存中较多
的key集中过期
.解决方案(道), 设计角度
解决方案(术), 实施角度
总结
缓存雪崩就是瞬间过期数据量太大, 导致对数据库服务器造成压力, 如果能够有效避免过期时间集中, 可以有效解决雪崩现象的出现(约40%), 配合其他策略一起使用, 并监控服务器的运行数据, 根据运行记录做快速调整.
系统平稳运行过程中, 数据库连接瞬间激增, Redis服务器无大量key过期, Redis内存平稳, Redis服务器CPU正常, 热点key过期, 数据库服务器压力激增, 导致数据库崩溃.
问题排查
某个key过期
, 该key访问量巨大.问题分析: 单个key高热数据, 短时过期.
解决方案(术)
同时淘汰
就行.总结
缓存击穿, 就是单个key高热数据过期的瞬间, 数据访问量较大, 未命中Redis后, 发起了大量对统一数据的数据库访问, 导致对数据库服务器造成压力, 应对策略应该在业务数据分析与预防方面进行, 配合运行监控测试与即时调整策略, 毕竟单个key的过期监控难度较高, 配合雪崩处理策略即可.
系统平稳运行过程中, 应用服务器流量随时间增量较大, Redis服务器命中率随时间逐步降低, Redis内存平稳, CPU占用激增, 数据库服务器压力激增, 导致数据库崩溃.
问题排查: Redis中出现大面积未命中, 尤其是非正常URL
访问.
问题分析:
解决方案(术)
缓存null, 对查询结果为null的数据进行缓存(长期使用, 定期清理), 设定短时限, 例如30-60s, 最高5min.
白名单策略
实施监控redis命中率, 业务正常范围时会有一个波动值, null数据的占比.
根据倍数不同, 启动不同的排查流程. 然后使用黑名单进行防控运营.
key加密, 问题出现后临时启动防灾业务key, 对key进行业务层传输加密服务, 设定校验程序, 对请求的key校验.