聊聊服务发现的推拉模型

前言

过去一年,我的工作重心投入到了 API 网关(阿里云 CSB)中,这对于我来说是一个新的领域,但和之前接触的微服务治理方向又密不可分。API 网关适配微服务场景需要完成一些基础能力的建设,其一便是对接注册中心,从而作为微服务的入口流量,例如 Zuul、SpringCloud Gateway 都实现了这样的功能。实际上很多开源网关在这一特性上均存在较大的局限性,本文暂不讨论这些局限性,而是针对服务发现这一通用的场景,分享我对它的一些思考。


聊聊服务治理中的路由设计

前言

路由(Route)的设计广泛存在于众多领域,以 RPC 框架 Dubbo 为例,就有标签路由、脚本路由、权重路由、同机房路由等实现。

在框架设计层面,路由层往往位于负载均衡层之前,在进行选址时,路由完成的是 N 选 M(M <= N),而负载均衡完成的是 M 选一,共同影响选址逻辑,最后触发调用。

在业务层面,路由往往是为了实现一定的业务语义,对流量进行调度,所以服务治理框架通常提供的都是基础的路由扩展能力,使用者根据业务场景进行扩展。

路由过程

今天这篇文章将会围绕路由层该如何设计展开。


Dubbo中的连接控制,你真的理解吗?

前言

这是一篇很久之前就想动笔写的文章,最近正好看到群里有小伙伴分享了 Dubbo 连接相关的文章,才又让我想起了这个话题。今天想跟大家聊的便是 Dubbo 中的连接控制这一话题。说到“连接控制”,可能有读者还没反应过来,但你对下面的配置可能不会感到陌生:

1
<dubbo:reference interface="com.foo.BarService" connections="10" />

如果你还不了解 Dubbo 中连接控制的用法,可以参考官方文档:https://dubbo.apache.org/zh/docs/advanced/config-connections/ ,话说最近 Dubbo 官方文档来了一次大换血,好多熟悉的文档差点都没找到在哪儿 Orz。

众所周知,dubbo 协议通信默认是长连接,连接配置功能用于决定消费者与提供者建立的长连接数。但官方文档只给出了该功能的使用方法,却并没有说明什么时候应该配置连接控制,本文将主要围绕该话题进行探讨。

本文也会涉及长连接相关的一些知识点。


Dubbo 支持的几个主流序列化框架评测

前言

今天要聊的技术是序列化,这不是我第一次写序列化相关的文章了,今天动笔之前,我还特地去博客翻了下我博客早期的一篇序列化文章(如下图),竟然都过去 4 年了。

历史记录

为什么又想聊序列化了呢?因为最近的工作用到了序列化相关的内容,其次,这几年 Dubbo 也发生了翻天覆地的变化,其中 Dubbo 3.0 主推的 Tripple 协议,更是打着下一代 RPC 通信协议的旗号,有取代 Dubbo 协议的势头。而 Tripple 协议使用的便是 Protobuf 序列化方案。

另外,Dubbo 社区也专门搞了一个序列化压测的项目:https://github.com/apache/dubbo-benchmark.git ,本文也将围绕这个项目,从性能维度展开对 Dubbo 支持的各个序列化框架的讨论。

当我们聊序列化的时候,我们关注什么?

最近几年,各种新的高效序列化方式层出不穷,最典型的包括:

  • 专门针对 Java 语言的:JDK 序列化、Kryo、FST
  • 跨语言的:Protostuff,ProtoBuf,Thrift,Avro,MsgPack 等等

为什么开源社区涌现了这么多的序列化框架,Dubbo 也扩展了这么多的序列化实现呢?主要还是为了满足不同的需求。

序列化框架的选择主要有以下几个方面:

  1. 跨语言。是否只能用于 java 间序列化 / 反序列化,是否跨语言,跨平台。
  2. 性能。分为空间开销和时间开销。序列化后的数据一般用于存储或网络传输,其大小是很重要的一个参数;解析的时间也影响了序列化协议的选择,如今的系统都在追求极致的性能。
  3. 兼容性。系统升级不可避免,某一实体的属性变更,会不会导致反序列化异常,也应该纳入序列化协议的考量范围。

