All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.rubyeye.xmemcached.impl.MemcachedConnector Maven / Gradle / Ivy

There is a newer version: 2.4.8
Show newest version
/**
 *Copyright [2009-2010] [dennis zhuang([email protected])]
 *Licensed under the Apache License, Version 2.0 (the "License");
 *you may not use this file except in compliance with the License.
 *You may obtain a copy of the License at
 *             http://www.apache.org/licenses/LICENSE-2.0
 *Unless required by applicable law or agreed to in writing,
 *software distributed under the License is distributed on an "AS IS" BASIS,
 *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 *either express or implied. See the License for the specific language governing permissions and limitations under the License
 */
package net.rubyeye.xmemcached.impl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import net.rubyeye.xmemcached.CommandFactory;
import net.rubyeye.xmemcached.FlowControl;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedOptimizer;
import net.rubyeye.xmemcached.MemcachedSessionLocator;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.command.Command;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.networking.Connector;
import net.rubyeye.xmemcached.networking.MemcachedSession;
import net.rubyeye.xmemcached.utils.InetSocketAddressWrapper;
import net.rubyeye.xmemcached.utils.Protocol;

import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.Controller;
import com.google.code.yanf4j.core.ControllerStateListener;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.nio.NioSession;
import com.google.code.yanf4j.nio.NioSessionConfig;
import com.google.code.yanf4j.nio.impl.SocketChannelController;
import com.google.code.yanf4j.util.ConcurrentHashSet;
import com.google.code.yanf4j.util.SystemUtils;

/**
 * Connected session manager
 * 
 * @author dennis
 */
