强制把redis快照关闭了导致不能持久化的问题
现象
1 | 127.0.0.1:6379> ping |
解决办法
通过stop-writes-on-bgsave-error值设置为no即可避免这种问题。
有两种修改方法,一种是通过redis命令行修改,另一种是直接修改redis.conf配置文件。
redis命令行修改示例:
1 | 127.0.0.1:6379> config set stop-writes-on-bgsave-error no |
使用 redis-cli 过程中出现中文乱码
使用redis-cli
时后面加上--raw
就可以避免中文乱码。
1 | redis-cli --raw -h host -p port -a password |
NOAUTH Authentication required.
1 | Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required. |
进行密码验证或者把密码置空。
在多线程场景下使用redis时,可能会出现各种类型转换异常:如:B cannot be cast to java.util.List
原因
在多线程场景下,如果使用jedis连接池获取连接,在各个地方调用,且使用完之后没有释放,则可能在某次使用的时候,拿到的是其他线程的连接,则会出现类型转换异常。
解决方案
在每次使用完连接后,将其返回连接池。具体操作如下:
1 |
|
线上Redis禁止使用Keys正则匹配操作
背景
- redis是单线程的,其所有操作都是原子的,不会因并发产生数据异常
- 使用高耗时的Redis命令是很危险的,会占用唯一的一个线程的大量处理时间,导致所有的请求都被拖慢。
案例
- 运维人员进行
keys *
操作,该操作比较耗时,又因为redis是单线程的,所以redis被锁住。 - 此时QPS比较高,又来了几万个对redis的读写请求,因为redis被锁住,所以全部
Hang
在那。 - 因为太多线程
Hang
在那,CPU严重飙升,造成redis所在的服务器宕机。 - 所有的线程在redis那取不到数据,一瞬间全去数据库取数据,数据库就宕机了。
其他危险命令
但凡发现时间复杂度为O(N)的命令,都要慎重,不要在生产上随便使用。例如hgetall
、lrange
、smembers
、zrange
、sinter
等命令,它们并非不能使用,但这些命令的时间复杂度都为O(N),使用这些命令需要明确N的值,否则也会出现缓存宕机。
flushdb
命令用于清空当前数据库中的所有 keyflushall
命令用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key )config
客户端连接后可配置服务器
如何禁用危险命令
在redis.conf中,在SECURITY这一项中,我们新增以下命令:
1 | rename-command FLUSHALL "" |
另外,对于flushall
命令,需要设置配置文件中appendonly no
,否则服务器是无法启动。
请注意,更改[记录到AOF文件或传输到副本的命令]的名称可能会导致问题。
改良建议
使用scan
命令来改良keys
和smembers
命令
redis2.8版本以后有了一个新命令scan
,可以用来分批次扫描redis记录,这样肯定会导致整个查询消耗的总时间变大,但不会影响redis服务卡顿,影响服务使用。更多信息可参考redis scan命令
Redis Big Key问题
数据量大的 key ,由于其数据大小远大于其他key,导致经过分片之后,某个具体存储这个 big key 的实例内存使用量远大于其他实例,造成内存不足,拖累整个集群的使用。big key 在不同业务上,通常体现为不同的数据,比如:
- 论坛中的大型持久盖楼活动;
- 聊天室系统中热门聊天室的消息列表; ……
带来的问题
bigkey 通常会导致内存空间不平衡,超时阻塞,如果 key 较大,redis 又是单线程,操作 bigkey 比较耗时,那么阻塞 redis 的可能性增大。每次获取 bigKey 的网络流量较大,假设一个 bigkey 为 1MB,每秒访问量为 1000,那么每秒产生 1000MB 的流量,对于普通千兆网卡,按照字节算 128M/S 的服务器来说可能扛不住。而且一般服务器采用单机多实例方式来部署,所以还可能对其他实例造成影响。
- 如果是集群模式下,无法做到负载均衡,导致请求倾斜到某个实例上,而这个实例的QPS会比较大,内存占用也较多;对于Redis单线程模型又容易出现CPU瓶颈,当内存出现瓶颈时,只能进行纵向库容,使用更牛逼的服务器。
- 涉及到大key的操作,尤其是使用hgetall、lrange、get、hmget 等操作时,网卡可能会成为瓶颈,也会到导致堵塞其它操作,qps 就有可能出现突降或者突升的情况,趋势上看起来十分不平滑,严重时会导致应用程序连不上,实例或者集群在某些时间段内不可用的状态。
- 假如这个key需要进行删除操作,如果直接进行DEL 操作,被操作的实例会被Block住,导致无法响应应用的请求,而这个Block的时间会随着key的变大而变长。
什么是 big key
- 字符串类型:一般认为超过 10k 的就是 bigkey,但是这个值和具体的 OPS 相关。
- 非字符串类型:体现在哈希,列表,集合类型元素过多。
寻找big key
redis-cli自带
--bigkeys
。1
2$ redis-cli -p 999 --bigkeys -i 0.1
#Scanning the entire keyspace to find biggest keys as well as average sizes per key type. You can use -i 0.1 to sleep 0.1 sec per 100 SCAN commands (not usually needed).获取生产Redis的rdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行分析统计,根据size_in_bytes统计bigkey
1
2
3
4$ git clone https://github.com/sripathikrishnan/redis-rdb-tools
$ cd redis-rdb-tools
$ sudo python setup.py install
$ rdb -c memory dump-10030.rdb > memory.csv通过python脚本,迭代scan key,每次scan 1000,对扫描出来的key进行类型判断,例如:string长度大于10K,list长度大于10240认为是big bigkeys
其他第三方工具,例如:redis-rdb-cli
优化big key
优化big key的原则就是string减少字符串长度,list、hash、set、zset等减少成员数。
string类型的big key,建议不要存入redis,用文档型数据库MongoDB代替或者直接缓存到CDN上等方式优化。有些 key 不只是访问量大,数据量也很大,这个时候就要考虑这个 key 使用的场景,存储在redis集群中是否是合理的,是否使用其他组件来存储更合适;如果坚持要用 redis 来存储,可能考虑迁移出集群,采用一主一备(或1主多备)的架构来存储。
单个简单的key存储的value很大
该对象需要每次都整存整取: 可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;
该对象每次只需要存取部分数据: 可以像第一种做法一样,分拆成几个key-value,也可以将这个存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性。hash, set,zset,list 中存储过多的元素
可以将这些元素分拆。以hash为例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)
现在,固定一个桶的数量,比如 10000, 每次存取的时候,先在本地计算field的hash值,模除 10000,确定了该field落在哪个key上。
1 | newHashKey = hashKey + (hash(field) % 10000); |
set, zset, list 也可以类似上述做法。但有些不适合的场景,比如,要保证 lpop 的数据的确是最早push到list中去的,这个就需要一些附加的属性,或者是在 key的拼接上做一些工作(比如list按照时间来分拆)。