用SpringCloud Alibaba搭建属于自己的微服务(十六)~基础搭建~openfegin+ribbon的rpc调用高可用之重试机制

一.概述

服务之间的rpc调用要能达到高可用,重试机制是必不可少的.

二.重试机制

1.场景一

a.场景

(1).server-user调用server-basic.
(2).server-basic服务有节点A和节点B.
(3).server-user服务通过ribbon的算法拿到了节点A.
(4).server-basic服务节点A挂了.
(5).server-user发起调用.
(6).server-user调用连接超时,调用失败.

b.问题分析

server-basic服务有节点A和节点B,server-user服务从注册中心定时拉取注册表(比如30秒拉取一次),此时server-basic服务的节点A挂了,server-user调用server-basic服务时注册表未能及时更新,以为server-basic服务的节点A是健康,去调用了,结果就是握手失败,调用失败.这个时候如果server-user能够切换实例到节点B,进行重试,那么就解决了这个问题.

c.场景复现

(1)server-user服务关闭ribbon的重试机制,见ribbon相关配置,配置详解注释中已经说明.

server:
  port: 1001  #服务端口
spring:
  application:
    name: server-user #服务名称
  cloud:
    nacos:
      discovery:
        server-addr: 47.96.131.185:8848
      config:
        server-addr: 47.96.131.185:8848  #nacos config配置中心ip和端口
        file-extension: yaml  #文件扩展名格式,针对于默认的{spring.application.name}-${profile}.${file-extension:properties}配置
        enabled: true #开启或关闭配置中心
        shared-dataids: mysql-user.yaml #自定义的配置文件dataid,以逗号分隔
        refreshable-dataids: mysql-user.yaml #自定义的配置文件dataid实现自动刷新,以逗号分隔(其实就是热加载配置文件)
ribbon:
  #对所有操作请求都进行重试,默认false(false=只有get请求才会进行重试)
  OkToRetryOnAllOperations: false
  #响应超时时间
  ReadTimeout: 3000
  #请求连接的超时时间
  ConnectTimeout: 2000
  #对当前实例的重试次数,默认0
  MaxAutoRetries: 0
  #对切换实例的重试次数,默认1
  MaxAutoRetriesNextServer: 0

(2)启动一个server-user服务,启动两个server-basic服务不同的端口,测试代码复用上一章节的ribbon负载均衡测试,这里贴出.
server-user服务的RibbonTestConsumerController.java

package com.ccm.server.user.controller;

import com.ccm.common.exception.ServerException;
import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.user.openfeign.ServerBasicFeign;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description ribbon负载均衡测试客户端
 * @Author zhouzhiwu
 * @CreateTime 2020/07/18 19:37
 */
@RestController
@RequestMapping(value = "ribbonTestProducer")
@Api(tags = "ribbon负载均衡测试服务端")
public class RibbonTestConsumerController {

    @Autowired
    private ServerBasicFeign serverBasicFeign;


    @ApiOperation(value = "ribbon负载均衡测试")
    @GetMapping(value = "test01")
    public ResultSet test01() {
        ResultSet<String> feignVO = serverBasicFeign.test02();
        if(feignVO.getCode() == 0) {
            return ResultSet.success(feignVO.getData());
        }
        throw new ServerException("调用外部服务失败");
    }
}

server-user服务的ServerBasicFeign.java

package com.ccm.server.user.openfeign;


import com.ccm.common.exception.result.ResultSet;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 *  @Description 调用server-basic的feign层
 *  @Author zhouzhiwu
 *  @CreateTime 2020/07/17 9:46
 */
@FeignClient(name = "server-basic")
public interface ServerBasicFeign {

    @GetMapping(value = "openFeignTest/test01")
    ResultSet<String> test01();

    @GetMapping(value = "ribbonTestProducer/test01")
    ResultSet<String> test02();
}

server-basic服务的RibbonTestProducerController.java

package com.ccm.server.basic.controller;

import com.ccm.common.exception.result.ResultSet;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description ribbon负载均衡测试服务端
 * @Author zhouzhiwu
 * @CreateTime 2020/07/18 19:37
 */
@RestController
@RequestMapping(value = "ribbonTestProducer")
@Api(tags = "ribbon负载均衡测试服务端")
public class RibbonTestProducerController {

    @Value("${server.port}")
    private String port;

    @ApiOperation(value = "ribbon负载均衡测试")
    @GetMapping(value = "test01")
    public ResultSet test01() {
        return ResultSet.success("我是server-basic的数据,端口="+port);
    }
}

启动服务
在这里插入图片描述
(3)服务启动成功后等待1分钟(保证server-user中当前的注册表为最新),然后关闭server-basic的其中一个节点,迅速使用swagger调用两次(保证server-user服务中注册表刷新前,一般是10秒左右刷新一次).

显然有一次调用失败,报了连接超时的错误,场景得到复现.
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

d.问题解决.

(1)阅读到这里,读者们早就发现了,默认情况下ribbon是有重试机制的,不需要我们去加入,相关的配置参数如下.

ribbon:
  #对所有操作请求都进行重试,默认false(false=只有get请求才会进行重试)
  OkToRetryOnAllOperations: false
  #响应超时时间
  ReadTimeout: 3000
  #请求连接的超时时间
  ConnectTimeout: 2000
  #对当前实例的重试次数,默认0
  MaxAutoRetries: 0
  #对切换实例的重试次数,默认1
  MaxAutoRetriesNextServer: 0

(2)配置建议
a.开启连接重试,关闭响应重试(原因:响应重试一般是被调用方代码执行时间较长,关闭响应重试可以保证代码不会重复执行).
b.关闭当前实例重试,开启切换重试实例(原因:当前实例失败了,第二次重试的失败的几率也会很高).
c.相关配置组合很多,要熟悉这一章节,必须自己手动去配置玩玩,看看不同的效果.

源码地址:https://gitee.com/chouchimoo/ccm-mall.git(本章节分支:zj-16)

猜你喜欢

转载自blog.csdn.net/theOldCaptain/article/details/107488273