MySQL建表添加乐观锁字段_Java秒杀系统优化-Redis缓存-分布式session-RabbitMQ异步下单-页面静态化...

Java秒杀系统优化-Redis缓存-分布式session-RabbitMQ异步下单-页面静态化

项目介绍

基于SpringBoot+Mybatis搭建的秒杀系统,并且针对高并发场景进行了优化,保证线程安全的同时极大地提高了服务器的吞吐量,主要优化手段有页面静态化、Redis缓存(页面缓存、对象缓存)、RabbitMQ异步下单,项目实现的主要功能为 登录-->商品列表浏览-->秒杀-->付款或返回。本项目利用压测工具Jmeter对优化前后的性能做了详细的评测,附带完整的测试报告以及整个系统的设计思路报告。

软件说明

整个项目的结构为严格的Maven项目结构,源码包下的包名即为其主要作用的缩写名,易于理解,概不赘述。

本文重点阐述计思路及优化方案,并附在Jmeter下压测的完整结果报告。

设计方案

以下分功能模块阐述:

一、登录功能及session

登录的设计并不复杂,主要思路为两次MD5+盐化,首先为前端用固定salt+password做一次MD5然后传至后端,后端逻辑首先对用户存在与否做判断,然后取出该用户的salt值,和前端传来的已经一次MD5的password再次MD5,然后比较即可,然后生成session并存储在redis缓存中,返回cookie。值得注意的是在user表中加入随机的salt可极大降低彩虹表攻击的风险,还有就是将session值存储于redis缓存中也极大地降低了对数据库的访问。

二、商品列表、订单详情、商品详情功能

这部分功能逻辑十分简单,即为查数据,然后展示,无须赘述,需注意的是,这部分功能的优化策略,详见后文。

三、秒杀功能

该功能为系统的核心功能,既要保证程序的线程安全性,又要满足高并发场景的需求。

在未优化前,其主要的逻辑为:查库存-->查是否秒杀-->秒杀,又因为该逻辑理论上应为一个原子的操作,所以加锁,或者作为事务,但是这样会大大影响程序的并发性能,所以需做优化。

四、数据库

数据库的主要设计为五张表:goods、miaosha_goods、miaosha_user、miaosha_order、order_info

具体数据表的主要结构,在./sql/中有sql文件,并且./java/util/中有用于生成测试用户的脚本,有兴趣的读者可以查看。

以上即为主要功能,接下来阐述对应的一些优化手段。

优化方案

一、页面缓存

页面缓存的主要思路为,将一些用户经常请求的页面,例如/goods/to_list--商品列表页面,存储到redis缓存中,在用户请求的时候直接在缓存中获取并返回,如果取缓存失败,则利用thymeleaf的手动渲染,渲染后存入缓存,并且返回。我们可以很明显的知道,不使用页面缓存的请求,每次都先访问数据库,然后经thymeleaf渲染,然后返回,其中渲染的过程可能需要从磁盘中读取html模板,而使用页面缓存以后,直接在内存缓存中读取,无需查库和渲染,只有失效的情况下才需要查库渲染,所以在些用户经常请求的页面中使用页面缓存优化,可大大降低对数据库和服务器的压力。(需要注意的是合理的设置页面缓存的有效期)。

二、对象缓存

相对于页面缓存,对象缓存是个更细粒度的缓存,比如说在登录模块中的session中,我们把session对应的user对象存储到redis缓存中,那么在需要user对象的页面中,既不需要登录,也不需要更具cookie去查找数据库,只需要通过cookie在redis中获取user对象,即可使用,同理,这样类型的缓存也会减小对数据库的压力。

三、页面静态化

上述的两种缓存,都是利用redis缓存服务器来实现的,虽然可以降低对数据库和服务器的压力,但是,redis服务器的容量和处理能力也是有限的,所以我们可以考虑将页面模板直接缓存到用户的浏览器,那么每次请求用户只需要请求用于渲染的对象即可,这不仅仅减轻了redis服务器的压力,同时也减少了带宽的消耗,此即为页面静态化。

在本项目中,主要实现的是商品详情、订单详情页面、秒杀页面的静态化,主要方法是利用ajax的异步加载,请求渲染需要的对象,并且通过配置

