文章目录
  1. 1. 为什么要进行系统拆分?
  2. 2. 如何进行系统拆分?
  3. 3. 拆分后不用dubbo可以吗?
  4. 4. dubbo工作原理是啥?
  5. 5. 注册中心挂了可以继续通信吗?
  6. 6. dubbo支持哪些通信协议?支持哪些序列化协议?
    1. 6.1. dubbo支持不同的通信协议
    2. 6.2. dubbo支持的序列化协议
  7. 7. dubbo负载均衡策略?
  8. 8. dubbo集群容错策略
  9. 9. dubbo动态代理策略
  10. 10. dubbo的spi思想是什么?
  11. 11. 如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试?
    1. 11.1. 服务治理
    2. 11.2. 服务降级
    3. 11.3. 失败重试和超时重试

分布式系统在互联网公司间的应用变得越来越多,传统的单体式应用已渐渐无法业务需求,而分布式系统相关知识也逐渐变成Java工程师一项必备技能。

分布式业务系统简单的说,就是把原来用java开发的一个大块系统,给拆分成多个子系统,多个子系统之间互相调用,形成一个大系统的整体。

关于分布式系统有一些常见的问题。

为什么要进行系统拆分?

1)要是不拆分,一个大系统几十万行代码,几十人维护会非常麻烦。代码经常改着改着就冲突了,各种代码冲突和合并要处理,非常耗费时间;经常我改动了我的代码,你调用了我,导致你的代码也得重新测试,麻烦的要死;然后每次发布都是几十万行代码的系统一起发布,大家得一起提心吊胆准备上线,几十万行代码的上线,可能每次上线都要做很多的检查,很多异常问题的处理,简直是又麻烦又痛苦;而且如果我现在打算把技术升级到最新的spring版本,还不行,因为这可能导致你的代码报错,不敢随意乱改技术。

2)拆分了以后,整个世界清爽了,几十万行代码的系统,拆分成几十个服务,平均每个服务就1~2万行代码,每个服务部署到单独的机器上。几十个工程,几十个git代码仓库里,若干个码农,每个人维护自己的那个服务就可以了,是自己独立的代码,跟别人没关系。再也没有代码冲突了,爽。每次就测试我自己的代码就可以了,爽。每次就发布我自己的一个小服务就可以了,爽。技术上想怎么升级就怎么升级,保持接口不变就可以了,爽。

要提醒的一点是,系统拆分成分布式系统之后,大量的分布式系统面临的问题也是接踵而来,所以后面的问题都是在围绕分布式系统带来的复杂技术挑战在说。

如何进行系统拆分?

系统拆分成分布式系统,多个服务,微服务的架构,是需要拆很多轮的。不是说上来一个架构师第一轮就给拆好了的。第一轮拆完后;团队继续扩大,拆好的某个服务,刚开始是1个人维护1万行代码,后来业务系统越来越复杂,这个服务变成了是10万行代码,5个人维护;那么就需要进行第二轮拆分,1个服务 -> 5个服务,每个服务2万行代码,每人负责一个服务。

建议,一个服务的代码不要太多,1万行左右,两三万撑死了吧。

总结:大部分的系统,是要进行多轮拆分的,第一次拆分,可能就是将以前的多个模块该拆分开来了,比如说将电商系统拆分成订单系统、商品系统、采购系统、仓储系统、用户系统,等等吧。但是后面可能每个系统又变得越来越复杂了,比如说采购系统里面又分成了供应商管理系统、采购单管理系统,订单系统又拆分成了购物车系统、价格系统、订单管理系统。

拆分后不用dubbo可以吗?

当然可以了,大不了最次,就是各个系统之间,直接基于spring mvc,就纯http接口互相通信呗,还能咋样。但是这个肯定是有问题的,因为http接口通信维护起来成本很高,你要考虑超时重试、负载均衡等等各种乱七八糟的问题,比如说你的订单系统调用商品系统,商品系统部署了5台机器,你怎么把请求均匀地甩给那5台机器?这不就是负载均衡?你要是都自己搞那是可以的,但是确实很痛苦。

所以dubbo说白了,是一种rpc框架,就是本地就是进行接口调用,但是dubbo会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡了、服务实例上下线自动感知了、超时重试了,等等乱七八糟的问题。那你就不用自己做了,用dubbo就可以了。

dubbo工作原理是啥?

既然开始聊分布式系统了,自然重点先聊聊dubbo了,毕竟dubbo是目前事实上大部分公司的分布式系统的rpc框架标准,基于dubbo也可以构建一整套的微服务架构。但是需要自己大量开发。

dubbo采用分层架构,一共有十层,分别是:

第一层:service层,接口层,给服务提供者和消费者来实现的

第二层:config层,配置层,主要是对dubbo进行各种配置的

第三层:proxy层,服务代理层,透明生成客户端的stub和服务单的skeleton

