Notes
Notes copied to clipboard
Akka Remote2.4 NAT穿透分析
在Akka2.3里面,默认的实现我们是无法使得两个NAT后的Akka实例组成集群的,原因是这两个NAT是无法直接访问的。 拓扑结构可能是
|---------------NAT1(Docker1)----------Akka Application A1
Net-----|
|---------------NAT2(Docker2)----------Akka Application A2
这里,如果A1,A2绑定的地址可能是:
- 127.0.0.1
- 172.x.x.x
- 0.0.0.0
但是无论你绑定在什么地址上,默认的配置:port和hostname都是没法满足你跨NAT组建Akka集群的。原因是在Akka的Remoting的Transport实现里面,任意的两个相互能够通讯的节点之间,必须要建立两个连接,其分别是:
- 对等节点到本机所绑定在
hostname:port地址Socket上的一个连接。 - 本机的一个随机地址到对等节点连接本机地址时所附带的地址信息的连接,本机将通过同样的方式连接对等节点。
需要注意的是这里的地址信息不一定是TCP传输,协议是可以多样的,可能是TCP/UDP/UNIX也可以是其他的,但是在Transport的API层面都是一样的。
akka.<protocal>://<host>:<port>。同时上面的信息是关于2.3的默认的transport实现的。
这样带来的问题是,在2.3的默认实现里面,我们所捎带的信息是0.0.0.0等这样的信息,作为Server的这个NAT(Docker)后面的应用程序是无法通过这样的地址信息连接到对等节点的。这也就是观察到现象是,收到了接入信息,但是反向连接的时候,失败。
注:官方的收费版本可以在2.3使用该特性。
为了解决这个问题,Akka2.4引入了一个新的设置,bind-hostname和bind-port,这组配置和原来的配置在一起组成了下面的配置对:
- hostname //逻辑地址
- port //逻辑端口
- bind-hostname //实际绑定的地址
- bind-port //实际绑定的端口
需要注意的是官方对这个地方的描述和这里不太一样,但是我这个也是通俗易懂的。
实际上在2.4和2.3之间,hostname和port这两个配置项的意义是发生了改变的,但是代码里面通过针对默认的“”值的特殊处理,做到了2.3保持一样的实际效果。
在这个时候我们就需要在做跨NAT(Docker)的Akka集群的时候,将hostname和port填写为NAT(Docker)的外网地址,然后将bind-hostname和bind-port填写为NAT(Docker)后的地址。比如(Docker环境):
A1
- hostname = 192.168.0.125//逻辑地址
- port = 2552//逻辑端口
- bind-hostname = 0.0.0.0//实际绑定的地址
- bind-port = 2552 //实际绑定的端口
A2
- hostname = 192.168.0.125//逻辑地址
- port = 2553//逻辑端口
- bind-hostname = 0.0.0.0//实际绑定的地址
- bind-port = 2552 //实际绑定的端口
这样Akka就可以进行跨Docker的集群了。
实际上的实现也很简单,就是Transport实现这里,做Listen的时候是绑定的bind-hostname:bind-port,而在逻辑层面(返回给Akka上层次协议)的时候呢是一个逻辑地址,这个地址也便会在做Association的时候发送给对等节点。
这样知道了原理后,我们在实现自己的Transport的时候,也需要做同样的操作。 如(来自我们自己的基于Netty4的实现):
manager ! Bind(self, realBindAddress, pipeline = setting.Tcp.stage, shared = true)
...
在绑定完成后:
promise.tryComplete(Try((Address("tcp", context.system.name, logicBindAddress.getHostString, logicBindAddress.getPort), listenerPromise)))