1. 为什么需要 Dubbo(摘自http://dubbo.apache.org/zh-cn/docs/user/quick-start.html)

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

初识dubbo-LMLPHP

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

2. Dubbo 的架构(摘自http://dubbo.apache.org/zh-cn/docs/user/quick-start.html)

初识dubbo-LMLPHP

节点角色说明

Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:

初识dubbo-LMLPHP

节点角色说明
Deployer自动部署服务的本地代理
Repository仓库用于存储服务应用发布包
Scheduler调度中心基于访问压力自动增减服务提供者
Admin统一管理控制台
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心

3. Dubbo 的使用,快速启动 Dubbo 服务

  我这里使用IDEA建立父子项目,具体的项目结构如下:

初识dubbo-LMLPHP

  其中 dubbo-server项目引用了 api,provider 两个公共模块。pom文件如下:

<?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">
<parent>
<artifactId>study</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion>
<artifactId>dubbo-study</artifactId>
<packaging>pom</packaging>
<modules>
<module>dubbo-client</module>
<module>dubbo-server</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>

  再来看一下dubbo-api的pom文件:

<?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">
<parent>
<artifactId>dubbo-server</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion> <artifactId>dubbo-api</artifactId>
</project>

  dubbo-provider:

<?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">
<parent>
<artifactId>dubbo-server</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion> <artifactId>dubbo-provider</artifactId> <dependencies>
<dependency>
<artifactId>dubbo-api</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--&lt;!&ndash; https://mvnrepository.com/artifact/com.alibaba/dubbo &ndash;&gt;-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.</version>
</dependency>
<dependency><!--注册中心-->
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency><!--多协议-->
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.</version>
</dependency>
<!--<dependency>-->
<!--<groupId>javax.servlet</groupId>-->
<!--<artifactId>servlet-api</artifactId>-->
<!--<version>2.5</version>-->
<!--</dependency>-->
<dependency><!--多容器启动的时候-->
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.</version>
</dependency>
</dependencies> </project>

  最后来看一下dubbo-client的pom文件:

<?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">
<parent>
<artifactId>dubbo-study</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion> <artifactId>dubbo-client</artifactId> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<artifactId>dubbo-server</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.</version>
</dependency>
</dependencies>
</project>

  好了,环境准备好接下去可以进入代码的编写,快速开始一个dubbo项目。先来编写服务提供方,之前我们学习过RPC远程过程调用,这里的机制差不多,同样需要一个接口,通过注册中心把这个服务发布出去:先再api 工程种定义好接口:

public interface HelloService {
String sayHello(String msg);
}

  在 provider 项目中对其进行实现:

public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello:"+msg;
}
}

  dubbo 是基于 spring 进行拓展的。这里相应的配置需要通过spring的配置文件的方式去加载,我们这里需要新增一个 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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="20880" name="dubbo"/> <!--需要发布的服务-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" />
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/> </beans>

  由于 dubbo 读取配置文件的默认路径是 classpath下的 META-INF/spring/ 这个路径下,所以我也是放在此路径下,下面启动服务:

public class Bootstrap {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-server.xml");
context.start();
System.in.read(); //阻塞当前进程
}
}

  对于启动还有另外一种方式:

public static void main(String[] args) {
//默认情况下会使用spring容器来启动服务
com.alibaba.dubbo.container.Main.main(
new String[]{"spring","log4j"}); }

  这种方式可以指定启动的容器等信息。

  接下去会在 zk 服务器上面看到如下节点:/dubbo/com.wuzz.demo.service.HelloService/providers/下又一个服务注册信息:

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D54380%26side%3Dprovider%26timestamp%3D1565685934056

翻译过来就是

  dubbo://192.168.1.1:20880/com.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D54380%26side%3Dprovider%26timestamp%3D1565685934056

红色部分后面就是请求的一些参数,版本信息等等的请求参数。这样就把服务注册好了。现在我们需要来看看服务调用方,客户端调用也需要一个配置文件:

<?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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <!--调用的服务名称,协议-->
<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo"/> </beans>

  然后启动服务进行调用,控制台输出Hello:WUZZ 完成服务调用:

