目录儿
五、远程调用-RestTemplate+Ribbon
5.1 RestTemplate 简介
RestTemplate
是spring-web.jar
包下的一个类,简化了http
的请求过程,在应用中能够方便地调用rest
服务。
默认情况下,使用无参构造器创建的RestTemplate
对象用的是java.net
包的实现完成http
的连接。
可以使用带参构造器RestTemplate(ClientHttpRequestFactory requestFactory)
创建用HttpClient
实现http
连接的RestTemplate
对象
5.2 Ribbon 简介
我们这里用到不仅仅是Netflix Ribbon
这个基础的Ribbon
组件,还包含springframework.cloud.netflix.ribbon
这个基于Netflix Ribbon
再次封装的负载均衡框架。主要功能就是通过负载均衡算法完成服务调用。Ribbon
可以从服务注册中心拿到具体的服务地址列表,根据负载均衡算法选择调用其中一个服务。
5.3 Ribbon 内置负载均衡算法
所有规则(算法)都要实现IRule
接口,所以先看接口结构
package com.netflix.loadbalancer;
public interface IRule {
Server choose(Object var1); // 选择一个服务
void setLoadBalancer(ILoadBalancer var1); // 设置负载均衡器
ILoadBalancer getLoadBalancer(); // 获取负载均衡器
}
主要的方法就是那个choose()
方法,用来选择服务,暂不深究,
查看IRule
接口的实现类就能知道Ribbon
有哪些内置算法。
名称 | 说明 |
---|---|
RoundRobinRule | 轮询算法 |
RandomRule | 随机算法 |
AvailabilityFilteringRule | 先过滤掉不可用服务,再使用轮询算法 |
WeightedResponseTimeRule | 自动根据响应时间计算权重,统计信息不足时使用轮询算法 |
RetryRule | 使用轮询算法获取服务,如遇失败在规定时间内重试 |
BestAvaliableRule | 先过滤掉不可用服务,选择并发量最小的服务 |
ZoneAvoidanceRule | 根据服务的性能和可用性去选择服务 |
5.4 如何设置负载均衡规则
5.4.1 默认规则
在原本的Netflix Ribbon
组件中的BaseLoadBalancer
中,可以看到默认的负载均衡策略是RoundRobinRule
轮询算法。
package com.netflix.loadbalancer;
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnectionListener, IClientConfigAware {
...
private static final IRule DEFAULT_RULE = new RoundRobinRule();
...
}
但是在springframework.cloud.netflix.ribbon
框架中重新定义了负载均衡的初始化逻辑
@Bean
@ConditionalOnMissingBean // 当容器没有 IRule 这个 Bean 对象的时候进行初始化
public IRule ribbonRule(IClientConfig config) {
// 先去检查有没有配置对应的规则
if (this.propertiesFactory.isSet(IRule.class, this.name)) {
return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
} else { // 没有的话直接创建一个 ZoneAvoidanceRule 对象交给容器管理
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
}
所以在springframework.cloud.netflix.ribbon
框架中Ribbon Client
的默认负载均衡策略是ZoneAvoidanceRule
根据服务的性能和可用性去选择服务。
5.4.2 修改规则
基于springframework.cloud.netflix.ribbon
框架中负载均衡的初始化逻辑,可以知道它在容器中没有IRule
对象的时候会先检查配置,如果没有就创建默认的规则。所以我们可以通过注入IRule
对象的方式修改成自己想要的规则。
@Configuration
public class RuleConfig {
@Bean
public IRule injectRule() {
return new RandomRule(); // 创建一个 RandomRule 交给容器管理
}
}
5.5 RestTemplate 与 Ribbon的使用
创建一个配置文件,注入RestTemplate
对象
@Configuration
public class GenericConfig {
@Bean
@LoadBalanced // 必须添加负载均衡注解
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
因为前面已经设置过Ribbon
的规则了,注意这个@LoadBalanced
,它能让RestTemplate
对象直接被Ribbon
代理,所以后面我们直接使用RestTemplate
对象去调用服务就行,它会自动均衡负载。
用这个RestTemplate
对象远程调用服务,我这里用了了nacos
作为服务注册中心。
@RestController
public class TestController {
@Value("${server.port}")
private String port;
private final String SERVER_URL = "http://nacos-provider"; // 这里不写具体的某个服务的ip和端口,而是通过注册到 Nacos 的服务名代替
@Resource
private RestTemplate restTemplate;
@RequestMapping("/test")
public String test() {
String result = restTemplate.getForObject(SERVER_URL + "/test", String.class);
return "①号消费者(" + port + "):" + result;
}
}
5.6 自定义负载均衡策略
最简单的方法就是模仿,随便挑一个规则看看它是怎么实现的,这里挑最简单的RandomRule
public class RandomRule extends AbstractLoadBalancerRule {
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
...
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
可以看到它继承了AbstractLoadBalancerRule
,然后重写(这里应该叫实现比较合适)了它的choose()
方法和initWithNiwsConfig()
,还有两个内部的算法函数,里面是具体的负载均衡算法,暂不深究。
依葫芦画瓢,写个简单的算法
@Component
public class CustomRule extends AbstractLoadBalancerRule {
private AtomicInteger atomicInteger = new AtomicInteger(0); //
public Server choose(Object key) {
return chooseLogic(getLoadBalancer(), key);
}
public Server chooseLogic(ILoadBalancer loadBalancer, Object key) {
if (loadBalancer == null) {
return null;
}
List<Server> allServers = loadBalancer.getAllServers(); // 获取服务列表
int requestNum = this.atomicInteger.incrementAndGet(); // 自增
if (requestNum >= Integer.MAX_VALUE) {
atomicInteger = new AtomicInteger(0); // 重置
}
if (allServers != null) {
int size = allServers.size();
int index = requestNum % size;
Server server = allServers.get(index);
if (server == null || !server.isAlive()) {
return null;
}
return server;
}
return null;
}
public void initWithNiwsConfig(IClientConfig iClientConfig) { } // 空实现
}
同样的,注入容器即可
@Configuration
public class RuleConfig {
@Bean
public IRule injectRule() {
return new CustomRule ();
}
}
总结
RestTemplate
+Ribbon
这个远程调用方式是比较老的,而且在调用的时候都要填写地址。现在一般都用OpenFeign
或者Dubbo Spring Cloud
代替它,更加方便简洁。
承上:SpringCloudAlibaba系列微服务搭建笔记一_Nacos
启下:SpringCloudAlibaba系列微服务搭建笔记三_Sentinel