wordpress 宅男猫网站,php网站如何编辑,常德网站建设wynet123,专门做超市海报的网站byte[] --堆外内存 / 堆外内存-- byte[] --java对象。 优化#xff1a;省去 byte[] 环节#xff0c;直接 读/写 堆外内存#xff0c;这需要扩展对应的序列化框架。 String 编码/解码优化。 Varint 优化#xff1a;多次 writeByte 合并为 writeShort/writeInt/w…byte[] --堆外内存 / 堆外内存-- byte[] --java对象。 优化省去 byte[] 环节直接 读/写 堆外内存这需要扩展对应的序列化框架。 String 编码/解码优化。 Varint 优化多次 writeByte 合并为 writeShort/writeInt/writeLong。 Protostuff 优化举例UnsafeNioBufInput 直接读堆外内存/UnsafeNioBufOutput 直接写堆外内存。
3IO 线程绑定 CPU
4同步阻塞调用的客户端和容易成为瓶颈客户端协程
Java层面可选的并不多暂时也都不完美。5Netty Native Transport PooledByteBufAllocator
减小GC带来的波动。
6尽快释放 IO 线程去做他该做的事情尽量减少线程上下文切换。
四 Why Netty?
1 BIO vs NIO 2 Java 原生 NIO API 从入门到放弃
复杂度高
API复杂难懂入门困。粘包/半包问题费神。需超强的并发/异步编程功底否则很难写出高效稳定的实现。
稳定性差坑多且深
调试困难偶尔遭遇匪夷所思极难重现的bug边哭边查是常有的事儿。linux 下 EPollArrayWrapper.epollWait 直接返回导致空轮训进而导致 100% cpu 的 bug 一直也没解决利索Netty帮你 work around (通过rebuilding selector)。
NIO代码实现方面的一些缺点
1Selector.selectedKeys() 产生太多垃圾
Netty 修改了 sun.nio.ch.SelectorImpl 的实现使用双数组代替 HashSet 存储来 selectedKeys
相比HashSet(迭代器包装对象等)少了一些垃圾的产生(help GC)。轻微的性能收益(1~2%)。
Nio 的代码到处是 synchronized (比如 allocate direct buffer 和 Selector.wakeup() )
对于 allocate direct bufferNetty 的 pooledBytebuf 有前置 TLAB(Thread-local allocation buffer)可有效的减少去竞争锁。wakeup 调用多了锁竞争严重并且开销非常大(开销大原因: 为了在 select 线程外跟 select 线程通信linux 平台上用一对 pipewindows 由于 pipe 句柄不能放入 fd_set只能委曲求全用两个 tcp 连接模拟)wakeup 调用少了容易导致 select 时不必要的阻塞(如果懵逼了就直接用 Netty 吧Netty中有对应的优化逻辑)。Netty Native Transport 中锁少了很多。
2fdToKey 映射
EPollSelectorImpl#fdToKey 维持着所有连接的 fd(描述符)对应 SelectionKey 的映射是个 HashMap。每个 worker 线程有一个 selector也就是每个 worker 有一个 fdToKey这些 fdToKey 大致均分了所有连接。想象一下单机 hold 几十万的连接的场景HashMap 从默认 size16一步一步 rehash...
3Selector在linux 平台是 Epoll LT 实现
Netty Native Transport支持Epoll ET。
4Direct Buffers 事实上还是由 GC 管理
DirectByteBuffer.cleaner 这个虚引用负责 free direct memoryDirectByteBuffer 只是个壳子这个壳子如果坚强的活下去熬过新生代的年龄限制最终晋升到老年代将是一件让人伤心的事情…无法申请到足够的 direct memory 会显式触发 GCBits.reserveMemory() - { System.gc() }首先因为 GC 中断整个进程不说代码中还 sleep 100 毫秒醒了要是发现还不行就 OOM。更糟的是如果你听信了个别谗言设置了-XX:DisableExplicitGC 参数悲剧会静悄悄的发生...Netty的UnpooledUnsafeNoCleanerDirectByteBuf 去掉了 cleaner由 Netty 框架维护引用计数来实时的去释放。
五 Netty 的真实面目
1 Netty 中几个重要概念及其关系
EventLoop
一个 Selector。一个任务队列(mpsc_queue: 多生产者单消费者 lock-free)。一个延迟任务队列(delay_queue: 一个二叉堆结构的优先级队列复杂度为O(log n))。EventLoop 绑定了一个 Thread这直接避免了pipeline 中的线程竞争。
Boss: mainReactor 角色Worker: subReactor 角色
Boss 和 Worker 共用 EventLoop 的代码逻辑Boss 处理 accept 事件Worker 处理 readwrite 等事件。Boss 监听并 accept 连接(channel)后以轮训的方式将 channel 交给 WorkerWorker 负责处理此 channel 后续的read/write 等 IO 事件。在不 bind 多端口的情况下 BossEventLoopGroup 中只需要包含一个 EventLoop也只能用上一个多了没用。WorkerEventLoopGroup 中一般包含多个 EventLoop经验值一般为 cpu cores * 2(根据场景测试找出最佳值才是王道)。Channel 分两大类 ServerChannel 和 ChannelServerChannel 对应着监听套接字(ServerSocketChannel)Channel 对应着一个网络连接。
2 Netty4 Thread Model 3 ChannelPipeline 4 Poolingreuse
PooledByteBufAllocator
基于 jemalloc paper (3.x)ThreadLocal caches for lock free这个做法导致曾经有坑——申请(Bytebuf)线程与归还(Bytebuf)线程不是同一个导致内存泄漏后来用一个mpsc_queue解决代价就是牺牲了一点点性能。Different size classes。
Recycler
ThreadLocal Stack。曾经有坑申请(元素)线程与归还(元素)线程不是同一个导致内存泄漏。后来改进为不同线程归还元素的时候放入一个 WeakOrderQueue 中并关联到 stack 上下次 pop 时如果 stack 为空则先扫描所有关联到当前 stack 上的 weakOrderQueue。WeakOrderQueue 是多个数组的链表每个数组默认size16。存在的问题思考一下老年代对象引用新生代对象对 GC 的影响
5 Netty Native Transport
相比 Nio 创建更少的对象更小的 GC 压力。
针对 linux 平台优化一些 specific features
SO_REUSEPORT - 端口复用(允许多个 socket 监听同一个 IP端口与 RPS/RFS 协作可进一步提升性能)可把 RPS/RFS 模糊的理解为在软件层面模拟多队列网卡并提供负载均衡能力避免网卡收包发包的中断集中的一个 CPU core 上而影响性能。TCP_FASTOPEN - 3次握手时也用来交换数据。EDGE_TRIGGERED (支持Epoll ET是重点)。Unix 域套接字(同一台机器上的进程间通信比如Service Mesh)。
6 多路复用简介
select/poll
本身的实现机制上的限制(采用轮询方式检测就绪事件时间复杂度: O(n)每次还要将臃肿的 fd_set 在用户空间和内核空间拷贝来拷贝去)并发连接越大性能越差。poll 相比 select 没有很大差异只是取消了最大文件描述符个数的限制。select/poll 都是 LT 模式。
epoll
采用回调方式检测就绪事件时间复杂度: O(1)每次 epoll_wait 调用只返回已就绪的文件描述符。epoll 支持 LT 和 ET 模式。
7 稍微深入了解一点 Epoll
LT vs ET
概念
LTlevel-triggered 水平触发ETedge-triggered 边沿触发
可读
buffer 不为空的时候 fd 的 events 中对应的可读状态就被置为1否则为0。
可写
buffer 中有空间可写的时候 fd 的 events 中对应的可写状态就被置为1否则为0。
图解 epoll 三个方法简介
1主要代码linux-2.6.11.12/fs/eventpoll.c
2int epoll_create(int size)
创建 rb-tree(红黑树)和 ready-list (就绪链表)
红黑树O(logN)平衡效率和内存占用在容量需求不能确定并可能量很大的情况下红黑树是最佳选择。size参数已经没什么意义早期epoll实现是hash表所以需要size参数。
3int epoll_ctl(int epfdint opint fdstruct epoll_event *event)
把epitem放入rb-tree并向内核中断处理程序注册ep_poll_callbackcallback触发时把该epitem放进ready-list。
4int epoll_wait(int epfdstruct epoll_event * eventsint maxeventsint timeout)
ready-list — events[]。
epoll 的数据结构 epoll_wait 工作流程概述
对照代码linux-2.6.11.12/fs/eventpoll.c
1epoll_wait 调用 ep_poll
当 rdlist(ready-list) 为空(无就绪fd)时挂起当前线程,直到 rdlist 不为空时线程才被唤醒。
2文件描述符 fd 的 events 状态改变
buffer由不可读变为可读或由不可写变为可写导致相应fd上的回调函数ep_poll_callback被触发。
3ep_poll_callback 被触发
将相应fd对应epitem加入rdlist导致rdlist不空线程被唤醒epoll_wait得以继续执行。
4执行 ep_events_transfer 函数
将rdlist中的epitem拷贝到txlist中并将rdlist清空。如果是epoll LT并且fd.events状态没有改变(比如buffer中数据没读完并不会改变状态)会再重新将epitem放回rdlist。
5执行 ep_send_events 函数
扫描txlist中的每个epitem调用其关联fd对应的poll方法取得较新的events。将取得的events和相应的fd发送到用户空间。
8 Netty 的最佳实践
1业务线程池必要性
业务逻辑尤其是阻塞时间较长的逻辑不要占用netty的IO线程dispatch到业务线程池中去。
2WriteBufferWaterMark
注意默认的高低水位线设置(32K~64K)根据场景适当调整(可以思考一下如何利用它)。
3重写 MessageSizeEstimator 来反应真实的高低水位线
默认实现不能计算对象size由于write时还没路过任何一个outboundHandler就已经开始计算message size此时对象还没有被encode成Bytebuf所以size计算肯定是不准确的(偏低)。
4注意EventLoop#ioRatio的设置(默认50)
这是EventLoop执行IO任务和非IO任务的一个时间比例上的控制。
5空闲链路检测用谁调度?
Netty4.x默认使用IO线程调度使用eventLoop的delayQueue一个二叉堆实现的优先级队列复杂度为O(log N)每个worker处理自己的链路监测有助于减少上下文切换但是网络IO操作与idle会相互影响。如果总的连接数小比如几万以内上面的实现并没什么问题连接数大建议用HashedWheelTimer实现一个IdleStateHandlerHashedWheelTimer复杂度为 O(1)同时可以让网络IO操作和idle互不影响但有上下文切换开销。
6使用ctx.writeAndFlush还是channel.writeAndFlush?
ctx.write直接走到下一个outbound handler注意别让它违背你的初衷绕过了空闲链路检测。channel.write从末尾开始倒着向前挨个路过pipeline中的所有outbound handlers。
7使用Bytebuf.forEachByte() 来代替循环 ByteBuf.readByte()的遍历操作避免rangeCheck()
8使用CompositeByteBuf来避免不必要的内存拷贝
缺点是索引计算时间复杂度高请根据自己场景衡量。
9如果要读一个int用Bytebuf.readInt()不要Bytebuf.readBytes(buf04)
这能避免一次memory copy (longshort等同理)。
10配置UnpooledUnsafeNoCleanerDirectByteBuf来代替jdk的DirectByteBuf让netty框架基于引用计数来释放堆外内存
io.netty.maxDirectMemory 0: 不使用cleanernetty方面直接继承jdk设置的最大direct memory size(jdk的direct memory size是独立的这将导致总的direct memory size将是jdk配置的2倍)。 0: 使用cleanernetty方面不设置最大direct memory size。
0不使用cleaner并且这个参数将直接限制netty的最大direct memory size(jdk的direct memory size是独立的不受此参数限制)。11最佳连接数
一条连接有瓶颈无法有效利用cpu连接太多也白扯最佳实践是根据自己场景测试。
12使用PooledBytebuf时要善于利用 -Dio.netty.leakDetection.level 参数
四种级别DISABLED(禁用)SIMPLE(简单)ADVANCED(高级)PARANOID(偏执)。SIMPLEADVANCED采样率相同不到1%(按位与操作 mask 128 - 1)。默认是SIMPLE级别开销不大。出现泄漏时日志会出现“LEAK: ”字样请时不时grep下日志一旦出现“LEAK: ”立刻改为ADVANCED级别再跑可以报告泄漏对象在哪被访问的。PARANOID测试的时候建议使用这个级别100%采样。
13Channel.attr()将自己的对象attach到channel上
拉链法实现的线程安全的hash表也是分段锁(只锁链表头)只有hash冲突的情况下才有锁竞争(类似ConcurrentHashMapV8版本)。默认hash表只有4个桶使用不要太任性。
9 从 Netty 源码中学到的代码技巧
1海量对象场景中 AtomicIntegerFieldUpdater -- AtomicInteger
Java中对象头12 bytes(开启压缩指针的情况下)又因为Java对象按照8字节对齐所以对象最小16 bytesAtomicInteger大小为16 bytesAtomicLong大小为 24 bytes。AtomicIntegerFieldUpdater作为static field去操作volatile int。
2FastThreadLocal相比jdk的实现更快
线性探测的Hash表 — index原子自增的裸数组存储。
3IntObjectHashMap / LongObjectHashMap …
Integer— intNode[] — 裸数组
4RecyclableArrayList
基于前面说的Recycler频繁new ArrayList的场景可考虑。
5JCTools
一些jdk没有的 SPSC/MPSC/SPMC/MPMC 无锁并发队以及NonblockingHashMap(可以对比ConcurrentHashMapV6/V8)
原文链接 本文为阿里云原创内容未经允许不得转载。