1、为什么需要dubbo?(为了解决什么问题?)
架构演变 | 1 单一应用架构 2 应用和数据库单独部署 3 应用和数据库集群部署 4 数据库压力变大,读写分离 5 使用缓存技术加快速度 6 数据库分库分表 7 应用分为不同的类型拆分 |
应用之间的关系已经十分复杂,产生了以下问题:
- 当服务越来越多,服务 URL 配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
- 服务间依赖关系复杂,应用的启动顺序复杂、应用的架构关系复杂
- 服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
2、Dubbo是什么?(dubbo技术架构)
节点角色:
节点 | 角色说明 |
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
Dubbo架构简单来说其实是生产者-消费者模型。进一步在这种模型上,加上了注册中心和监控中心,用于管理提供方提供的url,以及管理整个过程。
发布订阅过程:
- 启动容器,加载,运行服务提供者。
- 服务提供者在启动时,在注册中心发布注册自己提供的服务。
- 服务消费者在启动时,在注册中心订阅自己所需的服务。
如果考虑失败或变更的情况,就需要考虑下面的过程。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
3、Dubbo如何使用?
3.1 定义服务提供者
新建provider提供者模块,定义如下接口:
/** * xml方式服务提供者接口 */ public interface ProviderService {
String SayHello(String word); } |
实现类
/** * xml方式服务提供者实现类 */ public class ProviderServiceImpl implements ProviderService{
public String SayHello(String word) { return word; } } |
3.2 服务暴露
导入maven依赖
<?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>com.test</groupId> <artifactId>dubbo-provider</artifactId> <version>1.0-SNAPSHOT</version>
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.6</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.5</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.8.0</version> </dependency>
</dependencies> </project> |
进行服务暴露:暴露接口(xml 配置方法)
首先,在项目 resource 目录下创建 META-INF.spring 包,然后再创建 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="provider" owner="cp"> <dubbo:parameter key="qos.enable" value="true"/> <dubbo:parameter key="qos.accept.foreign.ip" value="false"/> <dubbo:parameter key="qos.port" value="55555"/> </dubbo:application>
<dubbo:monitor protocol="registry"/>
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心--> <!--<dubbo:registry address="N/A"/>--> <dubbo:registry address="N/A" />
<!--当前服务发布所依赖的协议;webserovice、Thrift、Hessain、http--> <dubbo:protocol name="dubbo" port="20880"/>
<!--服务发布的配置,需要暴露的服务接口--> <dubbo:service interface="com.dubbo.provider.service.ProviderService" ref="providerService"/>
<!--Bean bean定义--> <bean id="providerService" class="com.dubbo.provider.service.ProviderServiceImpl"/> </beans> |
发布接口:通过 ClassPathXmlApplicationContext 拿到我们刚刚配置好的 xml ,然后调用 context.start() 方法启动
package com.dubbo.provider;
import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ProtocolConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.ServiceConfig; import com.alibaba.dubbo.container.Main; import com.dubbo.provider.service.ProviderService; import com.dubbo.provider.service.ProviderServiceImpl; import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
/** * xml方式启动 * */ public class App { public static void main( String[] args ) throws IOException { //加载xml配置文件启动 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/provider.xml"); context.start(); System.in.read(); // 按任意键退出 } } |
3.2 定义消费消费者
新建消费者consumer模块,先通过点对点方式:
<?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="consumer" owner="cp"/>
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心--> <!--点对点的方式--> <dubbo:registry address="N/A" /> <!--<dubbo:registry address="zookeeper://localhost:2181" check="false"/>-->
<!--生成一个远程服务的调用代理--> <!--点对点方式--> <dubbo:reference id="providerService" interface="com.dubbo.provider.service.ProviderService" url="dubbo://localhost:20880/com.dubbo.provider.service.ProviderService"/>
<!--<dubbo:reference id="providerService" interface="com.dubbo.provider.service.ProviderService"/>-->
</beans> |
导入maven依赖(同服务端类似)
<?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>com.test</groupId> <artifactId>dubbo-consumer</artifactId> <version>1.0-SNAPSHOT</version>
<dependencies> <dependency> <groupId>com.test</groupId> <artifactId>dubbo-provider</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.6</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.5</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.8.0</version> </dependency> </dependencies> </project> |
调用服务
package com.dubbo.consumer;
import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ReferenceConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.dubbo.provider.service.ProviderService; import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
/** * xml的方式调用 * */ public class App { public static void main( String[] args ) throws IOException {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml"); context.start(); ProviderService providerService = (ProviderService) context.getBean("providerService"); String str = providerService.SayHello("hello"); System.out.println(str); System.in.read();
} } |
3.4 加入zookeeper作为注册中心
在前面的操作中没有使用任何注册中心,用一种直连的方式进行。实际都是使用 dubbo + zookeeper 的方式,使用 zookeeper 作为注册中心,这里介绍 zookeeper 作为注册中心的使用方法。对前面的案例进行改造:
3.4.1 服务提供者修改
修改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="provider" owner="cp"> <dubbo:parameter key="qos.enable" value="true"/> <dubbo:parameter key="qos.accept.foreign.ip" value="false"/> <dubbo:parameter key="qos.port" value="55555"/> </dubbo:application>
<dubbo:monitor protocol="registry"/>
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心--> <!--<dubbo:registry address="N/A"/>--> <dubbo:registry address="zookeeper://localhost:2181" check="false"/>
<!--当前服务发布所依赖的协议;webserovice、Thrift、Hessain、http--> <dubbo:protocol name="dubbo" port="20880"/>
<!--服务发布的配置,需要暴露的服务接口--> <dubbo:service interface="com.dubbo.provider.service.ProviderService" ref="providerService"/>
<!--Bean bean定义--> <bean id="providerService" class="com.dubbo.provider.service.ProviderServiceImpl"/>
</beans> |
3.4.2 服务消费者修改
修改 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="consumer" owner="cp"/>
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心--> <!--点对点的方式--> <!--<dubbo:registry address="N/A" />--> <dubbo:registry address="zookeeper://localhost:2181" check="false"/>
<!--生成一个远程服务的调用代理--> <!--点对点方式--> <!--<dubbo:reference id="providerService" interface="com.dubbo.provider.service.ProviderService" url="dubbo://192.168.234.1:20880/com.dubbo.provider.service.ProviderService"/>-->
<dubbo:reference id="providerService" interface="com.dubbo.provider.service.ProviderService"/>
</beans> |
总结:加入zookeeper和直接点对点方式的区别就是:将 dubbo 发布的 url 注册到了 zookeeper,消费端从 zookeeper 消费,zookeeper 相当于一个中介,给消费者提供服务。
3.5注解配置方式
前面使用xml文件配置方式,这里使用注解配置方式,现在微服务都倾向于这种方式,这也是以后发展的趋势, 0配置是以后的趋势。那么如何对 dubbo 使用注解的方式呢?
3.5.1 服务提供者注解配置
接口:
package com.dubbo.provider.service.annotation;
/** * 注解方式接口 */ public interface ProviderServiceAnnotation { String SayHelloAnnotation(String word); } |
实现类
package com.dubbo.provider.service.annotation;
import com.alibaba.dubbo.config.annotation.Service;
/** * 注解方式实现类 */ @Service(timeout = 5000) public class ProviderServiceImplAnnotation implements ProviderServiceAnnotation{
public String SayHelloAnnotation(String word) { return word; } } |
1
、
@Service
用来配置 Dubbo 的服务提供方。
2、通过 Spring 中 Java Config
的技术(@Configuration
)和 annotation 扫描(@EnableDubbo
)来发现、组装、并向外提供 Dubbo 的服务。
package com.dubbo.provider.configuration;
import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ProtocolConfig; import com.alibaba.dubbo.config.ProviderConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
/** * 注解方式配置 */ @Configuration @EnableDubbo(scanBasePackages = "com.dubbo.provider.service.annotation") public class DubboConfiguration {
@Bean // #1 服务提供者信息配置 public ProviderConfig providerConfig() { ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setTimeout(1000); return providerConfig; }
@Bean // #2 分布式应用信息配置 public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("dubbo-annotation-provider"); return applicationConfig; }
@Bean // #3 注册中心信息配置 public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("localhost"); registryConfig.setPort(2181); return registryConfig; }
@Bean // #4 使用协议配置,这里使用 dubbo public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } } |
分析:通过 @EnableDubbo 指定在com.dubbo.provider.service.annotation 下扫描所有标注有 @Service 的类。
通过 @Configuration 将 DubboConfiguration 中所有的 @Bean 通过 Java Config 的方式组装出来并注入给 Dubbo 服务,也就是标注有 @Service 的类。这其中就包括了:
ProviderConfig:服务提供方配置
ApplicationConfig:应用配置
RegistryConfig:注册中心配置
ProtocolConfig:协议配置
启动服务:
package com.dubbo.provider;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; import com.dubbo.provider.configuration.DubboConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import sun.applet.Main;
import java.io.IOException;
/** * 注解启动方式 */ public class AppAnnotation {
public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DubboConfiguration.class); context.start(); System.in.read(); } } |
3.5.2 服务消费者注解配置
引用服务
package com.dubbo.consumer.Annotation;
import com.alibaba.dubbo.config.annotation.Reference; import com.dubbo.provider.service.annotation.ProviderServiceAnnotation; import org.springframework.stereotype.Component;
/** * 注解方式的service */ @Component("annotatedConsumer") public class ConsumerAnnotationService {
@Reference private ProviderServiceAnnotation providerServiceAnnotation;
public String doSayHello(String name) { return providerServiceAnnotation.SayHelloAnnotation(name); } } |
引入依赖
<dependency> <groupId>com.test</groupId> <artifactId>dubbo-provider</artifactId> <version>1.0-SNAPSHOT</version> </dependency> |
组装服务端消费者
package com.dubbo.consumer.configuration;
import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ConsumerConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;
import java.util.HashMap; import java.util.Map;
/** * 注解配置类 */ @Configuration @EnableDubbo(scanBasePackages = "com.dubbo.consumer.Annotation") @ComponentScan(value = {"com.dubbo.consumer.Annotation"}) public class ConsumerConfiguration { @Bean // 应用配置 public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("dubbo-annotation-consumer"); Map<String, String> stringStringMap = new HashMap<String, String>(); stringStringMap.put("qos.enable","true"); stringStringMap.put("qos.accept.foreign.ip","false"); stringStringMap.put("qos.port","33333"); applicationConfig.setParameters(stringStringMap); return applicationConfig; }
@Bean // 服务消费者配置 public ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setTimeout(3000); return consumerConfig; }
@Bean // 配置注册中心 public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("localhost"); registryConfig.setPort(2181); return registryConfig; } } |
发起调用
package com.dubbo.consumer;
import com.dubbo.consumer.Annotation.ConsumerAnnotationService; import com.dubbo.consumer.configuration.ConsumerConfiguration; import com.dubbo.provider.service.ProviderService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
/** * 注解方式启动 * */ public class AppAnnotation { public static void main( String[] args ) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); context.start(); // 启动 ConsumerAnnotationService consumerAnnotationService = context.getBean(ConsumerAnnotationService.class); String hello = consumerAnnotationService.doSayHello("annotation"); // 调用方法 System.out.println("result: " + hello); // 输出结果
} } |
3.6 主要使用场景:
3.6.1 启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 `check="true"。
3.6.2 集群容错
集群模式 | 说明 | 使用方法 |
Failover Cluster | 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 | cluster="xxx" xxx:集群模式名称 ,例如cluster="failover" |
Failfast Cluster | 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 |
|
Failsafe Cluster | 失败安全,出现异常时,直接忽略。 |
|
Failback Cluster | 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 |
|
Forking Cluster | 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。 |
|
Broadcast Cluster | 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。 |
|
3.6.3 直连服务提供者
直连就是点对点方式,绕过注册中心。在开发及测试环境下,只测试指定服务提供者,只需要直接连接服务端的地即可,这种方式简单。