原文来自我的博客
不多说,大纲走一波
我要直接说《分布式服务治理框架》讲道理没了解过的小伙伴应该是直接一脸懵逼,那么就从其主要想解决什么问题开始吧,首先说说历史
从经典的单体架构说起,通常这样是没有什么问题的,直到现在都很常用,然而随着用户量的增加,扩展性能也很方便,加服务器可以了,但随着用户越来越多,增加服务器的成本也大大的提高了,而且很多地方也容易造成性能的浪费,随着越加越多,渐渐的带来的提升也不是那么的明显了,开发维护起来也非常臃肿,于是就想把它拆分,例如这里我将它水平拆分,将所有的系统都拆分成单个一体的服务,这样利于开发维护和做技术积累,在扩展性能的时候也可以有针对的扩展
在我们水平拆分后,例如调用下订单这个服务,服务之间的交互是这样的,如上图,那么在这个通信的过程会遇见什么问题?
所谓的治理我个人理解起来就是:解决与管理
在 Dubbo 的架构中有 4 中角色
该图借鉴至 Dubbo 官方文档
Provider
服务提供者,提供服务给予消费者消费
Registry
注册中心,提供者将服务注册至注册中心,消费者向注册中心订阅,一旦服务发生变动都会通知消费者,其实就是服务的发现与注册
Consumer
服务消费者,没什么好说的
Monitor
监视者,监控服务的调用次数与调用时间
首先我创建 2 个项目,一个分别叫做dubbo-server-demo
一个叫做dubbo-client-demo
,分别都使用Maven
来构建
接着在server-api
模块里创建一个对外提供服务的接口TestService
package org.yuequan.dubbo.demo;
public interface TestService {
String sayHello(String msg);
}
接着在server-provider
中对该接口实现,当然也要添加对server-api
的依赖
<dependency>
<groupId>org.yuequan.dubbo.demo</groupId>
<artifactId>server-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.yuequan.dubbo.demo</groupId>
<artifactId>server-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
随后对该接口进行实现
package org.yuequan.dubbo.demo;
public class TestServiceImpl implements TestService {
@Override
public String sayHello(String msg) {
return "Hi,Customer:" + msg;
}
}
首先引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.10</version>
</dependency>
接着在/META-INF/dubbo/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-4.3.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="dubbo-server" owner="yuequan" />
<dubbo:registry address="N/A" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" />
<bean class="org.yuequan.dubbo.demo.TestServiceImpl" name="testService" ></bean>
</beans>
接着我们来写一个main
函数用于启动它,有 2 种方式一种是使用Spring
容器启动方式,还有一种是使用 Dubbo 的Main.main
启动,这里我暂时先用Spring
容器启动
public class App
{
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-server.xml");
context.start();
System.in.read();
}
}
接着启动应用,可以看到日志的输出
同时也看到服务发布成功了
接着来dubbo-client-demo
,首先引入dubbo
依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.10</version>
</dependency>
接着写配置文件META-INF/dubbo/dubbo-client.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-4.3.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="dubbo-client" owner="yuequan" />
<dubbo:registry address="N/A" />
<dubbo:reference interface="org.yuequan.dubbo.demo.TestService" url="dubbo://192.168.10.102:20880/org.yuequan.dubbo.demo.TestService" />
</beans>
还记得控制台日志输出的地址吗,这里就是了,现在暂时不用注册中心,先来一个最简单的点对点通信,接着我们来写客户端的启动类,通信协议默认是使用的dubbo
其默认端口就是 20880
public class App
{
public static void main( String[] args )
{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-client.xml");
TestService service = (TestService) context.getBean("testService");
System.out.println(service.sayHello("YueQuan"));
}
}
接着启动它
至此,一个简单的使用例子就写好了,接着进入下一个小节
上一个小节的例子,我们使用点对点通信的例子,接着我们来配置注册中心,dubbo
默认支持能够用来做注册中心的有
dubbo-server.xml
注册中心配置<!-- N/A 代表着没有,不使用的意思 -->
<dubbo:registry address="N/A" />
<!-- 修改成,当然你要启动ZooKeeper -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
接着引入 ZooKeeper 依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
接着启动
发现日志什么都没有输出,接着配置下日志log4j.properties
log4j.rootLogger=INFO, stdout
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
接着重启,可以看到控制台有日志输出了
接着看看来看看 Zookeeper 这边
// 运行客户端
./zkCli.sh
输入ls /
可以看到下面多了一个 dubbo 节点,接着来看下 dubbo 节点下的内容:ls /dubbo
可以看到接口已经被注册上来了,接着我们看看这个接口下的内容:
ls /dubbo/org.yuequan.dubbo.demo.TestService
可以看到下面现在就 2 个节点,我们来看看现在有多少个 providers:
ls /dubbo/org.yuequan.dubbo.demo.TestService/providers
可以看到已经注册上来了
接着来配置下dubbo-client-demo
,首先引入依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
然后修改配置dubbo-client.xml
<!-- 修改前 -->
<dubbo:registry address="N/A" />
<!-- 修改后 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 修改前 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" url="dubbo://192.168.10.102:20880/org.yuequan.dubbo.demo.TestService" />
<!-- 修改后 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" />
接着启动,可以看到日志输出
接着我们再来看下 Zookeeper:
ls /dubbo/org.yuequan.dubbo.demo.TestService
可以看见多了 2 个节点分别为routers
和consumers
这时候输入
ls /dubbo/org.yuequan.dubbo.demo.TestService/comsumers
会发现返回的是一个空数组,这是因为客户端的消费已经结束了,所以该下的节点也会被清除,这一特性可以很好的利用 Zookeeper 的临时节点来实现,实际上它也是这样做的,接着我们来修改下客户端的代码让他不那么快结束
public class App
{
public static void main( String[] args ) throws InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-client.xml");
TestService service = (TestService) context.getBean("testService");
System.out.println(service.sayHello("YueQuan"));
Thread.sleep(10000);
}
}
启动后,再来看看
ls /dubbo/org.yuequan.dubbo.demo.TestService/comsumers
可以看到消费者信息,这是单个配置注册中心,那如果有多个呢?配置多注册中心也非常简单,如下:
<dubbo:registry protocol="zookeeper" address="192.168.1.1:2181,192.16.1.2:2181,192.168.1.3:2181" />
<!-- OR -->
<dubbo:registry address="zookeeper://192.168.1.1:2181?backup=192.16.1.2:2181,192.168.1.3:2181" />
<!-- 更多可以看官方文档~~~ -->
我们一个服务可能之前是使用的dubbo
协议但后面又改成了hessian
协议,也有可能一个服务提供了dubbo
协议也提供了hessian
协议,但也有的客户端它只使用一种协议的通信方式,在多协议的情况下,Dubbo
框架也做了很好的实现接着修改dubbo-server.xml
文件,这里我将会使用hessian
和dubbo
2 个协议,所以需要先引入hessian
的依赖,这里需要使用到容器所以还需要引入servlet-api
和一个servlet
容器,那这里我就使用jetty
,添加的依赖如下
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.26</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.51</version>
</dependency>
接着修改配置
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8011" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo,hessian" />
接着启动,这样就配置好了,但是这样看不到明显的效果啊?我怎么知道我客户端再未指定协议的情况下消费的是hessian
协议还是dubbo
协议?于是为了演示可以这样做将TestServiceImpl
在复制一份,修改下其中内容
public class TestService2Impl implements TestService {
@Override
public String sayHello(String msg) {
return "Hi,Customer:" + msg + ",through Hessian protocol";
}
}
然后再调整下配置
<bean class="org.yuequan.dubbo.demo.TestServiceImpl" name="testService" ></bean>
<bean class="org.yuequan.dubbo.demo.TestService2Impl" name="testService2"></bean>
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService2" protocol="hessian" />
接着启动,然后为了明显,再修改下客户端的调用代码并引入依赖,让它调用十次,可以看出这十次分别调用的那个
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.26</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.51</version>
</dependency>
客户端代码修改如下
public class App
{
public static void main( String[] args ) throws InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-client.xml");
TestService service = (TestService) context.getBean("testService");
for (int i = 0; i < 10; i++) {
System./out/.println(service.sayHello("YueQuan"));
}
}
}
接着启动查看控制台输出
Dubbo 默认的负载均衡机制是随机的,具体的会在后续文章细写
服务提供对外的接口可能会采取某种策略逐步平滑升级,例如原先使用 1.0.0 的接口,又对外提供了 2.0.0 处于刚刚开放,存在某些不稳定因素,可能想逐步开发,某些客户端继续使用 1.0.0,让一部分客户端先使用 2.0.0
接着我们修改dubbo-server.xml
<!-- 修改前 -->
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService2" protocol="hessian"/>
<!-- 修改后 -->
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo" version="1.0.0" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService2" protocol="hessian" version="2.0.0" />
对于 1.0.0 的接口访问使用dubbo
协议,2.0.0 的接口访问使用hessian
然后修改客户端配置dubbo-client.xml
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" version="1.0.0" />
然后运行,你会发现只会调用定义的1.0.0
版本的服务,2.0.0
同理
在服务的启动过程中可能有依赖于其它服务,但其它服务并没有运行起来,这时候你先起起来了,必然会报错,更有意思的是如果是互相依赖,那互相都起不来怎么办呢?所以这里就要关闭启动检查,例如
<!-- 修改前 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" version="1.0.0" />
<!-- 修改后 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" version="1.0.0" check="false"/>
在集群调用中出现调用失败时,比如所需要调用的服务它一直未返回数据阻塞住了,直到超时等情况,对于调用失败 Dubbo 提供了几种容错方案,分别是
Failover
默认就是它,失败后自动切换,当出现失败后,自动切换其它服务器,当然可以设置重试次数
Failfast
快速失败,失败后就立马报错
Failfast
安全失败,发生异常,立马吞掉,也就是直接忽略
Failback
失败后自动恢复
Forking
同时并行调用几个相同的服务,只要一个成功即返回,但可以设置最大并行数
Broadcast
广播调用所有提供者,挨个调用,只要有一个报错就报错
通常可以通过配置可以对容错的手段进行更改,如下:
<!-- 修改前 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" />
<!-- 修改后 -->
<dubbo:reference cluster="failsafe" id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" />
这里我配置的failsafe
所以消费者调用该服务的提供者就算出现异常也会被忽略掉,为了演示效果,我这里将超时时间修改为 50 毫秒
因为我这里是本机所以 50 毫秒也还是有执行成功的现象不过无所谓,看第一个null
执行失败的异常并没有输出而是直接返回了 null
降级我用一个比较笼统的说法来简单的阐述一下,降级实际上就是关闭或者减少其它服务所占用的资源来保证核心服务可用,就比如小米的饥饿营销或者在双 11 下单的时候经常会出现服务器繁忙,请稍后再试,再比如说在双 11 下单高峰的时候,为了集中资源,将平时首页给推荐的数据分析并推送给我的相关服务全部关掉,仅保留基本核心服务,等高峰期一过在把这些附加服务开放,实际上降级有很多种,比如:页面降级、读降级、写降级、超时降级、开关降级等等等,反正这里 get 到点就可以了,以后应该是有机会来单独说降级这个概念的。
对于服务降级 Dubbo 也是提供了相应的机制来让我们处理的。
就比如说一个接口超过了 50 毫秒的响应时间我们就对它进行降级,来保证它基本可用,来写个 demo 修改配置文件dubbo-client.xml
<!-- 修改前 -->
<dubbo:reference cluster="failsafe" id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" />
<!-- 修改后 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" mock="org.yuequan.dubbo.demo.TimeoutMock" timeout="50"/>
这里值得一提的是,可以发现我将cluster
因为这里如果是使用的failsafe
的话是不会降级的,而是直接走failsafe
的机制的,接着运行,我们来看下控制台
可以看到调用超过了 50 毫秒的会直接降级,本节就先到这里。
本文转载自: http://www.spring4all.com/article/1353