####### spring.resources的相关参数来告诉浏览器是否缓存,缓存有效时间等等。

四、静态资源优化

主要手段包括JS/CSS压缩,CDN等,此项目中并没有尝试,但不失为优化的另外一些好的思路。

以上部分的优化手段主要为缓存、页面优化等于前端比较接近的手段,对于后端接口的优化将在以下部分阐述

五、接口优化

主要思路为Redis预减库存+RabbitMQ异步下单

具体流程如下:

1、系统初始化,加载库存到redis缓存

2、收到请求,预减库存

3、判断库存,若剩余,则入队列,否则秒杀失败

4、出队下单

分析:

一、通过将库存加载到redis中,使得每次判断、减少库存直接从内存中读取,无需访问数据库

二、收到请求预见库存,然后判断

注意这一顺序非常重要,保证了线程安全

分析:因为redis封装的decr()等函数是线程安全的,无需外加同步,所以你通过decr()减少库存后获取到的库存永远都是你刚刚减少后得到的库存,本身就是个原子操作,不会存在线程安全问题,然后根据这个库存来入队,不符合条件的秒杀请求直接返回失败,极大地减少了服务器的压力,而且整个后台逻辑中,需要保证原子性的也仅仅是decr()这一个操作,并且由于redis经过了乐观锁优化,所以整个系统的并发性相对于自己首先同步代码而言,并发性得到了极大的提高。

三、完成了上述的操作,再去实现接下来的逻辑就很简单了,唯一需要注意的是,从队列中出来的请求执行秒杀过程是一个事务,需完整执行,否则回滚。

同时,订单的详情页面做一个静态化优化,前端轮询秒杀结果,得到结果后进行渲染即可。

以上即为接口优化的阐述,接下来是压测报告。

测试参数

服务器:(Mysq、Redis、RabbitMQ等服务也均安装在本机上

CPU: Hasse/战神Z7m Intel-i7-6700HQ 2.6GHz-3.5Ghz 四核心八线程 三级cache 6M

内存: 8G DDR3L

磁盘: 5400转/s 1TB

Java版本:1.8.0_161 Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

MySQL:5.7.20-log MySQL Community Server (GPL)

Redis:4.0.10 容量--100M

RabbitMQ:3.7.7

Mybatis、Druid等具体配置参数见./resource/application.properties文件

测试方法

测试工具使用Jmeter并发测试工具,为保证无非相关变量的影响,每次测试的并发线程数均设为1000,并发时间1s,并发循环次数为10次,如图所示:

a0d55b37a87b974e1ac37e22e5372781.png

/goods/to_list接口测试

该接口为商品列表接口,对该接口做了页面缓存的优化,分别对优化前,优化后做压测,结果如下:

3e91a85d38b80f90f2cb32fe55d2acff.png

优化前的测试结果

0f43c992b102d42058381cf67f6ef292.png

优化后的测试结果

e9164d1c7f8606d4807a61111782ec9f.png

对于上图的两个测试,我们比较关注的是聚合报告里的 ThoughPut 的值,其值可以作为吞吐量的一个较好估计。

由图可见:

优化前,系统吞吐量为:921.1/sec

优化后,系统吞吐量为:4046.9/sec

也就意味着,加速比为:4.39。

/goods/detail/{goodsId}接口测试

该接口为商品详情接口,对该接口实现了页面静态化的处理,分别对优化前,优化后做压测,结果如下:

优化前的测试结果

82bb4289df608eb25a375175b94638d5.png

优化后的测试结果

96591e8233a002824ab9b2cda6cc3444.png

对比以上两图,会发现,似乎区别并不明显,很容易让人得出优化无效的结论,其实不然

据本人分析,这种情况应该是由Jmeter自身导致的,因为Jmeter做测试的时候并不会依赖于其他浏览器,只是发起http请求,而浏览器所具备的一些功能,他并没有,比如,缓存,所有,利用Jmeter进行测试并不能得出一个如意的结果,但是,我们可以通过浏览器来大致的了解页面静态化后的一些改变。如图:

9cada2f9c7fd77ae4da80cf2dfa86c3d.png

可以清楚的看到,这个页面大部分的内容都被“已缓存”,只有400个字节左右的对象被请求并传输,这也就达到了我们优化前的目的了。

秒杀接口优化

对于接口测试,我实现准备了1000个user和cookie,具体脚本见./java/util/下代码,在Jmeter中使用参数方法可自行百度,如图:

089fa238a8f9e937b1369812e8e795c5.png

对于秒杀接口,我们只对优化后的接口进行压测,测试的情况分为两类:

1、秒杀库存充足

2、秒杀库存不足

对于秒杀库存充足的情况,我们设置初始的库存为200,然后,并发量和其他测试设置一致,结果如图:

d31a173728e9f47b6086557ab7aca969.png

对于秒杀库存不足的情况,我们设置初始的库存为0,然后,并发量和其他测试设置一致,结果如图:

bb6f1591e2059361f8c0d4b3019498ed.png

对于处理逻辑比较复杂的接口而言,最好和最坏的情况能达到这样的一个吞吐量,同时保证线程安全性,比较满意。

以上为全部测试报告,读者若有不明之处,欢迎提问。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/526851.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

叶金荣mysql教程_mysql优化--叶金荣老师讲座笔记

copy to tmp table执行ALTER TABLE修改表结构时建议:凌晨执行Copying to tmp table拷贝数据到内存中的临时表,常见于GROUP BY操作时建议:创建索引Copying to tmp table on disk临时结果集太大,内存中放不下,需要将内存…

python qqbot实现qq聊天机器人_Python QQBot库的QQ聊天机器人

本文实例为大家分享了Python QQBot库的QQ聊天机器人的具体代码,供大家参考,具体内容如下1.安装pip install qqbot2.主动发出消息from qqbot import _bot as bot# 登录QQbot.Login([-q, 2816626661])buddy 获取指定名称/备注的好友group 获取群buddy bot…

tp5 mysql实现消息队列_TP5系列 | Queue消息队列

消费信息如下ThinkPHP5 Queue消息队列优点1、Queue内置了 Redis,Database,Topthink ,Sync这四种驱动,本文使用Redis驱动2、Queue消息队列适用于大并发或者返回结果 时间有点长并需要批量操作的第三方接口,可用于短信发…

java创建临时文件夹_java创建临时文件

[java]代码库/*** 创建临时文件** param prefix* 临时文件名的前缀* param suffix* 临时文件名的后缀* param dirName* 临时文件所在的目录,如果输入null,则在用户的文档目录下创建临时文件* return 临时文件创建成功返回true,否则返回false*…

linux cmake编译安装mysql_Linux源码安装MySQL 5.6.12 (Cmake编译)

Linux源码安装MySQL 5.6.12 (Cmake编译)1.安装make编译器(默认系统自带)下载地址:tar zxvf make-3.82.tar.gzcd make-3.82./configuremakemake install2.安装bison下载地址:tar zxvf bison-2.5.tar.gzcd bison-2.5./configuremakemake install3.安装gcc-…

JAVA怎么实现网页退出系统_java后台实现js关闭本页面,父页面指定跳转或刷新操作...

关闭本页面,跳转到百度response.setCharacterEncoding("gbk");PrintWriter outresponse.getWriter();out.print("");out.print("");关闭本页面,刷新父页面response.setCharacterEncoding("gbk");PrintWriter ou…

java 布尔逻辑运算符_Java运算符

Java语言提供许多操作符。操作符是特殊的符号(symbol),它对一个或者两个、三个的操作数进行运算,然后返回一个结果,最简单的就像我们一年级学到的 -号。一般地,可以将运算符分为四大类:算数运算符、位运算符、关系运算…

Java自动化获取页面主题_基于Selenium2+Java的UI自动化(4) - WebDriver API简单介绍

1. 启动浏览器前边有详细介绍启动三种浏览器的方式(IE、Chrome、Firefox);private WebDriver driver null;private String chromeDriverDir "D:\\workspace\\A_Test\\resource\\chromedriver.exe";/*** 打开谷歌浏览器;*/public void openCh…

js java 反射机制_java 类加载机制和反射机制

一.类的加载机制jvm把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成jvm可以直接使用的java类型的过程。(1)加载将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一…

lambda 流 peek java_JDK8 流与λ表达式

λ表达式什么是λ表达式λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或者语句块。public int add(int x, int y) {return x y;}转换为λ表达式(int x, int y) -> x y;去除参数类型(x, y) -> x y;无参 以及 只有一个参…

理解java虚拟机工作后了解吗_JAVA入门到再次入门——深入理解JAVA虚拟机(二)|七日打卡...

前言为什么叫做入门到到再次入门请参考前一篇或个人博客,在此不再赘述,嗯哼,了解了JVM的基本运行流程以及内存结构,算是初步认识了JVM,跟着课本往前走,继续了解根据JVM的内存模型探索java当中变量的可见性以…

java访问错误404_如何解决 Java web 项目中的 404 错误

在使用 Tomcat 进行 Java Web 开发的时候,经常会遇到以下 HTTP 404 错误:错误代码为 HTTP 404(未找到),描述信息是:“The origin server did not find a current representation for the target resource or is not willing to di…

java double 的精度_Java Double的精度问题

Java.text类 DecimalFormatjava.lang.Objectjava.text.Formatjava.text.NumberFormatjava.text.DecimalFormatvoid setMaximumFractionDigits(int newValue) 设置某个数的小数部分中所允许的最大数字位数。void setMinimumFractionDigits(int newValue) …

java餐饮管理系统图片,基于jsp的酒店餐饮管理系统-JavaEE实现酒店餐饮管理系统 - java项目源码...

基于jspservletpojomysql实现一个javaee/javaweb的酒店餐饮管理系统, 该项目可用各类java课程设计大作业中, 酒店餐饮管理系统的系统架构分为前后台两部分, 最终实现在线上进行酒店餐饮管理系统各项功能,实现了诸如用户管理, 登录注册, 权限管理等功能, 并实现对各类酒店餐饮管…

php 验证码一直不对,ThinkPHP验证码老是出错怎么办

ThinkPHP验证码老是出错的解决办法:1、找到服务器php配置文件php.ini在网站根目录下建一个info.php文件。例如:D:\wwwRoot\wp 这个是网站的根目录,在此目录下,新建一个txt文档,输入如下代码:然后另存为info…

如何在php中插入数据并修改,php怎么同时向2张表里插入数据

情况是这个样子的:我要做一个发消息的表,因为接受人可能是多个,所以又给接收人一单独的表,(这种方案好还是全部都放到一张表里好点呢?)2张表的字段如下:message_id是第一张表的主键,如果收件人有…

java设计模式之道文字版,Java Web设计模式之道 PDF

资源名称:Java Web设计模式之道 PDF第一部分 仙人指路——设计模式简介第1章 设计模式概述1.1 设计模式是什么1.2 软件设计模式的发展历程1.3 作者阐述软件设计模式的主要方式第二部分 设计红宝书——设计模式原则详解第2章 设计原则之开闭原则2.1 何谓开闭原则2.2 …

matlab变量由非标量,matlab中的if语句

有条件性地执行语句语法if expressionstatementsend描述MATLAB计算表达式,如果产生一个逻辑真或者非零结果,然后就执行一条或者多条MATLAB命令语句。当有嵌套if时,每一个if必须和一个相应的end匹配。当你在if语句里面嵌套使用else if或者else…

rodbc 连接oracle,R語言 使用RODBC連接oracle數據庫

使用R語言有多種包可以連接oracle數據庫,我今天在這里講一下使用使用RODBC連接oracle數據庫。1. 如果你的本地是windows系統的話,你需要安裝oracle客戶端。2. 然后需要在ODBC管理者界面配置你要進行連接的數據庫數據及使用的驅動等信息。如下圖所示&…

oracle实验七 答案,Oracle表的常用查询实验(七)

Oracle表的常用查询实验(七)1.问题描述:有一个商品信息表,该表反应了各种商品的销售情况,一个产品是按照gid和gname两个字段来区分的,一个产品可能会有多个型号。create table T_Goods(Id int primary key,GId varchar2(10) not n…