public class MemcachedConnector extends SocketChannelController implements
		Connector {
	private final DelayQueue waitingQueue = new DelayQueue();
	private BufferAllocator bufferAllocator;

	private final Set removedAddrSet = new ConcurrentHashSet();

	private final MemcachedOptimizer optimiezer;
	private long healSessionInterval = MemcachedClient.DEFAULT_HEAL_SESSION_INTERVAL;
	private int connectionPoolSize; // session pool size
	protected Protocol protocol;
	private boolean enableHealSession = true;
	private final CommandFactory commandFactory;
	private boolean failureMode;

	private final ConcurrentHashMap/*
																						 * standby
																						 * sessions
																						 */> standbySessionMap = new ConcurrentHashMap>();

	private final FlowControl flowControl;

	private volatile boolean shuttingDown = false;

	public void shuttingDown() {
		this.shuttingDown = true;
	}

	public void setSessionLocator(MemcachedSessionLocator sessionLocator) {
		this.sessionLocator = sessionLocator;
	}

	/**
	 * Session monitor for healing sessions.
	 * 
	 * @author dennis
	 * 
	 */
	class SessionMonitor extends Thread {
		public SessionMonitor() {
			this.setName("Heal-Session-Thread");
		}

		@Override
		public void run() {
			while (MemcachedConnector.this.isStarted()
					&& MemcachedConnector.this.enableHealSession) {
				ReconnectRequest request = null;
				try {
					request = MemcachedConnector.this.waitingQueue.take();

					InetSocketAddress address = request
							.getInetSocketAddressWrapper()
							.getInetSocketAddress();

					if (!MemcachedConnector.this.removedAddrSet
							.contains(address)) {
						boolean connected = false;
						Future future = MemcachedConnector.this
								.connect(request.getInetSocketAddressWrapper());
						request.setTries(request.getTries() + 1);
						try {
							log.info("Trying to connect to "
									+ address.getAddress().getHostAddress()
									+ ":" + address.getPort() + " for "
									+ request.getTries() + " times");
							if (!future.get(
									MemcachedClient.DEFAULT_CONNECT_TIMEOUT,
									TimeUnit.MILLISECONDS)) {
								connected = false;
							} else {
								connected = true;
							}
						} catch (TimeoutException e) {
							future.cancel(true);
						} catch (ExecutionException e) {
							future.cancel(true);
						} finally {
							if (!connected) {
								this.rescheduleConnectRequest(request);
							} else {
								continue;
							}
						}
					} else {
						log.info("Remove invalid reconnect task for " + address);
						// remove reconnect task
					}
				} catch (InterruptedException e) {
					// ignore,check status
				} catch (Exception e) {
					log.error("SessionMonitor connect error", e);
					this.rescheduleConnectRequest(request);
				}
			}
		}

		private void rescheduleConnectRequest(ReconnectRequest request) {
			if (request == null) {
				return;
			}
			InetSocketAddress address = request.getInetSocketAddressWrapper()
					.getInetSocketAddress();
			// update timestamp for next reconnecting
			request.updateNextReconnectTimeStamp(MemcachedConnector.this.healSessionInterval
					* request.getTries());
			log.error("Reconnect to " + address.getAddress().getHostAddress()
					+ ":" + address.getPort() + " fail");
			// add to tail
			MemcachedConnector.this.waitingQueue.offer(request);
		}
	}

	public void setEnableHealSession(boolean enableHealSession) {
		this.enableHealSession = enableHealSession;
		// wake up session monitor thread.
		if (this.sessionMonitor != null && this.sessionMonitor.isAlive()) {
			this.sessionMonitor.interrupt();
		}
	}

	public Queue getReconnectRequestQueue() {
		return this.waitingQueue;
	}

	@Override
	public Set getSessionSet() {
		Collection> sessionQueues = this.sessionMap.values();
		Set result = new HashSet();
		for (Queue queue : sessionQueues) {
			result.addAll(queue);
		}
		return result;
	}

	public final void setHealSessionInterval(long healConnectionInterval) {
		this.healSessionInterval = healConnectionInterval;
	}

	public long getHealSessionInterval() {
		return this.healSessionInterval;
	}

	public void setOptimizeGet(boolean optimiezeGet) {
		((OptimizerMBean) this.optimiezer).setOptimizeGet(optimiezeGet);
	}

	public void setOptimizeMergeBuffer(boolean optimizeMergeBuffer) {
		((OptimizerMBean) this.optimiezer)
				.setOptimizeMergeBuffer(optimizeMergeBuffer);
	}

	public Protocol getProtocol() {
		return this.protocol;
	}

	protected MemcachedSessionLocator sessionLocator;

	protected final ConcurrentHashMap> sessionMap = new ConcurrentHashMap>();

	public synchronized void addSession(Session session) {
		MemcachedSession tcpSession = (MemcachedSession) session;

		InetSocketAddressWrapper addrWrapper = tcpSession
				.getInetSocketAddressWrapper();
		// Remember the first time address resolved and use it in all
		// application lifecycle.
		if (addrWrapper.getRemoteAddressStr() == null) {
			addrWrapper.setRemoteAddressStr(String.valueOf(session
					.getRemoteSocketAddress()));
		}

		InetSocketAddress mainNodeAddress = addrWrapper.getMainNodeAddress();
		if (mainNodeAddress != null) {
			// It is a standby session
			this.addStandbySession(session, mainNodeAddress);
		} else {
			// It is a main session
			this.addMainSession(session);
			// Update main sessions
			this.updateSessions();
		}
	}

	private void addMainSession(Session session) {
		InetSocketAddress remoteSocketAddress = session
				.getRemoteSocketAddress();
		log.info("Add a session: "
				+ SystemUtils.getRawAddress(remoteSocketAddress) + ":"
				+ remoteSocketAddress.getPort());
		Queue sessions = this.sessionMap.get(remoteSocketAddress);
		if (sessions == null) {
			sessions = new ConcurrentLinkedQueue();
			Queue oldSessions = this.sessionMap.putIfAbsent(
					remoteSocketAddress, sessions);
			if (null != oldSessions) {
				sessions = oldSessions;
			}
		}
		// If it is in failure mode,remove closed session from list
		if (this.failureMode) {
			Iterator it = sessions.iterator();
			while (it.hasNext()) {
				Session tmp = it.next();
				if (tmp.isClosed()) {
					it.remove();
					break;
				}
			}
		}

		sessions.offer(session);
		// Remove old session and close it
		while (sessions.size() > this.connectionPoolSize) {
			Session oldSession = sessions.poll();
			((MemcachedSession) oldSession).setAllowReconnect(false);
			oldSession.close();
		}
	}

	private void addStandbySession(Session session,
			InetSocketAddress mainNodeAddress) {
		InetSocketAddress remoteSocketAddress = session
				.getRemoteSocketAddress();
		log.info("Add a standby session: "
				+ SystemUtils.getRawAddress(remoteSocketAddress) + ":"
				+ remoteSocketAddress.getPort() + " for "
				+ SystemUtils.getRawAddress(mainNodeAddress) + ":"
				+ mainNodeAddress.getPort());
		List sessions = this.standbySessionMap.get(mainNodeAddress);
		if (sessions == null) {
			sessions = new CopyOnWriteArrayList();
			List oldSessions = this.standbySessionMap.putIfAbsent(
					mainNodeAddress, sessions);
			if (null != oldSessions) {
				sessions = oldSessions;
			}
		}
		sessions.add(session);
	}

	public List getSessionListBySocketAddress(
			InetSocketAddress inetSocketAddress) {
		Queue queue = this.sessionMap.get(inetSocketAddress);
		if (queue != null) {
			return new ArrayList(queue);
		} else {
			return null;
		}
	}

	public void removeReconnectRequest(InetSocketAddress inetSocketAddress) {
		this.removedAddrSet.add(inetSocketAddress);
		Iterator it = this.waitingQueue.iterator();
		while (it.hasNext()) {
			ReconnectRequest request = it.next();
			if (request.getInetSocketAddressWrapper().getInetSocketAddress()
					.equals(inetSocketAddress)) {
				it.remove();
				log.warn("Remove invalid reconnect task for "
						+ request.getInetSocketAddressWrapper()
								.getInetSocketAddress());
			}
		}
	}

	private static final MemcachedSessionComparator sessionComparator = new MemcachedSessionComparator();

	public final void updateSessions() {
		Collection> sessionCollection = this.sessionMap.values();
		List sessionList = new ArrayList(20);
		for (Queue sessions : sessionCollection) {
			sessionList.addAll(sessions);
		}
		// sort the sessions to keep order
		Collections.sort(sessionList, sessionComparator);
		this.sessionLocator.updateSessions(sessionList);
	}

	public synchronized void removeSession(Session session) {
		MemcachedTCPSession tcpSession = (MemcachedTCPSession) session;
		InetSocketAddressWrapper addrWrapper = tcpSession
				.getInetSocketAddressWrapper();
		InetSocketAddress mainNodeAddr = addrWrapper.getMainNodeAddress();
		if (mainNodeAddr != null) {
			this.removeStandbySession(session, mainNodeAddr);
		} else {
			this.removeMainSession(session);
		}
	}

	private void removeMainSession(Session session) {
		InetSocketAddress remoteSocketAddress = session
				.getRemoteSocketAddress();
		// If it was in failure mode,we don't remove closed session from list.
		if (this.failureMode && ((MemcachedSession) session).isAllowReconnect()
				&& !this.shuttingDown && this.isStarted()) {
			log.warn("Client in failure mode,we don't remove session "
					+ SystemUtils.getRawAddress(remoteSocketAddress) + ":"
					+ remoteSocketAddress.getPort());
			return;
		}
		log.info("Remove a session: "
				+ SystemUtils.getRawAddress(remoteSocketAddress) + ":"
				+ remoteSocketAddress.getPort());
		Queue sessionQueue = this.sessionMap.get(session
				.getRemoteSocketAddress());
		if (null != sessionQueue) {
			sessionQueue.remove(session);
			if (sessionQueue.size() == 0) {
				this.sessionMap.remove(session.getRemoteSocketAddress());
			}
			this.updateSessions();
		}
	}

	private void removeStandbySession(Session session,
			InetSocketAddress mainNodeAddr) {
		List sessionList = this.standbySessionMap.get(mainNodeAddr);
		if (null != sessionList) {
			sessionList.remove(session);
			if (sessionList.size() == 0) {
				this.standbySessionMap.remove(mainNodeAddr);
			}
		}
	}

	@Override
	protected void doStart() throws IOException {
		this.setLocalSocketAddress(new InetSocketAddress("localhost", 0));
	}

	@Override
	public void onConnect(SelectionKey key) throws IOException {
		key.interestOps(key.interestOps() & ~SelectionKey.OP_CONNECT);
		ConnectFuture future = (ConnectFuture) key.attachment();
		if (future == null || future.isCancelled()) {
			this.cancelKey(key);
			return;
		}
		try {
			if (!((SocketChannel) key.channel()).finishConnect()) {
				this.cancelKey(key);
				future.failure(new IOException("Connect to "
						+ SystemUtils.getRawAddress(future
								.getInetSocketAddressWrapper()
								.getInetSocketAddress())
						+ ":"
						+ future.getInetSocketAddressWrapper()
								.getInetSocketAddress().getPort() + " fail"));
			} else {
				key.attach(null);
				this.addSession(this.createSession(
						(SocketChannel) key.channel(),
						future.getInetSocketAddressWrapper()));
				future.setResult(Boolean.TRUE);
			}
		} catch (Exception e) {
			future.failure(e);
			this.cancelKey(key);
			throw new IOException("Connect to "
					+ SystemUtils.getRawAddress(future
							.getInetSocketAddressWrapper()
							.getInetSocketAddress())
					+ ":"
					+ future.getInetSocketAddressWrapper()
							.getInetSocketAddress().getPort() + " fail,"
					+ e.getMessage());
		}
	}

	private void cancelKey(SelectionKey key) throws IOException {
		try {
			if (key.channel() != null) {
				key.channel().close();
			}
		} finally {
			key.cancel();
		}
	}

	protected MemcachedTCPSession createSession(SocketChannel socketChannel,
			InetSocketAddressWrapper wrapper) {
		MemcachedTCPSession session = (MemcachedTCPSession) this
				.buildSession(socketChannel);
		session.setInetSocketAddressWrapper(wrapper);
		this.selectorManager.registerSession(session, EventType.ENABLE_READ);
		session.start();
		session.onEvent(EventType.CONNECTED, null);
		return session;
	}

	public void addToWatingQueue(ReconnectRequest request) {
		this.waitingQueue.add(request);
	}

	public Future connect(InetSocketAddressWrapper addressWrapper)
			throws IOException {
		if (addressWrapper == null) {
			throw new NullPointerException("Null Address");
		}
		// Remove addr from removed set
		this.removedAddrSet.remove(addressWrapper.getInetSocketAddress());
		SocketChannel socketChannel = null;
		try {
			socketChannel = SocketChannel.open();
			this.configureSocketChannel(socketChannel);
			ConnectFuture future = new ConnectFuture(addressWrapper);
			if (!socketChannel.connect(addressWrapper.getInetSocketAddress())) {
				this.selectorManager.registerChannel(socketChannel,
						SelectionKey.OP_CONNECT, future);
			} else {
				this.addSession(this.createSession(socketChannel,
						addressWrapper));
				future.setResult(true);
			}
			return future;
		} catch (IOException e) {
			if (socketChannel != null) {
				socketChannel.close();
			}
			throw e;
		}
	}

	public void closeChannel(Selector selector) throws IOException {
		// do nothing
	}

	private final Random random = new Random();

	public Session send(final Command msg) throws MemcachedException {
		MemcachedSession session = (MemcachedSession) this.findSessionByKey(msg
				.getKey());
		if (session == null) {
			throw new MemcachedException(
					"There is no available connection at this moment");
		}
		// If session was closed,try to use standby memcached node
		if (session.isClosed()) {
			session = this.findStandbySession(session);
		}
		if (session.isClosed()) {
			throw new MemcachedException("Session("
					+ SystemUtils.getRawAddress(session
							.getRemoteSocketAddress()) + ":"
					+ session.getRemoteSocketAddress().getPort()
					+ ") has been closed");
		}
		if (session.isAuthFailed()) {
			throw new MemcachedException("Auth failed to connection "
					+ session.getRemoteSocketAddress());
		}
		session.write(msg);
		return session;
	}

	private MemcachedSession findStandbySession(MemcachedSession session) {
		if (this.failureMode) {
			List sessionList = this
					.getStandbySessionListByMainNodeAddr(session
							.getRemoteSocketAddress());
			if (sessionList != null && !sessionList.isEmpty()) {
				return (MemcachedTCPSession) sessionList.get(this.random
						.nextInt(sessionList.size()));
			}
		}
		return session;
	}

	/**
	 * Returns main node's standby session list.
	 * 
	 * @param addr
	 * @return
	 */
	public List getStandbySessionListByMainNodeAddr(
			InetSocketAddress addr) {
		return this.standbySessionMap.get(addr);
	}

	private final SessionMonitor sessionMonitor = new SessionMonitor();

	/**
	 * Inner state listenner,manage session monitor.
	 * 
	 * @author boyan
	 * 
	 */
	class InnerControllerStateListener implements ControllerStateListener {

		public void onAllSessionClosed(Controller controller) {

		}

		public void onException(Controller controller, Throwable t) {
			log.error("Exception occured in controller", t);
		}

		public void onReady(Controller controller) {
			MemcachedConnector.this.sessionMonitor.setDaemon(true);
			MemcachedConnector.this.sessionMonitor.start();
		}

		public void onStarted(Controller controller) {

		}

		public void onStopped(Controller controller) {
			if (MemcachedConnector.this.sessionMonitor.isAlive()) {
				MemcachedConnector.this.sessionMonitor.interrupt();
			}
		}

	}

	public final Session findSessionByKey(String key) {
		return this.sessionLocator.getSessionByKey(key);
	}

	/**
	 * Get session by InetSocketAddress
	 * 
	 * @param addr
	 * @return
	 */
	public final Queue getSessionByAddress(InetSocketAddress addr) {
		return this.sessionMap.get(addr);
	}

	public MemcachedConnector(Configuration configuration,
			MemcachedSessionLocator locator, BufferAllocator allocator,
			CommandFactory commandFactory, int poolSize,
			int maxQueuedNoReplyOperations) {
		super(configuration, null);
		this.sessionLocator = locator;
		this.protocol = commandFactory.getProtocol();
		this.addStateListener(new InnerControllerStateListener());
		this.updateSessions();
		this.bufferAllocator = allocator;
		this.optimiezer = new Optimizer(this.protocol);
		this.optimiezer.setBufferAllocator(this.bufferAllocator);
		this.connectionPoolSize = poolSize;
		this.soLingerOn = true;
		this.commandFactory = commandFactory;
		this.flowControl = new FlowControl(maxQueuedNoReplyOperations);
		this.setSelectorPoolSize(configuration.getSelectorPoolSize());
		// setDispatchMessageThreadPoolSize(Runtime.getRuntime().
		// availableProcessors());
	}

	public final void setConnectionPoolSize(int poolSize) {
		this.connectionPoolSize = poolSize;
	}

	public void setMergeFactor(int mergeFactor) {
		((OptimizerMBean) this.optimiezer).setMergeFactor(mergeFactor);
	}

	public FlowControl getNoReplyOpsFlowControl() {
		return this.flowControl;
	}

	@Override
	protected NioSession buildSession(SocketChannel sc) {
		Queue queue = this.buildQueue();
		final NioSessionConfig sessionCofig = this
				.buildSessionConfig(sc, queue);
		MemcachedTCPSession session = new MemcachedTCPSession(sessionCofig,
				this.configuration.getSessionReadBufferSize(), this.optimiezer,
				this.getReadThreadCount(), this.commandFactory);
		session.setBufferAllocator(this.bufferAllocator);
		return session;
	}

	/**
	 * Build write queue for session
	 * 
	 * @return
	 */
	@Override
	protected Queue buildQueue() {
		return new FlowControlLinkedTransferQueue(this.flowControl);
	}

	public BufferAllocator getBufferAllocator() {
		return this.bufferAllocator;
	}

	public synchronized void quitAllSessions() {
		for (Session session : this.sessionSet) {
			((MemcachedSession) session).quit();
		}
		int sleepCount = 0;
		while (sleepCount++ < 5 && this.sessionSet.size() > 0) {
			try {
				this.wait(1000);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}

	}

	public void setFailureMode(boolean failureMode) {
		this.failureMode = failureMode;
	}

	public void setBufferAllocator(BufferAllocator allocator) {
		this.bufferAllocator = allocator;
		for (Session session : this.getSessionSet()) {
			((MemcachedSession) session).setBufferAllocator(allocator);
		}
	}

	public Collection getServerAddresses() {
		return Collections.unmodifiableCollection(this.sessionMap.keySet());
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy