四 高并发优化
1.分析
1.详情页 部署到cdn上,这样用户访问的是cdn不是服务器了。
用户在上网时通过运营商访问最近的城域网,城域网访问主干网。
2.获取系统时间 不用优化
访问一次内存大概 10ns
无法使用cdn,适合服务器端缓存redis等(单台一秒10万qps,还可以做集群)
一致性维护非常低
3.秒杀地址优化
请求地址->访问redis–(超时穿透/主动刷新)->访问mysql
4.秒杀操作的优化分析
无法使用cdn
后端缓存困难,库存问题
一行数据竞争,热点商品
其他方案分析
运维成本高(nosql不稳定等),开发成本高(开发需要知道事务回滚等)
幂等性难保证,重复秒杀
mysql update同一条数据 ,4万qps
A :先update 后insert
B 先update 等待事务 ,A释放锁后update,insert
gc(新生代gc(暂停所有java代码,几十毫秒 )和老一代gc)
执行update –网络延迟/gc –>insert–网络延迟/gc –>commit/rollback
优化方向减少锁持有时间
同城机房(0.5~2ms)max(1000qps)
异地机房更长
如何判断update成功
1.自身没有报错2.客户端确认更新成功
优化思路
把客户端的业务逻辑放到mysql服务端
两种解决方案
1.定制sql方案update /*+[auto_commit]*/
需要修改mysql源码
自动进行update为1 -> commit ,为0 -> rollback
2.存储过程
2.redis 后端优化
官网下载redis
https://redis.io/download
安装完成后
redis-server服务器启动
redis-cli 客户端启动
引入jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
redis指令 get key ,set key value
redisDao
package org.seckill.dao.cache;
import org.seckill.entity.Seckill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisDao {
private final JedisPool jedisPool;
private final Logger logger = LoggerFactory.getLogger(RedisDao.class);
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);
public RedisDao(String ip,int port){
jedisPool = new JedisPool(ip,port);
}
public Seckill getSeckill(long seckillId){
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:"+seckillId;
//没有实现 内部序列化
//get -> byte[] -> 反序列化 ->Object(seckill)
//采用自定义序列化
// protostuff:pojo(有get,set方法)
byte [] bytes = jedis.get(key.getBytes());
if(bytes != null){
//创建一个空对象
Seckill seckill = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
//被反序列
return seckill;//比java 原生的压缩了1/10~1/5 压缩速度 差2个数量级
}
}finally{
jedis.close();
}
} catch (Exception e) {
logger.error(e.getMessage());
}
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;//小时
String result = jedis.setex(key.getBytes(), timeout, bytes);
return result;
} finally{
jedis.close();
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
}
key存放seckill:id value存放序列化对象
所以需要protostuff(性能更好)serializable(性能较低)
添加依赖
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.8</version>
</dependency>
添加配置
<!-- RedisDao -->
<!-- 构造方法注入 -->
<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>
单元测试
RedisDaoTest
package org.seckill.dao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.dao.cache.RedisDao;
import org.seckill.entity.Seckill;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class RedisDaoTest {
@Autowired
private RedisDao redisDao;
private long id = 1002;
@Autowired
private SeckillDao seckillDao;
@Test
public void testSeckill() throws Exception{
//get and put
Seckill seckill = redisDao.getSeckill(id);
if(seckill == null){
seckill = seckillDao.queryById(id);
if(seckill != null){
String result = redisDao.putSeckill(seckill);
System.out.println(result);
seckill = redisDao.getSeckill(id);
System.out.println(seckill);
}
}
}
@Test
public void testGetSeckill() throws Exception{
Seckill seckill = redisDao.getSeckill(id);
System.out.println(seckill);
}
@Test
public void testputSeckill() throws Exception{
}
}
3.并发优化
原来的逻辑 update->insert->commit/rollback
修改为insert->update->commit/rollback
减少锁持有的时间
存储过程
--- 秒杀执行的存储过程
--;
DELIMITER $$ --console ; 转换为 $$
CREATE PROCEDURE `SECKILL`.`execute_seckill`
--参数 in 输入参数 out 输出参数
-- row_count():返回上一条修改类型sql(d,i,u)的影响行数
--row-count() = 0 未修改数据 >0 表示修改的行数 <0 sql错误/未执行
(in v_seckill_id bigint,in 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,create_time)
values(v_seckill_id,v_phone,0,v_kill_time);
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 end_time >v_kill_time
and start_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(1001,13934131331,now(),@r_result)
select @r_result
SeckillDao添加方法
/**
* 使用存储过程执行秒杀
* @param parammap
*/
void killByProcedure(Map<String,Object> paramMap);
SeckillDao.xml添加
<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>
service添加
//执行秒杀操作by存储过程
SeckillExecution excuteSeckillProcedure(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException;
实现类
public SeckillExecution excuteSeckillProcedure(long seckillId, long userPhone, String md5)
throws RepeatKillException, seckillCloseException, SeckillException {
if(md5==null||!md5.equals(getMD5(seckillId))){
return new SeckillExecution(seckillId,SeckillStatEnum.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);
try {
seckilldao.killByProcedure(map);
int result = MapUtils.getInteger(map, "result",-2);
if(result==1){
SuccessKilled sk = successkilleddao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,sk);
}else{
return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage());
return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
}
}
系统用到哪些服务
cdn
webserver : nginx(集群化,http服务器,给后端服务器做反向代理)+jetty
redis服务器端缓存
mysql mysql事务,保证数据的一致性和完整性