负载均衡Ribbon
负载均衡概述
- 实际环境中,我们往往会开启很多个 goods-service 服务的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢
- 如何从多台服务器当中, 均衡的调用
SpringCloud-Ribbon
- Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具
- Ribbon是Netflix发布的开源项目,主要功能是提供客户端的负载均衡算法,将Netflix的中间层服务连接在一起
- Ribbon是Netflix发布的负载均衡器,Ribbon默认为我们提供了很多负载均衡算法
- 例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法
- 在SpringCloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表
- 并基于负载均衡算法,请求其中一个服务提供者实例。展示了Ribbon与Eureka配合使用时的架构
Ribbon负载均衡配置
- 在Eureka当中已经包含了Ribbon, 所以我们不需要单独添加依赖
- 在restTemplate的Bean上再添加一个注解
@LoadBalanced
- 在控制器当中定义要调用服务的实例名称
默认是轮训
🐤注意点
- 就是配置服务端口号的问题,不要使用Java的配置方式进行配置端口,要使用 yaml 文件 或者 properties 文件配置的方式进行配置否则远程调用服务会调用不了
负载均衡算法
- 随机
- 轮询
- hash(哈希)
- 最少访问
- 这些算法, 都不需要要我们去写, 直接使用一个Robbin进行操作
核心组件IRule
- IRule是Ribbon对于负载均衡策略实现的接口
- 默认实现IRule接口的类
- RoundRobinRule: 轮询
- RandomRule: 随机
- AvailabilityFilteringRule: 会先过滤由于多次访问故障处于断路器跳闸状态的服务, 还有并发的连接数据超过阈值的服务, 然后对剩余的服务列表按照轮询策略进行选取
- WeightedResponseTimeRule: 根据平均响应时间计算所有服务的权重, 响应时间越快权重越大, 刚启动时如果统计信息不足, 则使用RoundRobinRule策略, 等统计信息足够时后, 会切换到 WeightedResponseTieRule
- RetryRule: 先按照RoundRobinRule的策略获取服务, 如果获取服务失败, 则在制定时间内会重试, 再获取不到, 则放弃
- BestAvailableRule: 会先过滤掉由于多次访问故障, 而处于断路器跳闸的服务, 然后选择一个并发量最小的服务
- ZoneAvoidanceRule: 默认规则, 符合判断 Server 所在区域的性能和 server 的可用性选择服务器
🐤IRule配置
@Bean
public IRule iRule() {
return new RandomRule();
}
🐸自定义IRule
- 伪随机
- 当一个下标(伪服务)连接被调用两次
- 第三次如果还是它, 就让再随机一次
核心代码
int index = chooseRandomInt(serverCount);
//如果取出来的下标 是要跳过的下标
if (index == skipIndex) {
System.out.println("跳过重新随机");
index = chooseRandomInt(serverCount);
}
// 跳过后, 清空要跳过的下标
skipIndex = -1;
// 记录这次下标
nowIndex = index;
// 如果这次下标等于上次下标
if (lastIndex == nowIndex) {
// 就把这次下标赋值给跳过的下标
skipIndex = nowIndex;
System.out.println("这次和上次的下标一样");
}
// 随机完之后,这一次的下标就是上一次的下标
lastIndex = nowIndex;
递归写法
public int bnTangRandom(int index, int serverCount) {
// 如果取出来的下标 是要跳过的下标
if (index == skipIndex) {
System.out.println("跳过重新随机");
index = chooseRandomInt(serverCount);
if (index == skipIndex) {
index = bnTangRandom(index, serverCount);
}
}
// 跳过后,清空要跳过的下标
skipIndex = -1;
// 记录这次下标
nowIndex = index;
// 如果这次下标等于上次下标
if (lastIndex == nowIndex) {
// 就把这次下标赋值给跳过的下标
skipIndex = nowIndex;
System.out.println("这次和上次的下标一样");
}
// 随机完之后,这一次的下标就是上一次的下标
lastIndex = nowIndex;
return index;
}
MyIRule.java 全部代码
package com.bntang666.config.irule;
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.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author BNTang
* @version V1.1.1
* @program springcloud-netflix
* @date Created in 2020/9/7 9:33
* @description 自定义IRule
**/
public class MyIRule extends AbstractLoadBalancerRule {
Random random;
// 这次下标
private int nowIndex = -1;
// 上一次下标
private int lastIndex = -1;
// 要跳过的下标(上一次下标等于这次下标 就把这次下标赋值给跳过的下标)
private int skipIndex = -1;
public MyIRule() {
random = new Random();
}
/**
* Randomly choose from all living servers
*/
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);
index = bnTangRandom(index, serverCount);
server = upList.get(index);
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
public int bnTangRandom(int index, int serverCount) {
// 如果取出来的下标 是要跳过的下标
if (index == skipIndex) {
System.out.println("跳过重新随机");
index = chooseRandomInt(serverCount);
if (index == skipIndex) {
index = bnTangRandom(index, serverCount);
}
}
// 跳过后,清空要跳过的下标
skipIndex = -1;
// 记录这次下标
nowIndex = index;
// 如果这次下标等于上次下标
if (lastIndex == nowIndex) {
// 就把这次下标赋值给跳过的下标
skipIndex = nowIndex;
System.out.println("这次和上次的下标一样");
}
// 随机完之后,这一次的下标就是上一次的下标
lastIndex = nowIndex;
return index;
}
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
}
}
配置 MyIRule.java
不同服务设置策略
- 创建两个 order 子工程
order
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
添加启动类
@SpringBootApplication
@EnableEurekaClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
添加 OrderController 控制器
@RestController
public class OrderController {
@RequestMapping("/getOrder.do")
public Object getOrder() {
Map<Object, Object> map = new HashMap<>();
map.put("name", "orderValue");
return map;
}
}
添加 yml 配置文件,并把服务注册到注册中心中,我直接上配置文件截图和代码了
eureka:
client:
serviceUrl:
# eureka服务端提供的注册地址 参考服务端配置的这个路径
defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
instance:
# 此实例注册到eureka服务端的唯一的实例ID
instance-id: order-1
# 是否显示IP地址
prefer-ip-address: true
# eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseRenewalIntervalInSeconds: 10
# Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
leaseExpirationDurationInSeconds: 30
spring:
application:
# 此实例注册到eureka服务端的name
name: server-order
server:
port: 8001
order1 模块照葫芦画瓢即可, order1 我配置的端口号为 8002,我还是粘贴在下方吧
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
添加启动类
@SpringBootApplication
@EnableEurekaClient
public class Order1Application {
public static void main(String[] args) {
SpringApplication.run(Order1Application.class, args);
}
}
添加 Order1Controller 控制器
@RestController
public class Order1Controller {
@RequestMapping("/getOrder.do")
public Object getOrder() {
Map<Object, Object> map = new HashMap<>();
map.put("name", "order1Value");
return map;
}
}
添加 yml 配置文件,并把服务注册到注册中心中
eureka:
client:
serviceUrl:
# eureka服务端提供的注册地址 参考服务端配置的这个路径
defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
instance:
# 此实例注册到eureka服务端的唯一的实例ID
instance-id: order-2
# 是否显示IP地址
prefer-ip-address: true
# eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseRenewalIntervalInSeconds: 10
# Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
leaseExpirationDurationInSeconds: 30
spring:
application:
# 此实例注册到eureka服务端的name
name: server-order
server:
port: 8002
配置负载均衡
- 如果把负载均衡策略配置放到了 com 包中, 那么它配置的是所有的服务, 它和 UserApplication 在同一个目录
- 如果想要不同服务设置不同的策略, 就要单独创建一个目录, 不要和UserApplication同一个目录
@Bean
public IRule iRule() {
return new MyIRule();
}
@Bean
public IRule iRule() {
return new RoundRobinRule();
}
在启动类当中为指定的服务, 配置不同的负载均衡调用策略, 配置完成之后自行测试即可
@EnableEurekaClient
@SpringBootApplication
@RibbonClients({
@RibbonClient(name = "CLIENT-GOODS", configuration = GoodsConfig.class),
@RibbonClient(name = "SERVER-ORDER", configuration = OrderConfig.class)
})
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具