注意:此项目仅为个人学习使用的项目
这个项目是根据我在慕课网上面学习的《Java秒杀系统方案优化 高性能高并发实战》这门课程后,又加了个支付宝支付的整合的一个项目。
这是一个基于java技术的手机秒杀网站主要是学习秒杀、多并发、性能提升方面的知识。
IDEA(Eclipse)+Maven
本地虚拟机 + centos7 (注:有真实服务器更好。可以测试更真实的压测数据。)
Redis Desktop Manager:redis客户端可视化软件
Navicat:mysql免费可视化管理软件
jMeter:压测工具,用于对网站进行压力测试
lombok : lombok是一个可以通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码的工具,
推荐一个前端页面代码生成的网站:http://www.ibootstrap.cn/
ProcessOn:一个画架构图以及各种图的软件
Spring Boot
MyBatis
Redis, MySQL
Thymeleaf
freemarker
RabbitMQ
Durid
Bootstrap
Ajax
jQuery
javaScript
根据架构图分析一下,
1、首先用户通过任意渠道访问我们的网站,然后根据一定的路由规则(比如对用户的ID进行hash)被分配到某个服务器接受服务。(我这里没有用Nginx做负载均衡,但实际应该要做的,不然所有用户都访问同一个服务器,负载 太大。)
2、当用户进行秒杀时,因为考虑到同一个时刻,并发量可能会特别大。所以不能让服务器直接访问DB,不然DB很容易挂掉,所以应该使用redis加缓存。用户在秒杀的时候在Redis中预减库存减少数据库的访问,同时使用内存标记减少redis的访问,(redis的处理能力也是有限的,负载太大也是会宕机的,所以这里也要进行Redis的保护,即加一个标记变量记录是否还有商品,如果商品已经没有了,那就置位,这样的话,后续的请求就不会去访问redis然后直接返回秒杀失败)。
3、RabbitMQ队列缓冲,异步下单。因为服务器处理下单涉及DB的读写,当并发量很大的时候,需要很多时间,从而用户体验会很不好,因为需要等待很久才知道结果。所以采用消息队列异步下单。即如果用户秒杀成功,那么创建的订单并不直接写入DB。而是给rabbitmq发送一条message.然后就直接返回给用户说下单成功。然后由监听消息队列的消费者根据接收到的消息,创建订单并写入DB.这里为了提高效率,可以使用一个线程池来解决并发及连接复用的的问题。
4、用户下单完成后,点击订单详情可以查看订单详情,然后选择立即支付。 可以使用支付宝支付,因为时测试,所以使用的是沙箱环境。想体验的朋友可以下载沙箱钱包来测试一下。支付完成后,可以回到商品列表继续秒杀。
因为高并发系统瓶颈在数据库:
根本解决方案加缓存!!!在保证数据一致性的前提下加缓存!
页面优化技术:
1、页面缓存+URL缓存+对象缓存
因为内存redis里面比DB要快,所以最好加各种各样的缓存,尽量少访问DB。
对象级缓存,如果有更新一定要记得把数据库和缓存一起更新,这样的才能保证数据一致性。
2、页面静态化,前后端分离
最近比较火的,Restful 风格:前后端分离,在本项目中前端就是html+Ajax ,只传输动态参数,也就是VO对象。然后前端拿到数据后通过Ajax进行渲染。把页面等静态资源缓存在客户端,这样前端和后端之间的交互就只传输需要的参数就行。并不需要后端模板引擎吧页面渲染好然后把整个页面传到前端,极大的减少了服务器的压力和网络带宽的压力。
3、静态资源优化
js/css压缩,减小流量。
多个js/css组合。减少连接数。
4、CDN优化(未涉及)
接口优化技巧:
1、Redis预减库存减少数据库的访问。
2、内存标记减少Redis访问。(即:如果预减库存那一步已经把flag置位,表示没有商品了。那就不应该访问redis了。这样以后的请求就可以直接返回秒杀失败,从而减少redis的压力)。
3、RabbitMQ队列异步下单,增强用户体验。原理见:秒杀过程
安全优化:
1、秒杀接口地址隐藏:
为了防止别人提前得到接口,通过机器人来刷单。所以必须得等到秒杀开始时候才能点击按钮,此时再去请求真正的地址。然后再进行秒杀。
2、数学公式验证码:
数学验证码或者其他什么选字验证码之类的,主要是也是为了防止机器人刷单比如按键脚本什么的。但是对于用户来说的话不太友好。因为太麻烦了。所以这个看情况使用。
3、接口防刷
主要就是防止按键脚本之类的。疯狂点击秒杀按钮。这样疯狂发送请求的话,容易给服务器带来很大的压力。所以我们对每个用户进行了限定。比如每个用户1秒钟内或者3秒钟内只能点击5次按钮。超过规定的次数。就返回点击太频繁的异常提示。后台接口不处理业务,直接返回异常。这样的话可以很大程度上减少服务器的压力。
具体实现:使用redis缓存服务存储每个user在一段时间内的访问次数。设置一个key和过期时间。如果在过期时间内次数超过规定次数,那就返回点击频繁异常。不进行任何操作。
补充:针对对象缓存
public MiaoshaUser getById(long id) {
//
MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
if(user != null) {
return user;
}
//缓存没有取数据库
user = miaoshaUserDao.getById(id);
if(user != null) {
redisService.set(MiaoshaUserKey.getById, ""+id, user);
}
return user;
}
// 缓存
public boolean updatePassword(String token, long id, String formPass) {
//取user
MiaoshaUser user = getById(id);
if(user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//更新数据库 1
MiaoshaUser toBeUpdate = new MiaoshaUser();
toBeUpdate.setId(id);
toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
miaoshaUserDao.update(toBeUpdate);
//处理缓存 2
redisService.delete(MiaoshaUserKey.getById, ""+id); // 先把以前的删掉
user.setPassword(toBeUpdate.getPassword());
redisService.set(MiaoshaUserKey.token, token, user); // 再存
return true;
}