负载均衡策略
Ribbon常见的负载均衡策略
// RoundRobinRule 轮询,默认策略,按序获取provider // RandomRule 随机 // AvailabilityFilteringRule : 可用性敏感策略,会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~ // RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试 //WeightedResponseTimeRule 权重轮询策略,根据每个Provider的响应时间分配一个权重,响应时间越长权重越小,被选中的可能性就越低,初次会使用轮询策略,直到分配好权重 //BestAvailableRule 最少并发数策略,选择正在请求中并发数量小的provider,除非这个provider在熔断中 复制代码
如果想要更换负载均衡策略,在本例中,需要修改服务消费者项目中的 MyConfig 配置文件,内容如下:
@Bean public IRule getRule(){ return new RandomRule(); } 复制代码
增加对 RandomRule 类的注入,使得 Ribbon 优先选择随机策略。
Ribbon自定义负载均衡策略
在实际生产中,我们可能需要根据公司实际需求设计专门的负载均衡策略,接下来我们来设计相关代码。
新建一个文件夹,结构如下:
模仿随机策略的类进行修改,自定义 MyRandomRule 类
public class MyRandomRule extends AbstractLoadBalancerRule { private int count = 0; //每个服务执行次数 private int providerNum = 0; //当前哪个服务被执行 public MyRandomRule(){ } @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"}) public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } else { Server server = null; while(server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); //获得活着的服务 List<Server> allList = lb.getAllServers(); //获得全部的服务 int serverCount = allList.size(); if (serverCount == 0) { return null; } //核心部分 // int index = this.chooseRandomInt(serverCount); // server = (Server)upList.get(index); //我们定义属于自己的执行策略,目前我们有3个provider,那么决定每个provider执行3次,然后接着执行下一个provider if(count<3){ server = (Server)upList.get(providerNum); count++; }else{ count = 0; providerNum++; if(providerNum>=serverCount){ providerNum = 0; } server = (Server)upList.get(providerNum); } if (server == null) { Thread.yield(); } else { if (server.isAlive()) { return server; } server = null; Thread.yield(); } } return server; } } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public void initWithNiwsConfig(IClientConfig clientConfig) { } } 复制代码
RuleConfig
@Configuration public class RuleConfig { @Bean public IRule getRule(){ return new MyRandomRule(); } } 复制代码
最后在入口类上增加注解,启动该策略。
@SpringBootApplication @EnableEurekaClient //在微服务启动时去加载我们自定义的负载均衡策略 @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = RuleConfig.class) public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class,args); } } 复制代码
关于上述 myrule 文件夹的位置,原因在于:RuleConfig 不能在主应用程序上下文的@ComponentScan
中,否则将由所有@RibbonClients
共享。如果您使用@ComponentScan
(或@SpringBootApplication
),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在@ComponentScan
)。
Feign
spring cloud feign
基于Netfix Feign
实现,整合了spring cloud Ribbon
和spring cloud Hystrix
, 是声明式的 web service 客户端,它让微服务之间的调用变得更简单了,类似 controller 调用 service。SpringCloud 集成了 Ribbon 和 Eureka,可在使用 Feign 时提供负载均衡的 http 客户端。
Feign能做什么
我们在上一节讲述 Ribbon 的使用时,利用它对 RestTemplate 的请求拦截来实现对依赖服务的接口调用,而 RestTemplate 已经实现了对 http 请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以我们需要针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用,模块化的代码重复出现在每一个客户端中,代码量会比较大。
比如我们之前案例中使用到的 springcloud-consumer-dept-80 项目,其中的 DeptConsumerController 类封装了对 http 请求的处理方法,如果我们此时再新建一个服务消费者,同样需要处理 dept 对象的相关逻辑,则需要复制一份 DeptConsumerController 代码。
而 spring cloud feign
在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在spring cloud feign
的实现下,我们只需创建一个接口并调用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用spring cloud ribbon
时自动封装服务调用客户端的开发量。
实现
首先我们需要在 springcloud-api 项目中增加实现了 Feign 的接口类。
1、导入依赖
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.6.RELEASE</version> </dependency> </dependencies> 复制代码
2、增加一个 service 接口类
DeptFeignService
package com.msdn.service; import com.msdn.pojo.Dept; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import java.util.List; @Service @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT") public interface DeptFeignService { @PostMapping("/dept/add") boolean addDept(@RequestBody Dept dept); @GetMapping("/dept/get/{id}") Dept queryDept(@PathVariable("id") long id); @GetMapping("/dept/list") List<Dept> queryAll(); } 复制代码
参考 springcloud-consumer-dept-80 项目新建一个名为 springcloud-consumer-dept-80-feign 的 maven 项目,大体内容不变,修改少许内容。
1、导入依赖
<dependencies> <dependency> <groupId>com.msdn</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.6.RELEASE</version> </dependency> </dependencies> 复制代码
2、DeptConsumerController 文件
@RestController public class DeptConsumerController { @Autowired DeptFeignService service; @RequestMapping("/consumer/dept/get/{id}") public Dept getDept(@PathVariable("id") long id) { return service.queryDept(id); } @RequestMapping("/consumer/dept/list") public List<Dept> queryAll() { return service.queryAll(); } @RequestMapping(name = "/consumer/dept/add") public boolean addDept(Dept dept) { return service.addDept(dept); } } 复制代码
3、入口类增加 EnableFeignClients 注解
@SpringBootApplication @EnableEurekaClient @EnableFeignClients public class FeignDeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(FeignDeptConsumer_80.class,args); } } 复制代码
4、启动3个注册中心,3个服务提供者,然后启动新的服务消费者,访问 http://localhost/consumer/dept/list,多次刷新该页面,发现 consumer 的调用顺序是按序执行的,即轮询策略。