版本控制
- Spring Cloud 基于 Hoxton.RELEASES
- Spring Boot 基于 2.2.5.RELEASE
介绍
一般来说,为了服务的高可用,在生产环境中,每个微服务通常都会部署多个实例。因此服务消费者需要将请求合理的分摊到多个服务提供者实力上。Spring cloud Ribbon
是基于Netflix Ribbon
实现的一套客户端负载均衡工具。
Nginx & Ribbon
Nginx
Nginx是服务端负载均衡。Nginx存在于服务的前端。客户端所有的请求都会交给Nginx服务器,然后由Nginx实现请求的分发。属于服务端负载均衡。
Ribbon
Ribbon是客户端负载均衡。它可以存在于多个客户端中。在SpringCloud中,Ribbon可以配合Eureka
使用,在调用接口时,会从Eureka Server
中获取服务列表,并基于指定的负载策略请求其中的一个实例。
LoadBalancerClient
LoadBalancerClient
是Ribbon
的核心组件。它是定义客户端负载均衡的接口。
- 定义了几个方法:
- execute()方法:用于执行请求;
- econstructURI()方法:重构URI。整合Eureka后可以直接通过服务名去访问其他服务,如:
http://api/restTemplate/
其中api是其他服务在Eureka中注册的服务名称,在实际调用的时候需要将服务名api转为换具体的IP和端口的形式;
- 继承了
ServiceInstanceChooser
接口:- 定义了choose()方法,用来从LoadBalancer中获取指定服务的一个实例;
- 实现类为
RibbonLoadBalancerClient
可以调用choose
方法来获取服务实例:
ServiceInstance instance = loadBalancerClient.choose("serviceId");
在RibbonLoadBalancerClient
的choose()
方法中,通过负载均衡器ILoadBalancer
去获取实例信息:
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// 获取服务实例
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
ILoadBalancer
定义负载均衡具体操作的接口。维护服务实例信息;
public interface ILoadBalancer {
/**
* 初始化服务列表
*/
public void addServers(List<Server> newServers);
/**
* 从负载均衡中获取一个服务实例
*/
public Server chooseServer(Object key);
/**
* 标记指定服务已下线
*/
public void markServerDown(Server server);
/**
* 获取可用的服务列表
*/
public List<Server> getReachableServers();
/**
* 获取所有已知服务器,包括可用和不可用的
*/
public List<Server> getAllServers();
}
IRule
具体定义负载均衡策略的接口。ILoadBalancer
会根据IRule
的规则去选择一个符合条件的服务实例
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
IRule
默认实现了一些策略,具体可以查看接口实现类:
- RoundRobinRule:轮询方式(默认规则);
- RandomRule:随机访问;
- RetryRule:在轮询方式上加入重试机制;
- BestAvailableRule:请求数最小的;
- WeightedResponseTimeRule:根据响应时间加权
- ...
如果有特殊的需要可以自定义负载策略,只需要实现IRule`接口
捋一捋
整理一下负载均衡获取实例的流程:
- loadBalancerClient调用choose方法;
- ILoadBalancer调用chooseServer方法;
- IRule调用choose方法;
实战
Ribbon的引入很简单,这里我们通过RestTemplate + Ribbon来实现服务的负载均衡。一个访问第三方RESTful API接口的网络请求框架,封装了常用的请求。首先创建一个Ribbon服务
pom
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
yml文件
server:
port: 8081
spring:
application:
name: ribbon
eureka:
instance:
# 以IP地址注册到服务中心,相互注册使用IP地址
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipaddress}:${server.port}
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
# 配置 Ribbon 饥饿加载
ribbon:
eager-load:
enabled: true
clients: api
Ribbon默认是懒加载的,只有当第一次请求时才回去创建
ILoadBalancer
,这就导致第一次请求时消耗更多时间,可能会出现请求超时的情况。通过ribbon.eager-load.enabled=true
和ribbon.eager-load.clients=server1,server2
的形式配置饥饿加载模式,在项目启动时就创建对应的ILoadBalancer
config
@Configuration
public class RibbonConfig {
/**
* 通过RestTemplate来进行通信
* 注解:@LoadBalanced 开启负载均衡
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule() {
// 随机方式,默认轮询:RoundRobinRule;也可以使用自己实现的
return new RandomRule();
}
}
通过
@LoadBalanced
注解表示在RestTemplate
中使用Ribbon
负载均衡;默认的负载策略为轮询,如果需要实现其他的负载策略,只需要引入对应的
IRule
实例;
访问接口
@RestController
@RequestMapping("/ribbon")
public class RibbonController {
private final RestTemplate restTemplate;
private final LoadBalancerClient loadBalancerClient;
public RibbonController(RestTemplate restTemplate, LoadBalancerClient loadBalancerClient) {
this.restTemplate = restTemplate;
this.loadBalancerClient = loadBalancerClient;
}
/**
* LoadBalancerClient 从 Eureka Client 获取服务注册列表信息的,并进行缓存
* 调用choose()方法时,会根据负载策略选择一个实例
*/
@GetMapping("/loadBalancer")
public String loadBalancerTest() {
ServiceInstance instance = loadBalancerClient.choose("api");
return instance.getHost() + ":" + instance.getPort();
}
/**
* 整合Eureka后可以直接通过微服务名称进行访问
*/
@GetMapping("/restTemplate/{msg}")
public String restTemplateTest(@PathVariable String msg) {
return restTemplate.getForObject("http://api/api/restTemplate/" + msg, String.class);
}
}
API服务
新建一个API服务,注册到Eureka服务,创建对外接口类:
@RestController
@RequestMapping("/api")
public class AipController {
@Value("${server.port}")
private String port;
@GetMapping("/restTemplate/{msg}")
public String forRestTemplate(@PathVariable String msg) {
System.out.printf("get message %s with restTemplate\n", msg);
return "[restTemplate] from port: " + port;
}
}
测试
启动Eureka服务,启动Ribbon服务,通过修改API服务端口号,多次启动服务。调用Ribbon服务的接口查看返回数据
访问源码
所有代码均上传至Github
日常求赞
创作不易,如果各位觉得有帮助,求点赞 支持
求关注
微信公众号: 俞大仙
俞大仙
网友评论