随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
节点角色说明
节点 | 角色说明 |
---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
调用关系说明
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
节点角色说明
节点 | 角色说明 |
---|---|
Deployer |
自动部署服务的本地代理 |
Repository |
仓库用于存储服务应用发布包 |
Scheduler |
调度中心基于访问压力自动增减服务提供者 |
Admin |
统一管理控制台 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
我这里使用IDEA建立父子项目,具体的项目结构如下:
其中 dubbo-server项目引用了 api,provider 两个公共模块。pom文件如下:
study
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-study
pom
dubbo-client
dubbo-server
UTF-8
1.8
1.8
再来看一下dubbo-api的pom文件:
dubbo-server
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-api
dubbo-provider:
dubbo-server
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-provider
dubbo-api
com.wuzz.demo
1.0-SNAPSHOT
com.alibaba
dubbo
2.5.3
org.springframework
spring
org.apache.zookeeper
zookeeper
3.4.10
com.101tec
zkclient
0.10
com.caucho
hessian
4.0.38
org.mortbay.jetty
jetty
6.1.26
最后来看一下dubbo-client的pom文件:
dubbo-study
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-client
junit
junit
4.11
test
dubbo-server
com.wuzz.demo
1.0-SNAPSHOT
com.alibaba
dubbo
2.5.3
org.apache.zookeeper
zookeeper
3.4.10
com.101tec
zkclient
0.10
com.caucho
hessian
4.0.38
javax.servlet
servlet-api
2.5
org.mortbay.jetty
jetty
6.1.26
好了,环境准备好接下去可以进入代码的编写,快速开始一个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 配置文件。
由于 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
红色部分后面就是请求的一些参数,版本信息等等的请求参数。这样就把服务注册好了。现在我们需要来看看服务调用方,客户端调用也需要一个配置文件:
然后启动服务进行调用,控制台输出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(1000);
System.in.read();
}
}
注册中心原理在之前我们RPC集成ZK注册中心我们就说过,这里简单重复一下,就是利用zookeeper的节点特性,在一个服务根节点永久节点下创建一个带有协议地址和参数的临时节点,同时客户端获取服务节点集合并且注册事件,在服务注册及服务宕机的时候引发watch事件来监控服务的上下线。
多协议支持的演示,我这里把刚刚哪个服务换成dubbo跟hessian协议,两种协议支持,需要在provider项目的 dubbo-server.xml种配置如下信息:
启动项目后我们会发现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
客户端调用也需要相应的配置,如下:
Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的。
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 之所以性能如此好,主要得益于两个:
对于两个dubbo服务需要相互调用,但是在某一个服务未启动,另外的服务启动的时候从注册中心上获取不到该服务的信息就会报错,这里需要配置一下不启动检查机制:
这里的 check="false" 就是取消启动检查,但是还是会提示一些服务未注册的错误信息。
实现集群的方式这里也是同样的简单。我们只要发布同一个服务到两个地址,我这边先重新定义一个接口实现,便于辨别:
public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello2:"+msg;
}
}
然后我这边重新定义两个server.xml文件,这两个配置的server的ID必须要是一致的:dubbo-cluster1.xml
dubbo-cluster2.xml:
然后新建两个启动类:
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配置文件修改如下
这里实现集群,使用的是默认负载的随机算法,我们使用客户端循环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=0;i<10;i++) {
// 得到IGpHello的远程代理对象
HelloService helloService = (HelloService) context.getBean("helloService");
System.out.println(helloService.sayHello("WUZZ"));
Thread.sleep(1000);
}
System.in.read();
}
}
启动后我们会发现控制台大致如下:由于是随机的算法,多次实验下来还是差不多的
基于原来的实现类HelloServiceImpl,我们需要新增一个新版本的实类:HelloServiceImpl3
public class HelloServiceImpl3 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello3:"+msg;
}
}
然后需要修改服务发布的dubbo-server.xml文件:
然后启动服务,在客户端需要配置调用服务的版本号:
然后启动调用即可。这样就实现了多版本的功能。通过不同服务版本的发布,我们可以看到 注册中心上的节点变化:
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上体现,在客户端调用服务端的时候会对服务端注册的时候的信息进行匹配调用。
发布一个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地址
}
}
}
什么是容错机制? 容错机制指的是某种系统控制在一定范围内的一种允许或包容犯错情况的发生,举个简单例子,我们在电脑上运行一个程序,有时候会出现无响应的情况,然后系统会弹出一个提示框让我们选择,是立即结束还是继续等待,然后根据我们的选择执行对应的操作,这就是“容错”。
在分布式架构下,网络、硬件、应用都可能发生故障,由于各个服务之间可能存在依赖关系,如果一条链路中的其中一个节点出现故障,将会导致雪崩效应。为了减少某一个节点故障的影响范围,所以我们才需要去构建容错服务,来优雅的处理这种中断的响应结果。
Dubbo提供了6种容错机制,分别如下:
1.Failover 模式:
失败自动切换,当出现失败,重试其它服务器。(缺省) ,通常用于读操作,但重试会带来更长延迟。 可通过retries=”2”来设置重试次数(不含第一次)。
2.Failfast :
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
3.Failsafe :
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
4.Failback :
失败自动恢复,后台记录失败请求,定时重发。 通常用于消息通知操作。
5.Forking :
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 可通过forks=”2”来设置最大并行数。
6.Broadcast :
广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持) 通常用于通知所有提供者更新缓存或日志等本地资源信息。
client 配置方式如下,通过cluster方式,配置指定的容错方案:
配置的优先级别:如果现在客户端服务端同时配置了超时时间,但是数值不一样,这个时候是客户端优于服务端,在客户端可以配置到细粒度的点可以配置指定服务的方法参数。方法级别优先,然后是接口,然后是全局配置。如果配置级别一样,这个时候客户端优先。
降级的目的是为了保证核心服务可用。
降级可以有几个层面的分类: 自动降级和人工降级; 按照功能可以分为:读服务降级和写服务降级;
dubbo的降级方式: Mock
实现步骤:
1. 在client端创建一个TestMock类,实现对应IGpHello的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾
2. 在client端的xml配置文件中,添加如下配置,增加一个mock属性指向创建的TestMock
3. 模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到TestMock这个类。当服务端故障解除以后,调用过程将恢复正常,
配置如下:
当然,dubbo作为一个分布式服务治理架构,所具备的功能远远不止这些,了解了dubbo所提供的一些功能,我要进行下一步更加深入的学习啦。下面的学习都基于这个demo
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
节点角色说明
节点 | 角色说明 |
---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
调用关系说明
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
节点角色说明
节点 | 角色说明 |
---|---|
Deployer |
自动部署服务的本地代理 |
Repository |
仓库用于存储服务应用发布包 |
Scheduler |
调度中心基于访问压力自动增减服务提供者 |
Admin |
统一管理控制台 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
我这里使用IDEA建立父子项目,具体的项目结构如下:
其中 dubbo-server项目引用了 api,provider 两个公共模块。pom文件如下:
study
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-study
pom
dubbo-client
dubbo-server
UTF-8
1.8
1.8
再来看一下dubbo-api的pom文件:
dubbo-server
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-api
dubbo-provider:
dubbo-server
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-provider
dubbo-api
com.wuzz.demo
1.0-SNAPSHOT
com.alibaba
dubbo
2.5.3
org.springframework
spring
org.apache.zookeeper
zookeeper
3.4.10
com.101tec
zkclient
0.10
com.caucho
hessian
4.0.38
org.mortbay.jetty
jetty
6.1.26
最后来看一下dubbo-client的pom文件:
dubbo-study
com.wuzz.demo
1.0-SNAPSHOT
4.0.0
dubbo-client
junit
junit
4.11
test
dubbo-server
com.wuzz.demo
1.0-SNAPSHOT
com.alibaba
dubbo
2.5.3
org.apache.zookeeper
zookeeper
3.4.10
com.101tec
zkclient
0.10
com.caucho
hessian
4.0.38
javax.servlet
servlet-api
2.5
org.mortbay.jetty
jetty
6.1.26
好了,环境准备好接下去可以进入代码的编写,快速开始一个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 配置文件。
由于 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
红色部分后面就是请求的一些参数,版本信息等等的请求参数。这样就把服务注册好了。现在我们需要来看看服务调用方,客户端调用也需要一个配置文件:
然后启动服务进行调用,控制台输出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(1000);
System.in.read();
}
}
注册中心原理在之前我们RPC集成ZK注册中心我们就说过,这里简单重复一下,就是利用zookeeper的节点特性,在一个服务根节点永久节点下创建一个带有协议地址和参数的临时节点,同时客户端获取服务节点集合并且注册事件,在服务注册及服务宕机的时候引发watch事件来监控服务的上下线。
多协议支持的演示,我这里把刚刚哪个服务换成dubbo跟hessian协议,两种协议支持,需要在provider项目的 dubbo-server.xml种配置如下信息:
启动项目后我们会发现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
客户端调用也需要相应的配置,如下:
Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的。
对于两个dubbo服务需要相互调用,但是在某一个服务未启动,另外的服务启动的时候从注册中心上获取不到该服务的信息就会报错,这里需要配置一下不启动检查机制:
这里的 check="false" 就是取消启动检查,但是还是会提示一些服务未注册的错误信息。
实现集群的方式这里也是同样的简单。我们只要发布同一个服务到两个地址,我这边先重新定义一个接口实现,便于辨别:
public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello2:"+msg;
}
}
然后我这边重新定义两个server.xml文件,这两个配置的server的ID必须要是一致的:dubbo-cluster1.xml
dubbo-cluster2.xml:
然后新建两个启动类:
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配置文件修改如下
这里实现集群,使用的是默认负载的随机算法,我们使用客户端循环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=0;i<10;i++) {
// 得到IGpHello的远程代理对象
HelloService helloService = (HelloService) context.getBean("helloService");
System.out.println(helloService.sayHello("WUZZ"));
Thread.sleep(1000);
}
System.in.read();
}
}
启动后我们会发现控制台大致如下:由于是随机的算法,多次实验下来还是差不多的
基于原来的实现类HelloServiceImpl,我们需要新增一个新版本的实类:HelloServiceImpl3
public class HelloServiceImpl3 implements HelloService {
@Override
public String sayHello(String msg) {
return "Hello3:"+msg;
}
}
然后需要修改服务发布的dubbo-server.xml文件:
然后启动服务,在客户端需要配置调用服务的版本号:
然后启动调用即可。这样就实现了多版本的功能。通过不同服务版本的发布,我们可以看到 注册中心上的节点变化:
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上体现,在客户端调用服务端的时候会对服务端注册的时候的信息进行匹配调用。
发布一个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地址
}
}
}
什么是容错机制? 容错机制指的是某种系统控制在一定范围内的一种允许或包容犯错情况的发生,举个简单例子,我们在电脑上运行一个程序,有时候会出现无响应的情况,然后系统会弹出一个提示框让我们选择,是立即结束还是继续等待,然后根据我们的选择执行对应的操作,这就是“容错”。
在分布式架构下,网络、硬件、应用都可能发生故障,由于各个服务之间可能存在依赖关系,如果一条链路中的其中一个节点出现故障,将会导致雪崩效应。为了减少某一个节点故障的影响范围,所以我们才需要去构建容错服务,来优雅的处理这种中断的响应结果。
Dubbo提供了6种容错机制,分别如下:
1.Failover 模式:
失败自动切换,当出现失败,重试其它服务器。(缺省) ,通常用于读操作,但重试会带来更长延迟。 可通过retries=”2”来设置重试次数(不含第一次)。
2.Failfast :
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
3.Failsafe :
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
4.Failback :
失败自动恢复,后台记录失败请求,定时重发。 通常用于消息通知操作。
5.Forking :
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 可通过forks=”2”来设置最大并行数。
6.Broadcast :
广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持) 通常用于通知所有提供者更新缓存或日志等本地资源信息。
client 配置方式如下,通过cluster方式,配置指定的容错方案:
配置的优先级别:如果现在客户端服务端同时配置了超时时间,但是数值不一样,这个时候是客户端优于服务端,在客户端可以配置到细粒度的点可以配置指定服务的方法参数。方法级别优先,然后是接口,然后是全局配置。如果配置级别一样,这个时候客户端优先。
降级的目的是为了保证核心服务可用。
降级可以有几个层面的分类: 自动降级和人工降级; 按照功能可以分为:读服务降级和写服务降级;
dubbo的降级方式: Mock
实现步骤:
1. 在client端创建一个TestMock类,实现对应IGpHello的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾
2. 在client端的xml配置文件中,添加如下配置,增加一个mock属性指向创建的TestMock
3. 模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到TestMock这个类。当服务端故障解除以后,调用过程将恢复正常,
配置如下:
当然,dubbo作为一个分布式服务治理架构,所具备的功能远远不止这些,了解了dubbo所提供的一些功能,我要进行下一步更加深入的学习啦。下面的学习都基于这个demo