avatar

负载均衡服务调用

Ribbon简介

SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡工具
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。就是在配置文件中列举出Load Blancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些个机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
Ribbon负载均衡wiki,目前ribbon已经进入维护模式,未来可以通过springcloud loadbalancer替代,但是目前还做不到。

Ribbon能干什么

LB负载均衡是什么

简单的说就是将用户的请求平摊的分配到多个服务器上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,LVS、硬件有F5等

Ribbon vs Nginx

  • Nginx是服务器负载均衡、客户端所有请求都会交给ngnix,然后由nginx实现转发请求。即负载均衡是由服务端实现的。(集中式LB)
  • Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。(进程内LB)

Ribbon工作原理

Ribbon在工作时分成两步
第一步先选择EurekaServer(不管是Eureka 还是 Zookeeper,Consul是一样的),它会优先选择在同一个区域内负载较少的server
第二步再根据用于指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

如何通过maven添加ribbon

因为我们引入注册中心的时候,注册中心已经将ribbon的包导入了,如果我们打算单独使用的话,需要在xml文件中加入

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.1.RELEASE</version>
<scope>compile</scope>
</dependency>

Ribbon负载均衡

源码分析
Ribbon是在RestTemplate的基础上使用的。所以要说Ribbon就不得不说RestTemplate

RestTemplate使用

RestTemplate官方说明

RestTemplate有几种比较重要的方法:postForEntity()postForObject()getForEntity()getForObject()

  • XXXForObject(): 返回对象为响应体中数据转化成的对象,基本上可以理解为json;
  • XXXForEntity():返回对象为ResponseEntity对象,包含了相应中的一些重要信息,比如相应头、相应状态码、相应体等。
@GetMapping("/consumer/payment/getForObject/{id}")
public CommonResult<Payment> getPaymentObject(@PathVariable("id") Long id)
{
return restTemplate.getForObject(PAYMENT_URL+"payment/get/"+id.toString(),CommonResult.class);
}
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPaymentEntity(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "payment/get/" + id.toString(), CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful())
{
return entity.getBody();
}
else
{
return new CommonResult(444,"操作失败");
}
}

之前介绍过源码,这个RestTemplate是在底层调用了Ribbon的方法,通过Ribbon来实现负载均衡。所以我们直接使用RestTemplate就可以使用负载均衡。

Ribbon负载均衡规则

Ribbon负载均衡规则的重要组件:IRule,根据特定算法中从服务列表中获取一个要访问的服务。

  • RoundRobinRule : 轮询
  • RandomRule : 随机
  • RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试,然后获取可用的服务
  • WeightedResponseTimeRule:对RoundRobinRule的扩展,相应速度越快的实例选择权重越大,越容易被选择
  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • AvailabilityFilteringRule : 先过滤掉故障实例,再选择并发较小的实例
  • ZoneAvoidanceRule: 默认规则,复合判断server所在区域的性能和server的可用性选择服务器

Ribbon如何替换规则

  • 如果我们要自定义配置类,那么就不能将这个配置类放在ComponentScan所扫描的当前包下以及子包下,否则所有的ribbon客户端就会共享这个配置文件,达不到特殊定制化的目的了。

1、Main方法入口上面添加了SpringBootApplication注解,而这个注解被ComponentScan修饰所有我们不能将ribbon的自定义规则放在main函数的包以及子包下,我们新建一个包,与main方法所在的包同级
2、编写mySelfRule类,进行自定义配置

@Configuration
public class mySelfRule {
@Bean
public IRule myRule()
{
return new RandomRule();//定义为随机
}
}

3、在主配置类上添加注解RibbonClient

@EnableEurekaClient
@SpringBootApplication
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = mySelfRule.class) //这样就进行了替换,如果是多个那么就使用``
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}

Ribbon负载均衡算法

原理

轮询负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始。

例如我们有两台服务器支撑该服务:Server1,Server(数组形式)
第一次调用时, 1 % 2 = 1 调用Server2
第二次调用时, 2 % 2 = 0 调用Server1
第三次调用时, 3 % 2 = 1 调用Server2
…以此类推

我们通过服务发现客户端能够找到注册到注册中心该服务到底有几台服务器。然后根据获取到的服务器个数进行负载均衡。

手写一个负载均衡算法

设计理念就是保存一个整型变量,用来保存请求该链接的次数,然后根据次数去与可用服务器数整除取余数,然后根据余数取服务器上的资源。
1、在config类里面,注入RestTemplate时将@LoadBalanced注解去掉

2、编写LoadBalancer接口

