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.MemcachedSessionComparator;
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;
  }

  public void setSessionComparator(MemcachedSessionComparator sessionComparator) {
    this.sessionComparator = sessionComparator;
  }

  /**
   * 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("Reconnected to " + address + " 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 MemcachedSessionComparator sessionComparator;

  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,
          addrWrapper.getResolvedMainNodeSocketAddress(), addrWrapper);
    } else {
      // It is a main session
      this.addMainSession(session, addrWrapper.getResolvedSocketAddress(), addrWrapper);
      // Update main sessions
      this.updateSessions();
    }
  }

  private void addMainSession(Session session, InetSocketAddress lastReolvedAddr,
      InetSocketAddressWrapper addrWrapper) {
    InetSocketAddress remoteSocketAddress = session.getRemoteSocketAddress();
    log.info("Add a session: " + SystemUtils.getRawAddress(remoteSocketAddress) + ":"
        + remoteSocketAddress.getPort());
    if (lastReolvedAddr != null && !lastReolvedAddr.equals(remoteSocketAddress)) {
      log.warn("Memcached node {} is resolved into {}.", lastReolvedAddr, remoteSocketAddress);
      // Remove and closed old resolved address.
      Queue sessions = sessionMap.remove(lastReolvedAddr);
      if (sessions != null) {
        for (Session s : sessions) {
          ((MemcachedSession) s).setAllowReconnect(false);
          s.close();
        }
      }
      // updated resolve addr
      addrWrapper.setResolvedSocketAddress(remoteSocketAddress);
    }
    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 lastResolvedMainAddr, InetSocketAddressWrapper addrWrapper) {
    InetSocketAddress remoteSocketAddress = session.getRemoteSocketAddress();
    log.info("Add a standby session: " + SystemUtils.getRawAddress(remoteSocketAddress) + ":"
        + remoteSocketAddress.getPort() + " for " + SystemUtils.getRawAddress(mainNodeAddress) + ":"
        + mainNodeAddress.getPort());
    if (lastResolvedMainAddr != null && !lastResolvedMainAddr.equals(remoteSocketAddress)) {
      log.warn("Memcached node {} is resolved into {}.", lastResolvedMainAddr, remoteSocketAddress);
      // Remove and closed old resolved address.
      List sessions = standbySessionMap.remove(lastResolvedMainAddr);
      if (sessions != null) {
        for (Session s : sessions) {
          ((MemcachedSession) s).setAllowReconnect(false);
          s.close();
        }
      }
      addrWrapper.setResolvedMainNodeSocketAddress(remoteSocketAddress);
    }
    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());
      }
    }
  }

  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) {
    if (addressWrapper == null) {
      throw new NullPointerException("Null Address");
    }
    // Remove addr from removed set
    this.removedAddrSet.remove(addressWrapper.getInetSocketAddress());
    SocketChannel socketChannel = null;
    ConnectFuture future = new ConnectFuture(addressWrapper);
    try {
      socketChannel = SocketChannel.open();
      this.configureSocketChannel(socketChannel);
      if (!socketChannel.connect(addressWrapper.getInetSocketAddress())) {
        this.selectorManager.registerChannel(socketChannel, SelectionKey.OP_CONNECT, future);
      } else {
        this.addSession(this.createSession(socketChannel, addressWrapper));
        future.setResult(true);
      }
    } catch (Exception e) {
      if (socketChannel != null) {
        try {
          socketChannel.close();
        } catch (IOException e1) {
          //propagate original exception
        }
      }
      future.failure(e);
    }
    return future;
  }

  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,
      MemcachedSessionComparator comparator, BufferAllocator allocator,
      CommandFactory commandFactory, int poolSize, int maxQueuedNoReplyOperations) {
    super(configuration, null);
    this.sessionLocator = locator;
    this.sessionComparator = comparator;
    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 - 2025 Weber Informatics LLC | Privacy Policy