怎么做外贸网站优化,中国工程建设管理协会网站,开发微信哪家好,常州建设银行网站通信框架功能设计
功能描述 通信框架承载了业务内部各模块之间的消息交互和服务调用#xff0c;它的主要功能如下#xff1a; 基于 Netty 的 NIO 通信框架#xff0c;提供高性能的异步通信能力#xff1b; 提供消息的编解码框架#xff0c;可以实现 POJO 的序列化和反…通信框架功能设计
功能描述 通信框架承载了业务内部各模块之间的消息交互和服务调用它的主要功能如下 基于 Netty 的 NIO 通信框架提供高性能的异步通信能力 提供消息的编解码框架可以实现 POJO 的序列化和反序列化 消息内容的防篡改机制 提供基于 IP 地址的白名单接入认证机制 链路的有效性校验机制 链路的断连重连机制 通信模型 1客户端发送应用握手请求消息携带节点 ID 等有效身份认证信息 2服务端对应用握手请求消息进行合法性校验包括节点 ID 有效性校验、节点重复 登录校验和 IP 地址合法性校验校验通过后返回登录成功的应用握手应答消息 3链路建立成功之后客户端发送业务消息 4链路成功之后服务端发送心跳消息 5链路建立成功之后客户端发送心跳消息 6链路建立成功之后服务端发送业务消息 7服务端退出时服务端关闭连接客户端感知对方关闭连接后被动关闭客户端 连接。 备注需要指出的是协议通信双方链路建立成功之后双方可以进行全双工通信无 论客户端还是服务端都可以主动发送请求消息给对方通信方式可以是 TWO WAY 或者 ONE WAY 。双方之间的心跳采用 Ping-Pong 机制当链路处于空闲状态时客户端主动发送 Ping 消息给服务端服务端接收到 Ping 消息后发送应答消息 Pong 给客户端如果客户端连 续发送 N 条 Ping 消息都没有接收到服务端返回的 Pong 消息说明链路已经挂死或者对方处 于异常状态客户端主动关闭连接间隔周期 T 后发起重连操作直到重连成功。 消息定义 消息定义包含两部分 消息头消息体。 在消息的定义上因为是同步处理模式不考虑应答消息需要填入请求消息 ID 所以 消息头中只有一个消息的 ID 。如果要支持异步模式则请求消息头和应答消息头最好分开 设计应答消息头中除了包括本消息的 ID 外还应该包括请求消息 ID 以方便请求消息的 发送方根据请求消息 ID 做对应的业务处理。 消息体则支持 Java 对象类型的消息内容。 Netty 消息定义表 消息头定义 Header 链路的建立 客户端的说明如下如果 A 节点需要调用 B 节点的服务但是 A 和 B 之间还没有建立 物理链路则有调用方主动发起连接此时调用方为客户端被调用方为服务端。 考虑到安全链路建立需要通过基于 Ip 地址或者号段的黑白名单安全认证机制作为 样例本协议使用基于 IP 地址的安全认证如果有多个 Ip 通过逗号进行分割。在实际的 商用项目中安全认证机制会更加严格例如通过密钥对用户名和密码进行安全认证。 客户端与服务端链路建立成功之后由客户端发送业务握手请求的认证消息服务端接 收到客户端的握手请求消息之后如果 IP 校验通过返回握手成功应答消息给客户端应 用层链路建立成功。握手应答消息中消息体为 byte 类型的结果 0 认证成功 -1 认证失败 服务端关闭连接。 链路建立成功之后客户端和服务端就可以互相发送业务消息了在客户端和服务端的 消息通信过程中业务消息体的内容需要通过 MD5 进行摘要防篡改。 可靠性设计
心跳机制 在凌晨等业务低谷时段如果发生网络闪断、连接被 Hang 住等问题时由于没有业务 消息应用程序很难发现。到了白天业务高峰期时会发生大量的网络通信失败严重的会 导致一段时间进程内无法处理业务消息。为了解决这个问题在网络空闲时采用心跳机制来 检测链路的互通性一旦发现网络故障立即关闭链路主动重连。 当读或者写心跳消息发生 I/O 异常的时候说明已经中断此时需要立即关闭连接如 果是客户端需要重新发起连接。如果是服务端需要清空缓存的半包信息等到客户端重 连。 空闲的连接和超时 检测空闲连接以及超时对于及时释放资源来说是至关重要的。由于这是一项常见的任务 Netty 特地为它提供了几个 ChannelHandler 实现。 IdleStateHandler 当连接空闲时间太长时将会触发一个 IdleStateEvent 事件。然后可 以通过在 ChannelInboundHandler 中重写 userEventTriggered() 方法来处理该 IdleStateEvent 事件。 ReadTimeoutHandler 如果在指定的时间间隔内没有收到任何的入站数据则抛出一个 ReadTimeoutException 并关闭对应的 Channel 。可以通过重写你的 ChannelHandler 中的 exceptionCaught() 方法来检测该 Read-TimeoutException 。 重连机制 如果链路中断等到 INTEVAL 时间后由客户端发起重连操作如果重连失败间隔周 期 INTERVAL 后再次发起重连直到重连成功。 为了保持服务端能够有充足的时间释放句柄资源在首次断连时客户端需要等待 INTERVAL 时间之后再发起重连而不是失败后立即重连。 为了保证句柄资源能够及时释放无论什么场景下重连失败客户端必须保证自身的资 源被及时释放包括但不现居 SocketChannel 、 Socket 等。 重连失败后可以打印异常堆栈信息方便后续的问题定位。 重复登录保护 当客户端握手成功之后在链路处于正常状态下不允许客户端重复登录以防止客户 端在异常状态下反复重连导致句柄资源被耗尽。 服务端接收到客户端的握手请求消息之后对 IP 地址进行合法性校验如果校验成功 在缓存的地址表中查看客户端是否已经登录如果登录则拒绝重复登录同时关闭 TCP 链路并在服务端的日志中打印握手失败的原因。 客户端接收到握手失败的应答消息之后关闭客户端的 TCP 连接等待 INTERVAL 时间 之后再次发起 TCP 连接直到认证成功。 实现 参考 netty-adv 模块下的代码 完成后 Handler 示意图如下 其中认证申请和认证检查可以在完成后移除。 前期准备 cn.tuling.nettyadv.vo 中定义了消息有关的实体类为了防篡改消息体需要进行摘要 vo 包下提供了 EncryptUtils 类可以对消息体进行摘要目前支持 MD5 、 SHA-1 和 SHA-256 这 三种缺省为 MD5 其中 MD5 额外提供了加盐摘要。 同时在 cn.tuling.nettyadv.kryocodec 中定义了有关序列化和反序列化的工具类和 Handler 本项目中序列化使用了 Kryo 序列化框架。 服务端 服务端中 NettyServe 类是服务端的主入口内部使用了 ServerInit 类进行 Handler 的安 装。 最先安装的当然是解决粘包和半包问题的 Handler 很自然这里应该用 LengthFieldBasedFrameDecoder 进行解码为了实现方便我们也没有在消息报文中附带消 息的长度由 Netty 帮我们在消息报文的最开始增加长度所以编码器选择了 LengthFieldPrepender 。 接下来自然就是序列化和反序列化直接使用我们在 kryocodec 下已经准备好的 KryoDecoder 和 KryoEncoder 即可。 服务端需要进行登录检查、心跳应答、业务处理对应着三个 handler 于是我们分别 安装了 LoginAuthRespHandler 、 HeartBeatRespHandler 、 ServerBusiHandler 。 为了节约网络和服务器资源如果客户端长久没有发送业务和心跳报文我们认为客户 端出现了问题需要关闭这个连接我们引入 Netty 的 ReadTimeoutHandler 当一定周期内 默认值 50s 我们设定为 15s 没有读取到对方任何消息时会触发一个 ReadTimeouttException这时我们检测到这个异常需要主动关闭链路并清除客户端登录 缓存信息等待客户端重连。 客户端 客户端的主类是 NettyClient 并对外提供一个方法 send 供业务使用内部使用了 ClientInit 类进行 Handler 的安装。 最先安装的当然是解决粘包和半包问题的 Handler 同样这里应该用 LengthFieldBasedFrameDecoder 进行解码编码器选择了 LengthFieldPrepender 。 接下来自然就是序列化和反序列化依然使用 KryoDecoder 和 KryoEncoder 即可。 客户端需要主动发出认证请求和心跳请求。 在 TCP 三次握手链路建立后客户端需要进行应用层的握手认证才能使用服务这 个功能由 LoginAuthReqHandler 负责而这个 Handler 在认证通过后其实就没用了所以 在认证通过后可以将这个 LoginAuthReqHandler 移除其实服务端的认证应答 LoginAuthRespHandler 同样也可以移除。 对于发出心跳请求有两种实现方式一是定时发出本框架的第一个版本就是这种实现 方式但是这种方式其实有浪费的情况因为如果客户端和服务器正在正常业务通信其实 是没有必要发送心跳的所以第二种方式就是当链路写空闲时为了维持通道避免服务 器关闭链接发出心跳请求。为了实现这一点我们首先在整个 pipeline 的最前面安装一个 CheckWriteIdleHandler 进行写空闲检测空闲时间定位 8S 取服务器读空闲时间 15S 的一半 然后再安装一个 HearBeatReqHandler 因为写空闲会触发一个 FIRST_WRITER_IDLE_STATE_EVENT 入站事件我们在 HearBeatReqHandler 的 userEventTriggered 方法中捕捉这个事件并发出心跳请求报文。 考虑到在我们的实现中并没有双向心跳即是客户端向服务器发送心跳请求是服务器 也向客户端发送心跳请求客户端这边同样需要检测服务器是否存活所以我们客户端这 边安装了一个 ReadTimeoutHandler 捕捉 ReadTimeoutException 后提示调用者并关闭通信 链路触发重连机制。 7、为了测试单独建立一个 BusiClient 模拟业务方的调用。因为客户端的网络通信代 码是在一个线程中单独启动的为了协调主线程和通信线程的工作我们引入了线程中的等 待通知机制。 测试 1、 正常情况 2、 客户端宕机服务器应能清除客户端的缓存信息允许客户端重新登录 3、 服务器宕机客户端应能发起重连 4、在 LoginAuthRespHandler 中进行注释可以模拟当服务器不处理客户端的请求时客户 端在超时后重新进行登录。 功能的增强 作为一个通信框架支持诊断也是很重要的所以我们在服务端单独引入了一个 MetricsHandler 可以提供目前在线 Channel 数、发送队列积压消息数、读取速率、写出 速率相关数据以方便应用方对自己的应用的性能和繁忙程度进行检查和调整。 当然对于一个通信框架还可以提供 SSL 安全访问、流控、 I/O 线程和业务线程分离、参 数的可配置化等等功能我们就不一一展现了同学们可以自行研究后实现因为 Netty 对 上述功能已经提供了很好的支持大家后面要学习的 Dubbo 框架源码分析中基本都有对应 的实现。 面试难题分析 Netty 是如何解决 JDK 中的 Selector BUG 的 Selector BUG JDK NIO 的 BUG 例如臭名昭著的 epoll bug 它会导致 Selector 空轮询 最终导致 CPU 100% 。官方声称在 JDK1.6 版本的 update18 修复了该问题但是直到 JDK1.7 版本该问题仍旧存在只不过该 BUG 发生概率降低了一些而已它并没有被根本解决甚 至 JDK1.8 的 131 版本中依然存在。 JDK 官方认为这是 Linux Kernel 版本的 bug 可以参见 https://bugs.java.com/bugdatabase/view_bug.do?bug_id6403933 https://bugs.java.com/bugdatabase/view_bug.do?bug_id2147719 https://bugs.java.com/bugdatabase/view_bug.do?bug_id6670302 https://bugs.java.com/bugdatabase/view_bug.do?bug_id6481709 简单来说JDK 认为 linux 的 epoll 告诉我事件来了但是 JDK 没有拿到任何事件 (READ 、 WRITE 、 CONNECT 、 ACCPET) 但此时 select() 方法不再选择阻塞了而是选择返回了 0 于 是就会进入一种无限循环导致 CPU 100% 。 这个问题的具体原因是在部分 Linux 的 2.6 的 kernel 中 poll 和 epoll 对于突然中断的 连接 socket 会对返回的 eventSet 事件集合置为 POLLHUP 或 POLLERR eventSet 事件集合发 生了变化这就可能导致 Selector 会被唤醒。但是这个时候 selector 的 select 方法返回 numKeys 是 0 所以下面本应该对 key 值进行遍历的事件处理根本执行不了又回到最上面的 while(true) 循环循环往复不断的轮询直到 linux 系统出现 100% 的 CPU 情况最终导致 程序崩溃。 Netty 解决办法对 Selector 的 select 操作周期进行统计每完成一次空的 select 操作 进行一次计数若在某个周期内连续发生 N 次空轮询则触发了 epoll 死循环 bug 。重建 Selector 判断是否是其他线程发起的重建请求若不是则将原 SocketChannel 从旧的 Selector 上去除注册重新注册到新的 Selector 上并将原来的 Selector 关闭。 具体代码在 NioEventLoop 的 select 方法中 如何让单机下 Netty 支持百万长连接 单机下能不能让我们的网络应用支持百万连接可以但是有很多的工作要做。 操作系统 首先就是要突破操作系统的限制。 在 Linux 平台上无论编写客户端程序还是服务端程序在进行高并发 TCP 连接处理时 最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制这是因为系统为 每个 TCP 连接都要创建一个 socket 句柄每个 socket 句柄同时也是一个文件句柄。 可使用 ulimit 命令查看系统允许当前用户进程打开的句柄数限制 $ ulimit -n 1024 这表示当前用户的每个进程最多允许同时打开 1024 个句柄这 1024 个句柄中还得除 去每个进程必然打开的标准输入标准输出标准错误服务器监听 socket, 进程间通讯的 unix 域 socket 等文件那么剩下的可用于客户端 socket 连接的文件数就只有大概 1024-101014 个左右。也就是说缺省情况下基于 Linux 的通讯程序最多允许同时 1014 个 TCP 并发连接。 对于想支持更高数量的 TCP 并发连接的通讯处理程序就必须修改 Linux 对当前用户 的进程同时打开的文件数量。 修改单个进程打开最大文件数限制的最简单的办法就是使用 ulimit 命令 $ ulimit –n 1000000 如果系统回显类似于Operation not permitted 之类的话说明上述限制修改失败实际 上是因为在中指定的数值超过了 Linux 系统对该用户打开文件数的软限制或硬限制。因此 就需要修改 Linux 系统对用户的关于打开文件数的软限制和硬限制。 软限制soft limit : 是指 Linux 在当前系统能够承受的范围内进一步限制一个进程同时 打开的文件数 硬限制hardlimit : 是根据系统硬件资源状况主要是系统内存计算出来的系统最多 可同时打开的文件数量。 第一步修改/etc/security/limits.conf 文件在文件中添加如下行 * soft nofile 1000000 * hard nofile 1000000 *号表示修改所有用户的限制 soft 和 hard 为两种限制方式其中 soft 表示警告的限制 hard 表示真正限制 nofile 表示打开的最大文件数。 1000000 则指定了想要修改的新的限制值即最大打开文件数请 注意软限制值要小于或等于硬限制。修改完后保存文件。 第二步修改/etc/pam.d/login 文件在文件中添加如下行 session required /lib/security/pam_limits.so 这是告诉 Linux 在用户完成系统登录后应该调用 pam_limits.so 模块来设置系统对该用 户可使用的各种资源数量的最大限制包括用户可打开的最大文件数限制而 pam_limits.so 模块就会从 /etc/security/limits.conf 文件中读取配置来设置这些限制值。修改完后保存此文 件。 第三步查看 Linux 系统级的最大打开文件数限制使用如下命令 [spengas4 ~]$ cat /proc/sys/fs/file-max 12158 这表明这台 Linux 系统最多允许同时打开即包含所有用户打开文件数总和 12158 个 文件是 Linux 系统级硬限制所有用户级的打开文件数限制都不应超过这个数值。如果没 有特殊需要不应该修改此限制除非想为用户级打开文件数限制设置超过此限制的值。 如何修改这个系统最大文件描述符的限制呢修改 sysctl.conf 文件 vi /etc/sysctl.conf # 在末尾添加 fs.file_max 1000000 # 立即生效 sysctl -p Netty 调优 设置合理的线程数 对于线程池的调优, 主要集中在用于接收海量设备 TCP 连接、 TLS 握手的 Acceptor 线程 池 ( Netty 通常叫 boss NioEventLoop Group) 上 , 以及用于处理网络数据读写、心跳发送的 1O 工作线程池 (Nety 通常叫 work Nio EventLoop Group) 上。 对于 Nety 服务端 , 通常只需要启动一个监听端口用于端侧设备接入即可 , 但是如果服务 端集群实例比较少 , 甚至是单机 ( 或者双机冷备 ) 部署 , 在端侧设备在短时间内大量接入时 , 需要 对服务端的监听方式和线程模型做优化 , 以满足短时间内 ( 例如 30s) 百万级的端侧设备接入的 需要。 服务端可以监听多个端口, 利用主从 Reactor 线程模型做接入优化 , 前端通过 SLB 做 4 层 门 7 层负载均衡。 主从 Reactor 线程模型特点如下 : 服务端用于接收客户端连接的不再是一个单独的 NO 线程 , 而是一个独立的 NIO 线程池 ; Acceptor 接收到客户端 TCP 连接请求并处理后 ( 可能包含接 入认证等 ), 将新创建的 Socketchanne 注册到 I/O 线程池 (subReactor 线程池 ) 的某个 IO 线程 , 由它负责 Socketchannel 的读写和编解码工作 ; Acceptor 线程池仅用于客户端的登录、握手 和安全认证等 , 一旦链路建立成功 , 就将链路注册到后端 sub reactor 线程池的 IO 线程 , 由 IO 线 程负责后续的 IO 操作。 对于 IO 工作线程池的优化 , 可以先采用系统默认值 ( 即 CPU 内核数× 2) 进行性能测试 , 在 性能测试过程中采集 IO 线程的 CPU 占用大小 , 看是否存在瓶颈 具体可以观察线程堆栈 如果连续采集几次进行对比 , 发现线程堆栈都停留在 Selectorlmpl. lockAndDoSelect 则说明 IO 线程比较空闲 , 无须对工作线程数做调整。 如果发现 IO 线程的热点停留在读或者写操作 , 或者停留在 Channelhandler 的执行处 , 则 可以通过适当调大 Nio EventLoop 线程的个数来提升网络的读写性能。 心跳优化 针对海量设备接入的服务端, 心跳优化策略如下。 (1) 要能够及时检测失效的连接 , 并将其剔除 , 防止无效的连接句柄积压 , 导致 OOM 等问题 (2)设置合理的心跳周期 , 防止心跳定时任务积压 , 造成频繁的老年代 GC( 新生代和老年代 都有导致 STW 的 GC, 不过耗时差异较大 ), 导致应用暂停 (3)使用 Nety 提供的链路空闲检测机制 , 不要自己创建定时任务线程池 , 加重系统的负担 , 以及增加潜在的并发安全问题。 当设备突然掉电、连接被防火墙挡住、长时间 GC 或者通信线程发生非预期异常时 , 会导 致链路不可用且不易被及时发现。特别是如果异常发生在凌晨业务低谷期间 , 当早晨业务高 峰期到来时 , 由于链路不可用会导致瞬间大批量业务失败或者超时 , 这将对系统的可靠性产生 重大的威胁。 从技术层面看, 要解决链路的可靠性问题 , 必须周期性地对链路进行有效性检测。目前最 流行和通用的做法就是心跳检测。心跳检测机制分为三个层面 (1)TCP 层的心跳检测 , 即 TCP 的 Keep-Alive 机制 , 它的作用域是整个 TCP 协议栈。 (2)协议层的心跳检测 , 主要存在于长连接协议中 , 例如 MQTT 。 (3)应用层的心跳检测 , 它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。 心跳检测的目的就是确认当前链路是否可用 , 对方是否活着并且能够正常接收和发送消 息。作为高可靠的 NIO 框架 ,Nety 也提供了心跳检测机制。 一般的心跳检测策略如下。 (1)连续 N 次心跳检测都没有收到对方的 Pong 应答消息或者 Ping 请求消息 , 则认为链路 已经发生逻辑失效 , 这被称为心跳超时。 (2)在读取和发送心跳消息的时候如果直接发生了 IO 异常 , 说明链路已经失效 , 这被称为 心跳失败。无论发生心跳超时还是心跳失败 , 都需要关闭链路 , 由客户端发起重连操作 , 保证链 路能够恢复正常。 Nety 提供了三种链路空闲检测机制 , 利用该机制可以轻松地实现心跳检测 (1)读空闲 , 链路持续时间 T 没有读取到任何消息。 (2)写空闲 , 链路持续时间 T 没有发送任何消息 (3)读写空闲 , 链路持续时间 T 没有接收或者发送任何消息 对于百万级的服务器一般不建议很长的心跳周期和超时时长。 接收和发送缓冲区调优 在一些场景下, 端侧设备会周期性地上报数据和发送心跳 , 单个链路的消息收发量并不大 , 针对此类场景 , 可以通过调小 TCP 的接收和发送缓冲区来降低单个 TCP 连接的资源占用率 当然对于不同的应用场景 , 收发缓冲区的最优值可能不同 , 用户需要根据实际场景 , 结合 性能测试数据进行针对性的调优 合理使用内存池 随着 JVM 虚拟机和 JT 即时编译技术的发展 , 对象的分配和回收是一个非常轻量级的工作。 但是对于缓冲区 Buffer, 情况却稍有不同 , 特别是堆外直接内存的分配和回收 , 是一个耗时的 操作。 为了尽量重用缓冲区 ,Nety 提供了基于内存池的缓冲区重用机制。 在百万级的情况下, 需要为每个接入的端侧设备至少分配一个接收和发送 ByteBuf 缓冲 区对象 , 采用传统的非池模式 , 每次消息读写都需要创建和释放 ByteBuf 对象 , 如果有 100 万个 连接 , 每秒上报一次数据或者心跳 , 就会有 100 万次 / 秒的 ByteBuf 对象申请和释放 , 即便服务 端的内存可以满足要求 ,GC 的压力也会非常大。 以上问题最有效的解决方法就是使用内存池, 每个 NioEventLoop 线程处理 N 个链路 , 在 线程内部 , 链路的处理是串行的。假如 A 链路首先被处理 , 它会创建接收缓冲区等对象 , 待解码 完成 , 构造的 POJO 对象被封装成任务后投递到后台的线程池中执行 , 然后接收缓冲区会被释 放 , 每条消息的接收和处理都会重复接收缓冲区的创建和释放。如果使用内存池 , 则当 A 链路 接收到新的数据报时 , 从 NioEventLoop 的内存池中申请空闲的 ByteBuf, 解码后调用 release 将 ByteBuf 释放到内存池中 , 供后续的 B 链路使用。 Nety 内存池从实现上可以分为两类 : 堆外直接内存和堆内存。由于 Byte Buf 主要用于网 络 IO 读写 , 因此采用堆外直接内存会减少一次从用户堆内存到内核态的字节数组拷贝 , 所以 性能更高。由于 DirectByteBuf 的创建成本比较高 , 因此如果使用 DirectByteBuf, 则需要配合 内存池使用 , 否则性价比可能还不如 Heap Byte 。 Netty 默认的 IO 读写操作采用的都是内存池的堆外直接内存模式 , 如果用户需要额外使 用 ByteBuf, 建议也采用内存池方式 ; 如果不涉及网络 IO 操作 ( 只是纯粹的内存操作 ), 可以使用 堆内存池 , 这样内存的创建效率会更高一些。 IO 线程和业务线程分离 如果服务端不做复杂的业务逻辑操作, 仅是简单的内存操作和消息转发 , 则可以通过调大 NioEventLoop 工作线程池的方式 , 直接在 IO 线程中执行业务 Channelhandler, 这样便减少了一 次线程上下文切换 , 性能反而更高。 如果有复杂的业务逻辑操作, 则建议 IO 线程和业务线程分离 , 对于 IO 线程 , 由于互相之间 不存在锁竞争 , 可以创建一个大的 NioEvent Loop Group 线程组 , 所有 Channel 都共享同一个 线程池。 对于后端的业务线程池, 则建议创建多个小的业务线程池 , 线程池可以与 IO 线程绑定 , 这 样既减少了锁竞争 , 又提升了后端的处理性能。 针对端侧并发连接数的流控 无论服务端的性能优化到多少, 都需要考虑流控功能。当资源成为瓶颈 , 或者遇到端侧设 备的大量接入 , 需要通过流控对系统做保护。流控的策略有很多种比如针对端侧连接数的 流控 在 Nety 中 , 可以非常方便地实现流控功能 : 新增一个 FlowControlchannelhandler 然后添 加到 ChannelPipeline 靠前的位置 , 覆盖 channelActive() 方法 , 创建 TCP 链路后 , 执行流控逻辑 , 如果达到流控阈值 , 则拒绝该连接 , 调用 ChannelHandler Context 的 close( 方法关闭连接。 JVM 层面相关性能优化 当客户端的并发连接数达到数十万或者数百万时, 系统一个较小的抖动就会导致很严重 的后果 , 例如服务端的 GC, 导致应用暂停 (STW) 的 GC 持续几秒 , 就会导致海量的客户端设备掉 线或者消息积压 , 一旦系统恢复 , 会有海量的设备接入或者海量的数据发送很可能瞬间就把服 务端冲垮。 JVM 层面的调优主要涉及 GC 参数优化 ,GC 参数设置不当会导致频繁 GC, 甚至 OOM 异常 , 对服务端的稳定运行产生重大影响。 1.确定 GC 优化目标 GC( 垃圾收集 ) 有三个主要指标。 (1)吞吐量 : 是评价 GC 能力的重要指标 , 在不考虑 GC 引起的停顿时间或内存消耗时 , 吞吐 量是 GC 能支撑应用程序达到的最高性能指标。 (2)延迟 :GC 能力的最重要指标之一 , 是由于 GC 引起的停顿时间 , 优化目标是缩短延迟时 间或完全消除停顿 (STW), 避免应用程序在运行过程中发生抖动。 (3)内存占用 :GC 正常时占用的内存量。 JVM GC 调优的三个基本原则如下。 (1) Minor go 回收原则 : 每次新生代 GC 回收尽可能多的内存 , 减少应用程序发生 Full gc 的 频率。 (2)GC 内存最大化原则 : 垃圾收集器能够使用的内存越大 , 垃圾收集效率越高 , 应用程序运 行也越流畅。但是过大的内存一次 Full go 耗时可能较长 , 如果能够有效避免 FullGC, 就需要做 精细化调优。 (3)3 选 2 原则 : 吞吐量、延迟和内存占用不能兼得 , 无法同时做到吞吐量和暂停时间都最 优 , 需要根据业务场景做选择。对于大多数应用 , 吞吐量优先 , 其次是延迟。当然对于时延敏感 型的业务 , 需要调整次序。 2.确定服务端内存占用 在优化 GC 之前 , 需要确定应用程序的内存占用大小 , 以便为应用程序设置合适的内存 , 提 升 GC 效率。内存占用与活跃数据有关 , 活跃数据指的是应用程序稳定运行时长时间存活的 Java 对象。活跃数据的计算方式 : 通过 GC 日志采集 GC 数据 , 获取应用程序稳定时老年代占用 的 Java 堆大小 , 以及永久代 ( 元数据区 ) 占用的 Java 堆大小 , 两者之和就是活跃数据的内存占用 大小。 3.GC 优化过程 1、 GC 数据的采集和研读 2、设置合适的 JVM 堆大小 3、选择合适的垃圾回收器和回收策略 当然具体如何做请参考 JVM 相关课程。而且 GC 调优会是一个需要多次调整的过程 期间不仅有参数的变化更重要的是需要调整业务代码。 什么是水平触发(LT)和边缘触发(ET) Level_triggered(水平触发 ) 当被监控的文件描述符上有可读写事件发生时 epoll_wait() 会通知处理程序去读写。如果这次没有把数据一次性全部读写完那么下次调用 epoll_wait() 时它还会通知你在上没读写完的文件描述符上继续读写当然如果你一直不去读写它会 一直通知你。 Edge_triggered(边缘触发 ) 当被监控的文件描述符上有可读写事件发生时 epoll_wait() 会通知处理程序去读写。如果这次没有把数据全部读写完那么下次调用 epoll_wait() 时 它不会通知你也就是它只会通知你一次直到该文件描述符上出现第二次可读写事件才会 通知你。这种模式比水平触发效率高系统不会充斥大量你不关心的就绪文件描述符 select() poll() 模型都是水平触发模式信号驱动 IO 是边缘触发模式 epoll() 模型即支 持水平触发也支持边缘触发默认是水平触发。 JDK 中的 select 实现是水平触发而 Netty 提供的 Epoll 的实现中是边缘触发。 请说说 DNS 域名解析的全过程 本题其实是“浏览器中输入 URL 到返回页面的全过程”这个题目的衍生题 1.根据域名进行 DNS 域名解析 2.拿到解析的 IP 地址建立 TCP 连接 3.向 IP 地址发送 HTTP 请求 4.服务器处理请求 5.返回响应结果 6.关闭 TCP 连接 7.浏览器解析 HTML 8.浏览器布局渲染 可见 DNS 域名解析是其中的一部分。 DNS 一个由分层的服务系统大致说来有 3 种类型的 DNS 服务器:根 DNS 服务器、顶 级域(Top-Level DomainTLD DNS 服务器和权威 DNS 服务器。 根 DNS 服务器。截止到 2022 年 4 月 22 日有 1533 个根名字服务器遍及全世界可到 https://root-servers.org/ 查询分布情况根名字服务器提供 TLD 服务器的 IP 地址。 顶级域DNS服务器。对于每个顶级域如 com、org、net、edu 和 gov和所有国家 的顶级域如 uk、fr、ca 和 jp都有 TLD 服务器或服务器集群。TLD 服务器提供了 权威 DNS 服务器的 IP 地址。 权威 DNS 服务器。在因特网上的每个组织机构必须提供公共可访问的 DNS 记录这些 记录将这些主机的名字映射为 IP 地址。一个组织机构的权威 DNS 服务器收藏了这些 DNS 记 录。一个组织机构能够选择实现它自己的权威 DNS 服务器以保存这些记录也可以交由商 用 DNS 服务商存储在这个服务提供商的一个权威 DNS 服务器中比如阿里云旗下的中国万 网。 有另一类重要的 DNS 服务器称为本地 DNS 服务器 local DNS server。严格说来 一个本地 DNS 服务器并不属于该服务器的层次结构但它对 DNS 层次结构是至关重要的。 每个 ISP 都有一台本地 DNS 服务器。同时很多路由器中也会附带 DNS 服务。 当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器它起着代理的作用并将该请 求转发到 DNS 服务器层次结构中同时本地 DNS 服务器也会缓存 DNS 记录。 所以一个 DNS 客户要决定主机名 www.baidu.com 的 IP 地址。粗略说来将发生下列事 件。客户首先与根服务器之一联系它将返回顶级域名 com 的 TLD 服务器的 IP 地址。该客 户则与这些 TLD 服务器之一联系它将为 baidu.com 返回权威服务器的 IP 地址。最后该 客户与 baidu.com 权威服务器之一联系它为主机名 www.baidu.com 返回其 IP 地址。