第四层:registry层,服务注册层,负责服务的注册与发现

第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务

第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控

第七层:protocol层,远程调用层,封装rpc调用

第八层:exchange层,信息交换层,封装请求响应模式,同步转异步

第九层:transport层,网络传输层,抽象mina和netty为统一接口

第十层:serialize层,数据序列化层

工作流程:

1)第一步,provider向注册中心去注册

2)第二步,consumer从注册中心订阅服务,注册中心会通知consumer注册好的服务

3)第三步,consumer调用provider

4)第四步,consumer和provider都异步的通知监控中心

注册中心挂了可以继续通信吗?

可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。

dubbo支持哪些通信协议?支持哪些序列化协议?

dubbo支持不同的通信协议

1)dubbo协议

dubbo://192.168.0.1:20188

默认就是走dubbo协议的,单一长连接,NIO异步通信,基于hessian作为序列化协议

适用的场景是:传输数据量很小(每次请求在100kb以内),但是并发量很高

为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。

否则如果上亿次请求每次都是短连接的话,服务提供者会扛不住。

而且因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑高并发访问。

2)rmi协议

走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少用

3)hessian协议

走hessian序列化协议,多个短连接,适用于提供者数量比消费者数量还多,适用于文件的传输,一般较少用

4)http协议

走json序列化

5)webservice

走SOAP文本序列化

dubbo支持的序列化协议

dubbo实际基于不同的通信协议,支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。hessian是其默认的序列化协议。

dubbo负载均衡策略?

1)random loadbalance

默认情况下,dubbo是random load balance随机调用实现负载均衡,可以对provider不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。

2)roundrobin loadbalance

还有roundrobin loadbalance,这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。

3)leastactive loadbalance

这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求

4)consistanthash loadbalance

一致性Hash算法,相同参数的请求一定分发到一个provider上去,provider挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。

dubbo集群容错策略

1)failover cluster模式

失败自动切换,自动重试其他机器,默认就是这个,常见于读操作

2)failfast cluster模式

一次调用失败就立即失败,常见于写操作

3)failsafe cluster模式

出现异常时忽略掉,常用于不重要的接口调用,比如记录日志

4)failbackc cluster模式

失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种

5)forking cluster

并行调用多个provider,只要一个成功就立即返回

6)broadcacst cluster

逐个调用所有的provider

dubbo动态代理策略

默认使用javassist动态字节码生成,创建代理类

但是可以通过spi扩展机制配置自己的动态代理策略

dubbo的spi思想是什么?

spi,简单来说,就是service provider interface,说白了是什么意思呢,比如你有个接口,现在这个接口有3个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要spi了,需要根据指定的配置或者是默认的配置,去找到对应的实现类加载进来,然后用这个实现类的实例对象。

spi机制,一般的使用场景如:插件扩展,你开发的是一个给别人使用的开源框架,如果想让别人自己写个插件,插到你的开源框架里面来,扩展某个功能就可以使用spi特性。

这和jdbc的思想很类似,java定义了一套jdbc的接口,但是java是没有提供jdbc的实现类。但是实际上项目跑的时候,要使用jdbc接口的哪些实现类呢?一般来说,我们要根据自己使用的数据库,比如msyql,你就将mysql-jdbc-connector.jar,引入进来;oracle,你就将oracle-jdbc-connector.jar,引入进来。在系统跑的时候,碰到你使用jdbc的接口,他会在底层使用你引入的那个jar中提供的实现类。

dubbo也用了spi思想,不过没有用jdk的spi机制,是自己实现的一套spi机制。

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

这行代码就是dubbo里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现。

Protocol接口,dubbo要判断一下,在系统运行的时候,应该选用这个Protocol接口的哪个实现类来实例化对象来使用呢?

他会去找一个你配置的Protocol,他就会将你配置的Protocol实现类,加载到jvm中来,然后实例化对象,就用你的那个Protocol实现类就可以了。

dubbo的设计思想是微内核,可插拔,大量的组件。如Protocol负责rpc调用的东西,你可以实现自己的rpc调用组件,实现Protocol接口,给自己的一个实现类即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SPI("dubbo")  
public interface Protocol {

int getDefaultPort();

@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

void destroy();

}

在dubbo自己的jar里,在/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中:

1
2
3
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol

所以说,这就看到了dubbo的spi机制默认是怎么玩儿的了,其实就是Protocol接口,@SPI(“dubbo”)说的是,通过SPI机制来提供实现类,实现类是通过dubbo作为默认key去配置文件里找到的,配置文件名称与接口全限定名一样的,通过dubbo作为key可以找到默认的实现了就是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol。dubbo的默认网络通信协议,就是dubbo协议,用的DubboProtocol.

