【SpringCloud】(一文通)多机部署, 负载均衡-LoadBalance

一. 负载均衡介绍

1.1 问题描述

观察上个文章远程调用的代码

List<ServiceInstance> instances = discoveryClient.getInstances("productservice");
//服务可能有多个, 获取第⼀个
EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);
  1. 根据应用名称获取了服务实例列表
  2. 从列表中选择了⼀个服务实例

思考: 如果⼀个服务对应多个实例呢? 流量是否可以合理的分配到多个实例呢?

现象观察:

我们再启动 2 个 product-service 实例

选中要启动的服务, 右键选择 Copy Configuration…

在这里插入图片描述

在弹出的框中, 选择 Modify options -> Add VM options

在这里插入图片描述

添加 VM options : -Dserver.port=9091

9091 为服务启动的端⼝号, 根据自己的情况进行修改

在这里插入图片描述

现在 IDEA 的 Service 窗口就会多出来⼀个启动配置, 右键启动服务就可以

在这里插入图片描述

同样的操作, 再启动1个实例, 共启动3个服务

在这里插入图片描述

观察 Eureka, 可以看到 product-service下有三个实例:

在这里插入图片描述

访问结果:

访问: http://127.0.0.1:8080/order/1

11:46:05.684+08:00 INFO 23128 --- [nio-8080-exec-1] 
com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:06.435+08:00 INFO 23128 --- [nio-8080-exec-2] 
com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:07.081+08:00 INFO 23128 --- [nio-8080-exec-3] 
com.bite.order.service.OrderService : LUCF:product-service:9090

通过日志可以观察到, 请求多次访问, 都是同⼀台机器.

这肯定不是我们想要的结果, 我们启动多个实例, 是希望可以分担其他机器的负荷, 那么如何实现呢?

解决方案:

我们可以对上述代码进行简单修改:

private static AtomicInteger atomicInteger = new AtomicInteger(1);

private static List<ServiceInstance> instances;

@PostConstruct
public void init(){
	//根据应⽤名称获取服务列表
	instances = discoveryClient.getInstances("product-service");
}

public OrderInfo selectOrderById(Integer orderId) {
	OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
	//String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
	//服务可能有多个, 轮询获取实例
	int index = atomicInteger.getAndIncrement() % instances.size();
	ServiceInstance instance =instances.get(index);
	log.info(instance.getInstanceId());
	//拼接url
	String url = instance.getUri()+"/product/"+ orderInfo.getProductId();
	ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
	orderInfo.setProductInfo(productInfo);
	return orderInfo;
}

观察日志:

12:02:13.245+08:00 INFO 1800 --- [nio-8080-exec-1] 
com.bite.order.service.OrderService : LUCF:product-service:9091
12:02:15.723+08:00 INFO 1800 --- [nio-8080-exec-2] 
com.bite.order.service.OrderService : LUCF:product-service:9090
12:02:16.534+08:00 INFO 1800 --- [nio-8080-exec-3] 
com.bite.order.service.OrderService : LUCF:product-service:9092
12:02:16.864+08:00 INFO 1800 --- [nio-8080-exec-4] 
com.bite.order.service.OrderService : LUCF:product-service:9091
12:02:17.078+08:00 INFO 1800 --- [nio-8080-exec-5] 
com.bite.order.service.OrderService : LUCF:product-service:9090
12:02:17.260+08:00 INFO 1800 --- [nio-8080-exec-6] 
com.bite.order.service.OrderService : LUCF:product-service:9092
12:02:17.431+08:00 INFO 1800 --- [nio-8080-exec-7] 
com.bite.order.service.OrderService : LUCF:product-service:9091

通过日志可以看到, 请求被均衡的分配在了不同的实例上, 这就是负载均衡.

1.2 什么是负载均衡

负载均衡(Load Balance,简称 LB) , 是⾼并发, 高可用系统必不可少的关键组件

当服务流量增大时, 通常会采用增加机器的方式进行扩容, 负载均衡就是用来在多个机器或者其他资源中, 按照⼀定的规则合理分配负载

1.3 负载均衡的⼀些实现

上面的例子中, 我们只是简单的对实例进行了轮询, 但真实的业务场景会更加复杂. 比如根据机器的配置进行负载分配, 配置高的分配的流量高, 配置低的分配流量低等.

服务多机部署时, 开发⼈员都需要考虑负载均衡的实现, 所以也出现了⼀些负载均衡器, 来帮助我们实现负载均衡.

负载均衡分为服务端负载均衡和客户端负载均衡.

服务端负载均衡

在服务端进行负载均衡的算法分配.比较有名的服务端负载均衡器是 Nginx. 请求先到达 Nginx 负载均衡器, 然后通过负载均衡算法, 在多个服务器之间选择⼀个进行访问.

在这里插入图片描述

客户端负载均衡

在客户端进行负载均衡的算法分配

把负载均衡的功能以库的⽅式集成到客⼾端, ⽽不再是由⼀台指定的负载均衡设备集中提供.

比如Spring Cloud的Ribbon, 请求发送到客户端, 客户端从注册中心(比如Eureka)获取服务列表, 在发送请求前通过负载均衡算法选择⼀个服务器,然后进行访问.

Ribbon是Spring Cloud早期的默认实现, 由于不维护了, 所以最新版本的Spring Cloud负载均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方维护)

在这里插入图片描述

客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置

二. Spring Cloud LoadBalancer

2.1 快速上手

SpringCloud 从 2020.0.1 版本开始, 移除了Ribbon 组件,使⽤Spring Cloud LoadBalancer 组件来代替 Ribbon 实现客户端负载均衡

2.1.1 使用 Spring Cloud LoadBalancer 实现负载均衡

  1. RestTemplate 这个Bean 添加 @LoadBalanced 注解就可以
@Configuration
public class BeanConfig {
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate(){
		return new RestTemplate();
	}
}
  1. 修改IP端口号为服务名称
public OrderInfo selectOrderById(Integer orderId) {
	OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
	//String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
	String url = "http://product-service/product/"+ orderInfo.getProductId();
	ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
	orderInfo.setProductInfo(productInfo);
	return orderInfo;
}

2.1.2 启动多个product-service实例

按照上⼀文章的方式, 启动多个product-service实例

在这里插入图片描述

2.1.3 测试负载均衡

连续多次发起请求: http://127.0.0.1:8080/order/1

观察 product-service 的日志, 会发现请求被分配到这3个实例上了

在这里插入图片描述

2.2 负载均衡策略

负载均衡策略是⼀种思想, 无论是哪种负载均衡器, 它们的负载均衡策略都是相似的. Spring Cloud LoadBalancer 仅支持两种负载均衡策略: 轮询策略 和 随机策略

  1. 轮询(Round Robin): 轮询策略是指服务器轮流处理用户的请求. 这是⼀种实现最简单, 也最常用的策略. 生活中也有类似的场景, 比如学校轮流值日, 或者轮流打扫卫生.
  2. 随机选择(Random): 随机选择策略是指随机选择⼀个后端服务器来处理新的请求

自定义负载均衡策略

Spring Cloud LoadBalancer 默认负载均衡策略是 轮询策略, 实现是 RoundRobinLoadBalancer, 如果服务的消费者如果想采用随机的负载均衡策略, 也非常简单.

  1. 定义随机算法对象, 通过 @Bean 将其加载到 Spring 容器中

此处使用 Spring Cloud LoadBalancer 提供的 RandomLoadBalancer

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class LoadBalancerConfig {
	@Bean
	ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		System.out.println("=============="+name);
		return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}
}

注意: 该类需要满足:

  1. 不用 @Configuration 注释
  2. 在组件扫描范围内
  1. 使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解

在 RestTemplate 配置类上方, 使用 @LoadBalancerClient 或@LoadBalancerClients 注解, 可以对不同的服务提供方配置不同的客户端负载均衡算法策略.

由于此项目中只有⼀个服务提供者, 所以使用@LoadBalancerClient

@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)
@Configuration
public class BeanConfig {
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate(){
		return new RestTemplate();
	}
}

@LoadBalancerClient 注解说明

  1. name: 该负载均衡策略对哪个服务生效(服务提供方)
  2. configuration : 该负载均衡策略 用哪个负载均衡策略实现

