若是没有事务的支持,损失最大的无疑是我们的用户和商家。在MySQL中,它内置的事务机制,可以准确的帮我们完成减库存和记录用户购买明细的过程。
当用户A秒杀id为10的商品时,此时MySQL需要进行的操作是:1.开启事务。2.更新商品的库存信息。3.添加用户的购买明细,包括用户秒杀的商品id以及唯一标识用户身份的信息如电话号码等。4.提交事务。若此时有另一个用户B也在秒杀这件id为10的商品,他就需要等待,等待到用户A成功秒杀到这件商品然后MySQL成功的提交了事务他才能拿到这个id为10的商品的锁从而进行秒杀,而同一时间是不可能只有用户B在等待,肯定是有很多很多的用户都在等待拿到这个行级锁。秒杀的难点就在这里,如何高效的处理这些竞争?如何高效的完成事务?在后面第4个模块如何进行高并发的优化为大家讲解。
我们只实现秒杀的一些重要功能:
两个实体,秒杀实体和秒杀成功实体,如下。
public class Seckill {
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;
// 省略getter和sette以及toString方法
...
}
public class SuccessKilled {
private long seckillId;
private long userPhone;
private short state;
private Date createTime;
// 多对一
private Seckill seckill;
}
SUCCESS(1, "秒杀成功"),
END(0, "秒杀关闭"),
REPEAT_KILL(-1, "重复秒杀"),
INNER_ERROR(-2, "系统异常"),
DATA_REWRITE(-3, "数据篡改");
dao层对应着这两个实体的dao,SeckillDao和SuccessKilledDao
public interface SeckillDao {
// 减库存
int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);
// 根据id查询秒杀对象
Seckill queryById(long seckillId);
// 根据偏移量查询秒杀商品列表
List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
// 使用存储过程执行秒杀
void killByprocedure(Map<String, Object> paramMap);
}
public interface SuccessKilledDao {
// 插入购买明细,可过滤重复
int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
// 根据Id查询SuccessKilled并携带秒杀产品对象实体
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
}
dao层接口对应的xml文件如下
<mapper namespace="org.seckill.dao.SeckillDao">
<update id="reduceNumber">
update seckill
set
number = number - 1
where seckill_id = #{seckillId}
and start_time #{killTime}
and end_time >= #{killTime}
and number > 0
update>
<select id="queryById" resultType="Seckill" parameterType="long">
select seckill_id, name, number, start_time, end_time, create_time
from seckill
where
seckill_id = #{seckillId}
select>
<select id="queryAll" resultType="Seckill">
select seckill_id, name, number, start_time, end_time, create_time
from seckill
order by create_time desc
limit #{offset}, #{limit}
select>
<select id="killByprocedure" statementType="CALLABLE">
call execute_seckill(
#{seckillId, jdbcType=BIGINT, mode=IN},
#{phone, jdbcType=BIGINT, mode=IN},
#{killTime, jdbcType=TIMESTAMP, mode=IN},
#{result, jdbcType=INTEGER, mode=OUT}
)
select>
mapper>
<mapper namespace="org.seckill.dao.SuccessKilledDao">
<insert id="insertSuccessKilled">
insert ignore into success_killed(seckill_id, user_phone, state)
values(#{seckillId}, #{userPhone}, 0)
insert>
<select id="queryByIdWithSeckill" resultType="SuccessKilled" parameterType="long">
select
sk.seckill_id,
sk.user_phone,
sk.state,
sk.create_time,
// 别名,省略了as,为了让mybatis知道是SuccessKilled中的Seckill属性
s.seckill_id "seckill.seckill_id",
s.name "seckill.name",
s.number "seckill.number",
s.start_time "seckill.start_time",
s.end_time "seckill.end_time",
s.create_time "seckill.create_time"
from
success_killed sk
inner join seckill s
on
sk.seckill_id = s.seckill_id
where sk.seckill_id = #{seckillId}
and sk.user_phone = #{userPhone}
select>
mapper>
mybatis中#{}与${}的区别
#
将传入的数据都当成一个字符串,#
方式能够很大程度防止sql注入。$
将传入的数据直接显示生成在sql中,$
方式无法防止Sql注入。$
方式一般用于传入数据库对象,例如传入表名。 例子:其实区别很简单的,举个例子大家就会明白的。写一句SQL-例如:select * from user_role where user_code = “100”;
这句话而言,需要写成 select * from ${tableName} where user_code = #{userCode}
所以,$符是直接拼成sql的 ,#符则会以字符串的形式 与sql进行拼接。
SQL注入
SQL注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。
例子:
考虑一下登录表单
<form action="/login" method="POST">
<div>Username: <input type="text" name="username" /></div>
<div>Password: <input type="password" name="password" /></div>
<div><input type="submit" value="登陆" /></div>
</form>
如果用户输入的用户名如下,密码任意
'myuser' or 'foo' = 'foo' --
那么我们的SQL变成了如下所示:
SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx'
在SQL里面--
是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。
dao层的接口方法参数中为什么都要加上@Param
注解
因为java没有保存行参的记录,java在运行的时候会把List
中的参数变成这样:queryAll(int arg0,int arg1)
,这样我们就没有办法去传递多个参数。所以需要使用@Param
注解给方法参数命名,然后在xml文件的该dao层方法对应的sql语句中就可以正常使用@Param
注解的参数名。
建立spring-dao.xml文件,内容如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${driver}" />
<property name="jdbcUrl" value="${url}" />
<property name="user" value="${username}" />
<property name="password" value="${password}" />
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<property name="autoCommitOnClose" value="false" />
<property name="checkoutTimeout" value="1000" />
<property name="acquireRetryAttempts" value="2" />
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="typeAliasesPackage" value="org.seckill.entity" />
<property name="mapperLocations" value="classpath:mapper/*.xml" />
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="org.seckill.dao" />
bean>
<bean id="redisDao" class="org.seckill.dao.cache.RedisDao">
<constructor-arg index="0" value="localhost">constructor-arg>
<constructor-arg index="1" value="6379">constructor-arg>
bean>
beans>
高并发优化点:
一组sql组成的事务,这组事务在java客户端完成 —> 这组事务在服务器端完成
使用存储过程:整个事务在MySQL端完成
本质也是优化网络延迟和GC的干扰
优化点1:使用redis来缓存Seckill秒杀商品对象
方法:“seckill:” + seckillId —> Seckill字节数组
实现:建立一个RedisDao类用来实现get和set对象方法,使用Google提供的Protostuff来实现序列化和反序列化,因为性能非常高,比Java自己提供的序列化机制效率好。
RedisDao的实现如下
package org.seckill.dao.cache;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import org.seckill.entity.Seckill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisDao {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private final JedisPool jedisPool;
public RedisDao(String ip, int port) {
jedisPool = new JedisPool(ip, port);
}
// schema用来描述pojo对象的结构和属性信息
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);
public Seckill getSeckill(long seckillId) {
// redis操作逻辑
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckillId;
// 并没有实现内部序列化操作
// get -> byte[] -> 反序列化 -> Object(Seckill)
// 采用自定义序列化
// protostuff : pojo
byte[] bytes = jedis.get(key.getBytes());
// 从缓存中获取到
if (bytes != null) {
// 空对象
Seckill seckill = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
// seckill 被反序列化
return seckill;
}
} finally {
jedis.close();
}
} catch(Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
public String putSeckill(Seckill seckill) {
// 序列化 set -> Object(Seckill) -> byte[]
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckill.getSeckillId();
byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
// 超时缓存
int timeout = 60 * 60; // 1小时
String result = jedis.setex(key.getBytes(), timeout, bytes);
return result;
} finally {
jedis.close();
}
} catch(Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
}
将RedisDao注入到Spring容器中,在spring-dao.xml文件中配置如下
然后在SeckillServiceImpl中改造暴露秒杀接口方法exposeSeckillUrl(long seckillId)
优化前Seckill对象都是从数据库中获取
seckill = seckillDao.queryById(seckillId);
if (seckill == null) {
return new Exposer(false, seckillId);
}
优化后分三步:1. 从redis中获取 2. 如果redis中没有,从数据库获取 3. 将从数据库获取到的Seckill对象存入redis中
// 优化点:缓存优化:超时的基础上维护一致性,数据只缓存1小时
// 1 访问redis
Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
// 2 访问数据库
seckill = seckillDao.queryById(seckillId);
if (seckill == null) {
return new Exposer(false, seckillId);
} else {
// 3 放入redis
redisDao.putSeckill(seckill);
}
}
优化点2:通过调整insert和update的执行顺序将网络延迟和GC减少一倍,减少行级锁到commit过程的时间
网络延迟和GC:是指执行完sql语句然后返回结果到java客户端的过程中的网络延迟和GC
关于先执行insert与先执行update的区别:
先update再insert,会有2倍的网络延迟和GC
先insert再update,只有一倍的网络延迟和GC
优化点3:事务sql在MySQL端执行(存储过程)
存储过程定义
-- 秒杀执行存储过程
delimiter $$ -- console ; 转换为 $$
-- 定义存储过程
-- 参数:in 输入参数;out 输出参数
-- row_count() 返回上一行修改类型sql(insert, delete, update)的影响行数
-- row_count: 0:未修改数据 >0:表示修改的行数 <0:sql错误/未执行修改sql
create procedure `seckill`.`execute_seckill`
(in v_seckill_id bigint, v_phone bigint, in v_kill_time timestamp, out r_result int)
BEGIN
declare insert_count int DEFAULT 0;
start transaction;
insert ignore into success_killed
(seckill_id, user_phone, state)
values(v_seckill_id, v_phone, 0);
select row_count() into insert_count;
if (insert_count = 0) THEN
ROLLBACK;
set r_result = -1;
elseif (insert_count < 0) THEN
ROLLBACK;
set r_result = -2;
else
update seckill
set number = number - 1
where seckill_id = v_seckill_id
and start_time <= v_kill_time
and end_time >= v_kill_time
and number > 0;
select row_count() into insert_count;
if (insert_count = 0) THEN
ROLLBACK;
set r_result = 0;
elseif (insert_count < 0) THEN
rollback;
set r_result = -2;
else
commit;
set r_result = 1;
end if;
end if;
end;
$$
-- 存储过程定义结束
delimiter ;
-- 设置变量
set @r_result = -3;
-- 执行存储过程
call execute_seckill(1003, 18801082263, now(), @r_result);
-- 返回结果
select @r_result;
-- 存储过程
-- 1 存储过程优化:事务行级锁持有的时间
-- 2 不要过度依赖存储过程
-- 3 简单的逻辑可以应用存储过程
-- 4 QPS:一个秒杀单6000/qps
连接上MySQL的seckill数据库,在命令行中执行上面的代码,就创建了seckill数据库的存储过程
然后再java客户端调用存储过程,如下
// web层 只用将java客户端执行秒杀改为使用存储过程执行秒杀
SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, userPhone, md5);
// SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);
// service层
public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
if (md5 == null || !md5.equals(this.getMD5(seckillId))) {
return new SeckillExecution(seckillId, SeckillStateEnum.DATA_REWRITE);
}
Date killTime = new Date();
Map<String, Object> map = new HashMap<String, Object>();
map.put("seckillId", seckillId);
map.put("phone", userPhone);
map.put("killTime", killTime);
map.put("result", null);
// 执行存储过程,result被赋值
try {
seckillDao.killByprocedure(map);
// 获取result
int result = MapUtils.getInteger(map, "result", -2);
if (result == 1) {
SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, sk);
} else {
return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
}
}
// dao层
void killByprocedure(Map<String, Object> paramMap);
<!-- mybatis调用存储过程 -->
// CALLABLE专门用来调用存储过程
<select id="killByprocedure" statementType="CALLABLE">
call execute_seckill(
#{seckillId, jdbcType=BIGINT, mode=IN},
#{phone, jdbcType=BIGINT, mode=IN},
#{killTime, jdbcType=TIMESTAMP, mode=IN},
#{result, jdbcType=INTEGER, mode=OUT}
)
</select>
测试:
使用存储过程
// 使用存储过程
// 第一次测试 050 - 060 10ms
23:06:15.008 [http-nio-8090-exec-58] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Preparing: call execute_seckill( ?, ?, ?, ? )
23:06:15.050 [http-nio-8090-exec-58] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Parameters: 1000(Long), 18801082263(Long), 2018-03-04 23:06:15.006(Timestamp)
23:06:15.060 [http-nio-8090-exec-58] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession[org.apache.ibatis.session.defaults.DefaultSqlSession@5ed3e3d]
// 第二次测试 783 - 792 9ms
23:11:54.778 [http-nio-8090-exec-68] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Preparing: call execute_seckill( ?, ?, ?, ? )
23:11:54.783 [http-nio-8090-exec-68] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Parameters: 1001(Long), 18801082263(Long), 2018-03-04 23:11:54.777(Timestamp)
23:11:54.792 [http-nio-8090-exec-68] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58bcc2fc]
// 第三次测试 512 - 516 4ms
23:13:40.505 [http-nio-8090-exec-69] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Preparing: call execute_seckill( ?, ?, ?, ? )
23:13:40.512 [http-nio-8090-exec-69] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Parameters: 1002(Long), 18801082263(Long), 2018-03-04 23:13:40.504(Timestamp)
23:13:40.516 [http-nio-8090-exec-69] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a21565f]
// 第四次测试 848 - 852 4ms
23:14:58.839 [http-nio-8090-exec-70] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Preparing: call execute_seckill( ?, ?, ?, ? )
23:14:58.848 [http-nio-8090-exec-70] DEBUG o.s.dao.SeckillDao.killByprocedure - ==> Parameters: 1003(Long), 18801082263(Long), 2018-03-04 23:14:58.839(Timestamp)
23:14:58.852 [http-nio-8090-exec-70] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4bd8e8ef]
平均:(10 + 9 + 4 + 4) / 4 = 6.75ms
// 一倍网络延迟 + GC
// 第一次测试:179 - 198 19ms
09:28:17.164 [http-nio-8090-exec-96] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
09:28:17.164 [http-nio-8090-exec-96] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1000(Long), 18801082265(Long)
09:28:17.178 [http-nio-8090-exec-96] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
09:28:17.178 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca]
09:28:17.178 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca] from current transaction
09:28:17.178 [http-nio-8090-exec-96] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
09:28:17.179 [http-nio-8090-exec-96] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1000(Long), 2018-03-05 09:28:17.161(Timestamp), 2018-03-05 09:28:17.161(Timestamp)
09:28:17.187 [http-nio-8090-exec-96] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
09:28:17.187 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca]
09:28:17.188 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca] from current transaction
09:28:17.188 [http-nio-8090-exec-96] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
09:28:17.188 [http-nio-8090-exec-96] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1000(Long), 18801082265(Long)
09:28:17.197 [http-nio-8090-exec-96] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
09:28:17.197 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca]
秒杀成功
09:28:17.198 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca]
09:28:17.198 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca]
09:28:17.198 [http-nio-8090-exec-96] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63567dca]
// 第二次测试 901 - 908 7ms
09:31:31.895 [http-nio-8090-exec-97] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
09:31:31.896 [http-nio-8090-exec-97] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1001(Long), 18801082265(Long)
09:31:31.900 [http-nio-8090-exec-97] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
09:31:31.901 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d]
09:31:31.901 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d] from current transaction
09:31:31.901 [http-nio-8090-exec-97] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
09:31:31.901 [http-nio-8090-exec-97] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1001(Long), 2018-03-05 09:31:31.895(Timestamp), 2018-03-05 09:31:31.895(Timestamp)
09:31:31.904 [http-nio-8090-exec-97] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
09:31:31.904 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d]
09:31:31.904 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d] from current transaction
09:31:31.905 [http-nio-8090-exec-97] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
09:31:31.905 [http-nio-8090-exec-97] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1001(Long), 18801082265(Long)
09:31:31.907 [http-nio-8090-exec-97] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
09:31:31.908 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d]
秒杀成功
09:31:31.908 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d]
09:31:31.908 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d]
09:31:31.908 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc1899d]
// 第三次测试 887 - 904 7ms
09:33:45.883 [http-nio-8090-exec-104] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@12dd9240] will be managed by Spring
09:33:45.883 [http-nio-8090-exec-104] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
09:33:45.883 [http-nio-8090-exec-104] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1002(Long), 18801082265(Long)
09:33:45.887 [http-nio-8090-exec-104] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
09:33:45.887 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b]
09:33:45.887 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b] from current transaction
09:33:45.887 [http-nio-8090-exec-104] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
09:33:45.893 [http-nio-8090-exec-104] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1002(Long), 2018-03-05 09:33:45.882(Timestamp), 2018-03-05 09:33:45.882(Timestamp)
09:33:45.898 [http-nio-8090-exec-104] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
09:33:45.898 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b]
09:33:45.898 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b] from current transaction
09:33:45.899 [http-nio-8090-exec-104] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
09:33:45.899 [http-nio-8090-exec-104] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1002(Long), 18801082265(Long)
09:33:45.903 [http-nio-8090-exec-104] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
09:33:45.903 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b]
秒杀成功
09:33:45.904 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b]
09:33:45.904 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b]
09:33:45.904 [http-nio-8090-exec-104] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53e0987b]
// 第四次测试 988 - 991 3ms
09:33:50.985 [http-nio-8090-exec-97] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@328c4c0f] will be managed by Spring
09:33:50.985 [http-nio-8090-exec-97] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
09:33:50.985 [http-nio-8090-exec-97] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1003(Long), 18801082265(Long)
09:33:50.987 [http-nio-8090-exec-97] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
09:33:50.987 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0]
09:33:50.988 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0] from current transaction
09:33:50.988 [http-nio-8090-exec-97] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
09:33:50.988 [http-nio-8090-exec-97] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1003(Long), 2018-03-05 09:33:50.984(Timestamp), 2018-03-05 09:33:50.984(Timestamp)
09:33:50.989 [http-nio-8090-exec-97] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
09:33:50.989 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0]
09:33:50.989 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0] from current transaction
09:33:50.990 [http-nio-8090-exec-97] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
09:33:50.990 [http-nio-8090-exec-97] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1003(Long), 18801082265(Long)
09:33:50.991 [http-nio-8090-exec-97] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
09:33:50.991 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0]
秒杀成功
09:33:50.991 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0]
09:33:50.991 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0]
09:33:50.991 [http-nio-8090-exec-97] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@672a17a0]
平均:(19 + 7 + 7 + 3) / 4 = 9ms
// 2倍网络延迟 + GC
// 第一次测试 682 - 714 32ms
11:07:05.680 [http-nio-8090-exec-99] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@d6ba532] will be managed by Spring
11:07:05.680 [http-nio-8090-exec-99] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
11:07:05.682 [http-nio-8090-exec-99] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1000(Long), 2018-03-05 11:07:05.677(Timestamp), 2018-03-05 11:07:05.677(Timestamp)
11:07:05.694 [http-nio-8090-exec-99] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
11:07:05.694 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c]
11:07:05.695 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c] from current transaction
11:07:05.695 [http-nio-8090-exec-99] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
11:07:05.696 [http-nio-8090-exec-99] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1000(Long), 18801082266(Long)
11:07:05.701 [http-nio-8090-exec-99] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
11:07:05.702 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c]
11:07:05.702 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c] from current transaction
11:07:05.702 [http-nio-8090-exec-99] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
11:07:05.703 [http-nio-8090-exec-99] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1000(Long), 18801082266(Long)
11:07:05.712 [http-nio-8090-exec-99] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
11:07:05.712 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c]
秒杀成功
11:07:05.714 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c]
11:07:05.714 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c]
11:07:05.714 [http-nio-8090-exec-99] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@161d3c2c]
// 第二次测试 800 - 816 16ms
11:23:38.799 [http-nio-8090-exec-108] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@57d7c1c6] will be managed by Spring
11:23:38.799 [http-nio-8090-exec-108] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
11:23:38.800 [http-nio-8090-exec-108] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1001(Long), 2018-03-05 11:23:38.798(Timestamp), 2018-03-05 11:23:38.798(Timestamp)
11:23:38.803 [http-nio-8090-exec-108] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
11:23:38.803 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16]
11:23:38.803 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16] from current transaction
11:23:38.804 [http-nio-8090-exec-108] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
11:23:38.805 [http-nio-8090-exec-108] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1001(Long), 18801082266(Long)
11:23:38.808 [http-nio-8090-exec-108] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
11:23:38.809 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16]
11:23:38.809 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16] from current transaction
11:23:38.810 [http-nio-8090-exec-108] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
11:23:38.810 [http-nio-8090-exec-108] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1001(Long), 18801082266(Long)
11:23:38.814 [http-nio-8090-exec-108] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
11:23:38.815 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16]
秒杀成功
11:23:38.816 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16]
11:23:38.816 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16]
11:23:38.816 [http-nio-8090-exec-108] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44eab16]
// 第三次测试 518 - 541 23ms
11:23:50.518 [http-nio-8090-exec-113] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@4ef91c67] will be managed by Spring
11:23:50.518 [http-nio-8090-exec-113] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
11:23:50.518 [http-nio-8090-exec-113] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1002(Long), 2018-03-05 11:23:50.517(Timestamp), 2018-03-05 11:23:50.517(Timestamp)
11:23:50.520 [http-nio-8090-exec-113] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
11:23:50.521 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c]
11:23:50.521 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c] from current transaction
11:23:50.521 [http-nio-8090-exec-113] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
11:23:50.535 [http-nio-8090-exec-113] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1002(Long), 18801082266(Long)
11:23:50.537 [http-nio-8090-exec-113] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
11:23:50.537 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c]
11:23:50.537 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c] from current transaction
11:23:50.537 [http-nio-8090-exec-113] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
11:23:50.539 [http-nio-8090-exec-113] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1002(Long), 18801082266(Long)
11:23:50.541 [http-nio-8090-exec-113] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
11:23:50.541 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c]
秒杀成功
11:23:50.541 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c]
11:23:50.541 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c]
11:23:50.541 [http-nio-8090-exec-113] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc4165c]
// 第四次测试 005 - 020 15ms
11:24:00.004 [http-nio-8090-exec-106] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@7d65ba66] will be managed by Spring
11:24:00.004 [http-nio-8090-exec-106] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0
11:24:00.005 [http-nio-8090-exec-106] DEBUG o.s.dao.SeckillDao.reduceNumber - ==> Parameters: 1003(Long), 2018-03-05 11:24:00.002(Timestamp), 2018-03-05 11:24:00.002(Timestamp)
11:24:00.008 [http-nio-8090-exec-106] DEBUG o.s.dao.SeckillDao.reduceNumber - <== Updates: 1
11:24:00.008 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447]
11:24:00.008 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447] from current transaction
11:24:00.009 [http-nio-8090-exec-106] DEBUG o.s.d.S.insertSuccessKilled - ==> Preparing: insert ignore into success_killed(seckill_id, user_phone, state) values(?, ?, 0)
11:24:00.010 [http-nio-8090-exec-106] DEBUG o.s.d.S.insertSuccessKilled - ==> Parameters: 1003(Long), 18801082266(Long)
11:24:00.013 [http-nio-8090-exec-106] DEBUG o.s.d.S.insertSuccessKilled - <== Updates: 1
11:24:00.014 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447]
11:24:00.014 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447] from current transaction
11:24:00.014 [http-nio-8090-exec-106] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Preparing: select sk.seckill_id, sk.user_phone, sk.state, sk.create_time, s.seckill_id "seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.create_time "seckill.create_time" from success_killed sk inner join seckill s on sk.seckill_id = s.seckill_id where sk.seckill_id = ? and sk.user_phone = ?
11:24:00.015 [http-nio-8090-exec-106] DEBUG o.s.d.S.queryByIdWithSeckill - ==> Parameters: 1003(Long), 18801082266(Long)
11:24:00.020 [http-nio-8090-exec-106] DEBUG o.s.d.S.queryByIdWithSeckill - <== Total: 1
11:24:00.020 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447]
秒杀成功
11:24:00.020 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447]
11:24:00.020 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447]
11:24:00.020 [http-nio-8090-exec-106] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fb7d447]
平均:(32 + 16 + 23 + 15) / 4 = 21.5ms
优化总结
测试数据:
2倍网路延迟 + GC:32ms 16ms 23ms 15ms
1倍网络延迟 + GC:19ms 7ms 7ms 3ms
存储过程:10ms 9ms 4ms 4ms
2倍网路延迟 + GC —> 1倍网络延迟 + GC 行级锁持有的平均时间从 21.5ms —> 9ms
1倍网络延迟 + GC —> 存储过程 行级锁持有的平均时间从9ms —> 6.75ms(实际会小于6.75ms)
// MD5的生成
private String getMD5(long seckillId) {
String base = seckillId + "/" + salt;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
ProtostuffIOUtil序列化和反序列化
反序列化:
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);
byte[] bytes = jedis.get(key.getBytes());
// 空对象
Seckill seckill = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
// seckill 被反序列化
return seckill;
序列化:
String key = "seckill:" + seckill.getSeckillId();
byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
// 超时缓存
int timeout = 60 * 60; // 1小时
String result = jedis.setex(key.getBytes(), timeout, bytes);
return result;
减库存的sql代码:
update seckill
set
number = number - 1
where seckill_id = #{seckillId}
and start_time <![CDATA[ <= ]]> #{killTime}
and end_time >= #{killTime}
and number > 0
对于秒杀活动来说,当时的剩余库存数在秒杀期间变化非常快,某个时间点上的库存个数并没有太大的意义,而用户更关心的是 能不能抢,true or false。如果缓存true or false的话,这个值在秒杀期间是相对稳定的,只需要在库存耗尽的时候更新一次,而且为了防止这一次的更新失败,可以重复更新,利用memcached的cas操作,最后memcached也只会真正执行一次set写操作。 因为秒杀期间查询活动状态的请求都打在memcached上,减少写的频率可以明显减轻memcached的负担。
一个用户从发出秒杀请求到成功秒杀简单地说需要两个步骤: 1. 扣库存 2. 发送秒杀商品 这是至少两条数据库操作,而且扣库存的这一步,在mysql的innodb引擎行锁机制下,update的sql到了数据库就开始排队,期间数据库连接是被占用的,当请求足够多时就会造成数据库的拥堵。 可以看出,秒杀请求接口是一个耗时相对长的接口,而且并发越高耗时越长,所以首先,一定要限制能够真正进行秒杀的人数。秒杀业务的一个特点是参与人数多,但是可供秒杀的商品少,也就是说只有极少部分的用户最终能够秒杀成功 比如有2500个名额,理论上来说先发送请求的2500个用户能够秒杀成功,这2500个用户扣库存的sql在数据库排队的时候,库存还没有消耗完,比如2500个请求,全部排队更新完是需要时间的,就比如说0.5s 在这个时间内,用户会看到当前仍然是可抢状态,所以这段时间内持续会有秒杀请求进入,秒杀的高峰期,0.5秒也有几万的请求,让几万条sql来竞争是没有意义的,所以要限制这些参与到扣库存这一步的人数。
可抢状态需要第三个因素来决定,那就是当前秒杀的排队人数。 加在判断库存剩余之前,挡上一层排队人数的校验, 即有库存 并且 排队人数 < 限制请求数 = 可抢,有库存 并且 排队人数 >= 限制请求数 = 抢完
比如2500个名额秒杀名额,目标放过去3000个秒杀请求
那么排队人数记在哪里? 这个可以有所选择,如果只记请求个数,可以用memcached的计数,一个用户进入秒杀流程increase一次,判断库存之前先判断队列长度,这样就限制了可参与秒杀的用户数量。
发起秒杀先去问排队队列是不是已满,满了直接秒杀失败,同时可以去更新之前缓存了是否可抢 true or false的缓存,直接把前台可抢的状态变为不可抢。没满继续查询库存等后续流程,开始扣库存的时候,把当前用户id入队。 这样,就限制了真正进入秒杀的人数。
这种方法,可能会有一个问题,既然限制了请求数,那就必须要保证放过去的用户能够秒完商品,假设有重复提交的用户,如果重复提交的量大,比如放过去的请求中有一半都是重复提交,就会造成最后没秒完的情况,怎么屏蔽重复用户呢? 就要有个地方来记参与的用户id,可以使用redis的set结构来保存,这个时候set的size代表当前排队的用户数,扣库存之前add当前用户id到set,根据add是否成功的结果,来判断是否继续处理请求。
最终,把实际上几万个参与数据库操作的用户从减少到秒杀商品的级别,这是一个数据库可控制的范围,即使参与的用户再多,实际上也只处理了秒杀商品数量级的请求。
1.分库存 一般这样做就已经能够满足常规秒杀的需求了,但有一个问题依然没有解决,那就是加锁扣库存依然很慢 假设的活动秒杀的商品量能够再上一个量级,像小米卖个手机,一次有几W到几十万的时候,数据库也是扛不住这个量的,可以先把库存数放在redis上,然而单一库存加锁排队依然存在,库存这个热点数据会成为扣库存的瓶颈。
一个解决的办法是 分库存,比如总共有50000个秒杀名额,可以分50份,放在redis上的50个不同的key,那么每份上1000个库存,用户进入秒杀流程后随机到其中一个库存来修改,这样有50个库存数来竞争,缩短请求的排队时间。
这样专门为高并发设计的系统最大的敌人 是低流量,在大部分库存都好近,而有几个剩余库存时, 用户会看到明明还能抢却总是抢不到,而在高并发下,用户根本就觉察不到。
2.异步消息 如果有必要继续优化,就是扣库存和发货这两个费时的流程,可以改为异步,得到秒杀结果后通过短信/push异步通知用户。 主要是利用消息系统削峰填谷的特性 来增加系统的容量。
参考文献:http://zxcpro.github.io/blog/2015/07/27/gao-bing-fa-miao-sha-xi-tong-de-she-ji/