# high-concurrency-practice **Repository Path**: calm_java/high-concurrency-practice ## Basic Information - **Project Name**: high-concurrency-practice - **Description**: 高并发实践,测试高并发场景下的事务问题,与性能调优 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2020-03-31 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 目标 > 相关名词:https://ruby-china.org/topics/26221 - 调优,在有限硬件资源的情况下,提供更大的吞吐率,并最终得到吞吐率与硬件,jvm参数的关系 - 调试事务,不同级别锁的影响程度,以及TPS - 秒杀实践 ## 衍生问题思考 ### MySQL数据库 - 两百万数据的 select count 很慢,大约11s,如何优化 - 通过 `explain` 查看走的是否是索引,本项目查询后发现走的是索引,因此不是索引的问题 - 开启 profiling - 先执行要分析的sql,如 `select count(1) from tbname` - 执行 `show profiles`,找到上条查询对应的 `Query_ID` - 执行 `SHOW PROFILE FOR QUERY ${上面的Query_ID}`,查看时间分布 - 发现时间都在`send data`上,对此网上有种说法是,大数据字段,如text,很长长度的varchar等,mysql会将部分数据放在`溢出页`里面, 因此还会走一遍磁盘扫描。但本项目直接查的`count(1)`,没有查询大字段,因此该说法不适合当前情况。继续分析发现,查询时,系统磁盘IO很猛, 按理查的是索引,在内存里面,不会有这么多的磁盘IO,遂想到修改配置文件`my.cnf`,增大`innodb_buffer_pool_size`, 增大的原则是`Data_length + Index_length 值的总和的110%`。修改后重启,查询降低到0.1s,问题解决。(注意第一次就有点慢, 因为刚刚启动,索引数据还没有加载到内存,后续查询很快) - 继续验证上面提到的问题,`select count(detail)`,查询大字段确实比查主键慢,当然原因在于索引,没有涉及到`溢出页`的验证 ## 秒杀实践 > 压测方式:通过 jmeter 模拟 100 用户同时下单 | 标题 | 方式 | 平均响应时间(去掉启动后的第一次) | 数据库数据 | 结论 | 补充 | | ------ | ------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | 秒杀一 | * 无事务 | (16+15+10) / 3 =14ms | * 库存剩余16件,销量79件,且数据库销量有多条相同记录。取的是其中一次测试的数据 * 随着测试次数的增加,库存越来越接近0,超卖越来越少。原因未知,也可能是偶然事件 | * 超卖 * 由于销量的创建是懒存储,当有第一笔订单时才添加销量。因此出现了多条记录,原理类似某些错误单例模式在并发情况下出现的多个实例 * 库存销量之和可能大于100,也可能小于100,原因在于库存和销量的新增不是原子操作,更新库存与更新销量可能分别是两笔订单最后处理的数据库 | | | 秒杀二 | * 方法级别添加事务@Transactional | (32+48+29)/ 3 = 36ms | 库存剩余53件,销量47件,且数据库销量有多条相同记录。库存销量之和始终为100 | * 超卖 * 事务保证了更新库存和更新销量的一致性,因此保证了两者之和为100不变 * 事务增加了消耗,相对而言响应较慢,超卖更多 (也可能是偶然,毕竟差距不大) | | | 秒杀三 | * 悲观锁(有事务) | (20+12+11) / 3 =14ms | 库存剩余0件,销量100件,始终如此,没有超卖 | * 成功秒杀 * 响应很快,并没有因为锁响应速度收到影响 | 一开始没有加事务,发现和方式一效果一样,这是因为悲观锁,或者说排他锁的释放时机是事务结束,没有事务锁不生效 | | 秒杀四 | * 乐观锁(无事务) | (20+12+11) / 3 =14ms | 库存剩余0件,销量100件,始终如此,没有超卖 | * 成功秒杀 * 响应很快,并没有因为锁响应速度收到影响 * 虽然最终的库存和销量是正确的,但过程中查询的两者之和不为100,因为两步是在不同的乐观锁逻辑之中,不像悲观锁,可重复度级别的事务保证了和始终100,这对业务逻辑影响较小,可以通过其它方式实现 | | | 秒杀五 | * 分布式锁+线程池(coreSize,maxSize=1) | (23+10+12) / 3 =15ms | 库存剩余0件,销量100件,始终如此,没有超卖 | * 成功秒杀 * 响应很快,并没有因为锁响应速度收到影响 * 将mysql数据库事务可能的问题转给redis,然后将任务存储到队列串行执行 | * 虽然没有测试吞吐量,但考虑到redis相对mysql,具体更高级别的响应能力,因而此种方式可能有更高的吞吐量 * 由于是异步存储到数据库,可能出现订单数据的延迟 |