文章目录

  • 前言
    • unlink 一个拯救性能的英雄?
      • 小结
      • 使用建议
      • 相关redis 异步删除配置建议
    • 性能测试
    • 总结:

前言

redis 在4.0之后就推出了异步删除,其中相关的最直接的命令就是unlink, 那如何去用unlink,它是否能够取代del 命令,我们从源码层面,来好好的剖析一下,阅读下面文章之前,我们先怀着以下几个疑问去看。

  1. 异步删除会存在并发问题吗?
  2. 它是怎么解决并发问题的?
  3. 为什么要异步删除?

unlink 一个拯救性能的英雄?

我们话不多说,先来直接上最核心的代码

unlink 入口代码

void unlinkCommand(client *c) {//传入lazy 为1delGenericCommand(c,1);
}

del 和 lazy del都会走到这里,这里有个意思的点就是如果被访问到的key 如果已经是一个过期key,那么直接会走过期流程,那么这个删除键可能就不是异步而是由expire 的流程来决定。

/* This command implements DEL and LAZYDEL. */
void delGenericCommand(client *c, int lazy) {int numdel = 0, j;//获取客户端传过来的变量for (j = 1; j < c->argc; j++) {//当前键是否过期,如果过期要走expireIfNeeded(c->db,c->argv[j]);int deleted  = lazy ? dbAsyncDelete(c->db,c->argv[j]) :dbSyncDelete(c->db,c->argv[j]);if (deleted) {//通知是否被watched keysignalModifiedKey(c,c->db,c->argv[j]);//提供一个通知事件入口,为后续代码做扩展notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[j],c->db->id);server.dirty++;numdel++;}}//客户端返回参数addReplyLongLong(c,numdel);
}

下面的代码有几个关键的信息点

  1. expired map(过期键的集合,看我上篇的文章应该知道是怎么回事),过期键的集合不参与会直接被回收内存。
  2. 不是调用了unlink 就会异步删除,而是对应的value 超过了一定的空间长度(空间长度不是字节数)这里要注意
  3. unlink 不是所有行为都是异步,而是只针对big value,且异步空间回收也是只针对bigvalue,其它的空间回收还是在同步代码块里面
/** Delete a key, value, and associated expiration entry if any, from the DB.* If there are enough allocations to free the value object may be put into* a lazy free list instead of being freed synchronously. The lazy free list* will be reclaimed in a different bio.c thread.* */
/*** 因为每个删除的key 其实对应的是一个entry,如果这个entry的value的空间长度超过我们设置的阈值,那么我们会* 将value 放入 一个list 里面去,如果通过bio.c 里面提供的子线程进行空间的回收*/
#define LAZYFREE_THRESHOLD 64
int dbAsyncDelete(redisDb *db, robj *key) {/* Deleting an entry from the expires dict will not free the sds of* the key, because it is shared with the main dictionary. *///首先expire 这个集合是不支持异步的所以会立即删除掉集合里面的数据if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);/* If the value is composed of a few allocations, to free in a lazy way* is actually just slower... So under a certain limit we just free* the object synchronously. *///先在map 总执行unlink 操作,保证元素在map 中移除掉,并得到对应Entry MapdictEntry *de = dictUnlink(db->dict,key->ptr);if (de) {//从entry找到valrobj *val = dictGetVal(de);//根据不同的类型得出val 所占用得字节数//size_t 是c语言常用来表示空间长度的类型,是可以跟long 类型互转size_t free_effort = lazyfreeGetFreeEffort(val);/* If releasing the object is too much work, do it in the background* by adding the object to the lazy free list.* Note that if the object is shared, to reclaim it now it is not* possible. This rarely happens, however sometimes the implementation* of parts of the Redis core may call incrRefCount() to protect* objects, and then call dbDelete(). In this case we'll fall* through and reach the dictFreeUnlinkedEntry() call, that will be* equivalent to just calling decrRefCount(). *///如果val长度超过64个字节 则将val放入异步线程,并将这个entry的value 设置为null,if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {atomicIncr(lazyfree_objects,1);bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);dictSetVal(db->dict,de,NULL);}}/* Release the key-val pair, or just the key if we set the val* field to NULL in order to lazy free it later. */if (de) {//下面的流程和同步流程基本差不多,dictFreeUnlinkedEntry(db->dict,de);//如果是集群情况下,则通知集群下面键删除if (server.cluster_enabled) slotToKeyDel(key->ptr);return 1;} else {return 0;}
}

