Java高并发秒杀API之高并发优化
---恢复内容开始---
第1章 秒杀系统高并发优化分析
1.为什么要单独获得系统时间
访问cdn这些静态资源不用请求系统服务器
而CDN上没有系统时间,需要单独获取,获取系统时间不用优化,只是new了一个日期对象返回,java访问一次内存(cacheline)的时间大概为10ns,即一秒可可访问一亿次
倒计时放在js端,在浏览器中,不会对服务器端造成影响,也不用优化
2.秒杀地址接口分析
秒杀未开启,秒杀开启,秒杀结束,秒杀地址返回的数据不同,不是静态的,无法使用CDN缓存
但它适合使用redis等服务器端缓存
超时穿透即当缓存超时后,请求穿透缓存直接到达mysql
主动更新即mysql更新后,主动更新到redis
3.秒杀操作优化分析
涉及到减库存,无法使用后端缓存,必须通过mysql的事务来维持一致性
4.其他方案分析
MQ即消息队列
普遍认为mysql低效,但经过测试mysql的QPS很高。
每秒查询率QPS(Query Per Second)
但由于事务及行级锁的存在,update成为了一个串行的操作
可能会出现GC,新生代GC会暂停所有事务产生约几十毫秒延迟
Minor GC都会触发(stop-the-world)
除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务
优化分析:
行级锁在commit或rollback之后释放
优化方向->减少行级锁的持有时间
如果出现GC锁的释放时间又会延长约50ms,并发越高GC也约多
update影响记录数即update返回值,若为0则失败
修改源码不现实,腾讯曾经做过
用户点击了秒杀按钮,会先禁用按钮,防止不停发送请求,将请求拦截在前端,减轻后端负载
第2章 redis后端缓存优化编码
用redis优化地址暴露接口
由于地址暴露接口是根秒杀单的时间来计算是否开启秒杀,是否结束,以及是否在秒杀中,所以不方便作为固定的内容放在CDN中作为缓存,它要放在服务器端,通过服务器端的逻辑去控制。由于这各接口调用也比较频繁,我们不希望它频繁访问数据库
原来在官网上可以下载的windows版本的,现在官网以及没有下载地址,只能在github上下载,官网只提供linux版本的下载
redis在windows下安装过程:http://www.cnblogs.com/M-LittleBird/p/5902850.html
我们不去做linux下javaweb环境搭建以及项目部署,目前学习的重点是Java以及Java WEB的相关知识,不是细枝末节的平台、IDE等工具。所以采用windows版的redis
在org.myseckill.dao下新建文件夹cache,在其中新建RedisDao如下
public class RedisDao {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final JedisPool jedisPool;
//构造方法
public RedisDao(String id,int port) {
jedisPool = new JedisPool(id,port);
} //只需要知道这个对象是什么class,内部有一个schema描述这个class是什么结构
//.class是字节码文件,代表这个类的字节码对象,通过反射可以知道字节码文件对应对象有哪些属性和方法。序列化的本质:通过字节码和字节码对应的对象有哪些属性,把字节码的数据传递给那些属性
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class); //不用访问DB直接通过redis拿到Seckill对象
public Seckill getSeckill(long seckillId) {
//redis操作逻辑
try {
//JedisPool相当于数据库连接池,Jedis相当于数据库的Connection
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckillId;
//redis或它原生的jedis并没有实现内部序列化操作,不像memcached内部做了序列化
//典型的缓存访问逻辑:get->得到一个二进制数组byte[](无论是什么对象或图片或文字存储都是二进制数组)->反序列化得到Object(Seckill)
//高并发里面容易被忽视的一个点,序列化问题,jdk自带的序列化机制serializable效率比较低
//采用自定义序列化,使用开源社区的方案,pom.xml中导入protostuff的依赖
//protostuff把一个对象转化为二进制数组,传入redis当中。只需要知道这个对象是什么class,内部有一个schema描述这个class是什么结构。要求该对象为pojo,即有getter setter方法的普通java对象
byte[] bytes = jedis.get(key.getBytes());
//缓存中获取到
if(bytes != null) {
Seckill seckill = schema.newMessage(); //Seckill的空对象
ProtostuffIOUtil.mergeFrom(bytes, seckill, schema); //把字节数组的数据传入空对象中
//Seckill被反序列化
return seckill;
}
} finally {
jedis.close();
} } catch (Exception e) {
logger.error(e.getMessage(),e);
}
return null;
} //当缓存没有时将Seckill放入redis缓存中
public String putSeckill(Seckill seckill) {
//把Seckill对象序列化为字节数组放入redis
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); //String类型的result,如果错误会返回错误信息,如果成功会返回ok return result;
}finally {
jedis.close();
}
}catch (Exception e) {
logger.error(e.getMessage(),e);
} return null;
} }
pom.xml中导入相关依赖:
<!-- redis客户端:Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
<!-- protolstuff序列化依赖 -->
<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>
</dependencies>
spring-dao.xml中注入该bean
<!-- redisDao -->
<bean id="redisDao" class="org.myseckill.dao.cache.RedisDao">
<!-- 构造方法注入 -->
<constructor-arg index="0" value="localhost"/>
<constructor-arg index="1" value="6379"/>
</bean>
RedisDao的测试类:
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class RedisDaoTest { private long id = 1001; @Autowired
private RedisDao redisDao; @Autowired
private SeckillDao seckillDao; @Test
public void testSeckill() {
//全局测试 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);
}
} } }
输出
OK
Seckill{seckillId=1001, name='800元秒杀ipad', number=200, startTime=Mon Mar 26 00:00:00 CST 2018, endTime=Sun Apr 15 00:00:00 CST 2018, createTime=Fri Dec 29 23:04:08 CST 2017}
修改SeckillServiceImpl,加入缓存优化并测试
@Autowired
private RedisDao redisDao; @Override
public Exposer exportSeckillUrl(long seckillId) {
// 缓存优化,在超时的基础上维护一致性,因为秒杀的对象一般不会改变
// 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);
} }
最后查看redis,发现缓存中已有数据
第3章 并发优化
1.简单优化
网络延迟是指update或insert操作返回结果到java客户端进行逻辑判断的延迟(下一节用存储过程在mysql本地执行科避免)
只有update操作需要获取行级锁
insert操作冲突的概率很小(重复秒杀时),可以看做并行的
调换update和insert的位置
先update的情况下,第一个事务到update锁住了,其他的全都在等,第一个update执行完,还要再去执行insert,所以持有锁的时间时间相当于是update+insert
先insert的情况下,insert并行,前一个事务到update锁住了,其他的在执行insert,所以持有锁的时间就是只有一个update
更新库存发现更新失败(此时影响结果行数为0)会回滚事务,清除掉前面插入的购买明细,所以不存在超卖问题
2.深度优化
利用存储过程,将事务SQL打包到在MySQL端执行
存储过程说白了就是一堆 SQL 的合并。中间加了点逻辑控制。
但是存储过程处理比较复杂的业务时比较实用。
比如说,一个复杂的数据操作。如果你在前台处理的话。可能会涉及到多次数据库连接。但如果你用存储过程的话。就只有一次。从响应时间上来说有优势。
insert和update的逻辑比较简单,我这里并没有使用存储过程
优势主要体现在:
1.存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般 SQL
语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。
2.当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。这些操作,如果用程序来完成,就变成了一条条的 SQL
语句,可能要多次连接数据库。而换成存储,只需要连接一次数据库就可以了。
第4章 系统部署架构
第5章 课程总结
数据层技术回顾:
数据库设计和实现;Mybatis理解与使用技巧;Mybatis整合Spring技巧
业务层技术回顾:
业务接口设计和封装(站在使用者的角度设计);SpringIOC配置技巧;Spring声明式事务使用与理解
WEB技术回顾:
前端交互设计过程,Restful接口设计,SpringMVC使用技巧,Bootstrap和JS的使用
并发优化:
系统瓶颈点分析;事务,锁,网路延迟理解;前端,CDN,缓存等理解使用;集群化部署
---恢复内容结束---
Java高并发秒杀API之高并发优化的更多相关文章
- imooc课程:Java高并发秒杀API 记录
Java高并发秒杀API之业务分析与DAO层 Java高并发秒杀API之Service层 Java高并发秒杀API之web层 Java高并发秒杀API之高并发优化 除了并发部分外的这个web开发的总结 ...
- Java高并发秒杀API之Service层
Java高并发秒杀API之Service层 第1章 秒杀业务接口设计与实现 1.1service层开发之前的说明 开始Service层的编码之前,我们首先需要进行Dao层编码之后的思考:在Dao层我们 ...
- Java高并发秒杀API之业务分析与DAO层
根据慕课网上关于java高并发秒杀API的课程讲解用maven+ssm+redis实现的一个秒杀系统 参考了codingXiaxw's blog,很详细:http://codingxiaxw.cn/2 ...
- 2017.4.26 慕课网--Java 高并发秒杀API(一)
Java高并发秒杀API系列(一) -----------------业务分析及Dao层 第一章 课程介绍 1.1 内容介绍及业务分析 (1)课程内容 SSM框架的整合使用 秒杀类系统需求理解和实现 ...
- Java高并发秒杀API之web层
第1章 设计Restful接口 1.1前端交互流程设计 1.2 学习Restful接口设计 什么是Restful?它就是一种优雅的URI表述方式,用来设计我们资源的访问URL.通过这个URL的设计,我 ...
- JAVA高并发秒杀API项目的学习笔记
一步一步的搭建JAVA WEB项目,采用Maven构建,基于MYBatis+Spring+Spring MVC+Bootstrap技术的秒杀项目学习的视频:http://www.imooc.com/l ...
- 2017.4.26 慕课网--Java 高并发秒杀API配置文件(持续更新)
新建项目,new maven project. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&q ...
- SSM框架学习之高并发秒杀业务--笔记1-- 项目的创建和依赖
在慕课网上看了Java高并发秒杀API视屏后,觉得这个案例真的让我学到了很多,现在重新自己实现一遍,博客记下,顺便分析其中的要点. 第一步是项目的创建和依赖 利用Maven去创建工程然后导入Idea中 ...
- Java高并发秒杀系统API之SSM框架集成swagger与AdminLTE
初衷与整理描述 Java高并发秒杀系统API是来源于网上教程的一个Java项目,也是我接触Java的第一个项目.本来是一枚c#码农,公司计划部分业务转java,于是我利用业务时间自学Java才有了本文 ...
随机推荐
- Quartz应用与集群原理分析
Quartz在CRM中的应用场景: https://tech.meituan.com/mt-crm-quartz.html https://www.mtyun.com/library/mt-crm-q ...
- PHP多进程编之pcntl_fork的实例详解
PHP多进程编之pcntl_fork的实例详解 其实PHP是支持并发的,只是平时很少使用而已.平时使用最多的应该是使用PHP-FMP调度php进程了吧. 但是,PHP的使用并不局限于做Web,我们完全 ...
- SQLSERVER 2014 SP1 的服务器 日志文件无法收缩的处理
1. 公司一台服务器 日子会文件到了 100g+ 但是无法收缩 2. 根据同事的经验进行验证 dbcc loginfo 单独看改数据库的 dbcc loginfo("CWBASEGS60&q ...
- ionic2中如何使用自动生成器
ionic generator是命令行的功能,ionic2自动帮我们创建应用程序,从而节省了大量的时间,并增加我们的速度来开发一个项目的关键部分. ionic generator使我们可以自动创建以下 ...
- NCO
NCO 摘自百度百科 (数字振荡器) 锁定 本词条由“科普中国”百科科学词条编写与应用工作项目 审核 . 数字控制振荡器(NCO,numerically controlled oscillator)是 ...
- BZOJ1299[LLH邀请赛]巧克力棒——Nim游戏+搜索
题目描述 TBL和X用巧克力棒玩游戏.每次一人可以从盒子里取出若干条巧克力棒,或是将一根取出的巧克力棒吃掉正整数长度.TBL先手两人轮流,无法操作的人输. 他们以最佳策略一共进行了10轮(每次一盒). ...
- BZOJ5343[Ctsc2018]混合果汁——主席树+二分答案
题目链接: CTSC2018混合果汁 显然如果美味度高的合法那么美味度低的一定合法,因为美味度低的可选方案包含美味度高的可选方案. 那么我们二分一个美味度作为答案然后考虑如何验证? 选择时显然要贪心的 ...
- 牛客国庆集训派对Day2 H 期望
小贝喜欢玩卡牌游戏.某个游戏体系中共有N种卡牌,其中M种是稀有的.小贝每次和电脑对决获胜之后都会有一个抽卡机会,这时系统会随机从N种卡中选择一张给小贝.普通卡可能多次出现,而稀有卡牌不会被重复抽到.小 ...
- Saddle Point ZOJ - 3955(求每个值得贡献)
题意: 给出一个矩阵,删掉一些行和列之后 求剩下矩阵的鞍点的总个数 解析: 对于每个点 我们可以求出来 它所在的行和列 有多少比它大的 设为a 有多少比它小的 设为b 然后对于那些行和列 都有两种操 ...
- hdu 3911 Black And White (线段树 区间合并)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3911 题意: 给你一段01序列,有两个操作: 1.区间异或,2.询问区间最长的连续的1得长度 思路: ...