通过案例学Netty-Promise and ChannelFuture
1.写一个简单的Redis客户端,通过命令输入操作Redis
public class RedisClient {
private static final String HOST = "你自己的Redis地址";
private static final int PORT = Integer.parseInt(System.getProperty("port", "6379"));
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new RedisDecoder());
p.addLast(new RedisBulkStringAggregator());
p.addLast(new RedisArrayAggregator());
p.addLast(new RedisEncoder());
p.addLast(new RedisClientHandler());
}
});
// Start the connection attempt.
Channel ch = b.connect(HOST, PORT).sync().channel();
// Read commands from the stdin.
System.out.println("Enter Redis commands (quit to end)");
ChannelFuture lastWriteFuture = null;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for (;;) {
final String input = in.readLine();
final String line = input != null ? input.trim() : null;
if (line == null || "quit".equalsIgnoreCase(line)) { // EOF or "quit"
ch.close().sync();
break;
} else if (line.isEmpty()) { // skip `enter` or `enter` with spaces.
continue;
}
// Sends the received line to the server.
lastWriteFuture = ch.writeAndFlush(line);
lastWriteFuture.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
System.err.print("write failed: ");
future.cause().printStackTrace(System.err);
}
}
});
}
// Wait until all messages are flushed before closing the channel.
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
} finally {
group.shutdownGracefully();
}
}
}
这个类非常简单,我们很熟悉的流程,New一个EventLoopGroup,再来个Bootstrap启动类,绑定上各式各样的Handler,最后一个无限for循环读取你的指令并发送给Redis
2.Promise and ChannelFuture
通过类图可以清晰的看到,Netty的ChannelFuture就是Java中的Future的子类。本身java的future就是用来处理异步操作的,Netty在它的基础上添加了listener机制来处理当任务完成后的操作。
也许你和有同样的疑惑,乍一看Promise
和ChannelFuture
没有什么区别。但实际上是一种职责分离的设计思想,ChannelFuture是对外的客户端使用,它提供了一种只读的方式来观察操作的结果。用户通过ChannelFuture检查操作的状态,添加监听器,并根据操作结果采取后续行动。他是线程安全的
Promise则是操作的生产者代码使用的接口,提供了设置操作结果setSuccess,setFailure,是内部Netty自己使用的。结合Netty的基于事件驱动的网络架构,使得代码更加模块化和清晰。
3.DefaultPromise.class
RedisClient中的lastWriteFuture获取通过writeAndFulsh()返回的ChannelFuture实际上是一个ChannelPromise。我们addlistener()的操作是以数组的形式保存起来的,下面详细介绍了这个过程
private GenericFutureListener<? extends Future<?>> listener;
private DefaultFutureListeners listeners;//里面有一个GenericFutureListener数组
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
checkNotNull(listener, "listener");
synchronized (this) {
addListener0(listener);
}
if (isDone()) {
notifyListeners();
}
return this;
}
private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
if (this.listener == null) {
if (listeners == null) {
this.listener = listener;
} else {
listeners.add(listener);
}
} else {
assert listeners == null;
listeners = new DefaultFutureListeners(this.listener, listener);
this.listener = null;
}
}
4.DefalutFutureListeners
DefaultFutureListeners(
GenericFutureListener<? extends Future<?>> first, GenericFutureListener<? extends Future<?>> second) {
listeners = new GenericFutureListener[2];
listeners[0] = first;
listeners[1] = second;
size = 2;
if (first instanceof GenericProgressiveFutureListener) {
progressiveSize ++;
}
if (second instanceof GenericProgressiveFutureListener) {
progressiveSize ++;
}
}
public void add(GenericFutureListener<? extends Future<?>> l) {
GenericFutureListener<? extends Future<?>>[] listeners = this.listeners;
final int size = this.size;
if (size == listeners.length) {
this.listeners = listeners = Arrays.copyOf(listeners, size << 1);
}
listeners[size] = l;
this.size = size + 1;
if (l instanceof GenericProgressiveFutureListener) {
progressiveSize ++;
}
}
第一步 | 检查listener是否为空 |
---|---|
第二步 | 加锁 |
第三步 | addListeners()方法很有意思,首先去检查listener和listeners是否为空,都为空则直接复制listeners,若listeners不为空,则调用DefaultFutureListeners的add()方法。在这里会进行一次大小判断,是否进行数组扩容。如果已经存在了一个listener,则调用构造方法,可以看到默认数组大小为2,然后将listener设置为null。这样在第二次及以后的判断都会直接转到add()方法上。 |
疑问 | Netty为什么要这样设计,一个简单给数组添加元素,为什么要多此一举的弄一个listener变量出来,直接使用listeners不行吗,还判断个啥劲,直接往数组里加就好了…我在第一次看到代码的时候就十分否困惑没有看懂,看了好几遍才明白它的逻辑。 |
待续 | 我在Netty的discord上发了帖子,希望能得到回到。也欢迎各位大神解答我的疑惑 |
后续 | 一位Netty的开发者说在大部分情况下,我们只需要一个listener就足够了,所以特意列出了这种情况去处理,为了节省内存的开销。想想也是,一个变量和一个数组所站的内存空间肯定是不一样的。我在后续的Netty中也遇到了很多这种场景,这种在我们日常的开发破CRUD中很难去使用和理解 ,大概高性能就是这样一点一点挤出来的吧 |
5.RedisClientHandler
public class RedisClientHandler extends ChannelDuplexHandler {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
String[] commands = ((String) msg).split("\\s+");
List<RedisMessage> children = new ArrayList<RedisMessage>(commands.length);
for (String cmdString : commands) {
children.add(new FullBulkStringRedisMessage(ByteBufUtil.writeUtf8(ctx.alloc(), cmdString)));
}
RedisMessage request = new ArrayRedisMessage(children);
ctx.write(request, promise);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
RedisMessage redisMessage = (RedisMessage) msg;
printAggregatedRedisResponse(redisMessage);
ReferenceCountUtil.release(redisMessage);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.err.print("exceptionCaught: ");
cause.printStackTrace(System.err);
ctx.close();
}
private static void printAggregatedRedisResponse(RedisMessage msg) {
if (msg instanceof SimpleStringRedisMessage) {
System.out.println(((SimpleStringRedisMessage) msg).content());
} else if (msg instanceof ErrorRedisMessage) {
System.out.println(((ErrorRedisMessage) msg).content());
} else if (msg instanceof IntegerRedisMessage) {
System.out.println(((IntegerRedisMessage) msg).value());
} else if (msg instanceof FullBulkStringRedisMessage) {
System.out.println(getString((FullBulkStringRedisMessage) msg));
} else if (msg instanceof ArrayRedisMessage) {
for (RedisMessage child : ((ArrayRedisMessage) msg).children()) {
printAggregatedRedisResponse(child);
}
} else {
throw new CodecException("unknown message type: " + msg);
}
}
private static String getString(FullBulkStringRedisMessage msg) {
if (msg.isNull()) {
return "(null)";
}
return msg.content().toString(CharsetUtil.UTF_8);
}
}
这个Handler主要是处理发送Redis的指令和打印从Redis接收到的信息。有兴趣您可以用这两个类实践一下,直接用命令行操作Redis
下一章我们会讲ChannelDupleshadler以及它背后的一些类,这一章已经挺长的了😶🌫️