下面的代码能够解释如果解决并发问题,unlink就是把我们db对应的map,利用链表的指向功能。将该元素从map移除掉

/* Search and remove an element. This is an helper function for* dictDelete() and dictUnlink(), please check the top comment* of those functions.*  这个方法unlink 和普通delete 都会找到这里,unlink的意思主要指在链表中去掉链接,即如果A->B->C, 如果要删除B,则变成A->C* */
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {uint64_t h, idx;dictEntry *he, *prevHe;int table;if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;//是否正在rehashif (dictIsRehashing(d)) _dictRehashStep(d);h = dictHashKey(d, key);for (table = 0; table <= 1; table++) {idx = h & d->ht[table].sizemask;he = d->ht[table].table[idx];//记录一个pre的结点,来用于跳过需要跳过的元素prevHe = NULL;while(he) {//如果匹配到key,则执行 unlink 操作,如果nofree 等于0 则释放内存if (key==he->key || dictCompareKeys(d, key, he->key)) {/* Unlink the element from the list */if (prevHe)prevHe->next = he->next;elsed->ht[table].table[idx] = he->next;//如果是异步删除,则会执行下面一步释放空间if (!nofree) {dictFreeKey(d, he);dictFreeVal(d, he);zfree(he);}d->ht[table].used--;return he;}prevHe = he;he = he->next;}//如果不是正在rehash , 则不需要遍历table[1] ,遍历table[0] 即可if (!dictIsRehashing(d)) break;}return NULL; /* not found */
}

小结

回到开始提出的三个问题
1, 异步删除不会存在并发问题,是一个让人安心,没有坑的方法。因为他是将会造成多线程并发问题的部分置为同步,而将空间回收的部分变为异步处理。
2, 通过将map 数据结构进行unlink 操作首先保证,元素不能被其它线程同步访问到,然后将big value交给异步线程回收
3, 显然对于大的空间回收是一个体力活动,至于他为什么是一个体力活动,咱们将单独说,其实空间回收就是一个大的内存遍历过程。涉及到cpu与内存打交道的一些知识只要知道这个点就行了。

使用建议

通过以上源码的分析,大多数场景都是可以用unlink来取代del操作的,原因如下

  1. redis 普遍删除操作都不会超过我们的默认阈值64的空间长度。因为一般规范我们都要避免大key的存在
  2. 即使我们删除大key,也一般都会处在空间足够的情况下面,所以这个时候空间回收我们都可以为了提高性能,让大的value 内存回收来做异步处理,利用多核的优势提高整个reids的负载能力

相关redis 异步删除配置建议

# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
#    in order to make room for new data, without going over the specified
#    memory limit.
# 2) Because of expire: when a key with an associated time to live (see the
#    EXPIRE command) must be deleted from memory.
# 3) Because of a side effect of a command that stores data on a key that may
#    already exist. For example the RENAME command may delete the old key
#    content when it is replaced with another one. Similarly SUNIONSTORE
#    or SORT with STORE option may delete existing keys. The SET command
#    itself removes any old content of the specified key in order to replace
#    it with the specified string.
# 4) During replication, when a replica performs a full resynchronization with
#    its master, the content of the whole database is removed in order to
#    load the RDB file just transferred.
#
# In all the above cases the default is to delete objects in a blocking way,
# like if DEL was called. However you can configure each case specifically
# in order to instead release memory in a non-blocking way like if UNLINK
# was called, using the following configuration directives.lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no

