Redis数据库——键过期时间

一.设置键的生存时间或者过期时间

        我们可以在Redis客户端输入命令,可以以秒或者毫秒精度为数据库中的某个键设置生存时间,在指定秒数或者毫秒数之后,服务器会自动删除生存时间为0的键。

        1.1 设置过期时间

        Redis有四个不同的命令可以用于设置键的生存时间或者过期时间:

  • EXPIRE <KEY> <TTL> 命令用于将键key的生存时间设置为ttl秒。
  • PEXPIRE <KEY> <TTL>命令用于将键key的生存时间设置为ttl毫秒。
  • EXPIREAT <KEY> <timestamp> 命令用于将键key的过期时间设置为timestamp所指定秒数时间戳。
  • PEXPIREAT <KEY> <timestamp> 命令用于将键key的过期时间设置为timestamp所指定毫秒数时间戳。

        虽然有多种不同单位不同形式的设置命令,但实际上EXPIRE, PEXPIRE, EXPIREAT三个命令都是使用PEXPIREAT命令来实现的。

def EXPIRE(key, ttl_in_sec):
    #将TTL从秒装换成毫秒
    ttl_in_ms = sec_to_ms(ttl_in_sec)\
    
    PEXPIRE(key, ttl_in_ms)

#PEXPIRE里面调用PEXPIREAT
def PEXPIRE(key, ttl_in_ms):
    #获取以毫秒计算的当前时间戳
    now_ms = get_current_unix_timestamp_in_ms()

    #当前时间加上ttl,得出毫秒格式的键的过期时间
    PEXPIREAT(key, now_ms + ttl_in_ms)

#EXPIREAT里面调用PEXPIREAT
def EXPIREAT(key, expire_time_in_sec):
    #将过期时间从秒转化成毫秒
    expire_time_in_ms = sec_to_ms(expire_time_in_sec)
    PEXPIREAT(key, expire_time_in_ms)

        1.2 保存过期时间

        redisDb结构的expires字典保存了数据库的所有键的过期时间,我们称这个字典为过期字典。

  • 过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库的键)。
  • 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间——一个毫秒精度的时间戳。

         举个例子:

        如果数据库当前状态如下图:

        那么服务器执行一下命令之后,过期字典将新增一个键值对,其中键为message键对象,而值为1391234400000(2014年2月1日0时)

PEXPIREAT message 1391234400000

        执行PEXPIREAT命令的伪代码:

def PEXPIREAT(key, expire_time_in_ms):
    #如果给定的键不存在于键空间,那么不能设置过期时间
    if key not in redisDb.dict:
        return 0

    #在过期字典中关联键和过期时间
    redisDb.expires[key] = expire_time_in_ms

    return 1

        1.3 移除过期时间

        PERSIST命令可以移除一个键的过期时间。

127.0.0.1:6379> set k1 'v1'
OK
127.0.0.1:6379> ttl k1
(integer) -1
127.0.0.1:6379> expire k1 1000
(integer) 1
127.0.0.1:6379> ttl k1
(integer) 998
127.0.0.1:6379> persist k1
(integer) 1
127.0.0.1:6379> ttl k1
(integer) -1
127.0.0.1:6379> 

        伪代码:

def PERSIST(key):
    #判断键是否在过期字典中
    if key not in redisDb.expires:
        return 0

    #删除过期字典中对应键值对
    redisDb.expires.remove(key)

    return 1 

        1.4 计算并返回剩余生存时间

        TTL命令是以秒为单位返回键的剩余生存时间,PTTL命令则是以毫秒为单位返回键的剩余生存时间:

127.0.0.1:6379> hset d1 k1 v1
(integer) 1
127.0.0.1:6379> expire d1 2000
(integer) 1
127.0.0.1:6379> ttl d1
(integer) 1994
127.0.0.1:6379> pttl d1
(integer) 1989776
127.0.0.1:6379> 

        伪代码:

def PTTL(key):
    #判断是否在键空间
    if key not in redisDb.dict:
        #已经过期
        return -2

    #判断是否存在过期字典
    if key not in redisDb.expires:
        #永不过期
        return -1

    #获得过期时间
    #如果键没有设置过期时间,返回None
    expire_timne_in_ms = redisDb.expires[key]

    if expire_time_in_ms is None:
        return -1

    #获取当前时间
    now_ms = get_current_unix_timestamp_in_ms()

    return (expire_timne_in_ms - now_ms)


