如何用Netty写一个高性能的分布式服务框架?

byte[] -->堆外内存 / 堆外内存--> byte[] -->java对象。 优化:省去 byte[] 环节,直接 读/写 堆外内存,这需要扩展对应的序列化框架。 String 编码/解码优化。 Varint 优化:多次 writeByte 合并为 writeShort/writeInt/writeLong。 Protostuff 优化举例:UnsafeNioBufInput 直接读堆外内存/UnsafeNioBufOutput 直接写堆外内存。

3)IO 线程绑定 CPU

4)同步阻塞调用的客户端和容易成为瓶颈,客户端协程:

  • Java层面可选的并不多,暂时也都不完美。

image.png

5)Netty Native Transport & PooledByteBufAllocator:

  • 减小GC带来的波动。

6)尽快释放 IO 线程去做他该做的事情,尽量减少线程上下文切换。

四 Why Netty?

1 BIO vs NIO

image.png

2 Java 原生 NIO API 从入门到放弃

复杂度高

  • API复杂难懂,入门困。
  • 粘包/半包问题费神。
  • 需超强的并发/异步编程功底,否则很难写出高效稳定的实现。

稳定性差,坑多且深

  • 调试困难,偶尔遭遇匪夷所思极难重现的bug,边哭边查是常有的事儿。
  • linux 下 EPollArrayWrapper.epollWait 直接返回导致空轮训进而导致 100% cpu 的 bug 一直也没解决利索,Netty帮你 work around (通过rebuilding selector)。

NIO代码实现方面的一些缺点

1)Selector.selectedKeys() 产生太多垃圾

Netty 修改了 sun.nio.ch.SelectorImpl 的实现,使用双数组代替 HashSet 存储来 selectedKeys:

  • 相比HashSet(迭代器,包装对象等)少了一些垃圾的产生(help GC)。
  • 轻微的性能收益(1~2%)。

Nio 的代码到处是 synchronized (比如 allocate direct buffer 和 Selector.wakeup() ):

  • 对于 allocate direct buffer,Netty 的 pooledBytebuf 有前置 TLAB(Thread-local allocation buffer)可有效的减少去竞争锁。
  • wakeup 调用多了锁竞争严重并且开销非常大(开销大原因: 为了在 select 线程外跟 select 线程通信,linux 平台上用一对 pipe,windows 由于 pipe 句柄不能放入 fd_set,只能委曲求全用两个 tcp 连接模拟),wakeup 调用少了容易导致 select 时不必要的阻塞(如果懵逼了就直接用 Netty 吧,Netty中有对应的优化逻辑)。
  • Netty Native Transport 中锁少了很多。

2)fdToKey 映射

  • EPollSelectorImpl#fdToKey 维持着所有连接的 fd(描述符)对应 SelectionKey 的映射,是个 HashMap。
  • 每个 worker 线程有一个 selector,也就是每个 worker 有一个 fdToKey,这些 fdToKey 大致均分了所有连接。
  • 想象一下单机 hold 几十万的连接的场景,HashMap 从默认 size=16,一步一步 rehash...

3)Selector在linux 平台是 Epoll LT 实现

  • Netty Native Transport支持Epoll ET。

4)Direct Buffers 事实上还是由 GC 管理

  • DirectByteBuffer.cleaner 这个虚引用负责 free direct memory,DirectByteBuffer 只是个壳子,这个壳子如果坚强的活下去熬过新生代的年龄限制最终晋升到老年代将是一件让人伤心的事情…
  • 无法申请到足够的 direct memory 会显式触发 GC,Bits.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 处理 read,write 等事件。
  • Boss 监听并 accept 连接(channel)后以轮训的方式将 channel 交给 Worker,Worker 负责处理此 channel 后续的read/write 等 IO 事件。
  • 在不 bind 多端口的情况下 BossEventLoopGroup 中只需要包含一个 EventLoop,也只能用上一个,多了没用。
  • WorkerEventLoopGroup 中一般包含多个 EventLoop,经验值一般为 cpu cores * 2(根据场景测试找出最佳值才是王道)。
  • Channel 分两大类 ServerChannel 和 Channel,ServerChannel 对应着监听套接字(ServerSocketChannel),Channel 对应着一个网络连接。

2 Netty4 Thread Model

image.png

3 ChannelPipeline

image.png

