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才有了本文 ...
随机推荐
- Spring Framework: @RestController vs @Controller
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annota ...
- 配置wbepack
proxyTable:{ //反向代理 先建立连接 '/sexLady':{ target:url//请求地址 暗号:'/sexLady ' changeOrigin:true ,//类似baseUr ...
- CentOS下使用VirtualBox 安装 Windows虚拟机的简单方法
1.物理服务器安装CentOS7.5 2. 安装VNC 3. 关闭防火墙,关闭selinux,上传virtualbox的rpm包. http://download.virtualbox.org/vir ...
- linux_目录基本操作
ls命令 ls命令用来显示目标列表,在Linux中是使用率较高的命令.ls命令的输出信息可以进行彩色加亮显示,以分区不同类型的文件. 语法 $ ls [选项] [目录] 选项 说明 -a 显示所有档案 ...
- 基于C#.NET的高端智能化网络爬虫(二)(攻破携程网)
本篇故事的起因是携程旅游网的一位技术经理,豪言壮举的扬言要通过他的超高智商,完美碾压爬虫开发人员,作为一个业余的爬虫开发爱好者,这样的言论我当然不能置之不理.因此就诞生了以及这一篇高级爬虫的开发教程. ...
- 如何禁止复制电脑文件到U盘、禁止U盘拷贝文件
在公司局域网中,有时候我们处于保护电脑文件安全和商业机密的需要,会禁止局域网电脑使用U盘.禁用USB存储设备:或者禁止通过U盘复制电脑文件.禁止U盘拷贝公司电脑文件.那么,怎样实现呢?本文提供两种方法 ...
- C#中的DateTime
一.DateTime是值类型还是引用类型的探索 二.了解DateTime结构体 三.DateTime.Now和DateTime.UtcNow是怎么计算出来的 一.DateTime是值类型还是引用类型的 ...
- timescale 时间尺度
1 `timescale为模块指定参考时间单位 `timescale<reference_time_unit>/<time_precision> 2 module endmou ...
- 【比赛】NOIP2018 总结
一.考试过程 Day1: 先看了一遍题目,得到的结论是没有题是直接秒掉的,然后一道一道认真看. 看T1的时候开始并没想起来有一道原题,只是脑海中有一个印象,好像求差分和可以.然后自测了一下小样例,发现 ...
- Leetcode 344.反转字符串 By Python
请编写一个函数,其功能是将输入的字符串反转过来. 示例: 输入:s = "hello" 返回:"olleh" 思路 Python里面的切片用来解决这个问题就很快 ...