Netty @Sharable的使用

一、@Sharable的真实含义

  1. 当netty尝试往多个channel的pipeline中添加同一个ChannelHandlerAdapter实例时,会判断该实例类是否添加了@Sharable,没有则抛出… is not a @Sharable handler, so can’t be added or removed multiple times异常
  1. 如果你添加的不是单例Handler,你加不加@Sharable没有任何区别

  2. 如果你添加的是单例Handler,只要它会被添加到多个channel的pipeline,那就必须加上@Sharable

  1. 网上有些资料说netty会将注解了@Sharable的Handler单例化,实在误导人。channelHandler是不是单例跟netty没有任何关系,netty只会在你尝试用单例ChannelHandler时加上第1条说明的限制,这个限制意思很明确,多个channel公用单例ChannelHandler,那它就必须是线程安全的,@Sharable就是用于告诉netty,我这个Handler是线程安全的,可以被多个channel安全的share,假如你搞了个线程不安全的类,你对它用上了单例,还加个@Sharable,这时候netty拿你也没办法,只是你的老板可能会有点不开心
  • 一般情况下,我们的代码是这样:
bootstrap.childHandler(new ChannelInitializer<Channel>() {
      @Override
      protected void initChannel(Channel ch) throws Exception {
            //每次都是new一个新对象,这时候你 ShareTestChannelHandler这个类加不加@Sharable没有任何区别
            ch.pipeline().addLast(new ShareTestChannelHandler());
      };
});

在initChannel方法中,我们addLast传入的Handler实例,他是不是单例的与netty没有任何关系,和你是否注解@Sharable也没有任何关系。就拿上面的代码说,它就不是单例的,每次都是new一个新对象,这时候你ShareTestChannelHandler这个类加不加@Sharable没有任何区别。

  • 如下代码
bootstrap.childHandler(new ChannelInitializer<Channel>() {
      @Override
      protected void initChannel(Channel ch) throws Exception {
            //每次传入的都是单例的Handler对象,只要它会被添加到多个channel的pipeline链中,那它就必须是@Sharable的,不然就抛出第1点说的异常
            ch.pipeline().addLast(new ShareTestChannelHandler());
      };
});


@Sharable
public class ShareTestChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
      public static ShareTestChannelHandler INSTANCE = new ShareTestChannelHandler();
      private ShareTestChannelHandler() {}
      ......
}

如果,你传入一个单例的Handler对象,只要它会被添加到多个channel的pipeline链中,那它就必须是@Sharable的,不然就抛出第1点说的异常

二、是不是所有Handler都可以加上@Sharable

  1. 不是,netty中,解码器有关的Handler都是不安全的,因为粘包拆包的缘故,Decoder必须要保存一些解析过程的中间状态,比如ByteToMessageDecoder类中维护的一个字节累加器cumulation,每次读到当前channel的消息后都会将消息累加到cumulation中,然后再调用子类实现的decode方法。所以它不能被多个channel安全的共享,netty明确的禁止了使用单例Decoder,即使你自作聪明的给他加上@Sharable,也会被禁止掉,抛出... is not allowed to be shared错误
  2. 所以我们在添加Decoder时:
  1. 必须不是单例
  2. 不要添加@Sharable
  1. 例子
@Slf4j
//这里添加了@Sharable,肯定会报错
@ChannelHandler.Sharable
public class MessageCodec extends ByteToMessageCodec<Message> {
    @Override
    public 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 字节的序列化方式 jdk 0 , json 1
        out.writeByte(0);
        // 4. 1 字节的指令类型
        out.writeByte(msg.getMessageType());
        // 5. 4 个字节
        out.writeInt(msg.getSequenceId());
        // 无意义,对齐填充,让固定字节数为2的n次方倍
        out.writeByte(0xff);
        // 6. 获取内容的字节数组(序列化)
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(msg);
        byte[] bytes = bos.toByteArray();
        // 7. 长度
        out.writeInt(bytes.length);
        // 8. 写入内容
        out.writeBytes(bytes);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int magicNum = in.readInt();
        byte version = in.readByte();
        byte serializerType = in.readByte();
        byte messageType = in.readByte();
        int sequenceId = in.readInt();
        in.readByte();
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Message message = (Message) ois.readObject();
        log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
        log.debug("{}", message);
        //可能解析出多条信息,加到list里面,给下一个handler处理
        out.add(message);
    }
}

在这里插入图片描述

但是,如果我们明确知道MessageCodec是线程安全的),要使用@Sharable,就不要继承ByteToMessageCodec了,可以通过继承 MessageToMessageCodec来实现。

@Slf4j
@ChannelHandler.Sharable
/**
 * 必须和 LengthFieldBasedFrameDecoder 一起使用,以确保接到的 ByteBuf 消息是完整的,防止没接收到完整信息,又切换到其他channel使用,导致线程不安全
 */
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {
    @Override
    public void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
        ByteBuf out = ctx.alloc().buffer();
        // 1. 4 字节的魔数
        out.writeBytes(new byte[]{1, 2, 3, 4});
        // 2. 1 字节的版本,
        out.writeByte(1);
        // 3. 1 字节的序列化方式 jdk 0 , json 1
        out.writeByte(Config.getSerializerAlgorithm().ordinal());
        // 4. 1 字节的指令类型
        out.writeByte(msg.getMessageType());
        // 5. 4 个字节
        out.writeInt(msg.getSequenceId());
        // 无意义,对齐填充
        out.writeByte(0xff);
        // 6. 获取内容的字节数组
        byte[] bytes = Config.getSerializerAlgorithm().serialize(msg);
        // 7. 长度
        out.writeInt(bytes.length);
        // 8. 写入内容
        out.writeBytes(bytes);
        outList.add(out);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int magicNum = in.readInt();
        byte version = in.readByte();
        byte serializerAlgorithm = in.readByte(); // 0 或 1
        byte messageType = in.readByte(); // 0,1,2...
        int sequenceId = in.readInt();
        in.readByte();
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);

        // 找到反序列化算法
        Serializer.Algorithm algorithm = Serializer.Algorithm.values()[serializerAlgorithm];
        // 确定具体消息类型
        Class<? extends Message> messageClass = Message.getMessageClass(messageType);
        Message message = algorithm.deserialize(messageClass, bytes);
        out.add(message);
    }
}

三、总结

加了@Sharable的类,netty就认为是线程安全,可以共享,而只要netty明确的禁止了使用单例Handler,即使你给他加上@Sharable,也会被禁止掉。同时,如果一个类是单例的Handler对象,同时他会被添加到多个channel的pipeline链中,那它就必须是@Sharable的,不然也会报错。

  • handler 不保存状态时,就可以安全地在多线程下被共享
  • 但要注意对于编解码器类,不能继承 ByteToMessageCodec 或 CombinedChannelDuplexHandler 父类,他们的构造方法对 @Sharable 有限制
  • 如果能确保编解码器不会保存状态,可以继承 MessageToMessageCodec 父类

参考视频
参考文章
参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值