public interface LoadBalancer {
/**
* 初始化得时候,得到机器列表,返回机器数
* @param serviceInstances
* @return
*/
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

3、编写接口实现类MyLoadBalancer

@Component
public class MyLoadBalancer implements LoadBalancer {

private AtomicInteger atomicInteger =new AtomicInteger(0);

//根据访问次数找到下次是多少
public final int getAndIncrement()
{
int current ;
int next;
do {//自旋
current = this.atomicInteger.get();//获取到当前的请求次数
next = current >= Integer.MAX_VALUE ? 0 : current+1; //如果当前的值大于最大值,那么从0开始
}while (!this.atomicInteger.compareAndSet(current,next));//当current更新为next成功,就不循环了
return next;
}

@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size(); //获取到这次访问次数与服务器总数取余
return serviceInstances.get(index);
}
}

4、在controller中使用该算法

@Autowired
private RestTemplate restTemplate; //用来操作http
@Autowired
private LoadBalancer loadBalancer; //编写的负载均衡
@Autowired
public DiscoveryClient discoveryClient;//服务发现客户端
private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE/";
@GetMapping("/consumer/payment/lb")
public String getPaymentLB()
{
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); //获取该服务的所有可用节点
if(instances == null || instances.size()<=0) return null;
ServiceInstance serviceInstance = loadBalancer.instances(instances); //利用算法对可用服务器做轮询
URI uri = serviceInstance.getUri(); //获取url
return restTemplate.getForObject(uri+"/payment/lb",String.class); //调用服务
}

5、实现效果就是每次访问都是返回不同的端口号,并且是依次返回。顺序相同。

OpenFeign介绍

Feign是一个声明式WebService客户端,使用Feign能让编写web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Fegin也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMeeageConverters。Fegin可以与Eureka和Ribbon组合使用以支持负载均衡。

Feign能干什么

Feign旨在使编写java http客户端变得更加容易。我们在使用Ribbon+RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一个模板化的调用方法。但是实际开发中,由于对服务的依赖调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并且使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注以一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成Ribbon

利用Ribbon维护服务生产者的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

Feign与OpenFeign

Feign OpenFeign
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。
Feign的使用方式是:使用Feign的注解定义接口,调用这个接口
就可以调用服务注册中心的服务
OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解
,如@RequestMapping等等。OpenFeign的@FeignClient可以解析
SpringMVC的@ReuqestMapping注解下的接口,并通过动态代理的方
式产生实现类,实现类中做负载均衡调用其他服务。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

OpenFeign服务调用

1、新建一个cloud-consumer-feign-order80工程,feign是在消费端使用的。
2、编辑pom文件

<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.zenshin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

3、配置yml文件

server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
# 之前配置好的集群
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

4、编写主函数

@SpringBootApplication
@EnableFeignClients // 启动Feign客户端
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}

5、业务类

  • 业务逻辑接口+@FeignClient配置调用provider服务,新建逻辑接口PaymentFeignService,并增加注解@FeignClient

    @Service
    @FeignClient("CLOUD-PAYMENT-SERVICE") //服务名称
    public interface PaymentFeignService {
    @GetMapping(value = "/payment/get/{id}")
    CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
    }
  • 控制层Controller进行调用

    @RestController
    public class OrderFeignController {
    @Autowired
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") long id)
    {
    return paymentFeignService.getPaymentById(id);
    }
    }

6、这样启动服务以后从Eureka上面读得时候就能通过OpenFegin进行负载均衡了,效果还是之前得样子即

OpenFeign超时控制

OpenFeign-Ribbon客户端默认等待一秒,如果在一秒之内没有响应,那么就说明这个服务端挂掉了,然后就会尝试其他的服务端进行调用。
如果服务提供者只有一个,那么很不幸,OpenFeign就会报错。如果我们不想一秒就直接无情的挂掉,那么我们就需要在yml文件中对OpenFegin进行配置。

#设置OpenFeign客户端超时时间
ribbon:
#指的是建立连接所用的时间,适合于网络状况正常的情况下,来个那段连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

这样修改后,Open Feign就能够容忍5秒以内的超时了。

OpenFeign日志增强

Fegin提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Fegin中Http请求细节,也就是对Feign接口的调用情况进行监控和输出。

OpenFeign日志级别

  • NONE:默认的,不显示任何日志
  • BASIC:仅记录请求方法、URL、相应状态码以及执行时间
  • HEADERS:除了BASIC中定义的信息之外,还有请求和相应的头信息
  • FULL:除了HEADERS中定义的信息之外,还有请求和相应的正文及元数据

使用Feign的日志

1、在代码中注册一个logger,日志器

@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}

Logger是属于import feign.Logger;包下的。

2、在配置文件中进行配置

logging:
level:
#feign日志以什么级别监控哪个接口
com.zenshin.springcloud.service: debug

3、运行就可以看到OpenFeign输出的日志了。

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/05/25/springcloud/balance/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论