Ribbon:负载均衡
1 Ribbon是什么?
- Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
- 简单地说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。
- Ribbon的客户端组件提供一系列的配置项:连接超时、重试等。
- 简单地说,就是在配置文件中列出LoadBalance(LB,负载均衡)后面所有的机器,Ribbon会自动地帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器。
- 我们也很容易使用Ribbon实现自定义的负载均衡算法。
2 Ribbon能干什么?
- LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。
- 负载均衡简单地说就是将用户的请求平摊到多个服务上,从而导到系统的HA(高可用)。
- 常见的负载均衡软件有Nginx、Lvs等。
- Dubbo、SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。
3 负载均衡的简单分类
1、集中式
- 在服务的消费方和提供方之间使用独立的LB设施(如Nginx,反向代理),由该设施负责把访问请求通过某种策略转发至服务的提供方。
2、进程式
- 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择一个合适的服务器。
- Ribbon就属于进程式LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
4 起步
1、springcloud-consumer-dept-80
pom.xml(导入Ribbon、Eureka依赖)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud</artifactId> <groupId>com.xianhuii</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-consumer-dept-80</artifactId> <dependencies> <!--Ribbon--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--Eureka--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!-- 实体类 --> <dependency> <groupId>com.xianhuii</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies> </project>
application.yaml
server: port: 80
Eureka配置
eureka: client: register-with-eureka: false # 不向Eureka注册 service-url: defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
DeptConsumer_80(@EnableEurekaClient)
package com.xianhuii.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class, args); } }
ConfigBean(@LoadBalanced)
package com.xianhuii.springcloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * 相当于Spring里的applicationContext.xml */ @Configuration public class ConfigBean { // 配置负载均衡实现RestTemplate @LoadBalanced // Ribbon @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
DeptConsumerController
package com.xianhuii.springcloud.controller; import com.xianhuii.springcloud.pojo.Dept; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; /** * 消费者:不应该有Service * RestTemplate:提供多种便捷访问远程http服务的方法,简单的RESTful服务模板 * (url, 参数, responseType) */ @RestController public class DeptConsumerController { @Autowired private RestTemplate restTemplate; // private static final String REST_URL_PREFIX = "http://localhost:8001"; // Ribbon,通过服务名来访问 private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT"; @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class); } @RequestMapping("/consumer/dept/add") public boolean add(Dept dept) { return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class); } @RequestMapping("/consumer/dept/list") public List<Dept> list() { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class); } }
5 自定义负载均衡算法
MyRandomRule
package com.xianhuii.myrule; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class MyRandomRule extends AbstractLoadBalancerRule { 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); // 从存活的服务中随机获取 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) { } }
MyRule
package com.xianhuii.myrule; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyRule { @Bean public IRule myRule() { return new MyRandomRule(); } }
DeptConsumer_80(@RibbonClient)
package com.xianhuii.springcloud; import com.xianhuii.myrule.MyRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; @EnableEurekaClient @SpringBootApplication // 在微服务启动的时候,加载自定义的Ribbon类 @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = MyRule.class) public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class, args); } }