Package net.rubyeye.xmemcached.impl

Source Code of net.rubyeye.xmemcached.impl.Optimizer

/**
*Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)]
*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
*/
/**
*Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)]
*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.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;

import net.rubyeye.xmemcached.MemcachedOptimizer;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.command.AssocCommandAware;
import net.rubyeye.xmemcached.command.Command;
import net.rubyeye.xmemcached.command.CommandType;
import net.rubyeye.xmemcached.command.OperationStatus;
import net.rubyeye.xmemcached.command.binary.BaseBinaryCommand;
import net.rubyeye.xmemcached.command.binary.BinaryGetCommand;
import net.rubyeye.xmemcached.command.binary.BinaryGetMultiCommand;
import net.rubyeye.xmemcached.command.binary.BinarySetMultiCommand;
import net.rubyeye.xmemcached.command.binary.BinaryStoreCommand;
import net.rubyeye.xmemcached.command.binary.OpCode;
import net.rubyeye.xmemcached.command.text.TextGetOneCommand;
import net.rubyeye.xmemcached.monitor.Constants;
import net.rubyeye.xmemcached.monitor.MemcachedClientNameHolder;
import net.rubyeye.xmemcached.monitor.XMemcachedMbeanServer;
import net.rubyeye.xmemcached.utils.ByteUtils;
import net.rubyeye.xmemcached.utils.OpaqueGenerater;
import net.rubyeye.xmemcached.utils.Protocol;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.impl.FutureImpl;

/**
* Memcached command optimizer,merge single-get comands to multi-get
* command,merge ByteBuffers to fit the socket's sendBufferSize etc.
*
* @author dennis
*/
public class Optimizer implements OptimizerMBean, MemcachedOptimizer {

  public static final int DEFAULT_MERGE_FACTOR = 50;
  private int mergeFactor = DEFAULT_MERGE_FACTOR; // default merge factor;
  private boolean optimiezeGet = true;
  private final boolean optimiezeSet = true;
  private boolean optimiezeMergeBuffer = true;
  private static final Logger log = LoggerFactory.getLogger(Optimizer.class);
  private Protocol protocol = Protocol.Binary;

  public Optimizer(Protocol protocol) {
    XMemcachedMbeanServer.getInstance().registMBean(
        this,
        this.getClass().getPackage().getName() + ":type="
            + this.getClass().getSimpleName() + "-"
            + MemcachedClientNameHolder.getName());
    this.protocol = protocol;
  }

  public void setBufferAllocator(BufferAllocator bufferAllocator) {

  }

  public int getMergeFactor() {
    return this.mergeFactor;
  }

  public void setMergeFactor(int mergeFactor) {
    if (this.mergeFactor != mergeFactor) {
      log.warn("change mergeFactor from " + this.mergeFactor + " to "
          + mergeFactor);
    }
    this.mergeFactor = mergeFactor;

  }

  public boolean isOptimizeGet() {
    return this.optimiezeGet;
  }

  public void setOptimizeGet(boolean optimiezeGet) {
    log.warn(optimiezeGet ? "Enable merge get commands"
        : "Disable merge get commands");
    this.optimiezeGet = optimiezeGet;
  }

  public boolean isOptimizeMergeBuffer() {
    return this.optimiezeMergeBuffer;
  }

  public void setOptimizeMergeBuffer(boolean optimiezeMergeBuffer) {
    log.warn(optimiezeMergeBuffer ? "Enable merge buffers"
        : "Disable merge buffers");
    this.optimiezeMergeBuffer = optimiezeMergeBuffer;
  }

  @SuppressWarnings("unchecked")
  public Command optimize(final Command currentCommand,
      final Queue writeQueue, final Queue<Command> executingCmds,
      int sendBufferSize) {
    Command optimiezeCommand = currentCommand;
    optimiezeCommand = this.optimiezeGet(writeQueue, executingCmds,
        optimiezeCommand);
    optimiezeCommand = this.optimiezeSet(writeQueue, executingCmds,
        optimiezeCommand, sendBufferSize);
    optimiezeCommand = this.optimiezeMergeBuffer(optimiezeCommand, writeQueue,
        executingCmds, sendBufferSize);
    return optimiezeCommand;
  }

