Redis设计与实现之AOF

一、AOF

Redis 分别提供了 RDB 和 AOF 两种持久化机制:

  • RDB 将数据库的快照(snapshot)以二进制的方式保存到磁盘中。

  • AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。

本章首先介绍 AOF 功能的运作机制,了解命令是如何被保存到 AOF 文件里的,观察不同的 AOF 保存模式对数据的安全性、以及 Redis 性能的影响。

之后会介绍从 AOF 文件中恢复数据库状态的方法,以及该方法背后的实现机制。

最后还会介绍对 AOF 进行重写以调整文件体积的方法,并研究这种方法是如何在不改变数据 库状态的前提下进行的。

因为本章涉及 AOF 运行的相关机制,如果还没了解过 AOF 功能的话,请先阅读 Redis 持久 化手册中关于 AOF 的部分 。

1、AOF 命令同步

Redis 将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的,为了方便起见,我们称呼这种记录过程为同步。 举个例子,如果执行以下命令:

redis> RPUSH list 1 2 3 4
(integer) 4
redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
redis> KEYS *
1) "list"
redis> RPOP list
"4"
redis> LPOP list
"1"
redis> LPUSH list 1
(integer) 3
redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"

那么其中四条对数据库有修改的写入命令就会被同步到 AOF 文件中:

RPUSH list 1 2 3 4
RPOP list
LPOP list
LPUSH list 1

为了处理的方便,AOF 文件使用网络通讯协议的格式来保存这些命令。 比如说,上面列举的四个命令在 AOF 文件中就实际保存如下:

*2
$6 
SELECT
$1
0
*6
$5
RPUSH
$4
list
$1
1
$1
2
$1
3
$1
4
*2
$4
RPOP
$4
list
*2
$4
LPOP
$4
list
*3
$5
LPUSH
$4
list
$1
1

除了 SELECT 命令是 AOF 程序自己加上去的之外,其他命令都是之前我们在终端里执行的命令。

同步命令到 AOF 文件的整个过程可以分为三个阶段:

  1. 命令传播:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程

    序中。

  2. 缓存追加:AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后

    将协议内容追加到服务器的 AOF 缓存中。

  3. 文件写入和保存:AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存 条件被满足的话,fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存 到磁盘中。

以下几个小节将详细地介绍这三个步骤。

2、命令传播

当一个 Redis 客户端需要执行命令时,它通过网络连接,将协议文本发送给 Redis 服务器。比如说,要执行命令 SET KEY VALUE ,客户端将向服务器发送文本 "*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n" 。

服务器在接到客户端的请求之后,它会根据协议文本的内容,选择适当的命令函数,并将各个参数从字符串文本转换为 Redis 字符串对象(StringObject)。

比如说,针对上面的 SET 命令例子,Redis 将客户端的命令指针指向实现 SET 命令setCommand 函数,并创建三个 Redis 字符串对象,分别保存 SET 、KEY 和 VALUE 三个参数(命 令也算作参数)。

每当命令函数成功执行之后,命令参数都会被传播到 AOF 程序,以及 REPLICATION 程序 这个执行并传播命令的过程可以用以下伪代码表示:

if (execRedisCommand(cmd, argv, argc) == EXEC_SUCCESS):
    if aof_is_turn_on():
        # 传播命令到 AOF 程序 
        propagate_aof(cmd, argv, argc)
    if replication_is_turn_on():
        # 传播命令到 REPLICATION 程序 
        propagate_replication(cmd, argv, argc)

 以下是该过程的流程图:

3、缓存追加

当命令被传播到 AOF 程序之后,程序会根据命令以及命令的参数,将命令从字符串对象转换 回原来的协议文本。

比如说,如果 AOF 程序接受到的三个参数分别保存着 SET 、KEY 和 VALUE 三个字符串,那么 它将生成协议文本 "*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n" 。

协议文本生成之后,它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。 redisServer 结构维持着 Redis 服务器的状态,aof_buf 域则保存着所有等待写入到 AOF 文件的协议文本:

struct redisServer {
    // 其他域...
    sds aof_buf;
    // 其他域... 
};

 至此,追加命令到缓存的步骤执行完毕。 综合起来,整个缓存追加过程可以分为以下三步:

1. 接受命令、命令的参数、以及参数的个数、所使用的数据库等信息。

2. 将命令还原成 Redis 网络通讯协议。
3. 将协议文本追加到aof_buf末尾。

4、文件写入和保存