和 CAP 理论有点类似,目前市面上很少有一款序列化框架能够同时在三个方面做到突出,例如 Hessian2 在兼容性方面的表现十分优秀,性能也尚可,Dubbo 便使用了其作为默认序列化实现,而性能方面它其实是不如 Kryo 和 FST 的,在跨语言这一层面,它表现的也远不如 ProtoBuf,JSON。

其实反过来想想,要是有一个序列化方案既是跨语言的,又有超高的性能,又有很好的兼容性,那不早就成为分布式领域的标准了?其他框架早就被干趴了。

大多数时候,我们是挑选自己关注的点,找到合适的框架,满足我们的诉求,这才导致了序列化框架百花齐放的局面。

性能测试

很多序列化框架都宣称自己是“高性能”的,光他们说不行呀,我还是比较笃信“benchmark everything”的箴言,这样得出的结论,更能让我对各个技术有自己的认知,避免人云亦云,避免被不是很权威的博文误导。

怎么做性能测试呢?例如像这样?

1
2
3
long start = System.currentTimeMillis();
measure();
System.out.println(System.currentTimeMillis()-start);

貌似不太高大上,但又说不上有什么问题。如果你这么想,那我推荐你了解下 JMH 基准测试框架,我之前写过的一篇文章《JAVA 拾遗 — JMH 与 8 个测试陷阱》推荐你先阅读以下。

事实上,Dubbo 社区的贡献者们早就搭建了一个比较完备的 Dubbo 序列化基础测试工程:https://github.com/apache/dubbo-benchmark.git。

dubbo-benchmark

你只要具备基本的 JMH 和 Dubbo 的知识,就可以测试出在 Dubbo 场景下各个序列化框架的表现。

我这里也准备了一份我测试的报告,供读者们参考。如果大家准备自行测试,不建议在个人 windows/mac 上 benchmark,结论可能会不准确。我使用了两台阿里云的 ECS 来进行测试,测试环境:Aliyun Linux,4c8g,启动脚本:

1
java -server -Xmx2g -Xms2g -XX:MaxDirectMemorySize=1g -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/home/admin/

为啥选择这个配置?我手上正好有两台这样的资源,没有特殊的设置~,况且从启动脚本就可以看出来,压测程序不会占用太多资源,我都没用满。

测试工程介绍:

1
2
3
4
5
6
7
8
9
public interface UserService {
public boolean existUser(String email);

public boolean createUser(User user);

public User getUser(long id);

public Page<User> listUser(int pageNo);
}

一个 UserService 接口对业务应用中的 CRUD 操作。server 端以不同的序列化方案提供该服务,client 使用 JMH 进行多轮压测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Benchmark
@BenchmarkMode({Mode.Throughput })
@OutputTimeUnit(TimeUnit.SECONDS)
@Override
public boolean existUser() throws Exception {
// ...
}

@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@Override
public boolean createUser() throws Exception {
// ...
}

@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@Override
public User getUser() throws Exception {
// ...
}

@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@Override
public Page<User> listUser() throws Exception {
// ...
}

整体的 benchmark 框架结构如上,详细的实现,可以参考源码。我这里只选择的一个评测指标 Throughput,即吞吐量。

省略一系列压测过程,直接给出结果:

Kryo

1
2
3
4
5
Benchmark           Mode  Cnt      Score      Error  Units
Client.createUser thrpt 3 20913.339 ± 3948.207 ops/s
Client.existUser thrpt 3 31669.871 ± 1582.723 ops/s
Client.getUser thrpt 3 29706.647 ± 3278.029 ops/s
Client.listUser thrpt 3 17234.979 ± 1818.964 ops/s

Fst

1
2
3
4
5
Benchmark           Mode  Cnt      Score       Error  Units
Client.createUser thrpt 3 15438.865 ± 4396.911 ops/s
Client.existUser thrpt 3 25197.331 ± 12116.109 ops/s
Client.getUser thrpt 3 21723.626 ± 7441.582 ops/s
Client.listUser thrpt 3 15768.321 ± 11684.183 ops/s

Hessian2

1
2
3
4
5
Benchmark           Mode  Cnt      Score      Error  Units
Client.createUser thrpt 3 22948.875 ± 2005.721 ops/s
Client.existUser thrpt 3 34735.122 ± 1477.339 ops/s
Client.getUser thrpt 3 20679.921 ± 999.129 ops/s
Client.listUser thrpt 3 3590.129 ± 673.889 ops/s