  /**
   * merge buffers to fit socket's send buffer size
   *
   * @param currentCommand
   * @return
   * @throws InterruptedException
   */
  @SuppressWarnings("unchecked")
  public final Command optimiezeMergeBuffer(Command optimiezeCommand,
      final Queue writeQueue, final Queue<Command> executingCmds,
      int sendBufferSize) {
    if (log.isDebugEnabled()) {
      log.debug("Optimieze merge buffer:" + optimiezeCommand.toString());
    }
    if (this.optimiezeMergeBuffer
        && optimiezeCommand.getIoBuffer().remaining() < sendBufferSize) {
      optimiezeCommand = this.mergeBuffer(optimiezeCommand, writeQueue,
          executingCmds, sendBufferSize);
    }
    return optimiezeCommand;
  }

  /**
   * Merge get operation to multi-get operation
   *
   * @param currentCmd
   * @param mergeCommands
   * @return
   * @throws InterruptedException
   */
  @SuppressWarnings("unchecked")
  public final Command optimiezeGet(final Queue writeQueue,
      final Queue<Command> executingCmds, Command optimiezeCommand) {
    if (optimiezeCommand.getCommandType() == CommandType.GET_ONE
        || optimiezeCommand.getCommandType() == CommandType.GETS_ONE) {
      if (this.optimiezeGet) {
        optimiezeCommand = this.mergeGetCommands(optimiezeCommand,
            writeQueue, executingCmds,
            optimiezeCommand.getCommandType());
      }
    }
    return optimiezeCommand;
  }

  public final Command optimiezeSet(final Queue writeQueue,
      final Queue<Command> executingCmds, Command optimiezeCommand,
      int sendBufferSize) {
    if (this.optimiezeSet
        && optimiezeCommand.getCommandType() == CommandType.SET
        && !optimiezeCommand.isNoreply() && this.protocol == Protocol.Binary) {
      optimiezeCommand = this.mergeSetCommands(optimiezeCommand, writeQueue,
          executingCmds, optimiezeCommand.getCommandType(),
          sendBufferSize);
    }
    return optimiezeCommand;
  }

  @SuppressWarnings("unchecked")
  private final Command mergeBuffer(final Command firstCommand,
      final Queue writeQueue, final Queue<Command> executingCmds,
      final int sendBufferSize) {
    Command lastCommand = firstCommand;
    Command nextCmd = (Command) writeQueue.peek();
    if (nextCmd == null) {
      return lastCommand;
    }

    final List<Command> commands = this.getLocalList();
    final ByteBuffer firstBuffer = firstCommand.getIoBuffer().buf();
    int totalBytes = firstBuffer.remaining();
    commands.add(firstCommand);
    boolean wasFirst = true;
    while (totalBytes + nextCmd.getIoBuffer().remaining() <= sendBufferSize
        && (nextCmd = (Command) writeQueue.peek()) != null) {
      if (nextCmd.getStatus() == OperationStatus.WRITING) {
        break;
      }
      if (nextCmd.isCancel()) {
        writeQueue.remove();
        continue;
      }
      nextCmd.setStatus(OperationStatus.WRITING);

      writeQueue.remove();

      if (wasFirst) {
        wasFirst = false;
      }
      // if it is get_one command,try to merge get commands
      if ((nextCmd.getCommandType() == CommandType.GET_ONE || nextCmd
          .getCommandType() == CommandType.GETS_ONE) && this.optimiezeGet) {
        nextCmd = this.mergeGetCommands(nextCmd, writeQueue, executingCmds,
            nextCmd.getCommandType());
      }

      commands.add(nextCmd);
      lastCommand = nextCmd;
      totalBytes += nextCmd.getIoBuffer().remaining();
      if (totalBytes > sendBufferSize) {
        break;
      }

    }
    if (commands.size() > 1) {
      byte[] buf = new byte[totalBytes];
      int offset = 0;
      for (Command command : commands) {
        byte[] ba = command.getIoBuffer().array();
        System.arraycopy(ba, 0, buf, offset, ba.length);
        offset += ba.length;
        if (command != lastCommand
            && (!command.isNoreply() || command instanceof BaseBinaryCommand)) {
          executingCmds.add(command);
        }
      }
      lastCommand.setIoBuffer(IoBuffer.wrap(buf));
    }
    return lastCommand;
  }

  private final ThreadLocal<List<Command>> threadLocal = new ThreadLocal<List<Command>>() {

    @Override
    protected List<Command> initialValue() {
      return new ArrayList<Command>(Optimizer.this.mergeFactor);
    }
  };

  public final List<Command> getLocalList() {
    List<Command> list = this.threadLocal.get();
    list.clear();
    return list;
  }