def TTL(key):
    #获取以毫秒为单位的过期时间
    ttl_in_ms = PTTL(key)
    
    if ttl_in_ms < 0:
        return ttl_in_ms
    else:
        return ms_to_sec(ttl_in_ms)

        1.5 过期键的判定

        通过过期字典,程序可以用以下步骤检查一个给定的键是否过期:

  • 检查给定的键是否存在于过期字典,存在,获取过期时间。
  • 检查当前UNIX时间戳是否大于键的过期时间,如果是的话,表示键已经过期,否则未过期。

        伪代码:

def is_expired(key):
    #获取键的过期时间
    expire_time_in_ms = redisDb.expires.get(key)

    #键没有设置过期时间
    if expire_time_in_ms is None:
        return False

    #获取当前时间时间戳
    now_time_ms = get_current_unix_timestamp_in_ms()

    if now_timw_ms > expire_time_in_ms:
        return True
    else
        return False

        我们也可以使用TTL或者PTTL命令来查看一个命令是否过期,大于等于0或者-1,表示未过期,-2表示过期。但是在redis代码中检查键是否过期的逻辑和上面的伪代码is_expired()方法一直,因为直接访问字典比执行一个命令快。

二. 过期键删除策略

        对于过期键删除有三种可选策略:

  • 定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
  • 惰性删除:放任键过期不管,但是每次从键空间中获取键时,检查取得的键是否过期,如果过期的话,键删除该键,如果没有过期,就要返回该键。
  • 定期删除:每个一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于删除多少过期键,以及检查多少数据库,则有算法决定。

        在这三种策略中,第一种和第三种为主动删除策略,而第二种则为被动删除策略。

        3.1 定时删除

        定时删除策略对内存是最友好的:通过定时器,定时删除策略可以保证过期键会尽可能快的被删除,并释放过期键所占用的内存。

        但是定时删除策略对CPU时间是最不友好的:在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分CPU时间。在内存不紧张但是CPU时间非常紧张的情况下,将CPU时间在删除和当前任务无关的过期键上,会对服务器的响应时间和吞吐量造成影响。

        创建一个定时器需要用到Redis服务器中的时间事件,而当前时间事件是用无序链表实现的,查找一个键是否过期的时间复杂度为O(N),效率比较低。

        所以,要让服务器创建大量的定时器,从而实现定时删除策略,现阶段不大现实。

        3.2 惰性删除

        惰性删除策略对CPU时间来说是友好的:程序只会在取出键时才对键进行过期检查,这样删除的键仅限于当前处理的键,不会在删除其他无关过期键上花费任何CPU时间。

        惰性删除策略对内存来说是最不友好的:键已经过期,但是没有删除,仍然占用内存。

        当数据库中有非常多的过期键,而这些键又恰好你没有被访问到,那他们就永远不会被删除,除非使用FLUSHDB命令,这种情况相当于是内存泄漏。

        比如:日志,在某个时间点后,对它们的访问就会大大减少,甚至不在访问,这些过期数据就会一直占用Redis服务器内存,导致可使用的内存越来越少。

        3.3 定期删除

        定期删除是前面两种策略的折中:

  • 定期删除策略每隔一段时间执行一次删除过期键操作,并且通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
  • 定期删除策略有效的减少因为过期键带来的内存浪费。

        但是定期删除策略的难点是确定删除操作的时长和频率:

  • 如果删除操作执行的太频繁,或者执行时间过长,就会退化成定时删除策略。占用CPU时间过长。
  • 如果删除操作执行的太少,或者执行的时间过短,就会退化成惰性删除,导致浪费内存。

        因此采用定期删除策略,服务器必须根据情况,合理的设置删除操作的执行时长和执行频率。

三. Redis的过期键删除策略

        Redis服务器实际使用的是惰性删除和定期删除两种策略。

        3.1 惰性删除策略的实现

        过期键的惰性删除策略有db.c/expireIfNeeded函数实现,所有读写数据库的redis命令在执行之前都会调用该函数进行检查:

  • 如果输入键已经过期,那么expireIfNeeded函数将输入键从数据库中删除。
  • 如果输入键没有过期,那么expireIfNeeded函数不动作。