FastJson

1
2
3
4
5
Benchmark           Mode  Cnt      Score      Error  Units
Client.createUser thrpt 3 26269.487 ± 1667.895 ops/s
Client.existUser thrpt 3 29468.687 ± 5152.944 ops/s
Client.getUser thrpt 3 25204.239 ± 4326.485 ops/s
Client.listUser thrpt 3 9823.574 ± 2087.110 ops/s

Tripple

1
2
3
4
5
Benchmark           Mode  Cnt      Score       Error  Units
Client.createUser thrpt 3 19721.871 ± 5121.444 ops/s
Client.existUser thrpt 3 35350.031 ± 20801.169 ops/s
Client.getUser thrpt 3 20841.078 ± 8583.225 ops/s
Client.listUser thrpt 3 4655.687 ± 207.503 ops/s

怎么看到这个测试结果呢?createUser、existUser、getUser 这几个方法测试下来,效果是参差不齐的,不能完全得出哪个框架性能最优,我的推测是因为序列化的数据量比较简单,量也不大,就是一个简单的 User 对象;而 listUser 的实现是返回了一个较大的 List<User> ,可以发现,Kryo 和 Fst 序列化的确表现优秀,处于第一梯队;令我意外的是 FastJson 竟然比 Hessian 还要优秀,位列第二梯队;Tripple(背后是 ProtoBuf)和 Hessian2 位列第三梯队。

当然,这样的结论一定受限于 benchmark 的模型,测试用例中模拟的 CRUD 也不一定完全贴近业务场景,毕竟业务是复杂的。

怎么样,这样的结果是不是也符合你的预期呢?

Dubbo 序列化二三事

最后,聊聊你可能知道也可能不知道的一些序列化知识。

hession-lite

Dubbo 使用的 Hessian2 其实并不是原生的 Hessian2 方案。注意看源码中的依赖:

1
2
3
4
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>hessian-lite</artifactId>
</dependency>

最早是阿里开源的 hessian-lite,后来随着 Dubbo 贡献给了 Apache,该项目也一并进入了 Apache,github 地址:https://github.com/apache/dubbo-hessian-lite。相比原生 Hessian2,Dubbo 独立了一个仓库致力于在 RPC 场景下,发挥出更高的性能以及满足一些定制化的需求。

在 IO 线程中进行序列化

Dubbo 客户端在高版本中默认是在业务线程中进行序列化的,而不是 IO 线程,你可以通过 decode.in.io 控制序列化与哪个线程绑定

1
2
3
4
5
<dubbo:reference id="userService" check="false"
interface="org.apache.dubbo.benchmark.service.UserService"
url="dubbo://${server.host}:${server.port}">
<dubbo:parameter key="decode.in.io" value="true" />
</dubbo:reference>

在 benchmark 时,我发现 IO 线程中进行序列化,性能会更好,这可能和序列化本身是一个耗费 CPU 的操作,多线程无法加速反而会导致更多的竞争有关。

SerializationOptimizer

某些序列化实现,例如 Kryo 和 Fst 可以通过显示注册序列化的类来进行加速,如果想利用该特性来提升序列化性能,可以实现 org.apache.dubbo.common.serialize.support.SerializationOptimizer 接口。一个示例:

1
2
3
4
5
6
public class SerializationOptimizerImpl implements SerializationOptimizer {
@Override
public Collection<Class<?>> getSerializableClasses() {
return Arrays.asList(User.class, Page.class, UserService.class);
}
}

按照大多数人的习惯,可能会觉得这很麻烦,估计很少有用户这么用。注意客户端和服务端需要同时开启这一优化。

别忘了在 protocol 上配置指定这一优化器:

1
<dubbo:protocol name="dubbo" host="${server.host}" server="netty4" port="${server.port}" serialization="kryo" optimizer="org.apache.dubbo.benchmark.serialize.SerializationOptimizerImpl"/>

序列化方式由服务端指定

一般而言,Dubbo 框架使用的协议(默认是 dubbo)和序列化方式(默认是 hessian2)是由服务端指定的,不需要在消费端指定。因为服务端是服务的提供者,拥有对服务的定义权,消费者在订阅服务收到服务地址通知时,服务地址会包含序列化的实现方式,Dubbo 以这样的契约方式从而实现 consumer 和 provider 的协同通信。

