一,Ribbon的简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具
Ribbon提供的主要功能:
- 客户端的软负载均衡
- 服务调用
Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等,简单的说就是在配置文件中列出Load Balancer(简称LB)的所有服务端,Ribbon会自动的帮助开发者基于某种规则(如简单轮询,随机连接等)去连接这些机器
Ribbon的模块:
- 功能区:在其他功能区模块和Hystrix之上集成负载平衡、容错、缓存/批处理的 API
- ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载平衡器 API
- ribbon-eureka:使用Eureka 客户端为云提供动态服务器列表的API
- 功能区传输:使用具有负载平衡功能的RxNetty支持 HTTP、TCP 和 UDP 协议的传输客户端
- Ribbon-httpclient:基于 Apache HttpClient 构建的 REST 客户端,集成了负载均衡器(已弃用并被功能区模块取代)
LB负载均衡(Load Balance):将客户端请求平摊分配到多个服务上,从而达到系统的HA(高可用)
Ribbon本地负载均衡和Nginx负载均衡的区别:
- Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的(集中式的负载均衡)
- Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程调服务(进程内的负载均衡)
集中式LB:即在服务的消费方和提供方之间使用独立LB(可以是硬件如F5,也可以是软件如Nginx),由该设施负责把访问请求通过某种策略转发到服务提供方
进程内LB:它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
注:Ribbon默认和Eureka做了整合,所以在引入Eureka Client或Eureka Server时就默认带了Ribbon
Ribbon在维护中:
二,Ribbon负载均衡机制
一,Ribbon的负载过程
Ribbon执行分为两步:
- 先选择EurekaServer,它优先选择在同一个区域负载较少的server
- 根据用户指定的负载均衡策略,在从server取到的服务注册列表中选择一个地址,通过RestTemplate进行访问
Ribbon只是一个负载均衡的服务工具,具体的服务调用需要依赖于RestTemplate(底层Httpclient)进行调用
一般使用Ribbon+RestTemplate进行服务调用
二,Ribbon负载均衡机制和规则替换
一,Ribbon负载均衡机制
Ribbon负载均衡算法通过IRule接口实现,如:轮询算法,随机算法都实现了IRule
IRule接口源码:
public interface IRule { //服务名称 Server choose(Object var1); //设置负载均衡算法 void setLoadBalancer(ILoadBalancer var1); //获取负载均衡算法 ILoadBalancer getLoadBalancer(); }
IRule的实现类:
- RoundRobinRule:轮询
- RandomRule:随机
- RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- WeightedResponseTimeRule:对RoundRobinRule的拓展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
二,Ribbon负载均衡的规则替换
官方给出的自定义规则替换的警告:自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则自定义的配置类就会被所有的Ribbon客户端所共享,达不到特殊定制化的目的
注:@ComponentScan所扫描的包就是SpringBoot启动类的包,所有在springboot项目中Ribbon自定义不能和SpringBoot启动类同包
① 父工程依赖
<?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>org.example</groupId> <artifactId>SpringCloud-Dome</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <lombok.version>1.18.10</lombok.version> <log4j.version>1.2.17</log4j.version> <mysql.version>5.1.31</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> </dependencies> </dependencyManagement> </project>
② maven依赖
<dependencies> <dependency> <groupId>com.cloud.commons</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!--eureka服务端,eureka内置了Ribbon依赖不需要单独引入--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.cloud.commons</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> </dependencies>
③ application配置
server: port: 8084 spring: application: name: cloud-ribbon-client eureka: client: fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡 register-with-eureka: true #是否将自己注册进Eureka Server service-url: defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/
④ 自定义IRule(这个包不能为主启动类的子包)
@Configuration public class MySelfRlue { @Bean public IRule Myrule(){ return new RandomRule(); //定义随机 } }
⑤ 启动类
@SpringBootApplication @EnableDiscoveryClient @EnableEurekaClient //name位服务名,configuration位自定义Rule的class @RibbonClient(name = "CLOUD-PAYMENT-SERVER",configuration = MySelfRlue.class) public class RibbonMain { public static void main(String[] args) { SpringApplication.run(RibbonMain.class,args);} }
⑥ controller
@RestController @RequestMapping("/consumer") @Slf4j public class ConsumerController { public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVER"; @Autowired private DiscoveryClient discoveryClient; @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/payment/create",method = RequestMethod.GET) public CommonResult<payment> create(payment payments){ return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payments,CommonResult.class); } @RequestMapping("/payment/select/{id}") public CommonResult<payment> select(@PathVariable("id") int id){ return restTemplate.getForObject(PAYMENT_URL+"/payment/select/"+id,CommonResult.class); }
测试:
三,Ribbon默认负载轮询算法原理
轮询负载均衡算法:(rest接口第几次请求数) % (服务器集群总数量) = 实际调用服务器位置下标(每一次服务重启后rest接口计数从1开始)
List<Servicelnstance> instances = discpveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
如:List[0] instance = 127.0.0.1:8002 List[1] instance = 127.0.0.1:8001
8001+8002组合成为集群,它们共计两台机器,集群总数为2
按照轮询算法原理:
- 当总请求数为1时: 1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
- 当总请求数位2时: 2%2=0对应下标位置为0,则获得服务地址为127.0.0.1:8002
- 当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
- 当总请求数位4时: 4%2=0对应下标位置为0,则获得服务地址为127.0.0.1:8002
- 如此类推.…………
源码:
public class RoundRobinRule extends AbstractLoadBalancerRule { private AtomicInteger nextServerCyclicCounter; private static final boolean AVAILABLE_ONLY_SERVERS = true; private static final boolean ALL_SERVERS = false; private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class); public RoundRobinRule() { this.nextServerCyclicCounter = new AtomicInteger(0); } public RoundRobinRule(ILoadBalancer lb) { this(); this.setLoadBalancer(lb); } public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } else { Server server = null; int count = 0; while(true) { if (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if (upCount != 0 && serverCount != 0) { int nextServerIndex = this.incrementAndGetModulo(serverCount); server = (Server)allServers.get(nextServerIndex); if (server == null) { Thread.yield(); } else { if (server.isAlive() && server.isReadyToServe()) { return server; } server = null; } continue; } log.warn("No up servers available from load balancer: " + lb); return null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; } } } private int incrementAndGetModulo(int modulo) { int current; int next; do { current = this.nextServerCyclicCounter.get(); next = (current + 1) % modulo; } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); return next; } public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public void initWithNiwsConfig(IClientConfig clientConfig) { } }
Comments | NOTHING
Warning: Undefined variable $return_smiles in /www/wwwroot/wql_luoqin_ltd/wp-content/themes/Sakura/functions.php on line 1109