RestTemplate
是spring-web.jar
包下的一个类,简化了http
的请求过程,在应用中能够方便地调用rest
服务。
默认情况下,使用无参构造器创建的RestTemplate
对象用的是java.net
包的实现完成http
的连接。
可以使用带参构造器RestTemplate(ClientHttpRequestFactory requestFactory)
创建用HttpClient
实现http
连接的RestTemplate
对象
我们这里用到不仅仅是Netflix Ribbon
这个基础的Ribbon
组件,还包含springframework.cloud.netflix.ribbon
这个基于Netflix Ribbon
再次封装的负载均衡框架。主要功能就是通过负载均衡算法完成服务调用。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 | 根据服务的性能和可用性去选择服务 |
在原本的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
根据服务的性能和可用性去选择服务。
基于springframework.cloud.netflix.ribbon
框架中负载均衡的初始化逻辑,可以知道它在容器中没有IRule
对象的时候会先检查配置,如果没有就创建默认的规则。所以我们可以通过注入IRule
对象的方式修改成自己想要的规则。
@Configuration
public class RuleConfig {
@Bean
public IRule injectRule() {
return new RandomRule(); // 创建一个 RandomRule 交给容器管理
}
}
创建一个配置文件,注入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;
}
}
最简单的方法就是模仿,随便挑一个规则看看它是怎么实现的,这里挑最简单的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
代替它,更加方便简洁。