【黑马-netty篇】- 第3章netty知识应用代码调优【已完结】

相关章节内容传送

0.netty基础知识深入浅出
1.netty基础组件知识
2.netty知识应用
4.netty源码

前言

针对第2章netty知识点聊天室代码进行调优,从加解码器序列化、连接参数调优以及RPC框架代码实现方面进行调整。

1、拓展序列化算法

序列化接口

之前对于传递信息参数序列化方式是固定写死的,将序列化进行拓展,首先定义一个序列化接口,里面两个方法 serialize 与 deserialize。

/**
 * 请求信息序列化接口
 **/
public interface Serializer {
    /**
     * 传递信息序列化的方法
     *
     * @param object 被序列化的对象
     * @param <T>    被序列化的对象类型
     * @return 序列化之后的字节数组
     */
    <T> byte[] serialize(T object);

    /**
     * 字节数组反序列化方法
     *
     * @param clazz 反序列化目标的Class对象
     * @param bytes 被反序列化的字节数组
     * @param <T>   反序列化的目标类
     * @return 反序列化之后的目标对象
     */
    <T> T deserialize(Class<T> clazz, byte[] bytes);
}

枚举实现类

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * 反序列化实现枚举类
 **/
@Slf4j
public enum SerializerEnum implements Serializer {
    // Java的序列化和反序列化实现
    JAVA {
        @Override
        public <T> byte[] serialize(T object) {
            // 定义序列化之后的数组
            byte[] bytes = null;
            // try-with-resource定义字节数组输出流、对象输出流对象
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
                oos.writeObject(object);
                bytes = bos.toByteArray();
            } catch (IOException e) {
                log.error("java serialize error: ", e);
            }
            return bytes;
        }

        @Override
        public <T> T deserialize(Class<T> clazz, byte[] bytes) {
            // 反序列化之后的目标对象
            T target = null;
            try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                 ObjectInputStream ois = new ObjectInputStream(bis)) {
                target = (T) ois.readObject();
            } catch (IOException | ClassNotFoundException e) {
                log.error("java deserialize error: ", e);
            }
            // 返回别序列化的对象
            return target;
        }
    },

    // JSON的序列化方式,此处使用到Gson依赖
    JSON {
        @Override
        public <T> byte[] serialize(T object) {
            String json = new Gson().toJson(object);
            log.info("serialize json of object={}", json);
            // 返回字符数组,并制定字符集
            return json.getBytes(StandardCharsets.UTF_8);
        }

        @Override
        public <T> T deserialize(Class<T> clazz, byte[] bytes) {
            String str = new String(bytes, StandardCharsets.UTF_8);
            log.info("serialize json of object byte={}", str);
            return new Gson().fromJson(str, clazz);
        }
    }
}

Json的序列化方式需要用到谷歌的gson依赖,在pom中添加如下依赖

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>

修改原编解码器

编码

///        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
///        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
///        objectOutputStream.writeObject(msg);
///        byte[] bytes = outputStream.toByteArray();
        // 使用指定的序列化方式
        SerializerEnum[] values = SerializerEnum.values();
        // 获得序列化后的对象
        byte[] bytes = values[out.getByte(5) - 1].serialize(msg);

解码

/// ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
/// Message message = (Message) objectInputStream.readObject();

// 需要通过Message的方法获得具体的消息类型
Message message = SerializerEnum.values()[serializerType-1]
    .deserialize(Message.getMessageClass(messageType), bytes);

2、参数调优

CONNECT_TIMEOUT_MILLIS

  • 属于 SocketChannal 的参数
  • 用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出 timeout 异常
  • 注意:Netty 中不要用成了SO_TIMEOUT 主要用在阻塞 IO,而 Netty 是非阻塞 IO
使用
public class TestParam {
    public static void main(String[] args) {
        // 客户端的参数设置使用 Bootstrap.option()对SocketChannel进行设置,在指定时间内未连接的会抛出connection timed out
        new Bootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);

        /// 服务端设置参数有两个方法,注意区分
        // ServerBootstrap().option()是对serverSocketChannel进行参数设置
        new ServerBootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
        // ServerBootstrap().childOption()是SocketChannel进行参数设置
        new ServerBootstrap().childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
    }
}
  • 客户端通过 Bootstrap.option 函数来配置参数,配置参数作用于 SocketChannel

  • 服务器通过

    ServerBootstrap
    

    来配置参数,但是对于不同的 Channel 需要选择不同的方法

    • 通过 option 来配置 ServerSocketChannel 上的参数
    • 通过 childOption 来配置 SocketChannel 上的参数
      在这里插入图片描述
