📢📢📢📣📣📣
哈喽!大家好,我是【一心同学】,专注app软件定制开发一位上进心十足的【Java领域博主】!😜😜😜
✨【一心同学】的写作风格:喜欢用【通俗易懂】专注app软件定制开发的文笔去讲解每一个知识点,专注app软件定制开发而不喜欢用【高大上】专注app软件定制开发的官方陈述。
✨【一心同学】博客的领域是【专注app软件定制开发面向后端技术】的学习,专注app软件定制开发未来会持续更新更多的【后端技术】以及【学习心得】。
✨如果有对【后端技术】感兴趣的【小可爱】,欢迎关注【一心同学】💞💞💞
❤️❤️❤️专注app软件定制开发感谢各位大可爱小可爱!❤️❤️❤️
目录
一、专注app软件定制开发什么是负载均衡?
负载均衡(Load Balance),专注app软件定制开发意思是将负载(工作任务,访问请求)进行平衡、专注app软件定制开发分摊到多个操作单元(服务器,组件)专注app软件定制开发上进行执行。是解决高性能,单点故障(高可用),扩展性(水平伸缩)的终极解决方案。
例子:
在早高峰乘地铁时候,紧挨小区的地铁口人特别多,一般会有限流,还会有个地铁工作人员Y用那个大喇叭在喊“着急的人员请走B口,B口人少车空”。。。
而这个地铁工作人员Y就是负责负载均衡的。为了提升网站的各方面能力,我们一般会把多台机器组成一个集群对外提供服务。然而,我们的网站对外提供的访问入口都是一个的,比如www.taobao.com。那么当用户在浏览器输入www.taobao.com的时候如何将用户的请求分发到集群中不同的机器上呢,这就是负载均衡在做的事情。
二、负载均衡分类
注:LB,即负载均衡 (LoadBalancer)
2.1 集中式LB
即在服务的提供方和消费方之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求通过某种策略转发至服务的提供方!
2.2 进程式 LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
Ribbon 就属于进程式LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址!
三、Spring Cloud 是什么?
(1)Ribbon负责实现客户端的负载均衡,负载均衡器提供很多对http和tcp的行为控制。
(2)Ribbon默认提供了很多负载均衡算法,如:轮询、随机等,也可以实现自定义的负载均衡算法。
(3)在Spring cloud中,当Ribbon与结合使用时,Ribbon可以自动的从Eureka Server获取服务列表,基于,进行服务调用。
(4)在Spring Cloud构建的微服务系统中,Ribbon作为客户端负载均衡器,有两种使用方式,第一种是和RestTemplate相结合,第二种是和Feign相结合。
四、Ribbon 的负载均衡算法
(1)RoundRobinRule:轮询策略,默认策略。
(2)RandomRule,随机,使用Random对象从服务列表中随机选择一个服务。
(3)RetryRule,轮询 + 重试。
(4)WeightedResponseTimeRule:优先选择响应时间快,此策略会根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越重、被选中的概率越高。此类有个DynamicServerWeightTask的定时任务,默认情况下每隔30秒会计算一次各个服务实例的权重。刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换回来。
(5)AvailabilityFilteringRul:,可用性过滤,会先过滤掉以下服务:由于多次访问故障而断路器处于打开的服务、并发的连接数量超过阈值,然后对剩余的服务列表按照RoundRobinRule策略进行访问。
(6)BestAvailableRule:优先选择并发请求最小的,刚启动时吗,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,才会切换回来。
(7)ZoneAvoidanceRule:可以实现避免可能访问失效的区域(zone)
五、环境准备-搭建Eureka
1、建立Maven父工程
编写pom.xml
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-alibaba-dependencies</artifactId>
- <version>0.2.0.RELEASE</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
-
-
- <!--springCloud的依赖-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>Hoxton.SR12</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- <!--SpringBoot-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-dependencies</artifactId>
- <version>2.3.12.RELEASE</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
2、建立以下子工程
注:同样也是Maven。
3、配置springcloud-eureka-7001
(1)目录如下
(2)导入依赖
- <dependencies>
-
- <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
- <!--导入Eureka Server依赖-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka-server</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
- <!--热部署工具-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- </dependency>
-
- </dependencies>
(3)编写配置文件
application.yml:
- server:
- port: 7001
- # Eureka配置
- eureka:
- instance:
- # Eureka服务端的实例名字
- hostname: localhost
- client:
- # 表示是否向 Eureka 注册中心注册自己(这个模块本身是服务器,所以不需要)
- register-with-eureka: false
- # fetch-registry如果为false,则表示自己为注册中心,客户端的化为 ture
- fetch-registry: false
- # Eureka监控页面~
- service-url:
- defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
-
(4)编写启动器
注意:要在主启动器上方添加 @EnableEurekaServer表示 服务端的启动类,可以接受别人注册进来。
- package com.yixin.springcloud;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
-
- @SpringBootApplication
- @EnableEurekaServer
- public class EurekaServer_7001 {
- public static void main(String[] args) {
- SpringApplication.run(EurekaServer_7001.class,args);
- }
- }
4、配置springcloud-provider-blog-8001
(1)建立目录如下
(2)导入依赖
- <!--导包~-->
- <dependencies>
-
- <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
- <!--导入Eureka Server依赖-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
-
- <!--Spring Boot-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-test</artifactId>
- <version>2.4.5</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <version>2.4.5</version>
- </dependency>
-
- <!--热部署工具-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- </dependency>
-
- </dependencies>
(3)编写配置文件
application.yml:
- server:
- port: 8001
-
- spring:
- application:
- name: springcloud-provider-blog
-
-
- # Eureka配置:配置服务注册中心地址
- eureka:
- client:
- service-url:
- defaultZone: http://localhost:7001/eureka/
- instance:
- instance-id: springcloud-provider-dept-8001 #修改Eureka上的默认描述信息
-
(4)编写BlogController
注:开发中,我们是需要连接到数据库的,但为了给大家演示清楚Ribbon,这里我们用简单dbsource来表示我们这个微服务对应的数据库。
- package com.yixin.springcloud.controller;
-
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- public class BlogController {
- //表示db01这个数据库
- @Value("db01")
- private String dbsource;
- //注册进来的微服务,获取一些消息
- @GetMapping("/blog/info")
- public String discovery(){
- return dbsource;
- }
- }
(5)编写启动类BlogProvider_8001
- package com.yixin.springcloud;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
- import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
-
- @SpringBootApplication
- @EnableEurekaClient
- public class BlogProvider_8001 {
- public static void main(String[] args) {
- SpringApplication.run(BlogProvider_8001.class,args);
- }
- }
5、编写springcloud-provider-blog-8002和springcloud-provider-blog-8003
(1)导入的依赖和springcloud-provider-blog-8001一样
(2) 配置springcloud-provider-blog-8002
a、application.yml(其实就是改了端口号而已)
- server:
- port: 8002
-
- spring:
- application:
- name: springcloud-provider-blog
-
-
- # Eureka配置:配置服务注册中心地址
- eureka:
- client:
- service-url:
- defaultZone: http://localhost:7001/eureka/
- instance:
- instance-id: springcloud-provider-dept-8002 #修改Eureka上的默认描述信息
-
b、编写BlogController
更改数据库名:db02
- package com.yixin.springcloud.controller;
-
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- public class BlogController {
-
- //表示db02这个数据库
- @Value("db02")
- private String dbsource;
- //注册进来的微服务,获取一些消息
- @GetMapping("/blog/info")
- public String discovery(){
- return dbsource;
- }
- }
c、编写启动类,和springcloud-provider-blog-8001一样
(3) 配置springcloud-provider-blog-8003
a、application.yml(其实就是改了端口号而已)
- server:
- port: 8003
-
- spring:
- application:
- name: springcloud-provider-blog
-
-
- # Eureka配置:配置服务注册中心地址
- eureka:
- client:
- service-url:
- defaultZone: http://localhost:7001/eureka/
- instance:
- instance-id: springcloud-provider-dept-8003 #修改Eureka上的默认描述信息
-
b、BlogController
更改数据库名:db03
- package com.yixin.springcloud.controller;
-
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- public class BlogController {
-
- //表示db03这个数据库
- @Value("db03")
- private String dbsource;
- //注册进来的微服务,获取一些消息
- @GetMapping("/blog/info")
- public String discovery(){
- return dbsource;
- }
- }
c、编写启动类,和springcloud-provider-blog-8001一样。
6、配置springcloud-consumer-blog-80
(1)目录如下
(2)导入依赖
- <!--导包~-->
- <dependencies>
-
- <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
- <!--导入Eureka依赖-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
-
- <!--Spring Boot-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-test</artifactId>
- <version>2.4.5</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <version>2.4.5</version>
- </dependency>
-
- <!--热部署工具-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- </dependency>
-
- </dependencies>
(3)编写ConfigBean
- package com.yixin.springcloud.config;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.client.RestTemplate;
-
- @Configuration
- public class ConfigBean {
-
- @Bean
- public RestTemplate getRestTemplate(){
- return new RestTemplate();
- }
- }
(4)编写BlogConsumerController
- package com.yixin.springcloud.controller;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.client.RestTemplate;
-
- @RestController
- public class BlogConsumerController {
- @Autowired
- private RestTemplate restTemplate;
- private static final String REST_URL_PREFIX="http://localhost:8001";
- @GetMapping("/consumer/blog")
- public String get(){
- return "消费端:"+restTemplate.getForObject(REST_URL_PREFIX +"/blog/info", String.class);
- }
- }
(5)测试
依次启动:
springcloud-eureka-7001
springcloud-provider-blog-8002
springcloud-consumer-blog-80
访问:http://localhost:8000/consumer/blog
至此,Eureka就搭建好了!
六、集成Ribbon
6.1 搭建Ribbon
由于Ribbon属于进程式 LB(Load Balance),即将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,所以我们只需要在消费方这边进行配置即可。
Eureka搭建好了,我们集成Ribbon非常简单,只需三步:
配置springcloud-consumer-blog-80
(1)添加依赖:
- <!--Ribbon-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-ribbon</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
(2)在配置类增加注解@LoadBalanced
- package com.yixin.springcloud.config;
-
- import org.springframework.cloud.client.loadbalancer.LoadBalanced;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.client.RestTemplate;
-
- @Configuration
- public class ConfigBean {
-
- @LoadBalanced //配置负载均衡实现RestTemplate
- @Bean
- public RestTemplate getRestTemplate(){
- return new RestTemplate();
- }
- }
(3)修改BlogConsumerController获取路径
- package com.yixin.springcloud.controller;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.client.RestTemplate;
-
- @RestController
- public class BlogConsumerController {
-
- @Autowired
- private RestTemplate restTemplate;
-
- //private static final String REST_URL_PREFIX="http://localhost:8001";
-
- private static final String REST_URL_PREFIX="http://SPRINGCLOUD-PROVIDER-BLOG";
-
- @GetMapping("/consumer/blog")
- public String get(){
- return "消费端:"+restTemplate.getForObject(REST_URL_PREFIX +"/blog/info", String.class);
- }
- }
注意:这里的SPRINGCLOUD-PROVIDER-BLOG指的就是我们服务注册中的服务名。
所以为了搭建服务产生方集群,我们刚刚在搭建springcloud-provider-blog-8001、springcloud-provider-blog-8002、springcloud-provider-blog-8003的时候,我们就已经将其服务名全部设置为一样的了。
至此,Ribbon就搭建好了,Ribbon的默认负载均衡算法是轮询算法,也就是说,请求结束后都会向下一个服务端发送请求,例如 我们的有服务生产方A8001,服务端生产方B8002,服务端生产方C8003,那么消费端请求三次,依次的顺序是A,B,C。
我们来进行测试下:
依次启动
springcloud-eureka-7001
springcloud-provider-blog-8001
springcloud-provider-blog-8002
springcloud-provider-blog-8003
springcloud-consumer-blog-80
访问:http://localhost:7001/
可以发现,我们三个服务生产方已经成功绑定了。
接着重头戏来了!
进行测试:
访问:http://localhost:8000/consumer/blog
访问第一次:
访问第二次:
访问第三次:
成功啦!!!
6.2 切换负载均衡的规则
修改springcloud-consumer-blog-80下的ConfigBean
- package com.yixin.springcloud.config;
-
- import com.netflix.loadbalancer.IRule;
- import com.netflix.loadbalancer.RandomRule;
- import org.springframework.cloud.client.loadbalancer.LoadBalanced;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.client.RestTemplate;
-
- @Configuration
- public class ConfigBean {
-
- @LoadBalanced //配置负载均衡实现RestTemplate
- @Bean
- public RestTemplate getRestTemplate() {
- return new RestTemplate();
- }
-
- /**
- * IRule:
- * RoundRobinRule 轮询策略
- * RandomRule 随机策略
- * AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
- * RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
- */
- @Bean
- public IRule myRule() {
- return new RandomRule();//使用随机策略
- //return new RoundRobinRule();//使用轮询策略
- //return new AvailabilityFilteringRule();//会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
- //return new RetryRule();//会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
- }
- }
测试:
重启springcloud-consumer-blog-80
访问:http://localhost:8000/consumer/blog
访问第一次:
访问第二次:
访问第三次:
可以发现它是随机的!
6.3 自定义负载均衡的规则
在myRule包下自定义一个配置类MyRule.java。
注意:myRule包不要和主启动类所在的包同级。
(1)编写自定义规则MyRule
规则:每个服务访问5次,换下一个服务。
- package com.yixin.myRule;
-
- import com.netflix.client.config.IClientConfig;
- import com.netflix.loadbalancer.AbstractLoadBalancerRule;
- import com.netflix.loadbalancer.ILoadBalancer;
- import com.netflix.loadbalancer.Server;
- import org.springframework.context.annotation.Configuration;
-
- import java.util.List;
- import java.util.concurrent.ThreadLocalRandom;
-
- @Configuration
- public class MyRule extends AbstractLoadBalancerRule {
- // 每个服务访问 5 次,换下一个服务
- // total=0 => 默认 0,如果等于 5 ,指向下一个服务节点
- // index=0 => 默认 0,如果 total 等于 5 ,index+1
- private int total = 0; //被调用的次数
- private int currentIndex = 0; //当前谁在提供服务
-
- 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 = chooseRandomInt(serverCount);//在区间内随机获得一个地址
-
- // server = upList.get(index);//从存活的列表中获得
- //=================================
- total++;
- if (total > 5) {
- total = 0;
- currentIndex++;
- }
- if (currentIndex >= upList.size()) currentIndex = 0;
- server = upList.get(currentIndex);
- //=================================
- 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;
- }
-
- protected int chooseRandomInt(int serverCount) {
- return ThreadLocalRandom.current().nextInt(serverCount);
- }
-
- @Override
- public Server choose(Object key) {
- return choose(getLoadBalancer(), key);
- }
-
- @Override
- public void initWithNiwsConfig(IClientConfig clientConfig) {
- // TODO Auto-generated method stub
- }
- }
(2)编写配置类MyRuleConf
作用:将我们写的这个规则注入到Spring中。
- package com.yixin.myRule;
-
- import com.netflix.loadbalancer.IRule;
- import com.netflix.loadbalancer.RandomRule;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- @Configuration
- public class MyRuleConf {
-
- @Bean
- public IRule myRule(){
- return new MyRule();//自定义规则
- }
- }
(3)启动类增加注解@RibbonClient
- package com.yixin.springcloud;
-
- import com.yixin.myRule.MyRuleConf;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
- import org.springframework.cloud.netflix.ribbon.RibbonClient;
-
- @SpringBootApplication
- @EnableEurekaClient
- //在微服务启动的时候就能加载自定义的Ribbon类(自定义的规则会覆盖原有默认的规则)
- @RibbonClient(name = "SPRINGCLOUD-PROVIDER-BLOG",configuration = MyRuleConf.class)//开启负载均衡,并指定自定义的规则
- public class BlogConsumer_80 {
-
- public static void main(String[] args) {
- SpringApplication.run(BlogConsumer_80.class,args);
- }
-
- }
(4)测试
重启 springcloud-consumer-blog-80
访问:http://localhost:8000/consumer/blog
访问1-5次:
访问第6-10次:
访问第11-15次:
自定义规则测试成功!!!
小结
以上就是【一心同学】对基于Spring Cloud的负载均衡Ribbon知识点和实操的讲解,实现负载均衡可以将我们的压力分摊到多个操作单元,大家可以重新回头去看上面【一心同学】对Ribbon的概念讲解,相信对Ribbon的理解马上就悟了!
如果这篇【文章】有帮助到你,希望可以给【一心同学】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【后端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【一心同学】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!