在大多数业务应用,应用可能既是服务 A 的提供者,同时也是服务 B 的消费者,所以建议在架构决策者层面协商固定出统一的协议,如果没有特殊需求,保持默认值即可。

但如果应用仅仅作为消费者,而又想指定序列化协议或者优化器(某些特殊场景),注意这时候配置 protolcol 是不生效的,因为没有服务提供者是不会触发 protocol 的配置流程的。可以像下面这样指定消费者的配置:

1
2
3
4
5
<dubbo:reference id="userService" check="false"
interface="org.apache.dubbo.benchmark.service.UserService"
url="dubbo://${server.host}:${server.port}?optimizer=org.apache.dubbo.benchmark.serialize.SerializationOptimizerImpl&amp;serialization=kryo">
<dubbo:parameter key="decode.in.io" value="true" />
</dubbo:reference>

&amp; 代表 &,避免 xml 中的转义问题

总结

借 Dubbo 中各个序列化框架的实现,本文探讨了选择序列化框架时我们的关注点,并探讨了各个序列化实现在 Dubbo 中具体的性能表现, 给出了详细的测试报告,同时,也给出了一些序列化的小技巧,如果在 Dubbo 中修改默认的序列化行为,你可能需要关注这些细节。

最后再借 Dubbo3 支持的 Tripple 协议来聊一下技术发展趋势的问题。我们知道 json 能替代 xml 作为众多前后端开发者耳熟能详的一个技术,并不是因为其性能如何如何,而是在于其恰如其分的解决了大家的问题。一个技术能否流行,也是如此,一定在于其帮助用户解决了痛点。至于解决了什么问题,在各个历史发展阶段又是不同的,曾经,Dubbo2.x 凭借着其丰富的扩展能力,强大的性能,活跃度高的社区等优势帮助用户解决一系列的难题,也获得了非常多用户的亲来;现在,Dubbo3.x 提出的应用级服务发现、统一治理规则、Tripple 协议,也是在尝试解决云原生时代下的难题,如多语言,适配云原生基础设施等,追赶时代,帮助用户。


Netty实现长连接服务的难点和优化点

转载自:https://www.dozer.cc/2014/12/netty-long-connection.html

原文作者:dozer

推送服务

还记得一年半前,做的一个项目需要用到 Android 推送服务。和 iOS 不同,Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging ,但是连国外都没统一,更别说国内了,直接被墙。

所以之前在 Android 上做推送大部分只能靠轮询。而我们之前在技术调研的时候,搜到了 jPush 的博客,上面介绍了一些他们的技术特点,他们主要做的其实就是移动网络下的长连接服务。单机 50W-100W 的连接的确是吓我一跳!后来我们也采用了他们的免费方案,因为是一个受众面很小的产品,所以他们的免费版够我们用了。一年多下来,运作稳定,非常不错!

时隔两年,换了部门后,竟然接到了一项任务,优化公司自己的长连接服务端。

再次搜索网上技术资料后才发现,相关的很多难点都被攻破,网上也有了很多的总结文章,单机 50W-100W 的连接完全不是梦,其实人人都可以做到。但是光有连接还不够,QPS 也要一起上去。

所以,这篇文章就是汇总一下利用 Netty 实现长连接服务过程中的各种难点和可优化点。


Dubbo 基础教程:使用 Nacos 实现服务注册与发现

什么是 Nacos

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

在接下里的教程中,将使用 Nacos 作为微服务架构中的注册中心,替代 ZooKeeper 传统方案。


Dubbo 迈向云原生的里程碑 | 应用级服务发现

1 概述

社区版本 Dubbo 从 2.7.5 版本开始,新引入了一种基于应用粒度的服务发现机制,这是 Dubbo 为适配云原生基础设施的一步重要探索。版本发布到现在已有近半年时间,经过这段时间的探索与总结,我们对这套机制的可行性与稳定性有了更全面、深入的认识;同时在 Dubbo 3.0 的规划也在全面进行中,如何让应用级服务发现成为未来下一代服务框架 Dubbo 3.0 的基础服务模型,解决云原生、规模化微服务集群扩容与可伸缩性问题,也已经成为我们当前工作的重点。

