项目背景请参考我的上一篇文章: http://my.oschina.net/qiangzigege/blog/495910
下面详细讲解如何修改JVM源码解决RMI的有状态化问题。
从JVM源码可以看到,client通过控制链得到对象ID后,走数据链发送到RMI Server,Server的查找过程如下:
target = ObjectTable.getTarget(new ObjectEndpoint(id, transport));
那么getTarget函数执行了啥?
/**
* Returns the target associated with the object id.
*/
static Target getTarget(ObjectEndpoint oe) {
synchronized (tableLock) {
return objTable.get(oe);
}
}
其中objTable是
public static final Map<ObjectEndpoint,Target> objTable =
new HashMap<ObjectEndpoint,Target>();
--------
而ObjectEndpoint的equals函数定义如下:
public boolean equals(Object obj) {
if (obj instanceof ObjectEndpoint) {
ObjectEndpoint oe = (ObjectEndpoint) obj;
return id.equals(oe.id) && transport == oe.transport;//行4
} else {
return false;
}
}
这里重点关注 行4的内容。先看前半部分
id.equals(oe.id)代码为
public boolean equals(Object obj) {
if (obj instanceof ObjID) {
ObjID id = (ObjID) obj;
return objNum == id.objNum && space.equals(id.space);
} else {
return false;
}
}
而space.equals(id.space)的代码为
public boolean equals(Object obj) {
if (obj instanceof UID) {
UID uid = (UID) obj;
return (unique == uid.unique &&
count == uid.count &&
time == uid.time);
} else {
return false;
}
}
所以结论就是:id.equals(oe.id)只需要相关的4个值(objNum unique count time)相等就行了。
----------------------------------------
再看 transport == oe.transport;//行4
先看这2行
Transport transport = id.equals(dgcID) ? null : this;//dgcID代表【0:0:0 2】
target = ObjectTable.getTarget(new ObjectEndpoint(id, transport));
对于业务来说,判断条件结果为false,所以结果为this.
然后这里根本不用考虑。只要保证对象绑定的port一致就可以了。
------------------------所以问题就很简单了,只要关注4个值(objNum unique count time)就行了。
而且网络中传递的也是这4个ID.
下面看看4个ID生成的规则。
/**
* Construct a new live reference for a server object in the local
* address space.
*/
public LiveRef(int port) {
this((new ObjID()), port);
}
这里是new ObjID(),所以需要去看看new ObjID())生成的规则。
/**
* Generates a unique object identifier.
*
* <p>If the system property <code>java.rmi.server.randomIDs</code>
* is defined to equal the string <code>"true"</code> (case insensitive),
* then this constructor will use a cryptographically
* strong random number generator to choose the object number of the
* returned <code>ObjID</code>.
*/
public ObjID() {
/*
* If generating random object numbers, create a new UID to
* ensure uniqueness; otherwise, use a shared UID because
* sequential object numbers already ensure uniqueness.
*/
if (useRandomIDs()) {
space = new UID();
objNum = secureRandom.nextLong();
} else {
space = mySpace;
objNum = nextObjNum.getAndIncrement();
}
}
可见这里根据是否使用随机ID来生成ID.useRandomIDs的函数定义如下:
private static boolean useRandomIDs() {
String value = AccessController.doPrivileged(
new GetPropertyAction("java.rmi.server.randomIDs"));
return value == null ? true : Boolean.parseBoolean(value);
}
然后我添加了调试信息
if (useRandomIDs()) {
System.out.println("use random id yes");
space = new UID();
objNum = secureRandom.nextLong();
} else {
System.out.println("use random id no");
space = mySpace;
objNum = nextObjNum.getAndIncrement();
}
System.out.println("id---"+ space +" "+objNum);
}
的打印结果为:
可见,默认情况下,虚拟机采用了随机ID.
--------------------------------------那如果不采用随机规则呢?
-Djava.rmi.server.randomIDs=false
测试2个对象的生成规则为
use random id no
id----445c7b23:14cb0a97861:-8000 0
use random id no
id----445c7b23:14cb0a97861:-8000 1
可以看到objNum是按照规则生成的,0,1,2,3,4, 但是space还是没有规律。
但是上面2个对象的space是一样的,因为
space = mySpace; 而mySpace是private static final UID mySpace = new UID();
是一个全局静态final 对象。
所以我们要保证new UID()的时候是一致的。
--------------------------------------------------
然后发现了一个奇怪的现象
就是当
-Djava.rmi.server.randomIDs=false时
感觉事情貌似突然变简单了。
难道-Djava.rmi.server.randomIDs=false就可以解决问题了?
----------------------------
想了半天,终于找到最终解决方案了,只需要修改3个地方
修改1:[java.rmi.server.ObjID]
public boolean equals(Object obj) {
if (obj instanceof ObjID) {
ObjID id = (ObjID) obj;
return objNum == id.objNum && space.equals(id.space);
} else {
return false;
}
}
修改成
public static String newRmi = System.getProperty("java.rmi.server.randomIDs");
public boolean equals(Object obj) {
if (obj instanceof ObjID) {
ObjID id = (ObjID) obj;
String localSpace = space.toString();
String objSpace = id.space.toString();
if (null != newRmi && newRmi.equals("false")) {
if (localSpace.startsWith("0:0:0") || objSpace.startsWith("0:0:0")) {
return objNum == id.objNum && space.equals(id.space);
} else {
return objNum == id.objNum;
}
} else {
return objNum == id.objNum && space.equals(id.space);
}
} else {
return false;
}
}
---
修改2:加上运行参数
-Djava.rmi.server.randomIDs=false
修改3:为了防止对象被回收,修改UnicastRemoteObject的
[java.rmi.server.UnicastRemoteObject]
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
修改为
public static String newRmi = System.getProperty("java.rmi.server.randomIDs");
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null,
( null!= newRmi && newRmi.equals(“false”) )?true:false);
}
修改4:
为了防止不存在前台reaper线程存在,导致VM退出(不依赖于任何其它业务),需要自启动一个前台线程
方法如下:
修改UnicastRemoteObject类[java.rmi.server.UnicastRemoteObject]
public static String newRmi = System.getProperty("java.rmi.server.randomIDs");
public static Thread selfCreateThead = null;
static{
if (null!= newRmi && newRmi.equals(“false”) && null == selfCreateThead) {
selfCreateThead = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// must always cycle
while (true) {
try {
Thread.currentThread().sleep(1 * 60 * 1000);// 1
// minutes
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
selfCreateThead.start();
}
}
修改5:[暂时丢弃]
[sun.rmi.transport.ObjectEndpoint]
{注意:这个类非java官方开源,所以一定要将所在linux机器的class文件反编译后的java文件
与
RPC技术文件夹下面的zip文件里的java文件
做一个仔细的比较,看是否有差异...}
public boolean equals(Object obj) { if (obj instanceof ObjectEndpoint) { ObjectEndpoint oe = (ObjectEndpoint) obj; return id.equals(oe.id) && transport == oe.transport; } else { return false; } }
/** * Returns the hash code value for this object endpoint. */ public int hashCode() { return id.hashCode() ^ (transport != null ? transport.hashCode() : 0); } |
修改为
public static String newRmi = System.getProperty("java.rmi.server.randomIDs");
public boolean equals(Object obj) { if (obj instanceof ObjectEndpoint) { ObjectEndpoint oe = (ObjectEndpoint) obj; if (null != newRmi && newRmi.equals("false")) { return id.equals(oe.id); } else { return id.equals(oe.id) && transport == oe.transport; } } else { return false; } }
/** * Returns the hash code value for this object endpoint. */ public int hashCode() { if (null != newRmi && newRmi.equals("false")) { return id.hashCode(); } else { return id.hashCode() ^ (transport != null ? transport.hashCode() : 0); } } |
比较复杂的是对象的回收机制,此方案是否实际可行,需要经过测试的验证。
目前已经在2个产品中上线,运维人员表示未发现问题。
觉得好的请点个赞,做个技术人员也是不容易滴 :)
Email: [email protected]