每当服务器常规任务函数被执行、或者事件处理器被执行时,aof.c/flushAppendOnlyFile 函数都会被调用,这个函数执行以下两个工作:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。 SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

两个步骤都需要根据一定的条件来执行,而这些条件由 AOF 所使用的保存模式来决定,以下 小节就来介绍 AOF 所使用的三种保存模式,以及在这些模式下,步骤 WRITE 和 SAVE 的调 用条件。

5、 AOF 保存模式

Redis 目前支持三种 AOF 保存模式,它们分别是:
1. AOF_FSYNC_NO :不保存。
2. AOF_FSYNC_EVERYSEC :每一秒钟保存一次。
3. AOF_FSYNC_ALWAYS :每执行一个命令保存一次。

以下三个小节将分别讨论这三种保存模,在这种模式下,每次调用 flushAppendOnlyFile 函数,WRITE 都会被执行,但 SAVE 会被 略过。

在这种模式下,SAVE 只会在以下任意一种情况中被执行:
• Redis 被关闭
• AOF 功能被关闭
• 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)

这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。 每一秒钟保存一次,在这种模式中,SAVE 原则上每隔一秒钟就会执行一次,因为 SAVE 操作是由后台子线程调用 的,所以它不会引起服务器主进程阻塞。

注意,在上一句的说明里面使用了词语“原则上” ,在实际运行中,程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次,它和调用 flushAppendOnlyFile 函数时 Redis 所处的 状态有关。

每当 flushAppendOnlyFile 函数被调用时,可能会出现以下四种情况: • 子线程正在执行 SAVE ,并且:

1. 这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。

2. 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。 注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的)SAVE ,因此这里 WRITE 会比平时阻塞更长时间。

• 子线程没有在执行 SAVE ,并且:
3. 上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。 4. 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。

可以用流程图表示这四种情况:

 根据以上说明可以知道,在“每一秒钟保存一次”模式下,如果在情况 1 中发生故障停机,那么 用户最多损失小于 2 秒内所产生的所有数据。

如果在情况 2 中发生故障停机,那么用户损失的数据是可以超过 2 秒的。
Redis 官网上所说的,AOF 在“每一秒钟保存一次”时发生故障,只丢失 1 秒钟数据的说法,实际上并不准确。每执行一个命令保存一次,在这种模式下,每次执行完一个命令之后,WRITE 和 SAVE 都会被执行。另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。

6、AOF 保存模式对性能和安全性的影响

在上一个小节,我们简短地描述了三种 AOF 保存模式的工作方式,现在,是时候研究一下这三个模式在安全性和性能方面的区别了。
对于三种 AOF 保存模式,它们对服务器主进程的阻塞情况如下:

  1. 不保存(AOF_FSYNC_NO):写入和保存都由主进程执行,两个操作都会阻塞主进程。

  2. 每一秒钟保存一次(AOF_FSYNC_EVERYSEC):写入操作由主进程执行,阻塞主进程。保存 操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞 时长。

  3. 每执行一个命令保存一次(AOF_FSYNC_ALWAYS):和模式 1 一样。

因为阻塞操作会让 Redis 主进程无法持续处理请求,所以一般说来,阻塞操作执行得越少、完

成得越快,Redis 的性能就越好。

模式 1 的保存操作只会在 AOF 关闭或 Redis 关闭时执行,或者由操作系统触发,在一般情况 下,这种模式只需要为写入阻塞,因此它的写入性能要比后面两种模式要高,当然,这种性能 的提高是以降低安全性为代价的:在这种模式下,如果运行的中途发生停机,那么丢失数据的 数量由操作系统的缓存冲洗策略决定。

模式 2 在性能方面要优于模式 3 ,并且在通常情况下,这种模式最多丢失不多于 2 秒的数据, 所以它的安全性要高于模式 1 ,这是一种兼顾性能和安全性的保存方案。

模式 3 的安全性是最高的,但性能也是最差的,因为服务器必须阻塞直到命令信息被写入并保 存到磁盘之后,才能继续处理请求。

综合起来,三种 AOF 模式的操作特性可以总结如下:

7、 AOF 文件的读取和数据还原

AOF 文件保存了 Redis 的数据库状态,而文件里面包含的都是符合 Redis 通讯协议格式的命令文本。
这也就是说,只要根据 AOF 文件里的协议,重新执行一遍里面指示的所有命令,就可以还原Redis 的数据库状态了。
Redis 读取 AOF 文件并还原数据库的详细步骤如下:

1. 创建一个不带网络连接的伪客户端(fake client)。
2. 读取 AOF 所保存的文本,并根据内容还原出命令、命令的参数以及命令的个数。 3. 根据命令、命令的参数和命令的个数,使用伪客户端执行该命令。
4. 执行 2 和 3 ,直到 AOF 文件中的所有命令执行完毕。

完成第 4 步之后,AOF 文件所保存的数据库就会被完整地还原出来。

注意,因为 Redis 的命令只能在客户端的上下文中被执行,而 AOF 还原时所使用的命令来自 于 AOF 文件,而不是网络,所以程序使用了一个没有网络连接的伪客户端来执行命令。伪客 户端执行命令的效果,和带网络连接的客户端执行命令的效果,完全一样。整个读取和还原过程可以用以下伪代码表示:

def READ_AND_LOAD_AOF():
    # 打开并读取 AOF 文件
    file = open(aof_file_name) 
    while file.is_not_reach_eof():
        # 读入一条协议文本格式的 Redis 命令
        cmd_in_text = file.read_next_command_in_protocol_format()
        # 根据文本命令,查找命令函数,并创建参数和参数个数等对象 
        cmd, argv, argc = text_to_command(cmd_in_text)
        # 执行命令
        execRedisCommand(cmd, argv, argc)
# 关闭文件 file.close()

作为例子,以下是一个简短的 AOF 文件的内容:

*2
$6
SELECT
$1
0
*3
$3
SET
$3
key
$5
value
*8
$5
RPUSH
$4
list
$1
1
$1
2
$1
3
$1
4
$1
5
$1
6

当程序读入这个 AOF 文件时,它首先执行 SELECT 0 命令——这个 SELECT 命令是由 AOF 写 入程序自动生成的,它确保程序可以将数据还原到正确的数据库上。

然后执行后面的 SET key value 和 RPUSH 1 2 3 4 命令,还原 key 和 list 两个键的数据。 Note: 为了避免对数据的完整性产生影响,在服务器载入数据的过程中,只有和数据库无关的订阅与发布功能可以正常使用,其他命令一律返回错误。

8、 AOF 重写

AOF 文件通过同步 Redis 服务器所执行的命令,从而实现了数据库状态的记录,但是,这种同步方式会造成一个问题:随着运行时间的流逝,AOF 文件会变得越来越大。 举个例子,如果服务器执行了以下命令:

RPUSH list 1 2 3 4 // [1, 2, 3, 4] 
RPOP list // [1, 2, 3]
LPOP list // [2, 3] 
LPUSH list 1 // [1, 2, 3]

那么光是记录 list 键的状态,AOF 文件就需要保存四条命令。

另一方面,有些被频繁操作的键,对它们所调用的命令可能有成百上千、甚至上万条,如果这 样被频繁操作的键有很多的话,AOF 文件的体积就会急速膨胀,对 Redis 、甚至整个系统的造 成影响。

为了解决以上的问题,Redis 需要对 AOF 文件进行重写(rewrite):创建一个新的 AOF 文件 来代替原有的 AOF 文件,新 AOF 文件和原有 AOF 文件保存的数据库状态完全一样,但新 AOF 文件的体积小于等于原有 AOF 文件的体积。

以下就来介绍 AOF 重写的实现方式。

9、AOF 重写的实现

所谓的“重写”其实是一个有歧义的词语,实际上,AOF 重写并不需要对原有的 AOF 文件进行 任何写入和读取,它针对的是数据库中键的当前值。

考虑这样一个情况,如果服务器对键 list 执行了以下四条命令:

RPUSH list 1 2 3 4 // [1, 2, 3, 4]
RPOP list // [1, 2, 3]
LPOP list // [2, 3]
LPUSH list 1 // [1, 2, 3]

那么当前列表键 list 在数据库中的值就为 [1, 2, 3] 。

如果我们要保存这个列表的当前状态,并且尽量减少所使用的命令数,那么最简单的方式不是 去 AOF 文件上分析前面执行的四条命令,而是直接读取 list 键在数据库的当前值,然后用一 条 RPUSH 1 2 3 命令来代替前面的四条命令。

再考虑这样一个例子,如果服务器对集合键 animal 执行了以下命令:

SADD animal cat// {cat}
SADD animal dog panda tiger// {cat, dog, panda, tiger}
SREM animal cat// {dog, panda, tiger}
SADD animal cat lion// {cat, lion, dog, panda, tiger}

那么使用一条 SADD animal cat lion dog panda tiger 命令,就可以还原 animal 集合的状 态,这比之前的四条命令调用要大大减少。

