文章目录
前言:
自己学习Redis路上的一点小笔记📒
记录下来,自己也方便翻看
课程链接:https://coding.imooc.com/class/151.html
书籍推荐: 《Redis开发与运维》
文章首发: https://sleepymonster.cn
这里只是一个引子或者说一个查找~
针对某个方面具体的,建议多看看别人的文章或者书
从而Redis的理解会更深入,当然上面的课程推荐!
获取Redis
安装
$ brew install redis
$ redis-server // 开启
可执行文件说明
$ redis-server // Redis服务器
$ redis-cli // Redis命令行客户端
$ redis-benchmark // Reids性能测试
$ redis-check-aof // Redis AOF文件修复工具
$ redis-check-dump // RDB文件检查工具
$ redis-sentinel // Sentinel服务器
三种启动方法
$ redis-server // 默认配置启动
$ redis-server --port 6380 // 动态参数启动
$ redis-server configPath // 配置文件启动
// 验证启动
$ ps -ef | grep redis
$ netstat -antpl | grep redis
$ redis-cli -h ip -p port ping
// 注意⚠️
// 单机多实例配置文件可以用端口区分开
Redis客户端的连接
$ redis-cli -h ip -p port
> set hello world
> Ok
> get hello
> "world"
Redis常用配置
- daemonize
是否是守护进程(no|yes)
- port
- logfile
Redis系统日志
- dir
Redis工作目录
配置文件启动
# 基础设置
# 开启持久化
daemonize yes
# 端口
port 6382
# 工作目录
dir "/usr/local/Cellar/redis/6382-data"
# 日志
logfile "/usr/local/Cellar/redis/6382.log"
$ redis-server ./6382-redis.conf
$ ps -ef | grep redis-server | grep 6382
$ cat ./6382.log //查看日志
合理使用API
通用命令
$ keys //获取所有的键
> keys*
> keys he* // 找出he开头的
> keys he[h-l]*
> keys ph?
// keys 命令一般不在生产环境使用
$ dbsize //数据库的大小
> dbsize
$ exists key // 判断是否存在
> exists a //返回1 0
$ del key [key...] //删除key
> del a
$ expire key seconds // 设置过期时间
> expire a 30
> ttl key // 查看过期时间 // -2 不存在了 -1 不存在过期时间
> persist key // 去掉过期时间
$ type key // 查看类型
// string hash list set zset(有序集合) none
注意事项
- 一次只运行一条明路
- 拒绝长命令
- 其实不是单线程
字符串
场景: 缓存 计数器 分布式锁…
$ get key
$ mget key1 key2 key3 // 批量获取
$ getset key newvalue // 设置新的并同时返回老的
$ append key value // 将value追加到旧的value
$ strlen key // 返回字符串的长度
$ getrange key start end // 获取字符串指定下标所有值
$ setrange key index value // 设置指定下标所有对应的值
$ set key value
$ setnx key value // key不存在才能设置
$ set key value xx // key存在才能设置
$ mset key1 value1 key2 value2 // 批量设置
$ del key
$ incr key // key自增1,如果key不存在,自增后get(key)=1
$ decr key
$ incrby key k // key自增k,如果key不存在,自增后get(key)=k
$ incrbyfloat key 3.5 // 增加浮点数
$ decrby key k
哈希
$ hget key field
$ hmget key field1 field2
$ hgetall key
$ hvals key // 返回hash key 对应所有的field的value
$ hkeys key
$ hset key field value
$ hmset key field1 value1 field2 value2
$ hsetnx key field value // 如果存在则设置失败
$ hdel key field
$ hexists key field
$ hlen key
$ hincrby key field intCounter // hash的key的field中的value自增intCounter
$ hincrbyfloat key field floatCounter
列表
// 增
$ rpush key v1 v2 v3 // 从右边插入
$ lpush key v1 v2 v3 // 从左边插入
$ linsert key before|after value newValue // 在key某个具体的值左右插入新的值
// 删
$ lpop key
$ rpop key // 右边弹出
$ lrem key count value // 删除与value相等的值 当 >0 从左开始 当 <0 从右开始 当 =0 全部
$ ltrim key start end // 按照索引范围修剪列表
$ blpop key timeout // timeout为阻塞超时时间
$ brpop key timeout // timeout为阻塞超时时间
// 查
$ lrange key start end (包括end) // 获取指定索引范围的item
// 改
$ lset key index newValue // 设置
集合
// 单个操作
$ sadd key element // 如果存在则添加失败
$ srem key element
$ scard key // 计算集合大小
$ sismember key value // 判断value是否存在于集合中
$ srandmember key count // 从集合中随机挑选count个元素
$ spop key count // 随机弹出count个元素
$ smember key // 返回所有
// 多个之间的差集,交集,并集
$ sdiff key1 key2 // 差集
$ sinter key1 key2 // 交集
$ sunion key1 key2 // 并集
$ sdiff|sinter|sunion + store key // 保存起来
有序集合
$ zadd key score element // score可以重复但是element不行
$ zrem key element // 删除element
$ zscore key element // 获取分数
$ zincrby key increScore element // 对唯一的element操控分数
$ zcard key // 返回元素个数
$ zrank key element // 获取排名
$ zrange key 0 -1 withscores // 获取第一个到最后一个并且打印出分数
$ zrangebyscore key min max [withscores] // 通过分数来获取
$ zcount key min max
$ zremrangebyrank key min max // 通过排名开始删除
$ zinterstore | zunionstore
瑞士军刀
生命周期: 发送命令 -> 排队 -> 执行命令 -> 返回结果
慢查询
慢查询发生在第三阶段
客户端超时不一定是慢查询问题,但是个因素
- 先进先出
- 固定长度
// 默认值 10000
$ slowlog-max-len // 慢查询的时间(ms)
$ config set slowlog-max-len 1000 // 采用动态配置
// 默认值 128
$ slow-log-slower-than //慢查询的阈值
// 基本API
$ slowlog get [n] //获取慢查询队列
$ slowlog len // 获取慢查询队列长度
$ slowlog reset // 清空慢查询
经验:
- slowlog-max-len通常设置为1ms
- slow-log-slower-than一般设置为1000
- 定期持久化慢查询
pipeline(流水线)
生命周期:传输命令-> 计算->返回结果
流水线就是一次性打包,传输命令之后,执行n次再反回来。
如何使用? 查询对应文档 看怎么把命令打包到pipeline 再发送。
使用建议:
- 注意携带数量
- pipeline只能作用在一个Redis上
- M操作与pipeline区别
发布订阅
与消息队列模式不同
消息队列是抢一个 发布订阅是广播📢
角色: 发布者 订阅者 频道
模型: 发布者 -> 频道 <- 订阅者
(可以订阅多个频道) (不能消息堆积功能)
$ publish channel message // 发布命令 返回的是订阅者数量
$ subscribe [channel] // 订阅 可以一个或者多个
$ unsubscribe [channel] // 取消订阅
$ psubscribe [pattern] // 订阅模式
$ punsubscribe [pattern] // 退订指定模式
$ punsub channels // 列出至少有一个订阅者的频道
$ punsub numsub [channel] // 列出给定频道订阅者数量
$ punsub numpat // 列出被订阅模式的数量
Bitmap(位图)
实际上可以操作位的(二进制)
$ setbit key offset value // 给位图指定索引设置值 // 返回之前对应的值
$ getbit key offset // 获取位图指定索引值
$ bitcount key [start end] // 获取位图某个范围的1的个数
$ bitop op destkey key [key...] // 做多个Bitmap的and or not xor操作并将结果存在destkey中
$ bitpos key targetbit [start] [end] // 计算位图指定范围第一个偏移量对应的值等于destkey的位置
使用建议:
- type=string 最大512M
- 注意偏移量
- 位图不是绝对好
HyperLogLog
极小的空间完成独立数量的统计
$ pfadd key element [element...] // 添加元素
$ pfcount key [key...] // 计算独立总数
$ pfmerge destkey sourcekey [sourcekey...] // 合并多个
局限性:
- 错误率 0.81%
- 没法取出来单条数据
GEO
存储类似经纬度 用于计算两地的距离
$ geo key longitude latitude member // 添加地理位置
$ geopos key member [member...] // 获取地理位置
$ geodist key member1 member2 [unit] // 获取两地之间的距离
$ georadius // 复杂 查文档https://cloud.tencent.com/developer/section/1374022
说明:
- since 3.2+
- type geoKey = zset
- 删除直接使用zsetAPI
Redis持久化的取舍
内存中的数据将会异步保存在磁盘当中
- 快照方式 Redis RDB
- 写日志方式 Redis AOF
RDB
创建一个RDB二进制文件快照存储在硬盘,重启后重新载入
触发三种方式
- save (同步)
save因为是同步的,可能会造成阻塞从而不能响应客户端
新的文件会替换老的文件。
- bgsave (异步)
创建一个子进程(这里也会阻塞) fork(),会正常响应客户端,但会消耗内存
- 自动
规则为: 保存为: dump.rdb
Seconds | Changes |
---|---|
900 | 1 |
300 | 10 |
60 | 10000 |
// 最佳配置
关闭自动
dbfilename dump-${port}.rdb
dir /oneBigDiskPath
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
- 不容忽略的方式
- 全量复制
- debug reload
- shutdown
AOF
实时写入AOF文件
- 三种策略
策略 | 描述 | 缺点 |
---|---|---|
always | 写入缓冲区,刷新到磁盘当中,每条命令都会写入。 | IO开销比较大 |
everysec | 每一秒刷新到磁盘中。 | 丢失1秒数据 |
no | 操作系统来决定 | 不可控 |
- AOF重写
解决越写越大等问题
把过期的,重复的,没有用的优化化简。
// bgrewriteaof
// AOF重写配置
// auto-aof-rewrite-min-size AOF文件重写需要的尺寸
// auto-aof-rewrite-percentage AOF文件增长率
// aof_current-size AOF当前的尺寸
// aof_base_size AOF上次启动和重写的尺寸
- 配置
appendonly yes
appendfilename "appendonly-${port}.aof"
appendfsync evertsec
dir /bigdiskpath
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
最佳策略
-
RDB
-
RDB “关”
-
集中管理
-
主从,从开
-
-
AOF
- 开:缓存和存储
- 重写集中管理
-
最佳
- 小分片
- 缓存和存储
- 监控
开发运维常见问题
- fork
- 同步操作
- 需要的内存越大,时间越大
- info: latest_fork_usec 查询上一次fork时间
- 改善: 控制Redis实例最大可用内存
maxmemory
; 合理配置linux内存分配策略vm.overcommit_memory=1;降低fork频率
- 子进程开销与优化
- 主要在文件生成的时候进行CPU密集写入
- 改善:不做CPU绑定,不和COU密集型部署
- 内存开销,会共享父进程的内存
- 硬盘消耗
iostat
/iotop
去分析 - 改善: 不要和高硬盘负载服务部署一起;no-appendfsync-on-rewrite=yes
- AOF阻塞
查看日志会发现:…xxxxxtaking too long.xxxx
aof_delayed_fsync 历史累计AOF阻塞数量
Redis 复制
主从复制
单机是有瓶颈的,发生机器故障,则客户端没法链接
且同样存在容量瓶颈 ; QPS瓶颈期
可以实现一主多从 一个slave只能有一个master 数据是单向的
可以到的 数据副本
, 拓展读性能
等功能
默认是全量复制
// 复制的配置
// 命令不需要重启
// 配置统一配置 需要重启
// slaveof 命令
$ slaveof masterIP
$ slaveof no one // 断掉成为别人的从节点
// 配置
slaveof ip port
slave-read-only yes
$ info replication // 查看分片(存在角色)
$ redis-cli -p 6379 info server | grep run # 查看runID
全量复制
开销: bgsave时间;RDB文件网络传输时间;从节点清空RDB时间;从节点加载RDB;AOF重写时间
部分复制
故障处理
自动故障转移: slave故障, master故障
slave故障 : 迁移到另外一个slave
master故障: 找一个slave成为master
开发运维中的问题
- 读写分离
- 读流量分摊到从节点
- 复制数据延迟
- 读到过期数据
- 从节点故障
- 主从配置不一致
- maxmemory不一致:丢失数据
- 数据结构优化参数(例如:hash-max-ziplist-entires): 内存不一致
- 规避全量复制
- 第一次全量复制
- 节点运行ID不匹配
- 复制积压缓冲区不足
- 规避复制风暴
- 单主节点挂掉,多个从节点开始全量复制
- 一个机器上全是master,机器挂掉了
Redis Sentinel
基本架构
主从复制高可用问题: 主节点出现问题,通常是手动故障转移;写能力与存储能力受限。
基本架构:对故障判断,转移,通知。
客户端从Sentinel获取redis信息。(可以监控多套)
自动完成故障转移… ( 端口默认: 26379
安装和配置
配置开启主从节点
配置开启Sentinel监控主节点
// 主节点
redis-server redis-7000.conf
port 7000
daemonzie yes
pidfile ${dir}-7000.pid
logfile 7000.log
dir ${dir}
// 从节点
redis-server redis-7001.conf
port 7001
daemonzie yes
pidfile ${dir}-7001.pid
logfile 7001.log
dir ${dir}
slaveof ip port
// Sentinel配置
port ${port}
dir ${dir}
pidfile ${port}.pid
sentinel monitor mymaster ip 7000 2 // 2的意思:2个sentinel认为有问题 则开始故障转移
sentinel down-after-milliseconds mymaster 30000 // 类似 ping
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
客户端
需要知道Sentinel节点集合和masterName开始遍历…
内部是用发布订阅来实现的。(不是代理模式)
from redis.sentinel import Sentinel
sentinel = Sentinel(['localhost', 26379], ['localhost', 26380], ['localhost', 26381], socket_timeout=0.1)
sentinel.discover_master('mymaster')
sentinel.discover_slaves('mymaster')
定时任务原理
- 每10秒每个sentinel对master和slave执行info
- 发现slave节点
- 确认主从关系
- 每2秒每个sentinel通过master节点的channel交换信息
- 通过频道交互
- 交互对节点的看法和自身信息
- 每1秒钟每个sentinel对其他sentinel和redis执行ping
主观/客观下线->领导者选举->完成故障转移
- 主观/客观下线
sentinel monitor mymaster ip 7000 <quorum>
sentinel down-after-milliseconds <masterName> <timeout>
主观下线: 即每个sentinel个体节点下线的判断
客观下线:超过了quorum统一,即多个认为下线了
- 领导者选举
sentinel is-master-down-by-addr // 不仅可以判断master下线了吗 还可以申请作为领导
sentinel没有同意过其他人,就会同意你。
票数超过一半且大于quorum则为领导者,同时多个则重新选举。
- 完成故障转移
- 选出来之后呢,会被执行
slaveof no one
使其成为master节点。 - 向剩余的slave节点发送命令,让他们成为新master的slave。
- 对原来的master设置为slave并且保持关注。
- 注意点
- 选择slave优先级更高的(slave-priority)
- 会选择偏移量更大的节点(复制的完整性更高)
- 选择runID更小的节点
常见开发运维问题
sentinel failover <masterName> // 手动下线(手动触发故障转移)
- 从节点下线的时候要判断时候是暂时的,例如后续的清理工作等
- 只会对slave进行下线而对应的读的客户端没法自己转移。=> 客户端连接slave节点资源池
- Sentinel数量最好>=3。
Redis Cluster
为什么需要集群?
需要更高的并发量,数据量
数据分布
- 顺序分区
- 分散度易倾斜
- 键值分布业务有关
- 哈希分区
- 数字分散度高
- 键值分布业务无关
- 节点取余分区(数据迁移量范围太大了)
- 节点伸缩即扩容等操作会导致数据迁移
- 对于迁移的性能,推荐翻倍扩容
- 一致性哈希分区
- 优化了取余分区
- 只影响邻近节点,但是仍然会数据迁移
- 保证最小的数据迁移和负载均衡
- 虚拟槽分区(Redis Cluster使用)
- 每个槽映射一个数据子集,实现了分区
- 用CRC16函数直接去找对用的区域
搭建集群
去访问,如果该数据不在对应的槽中,则会通知你去访问对应的槽。
每个节点负责数据的一部分,需要一个总的客户端来管理。
每个节点都是负责读写的,以及每个节点之间都是通信的。
- 节点
cluster-enabled yes // 集群模式启动
- meet
相互之间都会通信
- 指派槽
指派每个槽的大小
- 复制
每个主节点都有个副节点。
-
安装
- 原生命令的安装
// 配置节点 port ${port} daemonzie yes pidfile "${dir}-${port}.pid" logfile "${port}.log" dir "${dir}" dbfilename "dump-${port}.rdb" cluster-enabled yes // 集群模式启动 cluster-config-file "nodes-${port}.conf" // 添加对应的配置 cluster-node-timeout 15000 // 故障转移/节点下线...时间 cluster-require-full-coverage no // 如果有一个不行了不会停止服务 // 开启节点(这里为 3主 x 3从) $ redis-server redis-7000.conf $ redis-server redis-7001.conf ... // meet实现通信 $ redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001 // 双方建立联系 // 指派槽 $ redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0...5461} // 16384平均 $ redis-cli -h 127.0.0.1 -p 7001 cluster addslots {5461...10922} // 16384平均 $ redis-cli -h 127.0.0.1 -p 7002 cluster addslots {10923...16383} // 16384平均 // 主从关系的分配实现故障转移 $ redis-cli -h 127.0.0.1 -p 7003 cluster replicate ${node-id-7000} $ redis-cli -h 127.0.0.1 -p 7004 cluster replicate ${node-id-7001} $ redis-cli -h 127.0.0.1 -p 7005 cluster replicate ${node-id-7002} // 查询状态 $ redis-cli -p 7000 cluster nodes $ redis-cli -p 7000 cluster info $ redis-cli -p 7000 cluster slots
- 官方工具的安装
- Ruby环境准备 下载 编译 安装
- Rubygem redis客户端的安装
- 安装redis-trib.rb
- 参考链接: https://codeantenna.com/a/g07I0RgJZW
cp ${REDIS_HOME}/src/redis-trib.rb /usr/loacl/bin // 配置开启redis但是集群还没有上线 // 一键开启 // 每个主节点后有一个从节点 前三个是主节点 后三个是从节点 $ ./redis-trib.rb create --replicas 1 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005
集群伸缩
原理:slot键值的迁移(理解 槽 节点 key 之间的关系)
-
扩容
-
准备新的节点 -> 同样的配置
-
加入集群meet-> cluster meet
-
$ redis-trib.rb add-node 127.0.0.1:xxxx 127.0.0.1:xxxx(已经存在的) // 官方也有
-
作用1: 为迁移槽和数据做扩容
-
作为从节点负责故障转移
-
-
迁移槽和数据
-
槽迁移计划
-
迁移数据
$ cluster setslot {slot} importing {sourceNodeId} // 对目标节点发送让目标节点准备导入槽的数据 $ cluster setslot {slot} migrating {targetNodeId} // 让源节点准备迁出槽的数据 $ cluster getkeysinslot {slot} {conut} // 源节点循环执行 获取count个属于槽的键 $ migrate {targetIp} {targetPort} key 0 {timeout} // 源节点执行把指定key迁移 $ cluster setslot {slot} node {targetNodeId} // 通知其他节点 我现在的槽到新的上面去了 // 上面的太麻烦了 $ redis-trib.rb reshard 127.0.0.1:7000 // 跟着提示走 $ redis-cli -p 7000 cluster slots // 查看结果
- 添加从节点
-
-
-
收缩
-
下线迁移槽
-
$ redis-trib.rb reshard --from {Fromid} --to {Toid} --slots 1366 127.0.0.1:7006
-
-
忘记节点
-
$ cluster forget {downNodeID} // 需要对其他所有节点忘记 // 要先下从节点 再下主节点 否则会触发自动转移 $ redis-trib.rb del-node 127.0.0.1:7000 {DownnodeId} // 自动完成
-
-
关闭节点
-
客户端路由
- moved重定向(槽不命中)
- -c 命名会自动重定向 不使用会返回MOVED (cluster模式)
- ask重定向(客户端记录的是源节点 但是已经迁移到了目标节点)
- smart客户端
- 集群中选一个可以运行的节点,使用cluster slots初始化槽和节点映射
- 将cluster slots的结果映射到本地,为每一个创建jedisPool
- 准备执行命令
故障转移
-
故障发现:
-
通过 ping / pong 来发现
-
主观发现:
-
客观下线:
当半数以上持有槽的主节点都标记某节点主观下线
-
-
故障恢复
- 资格检查
- 准备选举时间
- 选举投票
开发运维常见问题
- 批量操作 mget mset必须在一个槽上
- 串行mget (for循环一个个来)
- 串行IO (客户端先内聚分组再去pipline)
- 并行IO (客户端先内聚分组再去pipline时候是多线程)
- hash_tag (用hash使所有key都落到了一个节点)
- 避免使用大集群,大业务可以使用多集群
- cluster-node-timeout:带宽和故障转移速度的均衡
- 发布订阅 建议单独一套集群 因为会在每个节点广播,加重带宽
- 内存配置不一致: hash-max-ziplist-value ;set-max-intset-entries等
- 对于热点key不要使用hash_tag;或者使用本地缓存
- 集群下的从节点不能写也不能读,会跳转到主节点
- 数据迁移不推荐官方的,推荐:redis-migrate-tool ; redis-port
集群vs单机 |
---|
key的批量操作必须在一个槽 |
key事务和Lua支持的key在一个节点上 |
key是数据分区最小粒度 |
不支持多个数据库 |
复制只能复制一层 |
缓存
得与失
受益 | 成本 | 使用场景 |
---|---|---|
加速读写 | 数据不一致 更新策略有关 | 降低高消耗的SQL |
降低后端负载 | 代码维护成本 | 加速响应时间 |
… | 运维成本 | 大量写合并为批量写 |
缓存更新策略
策略 | 一致性 | 维护成本 |
---|---|---|
LRU/LFU/FIFO算法剔除:例如 maxmemory-policy | 最差 | 底 |
超时剔除:例如expire | 较差 | 底 |
主动更新:开发控制生命周期 | 强 | 高 |
- 低一致性:最大内存和淘汰策略
- 高一致性:超时剔除与主动更新结合,最大内存和淘汰兜底
缓存粒度控制
- 通用性:全量属性更好
- 占用空间:部分属性更好
- 代码维护:表面上全量属性更好
缓存穿透问题
缓存的key不存在,大量流量打到了存储层
- 业务代码自身问题
- 恶意攻击、爬虫
发现:业务时间,业务逻辑,相关指标
解决:
- 缓存空对象:如果缓存不存在,将cache中的key为null
- 布隆过滤器拦截
缓存雪崩
缓存崩了,大量流量打到了存储层,设计的时候存储层是小流量,造成级联故障
- 保证混存高可用性
- 依赖隔离组件为后端限流
- 提前演练:压力测试
无底洞问题优化
“加机器”性能没提升反而下降,节点多了,IO(node)越来越高
- 命名本身优化
- 减少网络通信次数
- 降低接入成本
热点key重建优化
热点key + 较长的重建时间,并发的时候大量线程在重建时间的时候都不会命中导致都在重建。
- 减少重缓存次数
- 数据尽可能一致
- 减少潜在风险
- 互斥锁
- 永不过期,逻辑去解决。
Redis云平台CacheCloud
开源地址: https://github.com/sohutv/cachecloud
- 一键开启Redis
- 监控和报警
- 客户端:透明使用,性能上报
- 可视化运维:配置,扩容等
- 已存在的Redis直接接入与迁移
Redis布隆过滤器
基本:
参考链接:https://juejin.cn/post/6844904134894698510
问题的引出:如果存在50亿的电话号码,如何判断这10万个电话号码已经存在?
原理:一个很长的二进制向量和若干个哈希函数, 通过哈希做映射,如果走一遍的话均为1则存在
误差被设置为1的概率:1 - (1 - 1/m)^nk (m个向量 n个数据 k个哈希函数)
相比较于本地的过滤器,基于Redis的布隆过滤器可以同步且容量大
每个语言都有自己的写法,但是总的思想是一样的。
布隆过滤器调研与设计Go实现 https://juejin.cn/post/6863059963145617416
分布式布隆过滤器
- 多个布隆过滤器
- 基于pipeline提高效率
Redis开发规范
键值的设计
-
key名字设计
-
可读性和可管理性 例如业务名:表名:id
-
简洁行 控制key的长度
-
不包含特殊字符
短字符串是embstr(<=39) 长字符串为raw 限制长度会节省内存
-
-
value设计
-
拒绝bigkey 官方提供了
redis-cli --bigkeys
/debug object key
等 -
选择合适的数据结构
比如现在记录某个人发了什么图片
- 方案一 : set key value
- 方案二: hset allPics picId userId
- 方案三: hset picId/100 picID%100 userId
-
-
过期时间管理
- 不是垃圾桶 别啥都往里面放 以及要记得设置过期时间
- 过期时间不宜集中
发现与删除Bigkey
redis-cli --bigkeys
但是无法限制大小debug object key
可能会发生阻塞 可以使用llen
,zcard
,scard
,hlen
等命令来查询- 主动报警 当检测到大于报警值的时候 发出警报
- 阻塞:删除Bigkey会特别慢 注意隐性删除
- Reids4.0
lazy delete
后台删除
命名使用技巧
- O(N)以上命令关注N的数量。有遍历的需求可以使用hscan,sscan,zscan代替
- 禁用命令:key *, flushall等
- 合理使用select
- Redis事务功能较弱
- Redis集群版本再使用Lua上有特殊要求
- 必要情况下使用monitor命令,建议不要长时间使用
客户端优化
- 避免多个应用使用一个Redis实例:不想干的业务拆分,公共数据做服务化。
- 使用连接池
- maxTotal与maxIdle接近:Reids并发量与客户端执行时间(QPS/平均耗时)
Redis内存优化
查看内存情况
执行 info mermory
各个内存缓冲区
- 客户端缓冲区
- 输出缓冲区
- 普通客户端
- slave客户端
- pubsub客户端
- 输入缓冲区
- 缓冲内存
- 对象内存
- 内存碎片
- 子进程
内存管理
- 设置内存上限
$ config set maxmemory 4g
$ config rewrite
- 内存回收策略
- 过期键的处理
- 惰性删除
- 定时删除
- maxmemory-police来控制内存超出后的策略
- Noeviction
- Volatile-lru
- Allkeys-random
- volatile-random
- volatile-ttl
- 过期键的处理
内存优化
- 选择合理的数据结构(针对对象内存)
- 客户端优化 (出现了一次内存暴增)
Redis开发一些坑
内核优化
- 对于 vm.overcommit_memory
-
Redis设置合理的maxmemory,保证机器有20%~30%的闲置内存。
-
集中化管理AOF重写和RDB的bgsave。
-
设置vm.overcommit_memory=1,防止极端情况会造成fork失败。
- 对于swappiness
- 对于THP
- 对于OOM killer
- NTP 同步时间
- ulimit
- TCP-backlog
安全Redis
- 被攻击的特征
- 存在外网IP
- 默认端口
- 以root启动
- 没有设置密码
- bind设置为0.0.0.0
- 安全七法则
- 设置密码
- 伪装危险命令
- bind限制网卡
- 防火墙
- 定期备份
- 不使用默认端口
- 非root用户启动
热点Key
- 客户端发现:客户端记录,但是不太建议
- 代理:代理全增量统计
- 服务端:解析monitor输出统计
- 机器端:抓取Redis的TCP数据统计