假设客户端机器IP为IP1,HAProxyIP为IP2,后台的RMI服务器有2台,分别为IP3和IP4
IP1--->IP2------>IP3
|_____>IP4
正常情况下,IP1的客户端代码指向IP2,IP2做分流到IP3(负载均衡),IP3返回的真实服务地址为IP3.
则响应通过IP2回到IP1后,IP1直接连接了IP3.这就是要解决的问题。
解决方案:
修改上一篇的软件代码如下:
client端代码:
import java.rmi.RemoteException; import java.util.Enumeration; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameClassPair; import javax.naming.NamingException; public class WarehouseClient { public static void main(String[] args) throws NamingException, RemoteException { Context namingContext = new InitialContext(); /* Enumeration<NameClassPair> e = namingContext.list("rmi://192.168.243.109:8080/"); while (e.hasMoreElements()) { System.out.println(e.nextElement().getName()); } */ // String url = "rmi://IP2:8080/central_warehouse"; Warehouse centralWarehouse = (Warehouse) namingContext.lookup(url); if(null==centralWarehouse){ System.out.println("fail to find remote RMI server..."); return; } String descr = "Blackwell Toaster"; double price = centralWarehouse.getPrice(descr); System.out.println(descr + ": " + price); } }
服务器端代码为:
import java.rmi.registry.LocateRegistry; import java.rmi.server.RMISocketFactory; import javax.naming.Context; import javax.naming.InitialContext; public class WarehouseServer { public static void main(String[] args) { //在服务器端的容器内注册此对象 //注意:当服务器有多个IP时,client连接时会有问题。典型的服务器有多个 ip 引起的 rmi 连接问题。 //参考:http://blog.csdn.net/model_cz/article/details/6525029 // try { //指定服务器的设置 System.setProperty("java.rmi.server.hostname" , "IP2" ); //System.setProperty("java.rmi.server.hostname" , "1.2.3.4" ); LocateRegistry.createRegistry(8080); //指定数据连接端口 RMISocketFactory.setSocketFactory(new WareFactory()); WarehoseImpl centralWarehouse = new WarehoseImpl(); Context namingContext = new InitialContext(); namingContext.bind("rmi://IP3:8080/central_warehouse", centralWarehouse); } catch (Exception e) { System.out.println(e.toString()); return; } System.out.println("waiting for the client to connect..."); } }
解决思路:
参考了http://blog.csdn.net/multiarrow/article/details/8551298
to("---------------------------------");下面的代码可以获取这个远程引用的IP和端口,不够目前我们用不上这些代码 /* RemoteObjectInvocationHandler roih = (RemoteObjectInvocationHandler)Proxy.getInvocationHandler(centralWarehouse); o(roih.getRef().remoteToString()); sun.rmi.server.UnicastRef ref = (sun.rmi.server.UnicastRef)roih.getRef(); LiveRef liveRef = ref.getLiveRef(); Channel c = liveRef.getChannel(); if(c instanceof TCPChannel ){ o("big congratulations..."); TCPChannel tc =(TCPChannel)c; }else{ o(c.toString()); } //o("endpoint "+(liveRef.getChannel().getEndpoint()); o("port "+liveRef.getPort()); */ o("---------------------------------");
这样就获取了[IP:Port],其实我们要的就是Port.
由于控制链已经基于负载均衡,
所以数据链通过HAProxy直连这个[IP:Port],的话,
也是某种程度的负载均衡,不是吗?
-----------
背景知识:
控制链是短连接。
数据链是长连接。
PS:看过HADOO的RPC框架源码再理解RMI毫无压力。
==================================
然后,问题就转化为HAProxy的源码修改了。
需要增加的功能:
client--->HAProxy--->RMI SERVER
本质上就是在HAProxy源码中添加对RMI协议的分析。
所以,第一个问题是去找到RMI的RFC文档学习RMI协议。
发现找了半天没找到JRMP协议的RFC文档,于是我放弃了解析协议。
转而考虑依靠IP和端口提供线索。
经过抓包分析RMI的工作流后,我最终给出的思路如下:
一个HAProxy代理后面配置若干RMI服务器,大致的配置就是如下:
========================================
IP1--->IP2------>IP3
|_____>IP4
IP1是RMI_client. IP2是HAProxy, IP3&IP4都是RMI_Server.
(所以需要保证ip3的控制端口80,数据端口为81,服务器代码返回的RMI对象的ip:port为 IP2 : 81)
(所以需要保证ip4的控制端口80,数据端口为82,服务器代码返回的RMI对象的ip:port为 IP2 : 82)
那么配置就是:
HAProxy:
listen 0.0.0.0:80
server ip3 ip3:80
server ip4 ip4:80
这样的话,控制端仍然保持负载均衡。
剩下的关键在于数据端口
Haproxy:
listen 0.0.0.0:81
server ip3 ip3:81
listen 0.0.0.0:82
server ip4 ip4:82
这样就解决问题了。
不过haproxy的IP就暴露给了rmi server.
个人认为这是性价比最高的一种方案。
----------------------------------------
方案2是为技术狂准备的。
每个RMI服务器的数据端口都一样,
控制连的数据为RMI协议,修改haproxy源码做RMI协议(JRMP?)应用层协议分析。
提取(有用的信息比如objectid等 , ip)映射。
这个映射表可根据需要长期存在或者动态删除。
然后数据端建立连接时解析RMI协议的objectid,查找哈希表映射的IP,连接。
这个理论上很完美,但是因为要
1RMI应用层协议分析
2 修改haproxy源码。
所以性价比略低于方案1,而且在多台haproxy时无法共享哈希表映射关系(提取出来放在公共的缓存服务器?)。
------
从维护和劳动工作量的角度,个人偏向方案1,需改配置和服务器的数据端口即可。