Java高并发秒杀API之高并发优化(四)
来源:互联网 发布:c语言程序 编辑:程序博客网 时间:2024/05/22 07:40
四 高并发优化
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事务,保证数据的一致性和完整性
- Java高并发秒杀API之高并发优化(四)
- Java高并发秒杀API(四)之高并发优化
- 慕课网-java高并发秒杀api之高并发优化-总结
- 高并发秒杀API之Service
- #Java 高并发秒杀API 笔记
- Java高并发秒杀API
- Java高并发秒杀系统API
- Java高并发秒杀API(二)之Service层
- Java高并发秒杀API(三)之Web层
- JAVA高并发秒杀系统构建之——高并发优化分析
- Imooc·Java高并发秒杀API(三)
- 高并发秒杀之秒杀优化
- Java高并发秒杀API之DAO层实现(一)
- Java高并发秒杀API之service层实现(二)
- Java高并发秒杀API之web层实现(三)
- Java高并发(一)-- 秒杀
- 四、高并发秒杀API之Web层设计与实现
- 高并发秒杀API之业务分析与DAO
- 监管方也 Hold 不住了,七张图看清 AI 入侵华尔街趋势
- hadoop 学习总结系列 (二 ) 查看日志
- c#常用集合类使用练习(队列Queue、栈Stack、哈希表Hashtable和动态数组ArrayList)(1):
- MySQL数据类型的最优选择
- 穷爸爸、富爸爸的理财核心:财产性收入的龙门一跃
- Java高并发秒杀API之高并发优化(四)
- POJ3254 状压DP模板
- Java
- AtCoder Grand Contest 001 F permutation
- Input值改变触发的事件
- Mac原生开启本地服务器的两种方法
- log4j完整版把错误信息放到日志中
- 数组的空位与undefined的注意点
- list!=null和list.size()>0的区别