文章目录
一. Redis键值设计
1. 优雅的Key结构
Redis的key虽然可以自定义,但最好遵循下面的几个最佳实践约定:
- 遵循基本格式:[业务名称]:[数据名]:[id]
- 长度不超过44字节
- 不包含特色字符
例如:登录业务
login:user:10
- 可读性强
- 避免key冲突
- 方便管理
- 更节省内容:key是String类型,底层编码包含int、embstr和raw三种。embstr在小于44字节使用,采用连续内存空间,内存占用更小
2. 拒绝BigKey
什么是Bigkey
BigKey通常以key的大小和key中成员的数量来综合判定,例如:
- key本身的数据量过大:一个String类型的key,它的值为5MB
- key中的成员数过多:一个ZSET类型的key,它的成员数量为10000个
- Key中成员的数据量过大:一个Hash类型的key,它的成员数量虽然只有1000个,但这些成员value值总大小为100MB
推荐值
- 单个Key的value小于10KB
- 对于集合类型的key,建议元素数量小于1000
BigKey的危害
- 网络阻塞:对BigKey执行读请求时,少量的QPS就可能导致带宽使用率被占满,导致Redis实例,乃至所在屋里机变慢
- 数据倾斜:BigKey所在的Redis实例内存使用率远超过其他实例,无法使数据分片的内存资源达到均衡
- Redis阻塞:对元素较多的hash、list、zset等做运算会耗时较久、使主线程被阻塞
- CPU压力:对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其它应用
如何发现BigKey
- Redis-cli --bigkeys:利用redis-cli提供的–bigkeys参数,可以遍历分析所有的key,并返回Key的整体统计信息与每个数据的Top1的big key
- scan扫描:自己编程,利用scan扫描redis中的所有key,利用strlen、hlen等命令判断key的长度(此处不建议使用MEMORY USAGE)
- 第三方工具:如Redis-RDB- Tools分析RDB快照文件,全面分析内存使用情况(github上面下载,有使用方法)
- 网络监控:自定义工具,监控进出Redis的网络数据,超出预警值时主动报警
如何删除BigKey
BigKey占用内存较多,即便是删除这样的key也需要耗费很长时间,导致Redis主线程阻塞,引发一系列问题。 - redis3.0 及以下版本:如果是集合类型,则遍历BigKey所有元素,先逐个删除子元素,最后删除key
- redis4.0 以后:Redis在4.0后提供了异步删除命令:unlink
3. 恰当的数据类型
案例一:比如存储一个User对象,我们有三种存储方式:
方式一:Json字符串
user:1 | {“name”:“jack”,“age”:21 } |
---|
优点:实现简单粗暴
缺点:数据耦合,不够灵活
方式二:字段打散
user:1:name | “jack” |
---|---|
user:1:age | 21 |
优点:可以灵活访问对象任意字段
缺点:占用空间大,没办法做到统一控制
方式三:hash
user:1 | name | Jack |
---|---|---|
user:1 | JacK | 21 |
优点:底层使用ZipList,空间占用小,可以灵活访问对象的任意字段
缺点:代码相对复杂
案例二:假如有hash类型的key,其中100万对feild和value,field是自增的id,这个key存在什么问题?如何优化?
- hash的entry数量超过500时,会使用hash表而不是ZipList,内存占用较多
- 方案一:可以通过hash-max-entries配置entry上限。但是如果entry过多就会导致BigKey问题
3. 方案二:拆分为String字符串
这种方案存在的问题:
- string结构底层没有太多内存优化,内存占用比较多
- 想要批量获取这些数据比较麻烦
- 方案三:将原来大的hash拆分为小的hash,将id / 100作为key,将id%100作为field,这样每100个元素为一个hash
二. 批处理优化
1. Pipeline
单个命令的执行流程:客户端发送命令,命令通过网络传输到服务端,Redis服务端接收到命令后执行客户端的命令,再通过网络将结果返回给客户端,所以一次命令的响应时间=1次往反的网络传输耗时+1次Redis执行命令耗时。(命令很多时网络耗时会非常大)
N条命令批量执行:客户端一次批量发送N条命令,Redis收到命令后执行,然后一次性返回N条命令的执行结果,所以N次命令的响应时间=1次往返的网络传输耗时+N次redis执行命令耗时
Redis提供了很多Mxxx这样的命令,可以实现批量插入数据,例如mset和hmset
也要注意,不要在一次批处理中传输太多命令,否则单次命令占用带宽过多,会导致网络阻塞
MSET虽然可以批处理,但是却只能操作部分数据类型,因此如果有对复杂数据类型的批处理需要,建议使用PipeLine功能:
void TestPipeline(){
//创建管道
Pipeline pipeline=jdedis.pipelined();
for(int i=1;i<=100000;i++)
{
pipeline.set("test:key_"+i,"value_"+i);
if(i %1000=0)
//每放入1000条数据,批量执行
pipeline.sync();
}
}
总结
- 批处理的方案:
- 原生的M操作
- Pipeline批处理
- 注意事项:
- 批处理不建议一次携带太多命令
- Pipeline的多个命令之间不具备原子性
2. 集群下的批处理
如MSET或PipeLine这样的批处理需要在一次请求中携带多种命令,而此时如果Redis是一个集群,那批处理命令的多个key必须落在一个插槽中,否则就会导致执行失败(插槽不同可能在不同的节点上,而不同的节点则需要不同的连接,而批处理只能在一条链接上处理)
解决方案
三.服务端优化
1. 持久化配置
Redis持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:
- 用来做缓存的Redis实例尽量不要开启持久化功能
- 建议关闭RDB持久化功能,使用AOF持久化
- 利用脚本定期在slave节点做RDB,实现数据备份
- 设置合理的rewrite阈值,避免频繁使用bgrewrite
- 配置no-appendfsync-on-rewrite=yes。禁止在rewrite期间做aof,避免因aof引起的阻塞
部署有关的建议: - Redis实例的物理机要预留足够的内存,应对fork和rewrite
- 当个Redis实例的内存上限不要太大,例如4G或5G。可以加速fork的速度、减少主从同步、数据迁移压力
- 不要与CPU密集型应用部署在一起
- 不要与高硬盘负载应用一起部署。例如:数据库、消息队列
2. 慢查询
简介:在Redis中,执行耗时超过某个阈值的命令,叫做慢查询
慢查询的阈值可以通过配置指定:
slowlog-log-slower-than
:慢查询阈值,单位是毫秒。默认是10000,建议1000
慢查询会被放入慢查询日志中,日志的上限有限,可以通过配置指定:slowlog-max-len
:慢查询日志(本质是一个队列)的长度。默认是128建议是100
修改上面两个配置可以使用:config set命令
config get slowlog-max-len
config get slow-log-slower-than
config set slow-log-slower-than 1000
查看慢查询日志列表:
slowlog len
:查询慢查询日志长度
slowlog get[n]
:读取n条慢查询日志
slowlog reset
:清空慢查询列表
3. 命令及安全配置
漏洞重现看这篇文章
漏洞出现的核心原因:
- Redis未有设置密码
- 利用了Redis的config set命令动态修改Redis配置
- 使用了ROOT账号权限启动Redis
为了避免这些漏洞,这里给出一些建议:
- Redis一定要设置密码
- 禁止线上使用下面命令:keys、flushall、config set等命令 。可以使用rename-command(更改命令的名称,别人就不知道了)
- bind:限制网卡,禁止外网网卡访问
- 开启防火墙
- 不要使用root账户启动redis
- 尽量不要有默认的端口
4. 内存配置
当redis内存不足时,可能导致频繁被删除、响应时间变长、QPS不稳定等问题。当内存使用率达到90%以上时就需要我们警惕,并快速定位到内存占用的原因。
Redis内存信息
内存占用 | 说明 |
---|---|
数据内存 | 时Redis的主要的部分,存储Redis的键值信息。主要问题是BigKey问题、内存碎片问题 |
进程内存 | Redis主进程本身的运行肯定需要占用内存,入代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略不计 |
缓冲区内存 | 一般包括客户端缓存、AOF缓存、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两。这部分内存占用波动比较大,不当使用bigkey,可能导致内存泄露 |
Redis提供了一些命令,可以查看到Redis目前的内存分配状态:
info memory
memory xxx
内存缓冲区常见的有三种:
- 复制缓冲区:主从复制的repl_backlog_buf,如果太小可能导致频繁的全量复制,影响性能。通过repl_backlog_size来设置,默认1mb
- AOF缓冲区:AOF刷盘之前的缓存区域,AOF执行rewrite的缓冲区。无法设置上限
- 客户端缓冲区:分为输入缓冲区和输出缓冲区,输入缓冲最大1GB且不能设置。输出缓冲区可以设置
info clients
:查看redis客户端连接信息
clients list
:客户端连接详细信息
四. 集群最佳实践
集群虽然具备高可用特性,能自动实现故障恢复,但如果使用不当,也会存在一些问题:
- 集群完整性问题
在redis的默认配置中,如果发现任意一个插槽不可用,则整个集群会停止对外服务;
为了保证高可用性,这里建议将cluster-require-full-coverage
配置为false
- 集群带宽问题
集群节点之间会不断的互相ping来确定集群中其它节点的状态,每次ping携带的信息至少包括:
- 插槽信息
- 集群状态信息
集群中节点越多集群状态信息数据量也越大,10个节点的相关信息可以达到1kb,此时每次集群互通需要的带宽会非常高
解决途径:
- 避免大集群,集群的节点数量不要太多,最好小于1000,如果业务庞大,则建立多个集群
- 避免在单个物理机中运行太多的redis实例
- 配置合适的
cluster-node-timeout值
- 数据倾斜问题
- 客户端性能问题
- 命令的集群兼容性问题
- lua和事务问题