Skip to content

个人技能

熟悉Java面向对象、常用数据结构、多线程等专业知识,熟悉常用设计模式。 熟悉MySQL索引、事务和常见的SQL性能优化,了解分库分表相关算法与基本原理。 熟悉Redis线程模型、常用数据结构和应用场景,了解缓存击穿、穿透、雪崩等缓存高并发使用场景。 熟悉Redisson分布式锁的二次封装和应用场景。 熟悉tomcat的HttpServletRequest的异步调用(HttpServletRequest源码?) 消息队列??? 熟悉Spring、SpringBoot、MyBatis等开发框架技术,了解AOP、IOC等技术基本原理。了解Vue等前端开发技术。 了解SpringCloud、Nacos、RabbitMQ、Sharding-JDBC、ElasticSearch等分布式中间件的使用和基本原理。 掌握Linux常用命令,了解Docker项目部署的流程, 能够使用Github Actions和Cloudflare实现CI/CD和域名设置

  • 论文发表 掌握pytorch深度学习模型优化思路以及编码调试,独立编码的一篇图神经网络论文在投CCF-A会议(二作)

购票系统

微服务架构的售票系统,用户可以进行注册、登录、查询节目主页/列表、详情、座位信息、生成订单、支付、查询订单详情/列表等功能。

基因标识法