4 Pooling&reuse

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 是多个数组的链表,每个数组默认size=16。
  • 存在的问题:思考一下老年代对象引用新生代对象对 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

概念:

  • LT:level-triggered 水平触发
  • ET:edge-triggered 边沿触发

可读:

  • buffer 不为空的时候 fd 的 events 中对应的可读状态就被置为1,否则为0。

可写:

  • buffer 中有空间可写的时候 fd 的 events 中对应的可写状态就被置为1,否则为0。

图解:

image.png

epoll 三个方法简介

1)主要代码:linux-2.6.11.12/fs/eventpoll.c

2)int epoll_create(int size)

创建 rb-tree(红黑树)和 ready-list (就绪链表):

  • 红黑树O(logN),平衡效率和内存占用,在容量需求不能确定并可能量很大的情况下红黑树是最佳选择。
  • size参数已经没什么意义,早期epoll实现是hash表,所以需要size参数。

3)int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)

  • 把epitem放入rb-tree并向内核中断处理程序注册ep_poll_callback,callback触发时把该epitem放进ready-list。

4)int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)

  • ready-list —> events[]。

epoll 的数据结构

image.png

epoll_wait 工作流程概述

对照代码:linux-2.6.11.12/fs/eventpoll.c:

1)epoll_wait 调用 ep_poll

  • 当 rdlist(ready-list) 为空(无就绪fd)时挂起当前线程,直到 rdlist 不为空时线程才被唤醒。

2)文件描述符 fd 的 events 状态改变

  • buffer由不可读变为可读或由不可写变为可写,导致相应fd上的回调函数ep_poll_callback被触发。

3)ep_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到业务线程池中去。

2)WriteBufferWaterMark

  • 注意默认的高低水位线设置(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实现一个IdleStateHandler,HashedWheelTimer复杂度为 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(buf,0,4)

  • 这能避免一次memory copy (long,short等同理)。

10)配置UnpooledUnsafeNoCleanerDirectByteBuf来代替jdk的DirectByteBuf,让netty框架基于引用计数来释放堆外内存

io.netty.maxDirectMemory:

  • < 0: 不使用cleaner,netty方面直接继承jdk设置的最大direct memory size,(jdk的direct memory size是独立的,这将导致总的direct memory size将是jdk配置的2倍)。
  • == 0: 使用cleaner,netty方面不设置最大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(偏执)。
  • SIMPLE,ADVANCED采样率相同,不到1%(按位与操作 mask ==128 - 1)。
  • 默认是SIMPLE级别,开销不大。
  • 出现泄漏时日志会出现“LEAK: ”字样,请时不时grep下日志,一旦出现“LEAK: ”立刻改为ADVANCED级别再跑,可以报告泄漏对象在哪被访问的。
  • PARANOID:测试的时候建议使用这个级别,100%采样。

13)Channel.attr(),将自己的对象attach到channel上

  • 拉链法实现的线程安全的hash表,也是分段锁(只锁链表头),只有hash冲突的情况下才有锁竞争(类似ConcurrentHashMapV8版本)。
  • 默认hash表只有4个桶,使用不要太任性。

9 从 Netty 源码中学到的代码技巧

1)海量对象场景中 AtomicIntegerFieldUpdater --> AtomicInteger

  • Java中对象头12 bytes(开启压缩指针的情况下),又因为Java对象按照8字节对齐,所以对象最小16 bytes,AtomicInteger大小为16 bytes,AtomicLong大小为 24 bytes。
  • AtomicIntegerFieldUpdater作为static field去操作volatile int。

2)FastThreadLocal,相比jdk的实现更快

  • 线性探测的Hash表 —> index原子自增的裸数组存储。

3)IntObjectHashMap / LongObjectHashMap …

  • Integer—> int
  • Node[] —> 裸数组

4)RecyclableArrayList

  • 基于前面说的Recycler,频繁new ArrayList的场景可考虑。

5)JCTools

  • 一些jdk没有的 SPSC/MPSC/SPMC/MPMC 无锁并发队以及NonblockingHashMap(可以对比ConcurrentHashMapV6/V8)

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513153.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言rec文件如何打开,REC 文件扩展名: 它是什么以及如何打开它?

REC 疑难解答典型的 REC 开放挑战Squared 5 MPEG Streamclip 不在你尝试加载 REC 文件并收到错误&#xff0c;例如 “%%os%% 无法打开 REC 文件扩展名”。 通常&#xff0c;这是因为你没有安装适用于 %%os%% 的 Squared 5 MPEG Streamclip。 操作系统不知道如何处理你的 REC 文…

