该文为学习 Java 高并发的第四篇文章,主要内容还是基于读书笔记的整理。

title1 title2
content1 content2

流水线的出站流水线怎么截断呢,实际 Netty 的处理是,只要是出站的就不允许截断,强行截断的话 Netty 会抛出异常。这里可能是为了限制不能随便发射,没有反悔的后悔药。

流水线只初始化一次

一条通道流水线只需要做一次初始化。

ByteBuf 分为浅拷贝和深拷贝,浅拷贝实际上就是引用在复用,但是不能修改里面的内容,而深拷贝的可以把 ByteBuf 里面的 Byte 数据一同复制了,这样如果有对数据的操作也不会影响原来的数据,也是允许对新的 ByteBuf 的操作,毕竟都已经是全新的了。

在发送 ByteBuffer 到 Socket 的时候尽量使用直接内存,这样会少一些内存在内核空间和用户空间的拷贝,同时不受限不会引起 OOM。

Java 不能直接访问直接内存的数据,只能通过 get / read 去把数据同步到数组中才行。

避免创建大量 Handler

一个服务器处理数十万的通道,如果每次都是 new 很多的 Handler 实例,那么就会浪费很多的空间,同时也可能引起内存抖动。所以如果 Handler 实例中没有与特定的通道强关联,那么就应该设计成共享模式,同样滴,我们自己在设计代码的时候也要考虑避免大量朝生夕死的对象的频繁大量创建,使用享元模式或者静态函数去替代。

框架的作用

作为解码器的父类, ByteToMessageDecoder 仅仅提供了一个整体框架:它会调度子类的 decode() 方法,完成解码工作。

设置这种类似的框架可以很好的统筹工作的进行。

在解码器中可以设置对应的 Status 进行分阶段去解码,完成自定义的状态或者数据的解析,这个看起来就是一个状态机模式,只有完成了上面的阶段才能进行下面的继续解码。

取数据的时候需要 markReaderIndex 去重置位置。

netty 代码里面也是大量的使用了组合去替代继承这种模式。

序列化相关

评估序列化的几个维度:

  1. 结果数据大小
  2. 结构复杂度

在这方面 protoBuf 几乎做到了最好,使用他的协议它会动过注解或者一些 task 去给生成对应的目标类,这样用起来也没有那么麻烦,同时因为有了差分控制结果数据会更小。这个就是一个典范,不仅牛逼,还好用。

网络数据的半包、粘包: 网络数据在传输中会有半包、粘包问题,这里的问题核心是因为数据在 socket 中传输的时候是二进制数据流传输的,如果一个包不足以撑满一个 网络 frame 那么就会产生拿到部分数据的问题,同时粘包的也是类似的问题,就是在解析的时候把别的数据搞过来了,那么也会有问题,就是超过了所需的数据。

直接解决这个问题就是声明数据流的长度,在达到之后在进行处理,下一个包的数据给下一个任务处理,这些都是在应用层去处理。

心跳机制

虽然 TCP 给我们提供了 Keep-live 的标记位,但是因为时间太长了,所以我们需要自己轮询去判断长连接是否正常存活。 websocket 实际上是协议内部通过不断的 ping pong 去保证的长连接有效性,即使这样,在使用 websocket 的时候仍然需要去保证他的连接有效性。

反向代理: 所谓的反向代理,实际上是请求分发的过程。

Http 1.1 默认开启长连接,所谓的长连接也只是在应用完成之后不去立刻销毁这样,socket 资源没有释放,这样再用的时候可以避免 tcp 的 三次握手和四次挥手,节省了这部分时间。

大量的短连接会产生大量处于 TIME_WAIT 的连接,这样的连接同样消耗资源且浪费 fd 数目,因为这些占用数目,那么再有新的请求时就可能无法服务。

为了处理这种问题就是 Http 使用长连接,不立刻关闭连接。

集群

集群脑裂,说的是一个集群变成两个集群服务了。

羊群 / 惊群 效应:主要说的是一个锁释放后,有大量的线程在进行无畏的争抢。

总结

读书笔记到这里就算完了,毕竟不是专业的服务器选手,对这些很多概念有个初步了解就可以了,只有在真的需要的时候再去实操可能更好,或者有时间再去研究这些,去上手实践,例如搭建 Zookeeper 集群,使用 redis 作为分布式锁的实现,等等。