参考:<<重新定义springcloud>>
各个视频
什么是ribbon?它解决了什么问题
- Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,他是基于Netflix Ribbon实现的。
- 它不像spring cloud服务注册中心,配置中心,API网关那样独立部署,但是它几乎在每一个spring cloud微服务中,包括feign提供的声明式服务调用也是基于Ribbon实现的。
- ribbon提供了很多种负载均衡算法,例如 轮询、随机 等等。甚至是包含自定义的负载均衡算法。
- 他解决了微服务的负载均衡的问题
负载均衡解决方案的分类
目前业界主流的负载均衡方案课分为俩类:
1、集中式负载均衡,就是在consumer和provider之间使用独立的负载均衡设施(可以是硬件,如F5,也可以是软件 ,nginx)由这些设施 负责把访问请求 通过 某种策略转发至provider
2、进程内负载均衡,将负载均衡逻辑集成到consumer,consumer从注册中心获取有哪些服务地址,从中通过某种策略挑选出一个provider
Ribbon 就是属于后者,他只是一个类库,集成于consumer进程,consumer通过他来获取到provider的地址
他们的区别架构图
Ribbon的负载均衡算法策略
id | 命名 | 策略类 | 描述 |
1 | 轮询算法 | RoundRibbonRule | 按顺序循环选择server |
2 | 随机算法 | randomRule | 随机选择server |
3 | 重试策略 | retryRule | 在一个配置时间内当选择的server不成功,则一直尝试选择一个可用的server |
4 | 最低并发策略 | BestAvailableRule | 逐个考擦server,如果server断路器打开,则忽略,选择请求中并发最小的server, |
5 | 可用过滤策略 | availabilityFilteringRule | 过滤掉一直连接失败并被标记为ciruruit tripped的server,过滤掉哪些高并发连接的server(active connections超过配置的阈值) |
6 | 响应时间加权策略 | responseTimeWeightRule | 根据server的响应时间分配权重,响应时间越长,权重越低,响应时间越短,权重越高,被选中的概率就越高,这个策略很贴切,综合了。网络 磁盘 IO等因素 |
7 | 区域权衡策略 | zoneavoidanceRule |
综合判断server所在区域的性能和server的可用性轮询选择server,判断一个区域是否可用,如果不可用直接将这个区域的sever都丢弃 如:多机房情况 |
使用RIbbon实现的入门实例:
1.先创建一个父工程:只有pom文件 hello-springcloud
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xiaodao</groupId> <artifactId>hello-springcloud</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>eureka_server</module> <module>client-a</module> <module>client-a</module> <module>ribbon-client</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> </parent> <!-- 利用传递依赖,公共部分 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- springboot web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.创建一个eurekaserver服务:client-a
pom文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>hello-springcloud</artifactId> <groupId>com.xiaodao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>eureka_server</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
bootstrap.yml 和启动类
server: port: 8888 eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
/**
* Created by xiaodao
* date: 2019/7/17
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
3.创建一个provider
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
启动类:
@SpringBootApplication @EnableDiscoveryClient public class ClientAApplication { public static void main(String[] args) { SpringApplication.run(ClientAApplication.class, args); } }
controller:
package com.xiaodao.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/add") public String add(Integer a, Integer b, HttpServletRequest request){ return " From Port: "+ request.getServerPort() + ", Result: " + (a + b); } }
3.创建第3个项目:ribbon-client
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
bootstrap.yml
spring: application: name: ribbon-loadbalancer server: port: 7777 eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/
#默认是hostname注册,改成ip注册
instance: prefer-ip-address: true
启动类:
@SpringBootApplication @EnableDiscoveryClient public class RibbonLoadbalancerApplication { public static void main(String[] args) { SpringApplication.run(RibbonLoadbalancerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
testController:
@RestController public class TestController { @Autowired private RestTemplate restTemplate; @GetMapping("/add") public String add(Integer a, Integer b) { String result = restTemplate .getForObject("http://CLIENT-A/add?a=" + a + "&b=" + b, String.class); System.out.println(result); return result; } }
现在我们的项目都创建完成了.我们启动俩个client-a不过端口不一样.启动ribbon-client 启动 hello-springcloud
当我们请求localhost:7777/add?a=1&b=10 的时候我们可以看到打印的日志:
From Port: 7070, Result: 11 From Port: 7071, Result: 11 From Port: 7070, Result: 11 From Port: 7071, Result: 11 From Port: 7070, Result: 11 From Port: 7071, Result: 11 From Port: 7070, Result: 11
换一个中策略来实现
前面提到过那么多策略模式.我们模式的是轮询模式,现在来挑选一个最简单的可以看出来的随机
我们有配置文件和java俩种配置
@Bean public IRule ribbonRule() { return new RandomRule();//这里配置策略,和配置文件对应 }
配置文件方式:
spring: application: name: ribbon-loadbalancer server: port: 7777 eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/ instance: prefer-ip-address: true CLIENT-A: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
点对点直连,让ribbon调试更加方便快捷
什么是点对点直连?
cusumer和provider点对点直连不经过注册中心,为什么呢?因为开发环境都是有eureka来做注册中心,开发中大家的idea都是启动的,你要和对方进行联调,很难联调
如果我和张三联调.负载均衡经常发给李四和王五,这种情况没法联调.有人说我要和张三联调,让李四和王五先停下来.这怎么行?
如何实现呢?
1.在consumer就是你要调用对方的自己的服务:
将uereka移除
<!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>-->
@SpringBootApplication //@EnableDiscoveryClient public class RibbonLoadbalancerApplication { public static void main(String[] args) { SpringApplication.run(RibbonLoadbalancerApplication.class, args); } @Bean // @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } /*@Bean public IRule ribbonRule() { return new RandomRule();//这里配置策略,和配置文件对应 }*/ }
pom文件:
spring: application: name: ribbon-loadbalancer server: port: 8082 #eureka: # client: # serviceUrl: # defaultZone: http://localhost:8888/eureka/ # # instance: # prefer-ip-address: true #禁用eureka ribbon: eureka: enabled: false CLIENT-A: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule listOfServers: http://127.0.0.1:7070,http://127.0.0.1:7071
在调用的时候需要这么写:
@RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient;//ribbon 负载均衡客户端 @GetMapping("/add") public String add(Integer a, Integer b) { ServiceInstance si=loadBalancerClient.choose("CLIENT-A"); StringBuffer sb=new StringBuffer(""); sb.append("http://"); sb.append(si.getHost()); sb.append(":"); sb.append(si.getPort()); sb.append("/add?a="+a+"&b="+b); System.out.println(sb.toString()); String result = restTemplate.getForObject(sb.toString(), String.class); // String result = restTemplate // .getForObject("http://CLIENT-A/add?a=" + a + "&b=" + b, String.class); System.out.println(result); return result; } }
这个时候,就可以实现点对点的联调.
关于@loadBanced的实现.
就是基于实际是通过实现 ClientHttpRequestInterceptor 实现的
LoadBalancerInterceptor
LoadBalancerClient
RibbonLoadBalancerClient
然后经过LoadBalancerAutoConfiguration自动配置将loadbalanceInterceptor变为resttemplate的的拦截器
@Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }
这里不做多讲.毕竟在下也是只知皮毛.