秒杀系统架构的设计和优化分析,以我一个小菜鸡,目前是说不出来的o(╥﹏╥)o。
因此呢,我这里仅从本项目已经实现的优化来介绍一下:
本项目中做到了以下优化:
秒杀接口采用md5加密方式防刷。
订单表使用联合主键方式,限制一个用户只能购买该商品一次。
配合Spring事务控制实现简单的优化。
使用redis缓存优化。
Spring的事务控制
Spring的声明式事务通过:传播行为、隔离级别、只读提示、事务超时、回滚规则来进行定义。
传播行为
事务的第一个方面就是传播行为。传播行为定义了客户端与被调用方法之间的事务边界。Spring定义了7中不同的传播行为,传播规则规定了何时要创建一个事务或何时使用已有的事务:
传播行为 含义
PROPAGATION_MANDATORY 表示该方法必须在事务中运行。如果当前事务不存在,则会抛出一个异常
PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立与当前事务进行单独的提交或回滚
PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中,如果当前正在有一个事务运行,则会抛出异常
PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否者,会启动一个新的事务
PROPAGATION_REQUIRES_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动
PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
隔离级别
声明式事务的第二个维度就是隔离级别。隔离级别定义了一个事务可能受其他并发事务影响的程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,但是可以回导致以下问题:
更新丢失:当多个事务选择同一行操作,并且都是基于最初的选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。
脏读:事务A读取了事务B已经修改但为提交的数据。若事务B回滚数据,事务A的数据存在不一致的问题。
不可重复读:书屋A第一次读取最初数据,第二次读取事务B已经提交的修改或删除的数据。导致两次数据读取不一致。不符合事务的隔离性。
幻读:事务A根据相同条件第二次查询到的事务B提交的新增数据,两次数据结果不一致,不符合事务的隔离性。
理想情况下,事务之间是完全隔离的,从而可以防止这些问题的发生。但是完全的隔离会导致性能问题,因为它通常会涉及锁定数据库中的记录。侵占性的锁定会阻碍并发性,要求事务互相等待以完成各自的工作。
因此为了实现在事务隔离上有一定的灵活性。因此,就会有多重隔离级别:
隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
SIOLATION_READ_UNCOMMITTED 允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务提交的数据。可以阻止脏读,但是幻读或不可重复读仍可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果是一致的,除非数据是被本事务自己所修改,可以阻止脏读和不可重复读,但幻读仍可能发生
ISOLATION_SERIALIZABLE 完全服从ACID的事务隔离级别,确保阻止脏读、不可重复读、幻读。这是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库来实现的
回滚规则
Spring的事务管理器默认是针对unchecked exception回滚,也就是默认对Error异常和RuntimeException异常以及其子类进行事务回滚。
也就是说事务只有在遇到运行期异常才会回滚,而在遇到检查型异常时不会回滚。
这也就是我们之前设计Service业务层逻辑的时候一再强调捕获try catch异常,且将编译期异常转换为运行期异常。
简单优化
这里我们还是要关注一些项目中的两个核心的业务:1.减库存;2.插入购买明细。我们以一张图来看一下这两个操作的事务执行流程:
可以看到我们的秒杀操作主要是基于Mysql的事务进行的,而基于MySQL事务的秒杀操作主要瓶颈是网络延迟和GC(Java垃圾回收机制)。执行一条update语句首先要拿到MySQL的行级锁rowLock,而我们要解决的就是如何降低update对rowLock的持有时间。
我们先了解一下MySQL的InnoDB储存引擎的行级锁(rowLock):
行锁的劣势:开销大;加锁慢;会出现死锁
行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强
加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁:
共享锁:select * from tableName where … + lock in share more
排他锁:select * from tableName where … + for update
InnoDB和MyISAM的最大不同点有两个:一,InnoDB支持事务(transaction);二,默认采用行级锁。加锁可以保证事务的一致性,可谓是有人(锁)的地方,就有江湖(事务)。
详细的介绍请看博文:MySQL 表锁和行锁机制
所以在此基础上我们可以进行简单的优化:
很简单,就是调整update和insert操作的执行顺序。目的就是为了缩短update对rowLock的持有时间提高性能,因为我们的查询语句使用了insert ignore into xx的方式来避免重复秒杀,那么闲执行insert语句可以在插入时就排除可能存在重复秒杀的操作,这样就不用再向下执行更新操作了。在一定程度上降低了一倍的rowLock持有时间。
下面是源码:
@Override
@Transactional
public SeckillExecution executeSeckill(long seckillId, BigDecimal money, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
throw new SeckillException(“seckill data rewrite”);
}
//执行秒杀逻辑:1.减库存;2.储存秒杀订单
Date nowTime = new Date();
try {
//记录秒杀订单信息
int insertCount = seckillOrderMapper.insertOrder(seckillId, money, userPhone);
//唯一性:seckillId,userPhone,保证一个用户只能秒杀一件商品
if (insertCount <= 0) {
//重复秒杀
throw new RepeatKillException(“seckill repeated”);
} else {
//减库存
int updateCount = seckillMapper.reduceStock(seckillId, nowTime);
if (updateCount <= 0) {
//没有更新记录,秒杀结束
throw new SeckillCloseException(“seckill is closed”);
} else {
//秒杀成功
SeckillOrder seckillOrder = seckillOrderMapper.findById(seckillId);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, seckillOrder);
}
}
} catch (SeckillCloseException e) {
throw e;
} catch (RepeatKillException e) {
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
//所有编译期异常,转换为运行期异常
throw new SeckillException(“seckill inner error:” + e.getMessage());
}
}
Redis缓存优化
准备
如果想使用Redis缓存进行优化,首先你需要连接什么是Redis缓存,以及Spring提供的一种操作Redis缓存的框架:Spring-data-redis。最终要的是:你需要在本地电脑上安装好Redis缓存服务器:
所以呢,我推荐你看一下我的几篇文章:
Redis即Spring-data-redis入门学习
优雅的整合SSM+Shiro+Redis+Solr框架
在看了上面的文章后相信你已经初步了解了使用Spring-data-redis操作Redis缓存服务器,下面讲解针对本项目的缓存优化实现:
启动安装好的Redis缓存服务器,修改项目中的 resources/application.yml 关于Redis和Jedis的配置,
例中我使用的本地Redis服务器:host:127.0.0.1;port:6379
沈阳妇科医院哪家好:http://iask.sina.com.cn/h-fk
沈阳性病医院哪家好:http://xb.029nk.com/