【Netty4 简单项目实践】十二、客户端连接池-压测利器

【前言】

之前想写压测工具来着,但是没有很好的分析工具,所以没什么动力写。最近学会了用阿尔萨斯赶紧把压测工具补上了,疯狂的压榨服务器性能。用Netty已有的连接池模型,可以省去自己维护连接保活的工作。本来写了5个类,整理了一下代码发现非常简单,基础类就一个。

【连接池】

忘记自己借鉴了谁的代码,客户端连接池采用Netty的ChannelPoolMap接口,用网络连接地址做Key,用FixedChannelPool实例化value,即不同的连接服务地址对应不同的连接池。FixedChannelPool的理论连接数上限是Integer.MAX_VALUE,并且使用ChannelHealthChecker接口来判断channel被取出池的时候是否是活的,如果是活的才向应用层吐出去。这样一来保活问题就不用自己操心了。

public class TcpClientPool {
    final EventLoopGroup group = new NioEventLoopGroup();
    final Bootstrap bootstrap = new Bootstrap();
    private static final int thread_num = Runtime.getRuntime().availableProcessors();
    // key 是地址, value是pool,即一个地址一个pool
    private AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap;

    public void build(ChannelPoolHandler poolHandler) throws Exception {
        bootstrap.group(group)
            .channel(NioSocketChannel.class)
            .option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.SO_KEEPALIVE, true);

        poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
            @Override
            protected SimpleChannelPool newPool(InetSocketAddress key) {
                return new FixedChannelPool(bootstrap.remoteAddress(key), poolHandler, thread_num);
            }
        };
    }
    /* 下面的代码省略 */
}

构造方法需要传入一个实现了ChannelPoolHandler接口处理Handler,这个Handler需要实现三个方法

void channelReleased(Channel ch) throws Exception;

void channelAcquired(Channel ch) throws Exception;

void channelCreated(Channel ch) throws Exception;

在处理类中最重要的事情是给channel加载业务协议的编解码处理器

public abstract class BaseChannelPoolHandler implements ChannelPoolHandler {

    private HandlerConfiguratorInterface configurator;
    public BaseChannelPoolHandler(HandlerConfiguratorInterface configurator) {
        this.configurator = configurator;
    }
    /**
     * 因为是裸的channel,所以需要给他配置上编解码器
     * 只需要配置一次就可以,因为channel会被还回到池里
     */
    @Override
    public void channelCreated(Channel ch) throws Exception {
        configurator.configChannel(ch);
    }

    @Override
    public void channelReleased(Channel ch) throws Exception {}

    @Override
    public void channelAcquired(Channel ch) throws Exception {}
}

其中实现HandlerConfiguratorInterface接口(自定义的接口,只有一个方法public void configChannel(Channel channel);)的类,需要通过configChannel方法给Channel对象装配编解码器

对于Http的实现

public class HttpConfigurator implements HandlerConfiguratorInterface {
    private static final int HTTP_AGGREGATE_SIZE = 8192;

    @Override
    public void configChannel(Channel ch) {
        SocketChannel channel = (SocketChannel)ch;
        channel.config().setKeepAlive(true);
        channel.config().setTcpNoDelay(true);
        channel.pipeline()
            .addLast(new HttpClientCodec())
            .addLast(new HttpObjectAggregator(HTTP_AGGREGATE_SIZE))
            .addLast(new HttpResponseHandler());
    }
}
这一步和常见的Netty处理器挂载方式是一致的。最后一个HttpResponseHandler是处理应答的Handler。

【关闭连接池】

客户端池还需要提供关闭的能力,否则程序无法正常退出

public void close() {
    poolMap.close();
    group.shutdownGracefully();
}

【发消息】

客户端池封装了异步和同步发消息的方法

异步方法

public void asyncWriteMessage(InetSocketAddress address, Object message) {
    SimpleChannelPool pool = getPool(address);
    Future<Channel> future = pool.acquire();
    // 获取到实例后发消息
   future.addListener((FutureListener<Channel>)f -> {
        if (f.isSuccess()) {
            Channel ch = f.getNow();
            if (ch.isWritable()) {
                ch.writeAndFlush(message);
            }
            // 归还实例
            pool.release(ch);
        }
    });
}

同步方法

    public boolean syncWriteMessage(InetSocketAddress address, Object message) {
        SimpleChannelPool pool = getPool(address);
        Future<Channel> future = pool.acquire();
        try {
            Channel channel = future.get();
            if (channel.isWritable()) {
                channel.writeAndFlush(message);
                pool.release(channel);
                return true;
            }
            pool.release(channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

【其他】

如果要发HTTP消息,需要自己封装Http消息体,否则Netty编码器会扔掉

public class HttpMsgComposer {
    public static Object compose(URI uri, String msg) throws Exception  {
        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
            uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));

        // 构建http请求
        request.headers().set(HttpHeaderNames.HOST, uri.getHost());
        request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
        request.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
        return request;
    }
}

【调用方式】

public class App {
    public static void main(String[] args) throws Exception {
        TcpClientPool pool = new TcpClientPool();
        pool.build(new HttpChannelPoolHandler(new HttpConfigurator()));

        String url = "http://163.com";
        URI uri = new URI(url);
        String host = uri.getHost();
        int port = uri.getPort() == -1 ? 80 : uri.getPort();
        InetSocketAddress address = new InetSocketAddress(host, port);

        for (int i = 0; i < 10; i++) {
            pool.asyncWriteMessage(address, HttpMsgComposer.compose(uri, "Hello World"));
        }

        while (true) {
            Thread.sleep(1000L);
            pool.close();
            break;
        }
    }
}

【后续计划】

1. github上发布代码

2. 支持UDP的客户端

3. 支持websocket

4. 支持Protocol Buff

5. 支持MQTT 


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值