开发公司负载均衡Ribbon入门

一、开发公司负载均衡的引入

开发公司在微服务中,开发公司服务的调用很常见,比如有2个集群,A和B,如果A开发公司集群需要调用B开发公司集群的某个接口,但是B开发公司集群中有很多服务b,这时A开发公司集群就不知道调用哪个

开发公司为了解决这个问题,开发公司大佬们引入了负载均衡器。

开发公司负载均衡有2种类型

一种是以Nginx开发公司为代表的服务端的负载均衡

开发公司我们用户服务发送请求首先打到Ng上,然后Ng开发公司根据进行选择一个服务调 用,而我们的Ng开发公司部署在服务器上的,所以Ng开发公司又称为服务端的负载均衡(开发公司具体调用哪个服务, 由Ng所了算)

生活案例: 开发公司程序员张三 开发公司去盲人按摩, 开发公司前台的小姐姐接待了张三,开发公司然后为张三分派技师按摩.

开发公司另一种是以为代表的客开发公司户端负载均衡

生活案例: 开发公司程序员张三去盲人按摩,开发公司张三自己选技师按摩.

spring cloud ribbon是 基于NetFilix ribbon 开发公司实现的一套客户端的负载 均衡工具,Ribbon开发公司客户端组件提供一系列开发公司的完善的配置,如超时,重试 等。通过Load Balancer(LB)开发公司获取到服务提供的所有机器实例,Ribbon 开发公司会自动基于某种规则(,随机)开发公司去调用这些服务。Ribbon也可以实 开发公司现我们自己的负载均衡算法。

二、开发公司自定义的负载均衡算法

开发公司我们可以通过DiscoveryClient开发公司组件来去我们的Nacos开发公司服务端拉取给名称的微服务列表。我们可以通过这个特性来改写我们的RestTemplate 组件,经过阅读源码RestTemplate组件得知,不管是post,get请求最终是会调 用我们的doExecute()方法,所以我们写一个TulingRestTemplate类继承 RestTemplate,从写doExucute()方法。