除了列表和集合之外,字符串、有序集、哈希表等键也可以用类似的方法来保存状态,并且保 存这些状态所使用的命令数量,比起之前建立这些键的状态所使用命令的数量要大大减少。

根据键的类型,使用适当的写入命令来重现键的当前值,这就是 AOF 重写的实现原理。整个 重写过程可以用伪代码表示如下:

def AOF_REWRITE(tmp_tile_name): 
    f = create(tmp_tile_name)
    # 遍历所有数据库
    for db in redisServer.db:
    # 如果数据库为空,那么跳过这个数据库 
    if db.is_empty(): continue
    # 写入 SELECT 命令,用于切换数据库 
        f.write_command("SELECT " + db.number)
    # 遍历所有键 
    for key in db:
    # 如果键带有过期时间,并且已经过期,那么跳过这个键
    if key.have_expire_time() and key.is_expired(): 
        continue
    if key.type == String:
        # 用 SET key value 命令来保存字符串键 
        value = get_value_from_string(key) 
        f.write_command("SET " + key + value)
    elif key.type == List:
    # 用 RPUSH key item1 item2 ... itemN 命令来保存列表键
        item1, item2, ..., itemN = get_item_from_list(key) 
        f.write_command("RPUSH " + key + item1 + item2 + ... + itemN)
    elif key.type == Set:
    # 用 SADD key member1 member2 ... memberN 命令来保存集合键
    member1, member2, ..., memberN = get_member_from_set(key) 
        f.write_command("SADD " + key + member1 + member2 + ... + memberN)
    elif key.type == Hash:
    # 用 HMSET key field1 value1 field2 value2 ... fieldN valueN 命令来保存哈希键
        field1, value1, field2, value2, ..., fieldN, valueN =\
        get_field_and_value_from_hash(key)
        f.write_command("HMSET " + key + field1 + value1 + field2 + value2 +\
                        ... + fieldN + valueN)
    elif key.type == SortedSet:
        # 用 ZADD key score1 member1 score2 member2 ... scoreN memberN # 命令来保存有序集键score1, member1, score2, member2, ..., scoreN, memberN = \
    get_score_and_member_from_sorted_set(key)
    f.write_command("ZADD " + key + score1 + member1 + score2 + member2 +\
                    ... + scoreN + memberN)
    else: raise_type_error()
    # 如果键带有过期时间,那么用 EXPIREAT key time 命令来保存键的过期时间 
    if key.have_expire_time():
    f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())
# 关闭文件 
f.close()

 10、 AOF 后台重写

上一节展示的 AOF 重写程序可以很好地完成创建一个新 AOF 文件的任务,但是,在执行这个程序的时候,调用者线程会被阻塞。
很明显,作为一种辅佐性的维护手段,Redis 不希望 AOF 重写造成服务器无法处理请求,所以

Redis 决定将 AOF 重写程序放到(后台)子进程里执行,这样处理的最大好处是:

1. 子进程进行 AOF 重写期间,主进程可以继续处理命令请求。

2. 子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数 据的安全性。

不过,使用子进程也有一个问题需要解决:因为子进程在进行 AOF 重写期间,主进程还需要 继续处理命令,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致。

为了解决这个问题,Redis 增加了一个 AOF 重写缓存,这个缓存在 fork 出子进程之后开始启 用,Redis 主进程在接到新的写命令之后,除了会将这个写命令的协议内容追加到现有的 AOF 文件之外,还会追加到这个缓存中:

换言之,当子进程在执行 AOF 重写时,主进程需要执行以下三个工作:

1. 处理命令请求。
2. 将写命令追加到现有的 AOF 文件中。
3. 将写命令追加到 AOF 重写缓存中。

这样一来可以保证:
1. 现有的 AOF 功能会继续执行,即使在 AOF 重写期间发生停机,也不会有任何数据丢失。

2. 所有对数据库进行修改的命令都会被记录到 AOF 重写缓存中。

当子进程完成 AOF 重写之后,它会向父进程发送一个完成信号,父进程在接到完成信号之后, 会调用一个信号处理函数,并完成以下工作:

1. 将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。

2. 对新的 AOF 文件进行改名,覆盖原有的 AOF 文件。
当步骤 1 执行完毕之后,现有 AOF 文件、新 AOF 文件和数据库三者的状态就完全一致了。 当步骤 2 执行完毕之后,程序就完成了新旧两个 AOF 文件的交替。

