分布式RPC系统框架Dubbo
导读
Apache Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力;面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo官网:点我直达
第一个Dubbo程序(小试牛刀)
创建业务接口工程
项目结构
创建包和接口类
安装项目
创建提供者Provider工程
项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.cyb</groupId> <artifactId>02-first-provider</artifactId> <version>1.0-SNAPSHOT</version> <!--编译器依赖--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>13</maven.compiler.source> <maven.compiler.target>13</maven.compiler.target> <!--自定义版本号--> <spring-version>4.3.16.RELEASE</spring-version> </properties> <dependencies> <!--自定义工程依赖--> <dependency> <groupId>org.cyb</groupId> <artifactId>01-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--dubbo依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.7</version> </dependency> <!--netty--> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency> <!--spring依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-version}</version> </dependency> </dependencies> </project>
注意
spring-dubbo-provider.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--当前工程名称,该名称将在监控平台使用--> <dubbo:application name="02-first-provider"/> <!--注册Service,将来服务的提供者--> <bean id="someService" class="com.cyb.service.SomeServiceImpl"></bean> <!--暴露服务,采用直连的方式--> <dubbo:service interface="com.cyb.service.SomeService" ref="someService" registry="N/A"></dubbo:service> </beans>
SomeServiceImpl.java
package com.cyb.service; public class SomeServiceImpl implements SomeService{ @Override public String hello(String name) { System.out.println("Dubbo World Welcome You"+name); return "chenyanbin"; } }
RunProvider.java
package com.cyb.run; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.IOException; public class RunProvider { public static void main(String[] args) throws IOException { //创建spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("spring-dubbo-provider.xml"); //启动spring容器 ((ClassPathXmlApplicationContext) ac).start(); //将当前主线程阻塞 System.in.read(); } }
创建消费者Consumer工程
项目结构图
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.cyb</groupId> <artifactId>02-first-consumer</artifactId> <version>1.0-SNAPSHOT</version> <!--编译器依赖--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>13</maven.compiler.source> <maven.compiler.target>13</maven.compiler.target> <!--自定义版本号--> <spring-version>4.3.16.RELEASE</spring-version> </properties> <dependencies> <!--自定义工程依赖--> <dependency> <groupId>org.cyb</groupId> <artifactId>01-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--dubbo依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.7</version> </dependency> <!--netty--> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency> <!--spring依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> </dependencies> </project>
spring-dubbo-consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--当前工程的名称,监控中心使用--> <dubbo:application name="02-first-consumer"/> <!--消费引用--> <dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService" url="dubbo://localhost:20880"></dubbo:reference> </beans>
注:端口号20880固定
RunConsumer.java
package com.cyb.run; import com.cyb.service.SomeService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class RunConsumer { public static void main(String[] args) { //创建Spring容器 ApplicationContext ac=new ClassPathXmlApplicationContext("spring-dubbo-consumer.xml"); SomeService service = (SomeService) ac.getBean("someSerivce"); service.hello("tom"); } }
运行
Zookeeper注册中心&Zookeeper集群(Dubbo官网推荐)
拷贝生产者和消费者工程
注册中心其他方式
地址:http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html
Zookeeper工程搭建
配置参考地址:点我直达
启动zookeeper服务
修改提供者工程
pom.xml添加curator客户端依赖
<!-- zk客户端依赖:curator-framework--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency>
注意事项
修改spring-dubbo-provider.xml
代码拷贝区
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--当前工程名称,该名称将在监控平台使用--> <dubbo:application name="03-provider-zk"/> <!--注册Service,将来服务的提供者--> <bean id="someService" class="com.cyb.service.SomeServiceImpl"></bean> <!-- 声明zk服务中心 --> <!-- 单机版 --> <!-- 方式一 --> <dubbo:registry address="zookeeper://192.168.1.111:2181"/> <!-- 方式二 --> <!-- <dubbo:registry protocol="zookeeper" address=""/> --> <!-- 集群配置 --> <!-- 方式一 --> <!-- <dubbo:registry address="zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181" /> --> <!-- 方式二 --> <!-- <dubbo:registry protocol="zookeeper" address="10.20.153.10:2181,10.20.153.11:2181,10.20.153.12:2181" /> --> <!-- 同一Zookeeper,分成多组注册中心 --> <!-- <dubbo:registry id="chinaRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="china" /> <dubbo:registry id="intlRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="intl" /> --> <!-- 暴露服务,将服务暴露给zk服务中心 --> <!-- <dubbo:service interface="com.cyb.service.SomeService" ref="someService" registry="N/A"></dubbo:service> --> <dubbo:service interface="com.cyb.service.SomeService" ref="someService"></dubbo:service> </beans>
修改消费者工程
pom.xml添加curator客户端依赖
<!-- zk客户端依赖:curator-framework--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency>
修改spring-dubbo-consumer.xml
代码拷贝区
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--当前工程的名称,监控中心使用--> <dubbo:application name="03-consumer-zk"/> <!-- 声明zk服务中心 --> <!-- 单机版 --> <dubbo:registry address="zookeeper://192.168.1.111:2181"/> <!--消费引用--> <!-- <dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService" url="dubbo://localhost:20880"></dubbo:reference> --> <dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService"></dubbo:reference> </beans>
运行
Dubbo声明式缓存
为了进一步提高消费者对用户的响应速度,减少提供者的压力。Dubbo提供了基于结果的声明式缓存。该缓存是基于消费者端的,所以使用很简单,只需修改消费者配置文件,与提供者无关。
修改消费者配置文件
仅需在<dubbo:reference />中添加cache="true"即可
修改RunConsumer.java
缓存VS无缓存
先不加缓存,发现提供者执行2次,加上缓存提供者只执行一次
dubbo提供了三种结果缓存机制
- lru:服务级别缓存的默认机制。该机制默认可以缓存1000个结果。若超出1000,将采用最近最少使用原则来删除缓存,以保证最热的数据被缓存。
- threadlocal:当前线程缓存。当多个线程要对当前线程进行某一操作时首先需要查询当前线程的某个信息,通过线程缓存,则可有效减少查询。
- jcache:可以桥接各种缓存实现,即第三方缓存产品。
spring-dubbo-consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--当前工程的名称,监控中心使用--> <dubbo:application name="03-consumer-zk"/> <!-- 声明zk服务中心 --> <!-- 单机版 --> <dubbo:registry address="zookeeper://192.168.1.111:2181"/> <!-- <dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService" url="dubbo://localhost:20880"></dubbo:reference> --> <!-- <dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService"></dubbo:reference> --> <!--消费引用:基于服务级别的声明式缓存(结果缓存)--> <!--<dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService" cache="true"></dubbo:reference>--> <!--消费引用:基于方法级别的声明式缓存(结果缓存)--> <dubbo:reference id="someSerivce" interface="com.cyb.service.SomeService" > <!--lru--> <dubbo:method name="hello" cache="lru"></dubbo:method> <!--threadlocal--> <dubbo:method name="hello" cache="threadlocal"></dubbo:method> </dubbo:reference> </beans>
结果缓存的应用场景
Dubbo的结果缓存可以应用在查询结果不变的场景。即不能使用在如下场景:消费者A调用的业务方法后从DB查询到一个结果a,此后,消费者B对DB中的该结果相关数据进行了修改,已使该查询结果变为b,但由于使用了结果缓存,消费者A中调用业务方法后的查询结果将长时间为a,直到该结果由于缓存空间满而被消除,否则,永远无法得到更新过的结果b。
多版本控制
Dubbo的多版本控制指的是,服务名称(接口名)相同的情况下提供不同的服务(实现类不同) ,然而消费者是通过服务名称(接口名)进行服务查询并进行消费的。提供者所提供的服务名称相同,如何让消费者通过名称进行服务查找呢?为服务添加一个版本号,使用“服务名称”+“版本号”的方式来唯一确定一个服务。
多版本控制主要的应用场景是:当一个接口的实现类需要升级时,可以使用版本号进行过渡(根据开闭原则,不能直接修改原实现类,只能添加新的实现类)。需要注意的是,版本号不同的服务间是不能互相引用的,因为新版本存在的目的是替换老版本。在生产环境中若存在多个提供者需要升级,一般不会一次性全部进行升级,而是会在低压力时间段先升级一部分,然后在下次再进行部分升级,直到全部升级完成。那么,这期间就需要使用版本号进行过渡。
项目准备
拷贝项目
提供者(04-provider-version)
修改配置文件
消费者(04-consumer-version)
执行
服务分组
服务分组与多版本控制的使用方式几乎是相同的,只要将version替换为group即可。但使用目的不同。使用版本控制的目的是为了升级,将原有老版本替换掉,将来不再提供老版本的服务,所以不同版本间不能出现相互调用。而分组的目的则不同,其也是针对相同接口,给出了多种实现类,但不同的是,这些不同实现并没有替换掉谁的意思,是针对不同需求,或针对不同功能模块锁给出的实现。这些实现所提供的服务是并存的,所以他们间可以出现相互调用关系。例如,支付服务的实现,可以有微信支付实现与支付宝实现等。
服务暴露延迟
如果我们的服务启动过程中需要warmup事件(预热事件,与JVM重启后的预热过程相似,在启动后一小段事件后性能才能达到最佳状态)。比如初始化缓存,等待相关资源就位等。可以使用deplay进行延迟暴露。
值需要在服务提供者的<dubbo:service/>标签中添加delay属性,其值若为整数,则单位为毫秒,表示在指定事件后再发布服务;若为-1,则表示在spring初始化完毕后再暴露服务。
多注册中心
很多时候一个项目中会有多个注册中心。
同一个服务注册到多个中心
同一个服务可以注册到不同地域的多个注册中心,以便为不同地域的服务消费者提供更为快捷的服务。
修改服务提供者配置文件。多个注册中心之间使用逗号分隔。
注:不是集群!!!
不同服务注册到不同中心
修改服务提供者配置文件
同一个服务引用自不同的中心
同一个消费者需要调用两个不同中心服务,而调用的该服务的名称(接口)、版本等都是相同的。不同中心的这个相同名称的服务调用时不同数据库中的数据,即相同服务最终执行的结果是不同的。
修改服务消费者配置文件
多协议支持
服务暴露协议
前面的示例中,服务提供者与服务消费者都是通过zookeeper连接协议连接上ZooKeeper注册中心的。
提供者与消费者均连接上了注册中心,那么消费者就理所当然的可以享受提供者提供的服务了么?
实际情况并不是这样的。前述ZooKeeper协议,是消费者/提供者连接注册中心的连接协议,而非消费者与提供者间的连接协议。
当消费者连接上注册中心后,在消费服务之前,首先需要连接上这个服务提供者。虽然消费者通过注册中心可以获取到服务提供者,但提供者对于消费者来说却是透明的,消费者并不知道真正的服务提供者是谁。不过,无论提供者是谁,消费者都必须连接上提供者才可以获取到真正的服务,而这个连接也是需要专门的连接协议的。这个协议称为服务暴露协议。
可是我们之前的代码示例中并没有看到服务暴露协议的相关配置,但仍可正常运行项目,这是为什么呢?因为采用了默认的暴露协议:Dubbo服务暴露协议。处理Dubbo服务暴露协议外,Dubbo框架还支持另外七种服务暴露协议:Hessian协议、HTTP协议、RMI协议、WebService协议、Thrift协议、Memcached协议、Redis协议;但在实际生产中,使用最多的就是Dubbo服务暴露协议。
Dubbo服务暴露协议,适合于小数据量大并发的服务调用,以及服务消费者主机数远大于服务提供者主机数的情况。
服务暴露协议用法
在服务提供者的spring配置文件中首先需要注册暴露协议,然后在暴露服务时具体制定所使用的已注册的暴露协议。
注:protocol属性用于指定当前服务所使用的暴露协议
同一服务支持多种协议
直接修改服务提供者的配置文件
不同服务使用不同协议
直接修改服务提供者配置文件
Dubbo的高级设置及使用建议
在提供者上尽量多配置消费者端属性
提供者上尽量多配置消费者端的属性,让提供者实现着一开始就思考提供者服务特点、服务质量等问题。因为作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间、合理的重试次数等。在提供者端配置后,消费者不配置则会使用提供者端的配置值,即提供者配置可以作为消费者的缺省值。否则,消费者会使用消费者端的全局设置,这对提供者是不可控的,并且往往不合理的。
以下属性在<dubbo:method/>上则是针对指定方法,配置在<dubbo:service/>上则是针对整个服务。
- timeout:远程服务调用超时时限。
- retries:失败重试次数,默认值是2。
- loadbalance:负载均衡算法,默认是随机的random。还可以有轮询roundrobin、最不活跃优先leastactive等。
- actives:消费者最大并发调用限制,即当Consumer对一个服务的并发调用到上限后,新调用会阻塞直到超时。
提供者上配置合理的提供者端属性
- threads:用于指定服务线程池大小
- executes:一个服务提供者并行执行请求上限,即当提供者对一个服务的并发调用达到上限后,新调用会阻塞,此时消费者可能会超时,该属性配置在<dubbo:method/>上则是针对指定方法的,配置在<dubbo:service/>上则是针对整个服务