项目总结
(1)第一个感觉是思维的解放。
在这么多业务的练习中,我的对于业务的抽象能力大大提升,简单的讲:我认为代码的编写,实际上就是对业务需求的不断解构,拆分,细化。
例如:实现购物车接口。最初我还在想:如何让用户端购物车可以自动显示添加的菜品这些内容。因为没有办法把该业务抽象拆分为具体的代码思路而感到厌烦。之后就明白了,其实就是建立一张表,买了啥都记到表里面,所谓的添加商品可以实时看到,只不过是加了一个数据库查询之后回显给前端而已
这就是我想要说的,再复杂的业务也可以不断的进行抽离,拆分,最终变为一个个简单的逻辑代码,而谁的抽象拆分能力越强,谁就越可能成为一位合格的程序员。
(2)第二个感觉是思维的提升。
回头望去,原来自己学习到了这么多的知识点,并且也没有自己最开始认为的那么难。回顾整个项目,我认为作为初写项目的学生来讲,我面临的最大的问题是:缺乏宏观思想。我在写业务代码的时候,通常只能局限于仅仅实现当前业务,并没有思考代码复用性,业务通用性,逻辑顺畅性这些问题。导致写了很多的功能相同的代码。四个字总结:站位不高。
而这也是我尝试写项目总结的原因,项目总结让我脱离具体的业务板块,不再把思维聚焦在某一个功能的实现上,而是尝试聚焦整个业务整体。在我的眼里,实现项目是从小到大,我用一个一个业务去组成了这个大的项目。而写项目日记是从大到小,当我从一整个项目整体开始拆分业务的时候,我是切身实地的觉得我的站位变高了,因为我在真真切切的思考不同业务代码之间的逻辑关系。由于实现过整个业务,我可以让思维在不同的业务之间穿梭,不断的解构这些业务。尝试探寻更好的业务解决方案。
我认为:如果我可以在业务逻辑代码搭建阶段就有这种宏观思考的能力,那么整个项目的业务逻辑实现就会变的轻松很多。
项目提问
redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
- 在智慧社区项目中,我们当时是把热点新闻的数据存入到了缓存中,虽然是热点数据,但是实时要求性并没有那么高,所以,我们当时采用的是异步的方案同步的数据
- 在外卖平台中,我们当时是把菜品的库存纳入缓存中,需要实时的进行数据同步,为了保证数据的强一致,我们当时采用的是redisson提供的读写锁来保证数据的同步
允许延时一致的业务,采用异步通知
- 延时双删策略:先删除缓存,更新数据库,使用MQ中间件,通知缓存删除
- 订阅binlog日志:利用canal中间件,伪装为mysql的一个从节点,通过读取binlog数据更新缓存
强一致性的,采用Redisson提供的读写锁
- 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作
- 排他锁:独占锁writeLock也叫,加锁之后,阻塞其他线程读写操作
Redisson
分布式读写锁:分别对缓存和数据库中的分布式加锁,持有写锁线程先更新数据库,再更新缓存,最后释放锁。
锁续期机制:Redisson 内部提供了“看门狗”机制,默认锁的有效期为 30 秒。如果在锁持有期间,持锁的 Redisson 实例未关闭,锁的有效期会自动延长,避免因 Redis 节点宕机导致的锁死问题。
可能出现问题
分布式解决问题
❌ 问题 1:超卖
多个用户同时下单,库存 = 1,但两个人都成功购买,导致库存变负数。
❌ 问题 2:订单重复提交==重复消费
同一个用户点击多次提交订单,导致创建多个相同订单。
消息幂等性:避免消息重复消费导致数据库数据错误:
-
使用 唯一 ID(如订单号)来标记已消费的消息,防止重复写入数据库。
-
可以在 数据库增加唯一约束,避免重复插入相同数据。
❌ 问题 3:并发写入数据覆盖
多个用户同时修改购物车,可能导致数据丢失或错误。
解决方案
- 悲观锁:确保同一时间只有一个用户对库存进行操作,避免多线程冲突
- 乐观锁:根据商品的版本号进行更新库存操作,只有开始的版本号与提交的版本号一致才行
- 使用 Redis 分布式锁 确保扣库存操作是原子的,防止并发问题
Redis分布式锁?
如何实现
在redis中提供了一个命令setnx(SET if not exists),由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,其他客户端是不能设置这个key的
那你如何控制Redis实现分布式锁有效时长呢?
Redisson 的自动续约机制: 通过 看门狗(Watchdog) 自动延长锁的有效期。获取锁后,默认超时时间为 30 秒,但 Redisson 会 每 10 秒 续约一次,确保锁不会过期,直到业务逻辑执行完成后手动释放锁,避免因业务执行时间过长而导致锁提前失效。
还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。==自旋锁
redisson实现的分布式锁是可重入的吗?
可重入锁 是指 同一个线程 在持有锁的情况下,可以再次获取同一个锁,而不会发生死锁。Redisson 也实现了 分布式可重入锁,让同一线程 在分布式环境中多次加锁,不会被自己锁住。
redisson实现的分布式锁能解决主从一致性的问题吗
这个是不能的,比如,当线程 1 加锁成功后,主节点数据异步复制到从节点时,如果当前持有 Redis 锁的主节点宕机,从节点被提升为新的主节点,但锁的同步尚未完成,新主节点上没有该锁的记录,此时其他线程可能会重新获取锁并修改数据,导致多个线程对同一资源进行并发修改,从而造成数据不一致的问题
redis分布式锁的粒度--按业务粒度分类
锁类型 | 锁定范围 | 适用场景 | 优缺点 |
---|---|---|---|
全局锁 | 整个系统或应用 | 重大业务操作,如定时任务、数据库表迁移 | 适用于保证全局数据一致性,但会降低系统并发性能 |
业务级锁 | 业务操作(如订单、支付、库存) | 如支付事务、订单状态变更 | 控制特定业务的并发,避免多个线程同时修改关键业务数据 |
资源级锁 | 具体的资源(商品 ID、用户 ID) | 如商品库存扣减、用户账户操作 | 降低锁冲突,提高并发性能 |
RabbitMQ
高并发场景下,写操作通过消息队列异步执行,减少数据库瞬时压力。
基于AMPQ协议,主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
消息可靠性:RabbitMQ-如何保证消息不丢失==>调用其他服务
当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不丢失。主要从三个层面考虑
- 1️⃣ 开启生产者确认机制,确保生产者的消息成功到达 Broker(RabbitMQ 服务器)。如果发送失败,生产者可以记录日志,并根据日志进行数据补偿。
- 2️⃣ 开启持久化功能,确保消息在 未被消费前不会丢失。其中,交换机、队列、和消息都需要开启持久化,以防 RabbitMQ 服务器宕机导致数据丢失。
- 3️⃣ 开启消费者确认机制,模式为
AUTO
。当 Spring 处理完消息后,会自动 ACK(确认消息已消费)。同时,我们设置了最大重试次数为 3 次,如果 3 次重试仍然失败,则将消息投递到异常交换机(Dead Letter Exchange, DLX),由人工处理。
消息幂等性:避免消息重复消费导致数据库数据异常。
- 1️⃣ 使用唯一 ID(如订单号) 作为消息的全局唯一标识,在数据库或 Redis 记录已消费的消息,防止重复写入。
- 2️⃣ 在数据库增加唯一约束(如基于订单号的唯一索引),防止同一数据被重复插入。
- 3️⃣ 采用事务性操作,确保消费逻辑和记录唯一 ID 的操作在同一事务中执行,避免因异常导致状态不一致
应用场景
(1) 订单系统:秒杀 / 外卖 / 电商高并发下的数据库优化
秒杀、外卖、双 11 活动等场景下,大量用户并发下单,数据库瞬间写入压力过大,可能导致死锁、事务冲突、数据库崩溃。
在外卖订单场景下,大量用户并发下单时,我们先在 Redis 预扣减库存,并将订单请求写入 MQ 消息队列。由异步消费者批量读取订单信息,并写入数据库。
- 如果数据库操作成功,则同步更新 Redis 中的库存,确保数据一致性。
- 如果数据库写入失败,则触发库存回滚,恢复 Redis 预扣减的库存,防止数据不一致。
(2)订单状态更新
外卖平台的订单状态(已下单、已支付、已完成)需要频繁更新,直接写数据库会导致大量 update 操作,影响数据库性能。
订单状态变更时,将变更信息放入 MQ 队列,由异步消费者读取并更新数据库。
为什么不用kafka
1订单支付、状态更新必须严格按顺序执行。
- RabbitMQ 支持事务和 ACK 确认机制
- Kafka 没有内置事务,无法保证严格的订单消息顺序!
2.适合短生命周期的消息队列
- RabbitMQ 是短生命周期消息的最佳选择:消息被消费后立即删除,毫秒级延迟
- Kafka 适用于日志、监控等长生命周期的数据流,不适合短时事务!
3.适合处理订单超时
- RabbitMQ 支持 TTL(Time-To-Live)+ 死信队列(DLX):订单消息进入 延迟队列;超时后,消息被路由到 死信队列;消费者监听死信队列,执行订单取消操作
- Kafka 没有 TTL 机制,只能依赖应用程序手动轮询,性能较差。
kafaka
Kafka 更适合大数据分析,不适合事务处理
Kafka 适用于:
-
日志存储(存储时间长,用户行为分析)
-
实时数据流(如用户个性化推荐、ETL 数据处理)
-
分布式事件驱动架构(微服务解耦)
RocketMQ
消息吞吐量能够达到百万级别, 适用于 高吞吐、大规模、分布式事务、日志分析等场景,尤其适合互联网业务,如电商、支付、物联网、数据流处理。
Threadlocal
为什么使用ThreadLocal?
如果每个请求都手动解析 token
并传递用户信息,可能会导致:
- 代码侵入性高,需要在每个方法中传递
token
解析结果,影响代码结构。 - 并发问题,如果多个线程共享同一个
token
解析结果,可能导致数据错乱。
ThreadLocal
方式的优势
- 避免参数层层传递:解析
token
后,将用户信息存入ThreadLocal
,整个请求链中的 拦截器、服务层、控制器 都能直接获取用户信息。 - 线程安全:
ThreadLocal
变量 仅对当前线程有效,不会被其他线程访问,避免并发数据混乱问题。
JWT 适用于整个访问过程,每次请求都应携带 JWT 令牌。为了减少重复解析 JWT 的开销,我们在解析 JWT 后获取 用户 ID 和权限信息,存入 ThreadLocal,在请求周期(单个线程)内使用,避免多次解析 JWT。
人话:如果我发起一个请求,携带JWT,但该请求可能包含多个操作,每个操作不可能多次验证JWT,所以我使用ThreadLocal来存储这个JTW的用户信息,确保多次操作能够对应上
如何使用ThreadLocal来存储和管理用户认证信息
在项目中,如何使用ThreadLocal来存储和管理用户的认证信息?
(1)初始化:在拦截器的 preHandle方法中,从请求中提取JWTToken,并进行校验。一旦校验通过,就把用户的信息从Token中解析出来,并存储到ThreadLocal中。
(2)使用:由于 ThreadLocal
变量在整个线程内有效,可以直接获取用户信息,而不需要传递 userId
参数。这样做的好处是,它消除了通过方法参数传递信息的要,使得方法签名更简洁、逻辑更清晰。
(3)清理:在请求的生命周期即将结束时,例如在返回响应之前,需要显式清除ThreadLocal中的数据。在拦截器的afterCompletion方法中完成清理,确保每个请求结束后清理掉所有关联的数据,防止内存泄漏。
内存泄漏
如果在业务处理完成后未及时清理ThreadLocal存储的数据,这些数据会一直存在于对应线程的ThreadLocalMap中,导致垃圾回收器无法释放这些对象,从而引发内存泄漏。
因为ThreadLocalMap是由一个个Entry构成的数组,并且每个Entry的key是弱引用,这就意味着当触发GC时,Entry的key也就是ThreadLocal就会被回收。如果此时value外部也没有强引用指向的话,那么这个value就永远无法访问了,按道理也该被回收,但是由于entry还在强引用value。那么此时value 就无法被回收。
- 每次使用完毕之后记得调用一下remove()方法清除数据
- ThreadLocal变量尽量定义成static final类型,避免频繁创建ThreadLocal实例。
数据污染
由于线程池中线程会被复用,未清除的ThreadLocal数据可能在下次任务中被错误地使用,进而引发数据污染问题。
在合适的时机手动调用ThreadLocal.remove()方法来清除ThreadLocal中的数据。这样可以确保每个请求或任务开始时ThreadLocal中不会包含上一次请求或任务的数据,从而保证了数据的独立性和正确性。
JWT
jwt服务端如何保证用户是对应用户?
用户登录成功后,服务器生成一个JWT并返回给客户端,客户端在后续请求中将JWT放在请求头中,服务器通过验证 JWT来识别用户。浏览器发起请求,请求登录接口时,如果登录成功,生成一个令牌token,即用户的合法身份凭证。接下来响应数据时,直接将令牌响应给前端。前端接收到令牌之后,可以将令牌存储在cookie。后续的每一次请求都需要将令牌携带到服务端(请求头header中携带,名称为token,值为登录时下发的JWT令牌)。服务端统一拦截请求,判断是否有令牌,如果无,直接拒绝访问,如果有,校验令牌是否有效。在同一次会话的多次请求之间想共享数据,可以将共享数据存储在令牌当中。
JWT的有效时长
基于安全性考虑,JWT 有效期一般是 30 分钟 - 2 小时,配合 Refresh Token(7-30天)
用户在登录时,服务端回返回两个令牌(JWT和RefreshToken),之后用户的每次请求都会携带JWT令牌,后端(redis)会验证JWT的有效期,没有过期则放行。如果JWT过期而RefreshToken没有过期,用户请求会被拒绝,然后服务端会重新生成一个新的JWT,发送给客服端。使客户端能够重新访问。
JWT黑名单
Redis存储的是失效的 JWT ,放在黑名单,每次请求时,检查 JWT 是否在黑名单中。如果在黑名单中,则拒绝访问。黑名单中只存储需要失效的 JWT,避免每次都查询所有 JWT。配置黑名单的过期时间,比如 1-2小时(与JWT失效时间一致),用来限制黑名单条目的生命周期。
完整的JWT流程
短期 JWT + Refresh Token 并结合 JWT 黑名单存储在 Redis
用户登录时:
- 用户登录后,服务器生成一个 短期有效的 JWT(例如,15-30分钟),并将其返回给客户端。
- 同时,生成一个 长效的 Refresh Token(例如 7 天),也返回给客户端。
- 将 Refresh Token 存储在 Redis 中,并设置过期时间(通常比 JWT 长,例如 7 天)。
- JWT 不存 Redis,因为它是自包含的,主要通过签名和过期时间来验证其有效性。
后续请求:
- 每次客户端发起请求时,携带 JWT 令牌,后端首先验证 JWT:
- JWT 的签名和过期时间:验证 JWT 是否有效。
- JWT 是否在 Redis 黑名单中:验证该 JWT 是否被标记为已失效(即用户登出)。
- 如果验证通过,放行请求,进行业务逻辑处理。
- 如果 JWT 无效或在黑名单中,拒绝请求,返回未授权(401)。
JWT 过期时:
- 如果 JWT 过期,客户端会使用 Refresh Token 来获取新的 JWT。
- 服务器验证 Refresh Token 是否存在于 Redis 中,并检查其有效性。如果有效,则返回新的 JWT,客户端更新其持有的 JWT。
用户登出时:
- 用户主动登出时,服务器会将 JWT 加入 Redis 黑名单,以使该 JWT 失效,即使它的有效期未到。
- 删掉 Redis 中存储的 Refresh Token,防止恶意使用。
为什么用到统一拦截?
程序中所开发的增删改查都需要使用以上套路进行登录校验。此时就会出现:相同代码逻辑,每个功能都需要编写,就会造成代码非常繁琐。为了简化这块操作,使用统一拦截技术。拦截浏览器发送过来的所有的请求,拦截到这个请求之后,通过请求来获取之前所存入的登录标记在获取到登录标记且标记为登录成功,说明员工已经登录。如果已经登录,直接放行(访问正常的业务接口)。
密码加密
用户密码的存储采用的是BCrypt算法(哈希+盐),而不是对称加密或非对称加密。
哈希计算成本一般为12
盐的长度固定为 16 字节(128 位),存储时以 22 个 Base64 字符表示
足够防止彩虹表攻击和哈希碰撞
彩虹表攻击 是一种基于预计算哈希值的密码破解技术。攻击者使用预先计算的哈希值表来快速反推出明文密码,而不需要暴力枚举所有可能的密码组合。盐是随机的,即使用户 A 和用户 B 的密码相同,哈希值也不一样.
为什么不需要解密?
-
哈希是单向的,不可逆,即无法通过哈希值反推出原始密码。
-
验证过程 只是重新对用户输入的密码进行哈希计算,然后与数据库中的哈希值进行匹配,而不需要解密。
Websoket
缺点
网络依赖强:客户端与服务器之间的网络不稳定,WebSocket 连接容易中断。--心跳机制
消息乱序:WebSocket 本身会确保消息的顺序性,但当网络中断或连接恢复时,消息的顺序可能会发生错乱。===>消息队列
消息丢失:WebSocket 本身不提供消息持久化功能,因此,如果连接断开或客户端失去连接,未接收到的消息可能会丢失。===>RabbitMQ持久化机制
资源消耗:WebSocket 是长连接协议,每个连接会占用一定的内存和 CPU 资源。对于每个客户端,服务器需要保持一个开放的连接和实时通信通道,这可能导致在大量用户连接时占用大量服务器资源。
5. 缺乏消息持久化
确保连接的稳定性
心跳机制:WebSocket 本身是长连接协议,为了确保连接不被中间网络设备(如路由器、负载均衡器)关闭,系统会周期性地发送心跳消息。心跳消息可以是简单的空消息或一个 "ping" 消息,客户端和服务器都可以发送心跳来检查连接是否仍然有效。
- 客户端心跳:每隔一定时间(如 30 秒或 1 分钟)向服务器发送一个心跳。
- 服务器心跳:每隔一定时间向客户端发送一个心跳,确保客户端在线。
- 如果在一定时间内没有收到心跳回应,则判定为连接已断开,并执行重连操作。
消息可靠性(解决消息丢失和高负载问题)
队列系统(如 RabbitMQ):为了提高消息的可靠性,可以将消息首先写入消息队列(如 RabbitMQ),再通过 WebSocket 传输给客户端。即使 WebSocket 连接断开,消息会被队列保存,连接恢复后,消息可以继续发送。
断线重连机制
在 WebSocket 连接断开后,需要实现重连机制,确保连接的可靠性。自动重连:当 WebSocket 连接出现异常或丢失时,客户端应自动尝试重新连接。、
项目亮点
数据库设计上有没有做过分库分表、索引优化等?能不能举个优化后明显提升的例子
场景:在订单模块中,由于每个用户每天可能产生多个订单,订单表只有一个,订单表数据量迅速增长,查询变慢,偶尔还会出现锁等待问题。
【优化手段一:分表设计】
我们按 user_id 取模分表,将 order
(订单)表分成了 order_0
、order_1
、...,一共 10 张表:
1、路由逻辑封装成工具类,user_id % 10
路由到对应分表;
//所谓“路由封装为工具类”,就是指我们不直接在代码里写:
int tableIndex = userId % 10;
String tableName = "order_" + tableIndex;
而是写成这样:
String tableName = OrderTableRouter.getTableName(userId);
把路由逻辑封装成工具类,是为了做到业务代码无感知、统一管理路由规则、方便扩展路由策略、支持未来自动扩容和动态配置,是高可维护性系统设计的基本功
2、利用 MyBatis 拦截器动态修改 SQL 实现透明分表;
//表面上访问
SELECT * FROM order WHERE user_id = 123;
//实际上访问
SELECT * FROM order_3 WHERE user_id = 123;
需要一个 “SQL 重写机制”:根据 user_id,自动把 SQL 中的表名 order
改成 order_3
需要使用MyBatis拦截器:拦截原始 SQL → 根据 user_id 动态改写 SQL 表名 → 放行执行
MyBatis 拦截器可以在以下生命周期阶段插入自定义逻辑:
- Executor 拦截执行器(执行 SQL 之前);
- StatementHandler 拦截 SQL 预处理;
- ParameterHandler 拦截参数填充;
- ResultSetHandler 拦截结果处理。
通常在 StatementHandler 的 prepare 阶段 动手脚,在 SQL 执行之前
优势有很多,核心是:做到“业务代码无感知”!👇
对比项 | 用拦截器 | 自己拼接 SQL |
---|---|---|
💡 路由逻辑 | 集中封装,统一管理 | 分散在各个 DAO 里 |
✨ 对业务透明 | ✅ 调用方法不变 | ❌ 每次写 SQL 都要加逻辑 |
🧼 SQL 语义清晰 | 仍然写 order 表 | 要写成 order_${userId % 10} |
🔧 运维改动小 | 改路由策略只动拦截器 | 改一次要改 N 个地方 |
☠️ 出错风险 | 小,统一测试 | 高,容易出错、SQL 注入风险 |
3、分表后单表数据量大幅下降,JMeter显示查询和插入性能平均提升 50%+。
补充说明:考虑后期数据迁移与分片路由扩展,我们还预留了“路由策略注册中心”的抽象接口,支持后期平滑扩容。
【优化手段二:索引优化】
我们通过慢 SQL 日志发现 order_status
+ create_time
的组合查询频率非常高:
订单状态查询(比如查看订单是否已完成、取消等)和 时间范围查询(比如查询某个时间段内的订单)。
SELECT * FROM order WHERE user_id = ? AND order_status = ? ORDER BY create_time DESC LIMIT 20
原本只在 user_id 上有索引,导致全表扫描;
👉 优化后新增联合索引:
CREATE INDEX idx_user_status_time ON order(user_id, order_status, create_time DESC);
- 查询耗时从 800ms 降低到 80ms;
- 慢 SQL 日志该类语句几乎消失。
补充说明:
删除了一个无效的冗余索引(user_id 单列),减少了写入时的索引维护开销;
利用
EXPLAIN
分析执行计划,确保走覆盖索引。
explain关心的字段
使用 EXPLAIN
命令时,重点关注以下几个方面:
- 连接类型 (
type
),确保使用高效的索引扫描。=> ALL全表扫描、index索引扫描、range范围扫描、ref
基于索引的查找 - 实际使用的索引 (
key
):显示实际使用的索引。如果为NULL
,说明没有使用索引,可能进行了全表扫描 - 行数预估 (
rows
):显示查询计划预估需要扫描的行数。这个数字越小,通常意味着查询越快。 - 额外信息 (
Extra
):警惕Using filesort
或Using temporary
这类可能影响性能的操作。
项目吞吐量:
- 下单接口的吞吐量大约在 400~500 QPS,因为会涉及库存校验、订单生成、RabbitMQ 推送等多个环节;
- WebSocket 推送能力:单服务器可以支撑 2000+ 长连接,做了心跳检测机制,响应延迟控制在 100ms 以内;
- RabbitMQ 消息消费能力:我们用多线程 + 手动 ack 的方式做了消费者优化,能做到每秒处理约 800~1000 条消息;
- SpringTask 定时任务(订单超时取消):使用线程池并发调度,最多支持每分钟处理上千个待处理订单。
智慧社区
“你项目上线后有没有遇到过性能瓶颈?比如 Redis 的命中率不高,或者线程阻塞等?你是如何排查并优化的?”
我们确实遇到过 Redis 缓存命中率低、接口响应变慢的性能瓶颈,具体发生在社区服务列表查询接口中。
问题现象:
- 监控系统(Prometheus + Grafana)告警接口响应时间大幅上升;
- Redis 命中率低于 60%,数据库 QPS 被打满,CPU 占用飙升;
排查过程:
首先排查缓存命中问题:
- 使用 Redis 自带命令
INFO stats
和MONITOR
查看命中率; - 发现大量缓存 key 是动态拼接的,存在 key 不统一问题(大小写问题)
数据库压力分析:
- 通过慢 SQL 日志分析,发现 service_list 查询未走索引;
- 线程池中部分查询线程 Block,队列堆积严重。
优化方案
Redis 缓存优化:
- 统一缓存 Key 的命名规范,去掉无意义动态参数,全部小写标准化;
- 设置 合理过期时间(TTL),避免热点 key 频繁重建;
- 引入 缓存预热机制,系统启动或数据更新时主动缓存热门数据;
数据库与线程池优化:
- 针对频繁查询接口,添加联合索引确保命中;
- 数据分页接口添加 limit + where 条件组合,避免全表扫描;
- 优化线程池参数配置:增加核心线程数 + 拒绝策略改为 CallerRunsPolicy;
项目吞吐量
关键接口吞吐量(QPS):
- 登录接口:约 300 QPS
- 查询服务列表:约 500~600 QPS
- 数据采集上传接口(异步):约 1000 TPS