Ribbon

负载均衡

(1)负载均衡是我们处理高并发、缓解网络压力和进行服务端扩容的重要手段之一,但是一般情况下我们所说的负载均衡通常都是指服务端负载均衡,服务端负载均衡又分为两种,一种是硬件负载均衡,还有一种是软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,常见的如F5。软件负载均衡则主要是在服务器上安装一些具有负载均衡功能的软件来完成请求分发进而实现负载均衡,常见的就是Nginx

(2)另一种则是客户端自己做负载均衡,根据自己的请求情况做负载,Ribbon就属于客户端自己来做负载均衡

(3)无论是硬件负载均衡还是软件负载均衡都会维护一个可用的服务端清单,然后通过心跳机制来删除故障的服务端节点以保证清单中都是可以正常访问的服务端节点,此时当客户端的请求到达负载均衡服务器时,负载均衡服务器按照某种配置好的规则从可用服务端清单中选出一台服务器去处理客户端的请求。这就是服务端负载均衡。

客户端负载均衡

“Ribbo是一个基于HTTP和TCP的客户端负载均衡器,当我们将Ribbon和Eureka一起使用时,Ribbon会从Eureka注册中心去获取服务端列表,然后进行轮询访问以到达负载均衡的作用,客户端负载均衡中也需要心跳机制去维护服务端清单的有效性,当然这个过程需要配合服务注册中心一起完成。”

从上面的描述我们可以看出, ****客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置。在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端清单,这些清单统统都是从Eureka服务注册中心获取的。****在Spring Cloud中我们如果想要使用客户端负载均衡,方法很简单,开启 @LoadBalanced注解即可,这样客户端在发起请求的时候会先自行选择一个服务端,向该服务端发起请求,从而实现负载均衡。

常见负载均衡算法

  • 轮询法:负载均衡器收到请求之后,按顺序分配到后端的服务器上,不考虑服务器的性能、负载。比如有服务器A和服务器B两个服务器,轮询法的处理逻辑是第一次收到报文后交给服务器A来处理,第二次交给服务器B来处理,依次轮询。
  • 随机法:负载均衡器收到请求之后,通过随机算法计算分配给哪台服务器,如果请求量特别大,那么起到的效果和轮询法一样。通常会使用随机函数,比如随机生成0到10的数据,小于5的使用服务器A,大于5的使用服务器B。
  • 加权轮询法:不同服务器的配置、性能可能不一样,配置高的服务器可以多分配请求,权重可以配得高一些,配置低的服务器少分配请求,权重可以配得低一些。
  • 加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置与系统的负载分配不同的权重。不同的是,它按照权重随机请求后端服务器,而非顺序。
  • 源地址哈希法:源地址哈希法是根据请求的客户端的IP地址,通过哈希函数计算得到一个哈希值,将此哈希值和服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号: Hash(ip) %(server_count)———>server_ip
  • 最小链接数法:负载均衡器获取服务器的链接数,根据链接数来决定将请求分配到哪台服务器上,把请求分配给积压链接数最少的服务器。

SpringCloud LoadBalancer

SpringCloud LoadBalancer是SpringCloud官方自己提供的客户端负载均衡器,用来替代Ribbon策略(SpringCloud-NetFlix)

Feign

Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了http调用流程

  1. 如果不使用rpc框架,那么调用服务需要走http的话,无论是使用 JDK 自带的 URLConnection,还是使用Http工具包 Apache 的httpclien, 亦或是 OkHttp, 都需要自行配置请求head、body,然后才能发起请求。且获得响应体后还需解析等操作,十分繁琐。
  2. Feign 只需要定义一个接口,并且通过注解的形式定义好请求模板,就可以项使用本地接口一样,使用Http请求
  3. Feign可以更方便地实现REST接口调用,而SpringCloud对Feign进行了增强(OpenFeign)以更好的使用SpringMvc:支持了 SpringMVC 的注解,如@RequestMapping,OpenFeign的@FeignClient 可以解析SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
  4. 使用OpenFeign虽然很好支持了SpringMvc,但是要求更严格地遵循SpringMvc的参数形式!

