Ribbon组件在Spring Cloud的作用是实现负载均衡,这里简单的说一下负载均衡的概念,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。如下图:
Nacos为我们提供了注册中心和配置中心,在集群模式下,Ribbon可以将请求通过一些策略分摊到各个Nacos,在讲具体实现之前,先来简单的自定义模拟一下负载均衡的实现:
创建两个服务提供者工程:
ribbon_provider_1
pom依赖:导入nacos启动器和springboot的web启动器,同时注入common公共属性工程(这里不展示,common工程只声明了一个User实体类)
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
配置文件:application.yaml
server:
port: 9090
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.40.141:8848
application:
name: ribbon-provider
controller:
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Autowired
private UserService userService;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userService.getUserById(id);
}
}
service:
public interface UserService {
User getUserById(Integer id);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Integer id) {
return new User(id,"张三-1",18);
}
}
启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class,args);
}
}
ribbon_provider_2:
配置文件:application.yaml
server:
port: 9091
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.40.141:8848
application:
name: ribbon-provider
Controller、Service与启动类和ribbon_provider_1保持一致(Service稍作区分,打印的User对象name属性为"张三-2")
创建服务消费者ribbon_consumer:
pom依赖(与ribbon_provider一样):
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
application.yaml配置文件
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.40.141:8848
application:
name: ribbon-consumer
controller:
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
//访问Rest服务的客户端
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
private int currentIndex;
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
//获取nacos中注册的指定服务信息
List serviceList = discoveryClient.getInstances("ribbon-provider");
//通过Random工具类获取随机数
currentIndex = new Random().nextInt(serviceList.size());
//随机获取服务
ServiceInstance serviceInstance = serviceList.get(currentIndex);
//拼接访问服务的url
String serviceUrl = serviceInstance.getHost() + ":" + serviceInstance.getPort();
String url = "http://"+serviceUrl+"/provider/getUserById/"+id;
//调用服务
return restTemplate.getForObject(url,User.class);
}
}
配置类(注册RestTemplate到ioc容器):
@Configuration
public class ConfigBean {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
启动消费者跟提供者工程,然后启动nacos服务,浏览器进入nacos后台(关于nacos上篇文章已详细记载)查看注册的服务
看到服务提供者实例数为2,服务消费者实例数为1,注册成功!
访问localhost/consumer/getUserById/1测试
查看响应结果是访问了ribbon_provider_2
继续刷新(这里模拟的策略为随机,可能连续几次访问的都是ribbon_provider_2)
负载均衡的随机分配模拟成功!
首先,nacos里面已经集成了ribbon,所以不用额外的再去引入依赖。
Ribbon实现负载均衡的核心在于IRule接口,这个接口给出了负载均衡的规范,并且官方提供了多个实现类,每个实现类可以近似的看做是负载均衡的一个实现策略。
如上图中的RandomRule就是随机分配策略,RoundRobinRule是轮询策略
简单的看下RandomRule源码如何实现:
//该注解的作用是告诉JVM,忽略掉一些警告信息
@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 upList = lb.getReachableServers();
List allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//调用自身的chooseRandomInt方法获取一个随机数
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);
}
源码中的核心业务逻辑跟模拟随机分配的实现很相似,都是获得所有服务的列表再通过随机索引去获取服务并调用。
了解这些原理后,改造一下上面的模拟实现案例,通过Ribbon来实操一波。
1.修改ribbon_consumer的ConfigBean配置类:
在RestTemplate的注册方法上加上@LoadBalanced注解,这样在通过ioc容器获取restTemplate对象时,Ribbon会通过拦截器将请求拦截并处理,拿到注册中心的所有可用服务,通过获取到的服务信息(ip,port)替换掉serviceId,同时在ioc容器中查找IRule是否已注册,如果已注册则根据IRule的类型来实施负载策略,否则通过实施默认策略。
@Bean
/**
* 添加了@LoadBalanced注解之后,Ribbon会给restTemplate请求添加一个拦截器,在拦截器中获取
* 注册中心的所有可用服务,通过获取到的服务信息(ip,port)替换 serviceId 实现负载请求。
*/
@LoadBalanced //开启负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
//随机策略
@Bean
public IRule iRule() {
return new RandomRule();
}
修改ribbon_consumer的controller:
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable("id") Integer id){
//在ribbon中不再使用host + ":" + port的方式获取url,而是通过serviceId(nacos中注册服务的名称)查找
String serviceUrl = "ribbon-provider";
String url = "http://"+serviceUrl+"/provider/getUserById/"+id;
//调用服务
return restTemplate.getForObject(url,User.class);
}
启动测试:
浏览器的地址栏输入localhost/consumer/getUserById/2测试访问
至此Ribbon的入门案例结束