Slf4jpublic class TulingRestTemplate extends RestTemplate {    private DiscoveryClient discoveryClient;    public TulingRestTemplate (DiscoveryClient discoveryClient) {        this.discoveryClient = discoveryClient;    }    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,                              @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {        Assert.notNull(url, "URI is required");        Assert.notNull(method, "HttpMethod is required");        ClientHttpResponse response = null;        try {            log.info("请求的url路径为:{}",url);            //把服务名 替换成我们的IP            url = replaceUrl(url);            log.info("替换后的路径:{}",url);            ClientHttpRequest request = createRequest(url, method);            if (requestCallback != null) {                requestCallback.doWithRequest(request);            }            response = request.execute();            handleResponse(url, method, response);            return (responseExtractor != null ? responseExtractor.extractData(response) : null);        }        catch (IOException ex) {            String resource = url.toString();            String query = url.getRawQuery();            resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);            throw new ResourceAccessException("I/O error on " + method.name() +                    " request for \"" + resource + "\": " + ex.getMessage(), ex);        } finally {            if (response != null) {                response.close();            }        }    }    /**     * 方法实现说明:把微服务名称  去注册中心拉取对应IP进行调用     * http://product-center/selectProductInfoById/1     * @author:smlz     * @param url:请求的url     * @return:     * @exception:     * @date:2020/2/6 13:11     */    private URI replaceUrl(URI url){        //1:从URI中解析调用的调用的serviceName=product-center        String serviceName = url.getHost();        log.info("调用微服务的名称:{}",serviceName);        //2:解析我们的请求路径 reqPath= /selectProductInfoById/1        String reqPath = url.getPath();        log.info("请求path:{}",reqPath);        //通过微服务的名称去nacos服务端获取 对应的实例列表        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(serviceName);        if(serviceInstanceList.isEmpty()) {            throw new RuntimeException("没有可用的微服务实例列表:"+serviceName);        }        String serviceIp = chooseTargetIp(serviceInstanceList);        String source = serviceIp+reqPath;        try {            return new URI(source);        } catch (URISyntaxException e) {            log.error("根据source:{}构建URI异常",source);        }        return url;    }    /**     * 方法实现说明:从服务列表中 随机选举一个ip     * @author:smlz     * @param serviceInstanceList 服务列表     * @return: 调用的ip     * @exception:     * @date:2020/2/6 13:15     */    private String chooseTargetIp(List<ServiceInstance> serviceInstanceList) {        //采取随机的获取一个        Random random = new Random();        Integer randomIndex = random.nextInt(serviceInstanceList.size());        String serviceIp = serviceInstanceList.get(randomIndex).getUri().toString();        log.info("随机选举的服务IP:{}",serviceIp);        return serviceIp;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
三、通过Ribbon组件来实习负载均衡

第一步:加入依赖(加入nocas-client和ribbon的依赖)

<!--加入nocas-client--><dependency>	<groupId>com.alibaba.cloud</groupId>	<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId></dependency><!--加入ribbon--><dependency>	<groupId>org.springframework.cloud</groupId>	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第二步:写注解: 在RestTemplate上加入@LoadBalanced注解

@Configurationpublic class WebConfig implements WebMvcConfigurer {    @LoadBalanced    @Bean    public RestTemplate restTemplate( ) {        return new RestTemplate();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

例如:下面的restTemplate还没有被@LoadBalanced进行处理,所以他不能把服务吗order进行处理。

@PostConstructpublic JsonResult getOrderById1(){    ResponseEntity<JsonResult> responseEntity= restTemplate.getForEntity("http://order/order/getOrder", JsonResult.class);    return responseEntity.getBody();  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第三步:写配置文件(这里是写Nacos 的配置文件,暂时没有配置Ribbon的配置)

spring:  application:    name: order #服务名是必须设置的,否则nacos发现不了这个服务  cloud:    nacos:      discovery:        server-addr: 192.168.93.224:8848
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第四步:测试,存储服务调取订单服务order

@GetMapping("getOrderById1/{orderNo}")public JsonResult getOrderById1(@PathVariable String orderNo){     ResponseEntity<JsonResult> responseEntity= restTemplate.getForEntity("http://order/order/getOrder", JsonResult.class);      return responseEntity.getBody();}
  • 1
  • 2
  • 3
  • 4
  • 5
四、Ribbon负载均衡规则


RandomRule:(随机选择一个Server)

RetryRule: 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功, 则一直尝试使用subRule的方式选择一个可用的server.

RoundRobinRule :轮询选择, 轮询index,选择index对应位置的Server

AvailabilityFilteringRule: 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端 Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查 status里记录的各个Server的运行状态

BestAvailableRule:选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。

WeightedResponseTimeRule: 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低;

ZoneAvoidanceRule:(默认是这个) 复合判断Server所在Zone的性能和Server的可用性选择Server,在没有Zone的情况下作用就是是轮询。

如果我们不想使用默认策略,我们可以这样配置

@Configurationpublic class WebConfig {    @LoadBalanced    @Bean    public RestTemplate restTemplate() {        return new RestTemplate();    }    /**     * 修改默认策略     */    @Bean    public RandomRule randomRule(){        return new RandomRule();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
五、Ribbon自定义负载均衡规则
5.1 基于权重的负载均衡

我们发现,nacos server上的页面发现 注册的微服务有一个权重的概 念。取值为0-1之间

权重选择的概念: 假设我们一个微服务部署了三台服务器A,B,C 其中A,B,C三台服务的性能不一,A的性能最牛逼,B次之,C最差. 那么我们设置权重比例 为5 : 3:2 那就说明 10次请求到A上理论是5次,B 服务上理论是3次,B服务理论是2次.

但是Ribbon 所提供的负载均衡算法中没有基于权重的负载均衡算法。那我们自己实现一个.

public class TulingWeightedRule extends AbstractLoadBalancerRule {    @Autowired    private NacosDiscoveryProperties discoveryProperties;    @Override    public void initWithNiwsConfig(IClientConfig clientConfig) {        //读取配置文件并且初始化,ribbon内部的 几乎用不上    }    @Override    public Server choose(Object key) {        try {            log.info("key:{}",key);            BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer();            log.info("baseLoadBalancer--->:{}",baseLoadBalancer);            //获取微服务的名称            String serviceName = baseLoadBalancer.getName();            //获取Nocas服务发现的相关组件API            NamingService namingService =  discoveryProperties.namingServiceInstance();            //获取 一个基于nacos client 实现权重的负载均衡算法            Instance instance = namingService.selectOneHealthyInstance(serviceName);            //返回一个server            return new NacosServer(instance);        } catch (NacosException e) {            log.error("自定义负载均衡算法错误");        }        return null;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
5.2 同集群优先权重负载均衡算法

业务场景:现在我们有二个微服务order-center, product-center二个微服 务。我们在南京机房部署一套order-center,product-center。为了容灾处理,我们在北京同样部署一套order-center,product-center

@Slf4jpublic class TheSameClusterPriorityRule extends AbstractLoadBalancerRule {    @Autowired    private NacosDiscoveryProperties discoveryProperties;    @Override    public void initWithNiwsConfig(IClientConfig clientConfig) {    }    @Override    public Server choose(Object key) {        try {            //第一步:获取当前服务所在的集群            String currentClusterName = discoveryProperties.getClusterName();            //第二步:获取一个负载均衡对象            BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();            //第三步:获取当前调用的微服务的名称            String invokedSerivceName = baseLoadBalancer.getName();            //第四步:获取nacos clinet的服务注册发现组件的api            NamingService namingService = discoveryProperties.namingServiceInstance();            //第五步:获取所有的服务实例            List<Instance> allInstance =  namingService.getAllInstances(invokedSerivceName);            List<Instance> theSameClusterNameInstList = new ArrayList<>();            //第六步:过滤筛选同集群下的所有实例            for(Instance instance : allInstance) {                if(StringUtils.endsWithIgnoreCase(instance.getClusterName(),currentClusterName)) {                    theSameClusterNameInstList.add(instance);                }            }            Instance toBeChooseInstance ;            //第七步:选择合适的一个实例调用            if(theSameClusterNameInstList.isEmpty()) {                toBeChooseInstance = TulingWeightedBalancer.chooseInstanceByRandomWeight(allInstance);                log.info("发生跨集群调用--->当前微服务所在集群:{},被调用微服务所在集群:{},Host:{},Port:{}",                        currentClusterName,toBeChooseInstance.getClusterName(),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());            }else {                toBeChooseInstance = TulingWeightedBalancer.chooseInstanceByRandomWeight(theSameClusterNameInstList);                log.info("同集群调用--->当前微服务所在集群:{},被调用微服务所在集群:{},Host:{},Port:{}",                        currentClusterName,toBeChooseInstance.getClusterName(),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());            }            return new NacosServer(toBeChooseInstance);        } catch (NacosException e) {            log.error("同集群优先权重负载均衡算法选择异常:{}",e);        }        return null;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
5.2 解决生产环境金丝雀发布(灰度发布)问题

比如 order-center 存在二个版本 V1(老版本) V2(新版本),product-center也存在二个版本V1(老版本) V2新版本 现在需要做到的是 order-center(V1)---->product-center(v1),order-center(V2)— ->product-center(v2)。记住v2版本是小面积部署的,用来测试用户对新版本功能的。若用户完全接受了v2。我们就可以把V1版本卸载完全部署V2版本。

@Slf4jpublic class TheSameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule {    @Autowired    private NacosDiscoveryProperties discoveryProperties;    @Override    public void initWithNiwsConfig(IClientConfig clientConfig) {    }    @Override    public Server choose(Object key) {        try {            String currentClusterName = discoveryProperties.getClusterName();            List<Instance> theSameClusterNameAndTheSameVersionInstList = getTheSameClusterAndTheSameVersionInstances(discoveryProperties);            //声明被调用的实例            Instance toBeChooseInstance;            //判断同集群同版本号的微服务实例是否为空            if(theSameClusterNameAndTheSameVersionInstList.isEmpty()) {                //跨集群调用相同的版本                toBeChooseInstance = crossClusterAndTheSameVersionInovke(discoveryProperties);            }else {                toBeChooseInstance = TulingWeightedBalancer.chooseInstanceByRandomWeight(theSameClusterNameAndTheSameVersionInstList);                log.info("同集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}",                        currentClusterName,toBeChooseInstance.getClusterName(),discoveryProperties.getMetadata().get("current-version"),                        toBeChooseInstance.getMetadata().get("current-version"),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());            }            return new NacosServer(toBeChooseInstance);        } catch (NacosException e) {            log.error("同集群优先权重负载均衡算法选择异常:{}",e);            return null;        }    }    /**     * 方法实现说明:获取相同集群下,相同版本的 所有实例     * @author:smlz     * @param discoveryProperties nacos的配置     * @return: List<Instance>     * @exception: NacosException     * @date:2019/11/21 16:41     */    private List<Instance> getTheSameClusterAndTheSameVersionInstances(NacosDiscoveryProperties discoveryProperties) throws NacosException {        //当前的集群的名称        String currentClusterName = discoveryProperties.getClusterName();        String currentVersion = discoveryProperties.getMetadata().get("current-version");        //获取所有实例的信息(包括不同集群的)        List<Instance> allInstance =  getAllInstances(discoveryProperties);        List<Instance> theSameClusterNameAndTheSameVersionInstList = new ArrayList<>();        //过滤相同集群的        for(Instance instance : allInstance) {            if(StringUtils.endsWithIgnoreCase(instance.getClusterName(),currentClusterName)&&               StringUtils.endsWithIgnoreCase(instance.getMetadata().get("current-version"),currentVersion)) {                theSameClusterNameAndTheSameVersionInstList.add(instance);            }        }        return theSameClusterNameAndTheSameVersionInstList;    }    /**     * 方法实现说明:获取被调用服务的所有实例     * @author:smlz     * @param discoveryProperties nacos的配置     * @return: List<Instance>     * @exception: NacosException     * @date:2019/11/21 16:42     */    private List<Instance> getAllInstances(NacosDiscoveryProperties discoveryProperties) throws NacosException {        //第1步:获取一个负载均衡对象        BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();        //第2步:获取当前调用的微服务的名称        String invokedSerivceName = baseLoadBalancer.getName();        //第3步:获取nacos clinet的服务注册发现组件的api        NamingService namingService = discoveryProperties.namingServiceInstance();        //第4步:获取所有的服务实例        List<Instance> allInstance =  namingService.getAllInstances(invokedSerivceName);        return allInstance;    }    /**     * 方法实现说明:跨集群环境下 相同版本的     * @author:smlz     * @param discoveryProperties     * @return: List<Instance>     * @exception: NacosException     * @date:2019/11/21 17:11     */    private List<Instance> getCrossClusterAndTheSameVersionInstList(NacosDiscoveryProperties discoveryProperties) throws NacosException {        //版本号        String currentVersion = discoveryProperties.getMetadata().get("current-version");        //被调用的所有实例        List<Instance> allInstance = getAllInstances(discoveryProperties);        List<Instance>  crossClusterAndTheSameVersionInstList = new ArrayList<>();        //过滤相同版本        for(Instance instance : allInstance) {            if(StringUtils.endsWithIgnoreCase(instance.getMetadata().get("current-version"),currentVersion)) {                crossClusterAndTheSameVersionInstList.add(instance);            }        }        return crossClusterAndTheSameVersionInstList;    }    private Instance crossClusterAndTheSameVersionInovke(NacosDiscoveryProperties discoveryProperties) throws NacosException {        //获取所有集群下相同版本的实例信息        List<Instance>  crossClusterAndTheSameVersionInstList = getCrossClusterAndTheSameVersionInstList(discoveryProperties);        //当前微服务的版本号        String currentVersion = discoveryProperties.getMetadata().get("current-version");        //当前微服务的集群名称        String currentClusterName = discoveryProperties.getClusterName();        //声明被调用的实例        Instance toBeChooseInstance = null ;        //没有对应相同版本的实例        if(crossClusterAndTheSameVersionInstList.isEmpty()) {            log.info("跨集群调用找不到对应合适的版本当前版本为:currentVersion:{}",currentVersion);            throw new RuntimeException("找不到相同版本的微服务实例");        }else {            toBeChooseInstance = TulingWeightedBalancer.chooseInstanceByRandomWeight(crossClusterAndTheSameVersionInstList);            log.info("跨集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}",                    currentClusterName,toBeChooseInstance.getClusterName(),discoveryProperties.getMetadata().get("current-version"),                    toBeChooseInstance.getMetadata().get("current-version"),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());        }        return toBeChooseInstance;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
六、Ribbon的细粒度负载均衡自定义配置

场景:我订单中心需要采用随机算法调用库存中心,而采用轮询算法调用其他中心微服务。

我们针对调用具体微服务的具体配置类 ProductCenterRibbonConfig,OtherCenterRibbonConfig不能被放在我们主启动类所 在包以及子包下,不然就起不到细粒度配置.

@Configurationpublic class PayCenterRibbonConfig {    @Bean    public IRule roundRobinRule() {        return new RoundRobinRule();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@Configurationpublic class ProductCenterRibbonConfig {    @Bean    public IRule randomRule() {        return new RandomRule();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

ribbon的全局配置

@Configuration@RibbonClients(value = {        @RibbonClient(name = "product-center",configuration = ProductCenterRibbonConfig.class),        @RibbonClient(name = "pay-center",configuration = PayCenterRibbonConfig.class)})@RibbonClients(defaultConfiguration = GlobalRibbonConfig.class)public class CustomRibbonConfig {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

或者我们使用配置文件实现上面的功能,如下

yml配置:(我们可以在order-center的yml中进行配置) 配置格式的语法如下

#自定义Ribbon的细粒度配置product‐center:   ribbon:     NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule pay‐center:   ribbon:     NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
七、Ribbon常用参数讲解
7.1 配置负载均衡策略

Ribbon 默认的策略是轮询,从我们前面讲解的例子输出的结果就可以看出来,Ribbon 中提供了很多的策略。我们通过配置可以指定服务使用哪种策略来进行负载操作。

<服务提供者名称>:  ribbon:    listOfServers: localhost:7901,localhost:7902    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  • 1
  • 2
  • 3
  • 4
7.2 超时时间

Ribbon 中有两种和时间相关的设置,分别是请求连接的超时时间和请求处理的超时时间,设置规则如下:

全局设置

# 请求连接的超时时间ribbon.ConnectTimeout=2000# 请求处理的超时时间ribbon.ReadTimeout=5000
  • 1
  • 2
  • 3
  • 4

局部设置

# 也可以为每个Ribbon客户端设置不同的超时时间, 通过服务名称进行指定:<服务提供者名称>.ribbon.ConnectTimeout=2000<服务提供者名称>.ribbon.ReadTimeout=5000
  • 1
  • 2
  • 3
7.3 并发参数
# 最大连接数ribbon.MaxTotalConnections=500# 每个host最大连接数ribbon.MaxConnectionsPerHost=500
  • 1
  • 2
  • 3
  • 4
7.4 重试机制

在集群环境中,用多个节点来提供服务,难免会有某个节点出现故障。用 Nginx 做负载均衡的时候,如果你的应用是无状态的、可以滚动发布的,也就是需要一台台去重启应用,这样对用户的影响其实是比较小的,因为 Nginx 在转发请求失败后会重新将该请求转发到别的实例上去。

由于 Eureka 是基于 AP 原则构建的,牺牲了数据的一致性,每个 Eureka 服务都会保存注册的服务信息,当注册的客户端与 Eureka 的心跳无法保持时,有可能是网络原因,也有可能是服务挂掉了。

在这种情况下,Eureka 中还会在一段时间内保存注册信息。这个时候客户端就有可能拿到已经挂掉了的服务信息,故 Ribbon 就有可能拿到已经失效了的服务信息,这样就会导致发生失败的请求。

这种问题我们可以利用重试机制来避免。重试机制就是当 Ribbon 发现请求的服务不可到达时,重新请求另外的服务。

有2种方法解决上述问题

第一种,RetryRule 重试,它是利用 Ribbon 自带的重试策略进行重试,此时只需要指定某个服务的负载策略为重试策略即可:

ribbon-config-demo.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RetryRule
  • 1

第一种,Spring Retry 重试,通过集成 Spring Retry 来进行重试操作。

在 pom.xml 中添加 Spring Retry 的依赖,代码如下所示。

<dependency>    <groupId>org.springframework.retry</groupId>    <artifactId>spring-retry</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

配置重试次数等信息:

# 对当前实例的重试次数ribbon.maxAutoRetries=1# 切换实例的重试次数ribbon.maxAutoRetriesNextServer=3# 对所有操作请求都进行重试ribbon.okToRetryOnAllOperations=true# 对Http响应码进行重试ribbon.retryableStatusCodes=500,404,502
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
7.4 解决Ribbon 第一次调用耗时高
#开启饥饿加载ribbon: eager‐load:  enabled: true  clients: product‐center #可以指定多个微服务用逗号分隔
  • 1
  • 2
  • 3
  • 4
  • 5
7.5 是否对所以的操作进行重试
#True 的话 会对post put操作进行重试,存在服务幂等问题,所以最好设置成falseribbon.OkToRetryOnAllOperations=false
  • 1
  • 2
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发