Part III 部分,我们已经了解到,接收客户端网络执行命令的网络请求,网络开销对Redis的开销影响很小,那么在Redis中,究竟有哪些影响性能的点呢?这节,我们详细来探讨下。
不像线上性能分析,可以根据各种参数指标去挖掘定位性能点,为了摸底Redis的性能,那么就得从Redis的方法面面入手,我们将从以下各个维度去解析性能影响点。
1、内存数据操作
读操作
每当客户端请求数据的时候,都需要等待命令执行完,获取到执行的结果,所以执行命令的复杂度决定了性能的好坏,我们在实际操作中,要尽量使用复杂度低的命令,少用复杂度低的命令,控制一次读取的数据量。
以下是复杂度比较高的命令:
-
集合统计排序:
SINTER
交集,复杂度O(N*M),其中 N 是最小集合的基数,M 是集合的数量;SUNION
并集,复杂度O(N),其中 N 是所有给定集合中元素的总数;SDIFF
差集,复杂度O(N) ,其中 N 是所有给定集合中元素的总数;SORT
排序,复杂度O(N+M*log(M)),其中 N 是列表或集合中要排序的元素数,M 是返回元素的数量。
-
大数据量查询:
- HGETALL 返回存储在字典中所有的键值对,时间复杂度O(N),其中 N 是字典的大小;
- SMEMBERS 返回集合中的所有成员key,时间复杂度O(N),其中N是设置的基数。
如果实在要执行此类操作,而数据量又比较大,建议将此类操作放到单独的从库中执行。
删除操作
如果我们删除了bigkey,那么就可能导致阻塞主线程。为什么呢?
因为删除操作除了会释放内存空间,还会把空闲空间插入操作系统的空闲内存块链表中,删除的key越大,那么就越耗时。
为了避免删除bigkey对主线程的阻塞,Redis 4.0开始新增了UNLINK命令实现惰性删除。删除bigkey,建议都使用UNLINK命令。
UNLINK命令是先释放掉字典和过期字典中的键值对引用,然后在符合以下任一条件的情况,决定是否需要放到lazyfree队列中进行异步删除:
- Hash、Set底层采用哈希,并且元素个数超过64;
- ZSET底层采用跳跃表,并且元素个数超过64;
- List节点数量超过64。
否则,还是会直接删除。
可见惰性删除也不一定会起效,所以为了杜绝此类性能问题,最好避免在Redis中存储bigkey。
另外,如果我们执行FLUSHALL或者FLUSHDB,也会阻塞线程。为了避免此种情况,可以通过向FLUSHALL/FLUSHDB添加async异步清理选项,redis在清理整个实例或db时会以异步运行。
2、磁盘写
AOF日志落盘
如果我们的AOF写回策略是Always同步写,那么每次写数据的过程中,都会因写磁盘而阻塞住了。
如果可以容忍一秒钟的数据丢失,那么,我们可以把AOF写回策略设置为Everysec,这样就会通过异步线程去落盘了,从而避免阻塞主线程。
如果我们使用了Always策略,那么就需要注意了,如果刚好Redis在执行AOF重新,会导致大量的磁盘IO,最终导致操作系统fsync被阻塞,最终导致主线程也被fsync调用阻塞住了。
为了进一步减小重写AOF被阻塞的风险,可以设置为AOF重写是,不进行fsync:
1 | no-appendfsync-on-rewrite yes |
3、主从同步
当我们要进行主从同步的时候,首先,主库会生成一份完整的RDB,传给从库,从库首先执行FLUSHDB清空原来数据库,然后从库在载入RDB文件,这个过程会导致从库被阻塞。
4、切片集群
在切片集群场景,如果刚好有big key需要迁移到其他节点,那么就会导致主线程阻塞,因为Redis Cluster是用的同步迁移。迁移过程中会同时阻塞源节点和目标节点。
而如果使用Codis进行集群,则可以利用其异步迁移的特性减少big key迁移对集群性能的影响。