lazyfree-lazy-eviction
针对redis内存使用达到maxmeory,并设置有淘汰策略时;在被动淘汰键时,是否采用lazy free机制;在lru,lfu场景里面已经阻塞了流程,来释放空间,但是如果在cpu使用率比较足的情况下,建议还是开启(有条件开启),具体我们在lru lfu章节继续分析。

lazyfree-lazy-expire
从expred场景我们本来就是在针对超过10%的过期的时候就会提高速度键的淘汰循环(具体可见expire key 章节),但是此时并不代表我们的整个redis 空间已经不足,所以异步删除来提高整个expire的吞吐速度。
此场景建议开启

lazyfree-lazy-server-del
针对有些指令在处理已存在的键时,会带有一个隐式的DEL键的操作。如rename命令,当目标键已存在,redis会先删除目标键,如果这些目标键是一个big key,那就会引入阻塞删除的性能问题。 此参数设置就是解决这类问题,建议可开启

slave-lazy-flush
针对slave进行全量数据同步,slave在加载master的RDB文件前,会运行flushall来清理自己的数据场景,这个场景我的建议是有条件开启,因为异步删除能够加快这一场景的执行速度,当然这里面有个风险点再与异步清理的时候可能会导致空间未完全释放,但是要加载的新数据。这个时候可能会出现等待清理的过程,具体我们会再其它章节讨论

性能测试

测试代码:

import redis.clients.jedis.Jedis;import java.util.HashMap;
import java.util.Map;/*** @date 2020/09/21**/
public class RedisTest {public static void main(String[] args){//连接本地redis 服务器Jedis jedis = new Jedis("127.0.0.1");Map<String,String> dataMap = new HashMap<>();for(int i=0;i<10000;i++){dataMap.put("test"+i,"adkfjksjdfsjf"+i);}setTestData(dataMap,jedis);Long beforeDelDataTime= System.currentTimeMillis();delData(jedis);Long afterDelDataTime = System.currentTimeMillis();System.out.println("del 100 keys bigcost time:"+(afterDelDataTime-beforeDelDataTime));setTestData(dataMap,jedis);Long beforeUnLInkDataTime= System.currentTimeMillis();unlinkData(jedis);Long afterUnlinkDataTime = System.currentTimeMillis();System.out.println("unlink 100 bigkeys cost time:"+(afterUnlinkDataTime-beforeUnLInkDataTime));}private static void setTestData(Map map,Jedis jedis){for(int i=0;i<100;i++){jedis.hset("test"+i,map);}}private static void delData(Jedis jedis){String[] keys= new String[100];for(int i=0;i<100;i++){keys[i]="test"+i;}jedis.del(keys);}private static void unlinkData(Jedis jedis){String[] keys= new String[100];for(int i=0;i<100;i++){keys[i]="test"+i;}jedis.unlink(keys);}

可以看到从测试角度来看unlink 在大键删除的优势,看文章的朋友有兴趣的也可以自测一下

总结:

这个章节我们从各个角度分析了异步删除的原理,性能和使用场景,但是这个章节里面我也埋下了几个坑,包括内存如何回收,异步线程又是具体如何处理的,lru场景又是怎么样的情况,这些坑我将会再后面的文章一一讲解到, 尽请关注。

redis系列,redis的异步删除我该怎么用?相关推荐

  1. redis系列-redis基础知识总结

    一.Redis 设计架构 1.1.Redis整体架构和redis学习思路 上图是我理解的redis单机工作的一个概图. 我尝试从以下基本内容来学习redis: 单机redis 就单机版而言,我们可以从 ...

  2. Redis系列-Redis笔记(一)

    Redis基础 Redis安装 # 下载 cd /tmp wget http://download.redis.io/releases/redis-3.2.11.tar.gz # 解压 tar -zx ...

  3. redis系列-redis的持久化

    redis对数据的持久化有两种方式:RDB(快照保存)和AOF(命令日志). RDB 介绍:将内存快照保存到磁盘,dump.rdb二进制文件 触发:满足"N 秒内数据集至少有 M 个改动&q ...

