当前位置: 首页 > ai >正文

微服务项目总结

1. 微服务

1.1 单体架构

单体架构(monolithic structure):整个项目中所有功能模块都在一个工程中开发;项目部署时需要对所有模块一起编译、打包;项目的架构设计、开发模式都非常简单。

所有的功能集中在一个项目中开发,打包一个包部署

1.2 微服务

微服务架构,首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。

1.3 SpringCloud

1. JWT

1.1 JWT的生成

  1. 从表单实体中拿到输入的用户的名字和密码
  2. 使用 MyBatis Plus 的 LambdaQueryWrapper 查询数据库中是否存在一个用户名为 username 的用户
  3. 校验用户状态
  4. 校验密码
  5. 生成TOKEN
  6. 将DTO封装成VO返回
 public UserLoginVO login(LoginFormDTO loginDTO) {// 1.数据校验String username = loginDTO.getUsername();String password = loginDTO.getPassword();// 2.根据用户名或手机号查询User user = lambdaQuery().eq(User::getUsername, username).one();Assert.notNull(user, "用户名错误");// 3.校验是否禁用if (user.getStatus() == UserStatus.FROZEN) {throw new ForbiddenException("用户被冻结");}// 4.校验密码if (!passwordEncoder.matches(password, user.getPassword())) {throw new BadRequestException("用户名或密码错误");}// 5.生成TOKENString token = jwtTool.createToken(user.getId(), jwtProperties.getTokenTTL());// 6.封装VO返回UserLoginVO vo = new UserLoginVO();vo.setUserId(user.getId());vo.setUsername(user.getUsername());vo.setBalance(user.getBalance());vo.setToken(token);return vo;}

JWT:是一个字符串,包含三部分

  • Header:算法和令牌类型
  • Payload:荷载,包含声明,如用户信息、过期时间等
  • Signature:签名,用于验证消息在传输过程中没有被更改

1.2 拦截器

在前端的每次请求,会携带JWT,在服务端需要获取 Authorization 请求头中的 JWT Token,解析并校验JWT,通过拦截器来实现

通过拦截器拿到用户信息,将用户信息存到ThreadLocal中

项目包含

  • 前置方法
    • Controller 方法执行前调用。
    • 用于身法校验、权限校验
  • 最终方法
    • 整个请求处理完成后执行(无论是否发生异常)。
    • 用于资源清理(如关闭数据库连接、清除 ThreadLocal),避免内存泄漏
public class LoginInterceptor implements HandlerInterceptor {private final JwtTool jwtTool;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的 tokenString token = request.getHeader("authorization");// 2.校验tokenLong userId = jwtTool.parseToken(token);// 3.存入上下文UserContext.setUser(userId);// 4.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理用户UserContext.removeUser();}
}

1.3 可能问到的问题

如何实现用户Token的存放的?为什么要用 ThreadLocal?

UserContext内部使用ThreadLocal来保存当前线程的用户信息,这样做的目的是为了实现线程格里,保证每个请求的用户信息不会不想干扰

拦截器的执行顺序和生命周期

前置方法应该在Controller 方法执行前调用,用于身份验证、权限校验。

结束方法在整个请求处理完成后执行(无论是否发生异常),用于资源清理(如关闭数据库连接、清除 ThreadLocal),避免内存泄漏

校验token抛出异常的原因有哪些?

Token 已过期,签名不匹配(签名被篡改),Token 格式错误(不是三段结构),Token 被加入黑名单(需配合 Redis 黑名单机制)

Token 过期后如何让用户继续使用而不用重新登录?

可以引入 Refresh Token:

  • 登录时返回两个 Token:Access Token(短时效) + Refresh Token(长时效)
  • Access Token 失效后,客户端携带 Refresh Token 向服务端换取新的 Access Token
  • Refresh Token 也需要校验合法性,并可加入黑名单

所有接口都需要经过这个拦截器吗?

不应该拦截所有接口,比如 /login 接口就不需要 Token。

可以在拦截器中增加白名单判断

浏览器跨域请求时,Authorization Header 会不会被浏览器拦截?

在跨域请求时:

  • 浏览器会先发送一个 OPTIONS 预检请求(preflight)
  • 服务器必须正确配置 CORS,允许 Authorization 请求头

2 远程调用

服务做了拆分,数据有了隔离后,当业务需要别的服务的数据时候,可以通过远程调用拿到别的服务的数据。

但是这个远程调用有个缺陷就是会把请求路径的IP给写死了

例如Cart-service请求Item-service的数据:

Spring提供了一个RestTemplete工具类,可以方便的实现Http请求的发送

  1. 注入RestTemplate到Spring容器
    @Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
  1. 发起远程调用
 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, ",")) // 请求参数);// 2.2 解析响应// 查询响应是否成功,如果失败则直接结束if(!response.getStatusCode().is2xxSuccessful()){return;}List<ItemDTO> items = response.getBody();

