Netty源码剖析与实战-第三周

Netty 源码:从“点”(领域知识)的角度剖析

目录

Netty 源码:从“点”(领域知识)的角度剖析

一、keepalive 与Idle 监测

• 为什么需要keepalive ?

• 为什么还需要应用层keepalive ?

• Idle 监测是什么?

• 如何在Netty 中开启TCP keepalive 和Idle 检测

二、Netty 的那些“锁”事

• 分析同步问题的核心三要素

Netty 玩转锁的五个关键点:

• 在意锁的对象和范围-> 减少粒度

• 注意锁的对象本身大小-> 减少空间占用

• 注意锁的速度-> 提高速度

• 不同场景选择不同的并发类-> 因需而变

• 衡量好锁的价值-> 能不用则不用


一、keepalive 与Idle 监测

• 为什么需要keepalive ?

生活场景:
假设你开了一个饭店,别人电话来订餐,电话通了后,订餐的说了一堆订餐要求,说着说
着,对方就不讲话了(可能忘记挂机/出去办事/线路故障等)。
• 这个时候你会一直握着电话等么?
不会
• 如果不会,那你一般怎么去做?
会确认一句“你还在么?”,如果对方没有回复,挂机。这套机制即“keepalive”
• 怎么设计keepalive ?以TCP keepalive 为例

Netty源码剖析与实战-第三周

TCP keepalive 核心参数:
# sysctl -a|grep tcp_keepalive
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
当启用(默认关闭)keepalive 时,TCP 在连接没有数据
通过的7200秒后发送keepalive 消息,当探测没有确认时,
按75秒的重试频率重发,一直发9 个探测包都没有确认,就认定
连接失效。
所以总耗时一般为:2 小时11 分钟(7200 秒+ 75 秒* 9 次)

 


• 为什么还需要应用层keepalive ?

1.协议分层,各层关注点不同:
传输层关注是否“通”,应用层关注是否可服务? 类比前面的电话订餐例子,电话能通,
不代表有人接;服务器连接在,但是不定可以服务(例如服务不过来等)。
2.TCP 层的keepalive 默认关闭,且经过路由等中转设备keepalive 包可能会被丢弃。
3.TCP 层的keepalive 时间太长:
默认> 2 小时,虽然可改,但属于系统参数,改动影响所有应用。

注意:

HTTP 属于应用层协议,但是常常听到名词“ HTTP Keep-Alive ”指的是对长连接和短连接的选择:
• Connection : Keep-Alive 长连接(HTTP/1.1 默认长连接,不需要带这个header)
• Connection : Close 短连接


• Idle 监测是什么?

重现生活场景:
假设你开了一个饭店,别人电话来订餐,电话通了后,订餐的说了一堆订餐要求,说着说着,
对方就不讲话了。
你会立马发问:你还在么?
• 不会
• 一般你会稍微等待一定的时间,在这个时间内看看对方还会不会说话(Idle 检测),如
果还不说,认定对方存在问题(Idle),于是开始发问“你还在么?”(keepalive),
或者问都不问干脆直接挂机(关闭连接)。

 

Idle 监测,只是负责诊断,诊断后,做出不同的行为,决定Idle 监测的最终用途:
• 发送keepalive :一般用来配合keepalive ,减少keepalive 消息。
Keepalive 设计演进:V1 定时keepalive 消息-> V2 空闲监测+ 判定为Idle 时才发
keepalive。
• V1:keepalive 消息与服务器正常消息交换完全不关联,定时就发送;
• V2:有其他数据传输的时候,不发送keepalive ,无数据传输超过一定时间,判定
为Idle,再发keepalive 。

 


• 如何在Netty 中开启TCP keepalive 和Idle 检测

 

开启keepalive:
• Server 端开启TCP keepalive
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true)
bootstrap.childOption(NioChannelOption.of(StandardSocketOptions.SO_KEEPALIVE), true)
提示:.option(ChannelOption.SO_KEEPALIVE,true) 存在但是无效

 