  4. redis系列-redis的连接

    Redis 是完全开源免费的,遵守BSD协议,先进的key - value持久化产品.它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list ...

  5. Redis系列(五):Redis的过期键删除策略

    Redis系列(五):Redis的过期键删除策略 - 申城异乡人 - 博客园 本篇博客是Redis系列的第5篇,主要讲解下Redis的过期键删除策略. 本系列的前4篇可以点击以下链接查看: Redis ...

  6. 深入剖析Redis系列(七) - Redis数据结构之列表

    前言 列表(list)类型是用来存储多个 有序 的 字符串.在 Redis 中,可以对列表的 两端 进行 插入(push)和 弹出(pop)操作,还可以获取 指定范围 的 元素列表.获取 指定索引下标 ...

  7. 深入剖析Redis系列(三) - Redis集群模式搭建与原理详解

    前言 在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态.Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 ...

  8. vagrant系列四:vagrant搭建redis与redis的监控程序redis-stat

    上一篇php7环境的搭建 真是火爆.仅仅两天时间,就破了我之前swagger系列的一片文章,看来,大家对搭建好开发环境真是情有独钟. 为了訪问量,我今天再来一篇redis的搭建. 当然不能仅仅是red ...

  9. vagrant系列教程(四):vagrant搭建redis与redis的监控程序redis-stat(转)

    阅读目录 下载redis 解压redis 编译安装redis 配置redis redis开机自启动 系统参数的调整 上一篇php7环境的搭建 真是火爆,仅仅两天时间,就破了我之前swagger系列的一 ...

  10. Redis系列教程(六):Redis缓存和MySQL数据一致性方案详解

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

最新文章

  1. 为窗口添加滚动条事件
  2. 嵌入式Linux下S3C2410的调色板彩色显示
  3. dubbo配置(一)
  4. GDCM:gdcm::ImageChangePlanarConfiguration的测试程序
  5. python大数据运维库_大数据集群运维(10)Pycharm下安装模块
  6. 《数据整理实践指南》一第1章 从头说起:什么是噪音数据
  7. 【转】C++类的sizeof大小
  8. 【Flink】Flink 写入到 CSV BucketingSink 的使用方法
  9. 运行后闪退_好消息好消息,王者荣耀闪退问题苹果也修复啦
  10. 集成activiti-modeler 到 自己的业务系统
  11. 移远 EC20 模组(4G通信模组)AT指令测试 TCP 通信过程
  12. memtest86内存测试工具介绍
  13. linux命令行连接蓝牙音箱,有些Linux发行版用蓝牙连接天猫精灵和小爱音箱没声音...
  14. Linux学习笔记精华总结(选自鸟哥的Linux私房菜)
  15. spring-boot-starter-quartz 添加定时任务立即执行一次的问题解决
  16. 分布式对象存储服务器minio
  17. 防火墙阻止了从docker容器到外部的网络连接
  18. 两点顶点之间最短路径问题
  19. 元境技术助力元宇宙营销 联合发起商广协元宇宙营销研究院
  20. 树莓派从U盘启动系统

热门文章

  1. DMZ区的介绍及连接图
  2. Android 电源键事件流程分析
  3. 做一名有幸福感的计算机教师,做一名有职业幸福感的教师为题目的作文
  4. freeswitch mrcp 源码分析--数据接收(下)
  5. 模仿元气森林:为什么会是画虎画皮难画骨?
  6. 软件测试工程师市场需求量是多少,带你探索软件测试工程师月薪是多少
  7. 最新影视双端app对接苹果cms+详细安装教程
  8. Objective-C 协议最基本解释
  9. FZU - 1759 Problem 1759 Super A^B mod C 欧拉降幂公式
  10. 个人用户永久免费,可自动升级版Excel插件,使用VSTO开发,Excel催化剂功能第5波-使用DAX查询从PowerbiDeskTop中获取数据源...