2.3 LoadBalancer 原理

LoadBalancer 的实现, 主要是 LoadBalancerInterceptor , 这个类会对 RestTemplate 的请求进行拦截, 然后从 Eureka 根据服务 id 获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id

来看看源码实现:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
 //...
 public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
	 URI originalUri = request.getURI();
	 String serviceName = originalUri.getHost();
	 Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
	 return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
 	}
}

可以看到这里的 intercept 方法, 拦截了用户的 HttpRequest 请求,然后做了几件事:

  1. request.getURI() 从请求中获取 uri, 也就是 http://product-
  2. service/product/1001 originalUri.getHost() 从 uri 中获取路径的主机名, 也就是服务 id, product-service
  3. loadBalancer.execute 根据服务 id, 进行负载均衡, 并处理请求

点进去继续跟踪

public class BlockingLoadBalancerClient implements LoadBalancerClient {
 
	 public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
		 String hint = this.getHint(serviceId);
		 LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter(request,this.buildRequestContext(request, hint));
		 Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);supportedLifecycleProcessors.forEach((lifecycle) -> {
		 	lifecycle.onStart(lbRequest);
	 	});
		 //根据serviceId,和负载均衡策略, 选择处理的服务
		 ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);
		if (serviceInstance == null) {
			supportedLifecycleProcessors.forEach((lifecycle) -> {
				lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));
		});
		throw new IllegalStateException("No instances available for " + serviceId);
		} else {
			return this.execute(serviceId, serviceInstance, lbRequest);
		}
	}
	
	
		/**
		* 根据serviceId,和负载均衡策略, 选择处理的服务
		*
		*/
	public <T> ServiceInstance choose(String serviceId, Request<T> request) {
	//获取负载均衡器
		ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);
		if (loadBalancer == null) {
			return null;
		} else {
		//根据负载均衡算法, 在列表中选择⼀个服务实例
			Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();
			return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();
		}
	}
}

三. 服务部署(Linux)

接下来我们把服务部署在Linux系统上

安装 mysql:参考后面文章

数据初始化

参考前面初始化数据-数据准备SQL

修改配置文件

修改配置文件中, 数据库的密码

3.2 服务构建打包

采用 Maven 打包, 需要对 3 个服务分别打包:

eureka-server, order-service, product-service

  1. 打包方式和SpringBoot项目⼀致, 依次对三个项目打包即可

在这里插入图片描述

3.3 启动服务

  1. 上传 Jar 包到云服务器

第⼀次上传需要安装 lrzsz
 
apt install lrzsz

直接拖动文件到 xshell 窗口, 上传成功.

  1. 启动服务
#后台启动eureka-server, 并设置输出⽇志到logs/eureka.log
nohup java -jar eureka-server.jar >logs/eureka.log &

#后台启动order-service, 并设置输出⽇志到logs/order.log
nohup java -jar order-service.jar >logs/order.log &

#后台启动product-service, 并设置输出⽇志到logs/order.log
8 nohup java -jar product-service.jar >logs/product-9090.log &

再多启动两台 product-service 实例

#启动实例, 指定端⼝号为9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &

#启动实例, 指定端⼝号为9092
nohup java -jar product-service.jar --server.port=9092 >logs/product-9092.log &

3.4 开放端口号

根据自己项目设置的情况, 在云服务器上开放对应的端口号,不同的服务器厂商, 开放端口号的入口不同, 需要自行找⼀找或者咨询对应的客服人员.

以腾讯云服务器举例:

  1. 进入防火墙管理页面

在这里插入图片描述

  1. 添加规则

在这里插入图片描述

端口号写需要开放的端口号, 多个端口号以逗号分割.

3.5 测试

  1. 访问 Eureka Server:

在这里插入图片描述

  1. 访问订单服务接口: http://110.41.51.65:8080/order/1

在这里插入图片描述

远程调用成功