public class App {
public static void main(String[] args) throws IOException, InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/dubbo-client.xml");
// 得到IGpHello的远程代理对象
HelloService iGpHello = (HelloService) context.getBean("helloService");
System.out.println(iGpHello.sayHello("WUZZ"));
Thread.sleep();
System.in.read();
}
}

4. Dubbo 注册中心原理

  注册中心原理在之前我们RPC集成ZK注册中心我们就说过,这里简单重复一下,就是利用zookeeper的节点特性,在一个服务根节点永久节点下创建一个带有协议地址和参数的临时节点,同时客户端获取服务节点集合并且注册事件,在服务注册及服务宕机的时候引发watch事件来监控服务的上下线。

5. 多协议支持

  多协议支持的演示,我这里把刚刚哪个服务换成dubbo跟hessian协议,两种协议支持,需要在provider项目的 dubbo-server.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="dubbo-server" owner="mic"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181"/> <dubbo:registry id="zk2" address="zookeeper://192.168.1.101:2181"/> <dubbo:protocol port="20880" name="dubbo"/> <dubbo:protocol port="8080" name="hessian"/>
   <!--需要发布的服务-->
   <dubbo:service interface="com.wuzz.demo.service.HelloService"
  ref="helloService" />
   <!--需要发布的服务实现类-->
   <bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/>
</beans> 

  启动项目后我们会发现zookeeper节点下出现2条注册信息的节点,这样就配置好了多协议服务支持:

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D57024%26side%3Dprovider%26timestamp%3D1565693963127
  hessian%3A%2F%2F192.168.1.1%3A8089%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D57024%26side%3Dprovider%26timestamp%3D1565693967933

  客户端调用也需要相应的配置,如下:

<?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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <dubbo:protocol port="" name="dubbo"/> <dubbo:protocol port="" name="hessian"/>
   <!--调用的服务名称,协议-->
   <dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
         protocol="hessian"/><!--这里的protocol可以自己指定需要的协议-->
</beans>

  Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的。

  • dubbo 协议:默认就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。
  • rmi 协议:走 Java 二进制序列化,多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。
  • hessian 协议:走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。
  • http 协议:走 json 序列化。
  • webservice:走 SOAP 文本

  dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议。但是 hessian 是其默认的序列化协议。

  Hessian 的对象序列化机制有 8 种原始类型:原始二进制数据、boolean、64-bit date(64 位毫秒值的日期)、64-bit double、32-bit int、64-bit long、null、UTF-8 编码的 string

  另外还包括 3 种递归类型:list for lists and arrays、map for maps and dictionaries、object for objects

  还有一种特殊的类型:ref:用来表示对共享对象的引用。

  Protocol Buffer 其实是 Google 出品的一种轻量并且高效的结构化数据存储格式,性能比 JSON、XML 要高很多。其实 PB 之所以性能如此好,主要得益于两个:

  • 它使用 proto 编译器,自动进行序列化和反序列化,速度非常快,应该比 XML 和 JSON 快上了 20~100 倍;
  • 它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。

6. 启动检查机制

对于两个dubbo服务需要相互调用,但是在某一个服务未启动,另外的服务启动的时候从注册中心上获取不到该服务的信息就会报错,这里需要配置一下不启动检查机制:

<dubbo:reference id="helloService"
           interface="com.wuzz.demo.service.HelloService"
           check="false"
           protocol="hessian"/>

  这里的 check="false" 就是取消启动检查,但是还是会提示一些服务未注册的错误信息。

7.集群的访问:

  实现集群的方式这里也是同样的简单。我们只要发布同一个服务到两个地址,我这边先重新定义一个接口实现,便于辨别:

public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello2:"+msg;
}
}

  然后我这边重新定义两个server.xml文件,这两个配置的server的ID必须要是一致的:dubbo-cluster1.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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="" name="dubbo"/> <!--需要发布的服务-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" />
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/> </beans>

  dubbo-cluster2.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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="" name="dubbo"/> <!--需要发布的服务-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" />
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl2"/> </beans>

  然后新建两个启动类:

public class BootstrapCluster1 {

    public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-cluster1.xml");
context.start();
System.in.read(); //阻塞当前进程
}
}
public class BootstrapCluster2 {

