logo头像

Always believe youself.

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

什么是 Netty? 能做什么?

  • Netty 是一个致力于创建高性能网络应用程序的成熟的 IO 框架。
  • 相比较与直接使用底层的 Java IO API,你不需要先成为网络专家就可以基于 Netty 去构建复杂的网络应用。
  • 业界常见的涉及到网络通信的相关中间件大部分基于 Netty 实现网络层。

设计一个分布式服务框架

Architecture

image

远程调用的流程

  • 启动服务端(服务提供者)并发布服务到注册中心。

  • 启动客户端(服务消费者)并去注册中心订阅感兴趣的服务。

  • 客户端收到注册中心推送的服务地址列表。

  • 调用者发起调用,Proxy从服务地址列表中选择一个地址并将请求信息 <group,providerName,version>,methodName,args[] 等信息序列化为字节数组并通过网络发送到该地址上。

  • 服务端收到收到并反序列化请求信息,根据 <group,providerName,version> 从本地服务字典里查找到对应providerObject,再根据 <methodName,args[]> 通过反射调用指定方法,并将方法返回值序列化为字节数组返回给客户端。

  • 客户端收到响应信息再反序列化为 Java 对象后由 Proxy 返回给方法调用者。

远程调用客户端图解

image

重要概念:RPC三元组 <ID,Request,Response>。

PS: 若是 netty4.x 的线程模型,IO Thread(worker) —> Map<InvokeId,Future> 代替全局 Map 能更好的避免线程竞争。

远程调用服务端图解

image

远程调用传输层图解

image

设计传输层协议栈

协议头

image

协议体

  • metadata: <group,providerName,version>

  • methodName

  • parameterTypes[] 真的需要吗?

    • 有什么问题?

      反序列化时 ClassLoader.loadClass() 潜在锁竞争。

      协议体码流大小。

      泛化调用多了参数类型。

    • 能解决吗?

      Java方法静态分派规则参考JLS <Java语言规范> $15.12.2.5 Choosing the Most Specific Method 章节。

    • args[]

    • 其他:traceId,appName…

一些Features&好的实践&压榨性能

创建客户端代理对象

1)Proxy 做什么?

- 集群容错 —> 负载均衡 —> 网络

2)有哪些创建 Proxy 的方式?

- jdk proxy/javassist/cglib/asm/bytebuddy

3)要注意的:

- 注意拦截toString,equals,hashCode等方法避免远程调用。

4)推荐的(bytebuddy):

image

优雅的同步/异步调用

  • 先往上翻再看看“远程调用客户端图解”

  • 再往下翻翻看看 Failover 如何处理更好

  • 思考下如何拿到 future?

单播/组播

  • 消息派发器

  • FutureGroup

泛化调用

  • Object $invoke(String methodName,Object… args)

  • parameterTypes[]

序列化/反序列化

  • 协议 header 标记 serializer type,同时支持多种。

可扩展性

Java SPI:

java.util.ServiceLoader

META-INF/services/com.xxx.Xxx

服务级别线程池隔离

  • 要挂你先挂,别拉着我。

责任链模式的拦截器

  • 太多扩展需要从这里起步。

指标度量(Metrics)

链路追踪

  • OpenTracing

注册中心

流控(应用级别/服务级别)

  • 要有能方便接入第三方流控中间件的扩展能力。

Provider线程池满了怎么办?

image

软负载均衡

  • 加权随机 (二分法,不要遍历)

image

  • 加权轮训(最大公约数)
    image

  • 最小负载

  • 一致性 hash (有状态服务场景)

  • 其他

注意:要有预热逻辑

集群容错

  • Fail-fast

  • Failover

  • Fail-safe

  • Fail-back

  • Forking

  • 其他

Why Netty?

BIO vs NIO

image

Java 原生 NIO API 从入门到放弃

  • 复杂度高
  • 稳定性差,坑多且深
  • NIO 代码实现方面的一些缺点
    • Selector.selectedKeys() 产生太多垃圾
    • fdToKey 映射
    • Selector在linux 平台是 Epoll LT 实现
    • Direct Buffers 事实上还是由 GC 管理

Netty 的真实面目

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 对应着一个网络连接。

Netty4 Thread Model

image

ChannelPipeline

image

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 的影响?

Netty Native Transport

多路复用简介

未完!!!