如果想要动态替换掉默认的实现类,需要使用@Adaptive接口,Protocol接口中,有两个方法加了@Adaptive注解,就是说那俩接口会被代理实现。

比如这个Protocol接口搞了俩@Adaptive注解标注了方法,在运行的时候会针对Protocol生成代理类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据url中的protocol来获取那个key,默认是dubbo,你也可以自己指定,你如果指定了别的key,那么就会获取别的实现类的实例了。通过这个url中的参数不同,就可以控制动态使用不同的组件实现类。

下面来说说怎么来自己扩展dubbo中的组件:

自己写个工程,要是那种可以打成jar包的,里面的src/main/resources目录下,搞一个META-INF/services,里面放个文件叫:com.alibaba.dubbo.rpc.Protocol,文件里搞一个my=com.zhss.MyProtocol。自己把jar弄到nexus私服里去。

然后自己搞一个dubbo provider工程,在这个工程里面依赖你自己搞的那个jar,然后在spring配置文件里给个配置:

这个时候provider启动的时候,就会加载到我们jar包里的my=com.zhss.MyProtocol这行配置里,接着会根据你的配置使用你定义好的MyProtocol了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的dubbo内部的组件,就是扔个你自己的jar包,然后配置一下即可。

dubbo里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个jar,让你的consumer或者是provider工程,依赖你的那个jar,在你的jar里指定目录下配置好接口名称对应的文件,里面通过key=实现类。

然后对对应的组件,用类似用你的哪个key对应的实现类来实现某个接口,你可以自己去扩展dubbo的各种功能,提供你自己的实现。

如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试?

服务治理

1)调用链路自动生成

一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,分布式系统由大量的服务组成。那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的清楚了,因为服务实在太多了,可能几百个甚至几千个服务。

那就需要基于dubbo做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将各个服务之间的依赖关系和调用链路生成出来,做成一张图,显示出来,大家才可以看到对吧。

2)服务访问压力以及时长统计

需要自动统计各个接口和服务之间的调用次数以及访问延时,而且要分成两个级别。一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50,TP90,TP99,三个档次的请求延时分别是多少;第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的TP50,TP90,TP99,分别是多少。

这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊

3)其他的

服务分层(避免循环依赖),调用链路失败监控和报警,服务鉴权,每个服务的可用性的监控(接口调用成功率?几个9?)99.99%,99.9%,99%

服务降级

比如说服务A调用服务B,结果服务B挂掉了,服务A重试几次调用服务B,还是不行,直接降级,走一个备用的逻辑,给用户返回响应

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface HelloService {

void sayHello();

}

public class HelloServiceImpl implements HelloService {

public void sayHello() {
System.out.println("hello world......");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
<?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-provider" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.zhss.service.HelloService" ref="helloServiceImpl" timeout="10000" />
<bean id="helloServiceImpl" class="com.zhss.service.HelloServiceImpl" />

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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-consumer" />

<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<dubbo:reference id="fooService" interface="com.test.service.FooService" timeout="10000" check="false" mock="return null">
</dubbo:reference>

</beans>

现在就是mock,如果调用失败统一返回null

但是可以将mock修改为true,然后在跟接口同一个路径下实现一个Mock类,命名规则是接口名称加Mock后缀。然后在Mock类里实现自己的降级逻辑。

1
2
3
4
5
6
7
public class HelloServiceMock implements HelloService {

public void sayHello() {
// 降级逻辑
}

}

失败重试和超时重试

所谓失败重试,就是consumer调用provider要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。

1
<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="2000"/>

某个服务的接口,要耗费5s,你这边不能干等着,你这边配置了timeout之后,我等待2s,还没返回,我直接就撤了,不能干等你

如果是超时了,timeout就会设置超时时间;如果是调用失败了自动就会重试指定的次数;timeout,一般设置为200ms,我们认为不能超过200ms还没返回;retries,3次,设置retries,还一般是在读请求的时候,比如你要查询个数据,你可以设置个retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取2次。写请求就不要retry了。


如果觉得文章很有趣或对你带来了帮助,欢迎请我喝杯咖啡哦~

文章目录
  1. 1. 为什么要进行系统拆分?
  2. 2. 如何进行系统拆分?
  3. 3. 拆分后不用dubbo可以吗?
  4. 4. dubbo工作原理是啥?
  5. 5. 注册中心挂了可以继续通信吗?
  6. 6. dubbo支持哪些通信协议?支持哪些序列化协议?
    1. 6.1. dubbo支持不同的通信协议
    2. 6.2. dubbo支持的序列化协议
  7. 7. dubbo负载均衡策略?
  8. 8. dubbo集群容错策略
  9. 9. dubbo动态代理策略
  10. 10. dubbo的spi思想是什么?
  11. 11. 如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试?
    1. 11.1. 服务治理
    2. 11.2. 服务降级
    3. 11.3. 失败重试和超时重试