开启不同的Idle Check:
• ch.pipeline().addLast(“idleCheckHandler", new IdleStateHandler(0, 20, 0, TimeUnit.SECONDS));

Netty源码剖析与实战-第三周
io.netty.handler.timeout.IdleStateHandler#IdleStateHandler(long, long, long, java.util.concurrent.TimeUnit)

 

二、Netty 的那些“锁”事


• 分析同步问题的核心三要素

锁的特性

1.原子性:“并无一气呵成,岂能无懈可击”
2.可见性:“你做的改变,别人看不见”
3.有序性:“不按套路出牌”

锁的分类

1.对竞争的态度:乐观锁(java.util.concurrent 包中的原子类)与悲观锁(Synchronized)
2.等待锁的人是否公平而言:公平锁new ReentrantLock (true)与非公平锁new ReentrantLock ()
3.是否可以共享:共享锁与独享锁:ReadWriteLock ,其读锁是共享锁,其写锁是独享锁


Netty 玩转锁的五个关键点:


• 在意锁的对象和范围-> 减少粒度

例:初始化channel (io.netty.bootstrap.ServerBootstrap#init)
Synchronized method -> Synchronized block

Netty源码剖析与实战-第三周

• 注意锁的对象本身大小-> 减少空间占用

例:统计待发送的字节数(io.netty.channel.ChannelOutboundBuffer
AtomicLong -> Volatile long + AtomicLongFieldUpdater

Netty源码剖析与实战-第三周

例:

Atomic long VS long:
前者是一个对象,包含对象头(object header)以用来保存hashcode、lock 等信息,32 位系统占
用8字节;64 位系统占16 字节,所以在64 位系统情况下:
• volatile long = 8 bytes
• AtomicLong = 8 bytes (volatile long)+ 16bytes (对象头)+ 8 bytes (引用) = 32 bytes
至少节约24 字节!
结论:Atomic* objects -> Volatile primary type + Static Atomic*FieldUpdater

 

• 注意锁的速度-> 提高速度

例1:记录内存分配字节数等功能用到的LongCounter
(io.netty.util.internal.PlatformDependent#newLongCounter() )
高并发时:java.util.concurrent.atomic.AtomicLong -> java.util.concurrent.atomic.LongAdder (JDK1.8)
结论: 及时衡量、使用JDK 最新的功能
• 不同场景选择不同的并发类-> 因需而变

Netty源码剖析与实战-第三周

Netty源码剖析与实战-第三周

 

例2:曾经根据不同情况,选择不同的并发包实现:JDK < 1.8 考虑
ConcurrentHashMapV8(ConcurrentHashMap 在JDK8 中的版本)

Netty源码剖析与实战-第三周

Netty源码剖析与实战-第三周

• 不同场景选择不同的并发类-> 因需而变

例1:关闭和等待关闭事件执行器(Event Executor):
Object.wait/notify -> CountDownLatch
io.netty.util.concurrent.SingleThreadEventExecutor#threadLock:

Netty源码剖析与实战-第三周

 

例2:Nio Event loop中负责存储task的Queue
Jdk’s LinkedBlockingQueue (MPMC) -> jctools’ MPSC
io.netty.util.internal.PlatformDependent.Mpsc#newMpscQueue(int):

Netty源码剖析与实战-第三周

 

 


• 衡量好锁的价值-> 能不用则不用

 

生活场景:
饭店提供了很多包厢,服务模式:
• 一个服务员固定服务某几个包厢模式;
• 所有的服务员服务所有包厢的模式。
表面上看,前者效率没有后者高,但实际上它避免了服务员之间的沟通(上下文切换)等
开销,避免客人和服务员之间到处乱串,管理简单。

 

局部串行:Channel 的I/O 请求处理Pipeline 是串行的

Netty源码剖析与实战-第三周

 

整体并行:多个串行化的线程(NioEventLoop)

Netty源码剖析与实战-第三周Netty源码剖析与实战-第三周

 

Netty 应用场景下:局部串行+ 整体并行> 一个队列+ 多个线程模式:
• 降低用户开发难度、逻辑简单、提升处理性能
• 避免锁带来的上下文切换和并发保护等额外开销

 

避免用锁:用ThreadLocal 来避免资源争用,例如Netty 轻量级的线程池实现
io.netty.util.Recycler#threadLocal

Netty源码剖析与实战-第三周