文章目录
- 前言
- 一、什么时候需要拆分微服务
- 1. 创业型项目
- 2. 大型项目
- 二、怎么拆
- 1. 拆分目标
- 2. 拆分方式
- 三、微服务之间远程调用
- 1. 实现方式
- 2. 手动发送Http请求(RestTemplate)
- 3. 服务注册中心
- 3.1 原理
- 3.2 Nacos注册中心
- 3.3 服务注册
- 3.4 服务发现(DiscoveryClient)
- 4. OpenFeign
- 4.1 实现步骤
- 4.2 连接池
- 5. 抽取Fegin客户端
- 实现方式
- 6. 日志
- 6.1 级别
- 6.2 实现步骤
前言
什么时候去拆分微服务?怎么拆分?拆分后微服务项目之间的业务怎么调用?
一、什么时候需要拆分微服务
1. 创业型项目
如果是创业型公司,最好先用单体架构快速迭代开发,验证市场运作模型,快速试错。当业务跑通以后,随着业务规模扩大、人员规模增加,再考虑拆分微服务。
2. 大型项目
如果是大型企业,有充足的资源,可以在项目开始之初就搭建微服务架构。
二、怎么拆
1. 拆分目标
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
- 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
2. 拆分方式
- 纵向拆分:按照业务模块来拆分
- 横向拆分:抽取公共服务,提高复用性
三、微服务之间远程调用
1. 实现方式
- 基于Http协议(不关心服务提供者的具体技术实现,只要对外暴露Http接口即可,更符合微服务的需要)
- 基于Dubbo协议
2. 手动发送Http请求(RestTemplate)
Java发送http请求可以使用Spring提供的RestTemplate
实现步骤:
1、注入RestTemplate到Spring容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
2、发起远程调用
// 利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {},
Map.of("ids", CollUtil.join(itemIds, ","))
);
3. 服务注册中心
问题引入:微服务项目a(服务提供者)多实例部署,每个实例其IP或端口不同,当微服务项目b(服务消费者)要远程调用a时不知道调用哪个。
3.1 原理
三个角色
服务提供者:暴露服务接口,供其它服务调用
服务消费者:调用其它服务提供的接口
注册中心:记录并监控微服务各实例状态,推送服务变更信息
流程
- 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
- 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
- 调用者自己对实例列表负载均衡,挑选一个实例
- 调用者向该实例发起远程调用
服务状态变更
服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
3.2 Nacos注册中心
Alibaba公司出品,目前被集成在SpringCloudAlibaba中
基于Docker来部署Nacos的注册中心
(1)准备nacos.sql数据库表,并导入到sql客户端
(2)再将sql文件导入到Docker中的MySQL容器中
3.3 服务注册
1、引入依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、配置Nacos地址
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.xxx.xxx:xxx # nacos地址
3、启动服务实例
3.4 服务发现(DiscoveryClient)
服务的消费者要去nacos订阅服务,这个过程就是服务发现。
步骤(步骤1、2和前面一样)
1、引入依赖
2、配置Nacos地址
3、发现并调用服务
DiscoveryClient,SpringCloud已经自动装配,只要注入就好。
4. OpenFeign
问题引入:利用RestTemplate实现了服务的远程调用,代码复杂。如何让远程调用像本地方法调用一样简单
概念:声明式的http客户端,利用SpringMVC的常见注解,基于动态代理生成远程调用的代码,而无需手动再编写,非常方便。
4.1 实现步骤
1、引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer
<!--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>
2、在启动类中添加@EnableFeignClients注解,启用OpenFeign功能
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
//basePackages = "com.hmall.api.client"因为是把微服务项目业务抽取到hm-api这个模块中,要调用这个模块中的方法,需要扫描到这个包下,并在FeignClient中药声明服务名称
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}
3、声明接口,无需实现方法,在接口上添加FeignClient注解
@FeignClient("item-service") //声明服务名称
public interface ItemClient {
@GetMapping("/items") //声明请求方式、请求路径
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
4、使用FeignClient,实现远程调用
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3))
4.2 连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
- HttpURLConnection:默认实现,不支持连接池
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
使用OKHttp步骤
1、引入依赖
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
2、开启连接池
feign:
okhttp:
enabled: true # 开启OKHttp功能
5. 抽取Fegin客户端
问题引入:微服务项目之间的调用复杂,不同的微服务项目调用同一个微服务项目,会出现代码重复的问题。
实现方式
- 思路1:抽取到微服务之外的公共module。结构清晰但耦合度偏高
- 思路2:每个微服务自己抽取一个module。结构复杂但耦合度低
具体代码实现参考黑马微服务开发https://www.bilibili.com/video/BV1S142197x7?p=64&vd_source=d7aecc6c1f43adaae25fcb7e1a25fec2
6. 日志
FeignClient所在包的日志级别为DEBUG时,才会输出日志
6.1 级别
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
6.2 实现步骤
1、在抽取各个微服务通用业务的hm-api模块下新建一个配置类
package com.hmall.api.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}
2、配置,让日志级别生效
局部配置:在某个FeignClient中配置,只对当前FeignClient生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
全局配置:在@EnableFeignClients中配置,针对所有FeignClient生效
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)