源码分析

客户端中连接服务器的线程是 NIO 线程,抛出异常的是主线程。这是如何做到超时判断以及线程通信的呢?

AbstractNioChannel.AbstractNioUnsafe.connect方法中

public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    
    ...
        
    // Schedule connect timeout.
    // 设置超时时间,通过option方法传入的CONNECT_TIMEOUT_MILLIS参数进行设置
    int connectTimeoutMillis = config().getConnectTimeoutMillis();
    // 如果超时时间大于0
    if (connectTimeoutMillis > 0) {
        // 创建一个定时任务,延时connectTimeoutMillis(设置的超时时间时间)后执行
        // schedule(Runnable command, long delay, TimeUnit unit)
        connectTimeoutFuture = eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                // 判断是否建立连接,Promise进行NIO线程与主线程之间的通信
                // 如果超时,则通过tryFailure方法将异常放入Promise中
                // 在主线程中抛出
                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                ConnectTimeoutException cause = new ConnectTimeoutException("connection timed out: " + remoteAddress);
                if (connectPromise != null && connectPromise.tryFailure(cause)) {
                    close(voidPromise());
                }
            }
        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
    }
    
   	...
        
}

超时的判断主要是通过 Eventloop 的 schedule 方法和 Promise 共同实现的

  • schedule 设置了一个定时任务,延迟connectTimeoutMillis秒后执行该方法
  • 如果指定时间内没有建立连接,则会执行其中的任务
    • 任务负责创建 ConnectTimeoutException 异常,并将异常通过 Pormise 传给主线程并抛出
    • 若在规定时间内建立链接,则取消定时任务
promise.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (future.isCancelled()) {
            if (connectTimeoutFuture != null) {
                connectTimeoutFuture.cancel(false);
            }
            connectPromise = null;
            close(voidPromise());
        }
    }
});

SO_BACKLOG

该参数是 ServerSocketChannel 的参数

三次握手与连接队列

在这里插入图片描述

  • 第一次握手,client发送SYN包到server,状态改为SYN_SEND,server收到,状态改为SYN_REVD,并将该请求放入到sync queue 队列中;
  • 第二次握手,server恢复SYN + ACK 给client,client收到状态改为ESTABLISHED;
  • 第三次握手,client发送ack给server,server收到ack,状态改为ESTABLISHED,将该请求从sync queue放入 accept queue

在 linux 2.2 之前,backlog 大小包括了两个队列的大小,在 linux 2.2 之后,分别用下面两个参数来控制

  • 半连接队列 - sync queue
    • 大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,在 syncookies 启用的情况下,逻辑上没有最大值限制,这个设置便被忽略
  • 全连接队列 - accept queue
    • 其大小通过 /proc/sys/net/core/somaxconn 指定,在使用 listen 函数时,内核会根据传入的 backlog 参数与系统参数,取二者的较小值
    • 如果 accpet queue 队列满了,server 将发送一个拒绝连接的错误信息到 client
作用

在Netty中,SO_BACKLOG主要用于设置全连接队列的大小。当处理Accept的速率小于连接建立的速率时,全连接队列中堆积的连接数大于SO_BACKLOG设置的值是,便会抛出异常

设置方式如下

// 设置全连接队列,大小为2
new ServerBootstrap().option(ChannelOption.SO_BACKLOG, 2);

使用断点进行调试,断点的位置是NioEventGroup类中的processSelectedKey方法中代码,如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j0a13tmV-1681647162093)(../../plan/202203/pic/image-20230312213748094.png)]

断点挺住位置说明已经建立了链接,即进入了全连接队列中,状态打印输入如下:
在这里插入图片描述

默认值

backlog参数在NioSocketChannel.doBind方法被使用

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

其中backlog被保存在了DefaultServerSocketChannelConfig配置类中

private volatile int backlog = NetUtil.SOMAXCONN;

具体的赋值操作如下(idea中服务代码在右侧显示为红色):

