微服务实战系列之SpringCloud Alibaba:
- 微服务实战系列之SpringCloud Alibaba学习(一)
- 微服务实战系列之SpringCloud Alibaba学习(二)
在微服务实战系列之SpringCloud Alibaba学习(一)中,学习了搭建三大微服务并完成交互开发与测试。
但在微服务实现过程中的会遇到一些问题:
- 就是将用户微服务所在的IP和端口,以及商品微服务所在的IP和端口硬编码到订单微服务的代码中了。这样的做法存在着非常多的问题。这就需要用到
服务治理
,来实现微服务之间的动态注册与发现。相应的就引出了使用Nacos
来实现服务的注册与发现功能。可以参考:SpringCloud架构之Nacos配置注册中心。 - 但是还存在一个很明显的问题,那就是如果用户微服务和商品微服务在服务器上部署多份的话,之前的程序无法实现服务调用的
负载均衡
功能。这就需要使用Ribbon
来实现服务调用的负载均衡功能。
文章目录
1. Nacos实现服务治理
1.1 硬编码的问题
如果将用户微服务和商品微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题,其中,最明显的问题有三个,如下所示。
(1)如果用户微服务和商品微服务的IP地址或者端口号发生了变化,则订单微服务将变得不可用,需要对同步修改订单微服务中调用用户微服务和商品微服务的IP地址和端口号。
(2)如果系统中提供了多个用户微服务和商品微服务,则无法实现微服务的负载均衡功能。
(3)如果系统需要支持更高的并发,需要部署更多的用户微服务和商品微服务以及订单微服务,如果将用户微服务和商品微服务的IP地址和端口硬编码到订单微服务,则后续的维护会变得异常复杂。
所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现。
1.2 服务治理
如果系统采用了微服务的架构模式,随着微服务数量的不断增多,服务之间的调用关系会变得纵横交错,以纯人工手动的方式来管理这些微服务以及微服务之间的调用关系是及其复杂的,也是极度不可取的。
所以,需要引入服务治理的功能。服务治理也是在微服务架构模式下的一种最核心和最基本的模块,主要用来实现各个微服务的自动注册与发现。
引入服务治理后,微服务项目总体上可以分为三个大的模块:服务提供者、服务消费者和注册中心,三者的关系如下图所示。
图片来源:冰河技术,下同。
(1)服务提供者会将自身提供的服务注册到注册中心,并向注册中心发送心跳信息来证明自己还存活,其中,心跳信息中就会包含服务提供者自身提供的服务信息。
(2)注册中心会存储服务提供者上报的信息,并通过服务提供者发送的心跳来更新服务提供者最后的存活时间,如果超过一段时间没有收到服务提供者上报的心跳信息,则注册中心会认为服务提供者不可用,会将对应的服务提供者从服务列表中剔除。
(3)服务消费者会向注册中心订阅自身监听的服务,注册中心会保存服务消费者的信息,也会向服务消费者推送服务提供者的信息。
(4)服务消费者从注册中心获取到服务提供者的信息时,会直接调用服务提供者的接口来实现远程调用。
需要注意的是:服务消费者一般会从注册中心中获取到所有服务提供者的信息,根据具体情况实现对具体服务提供者的实例进行访问。
1.3 注册中心
从上面的分析可以看出,微服务实现服务治理的关键就是引入了注册中心,它是微服务架构模式下一个非常重要的组件,主要实现了服务注册与发现,服务配置和服务的健康检测等功能。
1.3.1 服务注册与发现
(1)服务注册:注册中心提供保存服务提供者和服务消费者的相关信息。
(2)服务发现:也可以理解为服务订阅,服务调用者也就是服务消费者,向注册中心订阅服务提供者的信息,注册中心会向服务消费者推送服务提供者的信息。
1.3.2 服务配置
(1)配置订阅:服务的提供者和消费者都可以向注册中心订阅微服务相关的配置信息。
(2)配置下发:注册中心能够将微服务相关的配置信息主动推送给服务的提供者和消费者。
1.3.3 服务健康检测
注册中心会定期检测存储的服务列表中服务提供者的健康状况,例如服务提供者超过一定的时间没有上报心跳信息,则注册中心会认为对应的服务提供者不可用,就会将服务提供者踢出服务列表。
1.3.4 常见的注册中心
能够实现注册中心功能的组件有很多,但是常用的组件大概包含:Zookeeper、Eureka、Consul、Etcd、Nacos等。这里,就给大家简单介绍下这些能够实现注册中心功能的框架或组件。
(1)Zookeeper
接触过分布式或者大数据开发的小伙伴应该都知道,Zookeeper是Apache Hadoop的一个子项目,它是一个分布式服务治理框架,主要用来解决应用开发中遇到的一些数据管理问题,例如:分布式集群管理、元数据管理、分布式配置管理、状态同步和统一命名管理等。在高并发环境下,也可以通过Zookeeper实现分布式锁功能。
(2)Eureka
Eureka是Netflix开源的SpringCloud中支持服务注册与发现的组件,但是后来闭源了。
(3)Consul
Consul 是 HashiCorp 公司推出的开源产品,用于实现分布式系统的服务发现、服务隔离、服务配置,这些功能中的每一个都可以根据需要单独使用,也可以同时使用所有功能。
(4)Etcd
etcd 是一个高度一致的分布式键值存储,它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据。它可以优雅地处理网络分区期间的领导者选举,即使在领导者节点中也可以容忍机器故障。
(5)Nacos
这里,我们重点说下Nacos。Nacos是阿里巴巴开源的一款更易于构建云原生应用的支持动态服务发现、配置管理和服务管理的平台,其提供了一组简单易用的特性集,能够快速实现动态服务发现、服务配置、服务元数据及流量管理,主要如下所示。
服务注册
:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如IP地址、端口等信 息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。服务心跳
:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。服务健康检查
:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)服务发现
:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清 单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地存。服务同步
:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
这里,我们选用的注册中心就是阿里巴巴开源的Nacos。
1.4 搭建Nacos环境
1.4.1 Nacos环境配置与启动
(1)https://github.com/alibaba/nacos/releases 下载Nacos的安装包。
(2)解压Nacos安装包,并在命令行进入到Nacos的bin目录下执行如下命令以单机的方式启动Nacos。
startup.cmd -m standalone
(3)启动Nacos之后,在浏览器中输入链接http://localhost:8848/nacos 来访问Nacos的管理界面,默认的用户名和密码都是Nacos。
输入用户名和密码进入Nacos的管理界面,如下所示。
进入到Nacos的服务管理-服务列表菜单下,如下所示。
可以看到,在Nacos的服务管理-服务列表菜单下还没有任何服务,接下来,我们就对项目的代码进行改造。
1.4.2 集成Nacos注册中心
引入Nacos注册中心时,我们需要对项目的代码进行一定的改造,以便利用Nacos实现服务的注册与发现功能。
改造用户微服务
(1)在用户微服务的pom.xml文件中添加nacos的服务注册与发现依赖,如下所示。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)在用户微服务的resources目录下的application.yml文件中添加Nacos注册中心的服务地址配置,如下所示。
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
(3)在用户微服务的启动类io.binghe.shop#UserStarter上标注@EnableDiscoveryClient注解,如下所示。
/**
* @description 启动用户服的类
*/
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.user.mapper" })
@EnableDiscoveryClient
public class UserStarter {
public static void main(String[] args){
SpringApplication.run(UserStarter.class, args);
}
}
此时,就完成了对用户微服务的代码改造。
(4)启动用户微服务,并刷新Nacos页面,如下所示。
可以看到,用户微服务已经成功注册到Nacos中。
改造其他微服务
我们可以用同样的方式来改造商品微服务和订单微服务的代码,改造好之后,分别启动商品微服务和订单微服务,并再次刷新Nacos的页面,如下所示。
可以看到,用户微服务、商品微服务和订单微服务都已成功注册到Nacos。
实现服务发现
按照整个项目的执行流程,用户执行下单操作时,订单微服务会调用用户微服务的接口获取用户的基本信息,会调用商品微服务的接口获取商品的基本信息。
在订单微服务中校验用户的合法性和校验商品库存是否充足,如果用户合法并且商品库存充足,就会向订单数据表中记录订单信息并调用商品微服务的接口来扣减商品的库存。
用户微服务和商品微服务作为服务的提供者,而订单微服务作为服务的消费者,如果要实现服务的发现功能,我们还需要对订单微服务的代码进行改造。将订单微服务中硬编码的用户微服务和商品微服务的IP地址和端口号修改成从Nacos中获取。
为了让小伙伴们能够更好的对比修改前和修改后的代码,这里,并没有在订单微服务的 io.binghe.shop.order.service.impl#OrderServiceImpl 类上直接修改,还是将其重命名为 io.binghe.shop.order.service.impl.OrderServiceV1Impl 类,同时,再次将其复制一份并命名为io.binghe.shop.order.service.impl.OrderServiceV2Impl类,在后续的开发过程中,如果涉及到大的代码变动,都会以这种方式进行更新。
注入服务发现类
(1)在io.binghe.shop.order.service.impl.OrderServiceV2Impl 类中首先注入DiscoveryClient类的对象,如下所示。
@Autowired
private DiscoveryClient discoveryClient;
创建动态服务地址方法
在io.binghe.shop.order.service.impl.OrderServiceV2Impl 类中创建一个从Nacos中通过服务名称获取IP和端口号的方法getServiceUrl(),并在getServiceUrl()方法中将IP和端口号拼接成IP:PORT的形式,如下所示。
private String getServiceUrl(String serviceName){
ServiceInstance serviceInstance = discoveryClient.getInstances(serviceName).get(0);
return serviceInstance.getHost() + ":" + serviceInstance.getPort();
}
具体的实现方式就是调用DiscoveryClient对象的getInstances()方法,并传入服务的名称,从Nacos注册中心中获取一个ServiceInstance类型的List集合,从List集合中获取第1个元素,也就是从List集合中获取到一个ServiceInstance对象,从ServiceInstance对象中获取到IP地址和端口号,并将其拼接成IP:PORT的形式。
定义服务提供者名称
在io.binghe.shop.order.service.impl.OrderServiceV2Impl 类中定义两个成员变量userServer和productServer,表示用户微服务和商品微服务的服务名称,并将其分别复制为server-user和server-product。
private String userServer = "server-user";
private String productServer = "server-product";
注意:userServer的值需要与用户微服务下的application.yml文件中的如下配置的值相同。
spring:
application:
name: server-user
productServer的值需要与商品微服务下的application.yml文件中的如下配置的值相同。
spring:
application:
name: server-product
修改提交订单逻辑
在io.binghe.shop.order.service.impl.OrderServiceV2Impl 类的saveOrder()方法中,将硬编码的用户微服务和商品微服务的IP和端口修改成从Nacos注册中心中获取,涉及改动的代码片段如下所示。
(1)添加获取用户微服务与商品微服务的IP和端口号的代码片段,如下所示。
//从Nacos服务中获取用户服务与商品服务的地址
String userUrl = this.getServiceUrl(userServer);
String productUrl = this.getServiceUrl(productServer);
(2)修改使用restTemplate获取用户信息的代码片段,修改前的代码片段如下所示。
User user = restTemplate.getForObject("http://localhost:8060/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
修改后的代码片段如下所示。
User user = restTemplate.getForObject("http://" + userUrl + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
可以看到,订单微服务获取用户微服务信息时,不再是硬编码用户微服务的IP地址和端口号了。
(3)修改使用restTemplate获取商品信息的代码片段,修改前的代码片段如下所示。
Product product = restTemplate.getForObject("http://localhost:8070/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
修改后的代码片段如下所示。
Product product = restTemplate.getForObject("http://" + productUrl + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
可以看到,订单微服务获取商品微服务信息时,不再是硬编码商品微服务的IP地址和端口号了。
(4)修改使用restTemplate扣减商品库存的代码片段,修改前的代码片段如下所示。
Result<Integer> result = restTemplate.getForObject("http://localhost:8070/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("库存扣减失败");
}
修改后的代码片段如下所示。
Result<Integer> result = restTemplate.getForObject("http://" + productUrl + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("库存扣减失败");
}
可以看到,订单微服务调用商品微服务的扣减商品库存接口时,不再是硬编码商品微服务的IP地址和端口号了。
注意:修改后的io.binghe.shop.order.service.impl.OrderServiceV2Impl 类的完整源码,小伙伴们可自行查看项目代码,冰河在这里不再赘述。
至此,整个项目就改造完成了。
2. Ribbon实现负载均衡
2.1 负载均衡
在正式优化程序代码之前,我们先来看看什么是负载均衡。说的直白点,负载均衡就是将原本由一台服务器处理的请求根据一定的规则分担到多台服务器上进行处理。目前,大部分系统都实现了负载均衡的功能。
负载均衡根据发生的位置,可以分为服务端负载均衡和客户端负载均衡。
2.1.1 服务端负载均衡
服务端负载均衡指的是在服务端处理负载均衡的逻辑,如下图所示。
图片来源:冰河技术,下同。
负载均衡在服务端进行处理,当客户端访问服务端的服务A时,首先访问到服务端的负载均衡器,由服务端的负载均衡器将客户端的请求均匀的分发到服务端部署的两个服务A上。
2.1.2 客户端负载均衡
客户端负载均衡指的是在客户端处理负载均衡的逻辑,如下图所示。
负载均衡逻辑在客户端一侧,客户端应用调用服务端的应用A时,在向服务端发送请求时,就已经经过负载均衡的逻辑处理,并直接向服务端的某个服务发送请求。
2.2 启动多服务
为了实现服务调用的负载均衡功能,我们在本地的IDEA环境中分别启动两个用户微服务进程和两个商品微服务进程。
2.2.1 启动多个用户微服务
这里,我们在IDEA开发环境中启动多个用户微服务,其实也就是在同一台机器(服务器)上启动多个用户微服务。启动用户微服务时,默认监听的端口为8060,主要由如下配置决定。
server:
port: 8060
在同一台机器(服务器)上启动多个用户微服务时,只需要保证启动的多个用户微服务监听的端口号不同即可。
IDEA中可以通过配置不同的端口号来启动多个相同的服务,如下所示,再配置一个用户微服务,使其端口号为8061。
按照上图所示,在Name一栏输入UserStarter2,点击Main class一栏后面的弹出框按钮,选择用户微服务的启动类io.binghe.shop.UserStarter,最重要的就是在VM options一栏后面添加JVM启动参数-Dserver.port=8061,将新添加的用户微服务的监听端口设置为8061。
配置好之后,在IDEA中分别启动端口为8060和8061的两个用户微服务,启动后打开Nacos的服务列表,如下所示。
可以看到,在服务列表中出现了两个用户微服务的实例,说明两个用户微服务都启动成功了。
2.2.2 启动多个商品微服务
与用户微服务类似,在IDEA中再次配置一个端口号为8071的商品微服务,如下所示。
接下来,分别启动端口为8070和8071的两个商品微服务,启动后查看Nacos的服务列表,如下所示。
可以看到,端口为8070和8071的两个商品微服务,也成功启动啦。
2.3 实现自定义负载均衡
过修改订单微服务的代码来实现自定义负载均衡,由于在整个项目中,订单微服务作为客户端存在,由订单微服务调用用户微服务和商品微服务,所以,这里采用的是客户端负载均衡的模式。
2.3.1 修改订单微服务代码
此处实现的逻辑在订单微服务的io.binghe.shop.order.service.impl.OrderServiceV3Impl
类中,并且在OrderServiceV3Impl类上会标注@Service(“orderServiceV3”)注解。订单微服务的代码结构如下所示。
├─shop-order
│ │ pom.xml
│ │ shop-order.iml
│ │
│ └─src
│ └─main
│ ├─java
│ │ └─io
│ │ └─binghe
│ │ └─shop
│ │ │ OrderStarter.java
│ │ │
│ │ └─order
│ │ ├─config
│ │ │ LoadBalanceConfig.java
│ │ │
│ │ ├─controller
│ │ │ OrderController.java
│ │ │
│ │ ├─mapper
│ │ │ OrderItemMapper.java
│ │ │ OrderMapper.java
│ │ │
│ │ └─service
│ │ │ OrderService.java
│ │ │
│ │ └─impl
│ │ OrderServiceV1Impl.java
│ │ OrderServiceV2Impl.java
│ │ OrderServiceV3Impl.java
│ │
│ └─resources
│ │ application.yml
│ │
│ └─mapper
│ OrderItemMapper.xml
│ OrderMapper.xml
首先,在OrderServiceV3Impl
类中修改getServiceUrl()方法,使其能够在多个服务地址中随机获取一个服务地址,而不总是获取第一个服务地址。
修改前的代码如下所示。
private String getServiceUrl(String serviceName){
ServiceInstance serviceInstance = discoveryClient.getInstances(serviceName).get(0);
return serviceInstance.getHost() + ":" + serviceInstance.getPort();
}
修改后的代码如下所示。
private String getServiceUrl(String serviceName){
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
int index = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(index);
String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
log.info("负载均衡后的服务地址为:{}", url);
return url;
}
在getServiceUrl()方法中,首先通过服务的名称在Nacos中获取到所有的服务实例列表,然后使用随机函数随机生成一个0到服务列表长度减1的整数,而这个随机生成的整数恰好在服务实例列表的下标范围内,然后以这个整数作为下标获取服务列表中的某个服务实例。从而以随机的方式实现了负载均衡,并从获取到的服务实例中获取到服务实例所在服务器的IP地址和端口号,拼接成IP:PORT的形式返回。
接下来,将io.binghe.shop.order.controller.OrderController
类中的OrderService修改为注入beanName为orderServiceV3的OrderService对象,如下所示。
@Autowired
@Qualifier(value = "orderServiceV3")
private OrderService orderService;
至此,订单微服务的代码修改完成。
2.3.2 测试负载均衡效果
启动订单微服务,并在浏览器或其他测试工具中多次访问链接http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1
,在订单微服务输出的日志信息中会存在如下所示的日志。
负载均衡后的服务地址为:192.168.0.27:8061
负载均衡后的服务地址为:192.168.0.27:8071
负载均衡后的服务地址为:192.168.0.27:8060
负载均衡后的服务地址为:192.168.0.27:8070
负载均衡后的服务地址为:192.168.0.27:8060
负载均衡后的服务地址为:192.168.0.27:8071
负载均衡后的服务地址为:192.168.0.27:8061
负载均衡后的服务地址为:192.168.0.27:8070
其中,端口为8060和8061的微服务为用户微服务,端口为8070和8071的微服务为商品微服务。初步实现了订单微服务调用用户微服务和商品微服务的负载均衡。
2.4 使用Ribbon实现负载均衡
Ribbon是SpringCloud提供的一个能够实现负载均衡的组件,使用Ribbon能够轻松实现微服务之间调用的负载均衡。
2.4.1 修改订单微服务代码
在订单微服务中的io.binghe.shop.order.config.LoadBalanceConfig
类的 restTemplate()方法上添加@LoadBalanced注解,如下所示。
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
接下来,实现的逻辑在订单微服务的io.binghe.shop.order.service.impl.OrderServiceV4Impl
类中,并且在OrderServiceV4Impl类上会标注@Service(“orderServiceV4”)注解。订单微服务的代码结构如下所示。
├─shop-order
│ │ pom.xml
│ │ shop-order.iml
│ │
│ └─src
│ └─main
│ ├─java
│ │ └─io
│ │ └─binghe
│ │ └─shop
│ │ │ OrderStarter.java
│ │ │
│ │ └─order
│ │ ├─config
│ │ │ LoadBalanceConfig.java
│ │ │
│ │ ├─controller
│ │ │ OrderController.java
│ │ │
│ │ ├─mapper
│ │ │ OrderItemMapper.java
│ │ │ OrderMapper.java
│ │ │
│ │ └─service
│ │ │ OrderService.java
│ │ │
│ │ └─impl
│ │ OrderServiceV1Impl.java
│ │ OrderServiceV2Impl.java
│ │ OrderServiceV3Impl.java
│ │ OrderServiceV4Impl.java
│ │
│ └─resources
│ │ application.yml
│ │
│ └─mapper
│ OrderItemMapper.xml
│ OrderMapper.xml
在OrderServiceV4Impl类中删除如下代码。
@Autowired
private DiscoveryClient discoveryClient;
private String getServiceUrl(String serviceName){
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
int index = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(index);
String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
log.info("负载均衡后的服务地址为:{}", url);
return url;
}
在saveOrder()方法中删除如下两行代码。
//从Nacos服务中获取用户服务与商品服务的地址
String userUrl = this.getServiceUrl(userServer);
String productUrl = this.getServiceUrl(productServer);
修改通过restTemplate调用用户微服务和商品微服务的方法。修改前的代码如下所示。
User user = restTemplate.getForObject("http://" + userUrl + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = restTemplate.getForObject("http://" + productUrl + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
//#####################此处省略N行代码#########################
Result<Integer> result = restTemplate.getForObject("http://" + productUrl + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("库存扣减失败");
}
修改后的代码如下所示。
User user = restTemplate.getForObject("http://" + userServer + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = restTemplate.getForObject("http://" + productServer + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
//#####################此处省略N行代码#########################
Result<Integer> result = restTemplate.getForObject("http://" + productServer + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("库存扣减失败");
}
接下来,将io.binghe.shop.order.controller.OrderController
类中的OrderService修改为注入beanName为orderServiceV4的OrderService对象,如下所示。
@Autowired
@Qualifier(value = "orderServiceV4")
private OrderService orderService;
至此,订单微服务的代码修改完成。
2.4.2 测试负载均衡效果
启动订单微服务,并在浏览器或其他测试工具中多次访问链接http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1
,启动的每个用户微服务和商品微服务都会打印相关的日志,说明使用Ribbon实现了负载均衡功能。
注意:这里就不粘贴测试时每个启动的微服务打印的日志了,小伙伴们可自行测试并演示效果。
参考:
SpringCloud Alibaba
冰河技术:《SpringCloud Alibaba实战》