阅读 102
分布式服务框架-底层通信(2)

分布式服务框架-底层通信(2)

这是我参与8月更文挑战的第22天,活动详情查看: 8月更文挑战

本文使用源码地址:simple-rpc

服务端代码详解

为了篇幅问题,可能会做代码上省略,并且删除了注释,建议现在源码查看。

代码如下:

public class NettyServer {
    private static final NettyServer NETTY_SERVER = new NettyServer();
    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    private static final SerializeType SERIALIZE_TYPE = SerializeType.getByType(SimpleRpcPropertiesUtil.getSerializeType());
    public void startServer(int port) {
        synchronized (NettyServer.class) {
            if (bossGroup != null || workGroup != null) {
                log.debug("netty server is already start");
            } else {
                bossGroup = new NioEventLoopGroup(1);
                workGroup = new NioEventLoopGroup();
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(bossGroup, workGroup)
                        .channel(NioServerSocketChannel.class)
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childOption(ChannelOption.TCP_NODELAY, true)
                        .handler(new LoggingHandler(LogLevel.INFO))
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) {
                                socketChannel.pipeline().addLast(new NettyDecoderHandler(Request.class, SERIALIZE_TYPE));
                                socketChannel.pipeline().addLast(new NettyEncoderHandler(SERIALIZE_TYPE));
                                socketChannel.pipeline().addLast(new NettyServerBizHandler());
                            }
                        });
                try {
                    bootstrap.bind(port).sync().channel();
                    log.info("NettyServer startServer start now!!!");
                } catch (Exception e) {
                    log.error("NettyServer startServer error", e);
                    throw new SRpcException("NettyServer startServer error", e);
                }
            }
        }
    }
    public static NettyServer getInstance() {
        return NETTY_SERVER;
    }
    private NettyServer() {
    }
}
复制代码

首先SERIALIZE_TYPE是配置的服务端序列化方式,对应于前文中说到的序列化引擎。

bossGroup用于接受新连接线程,主要负责创建新连接,workGroup负责读取数据的线程,主要用于读取数据以及业务逻辑处理。

ChannelOption.SO_BACKLOG表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数。

childOption设置了两个参数:

  • ChannelOption.SO_KEEPALIVE:表示是否开启TCP底层心跳机制,true为开启
  • ChannelOption.TCP_NODELAY:表示是否开启Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启。

LoggingHandler设置日志级别,打印Netty的运行日志,方便问题排查。

childHandler完成的就是主要的业务数据逻辑处理了,首先是添加继承了ByteToMessageDecoder的解码器NettyDecoderHandler,实现了encode()方法。代码如下:

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    int readableLength = in.readableBytes();
    if (readableLength < 4) {
        return;
    }
    in.markReaderIndex();
    int dataLength = in.readInt();
    if (dataLength < 0) {
        ctx.close();
    }
    if (readableLength < dataLength) {
        in.resetReaderIndex();
        return;
    }
    byte[] data = new byte[dataLength];
    in.readBytes(data);
    Object obj = SerializerEngine.deserialize(data, genericClass, serializeType.getSerializeType());
    out.add(obj);
}
复制代码

接着添加继承了MessageToByteEncoder的编码器NettyEncoderHandler,实现了encode()方法。代码如下:

@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf out) {
    byte[] bytes = SerializerEngine.serialize(msg, serializeType.getSerializeType());
    out.writeInt(bytes.length);
    out.writeBytes(bytes);
}
复制代码

在这里使用了一种简单的方式来解决Netty的粘包和半包问题,就是在报文的消息头中添加消息体的字节数组长度,如果当前可读长度小于目标长度,则返回,直到可以获取到的字节数组长度等于目标长度才继续执行。

最后添加的就是继承了SimpleChannelInboundHandler<Request>的业务处理器NettyServerBizHandler,代码如下:

public class NettyServerBizHandler extends SimpleChannelInboundHandler<Request> {
    private static final Map<String, Semaphore> SERVICE_KEY_SEMAPHORE_MAP = Maps.newConcurrentMap();
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Request request) {
        if (ctx.channel().isWritable()) {
            Provider provider = this.getLocalCacheWithReq(request);
            if (provider == null) {
                log.error("can't find provider for request, request={}", request);
                throw new SRpcException("illegal request, can't find provider for request, request={}" + request);
            } else {
                Provider requestProvider = request.getProvider();
                Semaphore semaphore = this.initSemaphore(requestProvider.getServiceItf().getName(), requestProvider.getWorkerThreads());

                Object result = this.invokeMethod(provider, request, semaphore);

                Response response = Response.builder().invokeTimeout(request.getInvokeTimeout())
                        .result(result)
                        .uniqueKey(request.getUniqueKey())
                        .build();
                ctx.writeAndFlush(response);
            }
        } else {
            log.error("channel closed!");
        }
    }
	// ...略
    private Object invokeMethod(Provider provider, Request request, Semaphore semaphore) {
        Object serviceObject = provider.getServiceObject();
        Method serviceMethod = provider.getServiceMethod();
        Object result = null;
        boolean acquire = false;
        try {
            acquire = semaphore.tryAcquire(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
            if (acquire) {
                result = serviceMethod.invoke(serviceObject, request.getArgs());
            }
        } catch (Exception e) {
            result = e;
            log.error("NettyServerBizHandler invokeMethod error, provider={}, request={}", provider, request, e);
        } finally {
            if (acquire) {
                semaphore.release();
            }
        }
        return result;
    }
	// ....略
}
复制代码

SERVICE_KEY_SEMAPHORE_MAP:根据前文中提到的服务端线程数workerThreads参数来初始化流控基础设施。key:服务接口权限类名,valueSemaphore(关于Semaphore如何完成限流可以参考之前并发编程中的文章:允许多个线程同时访问:信号量(Semaphore))。

代码执行逻辑如下:

  1. 我们通过getLocalCacheWithReq()方法获取服务列表,根据request中的请求信息找到服务提供者。
  2. 在限流器Semaphore的影响下发起反射调用:semaphore.tryAcquire()获取许可,获取成功执行反射调用。semaphore.release()方法释放许可。
  3. 通过ctx.writeAndFlush()方法返回调用结果。

小结

Netty服务端接收客户端发起的请求字节数组,然后通过解码器NettyDecoderHandler将字节数组解码为对应的Java请求对象。然后通过解码得到的Java对象确定服务提供者接口和方法,然后通过使用Java反射来发起调用。为了控制服务端服务能力,使用Semaphore做了流控处理。

至此,我们完成了Netty服务端启动、数据序列化编码Handler、数据反序列化Handler、服务端服务调用及调用结果返回功能的实现。Netty服务端代码相对于客户端代码要简单很多,下篇我们将一起看下客户端端代码实现。

文章分类
后端
文章标签