承接上文,本文将介绍Dubbo的高级应用
通过spring配置文件
关闭某个服务的启动时检查 (没有提供者时报错):
关闭所有服务的启动时检查 (没有提供者时报错):
关闭注册中心启动时检查 (注册订阅失败时报错):
通过 dubbo.properties
dubbo.reference.com.foo.BarService.check=false
dubbo.consumer.check=false
dubbo.registry.check=false
通过 -D 参数
java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.consumer.check=false
java -Ddubbo.registry.check=false
public interface ClusterService {
String getClusterService(String str);
}
@DubboService(timeout = 1000000)
public class ClusterServiceImpl implements ClusterService {
@Override
public String getClusterService(String str) {
return "高级应用集群容错:"+str;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestClusterService {
//启动时检查和直连
@DubboReference(check = false)
private ClusterService clusterService;
@Test
public void test(){
System.out.println(clusterService.getClusterService("eclipse2019"));
}
}
Dubbo 中点对点的直连方式
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
配置:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestClusterService {
//启动时检查和直连
@DubboReference(check = false,url = "dubbo://localhost:20880")
private ClusterService clusterService;
@Test
public void test(){
System.out.println(clusterService.getClusterService("eclipse2019"));
}
}
集群调用失败时,Dubbo 提供的容错方案
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
各节点关系:
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2" 来设置重试次数(不含第一次)。该配置为缺省配置
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
Available Cluster
调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。
配置:
@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
@DubboService(timeout = 1000000)
public class ClusterServiceImpl implements ClusterService {
@Override
public String getClusterService(String str) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "高级应用集群容错:"+str;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestClusterService {
@DubboReference(check = false,cluster="broadcast",parameters = {"broadcast.fail.percent", "20"},timeout = 100)
private ClusterService clusterService;
@Test
public void test(){
System.out.println(clusterService.getClusterService("eclipse2019"));
}
}
Dubbo 提供的集群负载均衡策略
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。
具体实现上,Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个Provider 实例。
负载均衡策略
目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:
算法 | 特性 | 备注 |
RandomLoadBalance | 加权随机 | 默认算法,默认权重相同 |
RoundRobinLoadBalance | 加权轮询 | 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同 |
LeastActiveLoadBalance | 最少活跃优先 + 加权随机 | 背后是能者多劳的思想,最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差;使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 |
ShortestResponseLoadBalance | 最短响应优先 + 加权随机 | 更加关注响应速度 |
ConsistentHashLoadBalance | 一致性 Hash | 一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 |
配置:
使用服务分组区分服务接口的不同实现
当一个接口有多种实现时,可以用 group 区分。
public interface GroupService {
String getGroupServiceStr(String str);
}
@DubboService(group = "group01")
public class GroupServiceImpl01 implements GroupService {
@Override
public String getGroupServiceStr(String str) {
return "我是group1111,返回:"+str;
}
}
@DubboService(group = "group02")
public class GroupServiceImpl02 implements GroupService {
@Override
public String getGroupServiceStr(String str) {
return "我是group2222,返回:"+str;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestGroupService {
@DubboReference(check = false,group = "group01")
private GroupService groupService;
@Test
public void test(){
System.out.println(groupService.getGroupServiceStr("eclipse2019"));
}
}
通过分组对结果进行聚合并返回聚合后的结果
通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestGroupService {
@DubboReference(check = false,group = "*",parameters = {"merger","true"})
private GroupService groupService;
@Test
public void test(){
System.out.println(groupService.getGroupServiceStr("eclipse2019"));
}
}
SPI文件配置 在resources下创建META-INF文件夹并在其下面创建dubbo文件夹,然后在dubbo文件夹下面创建org.apache.dubbo.rpc.cluster.Merger文件,在该文件下写好Merger的实现类,如:
string=com.example.merge.StringMerger
StringMerger类:
public class StringMerger implements Merger {
//定义了所有group实现类的返回值的合并规则
@Override
public String merge(String... items) {
if(items.length == 0) {
return null;
}
StringBuilder builder = new StringBuilder();
for (String string : items) {
if(string != null) {
builder.append(string).append("----");
}
}
return builder.toString();
}
}
在 Dubbo 中为同一个服务配置多个版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
public interface VersionService {
String getVersionServiceStr(String str);
}
@DubboService(version = "1.0.0")
public class VersionServiceImpl01 implements VersionService {
@Override
public String getVersionServiceStr(String str) {
return "调用第一个版本,返回值:"+str;
}
}
@DubboService(version = "1.0.1")
public class VersionServiceImpl02 implements VersionService {
@Override
public String getVersionServiceStr(String str) {
return "调用第二个版本,返回值:"+str;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestVersionService {
@DubboReference(check = false,version = "1.0.0")
private VersionService versionService;
@Test
public void test(){
System.out.println(versionService.getVersionServiceStr("eclipse2019"));
}
}
在 Dubbo 中进行参数验证
参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
Maven 依赖
javax.validation
validation-api
1.0.0.GA
org.hibernate
hibernate-validator
4.2.0.Final
参数验证类:
public class ValidationParamter implements Serializable {
private static final long serialVersionUID = 32544321432L;
@NotNull
@Size(
min = 2,
max = 20
)
private String name;
@Min(18L)
@Max(100L)
private int age;
@Past
private Date loginDate;
@Future
private Date expiryDate;
}
public interface ValidationService {
void save(ValidationParamter parameter);
void update(ValidationParamter parameter);
void delete(@Min(1) long id,
@NotNull @Size(min = 2, max = 16) String operation);
}
@DubboService
public class ValidationServiceImpl implements ValidationService {
@Override
public void save(ValidationParamter parameter) {
System.out.println("参数验证案例的save方法");
}
@Override
public void update(ValidationParamter parameter) {
System.out.println("参数验证案例的update方法");
}
@Override
public void delete(long id, String operation) {
System.out.println("参数验证案例的delete方法");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestValidationService {
@DubboReference(check = false,validation = "true")
private ValidationService validationService;
@Test
public void validation() {
ValidationParamter paramter = new ValidationParamter();
paramter.setName("eclipse2019");
paramter.setAge(98);
paramter.setLoginDate(new Date(System.currentTimeMillis() - 10000000));
paramter.setExpiryDate(new Date(System.currentTimeMillis() + 10000000));
validationService.save(paramter);
}
}
做测试的,没有使用场景
实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现
泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。
消费者:
@Test
public void usegeneric() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo_consumer");
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://192.168.67.139:2184");
ReferenceConfig referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(applicationConfig);
referenceConfig.setInterface("com.eclipse2019.service.UserService");
//这个是使用泛化调用
referenceConfig.setGeneric(true);
GenericService genericService = referenceConfig.get();
Object result = genericService.$invoke("queryUser", new String[]{"java.lang.String"},
new Object[]{"eclipse2019"});
System.out.println(result);
}
通过参数回调从服务器端调用客户端逻辑
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
public interface CallbackService {
void addListener(String key, CallbackListener listener);
}
public interface CallbackListener {
void changed(String msg);
}
@DubboService(methods = {@Method(name = "addListener", arguments = {@Argument(index = 1,callback = true)})})
public class CallbackServiceImpl implements CallbackService {
@Override
public void addListener(String key, CallbackListener listener) {
listener.changed(getChanged(key));
}
private String getChanged(String key) {
return "Changed: " + new SimpleDateFormat("yyyy-MM-dd:mm:ss").format(new Date());
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestCallbaskService {
@DubboReference(check = false)
private CallbackService callbackService;
@Test
public void test(){
callbackService.addListener("eclipse2019", new CallbackListener() {
@Override
public void changed(String msg) {
System.out.println("又回调回来了" + msg);
}
});
}
}
在 Dubbo 中利用本地存根在客户端执行部分逻辑
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
public interface StubService {
String stub(String param);
}
@DubboService
public class StubServiceImpl implements StubService {
@Override
public String stub(String param) {
return "本地存根就是在消费端干一些事,返回:"+param;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestStubService {
@DubboReference(check = false,stub = "com.example.stub.LocalStubProxy")
private StubService stubService;
@Test
public void stub() {
System.out.println(stubService.stub("eclipse2019"));
}
}
代理层:
/*
* 1、必须实现服务端的接口
* 2、要定义构造函数(传递客户端引用的实例)
* 接管了studService的调用
*
* 只有这个类才会决定要不要远程调用
*/
public class LocalStubProxy implements StubService {
private StubService stubService;
//这个必须要,传stubService实例本身
public LocalStubProxy(StubService stubService) {
this.stubService = stubService;
}
@Override
public String stub(String param) {
//代码在客户端执行,你可以在客户端做ThreadLocal本地缓存,或者校验参数之类工作的
try {
//用目标对象掉对应的方法 远程调用
return stubService.stub(param);
}catch (Exception e) {
//用来降级
System.out.println("降级数据");
}
//掉完后又执行代码
return null;
}
}
mock
只关注调用异常,如果客户端调用服务端出现异常了,那么就会触发服务降级
如何在 Dubbo 中利用本地伪装实现服务降级
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
public interface MockService {
String mock(String param);
String queryArea(String areaCode);
String queryUser(String userCode);
}
@DubboService
public class MockServiceImpl implements MockService {
@Override
public String mock(String s) {
System.out.println("=======mockservice的业务处理=======");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=======mockservice的业务处理完成=======");
return "本地伪装案例返回:"+s;
}
@Override
public String queryArea(String s) {
return s;
}
@Override
public String queryUser(String s) {
return s;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestMockService {
//这两种方式会走rpc远程调用 fail -- 会走远程服务
@DubboReference(check = false,mock = "true")
// @DubboReference(check = false,mock = "com.course.mock.LocalMockService")
//不走服务直接降级 force -- 是不会走远程服务的,强制降级.
// @DubboReference(check = false,mock = "force:return eclipse2019")
// @DubboReference(check = false,mock = "throw java.lang.RuntimeException")
private MockService mockService;
@Test
public void test() {
System.out.println(mockService.mock("eclipse2019"));
}
}
默认mock实现
/*
* MockServiceMock
*
* 1、接口名称 + Mock
* MockService + Mock
* 2、mock逻辑必须定义在接口的包下面
*/
public class MockServiceMock implements MockService {
@Override
public String mock(String s) {
System.out.println(this.getClass().getName() + "--mock");
return s;
}
@Override
public String queryArea(String s) {
System.out.println(this.getClass().getName() + "--queryArea");
return s;
}
@Override
public String queryUser(String s) {
System.out.println(this.getClass().getName() + "--queryUser");
return s;
}
}
自定义mock实现
public class LocalMockService implements MockService {
@Override
public String mock(String s) {
System.out.println(this.getClass().getName() + "--mock,嘿嘿");
return s;
}
@Override
public String queryArea(String s) {
System.out.println(this.getClass().getName() + "--queryArea");
return s;
}
@Override
public String queryUser(String s) {
System.out.println(this.getClass().getName() + "--queryUser");
return s;
}
}
在 Dubbo 中发起异步调用
从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
代码案例
public interface AsyncService {
String asynctoDo(String name);
}
@DubboService
public class AsyncServiceImpl implements AsyncService {
@Override
public String asynctoDo(String s) {
for (int i = 0; i < 10; i++) {
System.out.println("===============AsyncServiceImpl.asynctoDo");
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return "hello," + s;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestAsyncService {
@DubboReference(check = false,timeout = 1000000000,methods = {@Method(name =
"asynctoDo",async = true)})
private AsyncService asyncService;
@Test
public void async() throws InterruptedException {
String aa = asyncService.asynctoDo("aa");
System.out.println("main==" + aa);
System.out.println("并行调用其他接口====");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//需要拿到异步调用的返回结果
CompletableFuture
Dubbo 服务提供方的异步执行
Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。
注意:
Provider 端异步执行和 Consumer 端异步调用是相互独立的,你可以任意正交组合两端配置
代码案例
public interface AsyncService {
String asynctoDo(String name);
CompletableFuture doOne(String var1);
}
@DubboService
public class AsyncServiceImpl implements AsyncService {
@Override
public String asynctoDo(String s) {
for (int i = 0; i < 10; i++) {
System.out.println("===============AsyncServiceImpl.asynctoDo");
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return "hello," + s;
}
@Override
public CompletableFuture doOne(String var1) {
return CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "doOne -- OK";
});
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConsumerConfiguration.class)
public class TestAsyncDoOne {
@DubboReference(check = false,timeout = 1000000000,methods = {@Method(name =
"asynctoDo",async = true),@Method(name = "doOne",async = false)})
private AsyncService asyncService;
@Test
public void asyncDoone() throws ExecutionException, InterruptedException {
CompletableFuture eclipse2019 = asyncService.doOne("eclipse2019");
eclipse2019.whenComplete((retValue,exception)->{
if(exception == null) {
System.out.println(retValue);
} else {
exception.printStackTrace();
}
});
Thread.currentThread().join();
}
}
在 Dubbo 中进行本地调用
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行Dubbo 的 Filter 链。
本地调用,调用的就是本地工程的接口实例
示例:
@DubboReference(check = false,injvm = true)
StudentService studentService;
@Test
public void inJvm() throws InterruptedException {
System.out.println(studentService.find("xx"));
}
为有状态服务配置粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启延迟连接,以减少长连接数。
sticky=true
@DubboReference(check = false,sticky = true)
UserService userService;
Dubbo的高级应用不仅仅只是这些,由于篇幅问题,本文只总结以上的应用,具体的其他应用可以参考官方文档
留言与评论(共有 0 条评论) “” |