暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【第一弹】详解分布式中间件Dubbo

佑佑有话说 2021-03-13
863

概述: 本系列文章将给大家详细讲解分布式系统中Dubbo框架,主要内容包括Dubbo相关概念、架构设计的演变、注册中心Zookeeper、Dubbo的高级特性、Dubbo源码分析、Dubbo中的设计模式和Dubbo项目实战。

系列作者:xiao xi yuan

内容整理:佑佑小哥哥

Dubbo知识点思维导图:

一、Dubbo相关概念

1. 分布式

分布式是指把一个系统拆分为多个子系统,每个子系统负责各自的那部分功能,独立部署,各司其职。简单来说,分布式就是多个系统在一起作不同的事。

2. 集群

集群是指多个实例共同工作,最简单或最常见的集群是把一个应用复制多份部署。简单来说,集群就是多个系统在一起作同样的事 。
常见的集群模式有:
主从集群:一主多从,主从各司其职,主节点负责资源分配和任务调度,从节点负责具体的执行;
主备集群:一主一备,主要是为了解决单点故障问题,便于备份恢复;
注意:分布式的核心体现是拆分;集群的核心体现是复制;所以分布式一定是集群,但集群不一定是分布式。

3. RPC

RPC(remote procedure call)即远程过程调用,比如发送了一个查询用户明细的请求get/userInfo?userId=1234,实际上是调用了服务端的一个方法userInfo(Integer userId)。即在本地电脑上可以调用一个远程服务端的方法,所以叫远程过程调用。这里的"远"也不一定是跨越网络的,也可以是同一台主机的两个进程之间的相互交流。RPC并不是一个具体的技术,它指定了一种通信方式,是一种服务间通信的理念。在java中RPC的落地框架比较多,常见的有Hessian、gRPC、Thrift、HSF (High-Speed Service Framework)、Dubbo等。
【Hessian】
Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。基于HTTP协议,采用二进制编解码。
【gRPC】
gRPC是Google 公布的开源软件,基于最新的HTTP 2.0协议,并支持常见的众多编程语言。
【Thrift】
Thrift是Facebook的开源 RPC 框架,主要是一个跨语言的服务开发框架。它拥有功能强大的代码生成引擎,无缝地支持C++,C#,Java,Python,PHP和Ruby。Thrift允许你定义一个描述文件,描述数据类型和服务接口。依据该文件,编译器方便地生成RPC客户端和服务器通信代码。
Thrift最初由Facebook开发用做系统内部语言之间的RPC通信,2007年由Facebook贡献到apache组织,现在是apache下的opensource之一 。支持多种语言之间的RPC方式的通信,比如PHP语言client可以构造一个对象,调用相应的服务方法来调用Java语言的服务,跨越语言的C/S RPC调用。底层通讯基于SOCKET。
【HSF】
HSF全称为High-Speed Service Framework,是一个分布式的服务框架,HSF从分布式应用层面以及统一的发布/调用方式层面提供服务支持,从而可以很容易的开发分布式的应用以及提供或使用公用功能模块,而不用考虑分布式领域中的各种细节技术,例如远程通讯、性能损耗、调用的透明化、同步/异步调用方式的实现等问题。
【Dubbo】
Dubbo框架是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成。

4. Restful API

RESTful是一种架构风格,其最大的几个特点为:资源、统一接口、URI 和无状态。
资源: 所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,就是一个具体的实体。
统一接口: RESTful架构风格规定,数据的元操作,即 CRUD(Create,Read,Update和Delete,即数据的增查改删)操作,分别对应于HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口,仅通过 HTTP方法,就可以完成对数据的所有增删查改工作。
URL: 可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源,要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。
无状态: 所谓无状态的,即所有的资源,都可以通过URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而改变。有状态和无状态的区别,举例如下:
比如查询员工的工资,如果查询工资是需要登录系统,进入查询工资的页面,执行相关操作后,获取工资的多少,则这种情况是有状态的。因为查询工资的每一步操作都依赖于前一步操作,只要前置操作不成功,后续操作就无法执行。如果输入一个URI即可得到指定员工的工资,则这种情况是无状态的,因为获取工资不依赖于其他资源或状态。且这种情况下,员工工资是一个资源,由一个URI与之对应,可以通过HTTP中的GET方法得到资源,这是典型的RESTful风格。
注意: RPC侧重于动作,即RPC可以通过动作去操作资源;RESTful侧重于资源,是面向资源的设计架构;在传输效率方面,RPC效率更高,RPC使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率;但RPC实现复杂,流程繁琐;REST调用及测试都很方便。

5. 长连接和短连接

短连接: HTTP1.0中每一个请求和响应都会触发一次TCP连接的建立和断开,此为短连接。
长连接: 在HTTP1.1中增加了keep-alive机制,允许在一个TCP连接上发送多个请求和响应,此为长连接。通俗点说,就是建立连接过后可以持续发送请求,无须再建立连接。HTTP请求头部中的Connection标识是否使用长连接,当为Connection: keep-alive时,表明使用长连接(通常无需设置,默认);当不希望使用长连接,则设为Connection: close。


Dubbo服务调用连接是长连接,Dubbo服务调用是小数据量的通信,针对每一次RPC通信,都会生成一个唯一的id来标识,这样就能区分出一次RPC请求对应的RPC响应了。
长连接和短连接的性能比较:
如果二者的限制条件一样(比如都是同步方式或IO复用方式、同等数据量通信、连接个数相同)情况下,长连接性能理论上要优于短连接,因为前者省去了三次握手和四次挥手过程。
如果二者限制条件不同,绝大多数场景的限制条件都是不同的,如果通信较频繁,使用长连接性能较好;如果通信次数较少,使用短连接更合适,毕竟不用维护长连接。

6. Dubbo框架

Dubbo官网:http://dubbo.apache.org/
官网上说,Apache Dubbo是一款高性能Java RPC框架。所以这里必须指出,我们经常所说的Dubbo不是指Dubbo协议,而是指RPC框架即远程服务调用的分布式框架,具体的说Dubbo框架是RPC理念的落地产品。而本文着重介绍的是Dubbo框架。
在分布式应用场景有高并发,高可扩展和高性能的要求。还涉及到,序列化/反序列化,网络,多线程以及设计模式的问题,而Dubbo框架对这些进行了封装。
Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC通信。

7. Dubbo协议

Dubbo协议是阿里巴巴自己实现的一种网络应用层协议,传输层还是TCP,Dubbo协议与HTTP协议、FTP协议,SMTP协议等这些应用层协议是并列的概念。

8. Dubbox框架

Dubbox是一个分布式服务框架,其前身是阿里巴巴开源项目Dubbo ,被国内电商及互联网项目中使用,后期阿里巴巴停止了该项目的维护,当当网便在Dubbo基础上进行优化,并继续维护,为了与原有的Dubbo区分,故将其命名为Dubbox。Dubbox弥补了Dubbo中没有REST框架的不足,即Dubbo服务端服务可以通过Dubbox快速包装为REST风格的服务。

9. Zookeeper

ZooKeeper是一个开源的分布式协调服务,它是服务集群的管理者,监视着集群中各个服务节点的状态,并根据节点提交的反馈进行合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
分布式应用程序基于Zookeeper可以实现数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理(注册中心)、Master选举、分布式锁和分布式队列等功能。
ZooKeeper将作为注册中心与Dubbo结合使用。

10. Dubbo和Zookeeper的关系

Dubbo为服务间提供了调用,主要用于管理分布式系统,从后面的Dubbo架构中我们可以看到Dubbo的工作需要一个非常重要的组件——注册中心(Register),而Zookeeper是分布式协调的治理工具,它能很好的提供服务注册和发现的功能,这样Dubbo和Zookeeper就完美的相遇了,当然Dubbo官网也推荐注册中心使用Zookeeper。简单来说,打个比方:每个dubbo服务就是动物园的动物,zookeeper是动物园。如果游客想看动物的话那么就去动物园看。比如你要看老虎,那么动物园有老虎你才能看到。换句话说我们把很多不同的dubbo服务(动物)放到zookeeper(动物园中)提供给我们游客进行观赏。这个过程中三个关键:场所、提供者、消费者。
再说一个分布式的项目,提供者与消费者被拆分了开来,部署在不同的tomcat中,在消费者中需要调用提供者的接口,但是两个运行在不同tomcat下的服务无法直接互调接口,那么就可以通过zookeeper和dubbo实现。就好比把动物放到动物园,我们要看了直接去动物园就行。而不能直接去动物生活的地方去看,会有性命安全之忧(比如你去看老虎)。
我们通过Dubbo建立提供者服务,并且到Zookeeper上面注册,即在Zookeeper上填写对应的Dubbo服务的IP及端口号。Zokeeper (注册中心)主要功能是服务的注册与发现,Dubbo(远程服务调用的分布式框架)主要实现应用服务与Zokeeper注册中心的链接调用(类似JDBC工具类)。

二、架构设计的演变

1. 单体架构

单体架构所有模块和功能都集中在一个项目中 ,部署时也是将项目所有功能整体部署到服务器中。

优点:
(1)小项目开发快,成本低
(2)架构简单
(3)易于测试
(4)易于部署
缺点:
(1)大项目模块耦合严重,不易开发维护,沟通成本高
(2)新增业务困难
(3)核心业务与边缘业务混合在一块,出现问题互相影响

2. 垂直架构

根据业务把项目垂直切割成多个项目,因此这种架构称之为垂直架构。

优点:
(1)系统拆分实现了流量分担,解决了并发问题
(2)可以针对不同系统进行优化
(3)方便水平扩展,负载均衡,容错率提高
(4)系统间相互独立,互不影响,新的业务迭代时更加高效
缺点:
(1)服务系统之间接口调用硬编码
(2)搭建集群之后,实现负载均衡比较复杂
(3)服务系统接口调用监控不到位,调用方式不统一
(4)数据库资源浪费,充斥慢查询,主从同步延迟大

3. 分布式架构(SOA )

SOA全称为Service Oriented Architecture,即面向服务的架构 。它是在垂直划分的基础上,将每个项目拆分出多个具备松耦合的服务,一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用,这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。

优点:
(1)服务以接口为粒度,为开发者屏蔽远程调用底层细节,使用Dubbo面向接口远程方法调用屏蔽了底层调用细节
(2)业务分层以后架构更加清晰,并且每个业务模块职责单一,扩展性更强
(3)数据隔离,权限回收,数据访问都通过接口,让系统更加稳定安全
(4)服务应用本身无状态化,这里的无状态化指的是应用本身不做内存级缓存,而是把数据存入数据库
(5)服务责任易确定,每个服务可以确定责任人,这样更容易保证服务质量和稳定
缺点:
(1)粒度控制复杂,如果没有控制好服务的粒度,服务的模块就会越来越多,就会引发超时、分布式事务等问题
(2)服务接口数量不宜控制,容易引发接口爆炸,所以服务接口建议以业务场景进行单位划分,并对相近的业务做抽象,防止接口爆炸
(3)版本升级兼容困难,尽量不要删除方法、字段,枚举类型的新增字段也可能不兼容等
(4)调用链路长,服务质量不可监控,调用链路变长,下游抖动可能会影响到上游业务,最终形成连锁反应,服务质量不稳定,同时链路的变长使得服务质量的监控变得困难

4. 微服务架构

微服务架构是SOA架构的一种扩展,这种架构模式下它拆分粒度更小、服务更独立。把应用拆分成为一个个微小的服务,不同的服务可以使用不同的开发语言和存储,服务之间往往通过Restful等轻量级通信。微服务架构关键在于微小、独立、轻量级通信。
微服务是在SOA上做的升华 , 粒度更加细致,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”。

优点:
(1)微服务很小,便于特定业务功能的聚焦
(2)微服务很小,每个微服务都可以被一个小团队单独实施(开发、测试、部署上线、运维),团队合作一定程度解耦,便于实施敏捷开发
(3)微服务很小,便于重用和模块之间的组装
(4)微服务很独立,不同的微服务可以使用不同的语言开发,松耦合
(5)微服务架构下,我们更容易引入新技术
(6)微服务架构下,我们可以更好的实现DevOps开发运维一体化

缺点:
(1)微服务架构下,分布式复杂难以管理,当服务数量增加,管理将越加复杂
(2)微服务架构下,分布式链路追踪难等

三、Dubbo架构概述

1. Dubbo基本架构


节点角色名称
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器,负责启动、加载、运行服务提供者

