性能优化
Redis Optimization | Redis 性能优化实践
大键删除
在
从
# Rename the key
newkey = "gc:hashes:" + redis.INCR( "gc:index" )
redis.RENAME("my.hash.key", newkey)
# Delete fields from the hash in batche of 100s
cursor = 0
loop
cursor, hash_keys = redis.HSCAN(newkey, cursor, "COUNT", 100)
if hash_keys count > 0
redis.HDEL(newkey, hash_keys)
end
if cursor == 0
break
end
end
def del_large_hash():
r = redis.StrictRedis(host='redis-host1', port=6379)
large_hash_key ="xxx" #要删除的大hash键名
cursor = '0'
while cursor != 0:
cursor, data = r.hscan(large_hash_key, cursor=cursor, count=500)
for item in data.items():
r.hdel(large_hash_key, item[0])
def del_large_list():
r = redis.StrictRedis(host='redis-host1', port=6379)
large_list_key = 'xxx' #要删除的大list的键名
while r.llen(large_list_key)>0:
r.ltrim(large_list_key, 0, -101) #每次只删除最右100个元素
def del_large_set():
r = redis.StrictRedis(host='redis-host1', port=6379)
large_set_key = 'xxx' # 要删除的大set的键名
cursor = '0'
while cursor != 0:
cursor, data = r.sscan(large_set_key, cursor=cursor, count=500)
for item in data:
r.srem(large_size_key, item)
def del_large_sortedset():
r = redis.StrictRedis(host='large_sortedset_key', port=6379)
large_sortedset_key='xxx'
while r.zcard(large_sortedset_key)>0:
r.zremrangebyrank(large_sortedset_key,0,99)#时间复杂度更低, 每次删除O(log(N)+100)
Redis 阻塞时延分析与讨论
Redis 时延问题分析及应对
耗时长的命令造成阻塞
keys、sort 等命令
解决方案:

在架构设计中,有“分流”一招,说的是将处理快的请求和处理慢的请求分离来开,否则,慢的影响到了快的,让快的也快不起来;这在
smembers 命令
解决方案:
和
如果不容易将集合划分为多个子集合,而坚持用一个大集合来存储,那么在取集合的时候可以考虑使用
save 命令
解决方案:
我没有想到需要用到
fork 产生的阻塞
在
类似的,以下这些操作都有进程
Master 向slave 首次同步数据:当master 结点收到slave 结点来的syn 同步请求,会生成一个新的进程,将内存数据dump 到文件上,然后再同步到slave 结点中;AOF 日志重写:使用AOF 持久化方式,做AOF 文件重写操作会创建新的进程做重写;( 重写并不会去读已有的文件,而是直接使用内存中的数据写成归档日志) ;
解决方案: 为了应对大内存页表复制时带来的影响,有些可用的措施:
- 控制每个
redis 实例的最大内存量; 不让fork 带来的限制太多,可以从内存量上控制fork 的时延; 一般建议不超过20G ,可根据自己服务器的性能来确定( 内存越大,持久化的时间越长,复制页表的时间越长,对事件循环的阻塞就延长) 新浪微博给的建议是不超过20G ,而我们虚机上的测试,要想保证应用毛刺不明显,可能得在10G 以下; - 使用大内存页,默认内存页使用
4KB ,这样,当使用40G 的内存时,页表就有80M ;而将每个内存页扩大到4M ,页表就只有80K ;这样复制页表几乎没有阻塞,同时也会提高快速页表缓冲TLB(translation lookaside buffer) 的命中率;但大内存页也有问题,在写时复制时,只要一个页快中任何一个元素被修改,这个页块都需要复制一份(COW 机制的粒度是页面) ,这样在写时复制期间,会耗用更多的内存空间; - 使用物理机;
如果有的选,物理机当然是最佳方案,比上面都要省事
; 当然,虚拟化实现也有多种,除了Xen 系统外,现代的硬件大部分都可以快速的复制页表; 但公司的虚拟化一般是成套上线的,不会因为我们个别服务器的原因而变更,如果面对的只有Xen ,只能想想如何用好它; - 杜绝新进程的产生,不使用持久化,不在主结点上提供查询;实现起来有以下方案:
1) 只用单机,不开持久化,不挂 slave 结点。这样最简单,不会有新进程的产生;但这样的方案只适合缓存; 如何来做这个方案的高可用? 要做高可用,可以在写redis 的前端挂上一个消息队列,在消息队列中使用pub-sub 来做分发,保证每个写操作至少落到2 个结点上;因为所有结点的数据相同,只需要用一个结点做持久化,这个结点对外不提供查询;slave 结点提供,从结点不提供持久化;这样,所有的fork 耗时的操作都在主结点上,而查询请求由slave 结点提供; 这个方案的问题是主结点坏了之后如何处理? 简单的实现方案是主不具有可替代性,坏了之后,redis 集群对外就只能提供读,而无法更新;待主结点启动后,再继续更新操作;对于之前的更新操作,可以用MQ 缓存起来,等主结点起来之后消化掉故障期间的写请求;Sentinel 将从升级为主,整体实现就相对复杂了;需要更改可用从的ip 配置,将其从可查询结点中剔除,让前端的查询负载不再落在新主上;然后,才能放开sentinel 的切换操作,这个前后关系需要保证;
持久化造成的阻塞
执行持久化
子进程持久化时,子进程的write 和主进程的fsync 冲突造成阻塞
在开启了
原因分析:
解决方案:
设置
子进程AOF 重写时,系统的sync 造成主进程的write 阻塞
我们来梳理下:
- 起因:有大量
IO 操作write(2) 但未主动调用同步操作 - 造成
kernel buffer 中有大量脏数据 - 系统同步时,
sync 的同步时间过长 - 造成
redis 的写aof 日志write(2) 操作阻塞; - 造成单线程的
redis 的下一个事件无法处理,整个redis 阻塞(redis 的事件处理是在一个线程中进行,其中写aof 日志的write(2) 是同步阻塞模式调用,与网络的非阻塞write(2) 要区分开来)
产生
解决方案:
控制系统
另外,
AOF 重写完成后合并数据时造成的阻塞
在
解决方案:
将硬盘设置的足够大,将
Redis 数据丢失
笔者在面试阿里的时候就被问到一个问题,是否遇到过
- 因为程序错误或者认为失误操作导致数据丢失,譬如误操作执行
flushall/flushdb 这类命令。对于这种潜在的危险情况,可以通过键数监控或者对各类删除命令的执行数监控:cmdtats_flushall, cmdstats_flushdb,cmdstat_del 对应时间范围,确认具体是什么操作。 - 主库故障后自动重启,可能导致数据全部丢失。这个笔者还真碰到过,时间点
T1, 主库故障关闭了,因设置有自动重启的守护程序,时间点T2 主库被重新拉起,因(T2-T1) 时间间隔过小,未达到Redis 集群或哨兵的主从切换判断时长;这样从库发现主库runid 变了或断开过,会全量同步主库rdb 清理,并清理自己的数据。而为保障性能,Redis 主库往往不做数据持久化设置,那么时间点T2 启动的主库,很有可能是个空实例( 或很久前的rdb 文件) 。 - 主从复制数据不一致,发生故障切换后,出现数据丢失。
- 网络分区的问题,可能导致短时间的写入数据丢失。这种问题出现丢失数据都很少,网络分区时,
Redis 集群或哨兵在判断故障切换的时间窗口,这段时间写入到原主库的数据,5 秒~15 秒的写入量。 - 客户端缓冲区内存使用过大,导致大量键被
LRU 淘汰
每个
server.h#163
/* Protocol and IO related defines */
#define PROTO_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
模拟
127.0.0.1:6390> info memory
# Memory
used_memory:46979129016
used_memory_human:43.75G
used_memory_rss:49898303488
used_memory_rss_human:46.47G
used_memory_peak:54796105584
used_memory_peak_human:51.03G
total_system_memory:134911881216
total_system_memory_human:125.65G
maxmemory:4294967296
maxmemory_human:4.00G
maxmemory_policy:allkeys-random
mem_fragmentation_ratio:1.06
mem_allocator:jemalloc-4.0.3
## 当client断开后,rss会马上释放内存给OS
127.0.0.1:6390> set a b
(error) OOM command not allowed when used memory > 'maxmemory'.