目录
- 概念说明
- 什么是Ribbon
- Ribbon和Nginx负载均衡的区别
- 工作流程
- 代码实现
- RibbonSDK
- 发送请求端
- 引入RibbonSDK和Nacos的依赖
- 配置文件中填写负载均衡策略
- 调用代码
- 接收请求端
- 执行效果
- 发送请求端
- 接收请求端
- 总结提升
概念说明
什么是Ribbon
Ribbon 是一个客户端负载均衡器,它是Spring Cloud Netflix开源的一个组件,用于在分布式系统中实现对服务实例的负载均衡。它可以作为一个独立的组件使用,也可以与 Spring Cloud 等微服务框架集成使用。
Ribbon 的主要功能是根据一定的负载均衡策略,将客户端请求分配到可用的服务实例上,以提高系统的可用性和性能。它通过周期性地从服务注册中心(如 Eureka)获取可用的服务实例列表,并根据配置的负载均衡策略选择合适的实例来处理请求。Ribbon 支持多种负载均衡策略,如轮询、随机、加权随机、加权轮询等。
Ribbon和Nginx负载均衡的区别
工作流程
- 客户端发起请求到 Ribbon。
- Ribbon 从服务注册中心获取可用的服务实例列表。
- 根据配置的负载均衡策略,选择一个合适的服务实例。
- 将请求转发给选中的服务实例进行处理。
- 如果请求失败或超时,Ribbon 会尝试选择其他的服务实例进行重试。
代码实现
RibbonSDK
sdk是每个使用ribbon的服务中需要引入的jar包,需要借助jar包中的功能来完成ribbon的使用。
package com.example.ribbonsdk.config.test;
import com.example.client.Controller.SDKController;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* @BelongsProject: ribbonDemo
* @BelongsPackage: com.example.ribbonsdk.config
* @Author: Wuzilong
* @Description: RibbonSDK
* @CreateTime: 2023-07-31 22:47
* @Version: 1.0
*/
@Component
public class RequestInterceptor implements ClientHttpRequestInterceptor, ApplicationContextAware {
public static ApplicationContext applicationContext;
int index = 0;
// 目前是写死的,应该放到注册中心中去,动态的添加注册服务和权重
public Map<String,Integer> serverList = new HashMap<>(){{
put("localhost:9002",7); // 权重值为7
put("localhost:9005",3); // 权重值为3
}};
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (this.applicationContext == null) {
this.applicationContext = applicationContext;
}
}
/**
* @Author:Wuzilong
* @Description: 手动注入AnnotationConfigApplicationContext用于判断
* @CreateTime: 2023/6/19 17:36
* @param:
* @return:
**/
@Bean
public AnnotationConfigApplicationContext annotationConfigApplicationContext() {
return new AnnotationConfigApplicationContext();
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("拦截器拦截进来了,拦截的地址是:"+request.getURI());
RestTemplate restTemplate = new RestTemplate();
//获取服务名
int startIndex = request.getURI().getPath().indexOf("/") + 1;
int endIndex = request.getURI().getPath().indexOf("/", startIndex);
String serveName = request.getURI().getPath().substring(startIndex, endIndex);
String newAuthority = null;
Environment environment = applicationContext.getBean(Environment.class);
String loadBalanceName = environment.getProperty("ribbon.loadBalanceName");
if (loadBalanceName.equals("polling")){
newAuthority = this.polling(serveName);
System.out.println("采用的是负载均衡策略————轮询");
}else if (loadBalanceName.equals("weight")){
newAuthority = this.weight();
System.out.println("采用的是负载均衡策略————权重");
}
String newHost= newAuthority.split(":")[0];
String newPort= newAuthority.split(":")[1];
URI newUri = UriComponentsBuilder.fromUri(request.getURI())
.host(newHost)
.port(newPort)
.build()
.toUri();
RequestEntity tRequestEntity = new RequestEntity(HttpMethod.GET, newUri);
ResponseEntity<String> exchange = restTemplate.exchange(tRequestEntity, String.class);
System.out.println("请求的服务是"+exchange.getBody());
// 创建一个ClientHttpResponse对象,并将实际的响应内容传递给它
ClientHttpResponse response = new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() {
return exchange.getStatusCode();
}
@Override
public int getRawStatusCode() {
return exchange.getStatusCodeValue();
}
@Override
public String getStatusText() {
return exchange.getBody();
}
@Override
public void close() {
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream(exchange.getBody().getBytes());
}
@Override
public HttpHeaders getHeaders() {
return exchange.getHeaders();
}
};
return response;
}
//轮询获取服务的IP地址
public String polling(String serverName){
List<String> pollingList = applicationContext.getBean(SDKController.class).getList(serverName);
String ipContext = pollingList.get(index);
index=(index+1)%pollingList.size();
return ipContext;
}
//权重获取服务的IP地址
public String weight() {
int totalWeight = serverList.values().stream().mapToInt(Integer::intValue).sum();
int randomWeight = new Random().nextInt(totalWeight); // 生成一个随机权重值
int cumulativeWeight = 0; // 累计权重值
for (Map.Entry<String,Integer> server : serverList.entrySet()) {
cumulativeWeight += server.getValue();
if (randomWeight < cumulativeWeight) {
return server.getKey();
}
}
return null; // 没有找到合适的服务器
}
}
RequestInterceptor 类实现了两个接口,一个是ClientHttpRequestInterceptor用来重写intercept方法,也就是说重写了拦截器中的业务逻辑,我们可以把拦截到的请求进行处理,处理的过程可以写到intercept方法中,另一个是ApplicationContextAware这个接口是用来获取bean容器中对象的。
发送请求端
引入RibbonSDK和Nacos的依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>RibbonSDK</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 手写nacos的sdk,用来获取注册列表-->
<dependency>
<groupId>com.example</groupId>
<artifactId>Client</artifactId>
<version>2.5-20230615.123611-1</version>
</dependency>
Nacos的其他配置可参考:手写Naocs注册中心基本原理 手写Nacos配置中心基本原理
配置文件中填写负载均衡策略
ribbon:
loadBalanceName: polling
调用代码
import com.example.ribbonsdk.config.test.RequestInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @BelongsProject: ribbonDemo
* @BelongsPackage: com.example.ribbonsdk.service
* @Author: Wuzilong
* @Description: 请求端
* @CreateTime: 2023-08-28 08:20
* @Version: 1.0
*/
@Service
public class ServiceA {
@Autowired
private RequestInterceptor requestInterceptor;
public void getServiceInfo(){
String url = "http://"+"localhost"+"/B/receiveMessage/";
RestTemplate restTemplate=new RestTemplateBuilder().build();
restTemplate.getInterceptors().add(requestInterceptor);
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
if (forEntity.getStatusCode() == HttpStatus.OK) {
System.out.println("调用B服务成功!");
}
}
}
import com.example.ribbonsdk.service.ServiceA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @BelongsProject: ribbonDemo
* @BelongsPackage: com.example.ribbonsdk.Controller
* @Author: Wuzilong
* @Description: 描述什么人干什么事儿
* @CreateTime: 2023-07-31 22:54
* @Version: 1.0
*/
@RestController
@RequestMapping("/ribbonsdk")
public class ServiceAController {
@Autowired
private ServiceA serviceA;
@RequestMapping(value="getInfo",method= RequestMethod.GET)
public void getInfo(){
serviceA.getServiceInfo();
}
}
接收请求端
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @BelongsProject: ServiceB
* @BelongsPackage: com.example.serviceb.Controller
* @Author: Wuzilong
* @Description: B服务
* @CreateTime: 2023-06-07 19:08
* @Version: 1.0
*/
@RestController
@RequestMapping("/B")
public class ServiceBController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/receiveMessage")
public String receiveMessage() throws UnknownHostException {
System.out.println("B:我被调用了");
//返回的内容是ip地址和端口号
return InetAddress.getLocalHost().getHostAddress()+":"+serverPort;
}
}
执行效果
发送请求端
接收请求端
总结提升
Ribbon 是一个强大的客户端负载均衡器,可以帮助构建可靠和高性能的分布式系统。它通过负载均衡策略将请求分发到多个服务实例上,提供了灵活的配置选项和额外的功能。