V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
java253738191
V2EX  ›  程序员

面试题: C++怎么实现热更新内存数据?

  •  
  •   java253738191 · 131 天前 · 2793 次点击
    这是一个创建于 131 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    第 1 条附言  ·  131 天前
    该题上一题,redis 的渐进式 rehash 如何实现的,其中有个 ht[2]如何切换的,衍生出该题
    31 条回复    2022-04-01 11:42:02 +08:00
    ysc3839
        1
    ysc3839  
       131 天前 via Android   ❤️ 1
    加载新的到另外一块内存里,加载完了 swap 指针不就好了吗?
    Protocol
        2
    Protocol  
       131 天前
    bitmap
    JeromeCui
        3
    JeromeCui  
       131 天前
    换个端口,启动后更新 nginx 上游,然后关掉老服务
    Kasumi20
        4
    Kasumi20  
       131 天前   ❤️ 4
    发一个通告, 停服 2 小时进行更新
    fishCatcher
        5
    fishCatcher  
       131 天前 via iPhone
    共享内存实现 jit 吧
    OSDI
        6
    OSDI  
       131 天前   ❤️ 3
    为内存中二进制文件维护一个 epoch 和 counter ,更新版本 epoch 加 1 ,请求使用该内存,对应 epoch 的 counter 加 1 ,用完 counter 减 1 。二进制文件组成单链表,head 指向最新的二级制文件,用 cas 的方式更新表头,请求使用当前 head 。gc 线程检查链表中除 head 外 epoch 的 counter 值,如果为 0 ,垃圾回收。
    nonwill
        7
    nonwill  
       131 天前
    楼上说的很具体了,再具体到 C++,智能指针保护二进制文件数据,每个请求处理暂存指针副本即可
    ipwx
        8
    ipwx  
       131 天前   ❤️ 2
    // 全局或者某个单例
    std::shared_ptr<...> yourData;


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

    // 使用数据的函数
    void useData() {
    std::shared_ptr<...> theData = yourData;
    // 使用 yourData
    }
    xuanbg
        9
    xuanbg  
       131 天前
    一开始还以为是 C ,刚想说把指针指向新内存地址就完了。。。仔细一看艹!更简单,把新对象赋值给变量就好了嘛。
    ysc3839
        10
    ysc3839  
       131 天前 via Android   ❤️ 1
    @ysc3839 补充一下,因为楼主没说具体情况,一些很复杂的情况下这种做法可能也会出问题。
    假设一个完整的“请求”是每隔一分钟读取这段数据中的某个字节发送给客户端,要发送一百次才算完成请求。此时如果代码里访问数据是直接用全局的指针,比如 send(globalPointer[x]) 这样,就会出现问题。
    需要更完整的细节才能确定正确做法。
    3dwelcome
        11
    3dwelcome  
       131 天前   ❤️ 1
    @JeromeCui "换个端口,启动后更新 nginx 上游,然后关掉老服务"

    其实不用,很多人并不知道,在最新版本的 linux 内核里,已经支持多个程序绑定同一个 TCP 端口了。
    feather12315
        12
    feather12315  
       131 天前 via Android
    我想到一个方法(未尝试):
    1. 使用 mmap 建立文件映射
    2. 文件更新后使用 madvice ( MADV_DONTNEED )释放映射

    理论基础:
    文件映射会将文件映射入内存,只有访问了内存才会触发缺页中断载入数据,madvice dontneed 释放了页表,访问相同的内存会再次触发缺页中断载入数据。
    feather12315
        13
    feather12315  
       131 天前 via Android
    疑惑:
    mmap 载入的数据会不会随文件的更新而自动更新?毕竟 vma 的 backend 是 file ,能够确认的是 msync 这个系统调用可以将内存中的数据 flush 到文件。
    Inn0Vat10n
        14
    Inn0Vat10n  
       131 天前
    直接分批停机 rolling
    exch4nge
        15
    exch4nge  
       131 天前
    @ipwx 多线程情况下修改 shared_ptr 应该是不安全的,建议用 std::atomic_load 与 std::atomic_save 替代修改与读的操作。
    icylogic
        16
    icylogic  
       131 天前
    读到另一块内存里然后 std::atomic<Content*> 一改不就完了……如果要自动释放原来的内存,套一层 shared_ptr ,或者 https://github.com/facebook/folly/blob/main/folly/concurrency/AtomicSharedPtr.h

    除非你有其他没说出来的需求,比如同一时刻所有 reader 访问的内容必须一致什么的 ……
    documentzhangx66
        17
    documentzhangx66  
       131 天前   ❤️ 1
    @ysc3839 大佬说的对。

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

    我觉得,应该是以事务的方式,进行新旧内存区域的替换,并且替换后还要重置数据结构与偏移量会更稳,但这就需要把很多业务,改成支持事务的接口调用,方便抽象,有一定的工作量。具体设计方法,可以参考数据库原理与设计相关书籍。
    LifStge
        18
    LifStge  
       131 天前
    面试题啊 这就..... 想考察啥呢 结果肯定是没有的 不都是要分各自情况吗 能保证直接替换没问题的 加载了 直接替换就是了 ... 关键问题还是要保证对前面调用是否出问题...
    说个方法 业务上对于这块数据的获取肯定有指定的几个接口吧 直接热更加载补丁代码 加载完数据 暂停相关线程 把获取数据相关的接口 直接 hook 到补丁代码上 直接返回新数据地址就好了 然后根据自己的业务逻辑 在可以判断前面的数据没有相关引用的情况下 释放掉就好了 再或者强迫症下 就把原本接口的相关数据的地址替换成热更的 再把 hook 的接口恢复过来....
    iceheart
        19
    iceheart  
       131 天前 via Android
    这不像面试题,是来要方案了
    sfqtsh
        20
    sfqtsh  
       131 天前 via Android   ❤️ 1
    还以为说的二进制[可执行]文件呢
    encro
        21
    encro  
       131 天前
    其实最安全可靠的是提供接口,让别人调用,而不是拿文件做交互,
    既然是文件交互,那么不可避免的存在实时性问题。
    如果不关心实时性问题,那么就如一楼所说,是一个内存指针 swap 的问题了。
    anonymousar
        22
    anonymousar  
       131 天前
    2 个 shared ptr swap 。 其实就是 double buffer
    zhangchongjie
        23
    zhangchongjie  
       131 天前
    @iceheart 🤫别说出来
    jones2000
        24
    jones2000  
       131 天前
    2 个内存块,奇偶切换
    java253738191
        25
    java253738191  
    OP
       131 天前
    @iceheart @zhangchongjie 你们这格局太小了,动不动说要方案,这种实现方法千千万又不是什么难题
    boaofCHIAN
        26
    boaofCHIAN  
       131 天前
    内存够的话 double buffer 双内存块奇偶切换 切换的指针用 atmoi 保护起来
    undef404
        27
    undef404  
       131 天前
    是给服务端程序添加一段热更新的代码? 还是用另外的程序热更新服务端的内存数据?
    java253738191
        28
    java253738191  
    OP
       131 天前
    @boaofCHIAN 切换都简单,不考虑性能下 std::atomic<SharedPtr>或者 AtomicSharedPtr 都可以做,关键要考虑 old 的释放时机,old 内存可能请求正在调用
    java253738191
        29
    java253738191  
    OP
       131 天前
    @LifStge 面试题上面还有一题忘记说了,redis 的渐进式 rehash 如何实现的,其中有个 ht[2]如何切换的,衍生出该题
    ipwx
        30
    ipwx  
       131 天前
    @exch4nge 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
    }
    ipwx
        31
    ipwx  
       131 天前
    @exch4nge 好吧你说的是 atomic_store(&yourData, make_shared<myClass>());

    你是对的。我的 lock 做法过了(滑稽)。这可真是个好东西
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2944 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 10:49 · PVG 18:49 · LAX 03:49 · JFK 06:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.