Dubbo进阶
- 2020 年 3 月 10 日
- 筆記
注册中心zookeeper
什么是注册中心:
注册中心就是用来存储服务信息的地方,就像房屋中介一样;
为什么需要注册中心:
在前面的例子中我们使用了客户端与服务器直连的方式完成了服务的调用,在实际开发中这回带来一些问题,例如服务器地址变更了,或服务搭建了集群,客户端不知道服务的地址,此时注册中心就派上用场了,服务提供方发布服务后将服务信息放在zookeeper中,然后消费方从zookeeper获取服务器信息,进行调用,这样就使提供方和消费方解开了耦合,也让服务提供方可以更方便的搭建集群;
使用:
1.启动zookeeper,单机和集群都可以
2.在双方配置文件中指定注册中心的信息(内容相同)
<!--注册中心 N/A 表示不使用注册中心 直连客户端 地址可以是一个或多个 多个表示集群--> <dubbo:registry protocol="zookeeper" address="10.211.55.6:2181,10.211.55.7:2181"/>
需要说明的是,注册中心不是必须使用zookeeper,dubbo还支持其他三种:Simple,Redis,Multicast,因其优秀的可用性,官方推荐使用zookeeper;
Dubbo的其他配置方式
API配置
简单的说就是不使用配置文件而是使用使用代码来完成配置,该方式主要用于测试环境或集成其他框架,不推荐用于生产环境;
服务提供者:
public class ProviderApplication { public static void main(String[] args) throws IOException { // xmlConfig(); apiConfig(); System.in.read();//阻塞主线程保持运行 } private static void apiConfig() { //应用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-service"); applicationConfig.setQosEnable(true); //注册中心 RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); //rpc协议 ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); //发布服务 ServiceConfig<HelloService> serviceConfig = new ServiceConfig<HelloService>(); serviceConfig.setApplication(applicationConfig); serviceConfig.setProtocol(protocolConfig); serviceConfig.setRegistry(registryConfig); serviceConfig.setInterface(HelloService.class); serviceConfig.setRef(new HelloServiceImpl()); serviceConfig.export(); }
消费者:
public class ConsumerApplication { public static void main(String[] args) { // xmlConfig(); apiConfig(); } private static void apiConfig() { //应用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-consumer"); applicationConfig.setQosEnable(false); //注册中心 RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); //调用服务 ReferenceConfig<HelloService> serviceReferenceConfig = new ReferenceConfig<HelloService>(); serviceReferenceConfig.setApplication(applicationConfig); serviceReferenceConfig.setRegistry(registryConfig); serviceReferenceConfig.setInterface(HelloService.class); HelloService service = serviceReferenceConfig.get(); String tom = service.sayHello("tom"); System.out.println(tom); }
注解配置
注解配置是使用较多的一种方式,可加快开发速度,让我们从繁琐的配置文件中解脱出来;
Dubbo使用了Spring容器来管理bean,所以配置方式也大同小异,可使用Configuration将一个类作为配置类;在该类中提供必要的几个bean
服务提供者
配置类:
@Configuration @EnableDubbo(scanBasePackages = "com.yyh.service") public class ProviderConfiguration { //无论如何配置我们最终需要的还是那几个bean @Bean public ApplicationConfig applicationConfig(){ //应用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-service"); applicationConfig.setQosEnable(true); return applicationConfig; } @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); return registryConfig; } @Bean public ProtocolConfig protocolConfig(){ //rpc协议 ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } }
服务实现类:
//注意该注解是Dubbo提供的 不要用错 @Service public class HelloServiceImpl implements HelloService { public String sayHello(String name) { return "hello: "+name; } }
发布服务:
public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read();//阻塞主线程保持运行 }
消费者
配置类:
@Configuration @EnableDubbo @ComponentScan("com.yyh.consumer") public class ConsumerConfiguration { @Bean public ApplicationConfig applicationConfig(){ //应用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-consumer"); applicationConfig.setQosEnable(true); return applicationConfig; } @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); return registryConfig; } }
消费者类:
@Component public class SayHelloConsumer { @Reference private HelloService helloService; public void sayHello(){ String jack = helloService.sayHello("jack"); System.out.println(jack); } }
执行测试:
public class ConsumerApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); SayHelloConsumer consumer = context.getBean(SayHelloConsumer.class); consumer.sayHello(); }
可以发现消费者不需要指定ProtocolConfig,主要服务端固定端口即可;
使用properties配置
相比xml和api的方式,properties是体量是最轻的,在面对一些简单配置时可以采用properties
服务提供者
在resource下提供名为dubbo.properties
的文件,内容如下:
dubbo.application.name=my-service dubbo.application.owner=jerry dubbo.protocol.dubbo.port=1099 dubbo.registry.address=zookeeper://10.211.55.6:2181
配置类:
@Configuration @EnableDubbo(scanBasePackages = "com.yyh.service") @PropertySource("classpath:/dubbo.properties") public class AnnotationAndPropperties { }
测试代码:
public class ProviderApplication { public static void main(String[] args) throws IOException { annotationAndPropConfig(); System.out.println("服务已启动 按任意键退出"); System.in.read();//阻塞 主线程 保持运行 } private static void annotationAndPropConfig() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAndPropperties.class); context.start(); } }
消费者
同样提供properties文件,但不需要指定protocol
dubbo.application.name=my-service dubbo.application.qos.enable=false dubbo.application.owner=jerry dubbo.registry.address=zookeeper://10.211.55.6:2181
配置类:
@EnableDubbo @Configuration @ComponentScan("com.yyh.consumer") @PropertySource("classpath:/dubbo.properties") public class AnnotationAndPropConfiguration { }
消费者类:
@Component public class SayHelloConsumer { @Reference private HelloService helloService; public void sayHello(){ String jack = helloService.sayHello("jack"); System.out.println(jack); } }
测试类:
public class ConsumerApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); SayHelloConsumer consumer = context.getBean(SayHelloConsumer.class); consumer.sayHello(); }
强调:注解使用时,扫描服务的实现类使用dubbo提供的EnableDubbo注解,而扫描其他bean用的是spring的ComponentScan注解;
常用配置项
1.启动时检查
默认情况下,dubbo在启动时会自动检查依赖(作为消费者)的服务是否可用,若服务不可用则直接抛出异常并阻止容器正常初始化,但在一些情况下我们会希望先启动程序,因为服务可能会在之后的时间里变为可用的;
启动检查注册中心
<!--启动程序时是否检查注册中心的可用性--> <dubbo:registry protocol="zookeeper" check="true" address="10.211.55.8:2181,10.211.55.7:2181,10.211.55.6:2181"/>
启动检查服务提供方(对所有提供者)
<!--这里的启动指的是从容器中获取一个服务方的代理对象时 即getBean()时是否检查--> <dubbo:consumer check="false"/>
启动检查服务提供方(对某个提供者)
<!--这里的启动指的是从容器中获取一个服务方的代理对象时 即getBean()时是否检查--> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService" />
properties文件写法:
java -Ddubbo.reference.com.foo.BarService.check=false #强制修改所有reference的check java -Ddubbo.reference.check=false #当reference的check为空时有效 java -Ddubbo.consumer.check=false java -Ddubbo.registry.check=false
2.集群容错
在后续的使用中我们可能会对某一个服务部署多个示例形成集群,随着项目的运行时间越来越常,一些服务节点可能会宕机或是由于网络原因暂时不可用,集群容错可指定在调用服务失败时dubbo要采取的行为;
dubbo提供以下6种容错机制:
策略名称 | 优点 | 缺点 | 主要应用场景 |
---|---|---|---|
failover(默认) | 对调用者屏蔽调用失败的信息 | 额外资源开销,资源浪费 | 通讯环境良好,并发不高的场景 |
failfast | 业务快速感知失败状态进行自主决策 | 产生较多报错的信息 | 非幂等性操作,需要快速感知失败的场景 |
failsafe | 即使失败了也不会影响核心流程 | 对于失败的信息不敏感,需要额外的监控 | 旁路系统,失败不影响核心流程正确性的场景 |
failback | 失败自动异步重试 | 重试任务可能堆积 | 对于实时性要求不高,且不需要返回值的一些异步操作 |
forking | 并行发起多个调用,降低失败概率 | 消耗额外的机器资源,需要确保操作幂等性 | 资源充足,且对于失败的容忍度较低,实时性要求高的场景 |
broadcast | 支持对所有的服务提供者进行操作 | 资源消耗很大 | 通知所有提供者更新缓存或日志等本地资源信息 |
幂等性:指的是每次调用都会产生相同的结果,即不会对数据进行写操作(增删改)
配置方式:
容错配置分为两个粒度:接口级别,方法级别
服务方配置:
服务方配置即将容错配置放在服务提供方,这样一来所有消费方就可以使用统一的容错机制,而不用每个消费方都配一遍;
<!--接口级别:--> <dubbo:service interface="com.yyh.service.HelloService" ref="helloService" cluster="failover" retries="2"/> <!--方法级别:--> <dubbo:service interface="com.yyh.service.HelloService" cluster="failover" ref="helloService"> <dubbo:method name="sayHello" retries="2"/> </dubbo:service>
消费方配置:
<!--接口级别:--> <dubbo:service interface="com.yyh.service.HelloService" ref="helloService" cluster="failsafe"/> <!--方法级别--> <dubbo:service interface="com.yyh.service.HelloService" cluster="failover" ref="helloService"> <dubbo:method name="sayHello" retries="2"/> </dubbo:service>
3.负载均衡
为了提高系统的可用性,能够承受更大的并发量,我们会将压力的服务部署为集群,但是如果每次请求都交给集群中的同一个节点,那这个几点很可能直接就宕了,所以合理的分配任务给集群中的每一台机器也是我们必须考虑的事情,好在dubbo已经提供相应的功能,我们只需简单的配置即可完成负载均衡;
dubbo支持的任务分配方式:
随机random
顾名思义,从Provider列表中选择随机选择一个,但是我们可以为Provider指定权重,权重越大的被选中的几率越高,因此对于性能更好的机器应设置更大的权重,反之则反,如果不指定负载均衡,默认使用随机负载均衡;
轮询roundrobin
即依次调用所有Provider,每个Provider轮流处理请求,当然我们也可以指定权重,Provider收到的请求数量比约等于权重比; 性能差的机器可能会累积一堆请求,最终拖慢整个系统;
基于活跃数leastactive
每个Provider收到一个请求则将活跃数+1,每处理完成一个请求则活跃数-1,新的请求将会交给活跃数最少的Provider; 简单的说性能越好的机器将收到更多的请求,反之则反;
基于hash一致consistenthash
将根据Provider的 ip 或者其他的信息为Provider生成一个 hash,并将这个 hash 投射到 [0, 232 – 1] 的圆环上。当有请求时,则使用请求参数计算得出一个hash值。然后查找第一个大于或等于该 hash 值的缓存Provider,并将请求交予该Provider处理。如果当前Provider挂了,则在下一次请求时,为缓存项查找另一个大于其 hash 值的Provider即可。 具体算法参考:去官网看看
配置方法:
与容错配置一样,我们可以选择在服务方或是消费方进行设置;
服务方:
<!--接口级别--> <dubbo:service interface="com.yyh.service.HelloService" loadbalance="roundrobin" /> <!--方法级别--> <dubbo:service interface="com.yyh.service.HelloService" cluster="failover" ref="helloService"> <dubbo:method name="sayHello" retries="2" loadbalance="roundrobin"/> </dubbo:service>
消费方:
<!--接口级别--> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService" loadbalance="roundrobin"/> <!--方法级别--> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService"> <dubbo:method name="sayHello" loadbalance="roundrobin"/> </dubbo:reference>
直连
即跳过注册中新直接找服务提供方,必须提前明确服务提供方的地址,所以该方式一般仅用于开发调试;
<dubbo:registry address="N/A"/>
仅订阅
仅订阅指的是,不发布服务到注册中心,只从注册中心订阅依赖的服务;
使用场景:当我们要开发一个新的Provider,而这个Provider需要依赖其他Provider时,使用,其目的是避免正在开发的服务发布后被消费方调用,因为开发还未完成,可能造成意想不到的结果; 这就用到了仅订阅,再搭配直连即可完成开发调试;
<dubbo:registry protocol="zookeeper" address="10.211.55.8:2181" register="false"/>
仅注册
仅注册指的是,发布自身服务到注册中心,但不从注册中心订阅依赖的服务;
使用场景: 自身需要对外提供服务,但是依赖的某个服务还在开发调试总,不能正常提供访问;
<dubbo:registry protocol="zookeeper" address="10.211.55.8:2181" subscribe="false"/>
多注册中心
对于一些大型系统,为了加快响应速度,可能会在不同地区进行部署,例如阿里云分布在7个不同城市,有的时候可能因为当地系统还未部署完成,但是仍然需要提供访问,这是就需要我们将相同的服务注册到多个不同的注册中心;
反过来,一些时候当前系统依赖的服务可能部署在不同的注册中心中,这就需要同时向多个不同的注册中心订阅服务;
配置方式也非常简单,添加额外registry即可;
案例:
我们在Common模块中创建新的接口com.yyh.service.UserService
,同时在Provider中实现该接口,最后发布到注册中心;
发布到多个注册中心
<!--两个注册中心--> <dubbo:registry id="reg1" protocol="zookeeper" address="10.211.55.8:2181,10.211.55.7:2181,10.211.55.6:2181"/> <dubbo:registry id="reg2" protocol="zookeeper" address="10.211.55.7:2188"/> <!--注册到多个注册中心 id用逗号隔开--> <dubbo:service registry="reg1,reg2" interface="com.yyh.service.HelloService" ref="helloService" cluster="failsafe" loadbalance="random"/> <!--userService仅注册到id为reg2的注册中心--> <dubbo:service registry="reg2" interface="com.kkb.service.UserService" ref="userService" cluster="failsafe" loadbalance="random"/> <!--实现Bean--> <bean id="helloService" class="com.kkb.service.impl.HelloServiceImpl"/> <bean id="userService" class="com.kkb.service.impl.UserServiceImpl"/>
从不同注册中心订阅
<!--两个注册中心--> <dubbo:registry id="reg1" protocol="zookeeper" address="10.211.55.8:2181,10.211.55.7:2181,10.211.55.6:2181"/> <dubbo:registry id="reg2" protocol="zookeeper" address="10.211.55.7:2188"/> <!--从两个注册中心分别订阅 --> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService" cluster="failover" retries="3" registry="reg1"/> <dubbo:reference interface="com.yyh.service.UserService" id="userService" cluster="failover" retries="3" registry="reg2"/> <dubbo:consumer check="false"/>