int expireIfNeeded(redisDb *db, robj *key) {
    if (!keyIsExpired(db,key)) return 0;

    /* If we are running in the context of a slave, instead of
     * evicting the expired key from the database, we return ASAP:
     * the slave key expiration is controlled by the master that will
     * send us synthesized DEL operations for expired keys.
     *
     * Still we try to return the right information to the caller,
     * that is, 0 if we think the key should be still valid, 1 if
     * we think the key is expired at this time. */
    if (server.masterhost != NULL) return 1;

    /* If clients are paused, we keep the current dataset constant,
     * but return to the client what we believe is the right state. Typically,
     * at the end of the pause we will properly expire the key OR we will
     * have failed over and the new primary will send us the expire. */
    if (checkClientPauseTimeoutAndReturnIfPaused()) return 1;

    /* Delete the key */
    deleteExpiredKeyAndPropagate(db,key);
    return 1;
}

        命令调用expireIfNeeded函数过程如下图:

        另外,因为每一个被访问的键都可能因为过期而被expireIfNeeded函数删除,所以每一个命令的实现函数必须同时处理键存在以及键不存在的情况:

  • 如果键存在,命令按照键存在的情况执行。
  • 当键不存在或者因为过期而被expireIfNeeded函数删除,命令按照不存在的情况执行。

        下图展示了输入GET命令执行过程:

        3.2 定期删除策略实现 

         过期键定期删除策略由redis.c/activeExpireCycle函数实现,每当Redis的服务器周期操作redis.c/serverCron函数执行时,activeExpireCycle函数会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机抽查一部分键的过期时间,并删除过期键。

#define CRON_DBS_PER_CALL 16

#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
                                                   we do extra efforts. */

void activeExpireCycle(int type) {
    /* Adjust the running parameters according to the configured expire
     * effort. The default effort is 1, and the maximum configurable effort
     * is 10. */
    unsigned long
    effort = server.active_expire_effort-1, /* Rescale from 0 to 9. */
    config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
                           ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
    config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
                                 ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
    config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
                                  2*effort,
    config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
                                    effort;

    /* This function has some global state in order to continue the work
     * incrementally across calls. */
    static unsigned int current_db = 0; /* Next DB to test. */
    static int timelimit_exit = 0;      /* Time limit hit in previous call? */
    static long long last_fast_cycle = 0; /* When last fast cycle ran. */

    int j, iteration = 0;
    int dbs_per_call = CRON_DBS_PER_CALL;
    long long start = ustime(), timelimit, elapsed;

    /* When clients are paused the dataset should be static not just from the
     * POV of clients not being able to write, but also from the POV of
     * expires and evictions of keys not being performed. */
    if (checkClientPauseTimeoutAndReturnIfPaused()) return;

    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
        /* Don't start a fast cycle if the previous cycle did not exit
         * for time limit, unless the percentage of estimated stale keys is
         * too high. Also never repeat a fast cycle for the same period
         * as the fast cycle total duration itself. */
        if (!timelimit_exit &&
            server.stat_expired_stale_perc < config_cycle_acceptable_stale)
            return;

        if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
            return;

        last_fast_cycle = start;
    }

    /* We usually should test CRON_DBS_PER_CALL per iteration, with
     * two exceptions:
     *
     * 1) Don't test more DBs than we have.
     * 2) If last time we hit the time limit, we want to scan all DBs
     * in this iteration, as there is work to do in some DB and we don't want
     * expired keys to use memory for too much time. */
     //初始化检查的数据库数量
    if (dbs_per_call > server.dbnum || timelimit_exit)
        dbs_per_call = server.dbnum;

    /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
     * time per iteration. Since this function gets called with a frequency of
     * server.hz times per second, the following is the max amount of
     * microseconds we can spend in this function. */
    timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
    timelimit_exit = 0;
    if (timelimit <= 0) timelimit = 1;

    if (type == ACTIVE_EXPIRE_CYCLE_FAST)
        timelimit = config_cycle_fast_duration; /* in microseconds. */

    /* Accumulate some global stats as we expire keys, to have some idea
     * about the number of keys that are already logically expired, but still
     * existing inside the database. */
    long total_sampled = 0;
    long total_expired = 0;
	//遍历所有数据库
    for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
        /* Expired and checked in a single loop. */
        unsigned long expired, sampled;

        redisDb *db = server.db+(current_db % server.dbnum);

        /* Increment the DB now so we are sure if we run out of time
         * in the current DB we'll restart from the next. This allows to
         * distribute the time evenly across DBs. */
        current_db++;

        /* Continue to expire if at the end of the cycle there are still
         * a big percentage of keys to expire, compared to the number of keys
         * we scanned. The percentage, stored in config_cycle_acceptable_stale
         * is not fixed, but depends on the Redis configured "expire effort". */
        do {
            unsigned long num, slots;
            long long now, ttl_sum;
            int ttl_samples;
            iteration++;

            /* If there is nothing to expire try next DB ASAP. */
			//过期键数量
            if ((num = dictSize(db->expires)) == 0) {
                db->avg_ttl = 0;
                break;
            }
            slots = dictSlots(db->expires);
            now = mstime();

            /* When there are less than 1% filled slots, sampling the key
             * space is expensive, so stop here waiting for better times...
             * The dictionary will be resized asap. */
            if (slots > DICT_HT_INITIAL_SIZE &&
                (num*100/slots < 1)) break;

            /* The main collection cycle. Sample random keys among keys
             * with an expire set, checking for expired ones. */
            expired = 0;
            sampled = 0;
            ttl_sum = 0;
            ttl_samples = 0;

            if (num > config_keys_per_loop)
                num = config_keys_per_loop;

            /* Here we access the low level representation of the hash table
             * for speed concerns: this makes this code coupled with dict.c,
             * but it hardly changed in ten years.
             *
             * Note that certain places of the hash table may be empty,
             * so we want also a stop condition about the number of
             * buckets that we scanned. However scanning for free buckets
             * is very fast: we are in the cache line scanning a sequential
             * array of NULL pointers, so we can scan a lot more buckets
             * than keys in the same time. */
            long max_buckets = num*20;
            long checked_buckets = 0;

            while (sampled < num && checked_buckets < max_buckets) {
                for (int table = 0; table < 2; table++) {
                    if (table == 1 && !dictIsRehashing(db->expires)) break;

                    unsigned long idx = db->expires_cursor;
                    idx &= db->expires->ht[table].sizemask;
                    dictEntry *de = db->expires->ht[table].table[idx];
                    long long ttl;

                    /* Scan the current bucket of the current table. */
                    checked_buckets++;
                    while(de) {
                        /* Get the next entry now since this entry may get
                         * deleted. */
                        dictEntry *e = de;
                        de = de->next;

                        ttl = dictGetSignedIntegerVal(e)-now;
                        if (activeExpireCycleTryExpire(db,e,now)) expired++;
                        if (ttl > 0) {
                            /* We want the average TTL of keys yet
                             * not expired. */
                            ttl_sum += ttl;
                            ttl_samples++;
                        }
                        sampled++;
                    }
                }
                db->expires_cursor++;
            }
            total_expired += expired;
            total_sampled += sampled;

            /* Update the average TTL stats for this database. */
            if (ttl_samples) {
                long long avg_ttl = ttl_sum/ttl_samples;

                /* Do a simple running average with a few samples.
                 * We just use the current estimate with a weight of 2%
                 * and the previous estimate with a weight of 98%. */
                if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
                db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
            }

            /* We can't block forever here even if there are many keys to
             * expire. So after a given amount of milliseconds return to the
             * caller waiting for the other active expire cycle. */
            if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
                elapsed = ustime()-start;
                if (elapsed > timelimit) {
                    timelimit_exit = 1;
                    server.stat_expired_time_cap_reached_count++;
                    break;
                }
            }
            /* We don't repeat the cycle for the current database if there are
             * an acceptable amount of stale keys (logically expired but yet
             * not reclaimed). */
        } while (sampled == 0 ||
                 (expired*100/sampled) > config_cycle_acceptable_stale);
    }

    elapsed = ustime()-start;
    server.stat_expire_cycle_time_used += elapsed;
    latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);

    /* Update our estimate of keys existing but yet to be expired.
     * Running average with this sample accounting for 5%. */
    double current_perc;
    if (total_sampled) {
        current_perc = (double)total_expired/total_sampled;
    } else
        current_perc = 0;
    server.stat_expired_stale_perc = (current_perc*0.05)+
                                     (server.stat_expired_stale_perc*0.95);
}

        activeExpireCycle工作模式:

  • 函数每次运行时,都从一定数量的数据库中取一定数量的随机键进行检查,并删除其中的过期键。
  • 全部变量current_db会记录当前activeExpireCycle函数检查的进度,并在下一次调用activeExpireCycle函数调用时,接着上一次的进度进行处理。比如:当前在执行activeExpireCycle函数在遍历10号数据库时返回了,那么下一次调用activeExpireCycle函数时,从11号数据库开始查找并删除过期键。
  • 随着activeExpireCycle函数的不断执行,服务器中的所有数据库都会检查一遍,这时函数current_db变量重置为0,然后再次开始新一轮的检查工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/270363.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