SOMAXCONN = AccessController.doPrivileged(new PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
        // Determine the default somaxconn (server socket backlog) value of the platform.
        // The known defaults:
        // - Windows NT Server 4.0+: 200
        // - Linux and Mac OS X: 128
        int somaxconn = PlatformDependent.isWindows() ? 200 : 128;
        File file = new File("/proc/sys/net/core/somaxconn");
        BufferedReader in = null;
        try {
            // file.exists() may throw a SecurityException if a SecurityManager is used, so execute it in the
            // try / catch block.
            // See https://github.com/netty/netty/issues/4936
            if (file.exists()) {
                in = new BufferedReader(new FileReader(file));
                // 将somaxconn设置为Linux配置文件中设置的值
                somaxconn = Integer.parseInt(in.readLine());
                if (logger.isDebugEnabled()) {
                    logger.debug("{}: {}", file, somaxconn);
                }
            } else {
                ...
            }
            ...
        }  
        // 返回backlog的值
        return somaxconn;
    }
}
  • backlog的值会根据操作系统的不同,来

    选择不同的默认值

    • Windows 200
    • Linux/Mac OS 128
  • 如果配置文件/proc/sys/net/core/somaxconn存在,会读取配置文件中的值,并将backlog的值设置为配置文件中指定的

ulimit -n

限制一个进程可以打开的最大文件数(FD),是操作系统参数。 在高并发情况下,需要调整该参数。

TCP_NODELAY

  • 属于 SocketChannal 参数,使用channel.option进行设置;
  • 因为 Nagle 算法,数据包会堆积到一定的数量后一起发送,这就可能导致数据的发送存在一定的延时
  • 该参数默认为false,如果不希望的发送被延时,则需要将该值设置为true

SO_SNDBUF & SO_RCVBUF

  • SO_SNDBUF 属于 SocketChannal 参数
  • SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)
  • 该参数用于指定接收方与发送方的滑动窗口大小

ALLOCATOR

  • 属于 SocketChannal 参数
  • 用来配置 ByteBuf 是池化还是非池化,是直接内存还是堆内存
使用
// 选择ALLOCATOR参数,设置SocketChannel中分配的ByteBuf类型
// 第二个参数需要传入一个ByteBufAllocator,用于指定生成的 ByteBuf 的类型
new ServerBootstrap().childOption(ChannelOption.ALLOCATOR, new PooledByteBufAllocator());

ByteBufAllocator类型

  • 池化并使用直接内存

    // true表示使用直接内存
    new PooledByteBufAllocator(true);
    
  • 池化并使用堆内存

    // false表示使用堆内存
    new PooledByteBufAllocator(false);
    
  • 非池化并使用直接内存

    // ture表示使用直接内存
    new UnpooledByteBufAllocator(true);
    
  • 非池化并使用堆内存

    // false表示使用堆内存
    new UnpooledByteBufAllocator(false);
    

RCVBUF_ALLOCATOR

  • 属于 SocketChannal 参数
  • 控制 Netty 接收缓冲区大小
  • 负责入站数据的分配,决定入站缓冲区的大小(并可动态调整),统一采用 direct 直接内存,具体池化还是非池化由 allocator 决定

3、RPC框架

准备工作

在聊天室代码的基础上进行一定的改进

Message中添加如下代码

public abstract class Message implements Serializable {

    // 省略了旧的代码

    // 添加RPC消息类型
    public static final int RPC_MESSAGE_TYPE_REQUEST = 101;
    public static final int  RPC_MESSAGE_TYPE_RESPONSE = 102;

    static {
        // 将消息类型放入消息类对象Map中
        messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.class);
        messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.class);
    }

}

RPC请求消息

public class RpcRequestMessage extends Message {
    /**
     * 调用的接口全限定名,服务端根据它找到实现
     */
    private String interfaceName;
    
    /**
     * 调用接口中的方法名
     */
    private String methodName;
    
    /**
     * 方法返回类型
     */
    private Class<?> returnType;
    
    /**
     * 方法参数类型数组
     */
    private Class[] parameterTypes;
    
    /**
     * 方法参数值数组
     */
    private Object[] parameterValue;

    public RpcRequestMessage(int sequenceId, String interfaceName, String methodName, Class<?> returnType, Class[] parameterTypes, Object[] parameterValue) {
        super.setSequenceId(sequenceId);
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.returnType = returnType;
        this.parameterTypes = parameterTypes;
        this.parameterValue = parameterValue;
    }

    @Override
    public int getMessageType() {
        return RPC_MESSAGE_TYPE_REQUEST;
    }
    
