spring cloud Ribbon用法及原理解析
作者:天宇轩-王 发布时间:2021-11-28 15:27:21
这篇文章主要介绍了spring cloud Ribbon用法及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
简介
这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿出来写用例测试的,实际生产一般是配置feign一起使用,更加方便开发。同时这里也通过源码来简单分析一下ribbon的基本实现原理。
基本使用
这里使用基于zookeeper注册中心+ribbon的方式实现一个简单的客户端负载均衡案例。
服务提供方
首先是一个服务提供方。代码如下。
application.properties配置文件
spring.application.name=discovery-service
server.port=0
service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
bootstrap.properties配置文件
spring.cloud.zookeeper.connect-string=192.168.0.15:2181
引导程序,提供了一个ribbonService的rest接口服务,注册程序到zookeeper中。
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class DiscoverClient {
public static void main(String[] args) {
SpringApplication.run(DiscoverClient.class, args);
}
@RequestMapping("/ribbonService")
public String ribbonService(){
return "hello too ribbon";
}
}
服务调用方
服务调用方就是进行负载均衡的一方,利用ribbo的RestTemplate进行负载调用服务。
RibbonConfig,配置ribbon的RestTemplate,通过@LoadBalanced注解实现,具体原理稍后分析。
@Configuration
public class RibbonConfig {
/**
* 实例化ribbon使用的RestTemplate
* @return
*/
@Bean
@LoadBalanced
public RestTemplate rebbionRestTemplate(){
return new RestTemplate();
}
/**
* 配置随机负载策略,需要配置属性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
*/
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
引导程序
@SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance")
@EnableDiscoveryClient
@RestController
public class TestRibbonApplocation {
public static void main(String[] args) {
SpringApplication.run(TestRibbonApplocation.class, args);
}
@Autowired
@LoadBalanced
RestTemplate restTemplate;
@GetMapping("/{applicationName}/ribbonService")
public String ribbonService(@PathVariable("applicationName") String applicationName){
return restTemplate.getForObject("http://" + applicationName+"/ribbonService", String.class);
}
}
配置文件同上,服务名称修改即可。
测试
启动两个discovery-service,由于端口设置为0,所以是随机端口。
启动服务调用方
浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如http://localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。
结果返回:hello too ribbon ,同时服务提供方启动的两个服务都可能被调用,取决于怎么配置负载策略。
上面就是一个简单使用ribbon的例子,结合feign使用基本上是做类似上面所写的工作,那么ribbon到底是怎么实现的呢?
原理与源码分析
ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的 * 机制,在 * 中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。
Ribbon的RestTemplate
RestTemplate中有一个属性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的 * 数据不为空,在RestTemplate进行http请求时,这个请求就会被 * 拦截进行, * 实现接口ClientHttpRequestInterceptor,需要实现方法是
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException;
也就是说 * 需要完成http请求,并封装一个标准的response返回。
ribbon中的 *
在Ribbon 中也定义了这样的一个 * ,并且注入到RestTemplate中,是怎么实现的呢?
在Ribbon实现中,定义了一个LoadBalancerInterceptor,具体的逻辑先不说,ribbon就是通过这个 * 进行拦截请求,然后实现负载均衡调用。
* 定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
//定义ribbon的 *
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
//定义注入器,用来将 * 注入到RestTemplate中,跟上面配套使用
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
ribbon中的 * 注入到RestTemplate
定义了 * ,自然需要把 * 注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了 * 的定义与 * 注入器的定义,那么肯定会有个地方使用注入器来注入 * 的。
在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码如下。
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
//遍历context中的注入器,调用注入方法。
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
//......
}
遍历context中的注入器,调用注入方法,为目标RestTemplate注入 * ,注入器和 * 都是我们定义好的。
还有关键的一点是:需要注入 * 的目标restTemplates到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入 * ,这里就是@LoadBalanced注解发挥作用的时候了。
LoadBalanced注解
严格上来说,这个注解是spring cloud实现的,不是ribbon中的,它的作用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。
例如我们定义Ribbon的RestTemplate的时候是这样的
@Bean
@LoadBalanced
public RestTemplate rebbionRestTemplate(){
return new RestTemplate();
}
因此才能为我们定义的RestTemplate注入 * 。
那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操作,@LoadBalance的源码如下
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
很明显,‘继承'了注解@Qualifier,我们都知道以前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是类似的实现,restTemplates通过@Autowired注入,同时被@LoadBalanced修饰,所以只会注入@LoadBalanced修饰的RestTemplate,也就是我们的目标RestTemplate。
* 逻辑实现
LoadBalancerInterceptor源码如下。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
拦截请求执行
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//在这里负载均衡选择服务
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
//执行请求逻辑
return execute(serviceId, ribbonServer, request);
}
我们重点看getServer方法,看看是如何选择服务的
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
//
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
代码配置随机loadBlancer,进入下面代码
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//使用配置对应负载规则选择服务
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
这里配置的是RandomRule,所以进入RandomRule代码
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//获取可用服务列表
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
//随机一个数
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。我们可以配置不同的Rule来实现不同的负载方式。
来源:https://www.cnblogs.com/dalianpai/p/11690322.html


