06-SpringCloud 之 Ribbon
负载均衡 (Load Balance) - LB
- 集中式 LB:在服务的消费方和提供方之间,使用独立的 LB 设施,如 Nginx,由该设施负责把请求通过某种策略转发至服务提供方
- 进程式 LB:将 LB 逻辑集成到消费方,消费方从注册中心获知有哪些地址可用,然后自行选择合适的服务器
什么是 Ribbon
Ribbon 即:客户端的负载均衡器,属于进程式 LB,它是一个类库,集成在消费方进程中,消费方通过它来获取服务提供方地址
创建多个服务提供者
每个提供者使用各自的数据库
-
新建 Module:spring-cloud-provider-dept-8002
-
编写 pom 引入依赖
pom.xml
<?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>spring-cloud-netflix</artifactId> <groupId>com.kaishen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-cloud-provider-dept-8002</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <!-- 引入该模块所需依赖 --> <dependencies> <!-- api --> <dependency> <groupId>com.kaishen</groupId> <artifactId>spring-cloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- junit --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <!-- log4j --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency> <!-- logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- SpringBoot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot 监控 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- DataSource --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!-- Eureka 服务提供方引入 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> </project> -
编写 application.yml
application.yml
server: port: 8002 # Spring 配置 spring: application: name: spring-cloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource # 数据源 driver-class-name: org.gjt.mm.mysql.Driver # 驱动 url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: 123456 # MyBatis 配置 mybatis: mapper-locations: classpath:mybatis/mappings/*.xml type-aliases-package: com.kaishen.pojo # 监控信息配置 info: app.name: kaishen-spring-cloud-dept8002 # Eureka 配置 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: spring-cloud-provider-dept8002 # 服务描述信息 prefer-ip-address: true # 显示 ip 地址 -
编写启动类,开启注解,注册到 Eureka
DeptProviderApplication8002
package com.kaishen; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; /** * 部门服务启动类 * @author : toby * Create in 0:33 2022/5/9 */ @SpringBootApplication @EnableEurekaClient public class DeptProviderApplication8002 { public static void main(String[] args) { SpringApplication.run(DeptProviderApplication8002.class, args); } } -
编写业务功能,新建 dao、mapper、service、controller 等
-
重复以上 1~5 步,创建多个提供者,注意端口号、数据库连接
Ribbon 自带的常用负载均衡算法
- AvailabilityFilteringRule:可用性过滤规则,先过滤掉跳闸的服务,再轮询
- BestAvailableRule:最优规则,过滤掉跳闸的服务,再选择并发量最小的
- RandomRule:随机策略
- RetryRule:重试策略
- RoundRobinRule:轮询调度规则
- WeightedResponseTimeRule:加权响应时间规则,每 30s 计算一次服务器响应时间,响应时间越短的服务器权重越高
Ribbon 实现默认负载均衡
-
新建 Module:spring-cloud-consumer-dept-80
-
编写 pom 引入依赖
pom.xml
<?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>spring-cloud-netflix</artifactId> <groupId>com.kaishen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-cloud-consumer-dept-80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <!-- 引入该模块所需依赖 --> <dependencies> <!-- api --> <dependency> <groupId>com.kaishen</groupId> <artifactId>spring-cloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--SpringBoot Web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot 监控 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Eureka Client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!-- Ribbon --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> </dependencies> </project> -
编写 application.yml,配置注册中心
application.yml
server: port: 80 # Eureka 配置 eureka: client: register-with-eureka: false # Consumer 无需注册 service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ -
创建启动类,使用默认负载均衡策略,不需要加 @RibbonClient 注解
DeptConsumerApplication80
package com.kaishen; import com.ribbon.config.KaiShenRule; 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; /** * 部门服务消费者启动类 * RibbonClient configuration = KaiShenRule.class ==> 启动时扫描 KaiShenRule * @author : toby * Create in 7:43 2022/5/9 */ @SpringBootApplication @EnableEurekaClient @RibbonClient(name = "SPRING-CLOUD-PROVIDER-DEPT", configuration = KaiShenRule.class) public class DeptConsumerApplication80 { public static void main(String[] args) { SpringApplication.run(DeptConsumerApplication80.class, args); } } -
编写配置类,配置 RestTemplate 并开启负载均衡注解 @LoadBalanced
ConfigBean
package com.kaishen.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; /** * LoadBalanced 注解,默认使用过滤轮询策略实现负载均衡,此时启动类无需添加 @RibbonClient 注解 * @author : toby * Create in 7:44 2022/5/9 */ @Configuration public class ConfigBean { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } } -
编写控制层,通过 RestTemplate 实现远程服务调用
DeptConsumerController
package com.kaishen.controller; import com.kaishen.pojo.CommonResult; import lombok.extern.slf4j.Slf4j; 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 javax.annotation.Resource; /** * 部门服务消费者控制层 * @author : toby * Create in 7:46 2022/5/9 */ @RestController @Slf4j public class DeptConsumerController { /** * 微服务前缀 */ private static final String REST_URL_PREFIX = "http://SPRING-CLOUD-PROVIDER-DEPT"; @Resource private RestTemplate restTemplate; /** * 根据部门编号查询部门信息 * @param id 部门编号 * @return 返回前端所需信息 */ @RequestMapping("/consumer/dept/get/{id}") public CommonResult queryDeptById(@PathVariable Long id) { // RestTemplate 实现远程调用 CommonResult result = restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, CommonResult.class); log.info("查询结果:" + result); return result; } }
Ribbon 实现自定义负载均衡策略
-
在实现默认负载均衡基础上,新建 KaiShenRandomRule 类,继承 AbstractLoadBalancerRule 并重写其 choose 方法,此处摘抄随机算法源码实现
KaiShenRandomRule
package com.ribbon.rules; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** * 自定义负载均衡策略 * 重写 choose 方法 * @author : toby * Create in 10:22 2022/5/9 */ @Slf4j public class KaiShenRandomRule extends AbstractLoadBalancerRule { @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } /** * RandomRule 原码 * @param key key * @return 返回可用的服务 */ @Override public Server choose(Object key) { log.info("key: " + key); // 获取当前使用的负载均衡器 ILoadBalancer loadBalancer = this.getLoadBalancer(); if (null == loadBalancer) { log.info("没有可用的负载均衡器"); return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } // 获取在线的服务和全部服务 List<Server> upList = loadBalancer.getReachableServers(); List<Server> allList = loadBalancer.getAllServers(); log.info("在线服务:" + upList + ";总服务:" + allList); int serverCount = allList.size(); if (serverCount == 0) { return null; } // 获取总服务数内的随机数 int index = ThreadLocalRandom.current().nextInt(serverCount); log.info("随机数:" + index); // 从在线的服务列表中随机获取指定下标的服务 server = upList.get(index); if (server == null) { // 如果没有获取到,则可能是该服务宕机了,暂时挂起 Thread.yield(); continue; } if (server.isAlive()) { return server; } // server = null; Thread.yield(); } return server; } } -
编写配置类,此类不能与启动类在同一包下,否则会被所有 Ribbon Client 共享
KaiShenRule
package com.ribbon.config; import com.netflix.loadbalancer.IRule; import com.ribbon.rules.KaiShenRandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 规则配置类 * @author : toby * Create in 10:06 2022/5/9 */ @Configuration public class KaiShenRule { @Bean public IRule myRule() { return new KaiShenRandomRule(); } } -
启动类开启注解 @RibbonClient(name = "SPRING-CLOUD-PROVIDER-DEPT", configuration = KaiShenRule.class)

浙公网安备 33010602011771号