     public String getInterfaceName() {
        return interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public Class<?> getReturnType() {
        return returnType;
    }

    public Class[] getParameterTypes() {
        return parameterTypes;
    }

    public Object[] getParameterValue() {
        return parameterValue;
    }
    
    @Override
    public String toString() {
        return "RpcRequestMessage{" +
                "interfaceName='" + interfaceName + '\'' +
                ", methodName='" + methodName + '\'' +
                ", returnType=" + returnType +
                ", parameterTypes=" + Arrays.toString(parameterTypes) +
                ", parameterValue=" + Arrays.toString(parameterValue) +
                '}';
    }
}

想要远程调用一个方法,必须知道以下五个信息

  • 方法所在的全限定类名
  • 方法名
  • 方法返回值类型
  • 方法参数类型
  • 方法参数值

RPC响应消息

public class RpcResponseMessage extends Message {
    /**
     * 返回值
     */
    private Object returnValue;
    /**
     * 异常值
     */
    private Exception exceptionValue;

    @Override
    public int getMessageType() {
        return RPC_MESSAGE_TYPE_RESPONSE;
    }
    
    
    public void setReturnValue(Object returnValue) {
        this.returnValue = returnValue;
    }

    public void setExceptionValue(Exception exceptionValue) {
        this.exceptionValue = exceptionValue;
    }
    
     public Object getReturnValue() {
        return returnValue;
    }

    public Exception getExceptionValue() {
        return exceptionValue;
    }
    
    @Override
    public String toString() {
        return "RpcResponseMessage{" +
                "returnValue=" + returnValue +
                ", exceptionValue=" + exceptionValue +
                '}';
    }
}

响应消息中只需要获取返回结果和异常值

服务器

public class RPCServer {
    public static void main(String[] args) {
        // 连接请求处理
        NioEventLoopGroup boss = new NioEventLoopGroup();
        // 读写事件处理
        NioEventLoopGroup worker = new NioEventLoopGroup();
        // 日志处理的handler
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        // 消息的编解码器
        MessageShareCodec shareCodec = new MessageShareCodec();
        // rpc请求消息处理器
        RpcRequestMessageHandler RPC_REQUEST_HANDLER = new RpcRequestMessageHandler();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss, worker);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProtocolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(shareCodec);
                    ch.pipeline().addLast(RPC_REQUEST_HANDLER);
                }
            });
            Channel channel = serverBootstrap.bind(8089).sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

服务器中添加了处理RPCRequest消息的handler

客户端

public class RPCClient {
    public static void main(String[] args) {
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageShareCodec shareCodec = new MessageShareCodec();
        // RPC响应消息处理器
        RpcResponseMessageHandler RPC_RESPONSE_HANDLER = new RpcResponseMessageHandler();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProtocolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(shareCodec);
                    ch.pipeline().addLast(RPC_RESPONSE_HANDLER);
                }
            });
            Channel channel = bootstrap.connect(new InetSocketAddress("localhost", 8089))
                    .sync().channel();
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

新增业务实现接口HelloService以及具体实现类

/**
 * 业务实现接口
 *
 * @author : ChenJH
 * @date : 2023-03-26 20:10
 */
public interface HelloService {
    /**
     * sayHello
     * @param name 用户名称
     * @return 处理之后结果
     */
    String sayHello(String name);
}
/**
 * 具体业务实现
 *
 * @author : ChenJH
 * @date : 2023-03-26 20:10
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String msg) {
        int i = 1 / 0;
        return "你好, " + msg;
    }
}

在resources路径下,新增配置文件application.properies,添加属性配置如下:

com.panape.server.service.HelloService=com.panape.server.service.HelloServiceImpl

新增一个配置类Config,代码实现如下:

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 配置类
 *
 * @author : ChenJH
 * @date : 2023-03-26 20:27
 **/
public class Config {
    static Properties properties;

