Erlang热更新原理

Erlang热更新原理

1
前言 实践是检验的结果

env

1
2
erl OTP 19
vmware linux centos7

一、热更新原理

1、热更新原理

 官方文档是这样写道:
Erlang supports change of code in a running system. Code replacement is done on module level.(Erlang支持在运行中的系统的代码修改,代码替换是在模块级别上完成的)

The code of a module can exist in two variants in a system: current and old. When a module is loaded into the system for the first time, the code becomes ‘current’. If then a new instance of the module is loaded, the code of the previous instance becomes ‘old’ and the new instance becomes ‘current’.(意思是维护了两套代码,new and old,代码使用的是current)

  • 这里是name为t2的原因,首次编写的时候是一个gen_server,发现pid没有变化
  • 所以又写了个t2来测试,gen_server热更新下面讲
    实现的热更新代码
    * 热更时实际调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -module(t2).
    -export([loop/0]).

    loop() ->
    receive
    code_switch ->
    t2:loop();
    Msg ->
    ...
    loop()
    end.

    需要一张图来解释一下,外部m:loop 和模块内loop的区别,内部调用不会加载模块代码,外部调用函数,会先加载模块代码再执行,外部调用的开销比内部调用的大,所以在写代码的时候,不要在内部用外部调用。

引用没有开花的树:

1
2
3
1.外部调用的开销比本地调用大一点。外部调用时通过指针找到这个模块函数的导出地址,当模块热更时,就会修改这个指针指向的地址。内部调用是上下文跳转,对比少了一个指针查找,外加原子锁的开销。

2.外部调用的函数代码加载的时间稍微长一点,需要获取外部函数在导出函数表的地址,避免在执行时才去导出函数表查找函数地址造成开销。
*发现的问题
  1. Gen_server在hot code loading的时候没有被kill掉,是怎么一回事?
1
2
3
4
5
6
7
8
9
%%@gen_sever start_link callback
init([]) ->
io:format("i am init ~p",[self()]),
{ok, #t_state{}, 10000}.

l() ->
compile:file(t),
code:purge(t),
code:load_file(t),

设置了一个超时gen_server,这时查看gen_server进程正在进行调用的函数

不同在于在代码的版本切换中,在官方文档中发现道:

 If a third instance of the module is loaded, the code server removes (purges) the old code and any processes lingering in it is terminated. Then the third instance becomes ‘current’ and the previously current code becomes ‘old’.
(假设模块的第三个实例被加载,code_server移除(使纯净)旧代码和调用他的进程,第三个实例将会变成’current’之前的实例(第二次加载)将会变成old)

 结合这个问题,由于t2例子是阻塞在loop/0里面,当第一次调用loadfile的时候,远本阻塞的函数继续使用old代码,而新调用loop函数的代码将会使用新代码,当第二次使用loadfile的时候,purge将会把初始化的那一套代码给delete掉,就并且kill掉使用旧代码的进程,而实现的gen_server,阻塞在gen_serverloop中,都是在gen调用t的代码,所以没有kill掉进程

2、热更新code_server源码阅读

 阅读code:load_file后,发现是code_server在做事
 code server是OTP框架的,有两种模式interactive(主动) or embedded(手动),在启动的时候通过参数修改

-mode xxx```
1
2
3
4
5
6
为了防止修改kernel、stadlib、compiler的代码,给这些模块的代码设置了sticky的状态,可以在命令行用-nostick修改

走到最后调用了erlang:module_loaded/1 (BIF函数)

#### 二、参考项目热更新工具
编译 -> 上传 -> erlang shell远程节点,封装cmd到arg中,执行处理函数,解析cmd,reload beam

code:soft_purge(Module),
Ret = code:load_file(Module),
?INFO_MSG(“Module:w, reload:w”, [Module, Ret])

1
2
最终代码走向熟悉的code模块
#### 三、自己实现一个热工新工具

#!/usr/bin/bash

ERL=’/opt/erlang19.3/bin/erl’
TARGET=$1
PARAMS=” -setcookie hill -pa src/“
NODE=”hill_test”
TARGET_ERL=$2

help(){
echo ‘使用说明’
echo ‘基本语法: 功能指令 [option]’
echo ‘命令模块:’
echo “输入start 或者 update”
}

start(){
echo “start erl”
CMD=start
RANDOM=$(date +%N)
cd src/
R= erlc *.erl
cd ..
${ERL} -name ${NODE} $PARAMS -s t s
}

update(){
echo ${TARGET_ERL}
echo “update now”
echo “compile now”
cd src
TARGET_ERL1=”${TARGET_ERL}.erl”
echo ${TARGET_ERL1}
erlc ${TARGET_ERL1}
cd ../
echo “compile ok”
CMD=$1
RANDOM=$(date +%N)
${ERL} -name command_${CMD}_${RANDOM} $PARAMS -s t l -extra ${NODE}
}

case ${TARGET} in
update) update $*;;
start) start;;
*) custom_param ;;
esac

1
2
3
4
![](https://note.youdao.com/yws/api/personal/file/862876B45F0842A2B5F42FED4D892104?method=download&shareKey=3c1504f3df4cb6561c804d0e6d674f5c)

#### 脑图总结
还没开搞

资源地址(提取码: )
脑图

1
2
##### 需要深究的点(待补充)
1. 能否看到代码调用时的版本时old还是current

待补充

1
##### 我自己的学习资料

https://pan.baidu.com/s/1X7XqI3QDehabRvVEFzEWFQ


##### 拓展阅读
1. 底层热更新的实现
    [参考这篇博客](https://blog.csdn.net/mycwq/article/details/43372687)

##### 引用  
##### * [没有开花的树](https://blog.csdn.net/mycwq/article/details/41175237)
##### * [坚强2002的博客](https://www.cnblogs.com/me-sa/archive/2011/10/29/erlang0010.html)
##### * [官方文档](http://erlang.org/doc/reference_manual/code_loading.html)