// Copyright (C) 2011 - Will Glozer. All rights reserved.
package com.lambdaworks.redis;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.codec.Utf8StringCodec;
import com.lambdaworks.redis.protocol.*;
import com.lambdaworks.redis.pubsub.PubSubCommandHandler;
import com.lambdaworks.redis.pubsub.RedisPubSubConnection;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.group.*;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.util.HashedWheelTimer;
import org.jboss.netty.util.Timer;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.*;
/**
* A scalable thread-safe <a href="http://redis.io/">Redis</a> client. Multiple threads
* may share one {@link RedisConnection} provided they avoid blocking and transactional
* operations such as BLPOP and MULTI/EXEC.
*
* @author Will Glozer
*/
public class RedisClient {
private ClientBootstrap bootstrap;
private Timer timer;
private ChannelGroup channels;
private int timeout;
private TimeUnit unit;
/**
* Create a new client that connects to the supplied host on the default port.
*
* @param host Server hostname.
*/
public RedisClient(String host) {
this(host, 6379);
}
/**
* Create a new client that connects to the supplied host and port.
*
* @param host Server hostname.
* @param port Server port.
*/
public RedisClient(String host, int port) {
ExecutorService connectors = Executors.newFixedThreadPool(1);
ExecutorService workers = Executors.newCachedThreadPool();
ClientSocketChannelFactory factory = new NioClientSocketChannelFactory(connectors, workers);
InetSocketAddress addr = new InetSocketAddress(host, port);
bootstrap = new ClientBootstrap(factory);
bootstrap.setOption("remoteAddress", addr);
setDefaultTimeout(60, TimeUnit.SECONDS);
channels = new DefaultChannelGroup();
timer = new HashedWheelTimer();
}
/**
* Set the default timeout for connections created by this client.
*
* @param timeout Default connection timeout.
* @param unit Unit of time for the timeout.
*/
public void setDefaultTimeout(int timeout, TimeUnit unit) {
this.timeout = timeout;
this.unit = unit;
bootstrap.setOption("connectTimeoutMillis", unit.toMillis(timeout));
}
/**
* Open a new connection to the redis server that treats all keys and
* values as UTF-8 strings.
*
* @return A new connection.
*/
public RedisConnection<String, String> connect() {
return connect(new Utf8StringCodec());
}
/**
* Open a new pub/sub connection to the redis server that treats all
* keys and values as UTF-8 strings.
*
* @return A new connection.
*/
public RedisPubSubConnection<String, String> connectPubSub() {
return connectPubSub(new Utf8StringCodec());
}
/**
* Open a new connection to the redis server. Use the supplied
* {@link RedisCodec codec} to encode/decode keys and values.
*
* @param codec Use this codec to encode/decode keys and values.
*
* @return A new connection.
*/
public <K, V> RedisConnection<K, V> connect(RedisCodec<K, V> codec) {
try {
BlockingQueue<Command<?>> queue = new LinkedBlockingQueue<Command<?>>();
ConnectionWatchdog watchdog = new ConnectionWatchdog(bootstrap, channels, timer);
CommandHandler handler = new CommandHandler(queue);
RedisConnection<K, V> connection = new RedisConnection<K, V>(queue, codec, timeout, unit);
ChannelPipeline pipeline = Channels.pipeline(watchdog, handler, connection);
Channel channel = bootstrap.getFactory().newChannel(pipeline);
ChannelFuture future = channel.connect((SocketAddress) bootstrap.getOption("remoteAddress"));
future.await();
if (!future.isSuccess()) {
throw future.getCause();
}
watchdog.setReconnect(true);
return connection;
} catch (Throwable e) {
throw new RedisException("Unable to connect", e);
}
}
/**
* Open a new pub/sub connection to the redis server. Use the supplied
* {@link RedisCodec codec} to encode/decode keys and values.
*
* @param codec Use this codec to encode/decode keys and values.
*
* @return A new pub/sub connection.
*/
public <K, V> RedisPubSubConnection<K, V> connectPubSub(RedisCodec<K, V> codec) {
try {
BlockingQueue<Command<?>> queue = new LinkedBlockingQueue<Command<?>>();
ConnectionWatchdog watchdog = new ConnectionWatchdog(bootstrap, channels, timer);
PubSubCommandHandler<K, V> handler = new PubSubCommandHandler<K, V>(queue, codec);
RedisPubSubConnection<K, V> connection = new RedisPubSubConnection<K, V>(queue, codec, timeout, unit);
ChannelPipeline pipeline = Channels.pipeline(watchdog, handler, connection);
Channel channel = bootstrap.getFactory().newChannel(pipeline);
ChannelFuture future = channel.connect((SocketAddress) bootstrap.getOption("remoteAddress"));
future.await();
if (!future.isSuccess()) {
throw future.getCause();
}
watchdog.setReconnect(true);
return connection;
} catch (Throwable e) {
throw new RedisException("Unable to connect", e);
}
}
/**
* Shutdown this client and close all open connections. The client should be
* discarded after calling shutdown.
*/
public void shutdown() {
for (Channel c : channels) {
ChannelPipeline pipeline = c.getPipeline();
RedisConnection connection = pipeline.get(RedisConnection.class);
connection.close();
}
ChannelGroupFuture future = channels.close();
future.awaitUninterruptibly();
bootstrap.releaseExternalResources();
}
}