浅学JWT跨域认证

Json Web令牌简称JWT 由HeaderPayloadSignature组成 Header JWT头是一个描述JWT元数据的JSON对象&#xff0c;alg属性表示签名使用的算法&#xff0c;默认为HMAC SHA256&#xff08;写为HS256&#xff09;&#xff1b;typ属性表示令牌的类型&#xff0c;JWT令牌统一写为JWT。…

AI技术迅猛发展,视频智能化给人类带来了哪些便利?

随着AI技术的迅猛发展&#xff0c;视频智能化也逐渐普及。在我们常见的生产工作和日常生活中&#xff0c;视频智能化都为人类带来了许多便利。今天小编就和大家探讨一下智能化监控带来了哪些便利。 1、安全监控 视频智能化可以实现智能安防监控&#xff0c;例如智慧安防系统Ea…

【并发设计模式】聊聊 基于Copy-on-Write模式下的CopyOnWriteArrayList

在并发编程领域&#xff0c;其实除了使用上一篇中的属性不可变。还有一种方式那就是针对读多写少的场景下。我们可以读不加锁&#xff0c;只针对于写操作进行加锁。本质上就是读写复制。读的直接读取&#xff0c;写的使用写一份数据的拷贝数据&#xff0c;然后进行写入。在将新…

Linux怎么解压zip格式文件?

Linux解压命令zip是一种常见的文件压缩格式&#xff0c;用于把文件打包成一个zip文件&#xff0c;当我们需要共享或是发送时&#xff0c;能够更快速的发送&#xff0c;储存起来能够减少储存空间。那我们在Linux上怎么使用解压命令zip来解压zip格式文件呢&#xff1f;我们一起来…

Web前端VScode/Vue3/git/nvm/node开发环境安装

目录 1 基本配置 2 安装vscode 3 安装vue 4 配置bash 5 安装nvm 6 安装node 7 安装yarn 8 新建项目 9 运行helloworld 1 基本配置 本篇是为了做前端开发的环境而写。使用的操作系统是windows 10 64位 2 安装vscode 现在做vue和node基本就是vscode和webstorm&#x…

入门级:用devEco Studio创建一个鸿蒙APP

文章概叙 本文主要讲的是如何在鸿蒙的开发工具devEco Studio新建一个项目&#xff0c;全文很水&#xff0c;只适合新手!! 开始贴图 假设当前你已经下载好了devEco Studio,但是还没正式开始安装&#xff0c;此时你点击安装包&#xff0c;你会发下如下页面&#xff0c;只需要点…

043、循环神经网络

之——RNN基础 杂谈 第一个对于序列模型的网络&#xff0c;RNN。 正文 1.潜变量自回归模型 潜变量总结过去的信息&#xff0c;再和当前信息一起结合出新的信息。 2.RNN 循环神经网络将观察作为x&#xff0c;与前层隐变量结合得到输出 其中Whh蕴含了整个模型的时序信息&#xf…

10-让Java性能提升的JIT深度剖析

文章目录 JVM的语言无关性解释执行与JITC1、C2与Graal编译器C1编译器C2编译器 分层编译(了解即可)热点代码热点探测方法调用计数器回边计数器 编译优化技术方法内联锁消除标量替换逃逸分析技术逃逸分析的原理逃逸分析 JVM的语言无关性 跨语言&#xff08;语言无关性&#xff0…

【各种**问题系列】Java 数组集合之间的相互转换

&#x1f4cc; 问题点&#xff1a; 在 Coding 过程中经常会遇到数组、List、Set、Map 之间的相互转换......这里记录一下转换的几种方式。&#x1f636;&#x1f636;&#x1f636; 目录 &#x1f4cc; 集合转换 1.数组 转 List&#xff1a; 2.List 转 数组&#xff1a; 3…

【DevOps 工具链】软件版本号命名规范 - 3种规则(读这一篇就够了)

