单体架构
单体架构是一种将所有功能模块紧密集成在一起的架构模式。在这种架构中,应用程序的所有组件(如用户界面、业务逻辑、数据访问层等)都被打包到一个单一的可执行文件或部署单元中。
单体架构的优势
-
开发效率高:对于小型或中型项目,开发团队可以快速实现功能,因为不需要考虑模块间的分布式通信问题。
-
性能优化容易:由于所有模块运行在同一进程中,性能优化相对简单,可以通过代码优化、缓存等手段直接提升整体性能。
-
技术门槛低:对于初学者或小型团队来说,单体架构更容易理解和上手,不需要掌握复杂的分布式技术。
-
事务管理简单:在单体架构中,事务管理相对简单,因为所有模块都在同一个数据库事务中操作。
单体架构的劣势
-
扩展性差:随着业务增长,单体应用的代码量和复杂度会不断增加,导致难以扩展。一旦某个模块出现问题,可能会影响整个应用程序的运行。
-
技术债务累积:随着时间推移,代码库会变得庞大且难以维护,新功能的添加和旧功能的修改都会变得越来越困难。
-
部署困难:每次更新都需要重新部署整个应用程序,即使只是修改了一个小模块。这可能导致部署时间长,且容易出错。
-
技术栈受限:由于所有模块共享相同的代码库和技术栈,难以引入新的技术或框架,限制了技术的创新。
微服务架构
微服务架构(Microservices Architecture)是一种将应用程序拆分为多个小型、独立服务的架构风格。每个服务运行在自己的进程中,通过轻量级通信机制(如HTTP/REST)进行交互,并独立部署和扩展。
微服务拆分原则
从拆分目标来说,要做到:
- * 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
- * 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
从拆分方式来说,一般包含两种方式:
- * 纵向拆分:按照业务模块来拆分
- * 横向拆分:抽取公共服务,提高复用性
各个服务模块实现通信的方法——注册中心原理
服务治理中的三个角色
- 服务提供者:暴露服务接口,供其它服务调用
- 服务消费者:调用其它服务提供的接口
- 注册中心:记录并监控微服务各实例状态,推送服务变更信息
消费者如何知道提供者的地址?
- 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
消费者如何得知服务状态变更?
- 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
当提供者有多个实例时,消费者该选择哪一个?
- 消费者可以通过负载均衡算法,从多个实例中选择一个
服务注册中心——Nacos
配置nacos的环境
docker直接使用环境变量启动Nacos:
docker run -d \
--name nacos \
-e MODE=standalone \
-e PREFER_HOST_MODE=hostname \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
之后通过docker logs -f nacos查看日志
登录的账号密码:都是nacos
或者window本机上直接运行
下载nacos的二进制文件,然后打开 Nacos 安装路径下的 bin/startup.cmd
文件进行编辑。
找到 set MODE=""
,将其值修改为 standalone
,即:
set MODE="standalone"
直接双击 startup.cmd
文件即可启动 Nacos。
单个微服务项目引入 Nacos Discovery 依赖:
<!-- nacos 服务注册发现,不需要指定版本,因为父组件里有 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 父组件 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
yml文件里配置 Nacos 地址:
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
在项目里配置好之后,在nacos的网页界面可以看到注册的服务
调用Nacos服务,实现服务发现
在启动类里注册RestTemplate配置(用于发送http请求接收响应)
@Bean // 放入配置类里
public RestTemplate restTemplate() {
return new RestTemplate();
}
使用lombok的@RequiredArgsConstructor注解注入相关依赖(推荐)
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final DiscoveryClient discoveryClient; // 发现nacos里注册的微服务
private final RestTemplate restTemplate;
......
}
方法里发送请求给其它微服务
private void handleCartItems(List<CartVO> vos) {
// 提取购物车中所有商品的ID,并去重
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 找到名为"item-service"的微服务
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
// 如果没有可用的服务实例,则直接返回
if(CollUtil.isEmpty(instances)){
return ;
}
// 从服务实例列表中随机选择一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size())); // 最小随机
// 构建请求URL,通过REST模板向选定的服务实例发送GET请求,获取响应信息
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
instance.getUri() + "/items?ids={ids}", // 注意请求的参数不要写错
HttpMethod.GET, // 请求方法
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
// 解析响应状态码,如果请求失败(非2xx成功状态码),则直接返回
if(!response.getStatusCode().is2xxSuccessful()){
// 查询失败,直接结束
return;
}
// 从响应中获取商品信息列表
List<ItemDTO> items = response.getBody();
// 如果没有获取到商品信息,则直接返回
if (CollUtils.isEmpty(items)) {
return;
}
......
}
以上代码有几个要根据具体业务而不同实现的:
微服务集群环境下负载均衡策略的选择
-
简单场景:随机或轮询。
-
高性能场景:最少连接或响应时间加权。
-
高可用场景:故障回避或区域优先。
-
复杂场景:自定义策略或一致性哈希。
构造请求
请求到方式、请求体、请求url里的参数
以上全部白雪😂😂😂
OpenFeign
OpenFeign 是一个声明式的 Web 服务客户端,主要用于简化微服务架构中 HTTP 请求的处理。它通过注解和接口的形式定义远程服务的调用,帮助开发者省去编写大量 HTTP 请求的样板代码😂
-
OpenFeign 已经被 SpringCloud 自动装配,实现起来非常简单:
<!--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>
-
在启动类里通过
@EnableFeignClients
注解,启用 OpenFeign 功能。@EnableFeignClients @SpringBootApplication public class CartApplication { // ... 略 }
-
在项目里新建Client文件夹,里面放xxxClient的接口文件,接口里定义springmvc风格的代码用于请求其它微服务
@FeignClient("item-service") // 指定请求的微服务 public interface ItemClient { @GetMapping("/items") // 可以直接把那个对应的微服务里的方法拷过来 List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids); }
-
要给其它微服务发送请求的方法所在的类里注入接口
@RequiredArgsConstructor
private final ItemClient itemClient; // 加上面的注解
List<ItemDTO> items = itemClient.queryItemByIds(new ArrayList<>(itemIds)); // 直接调用接口里的方法,获取请求的结果
OKhttp连接池
OKhttp连接池的特性
-
高效性:OkHttp 支持连接池化和复用,减少了请求延迟,提高了效率。它还支持HTTP/2和SPDY协议,可以更高效地使用网络资源。
-
可靠性:它支持自动重定向和自动恢复,例如在网络连接失败时自动重试请求。
-
安全性:OkHttp 支持HTTPS和TLS,提供了多种安全配置选项,包括证书固定(Certificate Pinning)和主机名验证。
-
可定制性:开发者可以自定义请求头、响应拦截器、缓存策略等,以满足不同的需求。
-
易用性:OkHttp 提供了简单直观的API,使得发送HTTP请求变得非常容易。
-
响应缓存:OkHttp 支持响应缓存,可以减少不必要的网络请求,加快应用的响应速度。
-
GZIP压缩:它支持请求和响应的GZIP压缩,可以减少数据传输量,加快传输速度。
-
同步和异步请求:OkHttp 支持同步和异步请求,开发者可以根据需要选择适合的方式。
-
WebSocket支持:OkHttp 还支持WebSocket,可以方便地实现双向通信。
引入依赖:
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
打开开关
feign:
okhttp:
enabled: true # 开启OkHttp连接池支持,默认是false
OpenFeign最佳实现方案
以上方案的实现思路方案
新建一个XXX-api的模块,同时导入公共的dto和和每个模块需要暴露出去的api(通过OpenFeign实现)。(config是配置openfeign日志级别)
之后在XXX-api模块里导入依赖
<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>
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
配置完XXX-api通用接口模块之后,在每一个需要使用到XXX-api模块里接口的模块导入XXX-api依赖。
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
此时注意:
只要哪个模块里导入了XXX-api通用模块,这个模块就不需要写额外实现的代码以及额外导入的依赖,因为这些都在 XXX-api通用模块里
最后找到在要使用到XXX-api模块里接口的模块,在它的启动类里加上注解如下,确定扫描范围。
@EnableFeignClients(basePackages = "com.hmall.api.client")
OpenFeign日志
OpenFeign 的日志级别设置:
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法、URL以及响应状态码和执行时间。
-
HEADERS:在 BASIC 的基础上,额外记录了请求和响应的头信息。
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
此外,OpenFeign 只会在 FeignClient 所在包的日志级别为 DEBUG 时,才会输出日志。
自定义日志级别 Bean:
-
创建一个配置类,并在其中定义日志级别。
public class DefaultFeignConfig { @Bean public Logger.Level feignLogLevel() { return Logger.Level.FULL; } }
配置特定 FeignClient 的日志:可以在 @FeignClient
注解(启动类里)中声明配置。
// 配置局部
// @FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
// 配置全局
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)// 配置全局
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}
此时就可以在日志里看到openfeign发送请求的详细信息了:
注意:如果是真正运行的过程中,不需要开启openfeign的日志