单一应用架构
所有功能部署在一个应用上,用于简化增删改查工作量的数据访问框架(ORM)是关键
垂直应用架构
将应用拆分成互不相干的几个应用,以提升效率,此时,用于加速前端页面开发的Web框架(MVC)是关键
分布式服务架构
将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键
流动计算架构
增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率,此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
节点 | 角色说明 |
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
连通性
健壮性
伸缩性
升级性
节点 | 角色说明 |
Deployer | 自动部署服务的本地代理 |
Repository | 仓库用于存储服务应用发布包 |
Scheduler | 调度中心基于访问压力自动增减服务提供者 |
Admin | 统一管理控制台 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
标签 | 用途 | 解释 |
服务配置 | 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心 | |
引用配置 | 用于创建一个远程服务代理,一个引用可以指向多个注册中心 | |
协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 | |
应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 | |
模块配置 | 用于配置当前模块信息,可选 | |
注册中心配置 | 用于配置连接注册中心相关信息 | |
监控中心配置 | 用于配置连接监控中心相关信息,可选 | |
提供方配置 | 当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值,可选 | |
消费方配置 | 当ReferenceConfig某属性没有配置时,采用此缺省值,可选 | |
方法配置 | 用于ServiceConfig和ReferenceConfig指定方法级的配置信息 | |
参数配置 | 用于指定方法参数配置 |
import com.alibaba.dubbo.config.annotation.Service;
@Service(version="1.0.0")
public class FooServiceImpl implements FooService {
// ...
}
// XML配置
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;
@Component
public class BarAction {
@Reference(version="1.0.0")
private FooService fooService;
}
// XML配置
也可以通过以下的XML配置完成annotation扫描,与前面的等价
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=”true”,懒加载或通过API编程延迟引用服务时可check
关闭某个服务的启动时检查(没有提供者时报错)
关闭所有服务的启动时检查(没有提供者时报错)
关闭注册中心启动时检查(注册订阅失败时报错)
在集群调用失败时,Dubbo提供了多种容错方案,缺省为failover重试
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。 可通过retries=”2” 来设置重试次数(不含第一次)
或
或
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
或
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2” 来设置最大并行数
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用
配置
缺省只对第一个参数Hash,如果要修改,配置
缺省用 160 份虚拟节点,如果要修改,配置
如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度
但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求
如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁
因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景
all
所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等
direct
所有消息都不派发到线程池,全部在 IO 线程上直接执行
message
只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在IO 线程上执行
execution
只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行
connection
在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池
fixed
fixed 固定大小线程池,启动时建立线程,不关闭,一直持有(缺省)
cached
缓存线程池,空闲一分钟自动删除,需要时重建
limited
可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题
绕过注册中心,只测试指定服务提供者(只应在测试阶段使用)
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务
禁用注册配置
或
如果有两个镜像环境,两个注册中心,有一个服务只在其中一个注册中心有部署,另一个注册中心还没来得及部署,而两个注册中心的其它应用都需要依赖此服务。这个时候,可以让服务提供者方只注册服务到另一注册中心,而不从另一注册中心订阅服务
禁用订阅配置
或
有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式
或
服务提供者初次注册时为禁用状态,需人工启用。断线时,将不会被自动删除,需人工禁用
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议
不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议
Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的
当一个接口有多种实现时,可以用 group 区分
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用
可以按照以下的步骤进行版本迁移:
然后将剩下的一半提供者升级为新版本
消费方需从每种group中调用一次返回结果,合并结果返回,实现聚合菜单项
搜索所有分组
合并指定分组
指定方法合并结果,其它未指定的方法,将只调用一个Group
某个方法不合并结果,其它都合并结果
指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称
指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身
回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控
所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService ,即可使用
代码
// 远程服务引用 MemberService
memberService = ctx.getBean("memberService");
EchoService echoService = (EchoService) memberService; // 强制转型为EchoService
// 回声测试可用性
String status = echoService.$echo("OK");
assert(status.equals("OK"));
上下文中存放的是当前调用过程中所需的环境信息,所有配置信息都将转换为URL的参数
// 远程调用
xxxService.xxx();
// 本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
public class XxxServiceImpl implements XxxService {
public void xxx() {
// 本端是否为提供端,这里会返回true
boolean isProviderSide = RpcContext.getContext().isProviderSide();
// 获取调用方IP地址
String clientIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("applicatio n");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();
}
}
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小
在 consumer.xml 中配置
调用代码
// 此调用会立即返回null
fooService.findFoo(fooId);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future fooFuture = RpcContext.getContext().getFuture();
// 此调用会立即返回null
barService.findBar(barId);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future barFuture = RpcContext.getContext().getFuture();
// 此时findFoo和findBar的请求同时在执行,客户端不需要启动多线程来支持并行,而是借助NIO的非阻塞完成
// 如果foo已返回,直接拿到返回值,否则线程wait住,等待foo返回后,线程会被notify唤醒
Foo foo = fooFuture.get();
// 同理等待bar返回
Bar bar = barFuture.get();
// 如果foo需要5秒返回,bar需要6秒返回,实际只需等6秒,即可获取到foo和bar,进行接下来的处理
可以设置是否等待消息发出
sent=”false” 不等待消息发出,将消息放入 IO 队列,即刻返回
如果只是异步调用,完全忽略返回值,可以配置 return=”false”,以减少 Future 对象的创建和管理成本
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy
配置
或
提供Stub的实现
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public (BarService barService) {
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行,你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出 异常,而是通过 Mock 数据返回授权失败
配置
或
在工程中提供Mock实现
package com.foo;
public class BarServiceMock implements BarService {
public String sayHello(String name) {
// 可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
如果服务的消费方经常需要 try-catch 捕获异常,可以考虑改为 Mock(只有出现 RpcException 时才执行)实现,并在Mock实现中 return null,如果只是想简单的忽略异常,在 2.0.11 以上版本可用
如果服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露
延迟5秒暴露服务
延迟到 Spring 初始化完成后,再暴露服务
限制 com.foo.BarService 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个
限制 com.foo.BarService 的 sayHello 方法,服务器端并发执行(或占用线程池线程数)不 能超过 10 个
限制 com.foo.BarService 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个
或
限制 com.foo.BarService 的 sayHello 方法,每客户端并发执行(或占用连接的请求数)不 能超过 10 个
或
限制服务器端接受的连接不能超过 10 个
或
限制客户端服务使用连接不能超过 10 个
或
延迟连接用于减少长连接数。当有调用发起时,再创建长连接
kill PID 可以实现,kill -9 PID 不可以
设置停机超时时间,缺省超时时间是10秒