调用案例

比如现在order-service中去远程调用stock-service,传统基于RestTemplate的方法需要载入许多参数(写明REST接口),而以下是使用OpenFeign调用的过程:

  1. 在调用者service包下创建一个feign.StockFeignService接口:
1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(name = "stock-service",path = "/stock")
/*name 指定所要调用的rest接口所对应的微服务名
* path 指定rest接口所在的Controller类上所指定的@RequestMapping路径(如果对应controller类上没有指定则不需要设置)
* */

//该接口不需要写实现类,原理同Mybatis中的Mapper接口—使用了动态代理
public interface StockFeignService {
//声明需要调用rest接口所对应的方法
@RequestMapping("/reduce")
String reduce ();

}
  1. 编写OrderController即可完成调用:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    @Resource
    StockFeignService stockFeignService;
    @RequestMapping("/add")
    public String add()
    {
    //使用openFeign来实现远程调用
    String messge = stockFeignService.reduce();
    System.out.println("OpenFeign调用成功");
    return "hello feign"+messge;
    }
    }
    同时不要忘记在Order-service的启动类上加入@EnableFeignClients注解!

OpenFeign 自定义配置

自定义日志级别

1
2
3
4
5
6
7
@Configuration   //使用此注解全局配置,即作用于所有服务提供方
public class FeignConfig {
public Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL; //输出最完整的日志
}
}

自定义拦截器

Feign的拦截器不同于Springmvc中的拦截器:在Springmvc中,客户端发送请求到服务端要经过拦截器拦截;而Feign的拦截器则是当服务消费者去调用服务提供者时需要经过拦截器拦截处理,即:

  • Spring MVC拦截器发生在客户端服务端之间,在客户端向服务端发送请求时进行拦截处理。
  • Spring Cloud OpenFeign拦截器发生在两个不同的服务之间,在服务消费端发送请求远程调用服务提供方时进行拦截处理。

(1)在服务提供者的Controller类(StockController)中新定义一个REST接口:

1
2
3
4
5
6
@RequestMapping("/{id}")
public String getId(@PathVariable("id") Integer id)
{
System.out.println("查询库存id为"+id);
return "库存id为"+id;
}

(2)在服务消费者的FeignService中定义调用方法:

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(name = "stock-service",path = "/stock",configuration = FeignConfig.class)
/*name 指定所要调用的rest接口所对应的微服务名
* path 指定rest接口所在的Controller类上所指定的@RequestMapping路径(如果对应controller类上没有指定则不需要设置)
* */
//该接口不需要写实现类,原理同Mybatis中的Mapper接口—使用了动态代理
public interface StockFeignService {

//声明需要调用rest接口所对应的方法
@RequestMapping("/{id}")
public String getId(@PathVariable("id") Integer id);
}

这里注意一定要指明@PathVariable("id"),OpenFeign要求我们严格遵守SpringMvc编写规范,否则会报错

(3)在服务消费者的Controller类中(OrderController)中通过Feign实现远程调用:

1
2
3
4
5
6
@RequestMapping("/findId")
public String findId()
{
String msg=stockFeignService.getId(5);
return msg;
}

然后通过Postman模拟请求http://localhost:8010/order/findId后,会发现控制台输出:**库存id为5**

(4)自定义拦截器类,对服务消费者发送至服务提供者的请求进行拦截处理:

1
2
3
4
5
6
7
8
9
//自定义feign拦截器
public class CustomFeignInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate requestTemplate) {

requestTemplate.uri("/9") ; // 拦截器拦截请求,将请求id更改为9
}
}

(5)同时在服务消费者的application.yaml中设置让拦截器生效

1
2
3
4
5
6
7
feign:
client:
config:
stock-service:
request-interceptors:
- com.lzc.interceptor.feign.CustomFeignInterceptor
#即由order-service发送至stock-service的所有请求都需要经自定义的拦截器(CustomFeignInterceptor)类来处理

再次模拟请求进行测试后可以发现库存已经id变为9了