【netty】- 优化

聊天室优化——扩展序列化算法

写一个枚举类来管理不同的序列化和反序列化方式

public interface Serializer {
    /**
     * 【反序列化方法】
     * 将字节数组还原为java对象
     * @param clazz:需要还原为java中什么类型的对象
     * @param bytes
     * @return
     * @param <T>
     */
    <T> T deserialize(Class<T> clazz, byte[] bytes);

    /**
     * 【序列化方法】
     * 将object对象转为字节数组
     * @param object
     * @return
     * @param <T>
     */
    <T> byte[] serialize(T object) throws IOException;

    // 使用内部枚举:管理序列化算法
    enum Algorithm implements Serializer {
        Java { // java自带的序列化方式
            @Override
            public <T> T deserialize(Class<T> clazz, byte[] bytes) {
                try {
                    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
                    T msg = (T) ois.readObject(); // 因为序列化的时候是把msg对象进行序列化,所以反序列化也是对msg对象进行操作
                    return msg;
                }catch (Exception e) {
                    throw new RuntimeException("反序列化失败", e);
                }
            }

            @Override
            public <T> byte[] serialize(T object) {
                try {
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(bos);
                    oos.writeObject(object);
                    byte[] bytes = bos.toByteArray();
                    return bytes;
                }catch (IOException e) {
                    throw new RuntimeException("序列化失败", e);
                }
            }
        },
        Json { // google的序列化方式(gson)
            @Override
            public <T> T deserialize(Class<T> clazz, byte[] bytes) {
                String json = new String(bytes, StandardCharsets.UTF_8);
                T msg = new Gson().fromJson(json, clazz);
                return msg;
            }

            @Override
            public <T> byte[] serialize(T object) throws IOException {
                String json = new Gson().toJson(object);
                return json.getBytes(StandardCharsets.UTF_8);
            }
        }
    }
}

自定义编码、解码器最终如下:

@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {
    /**
     * 编码器,最好固定字符要是2的整数倍
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        // 1. 魔数:4字节的魔数
        out.writeBytes(new byte[]{1, 2, 3, 4});
        // 2. 版本:1字节
        out.writeByte(1);
        /*
            3. 序列化算法:1字节
            0 - jdk
            1 - json
         */
        out.writeByte(Config.getSerializerAlgorithm().ordinal()); // 序列化方式(从配置中读取)
        // 4. 指令类型:1字节
        out.writeByte(msg.getMessageType());
        // 5. 请求序号:4字节
        out.writeInt(msg.getSequenceId());
        /*
            6. 目前固定字符是15字节(4 + 1 + 1 + 1 + 4 + 4)
               固定字符最好要是2的整数倍
               所以添加一个无意义的字节
         */
        out.writeByte(0xff);
        // 7. 消息正文
        byte[] bytes = Config.getSerializerAlgorithm().serialize(msg);
        // 8. 正文长度:4字节
        out.writeInt(bytes.length);
        // 9. 写入内容
        out.writeBytes(bytes);
    }

    /**
     * 解码
     * @param ctx
     * @param in
     * @param out
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        /*
            readXxx():让读指针向后走
            getXxx():只读,不会改变读指针
         */
        // 1. 魔数
        int magicNum = in.readInt();
        // 2. 版本号
        byte version = in.readByte();
        // 3. 序列化方式
        byte serializerType = in.readByte(); // 0 - java;1 - json
        // 4. 指令类型
        byte messageType = in.readByte();
        // 5. 请求序号
        int sequenceId = in.readInt();
        // 6. 无意义的字符(只用于填充)
        in.readByte();
        // 7. 正文长度
        int length = in.readInt();
        // 8. 正文内容
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);

        // 找到反序列化算法
        Serializer.Algorithm algorithm = Serializer.Algorithm.values()[serializerType];
        // 确定具体消息类型
        Class<? extends Message> messageClazz = Message.getMessageClass(messageType);
        // 反序列化
        Message msg = algorithm.deserialize(messageClazz, bytes);
        log.debug("magicNum:{}, version:{}, serializerType:{}, messageType:{}, sequenceId:{}", magicNum, version, serializerType, messageType, sequenceId);
        log.debug("msg:{}", msg);
        /*
            !netty规定!:解码出来的消息必须存入out这个形参中,不然接下来的handler就无法获得消息
         */
        out.add(msg);
    }
}

其中:
序列化方式是从配置文件中读取,读取后通过枚举的对象转换为byte

参数调优

CONNECT_TIMEOUT_MILLIS

SocketChannel参数,在客户端建立连接时,如果在指定毫秒内无法连接,就会抛出timeout异常。

  1. 客户端:通过.option()方法配置参数
  2. 服务端:
    • 给ServerSocketChannel配置参数:new ServerBootstrap().option()——影响服务端自身的监听行为(如绑定端口的超时)
    • 给SocketChannel配置参数:new ServerBootstrap().childOption()——影响服务端接受的客户端连接的参数

