Spring Cloud的负载均衡策略
Spring Cloud的负载均衡策略方式有两种:
- 基于微服务名字(Ribbon)
- 接口和注解方式(Feign)
- 负载均衡分类
- 集中式负载均衡:
- 在服务提供者和服务消费者之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求基于某种策略转发到服务的提供方!
- 进程式负载均衡:
- 将负载均衡逻辑集成到服务消费方,消费方通过去注册中心获取可用的服务列表,从服务列表中基于某种策略选取其中一个合适的服务节点。
- Ribbon属于进程式LB,它是一个类库,集成于服务消费者进程,消费者通过它获取服务提供方的地址。
- 集中式负载均衡:
Ribbon(客户端)
Ribbon简介
- Ribbon是Netfilx下的开源项目,Spring Cloud Ribbon是基于Netfilx Ribbon下实现的,提供了一系列客户端负载均衡的工具,
- 功能:Ribbon提供一系列客户端的软件负载均衡算法,将Netflix下的中间层服务连接在一起。
- 实现: Ribbon是基于客户端的,Ribbon的客户端组件提供一系列完整的配置选项,如: 连接超时,重试等。通过在配置文件中配置LoadBalance(简称LB:负载均衡策略)和其他的配置选项,Ribbon会自动的基于某种规则(如简单轮询,随机连接等等)去连接这些服务节点
- 可以通过自定义负载均衡的算法,满足项目的需求
Ribbon的作用
- 提供LB,即负载均衡(LoadBalancer),在分布式集群或微服务上是常用的应用。
- 负载均衡简单的说就是将用户的请求分摊到多个服务节点上,从而达到系统的HA(高用)。
- 常见的负载均衡工具有Nginx,Lvs等。
实现Ribbon
Ribbon是集成在服务提供者的,所有我们基于springcloud-consumer-user-80
- 添加依赖
<!--Ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--Eureka: Ribbon需要从Eureka服务中心拿到服务-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2.application.yml
server:
port: 80
# 标识自己是服务消费者
eureka:
client:
register-with-eureka: false #不向注册中心注册自己,只去获取服务
service-url:
defaultZone:
http://127.0.0.1:7001/eureka/
3.在主启动类中添加注解
4. 配置configConfig类
@Configuration
public class ConsumerConfig {
//将RestTemplate加入到容器中 ,RestTemplate是Spring提供的工具类,RestTemplate是一个同步的Rest Api客户端
//RestTemplate提供高度封装的接口,让我们更方便的进行REST APi的调用
@Bean
@LoadBalanced //配置负载均衡实现RestTemplate
public RestTemplate restTemplate()
{
return new RestTemplate();
}
//配置负载均衡的策略 配置com.netflix.loadbalancer.IRule
/**
* IRule:
* RoundRobinRule 轮询策略
* RandomRule 随机策略
* AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
* RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
*/
@Bean
public IRule myRule()
{
return new RandomRule(); //随机策略
}
}
5.ConsumerController类
//原来调用的IP地址和端口号
//Ribbon是基于RestTemplate进行访问的,Rest的URL前缀localhost:8081变为服务ID
private static final String REST_URL_PREFIX ="http://SPRINGCLOUD-USER-PROVIDER-8081"; // "http://localhost:8081";
不再是通过服务提供者的url获取,而是通过服务提供者的在eureka上的实例名获取。
6.创建两个子模块,分别名叫springcloud-provider-user-8081、springcloud-provider-user-8082。
7.参照springcloud-provider-user-8081 依次为另外两个Moudle添加pom.xml依赖 、resourece下的mybatis和application.yml配置,Java代码
8.添加标志,让我们识别得到了不同的服务
在每个服务提供者的UserControlle类设置一下别名
8081端口UserController
@GetMapping("query/{id}")
public User querySingleUser(@PathVariable("id") int id) {
//模拟一下
User user = userService.getUserById(id);
user.setAlias("8081");
return user;
}
我们通过查询指定用户,来判断是否是在不同的服务提供者中获取到的服务,8082,8083配置也如上。
9.启动相应的进程进行测试
Ribbon默认是以轮询策略实现的。
更改负载均衡方式
在springcloud-consumer-user-80下配置
ConsumerConfig
//向Spring容器中加入负载均衡策略
@Bean
public IRule myRule()
{
return new RandomRule(); //随机策略
}
自定义负载均衡算法
配置负载均衡算法的包要和启动类的包同级
1.创建一个负载均衡算法
package org.rule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
@Configuration
// 自定义负载均衡算法可以参照IRule的子类
public class MyLBRule extends AbstractLoadBalancerRule{
public MyLBRule() {
}
//定制一个每一个服务执行三次才切换下一个服务的策略
int total = 0; //初始数量为0
int currentIndex = 0;
@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<Server> upList = lb.getReachableServers(); //获得可用的服务
List<Server> allList = lb.getAllServers(); //获得全部服务
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if(total<3) {
// int index = this.chooseRandomInt(serverCount); 不随机
server = (Server) upList.get(currentIndex);
total++;
}
else{
//超过三次服务
total =0;
currentIndex++;
if(currentIndex>upList.size())
{
currentIndex =0;
server =(Server)upList.get(currentIndex);
total=total+1;
}
server = (Server) upList.get(currentIndex);
total=total+1; //加一
}
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);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
2.修改ConsumerConfig配置文件
@Bean
public IRule myRule()
{
return new MyLBRule();// new RandomRule(); 随机策略
}
3.主启动类添加Ribbon注解
//配置自定义负载均衡策略,并指定自定义规则
/**
* name 为服务ID
* configuration为自定义负载均衡策略
*/
@RibbonClient(name = "http://SPRINGCLOUD-USER-PROVIDER-8081",configuration = MyLBRule.class)
4.启动测试满足需求
Fegin(基于服务端)
Fegin简介
Feign是声明式Web Service客户端,它让微服务之间的调用更加简便,类似于Controller调用Service。Feign是基于面向接口编程习惯的,SpringCloud集成了Ribbon和Eureka,可以使用Feign提供负载均衡的http客户端。
Feign的作用
-
Feign使编写Java Http客户端变得更容易
-
Eureka使用了Ribbon+RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一个客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它 (类似以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。
-
Feign默认集成了Ribbon,通过Feign需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
使用Feign
1.创建springcloud-user-feign-80模块,拷贝springcloud-consumer-dept-80模块下的pom.xml,resource,以及java代码到springcloud-consumer-feign模块
2.添加feign依赖。
<!--Feign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
3.api模块配置
- 添加feign依赖
<!--Feign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
新建service包,并新建UserService.java接口,
//微服务客户端注解,value:指定微服务的名字 这样微服务就可以直接通过微服务名字查找对应的服务
@FeignClient(value = "SPRINGCLOUD-USER-PROVIDER-8081")
@Service //加入到Spring容器中
public interface UserService{
@PostMapping("/user/add")
public boolean addUser(User user);
@GetMapping("/user/query")
public List<User> query();
@GetMapping("/user/query/{id}")
public User querySingleUser(@PathVariable("id")int id);
}
4.ConsumerService.Java
@RestController
public class ConsumerController {
@Autowired
//使用了Fegin的接口
private UserService userService;
//添加Post
@PostMapping("/consumer/user/add")
public boolean addUser(User user)
{
return userService.addUser(user);
}
//查询
@GetMapping("/consumer/user/query/{id}")
public User getUserById(@PathVariable("id") int id)
{
return userService.querySingleUser(id);
}
@GetMapping("/consumer/user/query")
public List<User> getUsers()
{
return userService.query();
}
}
基于Feign的方式,不需要配置RestTemplate,而是通过注解接口绑定,更加简洁,指定具体的服务ID即可
5.在主启动类使用Feign注解
@SpringBootApplication
@EnableEurekaClient //开启Eureka客户端
//通过接口和注解提供负载均衡的Http客户端
@EnableFeignClients(basePackages = {"org.liang.service"})
//feign客户端注解,指定要扫描的包和配置的接口UserService
// 切记不要加这个注解,不然会出现404访问不到
//@ComponentScan("org.liang.service")
public class Consumer_80 {
public static void main(String[] args) {
SpringApplication.run(Consumer_80.class,args);
}
}
Feign和Ribbon选择
根据个人习惯而定,如果喜欢REST风格使用Ribbon,如果喜欢社区版的面向接口风格使用Feign.
Feign本质上就是实现了Ribbon,只不过调用的方式,满足了一些开发者面向接口的使用习惯。