天天看点

dubbodubbo连接方式dubbo连接所使用的协议雪崩效应分布式接口的幂等设计

dubbo

  • dubbo连接方式
    • 采用zookeeper作为注册中心
      • 使用场景:
      • 配置:
    • 无注册中心, 直连接方式
      • 使用场景:
      • 配置:
    • 分组连接
      • 使用场景:
      • 配置:
  • dubbo连接所使用的协议
    • dubbo://协议
      • 使用场景:
      • 特性:
    • rmi://协议
      • 使用场景:
      • 特性
    • hessian://协议
      • 使用场景:
      • 特性
    • http://协议
      • 使用场景:
      • 特性
    • webservice://协议
      • 使用场景:
      • 特性
    • thrift://协议
      • 使用场景:
    • memcached://协议
      • 注册 memcached 服务的地址
      • 在客户端引用
    • redis://协议
      • 注册 redis 服务的地址
      • 在客户端引用
  • 雪崩效应
    • 1 服务雪崩的原因
      • a. 某几个机器故障:
      • b. 服务器负载发生变化:
      • c. 人为因素:
    • 2 解决或缓解服务雪崩的方案
      • a. 熔断模式:
      • b. 隔离模式:
      • c. 限流模式:
    • 3 熔断设计
      • a. 熔断请求判断机制算法:
      • b. 熔断恢复:
      • c. 熔断报警:
    • 4 隔离设计
      • a. 线程池隔离模式:
      • b. 信号量隔离模式:
    • 5 超时机制设计
  • 分布式接口的幂等设计
    • 什么是接口的幂等性
      • 解释
      • 举例
    • 解决方案
      • 全局唯一ID(通用解决方案)
      • 去重表(适用于插入或更新操作)
      • 插入或更新(适用于插入或更新)
        • 多版本控制(适用于更新)
        • 状态机控制(适用于某状态字段有序更新)

dubbo连接方式

采用zookeeper作为注册中心

使用场景:

​ 在线上部署阶段使用, 对于某些并发访问压力大的服务器节点可以部署集群, 这时dubbo的服务提供方服务器集群可以使用zookeeper来管理.

配置:

服务提供方配置:

<!-- 声明应用名称 -->
<dubbo:application name="pinyougou-sellergoods-service"/>
<!--注册中心为zookeeper -->
<dubbo:registry protocol="zookeeper"  address="192.168.200.128:2181"/>
<!-- 暴露20881端口, 服务调用方通过这个端口调用本机service服务 -->
<dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>
<!-- 包扫描, 在这个包下面编写service实现 -->
<dubbo:annotation package="cn.itcast.core.service" /> 
           

服务调用方配置:

<!-- 声明应用名称 -->
<dubbo:application name="pinyougou-manager-web" />
<!--注册中心为zookeeper -->
<dubbo:registry address="zookeeper://192.168.200.128:2181"/>
<!-- 配置包扫描, 在此包下调用dubbo服务提供方 -->
<dubbo:annotation package="cn.itcast.core.controller" />  
           

无注册中心, 直连接方式

使用场景:

​ 在开发阶段, 服务提供方没有必要部署集群, 所以采用服务调用方直接连接服务提供方更方便测试与开发.

配置:

服务提供方配置:

<!-- 1. 声明应用名称 -->
<dubbo:application name="pinyougou-sellergoods-service"/>
<!-- 2. 配置直接连接 -->
<dubbo:registry address="N/A"/>
<!-- 3. 标记自己的ip和端口 -->
<dubbo:protocol port="20880" host="127.0.0.1"/>
<!-- 4. 提供方注册接口 -->
<dubbo:service interface="cn.itcast.core.service.TbTestService" ref="tbTestServiceImpl"/>

           

服务调用方配置

<!-- 1. 声明应用名称 -->
<dubbo:application name="pinyougou-manager-web"/>
<!-- 2. 配置直接连接 -->
<dubbo:registry address="N/A"/>
<!-- 3. 消费方调用服务配置 -->
<dubbo:reference interface="cn.itcast.core.service.TbTestService" id="tbTestServiceImpl" 
	url="dubbo://127.0.0.1:20880"
/>

           

分组连接

使用场景:

​ 当一个接口有多种实现时,可以用 group 区分。

配置:

服务提供方:

<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
           

服务调用方指定调用的分组:

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

           

服务调用方调用任意分组:

注意: 分组配置需要dubbo

2.2.0

以上版本支持.

dubbo连接所使用的协议

dubbo://协议

使用场景:

​ Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。

反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。

特性:

​ 缺省协议,使用基于 minai a

1.1.7

和 hessian

3.2.1

的 tbremoting 交互。

  • 连接个数:单连接
  • 连接方式:长连接
  • 传输协议:TCP
  • 传输方式:NIO 异步传输
  • 序列化:Hessian 二进制序列化
  • 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。
  • 适用场景:常规远程服务方法调用

rmi://协议

使用场景:

​ RMI 协议采用 JDK 标准的

java.rmi.*

实现,采用阻塞式短连接和 JDK 标准序列化方式。

注意:如果正在使用 RMI 提供服务给外部访问 ,同时应用里依赖了老的 common-collections 包的情况下,存在反序列化安全风险 。

特性

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:TCP
  • 传输方式:同步传输
  • 序列化:Java 标准二进制序列化
  • 适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
  • 适用场景:常规远程服务方法调用,与原生RMI服务互操作

hessian://协议

使用场景:

​ Hessian协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。

Dubbo 的 Hessian 协议可以和原生 Hessian 服务互操作,即:

  • 提供者用 Dubbo 的 Hessian 协议暴露服务,消费者直接用标准 Hessian 接口调用
  • 或者提供方用标准 Hessian 暴露服务,消费方用 Dubbo 的 Hessian 协议调用。