Quick BI:降低使用门槛,大东鞋业8000家门店的数据导航

简介&#xff1a; 通过引入MaxCompute和Quick BI&#xff0c;大东解决了以往数据查询即刻导致数据库闪崩的状况&#xff0c;还搭建起完善的报表体系&#xff0c;稳定应对高频、高并发的数据分析。 大东鞋业一季大约有500款的新品。大区下辖的各个分公司要对这500款新品进行订货…

绿盟科技发布安全知识图谱技术白皮书

随着全球数字化和万物互联的加速发展&#xff0c;近年来勒索病毒的攻击手段不断升级&#xff0c;例如今年美国最大燃油管道受攻击导致美国17个州和华盛顿特区进入紧急状态&#xff0c;引起全球关注。当下企业如何提高安全运营知识以面对不断升级的威胁攻击&#xff1f; 近日&a…

「应用管理与交付」为什么会成为云原生新的价值聚焦点?

简介&#xff1a; 为什么“云原生应用管理与交付”会成为 Kubernetes 之上重要的价值聚焦点&#xff1f;CNCF App Delivery SIG 在推动 Kubernetes 之上应用层技术快速演进的过程中将扮演什么角色&#xff1f;这个领域又将发生哪些值得大家期待的创新&#xff1f;让我们一起了解…

android 4.4官方下载,安卓系统电脑版-android x86 4.4 iso下载官方最新版【pc版】-西西软件下载...

Android-X86是由Beyounn和Cwhuang主持设计的。提供了一套完整的可行源代码树&#xff0c;配套文档以及Live CD与Live USB。Android系统主要应用在智能手机以及平板电脑设备上。日前&#xff0c;越来越多使用英特尔和AMD处理器的计算机也开始运行Android系统。如何才 能让Androi…

dataframe iloc_如何使用iloc和loc 对Pandas Dataframe进行索引和切片

在这篇文章中&#xff0c;我们将使用iloc和loc来处理数据。更具体地说&#xff0c;我们将通过iloc和loc例子来学习切片和索引。一旦我们将一个数据集加载为Pandas dataframe&#xff0c;我们通常希望根据某些条件开始访问该数据的特定部分。例如&#xff0c;如果我们的数据集包…

Flink 和 Iceberg 如何解决数据入湖面临的挑战

简介&#xff1a; 4.17 上海站 Meetup 胡争老师分享内容&#xff1a;数据入湖的挑战有哪些&#xff0c;以及如何用 Flink Iceberg 解决此类问题。 一、数据入湖的核心挑战 数据实时入湖可以分成三个部分&#xff0c;分别是数据源、数据管道和数据湖&#xff08;数仓&#xf…

高并发下的 HashMap 为什么会死循环

作者 | tech-bus.七十一来源 | 程序员巴士前言HashMap并发情况下产生的死循环问题在JDK 1.7及之前版本是存在的&#xff0c;JDK 1.8 通过增加loHead头节点和loTail尾节点进行了修复&#xff0c;虽然进行了修复&#xff0c;但是如果涉及到并发情况下需要使用hash表&#xff0c;建…

唯品会:在 Flink 容器化与平台化上的建设实践

简介&#xff1a; 唯品会 Flink 的容器化实践应用&#xff0c;Flink SQL 平台化建设&#xff0c;以及在实时数仓和实验平台上的应用案例。 转自dbaplus社群公众号 作者&#xff1a;王康&#xff0c;唯品会数据平台高级开发工程师 自 2017 年起&#xff0c;为保障内部业务在平…

python怎么变成exe_Python怎样打包成exe?

分类&#xff1a;Python &#xff5c; 作者&#xff1a;凹凸曼 &#xff5c; 发表于2011/03/01Python怎样打包成exe&#xff1f;已关闭评论 发现PyInstaller 是个不错的东东&#xff0c;解决打包单个exe的问题&#xff0c;使用非常简单&#xff0c;不用编写setup脚本&#xff1…

PolarDB-X 2.0:使用一个透明的分布式数据库是一种什么体验

简介&#xff1a; 透明分布式&#xff0c;是PolarDB-X即将发布的能力&#xff0c;它能让应用在使用PolarDB-X的过程中&#xff0c;犹如使用单机数据库一般的体验。与传统的中间件类型的“分布式数据库”相比&#xff0c;有了透明分布式能力的PolarDB-X&#xff0c;不再需要应用…

