org.jgroups.blocks.MemcachedConnector Maven / Gradle / Ivy
package org.jgroups.blocks;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.util.Util;
import javax.management.MBeanRegistrationException;
import javax.management.MalformedObjectNameException;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.ClosedSelectorException;
import java.util.*;
import java.util.concurrent.*;
/** Class which listens on a server socket for memcached clients, reads the requests, forwards them to an instance of
* PartitionedHashMap and sends the response. A memcached client should be able to work without changes once the
* memcached protocol (http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt) has been implemented
* completely.
* @author Bela Ban
*/
public class MemcachedConnector implements Runnable {
@ManagedAttribute(writable=false)
private int port=11211;
@ManagedAttribute(writable=false)
private InetAddress bind_addr=null;
private PartitionedHashMap cache=null;
private Thread thread=null;
private ServerSocket srv_sock;
@ManagedAttribute(writable=true)
private int core_threads=1;
@ManagedAttribute(writable=true)
private int max_threads=500;
@ManagedAttribute(writable=true)
private long idle_time=5000L;
private Executor thread_pool;
private long start_time;
private final byte[] STORED="STORED\r\n".getBytes();
private final byte[] DELETED="DELETED\r\n".getBytes();
private final byte[] END="END\r\n".getBytes();
private final byte[] RN="\r\n".getBytes();
public MemcachedConnector(InetAddress bind_addr, int port, PartitionedHashMap cache) {
this.bind_addr=bind_addr;
this.cache=cache;
this.port=port;
}
public InetAddress getBindAddress() {
return bind_addr;
}
public void setBindAddress(InetAddress bind_addr) {
this.bind_addr=bind_addr;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port=port;
}
public PartitionedHashMap getCache() {
return cache;
}
public void setCache(PartitionedHashMap cache) {
this.cache=cache;
}
public int getThreadPoolCoreThreads() {
return core_threads;
}
public void setThreadPoolCoreThreads(int core_threads) {
this.core_threads=core_threads;
}
public int getThreadPoolMaxThreads() {
return max_threads;
}
public void setThreadPoolMaxThreads(int max_threads) {
this.max_threads=max_threads;
}
public long getThreadPoolIdleTime() {
return idle_time;
}
public void setThreadPoolIdleTime(long idle_time) {
this.idle_time=idle_time;
}
public Executor getThreadPool() {
return thread_pool;
}
public void setThreadPool(Executor thread_pool) {
if(this.thread_pool instanceof ExecutorService) {
((ExecutorService)thread_pool).shutdown();
}
this.thread_pool=thread_pool;
}
public Map getStats() {
Map stats=new HashMap();
stats.put("time", System.currentTimeMillis());
stats.put("uptime", (System.currentTimeMillis() - start_time) / 1000L);
return stats;
}
@ManagedOperation
public void start() throws IOException, MalformedObjectNameException, MBeanRegistrationException {
srv_sock=new ServerSocket(port, 50, bind_addr);
if(thread_pool == null) {
thread_pool=new ThreadPoolExecutor(core_threads, max_threads, idle_time, TimeUnit.MILLISECONDS,
new SynchronousQueue(), new ThreadPoolExecutor.CallerRunsPolicy());
// thread_pool=new DirectExecutor();
}
if(thread == null || !thread.isAlive()) {
thread=new Thread(this, "Acceptor");
thread.start();
}
start_time=System.currentTimeMillis();
}
@ManagedOperation
public void stop() throws IOException {
Util.close(srv_sock);
thread=null;
if(thread_pool instanceof ExecutorService)
((ExecutorService)thread_pool).shutdown();
}
public void run() {
System.out.println("MemcachedConnector listening on " + srv_sock.getLocalSocketAddress());
while(thread != null && Thread.currentThread().equals(thread)) {
Socket client_sock=null;
try {
client_sock=srv_sock.accept();
// System.out.println("ACCEPT: " + client_sock.getRemoteSocketAddress());
final RequestHandler handler=new RequestHandler(client_sock);
/*new Thread() {
public void run() {
handler.run();
}
}.start();
*/
thread_pool.execute(handler);
}
catch(ClosedSelectorException closed) {
Util.close(client_sock);
break;
}
catch(Throwable e) {
}
}
}
private class RequestHandler implements Runnable {
private final Socket client_sock;
private final InputStream input;
private final OutputStream output;
public RequestHandler(Socket client_sock) throws IOException {
this.client_sock=client_sock;
this.input=new BufferedInputStream(client_sock.getInputStream());
this.output=new BufferedOutputStream(client_sock.getOutputStream());
}
public void run() {
byte[] val;
String line;
while(client_sock.isConnected()) {
try {
line=Util.readLine(input);
if(line == null)
break;
// System.out.println("line = " + line);
Request req=parseRequest(line);
if(req == null) {
break;
}
// System.out.println("req = " + req);
switch(req.type) {
case SET:
byte[] data=new byte[req.number_of_bytes];
int num=input.read(data, 0, data.length);
if(num == -1)
throw new EOFException();
cache.put(req.key, data, req.caching_time);
output.write(STORED);
output.flush();
Util.discardUntilNewLine(input);
break;
case GET:
case GETS:
if(req.keys != null && !req.keys.isEmpty()) {
for(String key: req.keys) {
val=cache.get(key);
if(val != null) {
int length=val.length;
output.write(("VALUE " + key + " 0 " + length + "\r\n").getBytes());
output.write(val, 0, length);
output.write(RN);
}
}
}
output.write(END);
output.flush();
break;
case DELETE:
cache.remove(req.key);
output.write(DELETED);
output.flush();
break;
case STATS:
Map stats=getStats();
StringBuilder sb=new StringBuilder();
for(Map.Entry entry: stats.entrySet()) {
sb.append("STAT ").append(entry.getKey()).append(" ").append(entry.getValue()).append("\r\n");
}
sb.append("END\r\n");
output.write(sb.toString().getBytes());
output.flush();
break;
}
}
catch(StreamCorruptedException corrupted_ex) {
try {
output.write(("CLIENT_ERROR failed to parse request: " + corrupted_ex + ":\r\n").getBytes());
output.flush();
}
catch(IOException e) {}
}
catch(EOFException end_of_file_ex) {
break;
}
catch(Throwable e) {
}
}
Util.close(client_sock);
}
private Request parseRequest(String line) throws IOException {
Request req=new Request();
String[] args=line.trim().split(" +");
String tmp=args[0];
if(tmp == null)
throw new EOFException();
if(tmp.equals("set"))
req.type=Request.Type.SET;
else if(tmp.equals("add"))
req.type=Request.Type.ADD;
else if(tmp.equals("replace"))
req.type=Request.Type.REPLACE;
else if(tmp.equals("prepend"))
req.type=Request.Type.PREPEND;
else if(tmp.equals("append"))
req.type=Request.Type.APPEND;
else if(tmp.equals("cas"))
req.type=Request.Type.CAS;
else if(tmp.equals("incr"))
req.type=Request.Type.INCR;
else if(tmp.equals("decr"))
req.type=Request.Type.DECR;
else if(tmp.equals("get"))
req.type=Request.Type.GET;
else if(tmp.equals("gets"))
req.type=Request.Type.GETS;
else if(tmp.equals("delete"))
req.type=Request.Type.DELETE;
else if(tmp.equals("stat"))
req.type=Request.Type.STAT;
else if(tmp.equals("stats"))
req.type=Request.Type.STATS;
else {
throw new StreamCorruptedException("request \"" + line + "\" not known");
}
switch(req.type) {
case SET:
case ADD:
case REPLACE:
case PREPEND:
case APPEND:
// key
tmp=args[1];
if(tmp == null)
throw new EOFException();
req.key=tmp;
// read flags and discard: flags are not supported
tmp=args[2];
if(tmp == null)
throw new EOFException();
// expiry time
tmp=args[3];
if(tmp == null)
throw new EOFException();
req.caching_time=Long.parseLong(tmp) * 1000L; // convert from secs to ms
// number of bytes
tmp=args[4];
if(tmp == null)
throw new EOFException();
req.number_of_bytes=Integer.parseInt(tmp);
break;
case GET:
case GETS:
req.keys=new ArrayList(5);
req.keys.addAll(Arrays.asList(args).subList(1, args.length));
break;
case DELETE:
// key
tmp=args[1];
if(tmp == null)
throw new EOFException();
req.key=tmp;
break;
case STATS:
break;
}
return req;
}
}
public static class Request {
public static enum Type {SET, ADD, REPLACE, PREPEND, APPEND, CAS, INCR, DECR, GET, GETS, DELETE, STAT, STATS};
Type type;
String key;
List keys=null;
long caching_time;
int number_of_bytes=0;
public Request() {
}
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append(type + ": ");
if(key != null)
sb.append("key=" + key);
else if(keys != null && !keys.isEmpty())
sb.append("keys=" + keys);
sb.append(", caching_time=" + caching_time + ", number_of_bytes=" + number_of_bytes);
return sb.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy