整体流程
- 暴露本地服务
- 暴露远程服务
- 启动netty
- 连接zookeeper
- 到zookeeper注册
- 监听zookeeper
URL
一般而言我们说的 URL 指的就是统一资源定位符,在网络上一般指代地址,本质上看其实就是一串包含特殊格式的字符串,标准格式如下:
protocol://username:password@host:port/path?key=value&key=value
Dubbo 采用 URL 的方式来作为约定的参数类型,被称为公共契约,统一的契约使代码更加的规范化、形成一种统一的格式,dubbo的URL参数有:
protocol:指的是 dubbo 中的各种协议,如:dubbo thrift http
username/password:用户名/密码
host/port:主机/端口
path:接口的名称
parameters:参数键值对
dubbo://172.27.32.197:20880/com.guahao.ai.aicdss.biz.service.kano.Aqqq?anyhost=true&application=ai-cdss-service&bind.ip=172.27.32.197&bind.port=20880&deprecated=false&dubbo=2.0.2&dubbo.tag=DEV&dynamic=true&generic=false&interface=com.guahao.ai.aicdss.biz.service.kano.Aqqq&methods=test&pid=277316&qos.enable=false®ister=true&release=2.7.3&side=provider×tamp=1604492011040
服务发布过程
ServiceBean.export
这里需要关注两个父类:ServiceConfig,InitializingBean,ApplicationListener
InitializingBean的afterPropertiesSet会在ServiceBean初始化完成后调用,对ServiceConfig的配置进行加载
在afterPropertiesSet方法的最后,有下面一行代码
if (!supportedApplicationListener) {
export();
}
判断如果是支持springboot监听器触发,则afterPropertiesSet方法执行结束,同时看到父类有ApplicationListener,所以ServiceConfig同时也是spring时间监听器,当有event时间推送的时候,会触发onApplicationEvent方法
ServiceBean.doExportUrls+doExportUrlsFor1Protocol
dubbo支持多协议多注册中心,所以这里根据配置查询出需要支持的协议和注册中心,存在一个嵌套for循环支持多协议多注册中心
本地暴露
为什么要搞个本地暴露呢?
因为可能存在同一个 JVM 内部引用自身服务的情况,因此暴露的本地服务在内部调用的时候可以直接消费同一个 JVM 的服务避免了网络间的通信。
本地暴露会将发布的服务的invoker对象,存在exporterMap中,等后续消费者初始化的时候会用到
远程暴露
服务暴露流程主要包括启动netty服务和注册服务到注册中心,让消费者能够拉取 netty启动不介绍了,自行了解
服务注册:
服务注册主要是向zk注册节点,注意这里跟节点和叶子节点的属性区别,根节点是创建持久节点,叶子节点是临时节点(服务关闭会自动删除)
订阅(2.7已经不推荐使用,可以忽略)
服务发布的一些点
只订阅
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。
可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。
禁用注册配置
<dubbo:registry address="10.20.153.10:9090" register="false" />
或者
<dubbo:registry address="10.20.153.10:9090?register=false" />
只注册
如果有两个镜像环境,两个注册中心,有一个服务只在其中一个注册中心有部署,另一个注册中心还没来得及部署,而两个注册中心的其它应用都需要依赖此服务。这个时候,可以让服务提供者方只注册服务到另一注册中心,而不从另一注册中心订阅服务。
禁用订阅配置
<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />
<dubbo:registry id="qdRegistry" address="10.20.141.150:9090" subscribe="false" />
多协议
不同服务不同协议
多协议暴露服务
延迟暴露
如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。我们在 Dubbo 2.6.5 版本中对服务延迟暴露逻辑进行了细微的调整,将需要延迟暴露(delay > 0)服务的倒计时动作推迟到了 Spring 初始化完成后进行。你在使用 Dubbo 的过程中,并不会感知到此变化,因此请放心使用。
<dubbo:service delay="-1" />
服务分组
当一个接口有多种实现时,可以用 group 区分
服务:
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
引用:
<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />
任意组:
<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />
任意组:
<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />
多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
在低压力时间段,先升级一半提供者为新版本 再将所有消费者升级为新版本 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
如果不需要区分版本,可以按照以下的方式配置 1:
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
如果不需要区分版本,可以按照以下的方式配置 1:
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
线程模型
如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。
但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。
如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />
Dispatcher:
all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
ThreadPool
fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
cached 缓存线程池,空闲一分钟自动删除,需要时重建。
limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)ThreadPool
fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
cached 缓存线程池,空闲一分钟自动删除,需要时重建。
limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)
本文使用 mdnice 排版