Chrome 96 又更新了 5 个巨巨巨好用的功能

作者 | 零一来源 | 前端印象‍‍‍‍‍‍‍大家好&#xff0c;收到了 Chrome 96 版本的更新推送&#xff0c;简单看了一下&#xff0c;还是更新了几个挺有趣的东西的&#xff0c;一起来看看到底都有啥~先下载 Chrome Beta 版本才能体验 Chrome 96 哈Chrome Beta我们顺便来给每个…

编译优化 | LLVM代码生成技术详解及在数据库中的应用

简介&#xff1a; 作者&#xff1a;长别 1. 前言 随着IT基础设施的发展&#xff0c;现代的数据处理系统需要处理更多的数据、支持更为复杂的算法。数据量的增长和算法的复杂化&#xff0c;为数据分析系统带来了严峻的性能挑战。近年来&#xff0c;我们可以在数据库、大数据系…

低代码发展专访系列之二:两三年内会出现“现象级”低代码产品吗?

前言&#xff1a;2019年开始&#xff0c;低代码爆火。有人认为它是第四代编程语言&#xff0c;有人认为它是开发模式的颠覆&#xff0c;也有人认为是企业管理模式的变革……有很多声音&#xff0c;社区讨论很热烈。CSDN 随后展开低代码平台产品系列活动&#xff0c;包括低代码开…

为什么Spring仍然会是云原生时代最佳平台之一?

简介&#xff1a; 基于Java语言的Spring生态&#xff0c;还能否适应新的开发方式&#xff0c;比如Cloud Native、Serverless、Faas等&#xff0c;它还会是云原生时代的最佳平台的选择吗&#xff1f;本文将从5个角度来为你分析一下这个问题&#xff0c;分别是&#xff1a;Java和…

贾又福大象鸿蒙,奏乐!继续吹!库里又创记录,射进MVP榜单,众多名记变“库吹“...

库里本月已投进85记三分 打破哈登保持的NBA单月三分命中数纪录加上今天的7记三分&#xff0c;库里本月已经投进85记三分&#xff0c;创造了新的NBA单月(自然月)三分命中数纪录。勇士本月还有两场比赛。此前&#xff0c;哈登曾单月82记三分。在NBA历史单月三分球命中数前三榜单中…

opencv4 图像特征匹配_概述 | 全景图像拼接技术全解析

点击上方蓝字关注我们微信公众号&#xff1a;OpenCV学堂关注获取更多计算机视觉与深度学习知识前言图像/视频拼接的主要目的是为了解决相机视野(FOV-Field Of View)限制&#xff0c;生成更宽的FOV图像/视频场景。视频拼接在体育直播、全景显示、数字娱乐、视频处理中都被广泛应…

数字化让618有了洞悉消费者内心的“大脑”

简介&#xff1a; 阿里云数据中台已形成包括会员智能运营、全域天攻智投、GMV策略模拟等在内的近10套解决方案&#xff0c;围绕“人”“货”“场”三大零售行业要素&#xff0c;逐个击破品牌业务难点&#xff0c;记者了解到&#xff0c;过去一年&#xff0c;悦诗风吟、Benefit、…

赋能工业互联网融合发展 | 北京信息化和工业化融合服务联盟平台化设计专业委员会、中国仿真学会CAE仿真专业委员会成立

11月28日&#xff0c;由北京市经济和信息化局指导&#xff0c;北京信息化和工业化融合服务联盟与中国仿真学会共同主办&#xff0c;联盟平台化设计专业委员会、中国仿真学会CAE仿真专业委员会、国家数字化设计与制造创新中心北京中心、北京数字化设计与制造产业创新中心共同承办…

升级鸿蒙系统有没有翻车,被寄予厚望的华为鸿蒙系统,这次要翻车?原来并不是我们想的那样...

华为鸿蒙系统早在去年就已经被正式发布&#xff0c;但那时的人们对这个操作系统还不熟悉。但近期华为又在其发布会上发布了鸿蒙OS2.0&#xff0c;并表示到了2021年华为手机将全面使用鸿蒙2.0。这消息一出&#xff0c;不少华为用户忍不住想去尝尝鲜&#xff0c;纷纷都将系统更新…