c++服务端初始化阶段会将某二进制文件load 到内存中,每次请求都会访问该内存数据,c++服务端有持续请求,现在该二进制文件更新了,如何将该二进制文件热更新到内存中且不影响线上请求?内存空间足够

该题上一题,redis 的渐进式 rehash 如何实现的,其中有个 ht[2]如何切换的,衍生出该题

加载新的到另外一块内存里,加载完了 swap 指针不就好了吗?

bitmap

换个端口,启动后更新 nginx 上游,然后关掉老服务

发一个通告, 停服 2 小时进行更新

共享内存实现 jit 吧

为内存中二进制文件维护一个 epoch 和 counter ,更新版本 epoch 加 1 ,请求使用该内存,对应 epoch 的 counter 加 1 ,用完 counter 减 1 。二进制文件组成单链表,head 指向最新的二级制文件,用 cas 的方式更新表头,请求使用当前 head 。gc 线程检查链表中除 head 外 epoch 的 counter 值,如果为 0 ,垃圾回收。

楼上说的很具体了,再具体到 C++,智能指针保护二进制文件数据,每个请求处理暂存指针副本即可

// 全局或者某个单例
std::shared_ptr<...> yourData;

// 载入数据的函数
void loadData() {
std::shared_ptr<...> newData;
// 载入
yourData = newData;
}

// 使用数据的函数
void useData() {
std::shared_ptr<...> theData = yourData;
// 使用 yourData
}

一开始还以为是 C ,刚想说把指针指向新内存地址就完了。。。仔细一看艹!更简单,把新对象赋值给变量就好了嘛。

补充一下,因为楼主没说具体情况,一些很复杂的情况下这种做法可能也会出问题。
假设一个完整的“请求”是每隔一分钟读取这段数据中的某个字节发送给客户端,要发送一百次才算完成请求。此时如果代码里访问数据是直接用全局的指针,比如 send(globalPointer[x]) 这样,就会出现问题。
需要更完整的细节才能确定正确做法。

"换个端口,启动后更新 nginx 上游,然后关掉老服务"

其实不用,很多人并不知道,在最新版本的 linux 内核里,已经支持多个程序绑定同一个 TCP 端口了。

我想到一个方法(未尝试):

  1. 使用 mmap 建立文件映射
  2. 文件更新后使用 madvice ( MADV_DONTNEED )释放映射

理论基础:
文件映射会将文件映射入内存,只有访问了内存才会触发缺页中断载入数据,madvice dontneed 释放了页表,访问相同的内存会再次触发缺页中断载入数据。

疑惑:
mmap 载入的数据会不会随文件的更新而自动更新?毕竟 vma 的 backend 是 file ,能够确认的是 msync 这个系统调用可以将内存中的数据 flush 到文件。

直接分批停机 rolling

多线程情况下修改 shared_ptr 应该是不安全的,建议用 std::atomic_load 与 std::atomic_save 替代修改与读的操作。

读到另一块内存里然后 std::atomic<Content*> 一改不就完了……如果要自动释放原来的内存,套一层 shared_ptr ,或者 github.com/facebook/folly/blob/main/folly/concurrency/AtomicSharedPtr.h

除非你有其他没说出来的需求,比如同一时刻所有 reader 访问的内容必须一致什么的 ……

大佬说的对。

前面的楼层说直接进行新旧内存区域的替换,我觉得业务上可能会有问题。比如直接替换后,新旧内存区域中的数据结构与偏移量都不一样了,如果替换后,还以以前的方式继续访问,很有可能会出问题。

我觉得,应该是以事务的方式,进行新旧内存区域的替换,并且替换后还要重置数据结构与偏移量会更稳,但这就需要把很多业务,改成支持事务的接口调用,方便抽象,有一定的工作量。具体设计方法,可以参考数据库原理与设计相关书籍。

面试题啊 这就..... 想考察啥呢 结果肯定是没有的 不都是要分各自情况吗 能保证直接替换没问题的 加载了 直接替换就是了 ... 关键问题还是要保证对前面调用是否出问题...
说个方法 业务上对于这块数据的获取肯定有指定的几个接口吧 直接热更加载补丁代码 加载完数据 暂停相关线程 把获取数据相关的接口 直接 hook 到补丁代码上 直接返回新数据地址就好了 然后根据自己的业务逻辑 在可以判断前面的数据没有相关引用的情况下 释放掉就好了 再或者强迫症下 就把原本接口的相关数据的地址替换成热更的 再把 hook 的接口恢复过来....

这不像面试题,是来要方案了

还以为说的二进制[可执行]文件呢

其实最安全可靠的是提供接口,让别人调用,而不是拿文件做交互,
既然是文件交互,那么不可避免的存在实时性问题。
如果不关心实时性问题,那么就如一楼所说,是一个内存指针 swap 的问题了。

2 个 shared ptr swap 。 其实就是 double buffer

🤫别说出来

2 个内存块,奇偶切换

你们这格局太小了,动不动说要方案,这种实现方法千千万又不是什么难题

内存够的话 double buffer 双内存块奇偶切换 切换的指针用 atmoi 保护起来

是给服务端程序添加一段热更新的代码? 还是用另外的程序热更新服务端的内存数据?

切换都简单,不考虑性能下 std::atomic或者 AtomicSharedPtr 都可以做,关键要考虑 old 的释放时机,old 内存可能请求正在调用

面试题上面还有一题忘记说了,redis 的渐进式 rehash 如何实现的,其中有个 ht[2]如何切换的,衍生出该题

atomic_load 要自己维护引用计数。

不过你说得对,多线程写同一个 shared_ptr 并不安全。但是多线程读是安全的,因此我们可以这样:

std::shared_ptr<...> yourData;
std::mutex yourDataLock;

// 载入数据的函数
void loadData() {
// 载入
std::shared_ptr<...> newData;
// 更新
{
std::unique_lock<...> lockGuard(yourDataLock);
yourData = newData;
}
}

// 使用数据的函数
void useData() {
std::shared_ptr<...> theData = yourData;
// 使用 yourData
}

好吧你说的是 atomic_store(&yourData, make_shared());

你是对的。我的 lock 做法过了(滑稽)。这可真是个好东西