既然这套新机制如此重要,那它到底是怎么工作的,今天我们就来详细解读一下。在最开始的社区版本,我们给这个机制取了一个神秘的名字 - 服务自省,下文将进一步解释这个名字的由来,并引用服务自省代指这套应用级服务发现机制。

熟悉 Dubbo 开发者应该都知道,一直以来都是面向 RPC 方法去定义服务的,并且这也是 Dubbo 开发友好性、治理功能强的基础。既然如此,那我们为什么还要定义个应用粒度的服务发现机制那?这个机制到底是怎么工作的?它与当前机制的区别是什么?它能给我们带来哪些好处那?对适配云原生、性能提升又有哪些帮助?

带着所有的这些问题,我们开始本文的讲解。


平滑迁移 Dubbo 服务的思考

前言

近日,有报道称在 HashCorp 的商业软件试用协议上发现,旗下所有商业产品禁止在中国境内使用、部署、安装,这其中就包含了 Terraform, Consul, Vagrant 等众多知名软件,其中 Consul 是一个在微服务领域的开源软件,可以用于做注册发现、配置管理等场景。

该新闻在国内发酵后,有人在 Twitter上咨询了HashCorp 公司的创始人,得到的回复是影响的软件仅限于 Vault 这款加密软件,目前 HashCorp 公司的官方网站上已经更新了相关的条款,明确了受影响的产品仅限 Vault 这一款产品。

Consul 开源版是否收到影响?

上面的条款里只提到了商业软件,那么开源的 Consul 是否受到影响呢?在 Github 的 Consul 仓库上,可以得知项目的 license 是 Mozilla Public License 2.0 ,这款许可证在 Apache 官网上是 Category B , 属于 Weak Copy Left 许可,那么它有哪些特点呢?

License

  1. 任何可以使用,复制,修改,重新分发该代码,包括商业目的使用。
  2. 如果修改了 MPL 协议许可下的源码,再重新发布这部分源码的话,必须保留原来 MPL 许可证不得更换。
  3. 如果基于该项目衍生出更大的项目,那么这部分工作可以使用新许可证的方式进行分发,只要没有修改原来 MPL 许可下的代码。(这也是为什么 Apache 项目的分发的源码中可以包含 MPL 协议下二进制文件的原因)

可以看到,MPL 通常被认为是介于 Apache License 和 GPL/LGPL 之间的一个折中方案。相对于 Apache License,MPL 2.0 要求修改了源码必须保持相同协议;相对于 GPL/LGPL, MPL 2.0 可以商用,同时衍生的作品在一定条件下也可以更换许可证类型。

总体来看的话,开源版 Consul 无论是私用还是商用都是不受限制的。但这也可能是一个警钟,如果对 Consul 还是有所顾忌的话,如何替代掉它呢?

在微服务领域,Consul 主要被用来做充当注册中心和配置中心,例如 Dubbo 和 SpringCloud 都有对应的支持。本文便以这个事为一个引子,介绍如何平滑地迁移 Dubbo 服务,达到替换注册中心的效果。


EDAS 微服务治理解密

前言

2020 是多事的一年,新冠状性病毒的肆虐,其次是自己也生了一场病,希望随着天气暖和起来,一起都能变得更好。

前一段时间真的很忙,一直没有抽出时间,也没有什么思路给大家分享优质的文章,今天这篇文章很久之前就想写了,抓住这次假期的尾巴,总结一下我最近这一年的工作。


一文聊透 Dubbo 优雅上线

1 前言

在此文之前,我写过一篇 《一文聊透 Dubbo 优雅停机》,这篇文章算是一个续集,优雅停机和优雅上线两者都是微服务生命周期中,开发者必须关心的环节。

优雅上线还有很多称呼:「无损上线」,「延迟发布」,「延迟暴露」。它们的对立面自然是:「有损上线」,「直接发布」。

我最近写的「一文聊透 Dubbo xx」系列文章,都有一个特点,即当你不注重文章中实践,你的 Dubbo 应用依旧可以正常运行,但总归在某些场景 case 下,你的系统会出现问题。做不到优雅上线,你的系统将会出现:在应用刚启动时,就有流量进入,而此时应用尚未初始化完毕,导致调用失败,在集群规模较大时,影响会变得很明显。


Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×