    static {
        try (InputStream inputStream = Config.class.getResourceAsStream("/application.properties")) {
            properties = new Properties();
            properties.load(inputStream);
        } catch (IOException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}

通过接口Class获取实例对象的ServicesFactory,代码实现如下:

/**
 * 根据接口class获取实例对象的factory
 *
 * @author : ChenJH
 * @date : 2023-03-26
 **/
public class ServicesFactory {
    static Properties properties;
    static Map<Class<?>, Object> map = new ConcurrentHashMap<>();

    static {
        properties = new Properties();
        try (InputStream resourceAsStream = Config.class.getResourceAsStream("/application.properties")) {
            properties.load(resourceAsStream);
            Set<String> names = properties.stringPropertyNames();
            for (String name : names) {
                if (name.endsWith("Service")) {
                    Class<?> interfaceClass = Class.forName(name);
                    Class<?> instanceClass = Class.forName(properties.getProperty(name));
                    map.put(interfaceClass, instanceClass.newInstance());
                }
            }
        } catch (IOException | IllegalAccessException | InstantiationException | ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 根据接口类型获取具体实现类对象
     *
     * @param interfaceClass 接口类型
     * @param <T>            具体的实现类型
     * @return 返回具体实现的对象
     */
    public static <T> T getService(Class<T> interfaceClass) {
        return (T) map.get(interfaceClass);
    }
}

RpcRequestMessageHandler

import com.panape.message.RpcRequestMessage;
import com.panape.message.RpcResponseMessage;
import com.panape.server.service.HelloService;
import com.panape.server.service.ServicesFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * rpc请求消息处理器
 *
 * @author : ChenJH
 * @date : 2023-03-19
 **/
@ChannelHandler.Sharable
public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage msg) {
        RpcResponseMessage responseMessage = new RpcResponseMessage();
        try {
            // 设置返回值的序列标识
            responseMessage.setSequenceId(msg.getSequenceId());
            // 返回一个实例
            HelloService service = (HelloService) ServicesFactory.getService(Class.forName(msg.getInterfaceName()));
            // 通过反射调用方法,并获取返回值
            Method method = service.getClass().getMethod(msg.getMethodName(), msg.getParameterTypes());
            // 获得返回值
            Object invoke = method.invoke(service, msg.getParameterValue());
            // 设置返回值
            responseMessage.setReturnValue(invoke);
        } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            // 设置异常
            responseMessage.setExceptionValue(new Exception("远程调用出错:" + msg));
        }
        // 向channel中写入Message
        ctx.writeAndFlush(responseMessage);
    }
}

远程调用方法主要是通过反射实现的,大致步骤如下

  • 通过请求消息传入被调入方法的各个参数
  • 通过全限定接口名,在map中查询到对应的类并实例化对象
  • 通过反射获取Method,并调用其invoke方法的返回值,并放入响应消息中
  • 若有异常需要捕获,并放入响应消息中

RpcResponseMessageHandler

import com.panape.message.RpcResponseMessage;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * rpc响应消息处理器
 *
 * @author : ChenJH
 * @date : 2023-03-19
 **/
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {
    private static final Logger log = LoggerFactory.getLogger(RpcResponseMessageHandler.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {
        log.info("响应结果:{}", msg);
        System.out.println((String) msg.getReturnValue());
    }
}

客户端发送消息

public class RPCClient {
    public static void main(String[] args) {
		...
           
        // 创建请求并发送请求响应消息
            channel.writeAndFlush(new RpcRequestMessage(
                    1,
                    "com.panape.server.service.HelloService",
                    "sayHello",
                    String.class,
                    new Class[]{String.class},
                    new Object[]{"张三"}
            )).addListener(promise -> {
                // 监听自身与服务端连接是否正常
                if (!promise.isSuccess()) {
                    log.info("远程链接异常:", promise.cause());
                }
            });  
            
        ...    
    }
}

运行结果

客户端

[nioEventLoopGroup-2-1] INFO  c.panape.protocol.MessageShareCodec - request message=RpcResponseMessage(super=Message(sequenceId=1, messageType=102), returnValue=你好, 张三, exceptionValue=null)
[nioEventLoopGroup-2-1] INFO  c.p.s.h.RpcResponseMessageHandler - 响应结果:RpcResponseMessage(super=Message(sequenceId=1, messageType=102), returnValue=你好, 张三, exceptionValue=null)
你好, 张三

改进客户端

目前客户端发送消息将消息类型,以及调用的方法名、参数、参数类型固定了,因此对以上两点进行调整。将之前的客户端代码复制粘贴一份,删除消息发送代码,并进行调整。

获得Channel

  • 建立连接,获取Channel的操作被封装到了init方法中,当连接断开时,通过addListener法异步关闭group
  • 通过单例模式创建与获取Channel
import com.panape.protocol.MessageShareCodec;
import com.panape.protocol.ProtocolFrameDecoder;
import com.panape.server.handler.RpcResponseMessageHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;

@Slf4j
public class RpcClientManager {
    private static Channel channel = null;
    private static final Object LOCK = new Object();

    public static Channel getChannel() {
        if (null != channel) {
            return channel;
        }
        synchronized (LOCK) {
            if (null != channel) {
                return channel;
            }
            initChannel();
        }
        return channel;
    }

    /**
     * 初始化channel
     */
    private static void initChannel() {
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageShareCodec shareCodec = new MessageShareCodec();
        // RPC响应消息处理器
        RpcResponseMessageHandler RPC_RESPONSE_HANDLER = new RpcResponseMessageHandler();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ProtocolFrameDecoder());
                ch.pipeline().addLast(LOGGING_HANDLER);
                ch.pipeline().addLast(shareCodec);
                ch.pipeline().addLast(RPC_RESPONSE_HANDLER);
            }
        });
        try {
            channel = bootstrap.connect(new InetSocketAddress("localhost", 8089))
                    .sync().channel();
            channel.closeFuture().addListener(future -> eventLoopGroup.shutdownGracefully());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

上述改造只是将channel进行了抽离,但是发送的内容还是固定。使用代码模式,处理请求消息的发送

远程调用方法

  • 为了让方法的调用变得简洁明了,将RpcRequestMessage创建与发送过程通过JDK的动态代理来完成
  • 通过返回的代理对象调用方法即可,方法参数为被调用方法接口的Class类
	/**
     * 通过代理模式,发送请求消息
     * @param serviceClass 代理的接口
     * @param <T> 代理类型
     * @return 代理对象
     */
    public static <T> T getService(Class<T> serviceClass) {
        // 获取serviceClass的类加载器
        ClassLoader classLoader = serviceClass.getClassLoader();
        // 获取serviceClass的实现接口
        Class<?>[] interfaces = serviceClass.getInterfaces();
        // 创建增强的对象
        Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 封装rpc请求消息对象RpcRequestMessage
                RpcRequestMessage rpcRequestMessage = new RpcRequestMessage(SequenceIdGenerator.nextId(),
                        serviceClass.getName(),
                        method.getName(),
                        method.getReturnType(),
                        method.getParameterTypes(),
                        args);
                // 发送消息
                getChannel().writeAndFlush(rpcRequestMessage);
                return null;
            }
        });
        return (T) proxy;
    }

