# second-kill-end **Repository Path**: jeavenwong/second-kill-end ## Basic Information - **Project Name**: second-kill-end - **Description**: 秒杀系统项目的后端代码 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-06-26 - **Last Updated**: 2021-06-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # second-kill-end ## 介绍 这是秒杀系统项目的后端。 对应的前端项目是 https://gitee.com/jeavenwong/second-kill-ui ## 开发环境 - OS: Windows 10 - JDK: Zulu OpenJDK 1.8 - Build: Maven 3.x - IDE: Intellij IDEA - Spring Boot 2.0.1.RELEASE - Servlet Container: Tomcat 8.x - Components: ZooKeeper 3.4.6(作分布式锁),Redis X64 3.2.100 (作分布式锁),Mysql 8.0.20 (数据持久化) - Tools: JMeter5.x, PostMan, Mysql Workbench ## 系统整体业务流程 ![process](https://gitee.com/jeavenwong/second-kill-end/raw/master/pic/process.png) ## 技术栈 - 前端框架: Vue 2.x - 后端框架: Spring Boot 2.x - ORM框架: Mybatis - MVC: Spring MVC - 数据持久化: Mysql , Redis - 消息中间件: RabbitMQ - 分布式组件:Redisson , ZooKeeper - 安全框架: Shiro ## 技术难点 #### 生成分布式唯一ID - 传统方式: 时间戳 + 一定位数的随机数 - 雪花算法:https://github.com/souyunku/SnowFlake 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。 有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。 而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。 比较: 比较一:传统的方式要么太长,没法排序;要么需要依赖中间件。 比较二:雪花算法-(整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,高效率) #### 使用 RabbitMQ 消息中间件来异步发送邮件 秒杀抢购商品成功后,如果直接发送邮件,会导致方法执行比较耗时(2-3s),比如需要两秒的时间,但是使用消息队列来进行异步发送的话,不会影响到主要逻辑的执行,不会太耗时。从而做到低延迟和高响应,提高用户体验。同时,消息中间件也将邮件服务和其他服务相解耦。 - 秒杀成功会生成订单详细信息,然后异步发送到指定的 rabbitMQ 队列中。 - 从 rabbitMQ 队列里面消费消息,并且使用spring-boot-starter-mail邮件服务来发送邮件。 #### 把不同的业务模块进行服务化 - 把不同的服务,比如rabbitMQ的服务写成一个@service,将邮件服务写成一个@service,而不是写在一起,这样可以实现服务之间的解耦。 - 大的服务拆分成小的彼此低耦合的小的服务,rabbitMQ消息中间件也是一种解耦的方式。 - 业务模块的服务化和功能模块的解耦。(比如 秒杀服务ItemService、邮件服务MailServer、消息队列接收RabbitReceiverService等服务模块相互解耦) #### 死信队列-超时未支付订单的失效 普通消息队列:即时消费。 死信队列:可以指定时间延迟消费。 - 秒杀订单消息生成后直接进入到死信队列中,死信队列会延迟一段时间,这段时间其实就是给用户对订单处理的时间,如果用户没有完成支付,那超过了等待时间之后,消息就会被最终的真正的队列消费掉,然后同时进行一些逻辑处理,比如判断用户没有处理的话,就让订单的状态失效。 - 这个和微信两个人之间转账有异曲同工之处。微信转账需要确认才能成功收款,否则超时就会退回。 RabbitMQ 死信队列缺陷:有许多订单在某个TTL集中失效,但是恰好RabbitMQ服务器挂了。 高可用解决方案: 1. 采用 RabbitMQ 集群。 2. 采用RabbitMQ的监控,挂了超过2分钟及时通知运维人员。 3. 数据少的时候也可以采用@Scheduled定时任务来查询订单是否超过TTL。 定时任务的正确使用完整解决方案:配合线程池一起使用,规避单一线程处理多个任务的缺陷,支持多线程处理。 #### Jmeter 高并发测试出现库存为负数 原因:产生的多个线程对“同一段操作共享数据的代码”进行并发操作,从而出现并发安全的问题! 核心解决方案:分布式锁解决共享资源在高并发访问时出现的“并发安全”的问题。 协助解决方案:对于瞬时流量,并发流量进行限流。(项目中是使用rabbitMQ进行接口的限流,有条件时还能进行网关层面的的限流) 辅助方案一:应用(秒杀系统)和中间件(RabbitMQ和Redis等)服务做集群部署,提高高可用性和稳定性。 辅助方案二:数据库Mysql做主备部署,如可以搭建一个Master写库,多个Slave读库实例的服务。 其他方案:前端页面进行数据缓存。 #### 项目中高并发下的具体的优化措施 分布式锁的部分可以参考我的博客:https://jeavenwong.gitee.io/2020/07/06/%E7%A7%92%E6%9D%80%E7%B3%BB%E7%BB%9F%E4%B8%AD%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%BA%94%E7%94%A8/ - “查询以及更减库存”时需要判断当前“可被更减的数量”是否仍然大于0。 - 基于Redis的分布式锁优化抢单逻辑。(SETNX + EXPIRE 联合使用) - 为了防止 Redis 服务器宕机导致某些key无法过期,所以可以采用基于Redission的分布式锁。 redission官方文档 : [https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95](https://github.com/redisson/redisson/wiki/目录) 了解下 Redisson 的监控锁的看门狗机制,即如何防止在 redisson 节点宕机产生死锁。 - 基于ZooKeeper的分布式锁进行优化。 因为ZooKeeper底层会创建大量临时节点,时间开销会有点大,所以在QPS不是很大的时候可以考虑使用ZooKeeper,而如果大量QPS的时候需要快速的话,就使用Redisson比较好。 #### 请求限流措施 使用 RabbitMQ 对秒杀请求进行限流。 #### 用户认证授权 - 集成了 Shiro 安全框架,对用户进行登录认证权限管理,对密码进行加密存储。