  static interface CommandCollector {
    public Object getResult();

    public void visit(Command command);

    public void finish();
   
    public CommandCollector reset();
  }

  static class KeyStringCollector implements CommandCollector {
    char[] buf = new char[1024*2];
    int count = 0;
    boolean wasFirst = true;

    public CommandCollector reset() {
      this.count = 0;
      this.wasFirst = true;
      return this;
    }

    public Object getResult() {
      return new String(this.buf, 0, this.count);
    }

    public void visit(Command command) {
      if (this.wasFirst) {
        this.append(command.getKey());
        this.wasFirst = false;
      } else {
        this.append(" ");
        this.append(command.getKey());
      }
    }

    private void expandCapacity(int minimumCapacity) {
      int newCapacity = (this.buf.length + 1) * 2;
      if (newCapacity < 0) {
        newCapacity = Integer.MAX_VALUE;
      } else if (minimumCapacity > newCapacity) {
        newCapacity = minimumCapacity;
      }
      char[] copy = new char[newCapacity];
      System.arraycopy(this.buf, 0, copy, 0, Math.min(this.buf.length, newCapacity));
      this.buf = copy;
    }

    private void append(String str) {
      int len = str.length();
      if (len == 0) {
        return;
      }
      int newCount = this.count + len;
      if (newCount > this.buf.length) {
        this.expandCapacity(newCount);
      }
      str.getChars(0, len, this.buf, this.count);
      this.count = newCount;
    }

    public void finish() {
      // do nothing

    }

  }

  private static class BinarySetQCollector implements CommandCollector {
    ArrayList<IoBuffer> bufferList = new ArrayList<IoBuffer>();
    int totalBytes;
    BinaryStoreCommand prevCommand;
    Map<Object, Command> mergeCommands;

   
   
    public CommandCollector reset() {
      this.bufferList.clear();
      this.totalBytes = 0;
      this.prevCommand = null;
      this.mergeCommands = null;
      return this;
    }

    public Object getResult() {
      byte[] buf = new byte[this.totalBytes];
      int offset = 0;
      for (IoBuffer buffer : this.bufferList) {
        byte[] ba = buffer.array();
        System.arraycopy(ba, 0, buf, offset, ba.length);
        offset += ba.length;
      }
      BinarySetMultiCommand resultCommand = new BinarySetMultiCommand(
          null, CommandType.SET_MANY, new CountDownLatch(1));
      resultCommand.setIoBuffer(IoBuffer.wrap(buf));
      resultCommand.setMergeCommands(this.mergeCommands);
      resultCommand.setMergeCount(this.mergeCommands.size());
      return resultCommand;
    }

    public void visit(Command command) {

      // Encode prev command
      if (this.prevCommand != null) {
        // first n-1 send setq command
        BinaryStoreCommand setqCmd = new BinaryStoreCommand(
            this.prevCommand.getKey(), this.prevCommand.getKeyBytes(),
            CommandType.SET, null, this.prevCommand.getExpTime(),
            this.prevCommand.getCas(),
            // set noreply to be true
            this.prevCommand.getValue(), true,
            this.prevCommand.getTranscoder());
        // We must set the opaque to get error message.
        int opaque = OpaqueGenerater.getInstance().getNextValue();
        setqCmd.setOpaque(opaque);
        setqCmd.encode();
        this.totalBytes += setqCmd.getIoBuffer().remaining();


        this.bufferList.add(setqCmd.getIoBuffer());
        // GC friendly
        setqCmd.setIoBuffer(MemcachedHandler.EMPTY_BUF);
        setqCmd.setValue(null);
        this.prevCommand.setValue(null);
        this.prevCommand.setIoBuffer(MemcachedHandler.EMPTY_BUF);
        if (this.mergeCommands == null) {
          this.mergeCommands = new HashMap<Object, Command>();
        }
        this.mergeCommands.put(opaque, this.prevCommand);
      }
      this.prevCommand = (BinaryStoreCommand) command;
    }