使用用户基因标识算法生成订单ID,不依赖路由表的情况下,实现用户ID和订单ID的分库分表查询(订单业务中,既通过订单号查询订单,也想通过userId查询该用户下的所有订单信息技术精华-解锁分库分表新姿势:基因法完全解读

多级缓存查询节目数据

用户购票高并发压力

[[用户购票生成订单 消息队列异步DB]] 总结起来就是 先获得锁,业务参数验证,然后获取座位和余票信息,接着验证Redis中余票是否充足,座位是否可以购买。然后拼接lua需要的数据,接着在lua中完整扣减的操作,然后生成订单

拆分分布式锁粒度,利用服务实例锁(本地锁)优化

幂等性(避免同一个用户重复下单)

组合模式业务验证

放到了获得锁的步骤之前,因为锁粒度拆分后,需要票档id,所以票档id需要先验证 (用户购票验证逻辑的类型为program_order_create_check,验证的逻辑有 - 验证座位参数 - 将节目缓存 - 验证缓存是否存在节目数据 - 验证用户是否存在

本地锁(当前服务实例锁)

先用本地锁将没有获得锁的请求拦在外,获得本地锁后,再去获得分布式锁,这样可以减少对redis的压力本地锁和分布式锁的键为 节目id+票档id,这样同一节目下不同票档的用户请求依旧可以并发执行

确实还有问题,就是本地锁的内存压力,每个节目票档都会有一把本地锁,那么当请求多了,本地锁的对象也会增多,这就给系统造成内存压力,时间长了就会内存溢出。

所以要有一个过期时间的机制,**当某个节目+票档在一段时间内没有访问的话,那么就把对应的锁对象设置过期,等gc时直接回收掉。 一般设计这种节目+票档 对应 锁 的关系基本都是用的Map结构,线程不安全的有HashMap,线程安全的有ConcurrentHashMap,这个结构是要管理该实例下所有本地锁对象的,肯定是要线程安全的,也就是ConcurrentHashMap,但ConcurrentHashMap并没有设置过期时间的功能

所以要换一种结构叫Caffeine,基于Java 1.8的高性能本地缓存库,由Guava改进而来,而且在Spring5开始的默认缓存实现就将Caffeine代替原来的Google Guava,官方说明指出,其缓存命中率已经接近最优值。实际上Caffeine这样的本地缓存和ConcurrentMap很像,即支持并发,并且支持O(1)时间复杂度的数据存取。二者的主要区别在于:

  • ConcurrentMap将存储所有存入的数据,直到你显式将其移除;
  • Caffeine将通过给定的配置,自动移除“不常用”的数据,以保持内存的合理占用。

因此,一种更好的理解方式是:Cache是一种带有存储和移除策略的Map。

分布式锁(数据一致性)

  • 锁的类型 公平锁(FIFO,维护等待队列,进出队列存在切换上下文开销,无线程饥饿) 非公平锁(非FIFO,自选等待,高效率),也叫可重入锁

  • 锁的粒度 可以根据要购买的票档类型来获取多个锁,还是以这个例子为例,这时请求1就要同时获取一等票和二等票两把锁了,执行完后再释放这两把锁,有人可能会想如果有多把锁会不会有死锁的问题?

这里要把加锁的顺序和解锁的顺序处理好,以及还要把已获得锁的请求,即使在业务执行出现异常后也要能安全的解开,把这两点做好了,就不会出现死锁的问题了

就算本地锁和分布式锁都是公平锁了,用户获得锁的顺序就是公平的吗?其实并不是 其实本地锁+分布式锁在全部采用公平锁的情况下,也只能保证局部有序,并不能保证全局有序

加锁流程总结:

  1. 初始化锁集合:为每个票档ID创建对应的本地锁和分布式锁,本地锁收集到localLockList集合中,分布式锁收集到serviceLockList集合中
  2. 加锁:
  • 本地锁加锁:遍历本地锁集合,尝试加锁,如果加锁成功,将该锁添加到加锁成功的本地锁localLockSuccessList集合中。如果加锁过程中遇到异常,则停止加锁流程。
  • 分布式锁加锁:遍历分布式锁集合,尝试加锁,如果加锁成功,将该锁添加到加锁成功的分布式锁serviceLockSuccessList集合中。同样,如果加锁过程中遇到异常,则停止加锁流程。
  1. 执行业务逻辑:在所有锁成功加锁之后,执行订单创建业务逻辑
  2. 解锁:
  • 分布式锁解锁:从加锁成功的分布式锁集合中,按照逆序遍历解锁每一个分布式锁
  • 本地锁解锁:从加锁成功的本地锁集合中,按照逆序遍历解锁每一个本地锁

Redis键值对设计

在向Redis中存放的余票数据的key是 d_mai_program_ticket_remain_number_resolution_节目id_票档id 在向Redis中存放的座位数据的key是:

  • d_mai_program_seat_no_sold_resolution_hash_节目id_票档id
  • d_mai_program_seat_lock_resolution_hash_节目id_票档id
  • d_mai_program_seat_sold_resolution_hash_节目id_票档id

在设计缓存结构的时候key命名除了节目id还有票档id

**V3策略思考无锁化

V2策略:

利用本地锁 , 减轻 Redission 网络带宽压力 , 通过 Redisson 保证并发安全

取演出时间、票档、座位、余票(多次 Redis 往返 , 分布式锁竞争 ), Java 端做票档余量、座位状态、价格比对 , 再交给单个 Lua 脚本做最后的原子更新。

V3策略:

利用本地锁保证互斥执行 , 全部校验逻辑都写在 Lua 脚本里,

在 Redis 端完成:票档余量检查 , 座位状态检查 , 价格一致性检查 , 相邻座位算法 , 锁定座位和扣除票档数量操作

单次往返,一切都在 Redis 单线程原子完成 , 锁竞争从分布式锁变为本地锁 , 高并发下 Redis 只执行脚本,不做上下文切换。

V2太依赖Redis: 目前的逻辑确实有在座位逻辑那里,做了座位状态验证和余票数量验证逻辑,**但它是从redis中获取然后再Java中做的验证,然后再组装数据在redis中运行修改状态,再加上开头都用了分布式锁也是借助redis,这多次操作redis,也就造成了多次的网络性能的消耗,**要知道网络请求对性能的影响可以说是和慢sql的影响不相上下。

“无锁化”的意思值得是将分布式锁去掉,以此来减少Redis和网络的压力。但是本地锁还是要保留的,因为在前一篇章节介绍过,本地锁的作用就是来缓解分布式锁的压力。所以不能去除。 订单创建,进行优化,一开始直接利用lua执行判断要购买的座位和票的数量是足够,不足够直接返回,足够的话进行相应扣除,这样既能将大量无用的抢购请求直接返回掉,又可以实现无锁化 **在lua执行过程中,将对应的异常code码和成功执行后生成的座位数据组装成了ProgramCacheCreateOrderData对象 接着通过ProgramCacheCreateOrderData对象的code就可以判断是否执行成功,如果失败,根据code码从BaseCode中获取异常提示信息,然后抛出业务异常直接拒绝继续执行 ** 问题 lua脚本的 只能在单个redis中保证原子性 , 如果在redis集群中, lua脚本无法实现跨节点的 原子性
所以要保证一次要操作的所有的 key都 被集中在一个 redis中, 否则 这个业务可能无法 原子执行 [[V3策略在Redis集群场景下如何解决key分散]]

命令模式 封装本地锁

当获得锁成功后,通过执行 lockTask.execute() 来实现执行任务流程。而 lockTask 是一个接口,当调用 localLockCreateOrder 方式时,可以将加锁逻辑当做一个任务传入此方法,类型就是 lockTask,这样就可以实现复用了,这样方式就是 命令模式。在多线程中的 RunnableCallable 就是用到了这种方式。 [[命令模式复用本地锁]]

等待归纳

  • 利用工厂模式策略模式提供多种类型的锁,提供自定义注解+AOP、方法操作、接口实现三种加锁方式,并解决了锁与事务的时序问题。

(包括:公平锁、非公平锁、读锁、写锁) 通过@Order控制切面执行顺序,确保加锁在事务开启前、释放锁在事务提交后,解决了锁与事务的时序问题。

  • 结合本地锁和分布式锁以及标识符的优化方案,防止用户重复点击和MQ中间件的重复消费。

  • 通过Redisson分布式锁+Redis记录的双重保障机制,支持多种失败策略(抛异常、返回null、执行降级逻辑),确保在高并发场景下的幂等性。

  • 统一管理初始化顺序:利用模板和策略模式将项目初始化行为进行封装,**只需实现接口即可,统一管理执行动作和顺序。**通过ApplicationRunner在Spring容器启动完成后按order顺序执行初始化任务(如缓存预热、配置加载),确保初始化操作的可控性和可维护性。

  • 利用组合模式和树形结构设计验证基础组件,实现统一管理复杂的业务验证逻辑。将购票前的多个验证环节(库存验证、用户资格验证、场次验证等)组装成树形结构,支持灵活的验证规则组合和复用,符合开闭原则。

  • 设计分布式链路ID,在上千个服务中通过此ID能够快速找到对应的请求链路,极大提高排查问题的效率。基于MDC机制实现链路ID的全链路透传,配合ELK日志系统,能够快速定位分布式环境下的问题根源。

  • OpenTelemetry+Prometheus+Tempo+Loki实现链路追踪、日志、指标监控,

4.1 技术架构亮点

  1. 分布式ID+雪花算法优化 - 解决K8s环境下ID重复,Lua+Redis实现workerId自动分配
  2. 分布式锁+多种锁类型 - 工厂+策略模式,提供多种锁类型和加锁方式,兼容事务
  3. 高性能延迟队列 - Redisson+分片+线程池,大幅提升延迟任务处理能力
  4. 多级缓存架构 - Caffeine+Redis+数据库三级缓存,演出时间淘汰策略

4.2 业务价值亮点

  1. 基因法+虚拟分片 - 基因位分离实现数据均匀分布,资源利用率50%→100%,支持灵活扩容
  2. Redis库存扣减 - Lua脚本保证原子性,吞吐量提升26%,防止超卖
  3. 多级缓存+击穿解决 - 读写锁+双重检测+先查询后加锁,三级缓存架构
  4. 异步创建订单 - Kafka异步处理+操作流水,吞吐量提升26%,数据可恢复
  5. MQ消息记录 - 消息全生命周期记录,自动对账及时发现异常
  6. API防刷+验证码 - 灵活的限流规则,布隆过滤器防缓存穿透