这个信号处理函数执行完毕之后,主进程就可以继续像往常一样接受命令请求了。在整个 AOF 后台重写过程中,只有最后的写入缓存和改名操作会造成主进程阻塞,在其他时候,AOF 后台 重写都不会对主进程造成阻塞,这将 AOF 重写对性能造成的影响降到了最低。

以上就是 AOF 后台重写,也即是 BGREWRITEAOF 命令的工作原理。

11、AOF 后台重写的触发条件

AOF 重写可以由用户通过调用 BGREWRITEAOF 手动触发。

另外,服务器在 AOF 功能开启的情况下,会维持以下三个变量:
• 记录当前 AOF 文件大小的变量 aof_current_size 。
• 记录最后一次 AOF 重写之后,AOF 文件大小的变量 aof_rewirte_base_size 。 • 增长百分比变量aof_rewirte_perc。

每次当 serverCron 函数执行时,它都会检查以下条件是否全部满足,如果是的话,就会触发 自动的 AOF 重写:

  1. 没有 BGSAVE 命令在进行。

  2. 没有 BGREWRITEAOF 在进行。

  3. 当前 AOF 文件大小大于 server.aof_rewrite_min_size (默认值为 1 MB)。

  4. 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分 比。

默认情况下,增长百分比为 100% ,也即是说,如果前面三个条件都已经满足,并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大一倍的话,那么触发自动 AOF 重写。

二、小结

  • AOF 文件通过保存所有修改数据库的命令来记录数据库的状态。

  • AOF 文件中的所有命令都以 Redis 通讯协议的格式保存。

  • 不同的 AOF 保存模式对数据的安全性、以及 Redis 的性能有很大的影响。

  • AOF 重写的目的是用更小的体积来保存数据库状态,整个重写过程基本上不影响 Redis 主进程处理命令请求。

  • AOF 重写是一个有歧义的名字,实际的重写工作是针对数据库的当前值来进行的,程序 既不读写、也不使用原有的 AOF 文件。

  • AOF 可以由用户手动触发,也可以由服务器自动触发。

 

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

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

相关文章

从零开发短视频电商 在AWS上SageMaker部署模型自定义日志输入和输出示例

从零开发短视频电商 在AWS上SageMaker部署模型自定义日志输入和输出示例 怎么部署自定义模型请看:从零开发短视频电商 在AWS上用SageMaker部署自定义模型 都是huaggingface上的模型或者fine-tune后的。 为了适配jumpstart上部署的模型的http输入输出,我…

springMVC-与spring整合

一、基本介绍 在项目开发中,spring管理的 Service和 Respository,SrpingMVC管理 Controller和ControllerAdvice,分工明确 当我们同时配置application.xml, springDispatcherServlet-servlet.xml , 那么注解的对象会被创建两次, 故…

2023 下半年系统架构设计师学习进度