    public void finish() {
      if (this.mergeCommands == null) {
        return;
      }
      // prevCommand is the last command,last command must be a SET
      // command,ensure
      // previous SETQ commands sending response back
      BinaryStoreCommand setqCmd = new BinaryStoreCommand(
          this.prevCommand.getKey(), this.prevCommand.getKeyBytes(),
          CommandType.SET, null, this.prevCommand.getExpTime(),
          this.prevCommand.getCas(),
          // set noreply to be false.
          this.prevCommand.getValue(), false, this.prevCommand.getTranscoder());
      // We must set the opaque to get error message.
      int opaque = OpaqueGenerater.getInstance().getNextValue();
      setqCmd.setOpaque(opaque);
      setqCmd.encode();
      this.bufferList.add(setqCmd.getIoBuffer());
      this.totalBytes += setqCmd.getIoBuffer().remaining();
      if (this.mergeCommands != null) {
        this.mergeCommands.put(opaque, this.prevCommand);
      }
    }

  }

  private static class BinaryGetQCollector implements CommandCollector {
    ArrayList<IoBuffer> bufferList = new ArrayList<IoBuffer>(50);
    int totalBytes;
    Command prevCommand;
   
    public CommandCollector reset(){
      this.bufferList.clear();
      this.totalBytes = 0;
      this.prevCommand = null;
      return this;
    }

    public Object getResult() {
      byte[] buf = new byte[this.totalBytes];
      int offset = 0;
      for (IoBuffer buffer : this.bufferList) {
        byte[] ba = buffer.array();
        System.arraycopy(ba, 0, buf, offset, ba.length);
        offset += ba.length;
      }
      BinaryGetMultiCommand resultCommand = new BinaryGetMultiCommand(
          null, CommandType.GET_MANY, new CountDownLatch(1));
      resultCommand.setIoBuffer(IoBuffer.wrap(buf));
      return resultCommand;
    }

    public void visit(Command command) {
      // Encode prev command
      if (this.prevCommand != null) {
        // first n-1 send getq command
        Command getqCommand = new BinaryGetCommand(
            this.prevCommand.getKey(), this.prevCommand.getKeyBytes(), null,
            null, OpCode.GET_KEY_QUIETLY, true);
        getqCommand.encode();
        this.totalBytes += getqCommand.getIoBuffer().remaining();
        this.bufferList.add(getqCommand.getIoBuffer());
      }
      this.prevCommand = command;
    }

    public void finish() {
      // prev command is the last command,last command must be getk,ensure
      // getq commands sending response back
      Command lastGetKCommand = new BinaryGetCommand(
          this.prevCommand.getKey(), this.prevCommand.getKeyBytes(),
          CommandType.GET_ONE, new CountDownLatch(1), OpCode.GET_KEY,
          false);
      lastGetKCommand.encode();
      this.bufferList.add(lastGetKCommand.getIoBuffer());
      this.totalBytes += lastGetKCommand.getIoBuffer().remaining();
    }

  }

  @SuppressWarnings("unchecked")
  private final Command mergeGetCommands(final Command currentCmd,
      final Queue writeQueue, final Queue<Command> executingCmds,
      CommandType expectedCommandType) {
    Map<Object, Command> mergeCommands = null;
    int mergeCount = 1;
    final CommandCollector commandCollector = this.createGetCommandCollector();
    currentCmd.setStatus(OperationStatus.WRITING);

    commandCollector.visit(currentCmd);
    while (mergeCount < this.mergeFactor) {
      Command nextCmd = (Command) writeQueue.peek();
      if (nextCmd == null) {
        break;
      }
      if (nextCmd.isCancel()) {
        writeQueue.remove();
        continue;
      }
      if (nextCmd.getCommandType() == expectedCommandType) {
        if (mergeCommands == null) { // lazy initialize
          mergeCommands = new HashMap<Object, Command>(
              this.mergeFactor / 2);
          mergeCommands.put(currentCmd.getKey(), currentCmd);
        }
        if (log.isDebugEnabled()) {
          log.debug("Merge get command:" + nextCmd.toString());
        }
        nextCmd.setStatus(OperationStatus.WRITING);
        Command removedCommand = (Command) writeQueue.remove();
        // If the key is exists,add the command to associated list.
        if (mergeCommands.containsKey(removedCommand.getKey())) {
          final AssocCommandAware mergedGetCommand = (AssocCommandAware) mergeCommands
              .get(removedCommand.getKey());
          if (mergedGetCommand.getAssocCommands() == null) {
            mergedGetCommand
            .setAssocCommands(new ArrayList<Command>(5));
          }
          mergedGetCommand.getAssocCommands().add(removedCommand);
        } else {
          commandCollector.visit(nextCmd);
          mergeCommands.put(removedCommand.getKey(), removedCommand);
        }
        mergeCount++;
      } else {
        break;
      }
    }
    if (mergeCount == 1) {
      return currentCmd;
    } else {
      commandCollector.finish();
      if (log.isDebugEnabled()) {
        log.debug("Merge optimieze:merge " + mergeCount
            + " get commands");
      }
      return this.newMergedCommand(mergeCommands, mergeCount,
          commandCollector, expectedCommandType);
    }
  }
 
