一、什么是 ribbon? 就是负载均衡! nginx也是负载均衡 1.1 !!!!ribbon和nginx的区别是什么? /* nginx: 正向代理(和客户端连在一起) 反向代理(和服务器端连在一起),nginx的负载均衡其实使用的就是反向代理 ribbon: 负载均衡是和客户端连在一起的,也就是说ribbon是客户端层面的负载均衡 负载均衡放在客户端的好处是什么? 可以让客户端非常直观的看到所有服务器的负载情况, 那么客户端一般情况下会选择负载比较少的服务器进行连接(选择游戏大区) */
二、consumer 开启负载均衡的配置
1.2 ribbon默认自带的有eureka的jar包,也就是如果需要使用ribbon的时候就必须要配置eureka, 也就是说需要把自己托管给eureka,ribbon是客户端层面的负载均衡,那么ribbon是配置在consumer中还是provider中? 必须要配置在consumer上 /* 将 20190927-springcloud-consumer-6081 项目的jar包放在父级目录上,做jar包管理。 我的放在 新建项目 20190927-springcloud-consumer-ribbon-6082 做负载均衡。和另一项目一样,需要创建启动类,和配置文件。 */ 1.3 添加jar包 添加eureka的client端,ribbon的jar包org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-netflix-ribbon
20190927-springcloud-consumer-management 父级jar包展示。
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>20190927-springcloud-parentartifactId> <groupId>com.aaagroupId> <version>1.0-SNAPSHOTversion> parent> <modelVersion>4.0.0modelVersion> <artifactId>20190927-springcloud-consumer-managementartifactId> <packaging>pompackaging> <modules> <module>20190927-springcloud-consumer-6081module> <module>20190927-springcloud-consumer-ribbon-6082module> modules> <dependencies> <dependency> <groupId>com.aaagroupId> <artifactId>20190927-springcloud-modelartifactId> <version>1.0-SNAPSHOTversion> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> dependencies> project>
20190927-springcloud-consumer-ribbon-6082 jar包依赖, 的配置文件,开启轮循,controller层。
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>20190927-springcloud-consumer-managementartifactId> <groupId>com.aaagroupId> <version>1.0-SNAPSHOTversion> parent> <modelVersion>4.0.0modelVersion> <groupId>com.aaagroupId> <artifactId>20190927-springcloud-consumer-ribbon-6082artifactId> <dependencies> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-ribbonartifactId> dependency> <dependency> <groupId>com.aaagroupId> <artifactId>20190927-springcloud-modelartifactId> <version>1.0-SNAPSHOTversion> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> dependencies> project>
1.配置文件
server.port=6082
server.servlet.context-path=/
#三、 设置eureka 不要将自己注册到注册中心里面。
eureka.client.register-with-eureka=false
# ribbon 负载均衡的配置,需要将自己分别在三个 eureka上去注册。
eureka.client.service-url.defaultZone=http://eureka01:7081/eureka,http://eureka02:7082/eureka,http://eureka03:7083/eureka
2.开启轮循
package com.aaa.zxf.config; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class ConfigRest { @Bean @LoadBalanced //开启负载均衡 轮循的模式 public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
3.controller层
package com.aaa.zxf.controller; import com.aaa.zxf.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController public class UserController { // private static final String URL = "http://USER-PROVIDER"; private static final String URL ="http://USER-PROVIDER"; @Autowired private RestTemplate restTemplate; @RequestMapping("/userAll") public ListselectAllUser(){ return restTemplate.getForObject(URL+"/userAll",List.class); } }
三、provider 负载均衡的配置。
1.4 真正的负载均衡 需要创建provider-8082和8083项目 1.5 进行配置 8081,8082,8083三个项目中的所有的controller都必须要一致(@RequestMapping("/userAll"))
8081,8082,8083项目的application.properties配置文件必须要更改
【eureka的实例id:因为eureka的实例id是不能重复的,所以必须更改】 eureka.instance.instance-id=user-provider-8082
spring.application.name不能改变,也就是说8081,8082,8083三个项目的application.name都必须要一样 1.6 启动顺序 首先启动三台eureka(7081,7082,7083) 再启动三台provider(8081,8082,8083) 最后启动consumer(6082)
provider的配置文件,修改id 其他的都一样。从8081复制即可。
#四
#在eureka中配置实例, 就是eureka的status下显示的名字
# 不可以重复!!! 等同于mysql中表的id。
eureka.instance.instance-id=user-provider-8082
#四
#在eureka中配置实例, 就是eureka的status下显示的名字
# 不可以重复!!! 等同于mysql中表的id。
eureka.instance.instance-id=user-provider-8083
四、负载均衡的源码分析?
1.7 ribbon的负载均衡算法 如果不指定ribbon的算法,则ribbon使用的是轮询!!! 如何指定ribbon的算法? 1.8 负载均衡源码解析 IRule:接口 ILoadBalancer:接口 AbstractLoadBalancer(抽象类) --> 实现了ILoadBalancer接口 AbstractLoadBalancerRule(抽象类) --> 实现了IRule接口 有一个属性:ILoadBalance-->getter/setter方法 RandomRule(最终所实现随机算法的地方)--->继承了AbstractLoadBalancerRule -->实现了IRule接口 RoundRobbinRule--->继承了AbstractLoadBalancerRule -->实现了IRule接口 IRule: public Server choose(Object key):选择出一个服务-->提供给consumer使用 public void setLoadBalancer(ILoadBalancer lb):ILoadBalancer-->实现类(AbstractLoadBalancer) public ILoadBalancer getLoadBalancer() ILoadBalancer: !!其实就是和每一个服务打交道!! getServer(Object key):所有的获取服务都必须要通过key来进行获取 虽然已经实现了ribbon算法自定义配置,但是ribbon框架是不知道的,但是自己已经实现了负载均衡算法,需要告诉ribbon,只需要在主启动类上添加注解@RibbonClient(name,configuration) name:通过eureka的Application的值来映射到真正的provider(8081,8082,8083) name = "USER-PROVIDER" configuration:指向的是自己自定义的算法配置类 @Configuration public class RibbonRuleMine { @Bean public IRule randoms() { return new RandomRule(); } }
算法?
package com.aaa.lee.springcloud.config; import com.netflix.loadbalancer.ILoadBalancer; import java.util.concurrent.ThreadLocalRandom; public class Test { // 随机算法 public Server choose(ILoadBalancer lb, Object key) { // 如果负载均衡器为null的时候,直接返回(其实就是标识了该consumer开启了负载均衡@LoadBalance) if (lb == null) { return null; } // 创建Server对象,没有初始化 Server server = null; // 循环进入条件是server为null-->也就是说第一次一定会进入循环 while (server == null) { // Thread.interrupted():标识线程所处于的状态,如果线程的状态为存在,返回值就是true,如果不存在返回值就是false // 什么时候线程会断开??-->网络故障,网络延迟,各种阻塞 if (Thread.interrupted()) { return null; } // getReachableServers():获取当前正在存活的服务(一共有20个服务,其中有4个宕机,存活的服务就是16个) // getAllServers():获取所有的服务个数(包含宕机的) ListupList = lb.getReachableServers(); List allList = lb.getAllServers(); // allList.size() == 0 说明没有服务,直接return null int serverCount = allList.size(); if (serverCount == 0) { return null; } // serverCount:就是所有服务的个数(包含宕机的服务) // 假设:30台 int index = chooseRandomInt(serverCount); // upList:存活的服务 server = upList.get(index); if (server == null) { /** * Thread.yield():线程谦让 * 什么叫多线程之间的谦让呢? * 当并发出现的时候,线程和线程是互不相让的(线程阻塞),其中一个线程就会出现问题,就会处于等待状态(线程阻塞会更严重) * 当调用yield()方法的时候,出现问题的线程就开始谦让,这个线程会再次被唤醒处于就绪状态,需要重新抢夺客户端所发送过来的并发 */ 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; } protected int chooseRandomInt(int serverCount) { // Math.random.nextInt(999):随机获取到从1到999的随机数 // ThreadLocalRandom.current().nextInt(30):就是获取的是随机数(从1到30进行随机数) // /* * Random和ThreadLocalRandom都是随机数,那么为什么使用ThreadLocalRandom不使用Random? * !!Random就是线程安全的!! * ThreadLocalRandom:是jdk7的新特性!! * 虽然random是线程安全的,但是在多线程的情况,Random效率就会非常低 * random在处理多线程的情况下是线程安全的,会受到线程保护,就会降低效率(队列) * Random所随机出来的数字是可以预测的(Random有规律) * * TreadLocalRandom官方给出的解释,当项目期望在多线程中运行的时候,如果使用到了随机数,可以直接使用该类,因为该 * 类就是针对于并发所开发的,和Random相比TreadLocalRandom可以减少线程之间的竞争,性能可以达到最优! * * */ return ThreadLocalRandom.current().nextInt(serverCount); } }
五、图解