特性

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:HTTP
  • 传输方式:同步传输
  • 序列化:Hessian二进制序列化
  • 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。
  • 适用场景:页面传输,文件传输,或与原生hessian服务互操作

http://协议

使用场景:

​ 基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现

特性

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:HTTP
  • 传输方式:同步传输
  • 序列化:表单序列化
  • 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。
  • 适用场景:需同时给应用程序和浏览器 JS 使用的服务。

webservice://协议

使用场景:

​ 基于 WebService 的远程调用协议,基于 Apache CXF 的

frontend-simple

transports-http

实现 。可以和原生 WebService 服务互操作,即:

  • 提供者用 Dubbo 的 WebService 协议暴露服务,消费者直接用标准 WebService 接口调用,
  • 或者提供方用标准 WebService 暴露服务,消费方用 Dubbo 的 WebService 协议调用。

特性

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:HTTP
  • 传输方式:同步传输
  • 序列化:SOAP 文本序列化
  • 适用场景:系统集成,跨语言调用

thrift://协议

使用场景:

​ 当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。

使用 dubbo thrift 协议同样需要使用 thrift 的 idl compiler 编译生成相应的 java 代码,后续版本中会在这方面做一些增强。

memcached://协议

​ 基于 memcached实现的 RPC 协议。

注册 memcached 服务的地址

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("memcached://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));

           

在客户端引用

在客户端使用:

或者,点对点直连:

也可以使用自定义接口:

方法名建议和 memcached 的标准方法名相同,即:get(key), set(key, value), delete(key)。

如果方法名和 memcached 的标准方法名不相同,则需要配置映射关系:

redis://协议

​ 基于 Redis实现的 RPC 协议。

注册 redis 服务的地址

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("redis://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));

           

在客户端引用

在客户端使用:

或者,点对点直连:

也可以使用自定义接口:

方法名建议和 redis 的标准方法名相同,即:get(key), set(key, value), delet(key)。

如果方法名和 redis 的标准方法名不相同,则需要配置映射关系:

雪崩效应

1 服务雪崩的原因

a. 某几个机器故障:

​ 例如机器的硬驱动引起的错误,或者一些特定的机器上出现一些的bug(如,内存中断或者死锁)。

b. 服务器负载发生变化:

​ 某些时候服务会因为用户行为造成请求无法及时处理从而导致雪崩,例如阿里的双十一活动,若没有提前增加机器预估流量则会造服务器压力会骤然增大二挂掉。

c. 人为因素:

​ 比如代码中的路径在某个时候出现bug

2 解决或缓解服务雪崩的方案

​ 一般情况对于服务依赖的保护主要有3中解决方案:

a. 熔断模式:

​ 这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

b. 隔离模式:

​ 这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。

c. 限流模式:

​ 上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。

3 熔断设计

​ 在熔断的设计主要参考了hystrix的做法。其中最重要的是三个模块:熔断请求判断算法、熔断恢复机制、熔断报警

a. 熔断请求判断机制算法:

​ 使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。

b. 熔断恢复:

​ 对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复。

c. 熔断报警:

​ 对于熔断的请求打日志,异常请求超过某些设定则报警

4 隔离设计

隔离的方式一般使用两种

a. 线程池隔离模式:

​ 使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)

b. 信号量隔离模式:

​ 使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)

5 超时机制设计

超时分两种,一种是请求的等待超时,一种是请求运行超时。

等待超时:在任务入队列时设置任务入队列时间,并判断队头的任务入队列时间是否大于超时时间,超过则丢弃任务。

运行超时:直接可使用线程池提供的get方法

分布式接口的幂等设计

什么是接口的幂等性

解释

什么是接口的幂等性,接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。有些接口可以天然的实现幂等性,比如查询接口,对于查询来说,你查询一次和两次,对于系统来说,没有任何影响,查出的结果也是一样。

除了查询功能具有天然的幂等性之外,增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?

举例

在微服务架构下,我们在完成一个订单流程时经常遇到下面的场景:

  1. (重复创建)一个订单创建接口,第一次调用超时了,然后调用方重试了一次
  2. (重复更新)在订单创建时,我们需要去扣减库存,这时接口发生了超时,调用方重试了一次
  3. (重复更新)当这笔订单开始支付,在支付请求发出之后,在服务端发生了扣钱操作,接口响应超时了,调用方重试了一次
  4. (无序更新)一个订单状态更新接口,调用方连续发送了两个消息,一个是已创建,一个是已付款。但是你先接收到已付款,然后又接收到了已创建

以上问题,就是在单体架构转成微服务架构之后,带来的问题。当然不是说单体架构下没有这些问题,在单体架构下同样要避免重复请求。但是出现的问题要比这少得多。

解决方案

全局唯一ID(通用解决方案)

如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。

从工程的角度来说,使用全局ID做幂等可以作为一个业务的基础的微服务存在,在很多的微服务中都会用到这样的服务,在每个微服务中都完成这样的功能,会存在工作量重复。另外打造一个高可靠的幂等服务还需要考虑很多问题,比如一台机器虽然把全局ID先写入了存储,但是在写入之后挂了,这就需要引入全局ID的超时机制。

使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。

去重表(适用于插入或更新操作)

这种方法适用于在业务中有唯一标识的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入到去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

插入或更新(适用于插入或更新)

这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:

insert into goods_category (goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE
update_time=now()
           

多版本控制(适用于更新)

这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等

在实现时可以如下

状态机控制(适用于某状态字段有序更新)

这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的创建肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99

在做状态机更新时,我们就这可以这样控制

以上就是保证接口幂等性的一些方法。