SpringCloudRibbon是基于NetflixRibbon实现的一套客户端负载均衡的工具
主要功能是提供客户端的软件负载均衡算法和服务调用,Ribbon会提供一系列完善的配置项如连接超时、重试等。简单的说就是在配合文件中列出Load Balancer后面所有的机器,Ribbon会自动帮你基于某种规则(简单轮询、随机连接等)去连接这些机器
Load Balance负载均衡:
LB将用户的请求分摊到多个服务上,从而达到系统的高可用
常见的负载均衡由软件Nginx、LVS,硬件F5等
Ribbon和Nginx的区别:
Nginx是服务器负载均衡(集中式LB),Ribbon是客户端负载均衡(进程内LB)
Niginx会收到客户端的所有请求,这些请求由Nginx来转发,LB是在服务端实现
Ribbon应用于客户端,当客户端调用微服务解救时,会在注册中心上获取注册表并缓存在本地JVM
从而根据某种策略选择某台机器在本地实现RPC远程服务调用
先前Consumer使用的spring-cloud-starter-netflix-eureka-client依赖中就已经包含了Ribbon
Ribbon的负载均衡功能起源于接口IRule
其实现:
这些具体实现就是客户端负载均衡策略的不同选择,默认使用轮询
RoundRobinRule: 轮询
RandomRule: 随机
RetryRule: 先按照轮询策略获取服务地址,如失败会在指定时间内重试
WeightedResponseTimeRule: 对轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailabilityFilteringRule: 先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule: 默认规则,复和判断server所在区域的性能和server的可用性来选择服务器
想要使用Ribbon其他的负载均衡策略,只需通过简单的配置即可:
1、新建package,新建配置类配置负载策略Bean
2、在主启动类中使用@RibbonClient注解声明将要访问的微服务名和使用的负载策略
轮询的原理很简单,通过discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”)
获得CLOUD-PAYMENT-SERVICE集群的所有机器地址信息(ip+port)
装在一个List中缓存在客户端本地JVM中
如此时集群中有两台机器,则:
List[0] instanceA = 127.0.0.1:8001
List[1] instanceB = 127.0.0.1:8002
轮询策略选择的机器 = 第n次请求数 % 集群中机器总数 = 数组中实例下标
如请求为1时 instance = List.get(1%2) = instanceB
此时选择了机器127.0.0.1:8002
回到源码,从IRule开始,choose方法就是从集群中选择机器的策略
找到IRule的一个实现类RoundRobinRule,即轮询策略:
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
this.nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
// 获取健康的服务器信息list
List<Server> reachableServers = lb.getReachableServers();
// 获取所有的服务器list
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
// 集群中的可用服务器个数和全部服务器个数不为0才能继续选择
if (upCount != 0 && serverCount != 0) {
// 根据当前请求数%集群中总机器数获取下标
int nextServerIndex = this.incrementAndGetModulo(serverCount);
// 根据下标从所有服务器list中获取服务器地址实例
server = (Server)allServers.get(nextServerIndex);
// 该地址不可用 之后继续重试
if (server == null) {
Thread.yield();
} else {
// 该地址可用 直接返回并使用
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
// 重试10次后仍然失败 返回null
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
// 获得当前请求次数 这个次数是AtomicInteger
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
// 通过CAS来保证访问次数的原子性 保证多线程调用同一微服务时也依次轮询
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
先前做服务调用时使用RestTemplate+Ribbon,RestTemplate需要指明调用的url
但在实际开发中,Consumer会调用多个Provider来完成业务,直接用RestTemplate显得复杂
为此在Feign的支持下,只需要创建一个接口并使用注解来配置,就可以绑定Provider
Feign已停止维护,目前可以使用OpenFeign代替
OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解
如@RequestMapping等等
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping下的接口
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
OpenFigen用于Consumer侧,通过微服务调用接口+@FeignClient调用Provider提供的Rest接口
新建子模块以Feign客户端形式调用支付服务
1、创建模块Cloud-Consumerfeign-Order80并引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、创建yaml文件并配置
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
instance:
instance-id: order80
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
3、完成主启动类,在主启动类上使用@EnableFeignClients注解
4、创建目标Provider的微服务接口并使用@FeignClient配置
Provider提供的Service:
在Consumer中创建对应的Feign接口,并使用@FeignClient注明Provider的名称
在Controller层调用Feign接口中的方法:
调用成功:
OpenFeign整合了RestTemplate+Ribbon,在Consumer侧编写对应的微服务接口并配置
就可以通过Rest方式访问到Provider的Controller层中的方法
当Consumer调用Provider的服务时,存在Provider业务复杂时可能导致超时的现象
OpenFeign客户端默认等待1s,超过1s后就会报TimeOut异常
为此可以在yaml中配置超时时间,这个超时时间由Ribbon决定
ribbon:
# 建立连接所用的时间
ReadTimeout: 5000
# 建立连接后从服务端读取可用资源所用时间
ConnectTimeout: 5000
OpenFeign提供了日志打印功能,可以通过配置来调整日志级别
从而了解OpenFeign中Http请求的细节,即对OpenFeign接口的调用情况进行监控和输出
日志级别:
NONE: 默认,不显示任何日志
BASIC: 仅记录请求方法、URL、响应状态码及执行时间
HEADERS: 除了BASIC中定义的信息外,还有请求和响应的头信息
FULL: 除了HEADERS中定义的信息外,还有请求和响应的正文及元数据
开启日志功能需要配置相关的Bean:
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
在yaml中配置日志监控级别和接口:
logging:
level:
# 配置监控的接口 及监控级别
com.coisini.springcloud.service.PaymentFeignService: debug