天猫商城是一个基于SSM框架的综合性B2C电商平台,需求设计主要参考天猫商城的购物流程:用户从注册开始,到完成登录,浏览商品,加入购物车,进行下单,确认收货,评价等一系列操作。 作为模拟天猫商城系统的核心组成部分之一,采用SSM框架的天猫数据管理后台包含商品管理,订单管理,类别管理,用户管理和交易额统计等模块,实现了对整个商城的一站式管理和维护。本课程是一门专业的Java微服架构开发实战课程,主要讲解了当下流行的SpringBoot框架、SpringCloud架构以及与第三方技术整合开发实战内容。过本课程的学习,能够理解并掌握SpringBoot的基础知识,同时能够掌握SpringBoot与常用的第三方技术整合实现实际开发中的业务需求,包括实现Web开发、数据访问、缓存管理、安全管理、消息服务、任务管理等;了解并掌握SpringCloud微服务架构的基础知识及相关组件的应用,掌握微服务架构在企业级开发的实践,建立起微服架构思想。项目技术栈:采用SpringBoot简化商城系统的初始搭建以及开发过程采用SpringMVC+Spring+IBatis完成项目的整合采用Mysql作为数据库存储,Druid配置数据库连接池采用SpringCloud+Netflix 微服务技术栈的实战开发使用Redis完成缓存的数据存储,搭建Redis搭建主从、哨兵、集群应用,保证Redis的高可用使用ElasticSearch全文检索系统进行商品数据搜索,使用ElasticSearch搭建搜索服务的高可用使用Ngnix实现页面动静分离与负载均衡的配置采用FastDFS文件储存系统文件存储,完成广告图片、商品图片的上传和存储系统使用采用CAS+shiro单点登录系统实现用户认证使用ECharts根据后台查询数据生成图表使用POI实现了商城盈利状况的Excel表格导出。商品的详情页使用Thymeleaf完成页面静态化,减少页面数据展示延迟项目中使用SpringBoot下的Aop + 自定义注解完成用户行为记录,日志采集后台管理系统使用Shiro实现登录验证和权限管理(超级管理员、管理员、产品编辑员)项目整合微信完成订单的支付使用Redission完成分布式锁,生成订单的编号使用SpringCloud Alibaba Seat完成下订单模块的分布式事务(新增订单表,库存减少,库存超卖设计)使用RabbitMQ 做消息队列,完成订单未支付自动取消和模块直接的解耦合使用Quartz任务调度,完成缓存的定时刷新,保证缓存的一致性使用本地消息表机制完成消息然队列RabbitMQ消息可靠性传输订单支付模块使用微信扫码支付,并设置订单超时自动取消过Jquery实现前端校验,过基于Hibernate的Valida注解实现后端的校验功能使用Base64编码对Json数据传输进行编码和解码项目使用RESTful设计风格实现资源的访问,实现前后端分离项目使用聚合数据第三方短信平台完成用户的登陆功能项目使用SpringBoot整合JavaMail完成邮件的发送项目使用SpringBoot整合Swagger2生成接口文档使用PostMan完成接口的测试项目的测试:SpringTest、dbunit、EasyMock使用Docker 进行应用的自动化打包和发布、自动化测试和持续集成、部署和调整其他应用使用 PowerDesigner,完成数据库的建模项目使用禅道进行BUG管理环境采用Maven实施多模块项目构建,采用Git进行项目版本管理 架构解读:  项目部分截图:              讲义部分截图:          
Spring Cloud中常用的负载均衡组件有Ribbon和OpenFegin。在Spring Cloud H版及之前的版本中,主要使用的是Ribbon和OpenFegin作为负载均衡的方案。然而,在Spring Cloud 2020版本之后,官方宣布剔除了除了eureka-server和eureka-client以外的所有Netflix组件,并推荐使用Spring Cloud Loadbalancer替代。尽管如此,Ribbon和OpenFegin仍然是目前主流的负载均衡解决方案。因此,在介绍负载均衡组件时,常会涉及到Ribbon和OpenFegin。 Ribbon作为负载均衡器在分布式网络中扮演着非常重要的角色。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [一文详解 Spring Cloud 负载均衡!](https://blog.csdn.net/m0_71777195/article/details/128913837)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SpringCloud负载均衡详解](https://blog.csdn.net/weixin_45717638/article/details/120111957)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Redamancy丶早晚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值