远程调用方法返回值获取

  • 调用方法的是主线程,处理返回结果的是NIO线程(RpcResponseMessageHandler)。要在不同线程中进行返回值的传递,需要用到Promise

  • RpcResponseMessageHandler中创建一个Map

    • Key为SequenceId
    • Value为对应的Promise
  • 主线程的代理类将RpcResponseMessage发送给服务器后,需要创建Promise对象,并将其放入到RpcResponseMessageHandler的Map中。需要使用await等待结果被放入Promise中。获取结果后,根据结果类型(判断是否成功)来返回结果或抛出异常

    // 创建Promise,用于获取NIO线程中的返回结果,获取的过程是异步的
    DefaultPromise<Object> promise = new DefaultPromise<>(getChannel().eventLoop());
    // 将Promise放入Map中
    RpcResponseMessageHandler.promiseMap.put(id, promise);
    // 等待被放入Promise中结果
    promise.await();
    if (promise.isSuccess()) {
        // 调用方法成功,返回方法执行结果
        return promise.getNow();
    } else {
        // 调用方法失败,抛出异常
        throw new RuntimeException(promise.cause());
    }
    

    整体改造之后的代码如下:

    import com.panape.message.RpcRequestMessage;
    import com.panape.protocol.MessageShareCodec;
    import com.panape.protocol.ProtocolFrameDecoder;
    import com.panape.protocol.SequenceIdGenerator;
    import com.panape.server.handler.RpcResponseMessageHandler;
    import com.panape.server.service.HelloService;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import io.netty.util.concurrent.DefaultPromise;
    import lombok.extern.slf4j.Slf4j;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.net.InetSocketAddress;
    
    /**
     * rpc客户端
     **/
    @Slf4j
    public class RpcClientManager {
        private static Channel channel = null;
        private static final Object LOCK = new Object();
    
        public static void main(String[] args) {
            HelloService service = getService(HelloService.class);
            log.info("result1={}",service.sayHello("zhangsan"));
            log.info("result2={}",service.sayHello("lisi"));
        }
    
        public static Channel getChannel() {
            if (null != channel) {
                return channel;
            }
            synchronized (LOCK) {
                if (null != channel) {
                    return channel;
                }
                initChannel();
            }
            return channel;
        }
    
        /**
         * 通过代理模式,发送请求消息
         * @param serviceClass 代理的接口
         * @param <T> 代理类型
         * @return 代理对象
         */
        public static <T> T getService(Class<T> serviceClass) {
            // 获取serviceClass的类加载器
            ClassLoader classLoader = serviceClass.getClassLoader();
            // 获取serviceClass的实现接口
            Class<?>[] interfaces = {serviceClass};
            // 创建增强的对象
            Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 封装rpc请求消息对象RpcRequestMessage
                    int nextId = SequenceIdGenerator.nextId();
                    RpcRequestMessage rpcRequestMessage = new RpcRequestMessage(nextId,
                            serviceClass.getName(),
                            method.getName(),
                            method.getReturnType(),
                            method.getParameterTypes(),
                            args);
                    // 发送消息
                    getChannel().writeAndFlush(rpcRequestMessage);
                    // 创建Promise,用于获取NIO线程中的返回结果,获取的过程是异步的,参数为channel的eventLoop
                    DefaultPromise<Object> defaultPromise = new DefaultPromise<>(getChannel().eventLoop());
                    // 将Promise放入Map中
                    RpcResponseMessageHandler.PROMISES.put(nextId, defaultPromise);
                    // 等待被放入Promise中结果
                    defaultPromise.await();
                    if (defaultPromise.isSuccess()) {
                        // 调用方法成功,返回方法执行结果
                        return defaultPromise.getNow();
                    } else {
                        // 调用方法失败,抛出异常
                        throw new RuntimeException(defaultPromise.cause());
                    }
                }
            });
            return (T) proxy;
        }
    
        /**
         * 初始化channel
         */
        private static void initChannel() {
            NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
            MessageShareCodec shareCodec = new MessageShareCodec();
            // RPC响应消息处理器
            RpcResponseMessageHandler RPC_RESPONSE_HANDLER = new RpcResponseMessageHandler();
    
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProtocolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(shareCodec);
                    ch.pipeline().addLast(RPC_RESPONSE_HANDLER);
                }
            });
            try {
                channel = bootstrap.connect(new InetSocketAddress("localhost", 8089))
                        .sync().channel();
                channel.closeFuture().addListener(future -> eventLoopGroup.shutdownGracefully());
            } catch (InterruptedException e) {
                log.error("client error", e);
            }
        }
    }
    
  • NIO线程负责通过SequenceId**获取并移除(remove)**对应的Promise,然后根据RpcResponseMessage中的结果,向Promise中放入不同的值

    • 如果没有异常信息(ExceptionValue),就调用promise.setSuccess(returnValue)放入方法返回值
    • 如果有异常信息,就调用promise.setFailure(exception)放入异常信息
    	    // 将返回的结果放入对应的promise,并将集合中的promise移除
            Promise<Object> promise = PROMISES.remove(msg.getSequenceId());
            if (promise != null) {
                Exception exceptionValue = msg.getExceptionValue();
                Object returnValue = msg.getReturnValue();
                if (null != exceptionValue) {
                    promise.setFailure(exceptionValue);
                } else {
                    promise.setSuccess(returnValue);
                }
            }
    

改进RpcResponseMessageHandler

import com.panape.message.RpcResponseMessage;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * rpc响应消息处理器
 **/
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {
    private static final Logger log = LoggerFactory.getLogger(RpcResponseMessageHandler.class);

    /**
     * 用于存放Promise的集合,Promise用于主线程与NIO线程之间进行通信
     */
    public static Map<Integer, Promise<Object>> PROMISES = new ConcurrentHashMap<>();


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {
        // 将返回的结果放入对应的promise,并将集合中的promise移除
        Promise<Object> promise = PROMISES.remove(msg.getSequenceId());
        if (promise != null) {
            Exception exceptionValue = msg.getExceptionValue();
            Object returnValue = msg.getReturnValue();
            if (null != exceptionValue) {
                promise.setFailure(exceptionValue);
            } else {
                promise.setSuccess(returnValue);
            }
        }
        log.info("响应结果:{}", msg);
    }
}

以上就是netty知识点运用相关的调优,希望对大家能帮助到看到文章的你,如有不正确,恳请指出来,不断地改进完善!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值