分布集群(Cluster)

    Erlang/OTP语言平台的分布式程序,由分布互联的Erlang运行系统组成,每个Erlang运行系统被称为节点(Node),节点(Node)间通过TCP互联,消息传递的方式通信:

    Erlang节点由唯一的节点名称标识,节点间通过名称进行通信寻址。 例如在本机启动四个Erlang节点,节点名称分别为:

    1. erl -name node2@127.0.0.1
    2. erl -name node3@127.0.0.1
    3. erl -name node4@127.0.0.1

    .1控制台下建立与其他节点的连接:

    1. (node1@127.0.0.1)1> net_kernel:connect_node('node2@127.0.0.1').
    2. true
    3. (node1@127.0.0.1)2> net_kernel:connect_node('node3@127.0.0.1').
    4. true
    5. (node1@127.0.0.1)3> net_kernel:connect_node('node4@127.0.0.1').
    6. true
    7. (node1@127.0.0.1)4> nodes().

    epmd

    epmd(Erlang Port Mapper Daemon) - Erlang端口映射服务程序,在Erlang节点运行主机上自启动,负责映射节点名称到通信TCP端口号:

    1. (node1@127.0.0.1)6> net_adm:names().
    2. {ok,[{"node1",62740},
    3. {"node2",62746},
    4. {"node3",62877},
    5. {"node4",62895}]}

    安全

    Erlang节点间通过一个相同的cookie进行互连认证。

    Erlang节点Cookie设置:

    1. 1. $HOME/.erlang.cookie文件
    2. 2. erl -setcookie <Cookie>

    本节内容来自: http://erlang.org/doc/reference_manual/distributed.html

    emqttd分布集群设计

    emqttd消息服务器集群基于Erlang/OTP分布式设计,集群原理可简述为下述两条规则:

    1. MQTT客户端订阅主题时,所在节点订阅成功后广播通知其他节点:某个主题(Topic)被本节点订阅。

    2. MQTT客户端发布消息时,所在节点会根据消息主题(Topic),检索订阅并路由消息到相关节点。

    emqttd消息服务器同一集群的所有节点,都会复制一份主题(Topic) -> 节点(Node)映射的路由表,例如:

    emqttd消息服务器每个集群节点,都保存一份主题树(Topic Trie)和路由表。

    例如下述主题订阅关系:

    最终会生成如下主题树(Topic Trie)和路由表(Route Table):

    1. --------------------------
    2. | / \ |
    3. | + # |
    4. | / \ |
    5. | x y |
    6. --------------------------
    7. | t/+/x -> node1, node3 |
    8. | t/+/y -> node1 |
    9. | t/# -> node2 |
    10. | t/a -> node3 |
    11. --------------------------

    订阅(Subscription)与消息派发

    客户端的主题订阅(Subscription)关系,只保存在客户端所在节点,用于本节点内派发消息到客户端。

    例如client1向主题’t/a’发布消息,消息在节点间的路由与派发流程:

    1. title: Message Route and Deliver
    2. client1->node1: Publish[t/a]
    3. node1-->node2: Route[t/#]
    4. node1-->node3: Route[t/a]
    5. node3-->client3: Deliver[t/a]

    假设部署两台服务器s1.emqtt.io, s2.emqtt.io上部署集群:

    节点名

    主机名(FQDN)

    IP地址

    emqttd@192.168.0.10

    s1.emqtt.io

    192.168.0.10

    emqttd@192.168.0.20

    s2.emqtt.io

    192.168.0.20

    节点名格式: , Host必须是IP地址或FQDN(主机名.域名)

    emqttd@s1.emqtt.io节点设置

    emqttd/etc/vm.args:

    1. -name emqttd@s1.emqtt.io
    2. -name emqttd@192.168.0.10

    Warning

    节点启动加入集群后,节点名称不能变更。

    emqttd/etc/vm.args:

    1. -name emqttd@s2.emqtt.io
    2. -name emqttd@192.168.0.20

    节点加入集群

    启动两台节点后,.io上执行:

    或,emqttd@s1.emqtt.io上执行:

    1. $ ./bin/emqttd_ctl cluster join emqttd@s2.emqtt.io
    2. Join the cluster successfully.
    3. Cluster status: [{running_nodes,['emqttd@s1.emqtt.io','emqttd@s2.emqtt.io']}]

    任意节点上查询集群状态:

    1. $ ./bin/emqttd_ctl cluster status
    2. Cluster status: [{running_nodes,['emqttd@s1.emqtt.io','emqttd@s2.emqtt.io']}]

    节点退出集群

    节点退出集群,两种方式:

    1. leave: 本节点退出集群

    2. remove: 从集群删除其他节点

    .io主动退出集群:

    1. $ ./bin/emqttd_ctl cluster leave

    或emqttd@s1.emqtt.io节点上,从集群删除emqttd@s2.emqtt.io节点:

    1. $ ./bin/emqttd_ctl cluster remove emqttd@s2.emqtt.io

    跨节点会话(Session)

    emqttd消息服务器集群模式下,MQTT连接的持久会话(Session)跨节点。

    例如负载均衡的两台集群节点:node1与node2,同一MQTT客户端先连接node1,node1节点会创建持久会话;客户端断线重连到node2时,MQTT的连接在node2节点,持久会话仍在node1节点:

    如果集群节点间存在防火墙,防火墙需要开启4369端口和一个TCP端口段。4369由epmd端口映射服务使用,TCP端口段用于节点间建立连接与通信。

    防火墙设置后,emqttd需要配置相同的端口段,etc/emqttd.config文件:

    1. [{kernel, [
    2. ...
    3. {inet_dist_listen_min, 20000},
    4. {inet_dist_listen_max, 21000}
    5. ]},

    注意事项: NetSplit

    emqttd消息服务器集群需要稳定网络连接以避免发生NetSplit故障。集群设计上默认不自动处理NetSplit,如集群节点间发生NetSplit,需手工重启某个分片上的相关节点。

    Note

    NoSQL数据库领域分布式设计,大多会采用一致性Hash或DHT。emqttd消息服务器集群架构可支持千万级的路由,更大级别的集群可采用一致性Hash、DHT或Shard方式切分路由表。