客户端:

@Slf4j
public class Test01ConnectTimeout {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 5s内没有和服务器建立连接就会出现超时异常
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new LoggingHandler());
            ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
            future.sync().channel().closeFuture().sync();
        }catch (Exception e) {
            e.printStackTrace();
            log.debug("timeout");
        }
    }
}

SO_BACKLOG

属于ServerSocketChannel参数
在这里插入图片描述
还没有完成三次握手:半连接队列
已经完成三次握手:全连接队列

因为服务器的处理能力是有限的,所以需要把之前已经三次握手的连接信息放入连接队列里。
注意,会先放入全连接队列,再调用accept()
只有accept()处理不了时,才会把连接信息放入全连接队列中堆积

Netty应用:RPC框架

客户端与服务器之间的RPC通信

服务器

@Slf4j
public class RpcServer {
    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        RpcRequestMessageHandler RPC_HANDLER = new RpcRequestMessageHandler(); // rpc请求消息处理器
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProtocolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC);
                    ch.pipeline().addLast(RPC_HANDLER);
                }
            });
            Channel channel = serverBootstrap.bind(8080).sync().channel();
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

rpc请求消息处理器:

public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage msg) {
        RpcResponseMessage response = new RpcResponseMessage();
        try {
            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());
            response.setReturnValue(invoke); // 设置正常信息
        }catch (Exception e) {
            e.printStackTrace();
            response.setExceptionValue(e); // 设置异常信息
        }
        ctx.writeAndFlush(response);
    }
	
	// 测试代码
    public static void main(String[] args) throws Exception {
        RpcRequestMessage message = new RpcRequestMessage(
                1,
                "com.itheima.server.service.HelloService",
                "sayHello",
                String.class,
                new Class[]{String.class},
                new Object[]{"xiaolin"}
        );
        HelloService service = (HelloService) ServicesFactory.getService(Class.forName(message.getInterfaceName()));
        Method sayHello = service.getClass().getMethod(message.getMethodName(), message.getParameterTypes());
        Object invoke = sayHello.invoke(service, message.getParameterValue());
        System.out.println(invoke);
    }
}

通过反射,远程调用HelloService里的方法

GSON类型不支持问题

public class Test03Gson {
    public static void main(String[] args) {
        System.out.println(new Gson().toJson(String.class));
    }
}

代码报错】:Exception in thread “main” java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: java.lang.String. Forgot to register a type adapter?

原因】:缺少了一个类型转换器,需要自己补一个

修改后代码如下:

public class Test03Gson {
    public static void main(String[] args) {
        Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create();
        System.out.println(gson.toJson(String.class)); // "java.lang.String"
    }
    static class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
        @Override
        public Class<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
            String str = json.getAsString();
            try {
                return Class.forName(str);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public JsonElement serialize(Class<?> src, Type type, JsonSerializationContext context) { // class -> json
            return new JsonPrimitive(src.getName());
        }
    }
}

序列化与反序列化算法:

public interface Serializer {
    /**
     * 【反序列化方法】
     * 将字节数组还原为java对象
     * @param clazz:需要还原为java中什么类型的对象
     * @param bytes
     * @return
     * @param <T>
     */
    <T> T deserialize(Class<T> clazz, byte[] bytes);

    /**
     * 【序列化方法】
     * 将object对象转为字节数组
     * @param object
     * @return
     * @param <T>
     */
    <T> byte[] serialize(T object) throws IOException;

    // 使用内部枚举:管理序列化算法
    enum Algorithm implements Serializer {
        Java { // java自带的序列化方式
            @Override
            public <T> T deserialize(Class<T> clazz, byte[] bytes) {
                try {
                    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
                    T msg = (T) ois.readObject(); // 因为序列化的时候是把msg对象进行序列化,所以反序列化也是对msg对象进行操作
                    return msg;
                }catch (Exception e) {
                    throw new RuntimeException("反序列化失败", e);
                }
            }

            @Override
            public <T> byte[] serialize(T object) {
                try {
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(bos);
                    oos.writeObject(object);
                    byte[] bytes = bos.toByteArray();
                    return bytes;
                }catch (IOException e) {
                    throw new RuntimeException("序列化失败", e);
                }
            }
        },
        Json { // google的序列化方式(gson)
            @Override
            public <T> T deserialize(Class<T> clazz, byte[] bytes) {
                String json = new String(bytes, StandardCharsets.UTF_8);
                Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create(); // 定义一个带转换器的gson
                T msg = gson.fromJson(json, clazz);
                return msg;
            }

            @Override
            public <T> byte[] serialize(T object) throws IOException {
                Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create(); // 定义一个带转换器的gson
                String json = gson.toJson(object);
                return json.getBytes(StandardCharsets.UTF_8);
            }
        }
    }

    class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
        @Override
        public Class<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
            String str = json.getAsString();
            try {
                return Class.forName(str);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public JsonElement serialize(Class<?> src, Type type, JsonSerializationContext context) { // class -> json
            return new JsonPrimitive(src.getName());
        }
    }
}

客户端

@Slf4j
public class RpcClient {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(group);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProtocolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC);
                    ch.pipeline().addLast(RPC_HANDLER);
                }
            });
            Channel channel = bootstrap.connect("localhost", 8080).sync().channel();

            // 发送请求消息
            ChannelFuture future = channel.writeAndFlush(new RpcRequestMessage(
                    1,
                    "com.itheima.server.service.HelloService",
                    "sayHello",
                    String.class,
                    new Class[]{String.class},
                    new Object[]{"xiaolin"}
            )).addListener(promise -> { // 调试
                if(!promise.isSuccess()) { // 失败 - 打印信息
                    Throwable cause = promise.cause();
                    log.error("error", cause);
                }
            });

            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("client error", e);
        } finally {
            group.shutdownGracefully();
        }
    }
}

获取channel(channel是单例的)

因为RPC客户端需要与服务器保持长连接,而不是每次请求都建立新的连接,所以这里Client创建与服务器之间的channel通道时,只要是单例的即可。

@Slf4j
public class RpcClientManager {
    private static Channel channel = null;
    private static final Object LOCK = new Object();
    // 获取唯一的channel对象
    public static Channel getChannel() {
        if(channel != null) {
            return channel;
        }
        // 单例模式 - 双重检查锁
        synchronized(LOCK) { // 如果同时有两个线程到了这里,只会有一个线程获取锁,此时就需要再里边在进行一次判空
            if(channel != null) {
                return channel;
            }
            initChannel(); // 初始化channel
            return channel;
        }
    }

    /*初始化channel*/
    private static void initChannel() {
        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.group(group);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ProtocolFrameDecoder());
                ch.pipeline().addLast(LOGGING_HANDLER);
                ch.pipeline().addLast(MESSAGE_CODEC);
                ch.pipeline().addLast(RPC_HANDLER);
            }
        });
        try {
            channel = bootstrap.connect("localhost", 8080).sync().channel();
            channel.closeFuture().addListener(future -> { // channel关闭时触发
                group.shutdownGracefully();
            });
        } catch (Exception e) {
            log.error("client error", e);
        }
    }
}

调用代码如下:

public static void main(String[] args) {
    RpcRequestMessage message = new RpcRequestMessage(
            1,
            "com.itheima.server.service.HelloService",
            "sayHello",
            String.class,
            new Class[]{String.class},
            new Object[]{"xiaolin"}
    );
    getChannel().writeAndFlush(message);
}

问题】上边这种调用方式并不直观,对于我们来说,可能更想通过helloService.sayHello("xiaolin");这个方法来调用。
解决】对于上边的问题,可以考虑用代理来解决

代理

public static void main(String[] args) {
    HelloService proxyService = getProxyService(HelloService.class);
    System.out.println(proxyService.sayHello("xiaolin"));
}
/*创建代理类*/
public static<T> T getProxyService(Class<T> serviceClass) {
    ClassLoader loader = serviceClass.getClassLoader(); // 类加载器
    Class<?>[] interfaces = new Class[]{serviceClass};
    int sequecedId = SequenceIdGenerator.nextId();
    Object o = Proxy.newProxyInstance(loader, interfaces, (proxy, method, args) -> {
        // 1. 将方法调用转换为消息对象
        RpcRequestMessage message = new RpcRequestMessage(
                sequecedId,
                serviceClass.getName(), // 接口全类名
                method.getName(), // 方法类名
                method.getReturnType(), // 方法返回值类型
                method.getParameterTypes(), // 方法参数类型
                args // 实际参数值
        );
        // 2. 发送消息对象
        getChannel().writeAndFlush(message);
        // 3. 准备一个空的Promise对象,用来异步接收结果
        DefaultPromise<Object> promise = new DefaultPromise<>(getChannel().eventLoop());
        RpcResponseMessageHandler.PROMISES.put(sequecedId, promise);
        // 4. 等待promise的结果
        promise.await();
        if(promise.isSuccess()) {
            // 调用正常
            return promise.getNow();
        }else {
            // 调用失败
            throw new RuntimeException(promise.cause());
        }
    });
    return (T)o;
}

把结果存放到Promise中:

@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {
    public static final Map<Integer, Promise<Object>> PROMISES = new ConcurrentHashMap<>(); // 序号 - 用来接受结果的Promise对象
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {
        // 拿到空的promise
        Promise<Object> promise = PROMISES.remove(msg.getSequenceId()); // 返回对应的value值,并且从集合中移除
        if(promise != null) {
            Object returnValue = msg.getReturnValue();
            Exception exceptionValue = msg.getExceptionValue();
            // 填充结果
            if(exceptionValue != null) {
                promise.setFailure(exceptionValue);
            }else {
                promise.setSuccess(returnValue);
            }
        }
        log.debug("{}", msg);
    }
}

用异步的网络调用来存放结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值