说明:目前SpringCloud最新版本为2022.0.x版本,对应的SpringBoot版本为3.x版本,但它们全部依赖于JDK17,目前在企业中使用相对较少。推荐使用次新版本:Spring Cloud 2021.0.x以及Spring Boot 2.7.x版本。(注意:该项目使用jdk版本是11,亲测jdk17运行不了)
1. 拆分商品、购物车服务
1. 创建新module - maven模块,并引入依赖
2. 新建包com.hmall.xx(业务名),添加和修改启动类,新建mapper包、domain包 - service包 - controller包
3. 拷贝并修改yaml配置文件到resources中,分别修改 端口号、服务名称、datasource(需创建sql datebase)、swagger接口文档说明与controller扫描包
4. 添加以下部分代码
1)domain包代码:dto、po、vo、(query)
2)mapper包代码 :mapper接口 及mapper.xml文件
3) service包:service接口及实现类
4)controller包
5. 刷新maven,添加该业务模块启动项到Services中,并把Active profiles 修改为 local
6. 运行,在访问地址后面添加doc.html访问swagger接口文档,进行调试
具体步骤参考:Docs
1.1 商品服务
在hmall下new module:(IDEA版本不一致跟文档中的创建有一定出入)
注意:在application.yaml文件中修改了端口号,数据库名称,接口文档设置
根据文档完成操作之后,启动 item-service,访问商品微服务的swagger接口文档:http://localhost:8081/doc.html
至此,商品服务拆分完成。
1.2 购物车拆分
在hmall下new module:
创建完后,pom.xml文件是橙色,代表idea没有识别到该项目为maven项目,pom.xml–>右键–>add as maven project即可。
在pom.xml文件导入依赖,与文档中创建module不同(文档里IDEA可以直接创建Maven,并且指定父工程),虽然按照如上步骤创建,父工程是默认的,但是可以直接在pom.xml文件修改。
根据文档完成操作之后,启动 cart-service,访问购物车微服务的swagger接口文档:http://localhost:8082/doc.html
把User写死,查询购物车数据成功。
1.3 服务调用
在购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service,导致无法查询。最终结果就是查询到的购物车数据不完整,为了解决这个问题,需要把原本本地方法调用,改造成跨微服务的远程调用(RPC,即Remote Produce Call)。
类比前端向服务端查询数据,其实就是从浏览器远程查询服务端数据。比如通过Swagger测试商品查询接口,就是向http://localhost:8081/items这个接口发起的请求。通过http请求的方式,可以完成远程查询、实现新增、删除等各种远程请求。
1.3.1 RestTemplate
Spring提供了一个RestTemplate的API,可以方便的实现Http请求的发送。
Java发送http请求可以使用Spring提供的RestTemplate,使用的基本步骤如下:
-
注册RestTemplate到Spring容器
-
调用RestTemplate的API发送请求,常见方法有:
-
getForObject:发送Get请求并返回指定类型对象
-
PostForObject:发送Post请求并返回指定类型对象
-
put:发送PUT请求
-
delete:发送Delete请求
-
exchange:发送任意类型请求,返回ResponseEntity
-
编写远程调用逻辑代码后,直接启动cart-service,测试查询购物车:
出现这个错误是因为没有启动item-service的服务。启动item-service服务后,重新测试,查询成功:
1.4 服务注册和发现
如果商品微服务调用较多,为了应对高并发,进行了多实例部署,每个item-service的实例IP或端口不同:
-
item-service这么多实例,cart-service如何知道每一个实例的地址?
-
http请求要写url地址,cart-service服务到底该调用哪个实例呢?
-
如果在运行过程中,某一个item-service实例宕机,cart-service依然在调用该怎么办?
-
如果并发太高,item-service临时多部署了N台实例,cart-service如何知道新实例的地址?
1.4.1 注册中心原理
在微服务远程调用的过程中,包括两个角色:
-
服务提供者:提供接口供其它微服务访问,比如item-service
-
服务消费者:调用其它微服务提供的接口,比如cart-service
流程如下:
-
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
-
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
-
调用者自己对实例列表负载均衡,挑选一个实例
-
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
-
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
-
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
-
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
-
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
1.4.2 Nacos注册中心
目前开源的注册中心框架有很多,国内比较常见的有:
-
Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用
-
Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用
-
Consul:HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言
文档中,直接使用Docker拉取镜像run nacos,我在本机操作也没有docker。步骤如下:
1)下载nacos:
Release 2.1.0 (Apr 29, 2022) · alibaba/nacos · GitHub
2)打开nacos安装路径,bin/startup.cmd,找到set MODE = "",把值修改为standalone(修改启动方式为单机启动)
3)双击启动
4)访问http://localhost:8848/nacos/ 账号密码都是nacos
1.4.3 服务注册
接下来,我们把item-service注册到Nacos,步骤如下:
-
引入依赖
-
配置Nacos地址
-
重启
为了测试一个服务的多个实例的情况,在配置一个item-service的部署实例,重命名并配置新的端口:
配置完成后启动服务,报错
ERROR 12176 --- [t.remote.worker] c.a.n.c.remote.client.grpc.GrpcClient : Server check fail, please check server 127.0.0.1 ,port 9848 is available , error ={}
问题原因:项目总使用的Nacos客户端是2.x版本以上,Nacos在这个版本中新增了gRPC通信,默认通信端口是Nacos的server-adddr的端口号加上1000的偏移量,所以报错会检查9848端口是否开放。
网上有很多的解决方案,五花八门。具体可见:springCloud 连接nacos报错Server check failport 9848 error={} 解决方案_server check fail, please check server 192.168.209-CSDN博客
方案一:检查nacos服务端与spingcloud服务器之间的网络,保障nacos服务端对外端口8848、9848、7848、9849及网络均可访问(telnet验证)。
1)Windows netstat -ano | findstr :9848查看被占用的端口,打开cmd以管理员身份运行,使用 netstat -ano | findstr :9848 命令。发现并没被占用。
2)在Windows上启用telnet客户端:
- 打开控制面板。
- 选择“程序”。
- 点击“打开或关闭Windows功能”。
- 在弹出的窗口中找到“Telnet客户端”,勾选并点击“确定”。
3)检查Nacos服务器的8848端口: 打开命令提示符(cmd),输入以下命令:
telnet 127.0.0.1 8848
连接成功,屏幕会变空白,表示可以访问端口。按 Ctrl + ],然后输入quit退出。
4)检查Nacos服务器的9848端口(如果使用9848):
telnet 127.0.0.1 9848
同样,连接成功,屏幕会变空白,表示可以访问端口。
明明都能访问,但是还是连不上,这个方案我搞不来。
方案2:在版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub查看对应版本,下载对应的Nocos客户端。我真的很呆,在自己本机上电脑下的叫服务端,项目依赖的是客户端!!!服务端(server)2.x向下兼容客户端(client)1.x版本,客户端(client)2.x版本无法使用服务端(server)1.x版本。
Three hours later... ....
我也不知道怎么回事鬼使神差的就成功了, 还试过新建一个bootstrap.yaml,在该配置文件下配置nacos。说是要优先其他配置进行配置啥的(具体见:nacos必须在bootstrap.yml文件中配置,在application-dev.yml文件中会报异常的问题 · Issue #7290 · alibaba/nacos · GitHub和(完美解决)Connection refused: no further information: /127.0.0.1:9848_cloud启动 no further information-CSDN博客)。但是后面我就想知道走到哪一步才成功的,我就把他删掉了,发现还是成功了。
解决:
item-service服务下application.yaml配置文件:
虽然也有ERROR,但是服务能正常启动,并且能在服务列表正常显示。后面复制item-service服务启动不成功,检查是防火墙和网络的问题,再重新启动成功,两个服务都注册成功。(哪有不疯的程序员呢~)
1.4.4 服务发现
服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:
-
引入依赖
-
配置Nacos地址
-
发现并调用服务
使用swagger文档测试查询购物车,有时候会因为翻墙和网络原因500错误,注意一点!!!
可以看到此次查询走的itemServiceApplication2
1.5 OpenFeign
远程调用的关键点就在于四个:
-
请求方式
-
请求路径
-
请求参数
-
返回值类型
OpenFeign利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理来生成远程调用的代码,而无需手动编写,非常方便。
1.5.1 引入依赖
在cart-service服务的pom.xml中引入OpenFeign的依赖和loadblancer依赖:
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
1.5.2 启用OpenFeign
接下来,我们在cart-service的启动类上添加注解@EnableFeignClients,启动OpenFeign功能:
1.5.3 编写OpenFeign客户端
在cart-service中,定义一个新的接口,编写Feign客户端:
package com.hmall.cart.client;
import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
这里只需要声明接口,无需实现方法。接口中的几个关键信息:
-
@FeignClient("item-service"): 声明服务名称
-
@GetMapping: 声明请求方式
-
@GetMapping("/items"): 声明请求路径
-
@RequestParam("ids") Collection<Long> ids: 声明请求参数
-
List<ItemDTO>: 返回值类型
有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items发送一个GET请求,携带ids为请求参数,并自动将返回值处理List<ItemDTO>
1.5.4 使用FeignCilent
1.5.5 连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
-
HttpURLConnection:默认实现,不支持连接池
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,使用OK Http。
按照文档,引入依赖,开启连接池。
在swagger文档调试:
1.5.6 抽取Feign客户端
为了避免重复编码的办法就是抽取。可以将公共代码抽取到微服务之外的公共module。
在hmall下定义一个新的module,命名为hm-api,导入依赖。
将ItemDTO和ItemClient都拷贝进hm-api。任何微服务要调用item-service中的接口,只需要在pom.xml引入hm-api模块依赖即可,无需自己编写Feign客户端了。
注意!!!要记得在微服务的启动类的类上声明扫描包。
2. 拆分微服务的其他模块
2.1 用户模块
目录结构如下:
记得要开启nacos,才能运行成功。记得yaml文件改成自己的nacos地址。
启动项目后,访问 http://localhost:8084/doc.html,测试登陆接口,200成功。
2.2 交易模块
根据文档创建模块,目录结构:
对hm-api做出对应的修改:
有一个要注意的地方,OrderServiceImpl会报错:
报错就是说OrderFormDTO的getDetails方法返回的类型是com.hmall.trade.domain.dto.OrderDetailDTO但是左侧接收的参数类型是com.hmall.api.dto.OrderDetailDTO,所以报错。不知道还有没有什么高明的解决办法,我直接更改在trade-service下的com/hmall/trade/domain/dto/OrderFormDTO.java,报错消失。
启动项目后,在http://localhost:8085/doc.html 查询1654779387523936258订单号,测试接口200成功。
2.3 支付模块
目录结构:
hm-api新增远程调用client
启动项目后,在http://localhost:8086/doc.html 查询支付订单,测试接口200成功。