Ribbon的工作原理
Ribbon是Netfix公司的开源项目,是基于HTTP和TCP的客户端负载均衡组件,她是不可以独立部署的。Spring Cloud Ribbon基于Ribbon实现,基于轮训、随机等规则自动调用服务,也可以自定义负载均衡算法。
Ribbon支持的负载均衡策略
负载均衡策略主要有几种:
- 线性轮训策略
通过一个计数器实现。
- 线性轮训策略
- 重试策略
- 如果选到的服务实例正常,则返回数据。
- 如果选到的服务实例为null或失效,则choose方法会在失效时间前不断进行重试。
- 如果超过了实效时间还是没取到,则返回一个null。
- 重试策略
- 加权响应时间策略
每30s计算一次各个服务实例的响应时间,以响应时间来计算权重。平均响应时间越短则权重越高,权重越高被选中的概率越高,反之被选中的概率越低。
- 加权响应时间策略
- 随机策略
(1)负载均衡通过upList()和allList()方法获得可用服务实例列表,然后初始化一个Random对象以生成一个不大于服务实例总数的随机数。
(2)choose()方法将该随机数作为下标获取一个服务实例。轮训“index”,选择“index”对应位置的服务实例。
- 随机策略
- 客户端配置启用线性轮训策略
- 最空闲策略
(1)根据loadBalanceStats()方法中保存的服务实例的状态信息来过滤实效服务实例。
(2)判断loadBalanceStats是否为空。
- 最空闲策略
- 过滤性轮训策略PredicateBasedRule
PredicateBasedRule类是ClientConfigEnabledRoundRobinRule类的子类,通过定义一个过滤器过滤出一部分服务实例清单,然后用线性轮询的方式从过滤出来的服务实例清单中选取一个服务实例。
- 过滤性轮训策略PredicateBasedRule
- 区域感知轮训策略ZoneAvoidanceRule
以区域、可用服务器为基础,选择服务实例并对服务实例进行分类。
- 区域感知轮训策略ZoneAvoidanceRule
- 可用性过滤策略
该策略根据服务状态(宕机或繁忙)来分配权重,过滤掉那些因为一直连接失败或高并发的服务实例。
- 可用性过滤策略
自定义负载均衡策略
1.添加依赖和配置
添加Web和Ribbon的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
添加配置项
spring.application.name=RibbonRule
server.port=9003
logging.level.root=DEBUG
provider.ribbon.listOfServers=localhost:8504,localhost:8505
2.开启负载均衡支持
在启动类中添加注解#@LoadBalnced以开启客户端负载均衡,然后实例化RestTemplate见以下代码:
/**
* 表示开启客户端负载均衡
*/
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
3.编写配置类,配置规则
配置类注解@Configuration,并用注解@RibbonClient配置服务名,然后实例了Ribbon规则
@Configuration
@RibbonClient(name = "provider", configuration = RibbonClientConfiguration.class)
public class RibbonConfig {
@Bean
public IRule iRule() {
return new RandomRule();
}
}
除配置方式以外,还可以用配置文件的方式进行配置
ServiceA.ribbon.NFLoadBalancerRuleClassName:com.netflix.loadbalance.RandomRule
其中“ServiceA”代表对服务A进行配置。
4.编写控制器
在控制器中编写测试代码,用于测试自定义的负载均衡策略。
@RestController
public class TestController {
@Autowired
private LoadBalancerClient loadBalancerClient;
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
@GetMapping("/test")
public String test() {
ServiceInstance serviceInstance = loadBalancerClient.choose("provider");
String result = serviceInstance.getHost() + serviceInstance.getPort() + " " + SDF.format(new Date());
System.out.println(result);
return result;
}
}
5.测试策略
多次访问http://localhost:9003/test,可以查看轮训结果。
Feign
SpringCloud的“服务发现”除可以用RestTemplate客户端实现外,还可以用Feign客户端实现。Feign是一个声明式Web Service客户端,它使得编写Web Service客户端变得容易。
Feign客户端过程:
(1)建立与“服务提供者”的网络连接
(2)构造请求
(3)发送请求到“服务提供者”
(4)处理“服务提供者”返回的响应结果。
在项目中,在接口中使用了注解@FeignClient,则Feign客户端会针对这个接口创建一个动态代理。调用该接口,实质就是调用Feign客户端创建的动态代理:Feign客户端的动态代理会根据接口上的@RequestMapping等注解动态构造要请求的服务地址和方法,并针对这个地址发起请求并解析响应。
添加依赖和配置
- 添加依赖
<!--Consul 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--Openfeign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
- 添加配置文件
spring.application.name=Feign Configuration
server.port=8701
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
#因为不提供,所以设置不需要将其注册到Consul
spring.cloud.consul.discovery.register=false
logging.level.com.ping.feign=DEBUG
添加支持
在启动类中添加注解@EnableDiscoveryClient(用于支持“服务中心”)和@EnableFeignClients(用于支持Feign)。
自定义Feign的配置
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
// 记录请求响应的标题,正文和元数据
return Logger.Level.FULL;
}
}
用feign.Contract.Default将契约改为Feign原生的契约,然后即可使用Feign自带的注解了。
自定义的Feign的接口
因为上面配置了"feign.Contract.Default",所以在接口中可以使用Feign原生的注解@RequestLine了。
@FeignClient(contextId = "feignClient", name = "service-provider", configuration = FeignConfiguration.class)
public interface MyFeignClient {
/**
* Spring MVC注解修改为Feign自带的注解;
* 使用feign自带的注解@RequestLine;
*/
@RequestLine("GET /hello")
public String hello();
}
用post方式构建多参数请求
如果“服务提供者”是通过注解@RequestBody获取数据的,则可以通过以下代码来发送请求:
@FeignClient(name="service-provider")
public interface MyFeignClient {
@PostMapping(value="/post")
public User post(@RequestBody User user);
}
Ribbon和Feign的区别
- 启动类所使用的注解
Ribbon用的是注解@RibbonClient,而Feign用的注解@EnableFeignCliens。
- 启动类所使用的注解
- 服务的指定位置
Ribbon的服务是在注解@RibbonClient中声明的,而Feign的服务则是在注解@FeignClient中声明的。
- 服务的指定位置
- 调用方式
Ribbon需要自己构建HTTP请求模拟HTTP请求,然后使用RestTemplate将该请求发送给其他服务。即, Ribbon使用@RibbonClient(value="服务名称")和RestTemplate调用远程对应的方法。
Feign则是在Ribbon的基础上封装,采用的接口方式,不需要自己构建HTTP请求,只需将“其他服务的方法”定义成抽象方法即可。即, Feign在接口上使用“@FeignClient("指定服务名")”方式来调用服务。注意,注解@FeignClient的属性值应与远程服务中的方法名完全一致。
- 调用方式
- Maven依赖
Ribbon的Maven依赖是spring-starter-ribbon。
Feign的Maven依赖是spring-starter-openfeign。
综上所述,Ribbon使用了RestTemplate,所以其代码的可读性、可维护性和开发体验一般;而Feign则相对好多。Ribbon性能强,灵活性强。
- Maven依赖