猜你喜欢
- 为了防止用户或者测试MM疯狂的点击某个button,写个方法防止按钮连续点击。具体实例代码如下所示:public class B
- 单元测试是编写测试代码,应该准确、快速地保证程序基本模块的正确性。JUnit是Java单元测试框架,已经在Eclipse中默认安装。JUni
- 在为什么阿里巴巴不建议在for循环中使用”+”进行字符串拼接一文中,我们介绍了几种Java中字符串拼接的方式,以及优缺点。其中还有一个重要的
- 前言先说结论,tauri是一个非常优秀的前端桌面开发框架,但是,rust门槛太高了。一开始我是用electron来开发的,但是打包后发现软件
- 要“监听”事件,我们总是可以将“ * ”作为事件源中的另一个方法写入事件,但这将使事件源与 * 的逻辑紧密耦合。对于实际事件,我们比直接方法
- DOM4可以读取和添加XML文件的属性或者元素读取属性:public static void ReadAttributes() throws
- 什么是委托?之前写了事件的介绍:https://www.jb51.net/article/59461.htm这里也把委托相关知识也总结一下。
- 这篇文章主要介绍了Spring-boot的debug调试代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
- 本文演示android中图片加载到内存首先设计界面:代码如下:<LinearLayout xmlns:android="ht
- 解释:二叉树的深度:从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。二叉树的宽度:二叉树的每一层中
- 工作以来,代码越写越多,程序也越来越臃肿,效率越来越低,对于我这样一个追求完美的程序员来说,这是绝对不被允许的,于是除了不断优化程序结构外,
- 一、利用word生成一个文档转成pdf说明:转换成pdf格式二、Abobe Acrobat DC图解利用Abobe Acrobat DC打开
- 一、表白墙简介在表白墙页面中包含三个文本框,分别表示表白者,表白对象,表白内容,在文本框中输入内容之后,内容能够保存,并且在下次启动页面的时
- 方法一:通过Theme.Translucent@android:style/Theme.Translucent@android:style/
- 生成的Android应用APK文件最好进行优化,因为APK包的本质
- 做了微信支付,下载了Demo,发现和之前有所改动,v3.0的版本,也许有的朋友还在摸索,这里我已经成功支付,话不多说,直接进入主题:一、首先
- Android对sdcard扩展卡文件的操作其实就是普通的文件操作,但是仍然有些地方需要注意。比如:1.加入sdcard操作权限;2.确认s
- 本文实例讲述了Android通过Handler与AsyncTask两种方式动态更新ListView的方法。分享给大家供大家参考,具体如下:有
- 以前使用HttpServletResponse可以通过输出流的方式来向前台输出图片。现在大部分都是使用springboot,在使用sprin
- 近日学习Easyui,发现非常好用,界面很美观。将学习的心得在此写下,这篇博客