首先说说起因,源于网上搜索高性能memcached客户端,找到了xmemcached.
在使用过程中想用用它提供的JMX的功能。
看过源码之后发现没有提供HTML的展现修改形式。
于是动了一点小手术:
类XMemcachedMbeanServer.java的initialize方法原来如下:
private void initialize() {
if (this.mbserver != null && this.connectorServer != null && this.connectorServer.isActive()) {
return;
}
// 鍒涘缓MBServer
String hostName = null;
try {
InetAddress addr = InetAddress.getLocalHost();
hostName = addr.getHostName();
} catch (IOException e) {
log.error("Get HostName Error", e);
hostName = "localhost";
}
String host = System.getProperty("hostName", hostName);
try {
boolean enableJMX = Boolean.parseBoolean(System.getProperty(Constants.XMEMCACHED_JMX_ENABLE, "false"));
if (enableJMX) {
this.mbserver = ManagementFactory.getPlatformMBeanServer();
int port = Integer.parseInt(System.getProperty(Constants.XMEMCACHED_RMI_PORT, "7077"));
String rmiName = System.getProperty(Constants.XMEMCACHED_RMI_NAME, "xmemcachedServer");
Registry registry = null;
try {
registry = LocateRegistry.getRegistry(port);
registry.list();
} catch (Exception e) {
registry = null;
}
if (null == registry) {
registry = LocateRegistry.createRegistry(port);
}
registry.list();
String serverURL = "service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/" + rmiName;
JMXServiceURL url = new JMXServiceURL(serverURL);
this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, this.mbserver);
this.connectorServer.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
if (XMemcachedMbeanServer.this.connectorServer.isActive()) {
XMemcachedMbeanServer.this.connectorServer.stop();
log.warn("JMXConnector stop");
}
} catch (IOException e) {
log.error("Shutdown Xmemcached MBean server error", e);
}
}
});
log.warn("jmx url: " + serverURL);
}
} catch (Exception e) {
log.error("create MBServer error", e);
}
}
修改如下:
private void initialize() {
if (this.mbserver != null && this.connectorServer != null && this.connectorServer.isActive()) {
return;
}
// 鍒涘缓MBServer
String hostName = null;
try {
InetAddress addr = InetAddress.getLocalHost();
hostName = addr.getHostName();
} catch (IOException e) {
log.error("Get HostName Error", e);
hostName = "localhost";
}
String host = System.getProperty("hostName", hostName);
try {
boolean enableJMX = Boolean.parseBoolean(System.getProperty(Constants.XMEMCACHED_JMX_ENABLE, "false"));
if (enableJMX) {
this.mbserver = MBeanServerFactory.createMBeanServer();
ObjectName adapterName = new ObjectName("XMemcachedMbeanServer:name=htmladapter,port=7077");
HtmlAdaptorServer htmlServer = new HtmlAdaptorServer();
this.mbserver.registerMBean(htmlServer, adapterName);
htmlServer.setPort(7077);
htmlServer.start();
}
} catch (Exception e) {
log.error("create MBServer error", e);
}
}
这样服务器启动我就可以通过http://localhost:7077访问JMX了。
下一步是进行破坏性测试
:
选择:
type=XMemcachedClient这里进去,里面是xmemcached提供的MBean接口调用。
注意其中的addServer(String list) p1和removeServer(String list) p1这两个方法,我所说的bug就是从这两个方法引发滴。
首先我调用addServer方法,传入一个非正常的memcached服务器地址,比如我传入了"localhost:11213"(这个memcached服务不存在),god,按预期控制台出现了不断地错误打印,当然是提示找不到这个服务。然后就一直无限地循环,
控制台被打爆了。于是我又想remove一下吧那就。恩,调用了一下removeServer("localhost:11213")。
恩,是这样。bug出现了,作者的源代码如下:
public final void removeServer(String hostList) {
List<InetSocketAddress> addresses = AddrUtil.getAddresses(hostList);
if (addresses != null && addresses.size() > 0) {
for (InetSocketAddress address : addresses) {
Queue<Session> sessionQueue = this.connector.getSessionByAddress(address);
for (Session session : sessionQueue) {
if (session != null) {
// Disable auto reconnection
((MemcachedSession) session).setAllowReconnect(false);
// Close connection
session.close();
}
}
this.connector.removeReconnectRequest(address);
}
}
}
this.connector.getSessionByAddress(address)这句取回来的sessionQueue是为null的,作者没有进行判断,引发了空指针,所以我的removeServer尝试也失败了,服务端果断滴抛出了异常。
恩,修改一下呗:
public final void removeServer(String hostList) {
List<InetSocketAddress> addresses = AddrUtil.getAddresses(hostList);
if (addresses != null && addresses.size() > 0) {
for (InetSocketAddress address : addresses) {
Queue<Session> sessionQueue = this.connector.getSessionByAddress(address);
if(sessionQueue != null){
for (Session session : sessionQueue) {
if (session != null) {
// Disable auto reconnection
((MemcachedSession) session).setAllowReconnect(false);
// Close connection
session.close();
}
}
}
this.connector.removeReconnectRequest(address);
}
}
}
恩。这样可以了,不报错了。
可是奇怪的现象又发生了。异常JMX不报错了,但是控制台还是在报:
java.io.IOException: Connect to localhost:11213 fail,Connection refused: no further information,removeServer不是this.connector.removeReconnectRequest(address)不是在这句进行了reconnectRequest的移除工作了么。
继续看源代码后发现:
public void removeReconnectRequest(InetSocketAddress inetSocketAddress) {
Iterator<ReconnectRequest> it = this.waitingQueue.iterator();
while (it.hasNext()) {
ReconnectRequest request = it.next();
if (request.getInetSocketAddressWrapper().getInetSocketAddress().equals(inetSocketAddress)) {
it.remove();
}
}
}
发现调用了迭代器进行操作。读到这里,我突然感觉到了存在一个缺陷,这个缺陷是怎么样的呢?
首先MemcachedConnector这个类本身创建了一个线程不断地从waitingQueue中取出重连请求进行重试。
那如果我刚好把需要删除的重连请求给取出进行重连,而迭代器此时锁定waitingQueue进行迭代发现没有这个重连请求,然后当这个迭代完成之时,刚才取出的重连请求发生错误以后又将该请求放回了waitingQueue,所以就出现了我的这个现象,调用了removeServer后没有起到作用(就好比你要去阿B家里暗杀他,而他却不在家,你当然是暗杀不成功,而当你离开的时候,他又回来了。这就相当于是暗杀失败)
所以有很大人机率会造成removeServer调用失败。
当然我对这个xmemcached的设计不是特别清楚。也不好做修正。望作者能出来指正。