项目中使用xmemcached-1.2.5找到该客户端的小小缺陷

首先说说起因,源于网上搜索高性能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了。
项目中使用xmemcached-1.2.5找到该客户端的小小缺陷
下一步是进行破坏性测试
选择: 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的设计不是特别清楚。也不好做修正。望作者能出来指正。

你可能感兴趣的:(thread,memcached)