本文章基于spring-boot-starter-parent 2.0.6RELEASE,spring-cloud-dependencies Finchley.SR2。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡,支持多种可插拔LB策略。Spring Cloud Ribbon基于Netflix Ribbon实现,是一个基于HTTP和TCP的客户端负载均衡工具。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。
我们通常所说的负载均衡指的是服务端负载均衡,其中分为硬件负载均衡,比如F5;软件负载均衡比如Nginx。不管是硬件负载均衡的设备还是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。
而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到的服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端清单来自服务注册中心。
下面通过对比架构方式来看下两者的区别:
在Spring Cloud微服务架构中Ribbon只需两步:
@LoadBalanced
注解修饰过的RestTemplate来实现面向服务的接口调用。这里使用一个服务注册中心(Eureka)+2个服务提供者(Order集群)+ 一个服务调用者(User)来演示。如果不了解Eureka,请先去看Spring Cloud组件之Eureka 这篇文章。
因为Eureka中已经集成了Ribbon,所以只需要引入Eureka依赖包,不再需要引用Ribbon的依赖。
AppUserApplication.class
@SpringBootApplication
@EnableEurekaClient
public class AppUserApplication {
public static void main(String[] args) {
SpringApplication.run(AppUserApplication.class);
}
}
UserController.class
@RestController
public class UserController {
@Autowired
RestTemplate restTemplate;
// 负载均衡需要使用服务名去调用微服务
private static final String ORDER_URL = "http://ORDER-MICRO";
@RequestMapping("/getOrder.do")
public R getOrder() {
return R.success("操作成功", restTemplate.getForObject(ORDER_URL + "/getOrder.do", Object.class));
}
}
AppConfig.class
@Configuration
public class AppConfig {
// @LoadBanlanced 开启负载均衡
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
application.yml
server:
port: 5000
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8081/eureka
register-with-eureka: false
instance:
instance-id: user #此实例注册到eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP地址
spring:
application:
name: user-micro #此实例注册到eureka服务端的name
AppOrderApplication.class
@SpringBootApplication
@EnableEurekaClient
public class AppOrderApplication {
public static void main(String[] args) {
SpringApplication.run(AppOrderApplication.class);
}
}
OrderController:
@RestController
public class OrderController {
@RequestMapping("/getOrder.do")
public Map getOrder() {
Map map = new HashMap();
map.put("key","order");
return map;
}
}
application.yml
server:
port: 7000
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8081/eureka
instance:
instance-id: order #此实例注册到eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP地址
spring:
application:
name: order-micro #此实例注册到eureka服务端的name
另外一个服务提供者与Order一样,端口号改为7001,返回值改为map.put("key","order1");
这里就不在粘贴代码了。
AppEureka.class
@SpringBootApplication
@EnableEurekaServer
public class AppEureka {
public static void main(String[] args) {
SpringApplication.run(AppEureka.class);
}
}
application.yml
server:
port: 8081
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 4000 #设置清理间隔(单位:毫秒 默认是60*1000)
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
分别启动后,访问Eureka注册中心http://locahost:u8081/
,可发现两个order正常启动成功,user因为没有将自己注册到eureka,所以eureka中看不到。
从user中访问order服务,http://5000/getOrder.do
,连续敲几次可发现每次返回的值不一样,说明客户端负载均衡起作用了。
IRule是Ribbon对于负载均衡策略实现的接口,通过实现这个接口,我们可以定义负载均衡策略,下表是Ribbon包含的默认实现。
类名 | 作用 |
---|---|
RoundRobinRule | 按照线性轮询的方式依次选择每个服务实例。 |
RandomRule | 从服务实例清单中随机选择一个服务实例。 |
PredicateBasedRule | 抽象策略,先通过子类中实现的逻辑来过滤清单,再轮询选择。 |
AvailabilityFilteringRule | 继承了PredicateBasedRule,通过线性抽样的方式直接尝试寻找可用且较空闲的实例来使用,优化了父类每次都要遍历所有实例的开销。 |
ZoneAvoidanceRule | 继承了PredicateBasedRule,完全遵循父类的过滤主逻辑,“先过滤清单,再轮询选择”。其中过滤条件为组合过滤,以ZoneAvoidandPredicate为主过滤条件,AvailabilityPredicate为次过滤条件。 |
WeightResponseTimeRule | 根绝平均响应时间计算所有服务的权重,响应时间越快服务权重越大,共启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息最够后再切换过来。 |
RetryRule | 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内反复重试,在获取不到则返回null。 |
BestAvaliableRule | 先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。 |
如何去切换负载均衡的默认实现呢?
在AppConfig中配置即可:
@Configuration
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public IRule iRule() {
// 返回要实现的负载均衡器
return new RandomRule();
}
}
除了使用Ribbon本身提供的,我们还可以自己实现负载均衡算法,只要实现了IRule
或者继承自AbstractLoadBalancerRule
就可以完成自定义负载均衡。
这里把Ribbon中的RandomRule的具体实现拿出来供读者参考:
public class RandomRule extends AbstractLoadBalancerRule {
Random rand = new Random();
public RandomRule() {
}
@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 upList = lb.getReachableServers();
// 获取所有服务清单列表
List allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// 随机选择一个服务Server
int index = this.rand.nextInt(serverCount);
server = (Server)upList.get(index);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
想了解常见的负载均衡算法原理那可参考这两篇文章
负载均衡算法原理解析(一)
负载均衡算法原理解析(二)