文章目录 1、简述2、常见软件的版本号命名规则3、版本号命名规范整理3.1、XYZ/MMP3.1.1、规则3.1.2、确定3.1.3、举例3.1.4、详细规则 3.2、XYZD/MMPD3.3、VRC3.3.1、规则3.3.2、对"Vxxx"的说明3.3.3、对"Rxxx"的说明3.3.4、对"LLL"的说明3.3.5、…

Databend 开源周报第 125 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 密码策略 Data…

8.21 PowerBI系列之DAX函数专题-帕累托分析

需求 实现 1 按商品小类累积 var rollup_sales calculate(//计算当前累计销售额 [销售额], filter(allselected(order_2[产品小类]),sum(order_2[订单金额])<[销售额]) ) //按小类累积金额,filter内的销售额为选中的各小类的销售额 //金额从大到小累积&#xff0c;用&l…

RabbitMQ如何做到不丢不重

目录 MQTT协议 如何保证消息100%不丢失 生产端可靠性投递 ​编辑 RabbitMQ的Broker端投 &#xff08;1&#xff09;消息持久化 &#xff08;2&#xff09;设置集群镜像模式 &#xff08;3&#xff09;消息补偿机制 消费端 ACK机制改为手动 总结 MQTT协议 先来说下MQTT…

阿里云经济型e实例2核2G3M99元1年,性价比超高的入门级云服务器

产品简介 经济型e实例是阿里云面向个人开发者、学生、小微企业&#xff0c;在中小型网站建设、开发测试、轻量级应用等场景推出的全新入门级云服务器&#xff0c;采用Intel Xeon Platinum架构处理器&#xff0c;支持1:1、1:2、1:4多种处理器内存配比&#xff0c;采用非绑定CPU…

IntelliJ IDEA快捷键及调试

文章目录 一、IntelliJ IDEA 常用快捷键一览表1-IDEA的日常快捷键第1组&#xff1a;通用型第2组&#xff1a;提高编写速度&#xff08;上&#xff09;第3组&#xff1a;提高编写速度&#xff08;下&#xff09;第4组&#xff1a;类结构、查找和查看源码第5组&#xff1a;查找、…

ES8生产实践——Kibana对接Azure AD实现单点登录

基本概念介绍 什么是单点登录 单点登录&#xff08;Single Sign-On&#xff0c;SSO&#xff09;是一种身份验证和访问控制机制&#xff0c;允许用户使用一组凭据&#xff08;通常是用户名和密码&#xff09;仅需登录一次&#xff0c;即可访问多个应用程序或系统&#xff0c;而…

DDIM详解

DDIM详解 参考&#xff1a;https://www.bilibili.com/video/BV1VP411u71p/ 虽然 DDIM 现在主要用于加速采样&#xff0c;但他的实际意义远不止于此。本文将首先回顾 DDPM 的训练和采样过程&#xff0c;再讨论 DDPM 与 DDIM 的关系&#xff0c;然后推导 DDIM 的采样公式&#xf…

MySQL的事务-隔离级别

上篇&#xff0c;整理了MySQL事务的原子性&#xff0c;这篇继续整理MySQL事务的一致性、隔离性和持久性。 2. 一致性指的是事务开始前和结束后&#xff0c;数据库的完整性约束没有被破坏&#xff0c;这保证了数据的完整性和一致性。一致性必须确保数据库从一个一致的状态转换到…

【无标题】【一周安全资讯1223】一图读懂《工业和信息化部办公厅关于组织开展网络安全保险服务试点工作的通知》;15亿条纽约房产记录泄露

要闻速览 1、一图读懂《工业和信息化部办公厅关于组织开展网络安全保险服务试点工作的通知》 2、国家数据局《“数据要素”三年行动计划 (2024—2026年)》公开征求意见 3、中国信息通信研究院发布《公共数据授权运营发展洞察 (2023年)》 4、15亿条纽约房产记录泄露&#xff0c…

c# OpenCvSharp透视矫正六步实现透视矫正(八)

透视矫正,引用文档拍照扫描&#xff0c;相片矫正这块。 读取图像Cv2.ImRead();预处理&#xff08;灰度化&#xff0c;高斯滤波、边缘检测&#xff09;轮廓检测&#xff08;获取到最大轮廓&#xff09;获取最大面积轮廓的四个顶点标识最小矩形坐标透视矫正显示 完整代码 // 1、…