    public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-cluster2.xml");
context.start();
System.in.read(); //阻塞当前进程
}
}

  客户端的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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <!--调用的服务名称,协议-->
<dubbo:reference id="helloService" loadbalance="roundrobin"
        interface="com.wuzz.demo.service.HelloService" protocol="dubbo"/>
</beans>

  这里实现集群,使用的是默认负载的随机算法,我们使用客户端循环10此去调用该服务:

public class App {
public static void main(String[] args) throws IOException, InterruptedException {
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/dubbo-client.xml");
      for(int i=;i<;i++) {
      // 得到IGpHello的远程代理对象
      HelloService helloService = (HelloService) context.getBean("helloService");        System.out.println(helloService.sayHello("WUZZ"));
      Thread.sleep();
      }
      System.in.read();
  }
}

  启动后我们会发现控制台大致如下:由于是随机的算法,多次实验下来还是差不多的

初识dubbo-LMLPHP

8.多版本支持:

  基于原来的实现类HelloServiceImpl,我们需要新增一个新版本的实类:HelloServiceImpl3

public class HelloServiceImpl3 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello3:"+msg;
}
}

  然后需要修改服务发布的dubbo-server.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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="" name="dubbo"/> <dubbo:protocol port="" name="hessian"/> <!--需要发布的服务 设置版本信息-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" version="1.0.0"/> <dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService3" version="2.0.0"/>
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/> <bean id="helloService3" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl3"/>
</beans>

  然后启动服务,在客户端需要配置调用服务的版本号:

<?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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <!--调用的服务名称,协议-->
<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo" version="2.0.0" /> </beans>

  然后启动调用即可。这样就实现了多版本的功能。通过不同服务版本的发布,我们可以看到 注册中心上的节点变化:

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26bean.name%3Dcom.wuzz.demo.service.HelloService%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D32116%26register%3Dtrue%26release%3D2.7.2%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1589005442923%26version%3D1.0.0,

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService2%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26bean.name%3Dcom.wuzz.demo.service.HelloService2%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D32116%26register%3Dtrue%26release%3D2.7.2%26revision%3D2.0.0%26side%3Dprovider%26timestamp%3D1589005443965%26version%3D2.0.0

  dubbo是基于URL驱动的,所有的配置信息都会在URL上体现,在客户端调用服务端的时候会对服务端注册的时候的信息进行匹配调用。

9.主机绑定

  发布一个Dubbo服务的时候,会生成一个dubbo://ip:port的协议地址,那么这个IP是根据什么生成的呢?可以在ServiceConfig.java代码中 doExportUrlsFor1Protocol 方法找到如下代码;可以发现,在生成绑定的主机的时候,会通过一层一层的判断,直到获取到合法的ip地址。以下源码就是绑定 host 的判断逻辑过程,对于不同协议绑定不同的端口。

if (NetUtils.isInvalidLocalHost(host)) { //从配置文件中获得IP 如果是非法的进入if
anyhost = true;
try {//从网卡 本地地址中获取
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (NetUtils.isInvalidLocalHost(host)) {
// 判断注册中心地址是否为空,可以配置多个注册中心
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
// socket连接注册中心的地址以后再去获取本地的host
host = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (NetUtils.isInvalidLocalHost(host)) {
host = NetUtils.getLocalHost();// 实质上遍历本地网卡返回一个合理的IP地址
}
}
} 

10.集群容错

  什么是容错机制? 容错机制指的是某种系统控制在一定范围内的一种允许或包容犯错情况的发生,举个简单例子,我们在电脑上运行一个程序,有时候会出现无响应的情况,然后系统会弹出一个提示框让我们选择,是立即结束还是继续等待,然后根据我们的选择执行对应的操作,这就是“容错”。

  在分布式架构下,网络、硬件、应用都可能发生故障,由于各个服务之间可能存在依赖关系,如果一条链路中的其中一个节点出现故障,将会导致雪崩效应。为了减少某一个节点故障的影响范围,所以我们才需要去构建容错服务,来优雅的处理这种中断的响应结果。

Dubbo提供了6种容错机制,分别如下:

1.Failover 模式:

  失败自动切换,当出现失败,重试其它服务器。(缺省) ,通常用于读操作,但重试会带来更长延迟。 可通过retries=”2”来设置重试次数(不含第一次)。

2.Failfast :

  快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

3.Failsafe :

  失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

4.Failback :

  失败自动恢复,后台记录失败请求,定时重发。 通常用于消息通知操作。

5.Forking :

  并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 可通过forks=”2”来设置最大并行数。

6.Broadcast :

  广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持) 通常用于通知所有提供者更新缓存或日志等本地资源信息。

client 配置方式如下,通过cluster方式,配置指定的容错方案:

<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo" version="2.0.0"
cluster="failsafe"/>

配置的优先级别:如果现在客户端服务端同时配置了超时时间,但是数值不一样,这个时候是客户端优于服务端,在客户端可以配置到细粒度的点可以配置指定服务的方法参数。方法级别优先,然后是接口,然后是全局配置。如果配置级别一样,这个时候客户端优先。

11.服务降级

  降级的目的是为了保证核心服务可用。

降级可以有几个层面的分类: 自动降级和人工降级; 按照功能可以分为:读服务降级和写服务降级;

  1. 对一些非核心服务进行人工降级,在大促之前通过降级开关关闭哪些推荐内容、评价等对主流程没有影响的功能
  2. 故障降级,比如调用的远程服务挂了,网络故障、或者RPC服务返回异常。 那么可以直接降级,降级的方案比如设置默认值、采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等
  3. 限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能会导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阀值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页(活动太火爆,稍后重试等)

dubbo的降级方式: Mock

实现步骤:

1. 在client端创建一个TestMock类,实现对应IGpHello的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾

2. 在client端的xml配置文件中,添加如下配置,增加一个mock属性指向创建的TestMock

3. 模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到TestMock这个类。当服务端故障解除以后,调用过程将恢复正常,

  配置如下:

<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo" timeout=""
mock="com.wuzz.demo.mock.TestMock"/>

  当然,dubbo作为一个分布式服务治理架构,所具备的功能远远不止这些,了解了dubbo所提供的一些功能,我要进行下一步更加深入的学习啦。下面的学习都基于这个demo

1. 为什么需要 Dubbo(摘自http://dubbo.apache.org/zh-cn/docs/user/quick-start.html)

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

初识dubbo-LMLPHP

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

2. Dubbo 的架构(摘自http://dubbo.apache.org/zh-cn/docs/user/quick-start.html)

初识dubbo-LMLPHP

节点角色说明

Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:

初识dubbo-LMLPHP

节点角色说明
Deployer自动部署服务的本地代理
Repository仓库用于存储服务应用发布包
Scheduler调度中心基于访问压力自动增减服务提供者
Admin统一管理控制台
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心

3. Dubbo 的使用,快速启动 Dubbo 服务

  我这里使用IDEA建立父子项目,具体的项目结构如下:

初识dubbo-LMLPHP

  其中 dubbo-server项目引用了 api,provider 两个公共模块。pom文件如下:

<?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">
<parent>
<artifactId>study</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion>
<artifactId>dubbo-study</artifactId>
<packaging>pom</packaging>
<modules>
<module>dubbo-client</module>
<module>dubbo-server</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>

  再来看一下dubbo-api的pom文件:

<?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">
<parent>
<artifactId>dubbo-server</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion> <artifactId>dubbo-api</artifactId>
</project>

  dubbo-provider:

<?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">
<parent>
<artifactId>dubbo-server</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion> <artifactId>dubbo-provider</artifactId> <dependencies>
<dependency>
<artifactId>dubbo-api</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--&lt;!&ndash; https://mvnrepository.com/artifact/com.alibaba/dubbo &ndash;&gt;-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.</version>
</dependency>
<dependency><!--注册中心-->
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency><!--多协议-->
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.</version>
</dependency>
<!--<dependency>-->
<!--<groupId>javax.servlet</groupId>-->
<!--<artifactId>servlet-api</artifactId>-->
<!--<version>2.5</version>-->
<!--</dependency>-->
<dependency><!--多容器启动的时候-->
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.</version>
</dependency>
</dependencies> </project>

  最后来看一下dubbo-client的pom文件:

<?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">
<parent>
<artifactId>dubbo-study</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.</modelVersion> <artifactId>dubbo-client</artifactId> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<artifactId>dubbo-server</artifactId>
<groupId>com.wuzz.demo</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.</version>
</dependency>
</dependencies>
</project>

  好了,环境准备好接下去可以进入代码的编写,快速开始一个dubbo项目。先来编写服务提供方,之前我们学习过RPC远程过程调用,这里的机制差不多,同样需要一个接口,通过注册中心把这个服务发布出去:先再api 工程种定义好接口:

public interface HelloService {
String sayHello(String msg);
}

  在 provider 项目中对其进行实现:

public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello:"+msg;
}
}

  dubbo 是基于 spring 进行拓展的。这里相应的配置需要通过spring的配置文件的方式去加载,我们这里需要新增一个 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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="20880" name="dubbo"/> <!--需要发布的服务-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" />
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/> </beans>

  由于 dubbo 读取配置文件的默认路径是 classpath下的 META-INF/spring/ 这个路径下,所以我也是放在此路径下,下面启动服务:

public class Bootstrap {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-server.xml");
context.start();
System.in.read(); //阻塞当前进程
}
}

  对于启动还有另外一种方式:

public static void main(String[] args) {
//默认情况下会使用spring容器来启动服务
com.alibaba.dubbo.container.Main.main(
new String[]{"spring","log4j"}); }

  这种方式可以指定启动的容器等信息。

  接下去会在 zk 服务器上面看到如下节点:/dubbo/com.wuzz.demo.service.HelloService/providers/下又一个服务注册信息:

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D54380%26side%3Dprovider%26timestamp%3D1565685934056

翻译过来就是

  dubbo://192.168.1.1:20880/com.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D54380%26side%3Dprovider%26timestamp%3D1565685934056

红色部分后面就是请求的一些参数,版本信息等等的请求参数。这样就把服务注册好了。现在我们需要来看看服务调用方,客户端调用也需要一个配置文件:

<?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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <!--调用的服务名称,协议-->
<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo"/> </beans>

  然后启动服务进行调用,控制台输出Hello:WUZZ 完成服务调用:

public class App {
public static void main(String[] args) throws IOException, InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/dubbo-client.xml");
// 得到IGpHello的远程代理对象
HelloService iGpHello = (HelloService) context.getBean("helloService");
System.out.println(iGpHello.sayHello("WUZZ"));
Thread.sleep();
System.in.read();
}
}

4. Dubbo 注册中心原理

  注册中心原理在之前我们RPC集成ZK注册中心我们就说过,这里简单重复一下,就是利用zookeeper的节点特性,在一个服务根节点永久节点下创建一个带有协议地址和参数的临时节点,同时客户端获取服务节点集合并且注册事件,在服务注册及服务宕机的时候引发watch事件来监控服务的上下线。

5. 多协议支持

  多协议支持的演示,我这里把刚刚哪个服务换成dubbo跟hessian协议,两种协议支持,需要在provider项目的 dubbo-server.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="dubbo-server" owner="mic"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181"/> <dubbo:registry id="zk2" address="zookeeper://192.168.1.101:2181"/> <dubbo:protocol port="20880" name="dubbo"/> <dubbo:protocol port="8080" name="hessian"/>
   <!--需要发布的服务-->
   <dubbo:service interface="com.wuzz.demo.service.HelloService"
  ref="helloService" />
   <!--需要发布的服务实现类-->
   <bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/>
</beans> 

  启动项目后我们会发现zookeeper节点下出现2条注册信息的节点,这样就配置好了多协议服务支持:

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D57024%26side%3Dprovider%26timestamp%3D1565693963127
  hessian%3A%2F%2F192.168.1.1%3A8089%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D57024%26side%3Dprovider%26timestamp%3D1565693967933

  客户端调用也需要相应的配置,如下:

<?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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <dubbo:protocol port="" name="dubbo"/> <dubbo:protocol port="" name="hessian"/>
   <!--调用的服务名称,协议-->
   <dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
         protocol="hessian"/><!--这里的protocol可以自己指定需要的协议-->
</beans>

  Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的。

6. 启动检查机制