调用关系说明:
虚线:代表异步调用
实线:代表同步访问
蓝色虚线:代表启动时完成的功能
蓝绿色虚线:代表程序运行中执行的功能
调用流程:
0:服务运行容器运行容器初始化,启动、加载、运行服务提供者
1:服务提供者在服务容器启动时,向注册中心注册自己提供的服务
2:服务消费者在启动时,向注册中心订阅自己所需的服务
3:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心会基于长连接推送变更数据给消费者
4:服务消费者从提供者地址列表中基于软负载均衡算法选一台提供者进行调用,如果调用失败,则重新选择一台
5:服务提供者和消费者在内存中的调用次数和调用时间会定时每分钟发送给监控中心

2. Dubbo的服务治理


在大规模服务化之前,应用可能只是通过RMI或Hessian等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过F5等硬件进行负载均衡。

2.1 服务注册与发现

问题: 当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
解决: 此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本。

2.2 服务依赖关系

问题: 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
解决: 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。

2.3 服务监控与统计

问题: 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
解决: 为了解决这些问题,首先,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。

2.4 服务分层架构

问题: 规模继续扩大,应用之间不再是扁平的对应关系,开始分层,比如核心数据层,业务集成层等,就算没有出现循环依赖,也不允许从低层向高层依赖,以免后续被逼循环依赖。
解决: 这时,需要在注册中心定义架构体系,列明有哪些层的定义,每个服务暴露或引用时,都必须声明自己应用属于哪一层,这样注册中心能更快的发现架构的腐化现象。

2.5 服务负责人和服务文档

问题: 服务多了,沟通成本也开始上升,调某个服务失败该找谁?服务的参数都有什么约定?
解决: 这时就需要登记每个服务都是谁负责的,并建立一个服务的文档库,方便检索。

2.6 服务权限控制

问题: 慢慢一些敏感数据也都服务化了,安全问题开始变得重要,谁能调该服务?如何授权?
解决: 这样的服务可能需要一个密码,访问时需带着此密码,但如果用密码,要改密码时,就会很不方便,所有的消费方都要改,所以动态生成令牌(Token)可能会更好,提供方将令牌告之注册中心,由注册中心决定是否告之消费方,这样就能在注册中心页面上做复杂的授权模型。

2.7 服务等级协定

问题: 就算是不敏感的服务,也不是能任意调用,比如某服务突然多了一个消费者,这个消费者的请求量直接把服务给拖跨了,其它消费者跟着一起故障。
解决: 首先服务提供方需要流控,当流程超标时,能拒绝部分请求,进行自我保护。其次,消费者上线前和提供者约定《服务质量等级协定(SLA)》,SLA包括消费者承诺每天调用量,请求数据量,提供方承诺响应时间,出错率等,将SLA记录在监控中心,定时与监控数据对比,超标则报警。

2.8 服务路由和服务负载均衡

问题: 虽然有SLA约定,如果不能控制,就只是君子协定,如何确保服务质量?
解决: 比如:一个应用很重要,一个不那么重要,它们调用同一个服务,这个服务就应该向重要应用倾斜,而不是一视同仁,当支撑不住时,应限制不重要应用的访问,保障重要应用的可用,如何做到这一点呢。这时,就需要服务路由,控制不同应用访问不同机器,比如:
应用分离:
consumer.application = foo ==> provider.host = 1,2,3
consumer.application != foo ==> provider.host = 5,6
读写分离:
method.name = find*,get* ==> provider.host = 1,2,3
method.name != find*,get* ==> provider.host = 5,6

2.9 服务自动测试

问题: 服务上线后,需要验证服务是否可用,但因防火墙的限制,线下是不能访问线上服务的,不得不先写好一个测试Main,然后放到线上去执行,非常麻烦,并且容易忘记验证。
解决: 所以线上需要有一个自动运行的验证程序,用户只需在界面上填上要验证的服务方法,以及参数值和期望的返回值,当有一个服务提供者上线时,将自动运行该用例,并将运行结果发邮件通知负责人。

2.10 服务模板工程