  private static final ThreadLocal<BinarySetQCollector> BIN_SET_CMD_COLLECTOR_THREAD_LOCAL = new ThreadLocal<Optimizer.BinarySetQCollector>(){

    @Override
    protected BinarySetQCollector initialValue() {
      return new BinarySetQCollector();
    }
   
  };

  private final Command mergeSetCommands(final Command currentCmd,
      final Queue writeQueue, final Queue<Command> executingCmds,
      CommandType expectedCommandType, int sendBufferSize) {
    int mergeCount = 1;
    final CommandCollector commandCollector = BIN_SET_CMD_COLLECTOR_THREAD_LOCAL.get().reset();
    currentCmd.setStatus(OperationStatus.WRITING);
    int totalBytes = currentCmd.getIoBuffer().remaining();
    commandCollector.visit(currentCmd);
    while (mergeCount < this.mergeFactor && totalBytes <= sendBufferSize) {
      Command nextCmd = (Command) writeQueue.peek();
      if (nextCmd == null) {
        break;
      }
      if (nextCmd.isCancel()) {
        writeQueue.remove();
        continue;
      }
      if (nextCmd.getCommandType() == expectedCommandType
          && !nextCmd.isNoreply()) {
        if (log.isDebugEnabled()) {
          log.debug("Merge set command:" + nextCmd.toString());
        }
        nextCmd.setStatus(OperationStatus.WRITING);
        writeQueue.remove();

        commandCollector.visit(nextCmd);

        mergeCount++;
      } else {
        break;
      }
      totalBytes += nextCmd.getIoBuffer().remaining();
    }
    if (mergeCount == 1) {
      return currentCmd;
    } else {
      commandCollector.finish();
      if (log.isDebugEnabled()) {
        log.debug("Merge optimieze:merge " + mergeCount
            + " get commands");
      }
      return (Command) commandCollector.getResult();
    }
  }

  private static ThreadLocal<KeyStringCollector> TEXT_GET_CMD_COLLECTOR_THREAD_LOCAL = new ThreadLocal<Optimizer.KeyStringCollector>(){
    @Override
    public KeyStringCollector initialValue(){
      return new KeyStringCollector();
    }
  };
 
  private static ThreadLocal<BinaryGetQCollector> BIN_GET_CMD_COLLECTOR_THREAD_LOCAL = new ThreadLocal<Optimizer.BinaryGetQCollector>(){
    @Override
    public BinaryGetQCollector initialValue(){
      return new BinaryGetQCollector();
    }
  };
 
  private CommandCollector createGetCommandCollector() {
    switch (this.protocol) {
    case Binary:
      return BIN_GET_CMD_COLLECTOR_THREAD_LOCAL.get().reset();
    default:
      return TEXT_GET_CMD_COLLECTOR_THREAD_LOCAL.get().reset();
    }
  }

  private Command newMergedCommand(final Map<Object, Command> mergeCommands,
      int mergeCount, final CommandCollector commandCollector,
      final CommandType commandType) {
    if (this.protocol == Protocol.Text) {
      String resultKey = (String) commandCollector.getResult();

      byte[] keyBytes = ByteUtils.getBytes(resultKey);
      byte[] cmdBytes = commandType == CommandType.GET_ONE ? Constants.GET
          : Constants.GETS;
      final byte[] buf = new byte[cmdBytes.length + 3 + keyBytes.length];
      ByteUtils.setArguments(buf, 0, cmdBytes, keyBytes);
      TextGetOneCommand cmd = new TextGetOneCommand(resultKey, keyBytes,
          commandType, null);
      cmd.setMergeCommands(mergeCommands);
      cmd.setWriteFuture(new FutureImpl<Boolean>());
      cmd.setMergeCount(mergeCount);
      cmd.setIoBuffer(IoBuffer.wrap(buf));
      return cmd;
    } else {
      BinaryGetMultiCommand result = (BinaryGetMultiCommand) commandCollector
          .getResult();
      result.setMergeCount(mergeCount);
      result.setMergeCommands(mergeCommands);
      return result;
    }
  }
}
TOP

Related Classes of net.rubyeye.xmemcached.impl.Optimizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.