文章目录 复习计划:每周350分钟第一周(339分钟)第二周(265分钟)第三周(171分钟)第四周(214分钟)第五周(274分钟)第六周(191分钟&#…

初识Stable Diffusion

界面选项解读 这是在趋动云上部署的Stable Diffusion txt2img prompt (1)分割符号:使用逗号 , 用于分割词缀,且有一定权重排序功能,逗号前权重高,逗号后权重低 (2)建议的通用范式…

【Java JMM】编译和优化

1 前端编译 在 Java 技术下, “编译期” 是一个比较含糊的表述, 因为它可能指的是 前端编译器 (“编译器的前端” 更准确一些) 把 *.java 文件转变成 *.class 文件的过程Java 虚拟机的即时编译器 (常称 JIT 编译器, Just In Time Compiler) 运行期把字节码转变成本地机器码的过…

《Python》面试常问:深拷贝、浅拷贝、赋值之间的关系(附可变与不可变)【用图文讲清楚!】

背景 想必大家面试或者平时学习经常遇到问python的深拷贝、浅拷贝和赋值之间的区别了吧?看网上的文章很多写的比较抽象,小白接收的难度有点大,于是乎也想自己整个文章出来供参考 可变与不可变 讲深拷贝和浅拷贝之前想讲讲什么是可变数据类型…

Pytorch常用的函数(五)np.meshgrid()和torch.meshgrid()函数解析

Pytorch常用的函数(五)np.meshgrid()和torch.meshgrid()函数解析 我们知道torch.meshgrid()函数的功能是生成网格,可以用于生成坐标; 在numpy中也有一样的函数np.meshgrid(),但是用法不太一样,我们直接上代码进行解释。 1、两者…

如何在Window系统下搭建Nginx服务器环境并部署前端项目

1.下载并安装Nginx 在nginx官网nginx: download 下载稳定版本至自己想要的目录。 解压后进入目录 2.启动Nginx服务器 启动方式有两种: (1)直接进入nginx安装目录下,双击nginx.exe运行,此时命令行窗口一闪而过&…

浏览器 cookie 的原理(详)

目录 1,cookie 的出现2,cookie 的组成浏览器自动发送 cookie 的条件 3,设置 cookie3.1,服务端设置3.1,客户端设置3.3,删除 cookie 4,使用流程总结 整理和测试花了很大时间,如果对你有…

python调用GPT API

每次让gpt给我生成一个调用api的程序时,他经常会调用以前的一些api的方法,导致我的程序运行错误,所以这期记录一下使用新的方法区调用api 参考网址 Migration Guide,这里简要地概括了一下新版本做了哪些更改 OpenAI Python API l…

引领汽车营销新趋势,3DCAT实时云渲染助力汽车三维可视化

当前,汽车产业发展正从电动化的上半场,向智能化的下半场迈进。除了车机技术体验的智能化之外,观车体验的智能化也不容忽视。 这是因为,随着数字化、智能化、个性化的趋势,消费者对汽车的需求和期待也越来越高&#xf…

2016年第五届数学建模国际赛小美赛B题直达地铁线路解题全过程文档及程序

2016年第五届数学建模国际赛小美赛 B题 直达地铁线路 原题再现: 在目前的大都市地铁网络中,在两个相距遥远的车站之间运送乘客通常需要很长时间。我们可以建议在两个长途车站之间设置直达班车,以节省长途乘客的时间。   第一部分&#xf…

Qt的简单游戏实现提供完整代码

文章目录 1 项目简介2 项目基本配置2.1 创建项目2.2 添加资源 3 主场景3.1 设置游戏主场景配置3.2 设置背景图片3.3 创建开始按钮3.4 开始按钮跳跃特效实现3.5 创建选择关卡场景3.6 点击开始按钮进入选择关卡场景 4 选择关卡场景4.1场景基本设置4.2 背景设置4.3 创建返回按钮4.…

Java面向对象(初级)

面向对象编程(基础) 面向对象编程(OOP)是一种编程范式,它强调程序设计是围绕对象、类和方法构建的。在面向对象编程中,程序被组织为一组对象,这些对象可以互相传递消息。面向对象编程的核心概念包括封装、继承和多态。…

2023.12.21 关于 Redis 常用数据结构 和 单线程模型

目录 各数据结构具体编码方式 查看 key 对应 value 的编码方式 Reids 单线程模型 经典面试题 IO 多路复用 Redis 常用数据结构 Redis 中所有的 key 均为 String 类型,而不同的是 value 的数据类型却有很多种以下介绍 5 种 value 常见的数据类型 注意&#xff1…

阿里云 ACK One 新特性:多集群网关,帮您快速构建同城容灾系统

云布道师 近日,阿里云分布式云容器平台 ACK One[1]发布“多集群网关”[2](ACK One Multi-cluster Gateways)新特性,这是 ACK One 面向多云、多集群场景提供的云原生网关,用于对多集群南北向流量进行统一管理。 基于 …

虚拟机的下载、安装(模拟出服务器)

下载 vmware workstation(收费的虚拟机) 下载vbox 网址:Oracle VM VirtualBox(免费的虚拟机) 以下选择一个下载即可,建议下载vbox,因为是免费的。安装的时候默认下一步即可(路径最好…

hiveserver负载均衡配置

一.安装nginx 参数我的另一篇文章:https://mp.csdn.net/mp_blog/creation/editor/135152478 二.配置nginx服务参数 worker_processes 1; events { worker_connections 1024; } stream { upstream hiveserver2 { # least_conn; # 使用最少连接路由…

八大排序算法@直接插入排序(C语言版本)

目录 直接插入排序概念算法思想代码实现核心算法:直接插入排序的算法实现: 特性总结 直接插入排序 概念 算法思想 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新…

【Spring实战】配置多数据源

文章目录 1. 配置数据源信息2. 创建第一个数据源3. 创建第二个数据源4. 创建启动类及查询方法5. 启动服务6. 创建表及做数据7. 查询验证8. 详细代码总结 通过上一节的介绍,我们已经知道了如何使用 Spring 进行数据源的配置以及应用。在一些复杂的应用中,…