共计 9154 个字符,预计需要花费 23 分钟才能阅读完成。
摘要:原创地址:https://zijiancode.cn/archive… 欢送转载,转载时请保留摘要,谢谢!
文章中的样例会应用 Nacos 篇中的服务,读者能够看文章也能够间接联合 gitee 的代码观看
gitee: https://gitee.com/lzj960515/m…
什么是 Ribbon
Ribbon 是由 Netflix 公司开发的,一个客户端的 IPC(过程间通信)库,它提供了以下个性
- 负载平衡
- 容错
- 反对多协定(HTTP、TCP、UDP)
- 缓存和批处理
github 地址:https://github.com/Netflix/ri…
什么是客户端的负载平衡?
在过程间通信时,服务提供者(服务端)具备多个实例,由服务消费者(客户端)通过负载平衡算法自主抉择某个实例发动调用,就是客户端的负载平衡。
与之对应的便是服务端的负载平衡,常见如 Nginx
Ribbon 入门
1. 引入 Ribbon 相干依赖
<dependencies>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxnetty</artifactId>
<version>0.4.9</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- 用于发送申请,当然也能够有 JDK 自带的 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.6.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
2. 编写代码
public class RibbonDemo {public static void main(String[] args) throws Exception {
// 重试 handler 第一个参数为调用同一个实例的次数,第二个为再次尝试调用其余实例的个数,1 就是调用失败后,换一个实例再来一次,还是失败就返回失败。final RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(0, 1, true);
List<Server> serverList = Lists.newArrayList(new Server("127.0.0.1", 8083),
new Server("127.0.0.1", 8084));
// 创立一个负载均衡器,默认负载平衡算法为轮询
ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder()
.buildFixedServerListLoadBalancer(serverList);
for (int i = 0; i < 6; i++) {String result = LoadBalancerCommand.<String>builder()
.withLoadBalancer(loadBalancer)
.withRetryHandler(retryHandler)
.build()
.submit(server -> {String url = "http://" + server.getHost() + ":" + server.getPort() + "/integral/remain";
return Observable.just(HttpUtil.get(url));
}).toBlocking().first();
System.out.println(result);
}
}
}
这里我应用了 Nacos 篇的积分服务,小伙伴能够本人轻易起一个服务。
3. 测试
起两个实例,端口别离为 8083 和 8084,测试后果:
您以后的积分为:31 服务端口:8084
您以后的积分为:78 服务端口:8083
您以后的积分为:37 服务端口:8084
您以后的积分为:31 服务端口:8083
您以后的积分为:59 服务端口:8084
您以后的积分为:54 服务端口:8083
停掉 8083,再次测试
您以后的积分为:66 服务端口:8084
您以后的积分为:32 服务端口:8084
您以后的积分为:52 服务端口:8084
您以后的积分为:87 服务端口:8084
您以后的积分为:66 服务端口:8084
您以后的积分为:46 服务端口:8084
能够发现 6 次都是胜利,并且都调到了 8084 端口的服务
将 RetryHandler 改为以下配置后测试
RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(0, 0, true);
您以后的积分为:59 服务端口:8084
Exception in thread "main" cn.hutool.core.io.IORuntimeException: ConnectException: Connection refused (Connection refused)
第一次调用了 8084 端口的服务,第二次调用 8083 端口的服务时抛出了异样
Spring Cloud 整合 Ribbon
理解了 Ribbon 的根本应用后,接下来就是学习如何整合到微服务零碎中了,Netflix 公司同样提供了一个 spring-cloud-starter-netflix-ribbon
供咱们疾速整合到 Spring Cloud 中。
github 地址:https://github.com/spring-clo…
留神:抉择 2.x 的 tag,3.x 的分支已移除 Ribbon
根本应用
1. 在 my-order 服务中引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
其实引入 Nacos 依赖时,Nacos 曾经引入了该依赖,这里就做做样子嘻嘻
2. 配置 RestTemplate
@LoadBalanced
@Bean
public RestTemplate restTemplate(){return new RestTemplate();
}
3. 发动调用
restTemplate.getForObject("http://my-goods/goods/get", String.class);
这就没了?嗯,这就没了~
RestTemplate Interceptor
看完根本应用后,小伙伴必定感觉很神奇,就加了个 @LoadBalanced
注解,就功败垂成了?
咱们当初就来简略唠一下他的原理吧。
在 RestTemplate
中,实现了拦截器机制,举个栗子
public class RestTemplateDemo {public static void main(String[] args) {RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new RestTemplateInterceptor());
restTemplate.setInterceptors(interceptors);
System.out.println(restTemplate.getForObject("http://127.0.0.1:8083/integral/remain", String.class));
}
static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {URI uri = request.getURI();
System.out.println(uri.getRawPath());
return execution.execute(request, body);
}
}
}
发动调用前增加了自定义拦截器
RestTemplateInterceptor
,调用时则将进入到intercept
办法@LoadBalanced 注解便是在服务启动时往 RestTemplate 中增加了一个拦截器
LoadBalancerInterceptor
测试成果:
12:43:33.585 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8083/integral/remain
12:43:33.603 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=
/integral/remain
12:43:33.773 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
12:43:33.775 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
您以后的积分为:83 服务端口:8083
当初咱们来模仿一下 Ribbon 中将 server 替换成实在的 ip 地址
public class RestTemplateDemo {public static void main(String[] args) {RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new RestTemplateInterceptor());
restTemplate.setInterceptors(interceptors);
System.out.println(restTemplate.getForObject("http://my-goods/goods/get", String.class));
}
static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@SneakyThrows
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {return execution.execute(new MyRequestWrapper(request), body);
}
}
static class MyRequestWrapper extends HttpRequestWrapper {
/**
* 模仿注册核心存储服务信息
*/
private final Map<String, String> serverMap = Maps.newHashMap("my-goods", "127.0.0.1:8081");
public MyRequestWrapper(HttpRequest request) {super(request);
}
@SneakyThrows
@Override
public URI getURI() {URI uri = super.getRequest().getURI();
String server = uri.getHost();
// 模仿从注册核心取出实在 ip
String host = serverMap.get(server);
// 替换 URI
return new URI(uri.getScheme() + "://" + host + uri.getRawPath());
}
}
}
这里自定义了一个
MyRequestWrapper
,并且重写了getURI
办法,由该办法进行 uri 替换,模仿实现了 Ribbon 的将服务名替换实在 ip 的过程。从 serverMap 里 get 服务信息的逻辑能够再进一步,封装成取出多个服务信息,应用负载平衡策略取出其中一个。
负载平衡策略
Ribbon 中实现了许多的负载平衡策略,它们都实现于一个 IRule
接口
RetryRule: 重试策略,外部蕴含一个 subRule,应用 subRule(默认为轮询)抉择服务,当服务不可用时,在配置的工夫内始终重试,间接找到可用的服务或者超时。
RoundRobinRule: 轮询策略。
WeightedResponseTimeRule: 依据响应工夫加权,响应工夫越长,权重越小,被选中的可能性越低。
ZoneAvoidanceRule: 默认的负载平衡策略,依据服务的区域和可用性失去一个服务列表,而后轮询。没有区域的话等同于轮询。
AvailabilityFilteringRule: 应用轮询抉择服务,对抉择的服务进行状态判断,过滤掉始终连贯失败的服务,直到找到失常可用的服务。
BestAvaliableRule: 抉择并发申请最小的服务。
RandomRule: 随机策略。
NacosRule: Nacos 提供的策略,同集群优先策略。
应用形式
应用 @Bean 注解
@Configuration
public class RuleConfiguration {
@Bean
public IRule rule(){return new NacosRule();
}
}
以上形式将在全局失效
如果想要为每个服务设置不同的负载策略可按如下配置:
配置形式
my-goods:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
留神,此形式必须带有服务名,独自应用
ribbon.NFLoadBalancerRuleClassName
是不会失效的
自定义负载平衡策略
除了应用框架中自带的负载平衡策略,咱们还能够实现本人的策略,比方雷同版本优先调用
@Slf4j
public class VersionRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
@Override
public Server choose(Object key) {
try {
// 失去元数据信息
Map<String, String> metadata = this.nacosDiscoveryProperties.getMetadata();
// 取出版本信息
String version = metadata.get("version");
// 获取调用的服务列表
String group = this.nacosDiscoveryProperties.getGroup();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties());
List<Instance> instances = namingService.selectInstances(name, group, true);
if (CollectionUtils.isEmpty(instances)) {log.warn("no instance in service {}", name);
return null;
}
List<Instance> instancesToChoose = instances;
if (StringUtils.isNotBlank(version)) {
// 筛选出雷同版本的服务
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(version,
instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {instancesToChoose = sameClusterInstances;}
else {
log.warn("A cross-cluster call occurs,name = {}, version = {}, instance = {}",
name, version, instances);
}
}
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
catch (Exception e) {log.warn("NacosRule error", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {}}
减少 version 配置:
spring:
application:
name: my-order
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 192.168.1.11:8850
namespace: public
username: nacos
password: nacos
metadata:
version: 1.0
metadata 是个 map 构造,键值对可自定义
服务容错
有时候申请一个服务呈现网络抖动或者其余问题时,导致申请失败,此时咱们心愿能够重试或者换一个服务发动调用,就能够加上如下配置
ribbon:
# 同服务重试次数
MaxAutoRetries: 1
# 再次尝试调用其余实例的个数
MaxAutoRetriesNextServer: 1
# 所以操作都发动重试,默认只重试 get 申请
OkToRetryOnAllOperations: true
# 服务连贯超时工夫
ConnectTimeout: 1000
# 服务响应超时工夫
ReadTimeout: 2000
# 应用 restclient 否则以上配置有效
restclient:
enabled: true
残缺配置
ribbon:
# 饥饿加载,启动时就创立客户端
eager-load:
enabled: true
clients:
- my-goods
# 同服务重试次数
MaxAutoRetries: 1
# 再次尝试调用其余实例的个数
MaxAutoRetriesNextServer: 1
# 所以操作都发动重试,默认只重试 get 申请
OkToRetryOnAllOperations: false
# 服务连贯超时工夫
ConnectTimeout: 1000
# 服务响应超时工夫
ReadTimeout: 2000
restclient:
enabled: true
my-goods:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
对于 Ribbon 的配置信息可查看类:CommonClientConfigKey
小结
本篇介绍了 Ribbon 相干常识,什么是 Ribbon,如何应用,以及如何集成到 Spring Cloud 中,最初还介绍了如何自定义负载平衡策略。
看完之后想必有所播种吧~ 想要理解更多精彩内容,欢送关注公众号:程序员阿鉴,阿鉴在公众号欢送你的到来~