远程调用的关键点就在于四个:

  • 请求方式
  • 请求路径
  • 请求参数
  • 返回值类型

3. 服务治理

3.1 注册中心原理

服务治理中的三个角色:

  • 服务提供者:提供接口供其它微服务访问,比如item-service
  • 服务消费者:调用其它微服务提供的接口,比如cart-service
  • 注册中心:记录并监控微服务各实例状态,推送服务变更信息

消费者如何知道提供者的地址?

服务提供者会在启动时注册自己的信息到注册中心,消费者可以从注册中心订阅和拉取服务信息

消费者如何得知服务状态变更?

服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务删除,并通知订阅了该服务的消费者

当提供者有多个实例时,消费者该选择哪一个?

负载均衡算法

3.2 Nacos注册中心

Nacos帮助我们取管理所有的服务,并监控所有微服务的状态

进入root目录,执行:

docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim

访问:http://192.168.100.128:8848/nacos/

3.3 服务注册

  1. 引入nacos discovery依赖

  2. 配置nacos地址

3.4 服务发现

  1. 引入nacos discovery依赖

  2. 配置nacos地址

  3. 服务发现

    • 根据服务名称获取服务的实例列表
    • 手写负载均衡,从服务列表中获取一个实例
    • 利用RestTemplate发起http请求,请求另外一个微服务提供相关服务
 // 2.1 根据服务的名称获获取服务的实例列表List<ServiceInstance> instances = discoveryClient.getInstances("item-service");if(CollUtil.isEmpty(instances)){return;}// 2.2 手写负载均衡(这里使用简单的随机负载均衡),从服务列表中挑选一个实例ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));// 2.3利用RestTemplate来发起http请求,请求item.service提供相关服务,获取实例的IP和端口:instance.getUri()ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(instance.getUri()+"/items?ids={ids}",HttpMethod.GET,null,new ParameterizedTypeReference<List<ItemDTO>>() {},Map.of("ids", CollUtil.join(itemIds, ",")));

4. OpenFeign

nacos使用太复杂,使用OpenFeign来进一步优化

4.1 快速入门

  1. **引入依赖,**包括OpenFeign和负载均衡组件SpingCloudLoadBalancer

    一个做调用一个做负载均衡

        <!--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. 通过@EnableFeignClients注解,启用OpenFeign功能
@EnableFeignClients
  1. 编写FeignClient
@FeignClient("item-service")   // 服务提供者的名字
public interface ItemClient {@GetMapping("/items")List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
  1. 使用FeignClient,实现远程调用
List<ItemDTO> items = itemClient.queryItemByIds(itemIds);

4.2 连接池

连接池指的是一组预先创建的 HTTP 连接,这些连接可以被重复使用,而不是每次请求都创建一个新的连接。

在 Feign 中,不同的 HTTP 客户端实现对连接池的支持有所不同:

HttpURLConnection:这是 Java 的默认 HTTP 客户端实现,不支持连接池。每次请求都会创建一个新的连接,请求完成后连接会被关闭。这种方式在高并发场景下性能较差。

Apache HttpClient:这是一个功能强大的 HTTP 客户端库,支持连接池。通过配置连接池,可以复用连接,提高性能。

OKHttp:这是另一个流行的 HTTP 客户端库,也支持连接池。OKHttp 的连接池实现高效且易于配置,适合在高并发场景下使用

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.

最好使用连接池,降低创建和修改连接的开销

OpenFeign整合OKHttp的步骤:

  1. 引入依赖

  2. src/main/resources/application.yaml 开启连接池功能

    feign:okhttp:enabled: true # 开启OKHttp功能
    

4.3 最佳实践

将与业务无关的接口单独抽取成一个微服务

4.4 日志

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

1-4 微服务中远程调用与服务治理可能问到的问题

1. 你是如何在微服务之间进行远程调用的?请描述一下你使用过哪些远程调用的方式?

  1. RestTemplate 是 Spring 提供的用于发送 HTTP 请求的工具类。可以通过 exchange 方法发起 GET/POST 等请求,并处理返回值类型(如 ParameterizedTypeReference)。缺点:请求路径中 IP 和端口是硬编码的,不灵活,不利于服务治理。

  2. 后续优化中,使用到了Nacos来进行服务的注册和发现,使用手动负载均衡进行业务的分流;

  3. 后续优化中,使用OpenFeign替换RestTemplate 进行优化,通过声明式客户端,简化远程调用;

  4. 继续优化中,为了避免频繁创建连接,使用带有连接池的客户端来代替默认的客户端连接。比如,我们使用OK Http.

  5. 最终,为了保证微服务之间的低耦合性,我们抽取 FeignClient 到公共模块,实现了接口的复用,在 FeignClient 我们不是调用方法,而是发送请求给指定的微服务以要求服务。

http://www.xdnf.cn/news/15652.html

相关文章:

  • 短视频矩阵系统:选择与开发的全方位指南
  • Python网络爬虫实现selenium对百度识图二次开发以及批量保存Excel
  • Java学习------使用Jemter测试若依项目自定义的功能
  • Unity 常见数据结构分析与实战展示 C#
  • APIs案例及知识点串讲(下)
  • CES Asia 2025备受瞩目,跨国企业锁定亚洲战略首发契机
  • 基于Ubuntu22.04源码安装配置RabbitVCS过程记录
  • ARM64高速缓存,内存属性及MAIR配置
  • 基于华为openEuler系统安装DailyNotes个人笔记管理工具
  • Java全栈面试实录:从Spring Boot到AI大模型的深度解析
  • Glary Utilities (PC维护百宝箱) v6.24.0.28 便携版
  • 云原生 DevOps 实战之Jenkins+Gitee+Harbor+Kubernetes 构建自动化部署体系
  • 密码学基础概念详解:从古典加密到现代密码体系
  • 外网访问基于 Git 的开源文件管理系统 Gogs
  • Anime.js 超级炫酷的网页动画库之SVG路径动画
  • 信息检索革命:Perplexica+cpolar打造你的专属智能搜索中枢
  • GI6E 加密GRID電碼通信SHELLCODE載入
  • 论文review SfM MVS VGGT: Visual Geometry Grounded Transformer
  • 需要保存至服务器的:常见编辑、发布文章页面基础技巧
  • 配置本地git到gitlab并推送
  • elasticsearch+logstash+kibana+filebeat实现niginx日志收集(未过滤日志内容)
  • .QOI: Lossless Image Compression in O(n) Time
  • Flutter 应用如何设计通知服务
  • Nature Communications:人工有机传入神经为智能机器人提供闭环触觉反馈
  • 半小时部署本地deepseek【1】
  • Hadoop与云原生集成:弹性扩缩容与OSS存储分离架构深度解析
  • Django母婴商城项目实践(五)
  • Linux中的LVS集群技术
  • 二进制写入与文本写入的本质区别:系统视角下的文件操作
  • IT 和OT指的什么?