Redis最佳实践
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
一、Redis键值设计
1.1、优雅的key结构
Redis的key最佳实践约定
- 遵循基本格式【业务名称】:【数据名】:【id】
- 长度不超过44字节
- 不包含特殊字符
好处
- 可读性强
- 避免key冲突
- 方便管理
- 更节省内存
1.2、拒绝BigKey
BigKey通常以Key的大小和Key中成员的数量来综合判定例如
- Key本身的数据量过大一个String类型的Key它的值为5MB
- Key中的成员数过多一个ZSET类型的Key它的成员数量为10,000个
- Key中成员的数据量过大一个Hash类型的Key它的成员数量虽然只有1,000个但这些成员的Value值总大小为100MB
推荐值
- 单个key的value小于10KB
- 对于集合类型的key建议元素数量小于1000
1.2.1、BigKey的危害
- 网络阻塞
- 对BigKey执行读请求时少量的QPS就可能导致带宽使用率被占满导致Redis实例乃至所在物理机变慢
- 数据倾斜
- BigKey所在的Redis实例内存使用率远超其他实例无法使数据分片的内存资源达到均衡
- Redis阻塞
- 对元素较多的hash、list、zset等做运算会耗时较旧使主线程被阻塞
- CPU压力
- 对BigKey的数据序列化和反序列化会导致CPU的使用率飙升影响Redis实例和本机其它应用
1.2.2、如何删除BigKey
BigKey内存占用较多即便是删除这样的Key也需要耗费很长时间导致Redis主线程阻塞引发一系列问题。
- redis3.0及以下版本
- 如果是集合类型则遍历BigKey的元素先逐个删除子元素最后删除BigKey
- redis4.0以后
- 异步删除unlink
1.3、恰当的数据类型
例1比如存储一个User对象我们有三种存储方式
①方式一json字符串
| user:1 | {“name”: “Jack”, “age”: 21} |
- 优点实现简单粗暴
- 缺点数据耦合不够灵活
②方式二字段打散
- 优点可以灵活访问对象任意字段
- 缺点占用空间大没办法做统一控制
③方式三hash推荐
优点底层使用zipList空间占用小可以灵活访问对象的任意字段
缺点代码相对复杂
1.4、总结
- Key的最佳实践
- 固定格式【业务名】:【数据名】:【id】
- 足够简短不超过44字节
- 不包含特殊字符
- Value的最佳实践
- 合理的拆分数据拒绝BigKey
- 选择合适数据结构
- Hash结构的entry数量不要超过1000
- 设置合理的超时时间
2、批处理优化
2.1、Pipeline
我们的客户端与redis服务器是这样交互的
- 单个命令的执行流程
- N条命令的执行流程
redis处理指令是最快的主要花费的时候在于网络传输。于是乎很容易想到将多条指令批量的传输给redis
2.1.1、Mset
Redis提供了很多Mxxx这样的命令可以实现批量插入数据例如
- mset
- hmset
@Test
void testMxx() {
String[] arr = new String[2000];
int j;
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
j = (i % 1000) << 1;
arr[j] = "test:key_" + i;
arr[j + 1] = "value_" + i;
if (j == 0) {
jedis.mset(arr);
}
}
long e = System.currentTimeMillis();
System.out.println("time: " + (e - b));
}
2.1.3、Pipeline
MSET虽然可以批处理但是却只能操作部分数据类型因此如果有对复杂数据类型的批处理需要建议使用Pipeline
@Test
void testPipeline() {
// 创建管道
Pipeline pipeline = jedis.pipelined();
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
// 放入命令到管道
pipeline.set("test:key_" + i, "value_" + i);
if (i % 1000 == 0) {
// 每放入1000条命令批量执行
pipeline.sync();
}
}
long e = System.currentTimeMillis();
System.out.println("time: " + (e - b));
}
2.2、集群下的批处理
如MSET或Pipeline这样的批处理需要在一次请求中携带多条命令而此时如果Redis是一个集群那批处理命令的多个key必须落在一个插槽中否则就会导致执行失败。大家可以想一想这样的要求其实很难实现因为我们在批处理时可能一次要插入很多条数据这些数据很有可能不会都落在相同的节点上这就会导致报错了
四种解决方案
3、服务器端优化-持久化配置
Redis的持久化虽然可以保证数据安全但也会带来很多额外的开销因此持久化请遵循下列建议
- 用来做缓存的Redis实例尽量不要开启持久化功能
- 建议关闭RDB持久化功能使用AOF持久化
- 利用脚本定期在slave节点做RDB实现数据备份
- 设置合理的rewrite阈值避免频繁的bgrewrite
- 配置no-appendfsync-on-rewrite = yes禁止在rewrite期间做AOF避免因AOF引起的阻塞
- 部署有关建议
- Redis实例的物理机要预留足够内存应对fork和rewrite
- 单个Redis实例内存上限不要太大例如4G或8G。可以加快fork的速度减少主从同步、数据迁移压力
- 不要与CPU密集型应用部署在一起
- 不要与高硬盘负载应用一起部署。比如数据库、消息队列
4、服务器端优化-慢查询优化
4.1、什么是慢查询
在Redis执行时耗时超过某个阈值的命令称为慢查询
慢查询的危害由于Redis是单线程的所以当客户端发出指令后他们都会进入到redis底层的queue来执行如果此时有一些慢查询的数据就会导致大量请求阻塞从而引起报错所以我们需要解决慢查询问题。
慢查询的阈值可以通过配置指定
- lowlog-log-slower-than 慢查询阈值单位是微秒。默认是10000建议1000
- slowlog-max-len: 慢查询日志本质是一个队列的长度。默认是128建议1000
4.2、如何查看慢查询
- slowlog len: 查询慢查询日志长度
- slowlog get [n]: 读取n条慢查询日志
- slowlog reset: 清空慢查询列表
5、服务器端优化-Redis内存划分和内存配置
当Redis内存不足时可能导致Key频繁被删除、响应时间变长、QPS不稳定等问题。当内存使用率达到90%以上时就需要我们警惕并快速定位到内存占用的原因
有关碎片问题分析
Redis底层分配并不是这个key有多大他就会分配多大而是有他自己的分配策略比如8,16,20等等假定当前key只需要10个字节此时分配8肯定不够那么他就会分配16个字节多出来的6个节点就不能被使用这就是我们常说的碎片问题
进程内存问题分析
这片内存通常我们都可以忽略不计
缓冲区内存问题分析
一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大所以这片内存也是我们需要重点分析的内存问题。
我们可以通过一些命令可以查看到Redis目前的内存分配状态
- info memory: 查看内存分配的情况
- memory xxx: 查看key的主要占用情况
接下来我们看到了这些配置最关键的缓存区内存如何定位和解决呢
内存缓冲区常见的有三种 - 复制缓冲区主从复制的repl_backlog_buf如果太小可能东芝频繁的全量复制影响性能。通过replbacklog-size来设置默认1mb
- AOF缓冲区AOF刷盘之前的缓存区域AOF执行rewrite的缓冲区。无法设置容量上限
- 客户端缓冲区分为输入缓冲区和输出缓冲区输入缓冲区最大1G且不能设置。输出缓冲区可以设置
以上复制缓冲区和AOF缓冲区不会有问题最关键就是客户但缓冲区的问题
客户端缓冲区指的就是我们发送命令时客户端用来缓存命令的一个缓冲区也就是我们向redis输入数据的输入端缓冲区和redis向客户端返回数据的响应缓存区输入缓冲区最大1G且不能设置所以这一块我们根本不用担心如果超过了这个空间redis会直接断开因为本来此时此刻就代表着redis处理不过来了我们需要担心的就是输出端缓冲区