前面我们已经介绍了Nacos
的安装与配置,以及Spring Cloud
集成Nacos
作为服务的注册中心。
接下来,我们接着上一讲,我们使用Spring Cloud
自带的LoadBalacer
来实现负载均衡
负载均衡
负载均衡(Load Balance) ,简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。
负载均衡一般分为硬件负载均衡和软件负载均衡,硬件负载均衡因为知识受限,这里不做说明
软件负载均衡又分为:
- 服务端负载均衡
服务端的负载均衡就是传统的Nginx
方式,它的一个特点是调用的客户端不知道具体是哪一个Server提供的服务,只需要将请求发送给Nginx
,再由Nginx
转发给Tomcat
,客户端只需要记住Nginx
的地址即可
- 客户端负载均衡
客户端负载均衡是将负载均衡逻辑以代码的形式封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如Eureka
Nacos
)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的
Ribbon
就是一个很典型的客户端负载均衡器,Ribbon
是Netflix
公司发布的开源项目(组件、框架、jar包),主要功能是提供客户端的软件负载均衡算法,它会从注册中心中获取一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点
但是,Ribbon
已经在最新的Spring Cloud
版本中被废弃,Spring Cloud Loadbalancer
是官方正式推出的一款新负载均衡利器,在未来,LoadBalancer
很有可能取代Ribbon的地位成为新一代的负载均衡器
而今天,我们的主角就是LoadBalancer
,我们将使用它与Nacos
集成,实现客户端的负载均衡,下面让我们开始愉快的编码吧
构建项目
创建新工程
首先,我们需要在原来的项目基础上再创建一个服务消费者,用于消费,如图所示:
导入依赖
这里需要重点注意一下,将Ribbon
的依赖从Nacos
的依赖中排除
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<!-- 将ribbon排除 -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加loadbalancer依赖
由于 Netflix Ribbon 进入停更维护阶段,因此 SpringCloud 2020.0.1 版本之后 删除了eureka中的ribbon,
替代ribbon的是spring cloud自带的LoadBalancer,默认使用的是轮询的方式
新版本的 Nacos discovery 都已经移除了 Ribbon ,此时我们需要引入 loadbalancer 代替,才能调用服务提供者提供的服务
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
配置application.yml
server:
port: 7001
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.199.128
application:
name: UserConsumer
profiles:
active: dev
management:
endpoints:
web:
exposure:
include: "*"
配置LoadBalancer
使用RestTemplate
与注解的方式配置对应的负载均衡策略
- 创建LoadBalancer配置类
package cuit.epoch.pymjl.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* 注意不要加@Configuration
*
* @author Pymjl
* @version 1.0
* @date 2022/8/26 13:05
**/
public class MyLoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 随机轮询
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
注意,MyLoadBalancerConfig这里有一个细节大家需要注意一下
- 如果你在
MyLoadBalancerConfig
上面加了@Configuration
注解,使用到的服务必须显式的在启动类上配置好
//假设项目中使用了2个服务,stock-service和product-service
//都配置好不会报错
@LoadBalancerClients(value = {
@LoadBalancerClient(name = "stock-services",configuration = MyLoadBalancerConfig.class),
@LoadBalancerClient(name = "product-service",configuration = MyLoadBalancerConfig.class)
})
//调用stock-service不会报错,调用product-service会报错
@LoadBalancerClients(value = {
@LoadBalancerClient(name = "stock-services",configuration = MyLoadBalancerConfig.class)
- 如果没有加
@Configuration
注解那么配了的服务会使用配置的负载均衡策略,没有配的服务会使用默认的策略 - 或者将对应的配置类放在
Spring
的扫描包外,效果同第二点一样
- 配置
RestTemplates
package cuit.epoch.pymjl.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* 在这里配置我们自定义的LoadBalancer策略 如果想自己扩展算法 需要实现ReactorServiceInstanceLoadBalancer接口
* /@LoadBalancerClients(defaultConfiguration = {name = "CLOUD-PAYMENT-SERVICE", configuration = MyLoadBalancerConfig.class})
* 注意这里的name属性 需要和Nacos页面中的服务提供者名字一致
*
* @author Pymjl
* @version 1.0
* @date 2022/8/26 12:20
**/
@Configuration
@LoadBalancerClient(name = "UserService", configuration = MyLoadBalancerConfig.class)
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
编写Controller
package cuit.epoch.pymjl.controller;
import cuit.epoch.pymjl.result.CommonResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* @author Pymjl
* @version 1.0
* @date 2022/8/26 12:17
**/
@RestController
@RequestMapping("/consumer")
public class TestController {
private static final String SERVICE_URL = "http://UserService";
@Resource
RestTemplate restTemplate;
@GetMapping("/test")
public CommonResult consumerTest() {
return restTemplate.getForObject(SERVICE_URL + "/user/test", CommonResult.class);
}
}
你也可以采用直接使用LoadBalanceClient
不使用注解的方式(这是我Copy的Nacos官方的样例,使用方法是一样的,就不再演示)
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApp {
@RestController
public class NacosController{
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/echo/app-name")
public String echoAppName(){
//使用 LoadBalanceClient 和 RestTemolate 结合的方式来访问
ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider");
String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request url:"+url);
return restTemplate.getForObject(url,String.class);
}
}
//实例化 RestTemplate 实例
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApp.class,args);
}
}
开始测试
- 我们先启动
UserService
,因为涉及到负载均衡,所以我们需要使用idea配置启动类,启动多个UserService,点击如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNMyQJJi-1662117457388)(https://pymjl.oss-cn-shanghai.aliyuncs.com/picgo/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202022-08-26%20163659.png)]
然后点击启动服务提供者和消费者,如图所示:
调用接口
经过多次刷新后你会发现消费者会随机的去调用服务提供者的服务,两者的概率大概是相同的
项目源码:gitee github 62117457390)]
[外链图片转存中…(img-Z73V2blX-1662117457391)]
经过多次刷新后你会发现消费者会随机的去调用服务提供者的服务,两者的概率大概是相同的
[外链图片转存中…(img-fojNGyVX-1662117457392)]
[外链图片转存中…(img-O63oaM6O-1662117457393)]