对于两个dubbo服务需要相互调用,但是在某一个服务未启动,另外的服务启动的时候从注册中心上获取不到该服务的信息就会报错,这里需要配置一下不启动检查机制:

<dubbo:reference id="helloService"
           interface="com.wuzz.demo.service.HelloService"
           check="false"
           protocol="hessian"/>

  这里的 check="false" 就是取消启动检查,但是还是会提示一些服务未注册的错误信息。

7.集群的访问:

  实现集群的方式这里也是同样的简单。我们只要发布同一个服务到两个地址,我这边先重新定义一个接口实现,便于辨别:

public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello2:"+msg;
}
}

  然后我这边重新定义两个server.xml文件,这两个配置的server的ID必须要是一致的:dubbo-cluster1.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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="" name="dubbo"/> <!--需要发布的服务-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" />
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/> </beans>

  dubbo-cluster2.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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="" name="dubbo"/> <!--需要发布的服务-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" />
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl2"/> </beans>

  然后新建两个启动类:

public class BootstrapCluster1 {

    public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-cluster1.xml");
context.start();
System.in.read(); //阻塞当前进程
}
}
public class BootstrapCluster2 {

    public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-cluster2.xml");
context.start();
System.in.read(); //阻塞当前进程
}
}

  客户端的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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <!--调用的服务名称,协议-->
<dubbo:reference id="helloService" loadbalance="roundrobin"
        interface="com.wuzz.demo.service.HelloService" protocol="dubbo"/>
</beans>

  这里实现集群,使用的是默认负载的随机算法,我们使用客户端循环10此去调用该服务:

public class App {
public static void main(String[] args) throws IOException, InterruptedException {
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/dubbo-client.xml");
      for(int i=;i<;i++) {
      // 得到IGpHello的远程代理对象
      HelloService helloService = (HelloService) context.getBean("helloService");        System.out.println(helloService.sayHello("WUZZ"));
      Thread.sleep();
      }
      System.in.read();
  }
}

  启动后我们会发现控制台大致如下:由于是随机的算法,多次实验下来还是差不多的

初识dubbo-LMLPHP

8.多版本支持:

  基于原来的实现类HelloServiceImpl,我们需要新增一个新版本的实类:HelloServiceImpl3

public class HelloServiceImpl3 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello3:"+msg;
}
}

  然后需要修改服务发布的dubbo-server.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="dubbo-server" owner="wuzz"/> <!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181" file="d:/dubbo-server"/> <!--配置需要发布的协议及端口-->
<dubbo:protocol port="" name="dubbo"/> <dubbo:protocol port="" name="hessian"/> <!--需要发布的服务 设置版本信息-->
<dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService" version="1.0.0"/> <dubbo:service interface="com.wuzz.demo.service.HelloService"
ref="helloService3" version="2.0.0"/>
<!--需要发布的服务实现类-->
<bean id="helloService" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl"/> <bean id="helloService3" class="com.wuzz.demo.ServiceImpl.HelloServiceImpl3"/>
</beans>

  然后启动服务,在客户端需要配置调用服务的版本号:

<?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="dubbo-client" owner="wuzz"/> <!--注册中心-->
<dubbo:registry address="zookeeper://192.168.1.101:2181"/> <!--调用的服务名称,协议-->
<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo" version="2.0.0" /> </beans>

  然后启动调用即可。这样就实现了多版本的功能。通过不同服务版本的发布,我们可以看到 注册中心上的节点变化:

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26bean.name%3Dcom.wuzz.demo.service.HelloService%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D32116%26register%3Dtrue%26release%3D2.7.2%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1589005442923%26version%3D1.0.0,

  dubbo%3A%2F%2F192.168.1.1%3A20880%2Fcom.wuzz.demo.service.HelloService2%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26bean.name%3Dcom.wuzz.demo.service.HelloService2%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.wuzz.demo.service.HelloService%26methods%3DsayHello%26owner%3Dwuzz%26pid%3D32116%26register%3Dtrue%26release%3D2.7.2%26revision%3D2.0.0%26side%3Dprovider%26timestamp%3D1589005443965%26version%3D2.0.0

  dubbo是基于URL驱动的,所有的配置信息都会在URL上体现,在客户端调用服务端的时候会对服务端注册的时候的信息进行匹配调用。

9.主机绑定

  发布一个Dubbo服务的时候,会生成一个dubbo://ip:port的协议地址,那么这个IP是根据什么生成的呢?可以在ServiceConfig.java代码中 doExportUrlsFor1Protocol 方法找到如下代码;可以发现,在生成绑定的主机的时候,会通过一层一层的判断,直到获取到合法的ip地址。以下源码就是绑定 host 的判断逻辑过程,对于不同协议绑定不同的端口。

if (NetUtils.isInvalidLocalHost(host)) { //从配置文件中获得IP 如果是非法的进入if
anyhost = true;
try {//从网卡 本地地址中获取
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (NetUtils.isInvalidLocalHost(host)) {
// 判断注册中心地址是否为空,可以配置多个注册中心
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
// socket连接注册中心的地址以后再去获取本地的host
host = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (NetUtils.isInvalidLocalHost(host)) {
host = NetUtils.getLocalHost();// 实质上遍历本地网卡返回一个合理的IP地址
}
}
} 

10.集群容错

  什么是容错机制? 容错机制指的是某种系统控制在一定范围内的一种允许或包容犯错情况的发生,举个简单例子,我们在电脑上运行一个程序,有时候会出现无响应的情况,然后系统会弹出一个提示框让我们选择,是立即结束还是继续等待,然后根据我们的选择执行对应的操作,这就是“容错”。

  在分布式架构下,网络、硬件、应用都可能发生故障,由于各个服务之间可能存在依赖关系,如果一条链路中的其中一个节点出现故障,将会导致雪崩效应。为了减少某一个节点故障的影响范围,所以我们才需要去构建容错服务,来优雅的处理这种中断的响应结果。

Dubbo提供了6种容错机制,分别如下:

1.Failover 模式:

  失败自动切换,当出现失败,重试其它服务器。(缺省) ,通常用于读操作,但重试会带来更长延迟。 可通过retries=”2”来设置重试次数(不含第一次)。

2.Failfast :

  快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

3.Failsafe :

  失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

4.Failback :

  失败自动恢复,后台记录失败请求,定时重发。 通常用于消息通知操作。

5.Forking :

  并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 可通过forks=”2”来设置最大并行数。

6.Broadcast :

  广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持) 通常用于通知所有提供者更新缓存或日志等本地资源信息。

client 配置方式如下,通过cluster方式,配置指定的容错方案:

<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo" version="2.0.0"
cluster="failsafe"/>

配置的优先级别:如果现在客户端服务端同时配置了超时时间,但是数值不一样,这个时候是客户端优于服务端,在客户端可以配置到细粒度的点可以配置指定服务的方法参数。方法级别优先,然后是接口,然后是全局配置。如果配置级别一样,这个时候客户端优先。

11.服务降级

  降级的目的是为了保证核心服务可用。

降级可以有几个层面的分类: 自动降级和人工降级; 按照功能可以分为:读服务降级和写服务降级;

  1. 对一些非核心服务进行人工降级,在大促之前通过降级开关关闭哪些推荐内容、评价等对主流程没有影响的功能
  2. 故障降级,比如调用的远程服务挂了,网络故障、或者RPC服务返回异常。 那么可以直接降级,降级的方案比如设置默认值、采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等
  3. 限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能会导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阀值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页(活动太火爆,稍后重试等)

dubbo的降级方式: Mock

实现步骤:

1. 在client端创建一个TestMock类,实现对应IGpHello的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾

2. 在client端的xml配置文件中,添加如下配置,增加一个mock属性指向创建的TestMock

3. 模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到TestMock这个类。当服务端故障解除以后,调用过程将恢复正常,

  配置如下:

<dubbo:reference id="helloService"
interface="com.wuzz.demo.service.HelloService"
protocol="dubbo" timeout=""
mock="com.wuzz.demo.mock.TestMock"/>

  当然,dubbo作为一个分布式服务治理架构,所具备的功能远远不止这些,了解了dubbo所提供的一些功能,我要进行下一步更加深入的学习啦。下面的学习都基于这个demo

05-06 05:39