问题: 服务应用和Web应用是有区别的,它是一个后台Daemon程序,不需要Tomcat之类的Web容器。但因公司之前以Web应用为主,规范都是按Web应用的,所以不得不把服务跑在一个根本用不上的Web容器里,而搭一个这样的Web工程也非常费事。
解决: 所以需要实现一个非Web的容器,只需简单的Main加载spring配置即可,并提供Maven模板工程,只需mvn dubbo:generate即可创建一个五脏俱全的服务应用。

2.11 服务开发IDE

问题: 开发服务的人越来越多,更注重开发效率,IDE的集成支持必不可少。
解决: 通过插件,可以在IDEA中直接运行服务,提供方可以直接填入测试数据测试服务,消费方可以直接Mock服务不依赖提供方开发。

2.12 服务上线审批和下线通知

问题: 因为暴露服务很简单,服务的上线越来越随意,有时候负责服务化的架构师都不知道有人上线了某个服务,使得线上服务鱼龙混杂,甚至出现重复的服务,而服务下线比上线还困难。
解决: 需要一个新服务上线审批流程,必须经过服务化的架构师审批过了,才可以上线。而服务下线时,应先标识为过时,然后通知调用方尽快修改调用,直到没有人调此服务,才能下线。

2.13 服务兼容性检测

问题: 因服务接口设计的经验一直在慢慢的积累过程中,很多接口并不能一促而蹴,在修改的过程中,如何保证兼容性,怎么判断是否兼容?另外,更深层次的,业务行为兼容吗?
解决: 可以根据使用的协议类型,分析接口及领域模型的变更是否兼容,比如:对比加减字段,方法签名等。而业务上,可能需要基于自动回归测试用例,形成Technology Compatibility Kit (TCK),确保兼容升级。

2.14 服务容错和降级

问题: 随着服务的不停升级,总有些意想不到的事发生,比如cache写错了导致内存溢出,故障不可避免,每次核心服务一挂,影响一大片,人心慌慌,如何控制故障的影响面?服务是否可以功能降级?或者资源劣化?
解决: 应用间声明依赖强度,哪些功能强依赖,哪些弱依赖,然后基于依赖强度,计算出影响面,并定期测试复查,加强关键路径上的服务的优化和容错,清理不该在关键路径上的服务。提供容错Mock数据,Mock数据也应可以在注册中心在运行时动态下发,当某服务不可用时,用Mock数据代替,可以减少故障的发生,比如某验权服务,当验权服务全部挂掉后,直接返回false表示没有权限,并打印Error日志报警。另外,前端的页面也应采用Portal进行降级,当该Portal获取不到数据时,直接隐藏,或替换为其它模块展示,并提供功能开关,可人工干预是否展示,或限制多少流量可以展示。

2.15 服务编排

问题: 当已有很多小服务,可能就需要组合多个小服务的大服务,为此,不得不增加一个中间层,暴露一个新服务,里面分别调其它小服务,这样的新服务业务逻辑少,却带来很多开发工作量。
解决: 此时,需要一个服务编排引擎,内置简单的流程引擎,只需用XML或DSL声明如何聚合服务,注册中心可以直接下发给消费者执行聚合逻辑,或者部署通用的编排服务器,所有请求有编排服务器转发。

2.16 服务使用情况报告

问题: 并不是所有服务的访问量都大,很多的服务都只有一丁点访问量,却需要部署两台提供服务的机器,进行HA互备,如何减少浪费的机器。
解决: 此时可能需要让服务容器支持在一台机器上部署多个应用,可以用多JVM隔离,也可以用ClassLoader隔离。

2.17 服务自动部署

问题: 多个应用如果不是一个团队开发的,部署在一台机器上,很有可以误操作,停掉了别人的服务。
解决: 需要实现自动部署,所有的部署都无需人工干扰,最好是一键式部署。

2.18 服务权重动态调整

问题: 机器总是的闲时和忙时,或者冗余机器防灾,如何提高机器的利用率?
解决: 即然已经可以自动部署了,那根据监控数据,就可以实现资源调度,根据应用的压力情况,自动添加机器并部署。如果你的应用是国际化的,有中文站,美国站之类,因为时差,美国站的机器晚上闲的时候,可能正是中文站的白天忙时,可以通过资源调度,分时段自动调配和部署双方应用。

入骨相思知不知


玲珑骰子安红豆


入我相思门,知我相思苦,长相思兮长相忆,短相思兮无穷极。

文章转载自佑佑有话说,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论