4. Netflix.Ribbon
4.1 简介
(1) 概念
- Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡工具。
(2) 负载均衡(LB:LoadBalance)和集群架构
- 应用集群:将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。
- 负载均衡设备:将用户访问的请求,根据负载均衡算法,分发到集群中的一台处理服务器。(一种把网络请求分散到一个服务器集群中的可用服务器上去的设备)。
- 负载均衡的作用(解决的问题):
- 解决并发压力,提高应用处理性能(增加吞吐量,加强网络处理能力);
- 提供故障转移,实现高可用;
- 通过添加或减少服务器数量,提供网站伸缩性(扩展性);
- 安全防护;(负载均衡设备上做一些过滤,黑白名单等处理)
(3) 负载均衡分类
- 集中式 LB
- 在服务的消费方和提供方之间使用独立的 LB 设施,如 Nginx(反向代理服务器),由该设施把访问请求通过某种策略转发至服务的提供方。
- 进程式 LB
- 将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后从这些地址中选用一个服务器。
- Ribbon 属于进程式 LB,它是一个类库,集成于消费方进程,消费方通过它来获取提供方的地址。
4.2 集成 Ribbon
调整 springcloud-consumer-dept-80 服务
● 添加依赖
<!-- 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.6.RELEASE</version>
</dependency>
<!-- 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.6.RELEASE</version>
</dependency>
● 为 RestTemplate 配置负载均衡
@Configuration // spring applicationContext.xml
public class ConfigBean {
// 注册 bean <bean></bean>
@Bean
// 配置负载均衡实现 RestTemplate
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
● 修改获取远程服务的地址
// DeptConsumerController
// Ribbon 实现负载均衡,此处的地址应是一个变量——服务名
// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
● 添加 Eureka 配置
# eureka
eureka:
client:
# 不向 eureka 注册自己
register-with-eureka: false
# 列出可使用的服务地址供消费者服务选择
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
● 开启 Eureka 客户端服务
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
4.3 模拟多服务访问
● 创建数据库
cloud01.dept.db_source = cloud01
cloud02.dept.db_source = cloud02
cloud03.dept.db_source = cloud03
● 创建两个 maven 模块作为服务提供者
springcloud-provider-dept-8001
springcloud-provider-dept-8002
springcloud-provider-dept-8003
-
修改配置文件
springcloud-provider-dept-8001 => cloud01 springcloud-provider-dept-8002 => cloud02 springcloud-provider-dept-8003 => cloud03
● 访问效果
4.4 自定义 Ribbon 规则
(1) 创建 Ribbon 配置类
-
自定义配置类的组件会覆盖 RibbonClientConfiguration 中的组件来完成配置。
-
自定义配置类的注解必须是 @Configuration,其不在主应用程序下的 @ComponentScan 中,否则将由所有 @RibbonClients 共享,若使用 @ComponentScan / @SpingBootApplication,则需要采取避免措施,,如将其放在一个单独的,不重叠的包中,或指定在 @ComponentScan。
-
自定义配置类放在启动类所在的目录外
/** * 每个服务访问 5 次,选择下一个服务 * @author why * @since 2021/9/7 21:02 */ public class RoundFiveRule extends AbstractLoadBalancerRule { // 被调用次数 private int toltal = 0; // 当前提供服务的索引 private int currentIndex = 0; // @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE") public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } 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 = chooseRandomInt(serverCount); // // 随机获取可用服务 // server = upList.get(index); //=============================================================================== if (toltal < 5) { server = upList.get(currentIndex); toltal++; } else { toltal = 1; currentIndex++; if (currentIndex >= upList.size()) { currentIndex = 0; } server = upList.get(currentIndex); } System.out.print(" " + currentIndex + " "); //================================================================================ if (server == null) { Thread.yield(); continue; } if (server.isAlive()) { return (server); } server = null; Thread.yield(); } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub } }
(2) 将自定义配置类注册到 Spring 中
@Configuration
public class MyRule {
// 将自定义配置类注册到 Spring 中
@Bean
public IRule myRule() {
return new RoundFiveRule();
}
}
(3) 配置启动类自动加载自定义配置类
@SpringBootApplication
@EnableEurekaClient
/*
* 微服务启动时,加载 Ribbon 配置类
* @param name 服务名
* @param configuration 自定义配置类的反射
*/
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = RoundFiveRule.class)
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
虽然 index 为轮询方式,但由于 index 并未与服务进行一对一绑定,所以服务的访问准确来说并不是每 5 次就更换下一个服务。
5. Netflix.Feign
5.1 简介
- Feign 是声明式的 web service 客户端,它简化了微服务之间的调用。
- Ribbon 使用微服务名字调用微服务,Feign 使用接口和注解调用微服务。
- 创建一个接口,使用 Feign 注解进行配置,即可完成对服务提供方的接口绑定。
- Feign 集成了 Ribbon,通过添加一层的方式增加了代码的可读性,但是性能有所降低
5.2 Feign 实现
调整 springcloud-consumer-dept-80 微服务
● 创建 maven moudle
● 添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
● 删除 Ribbon 配置
● 编写 Feign 服务层
在公共服务 springcloud-api 中添加 Feign 服务层
/**
* Feign 实现服务的调用
*
* @author why
* @since 2021/9/9 9:24
*/
// 将接口注册到 Spring,否则会报红,但是却不影响调用,原因不明
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
@GetMapping("/dept/getDept/{id}")
public Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
}
● 启动类开启 Feign 扫描
@SpringBootApplication
@EnableEurekaClient
/*
* @EnableFeignClients(basePackages = {"com.why.springcloud"}) 扫描 springcloud-api
* 务 com.why.springcloud 目录及以内目录下的 @FeignClient
*/
@EnableFeignClients(basePackages = {"com.why.springcloud"})
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class, args);
}
}
● 远程调用
Feign 服务层也可以放在当前服务服务中,此时无需对注解 @EnableFeignClients 的属性赋值
@SpringBootApplication
@EnableEurekaClient
/*
* @EnableFeignClients 扫描当前服务主类同级及以内目录下的 @FeignClient
*/
@EnableFeignClients
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class, args);
}
}