九章算法电商秒杀系统 - Spring项目实战2021

爱共享 爱生活 加油 2021

百度网盘javascript

提取码:qhhv css

优化的点

  • 稳定不太变化的数据,减小数据查询
  • redis解决超卖
  • rabbitMQ削峰和限流
  • nginx负载均衡
  • 解决负载均衡多个api之间session不一样步的问题
  • nginx静态资源缓存
  • nginx资源压缩
  • cdn内容分发
  • 流量防刷以及反爬虫

1.稳定数据优化方式

问题分析:电商和咱们平时app中,90% 以上都是数据读取操做,在海量数下,数据库最后可能成为高并发的瓶颈,所以减小数据库交互是首先须要考虑的事情。可是要区分静态数据和动态数据。长期不变的数据,好比商品详情,图片,介绍等适合作缓存,可是评论就不适合作缓存。html

解决方案1:reids缓存

查询一次数据库后,把商品详情,图片,介绍等放入redis,而后下次redis有的话直接从reids查询,避免再次查数据库。
具体切换到 step2-静态数据优化 分支java

git checkout step2-静态数据优化

安装redisnginx

docker pull redis;
docker run -d --name redis1 -p 6379:6379 redis --requirepass "123456"

安装完redis,对应在yml中修改本身的redis配置,访问下面地址,查看redis,就会发现redis中缓存了商品相关信息,具体实现,查看代码,很简单。git

http://localhost:8080/good?gid=2000

解决方案2:html页面静态化

页面静态化是指把动态页面,jsp,freemarker等为每个商品生成一个html静态页面。动态数据例如评论就在html页面用ajax异步获取。这样,就直接访问html避免了页面数据查询,解析的过程。web

逻辑流程
  • 首先把全部商品生成固定html,而后编写定时器,把每5分钟更新过的的商品从新查询出来,从新生成固定的html页面
  • 对于例如评论等动态数据,用ajax动态获取
  • 由于是静态页面,须要使用nginx作页面转发
操做流程
  • 切换到 step2-静态数据优化 分支,启动项目
  • 执行http://localhost:8080/doStatic,修改doStatic方法中放静态文件的目录。并把static中layui的包拷到生成的html目录,要引入。goods.ftl中路径写的./
  • 启动nginx,脚本以下。
  • 访问http://localhost/page/2000.html 就能出现页面。

总结:http://localhost/page/2000.html这里的流程
是首先去容器内部/usr/share/nginx/html/去找html文件,而后实际上是访问到了挂载在本地生成html的目录,而后html里面发起ajax请求去获取评论,实际是经过nginx把请求经过upstream转发到本地。ajax

docker安装nginx,指令以下:
--- nginx
docker pull nginx;
docker run -d --name nginx -p 80:80 -v /Users/along/websoft/nginx:/etc/nginx/conf.d -v /Users/along/Desktop/template:/usr/share/nginx/html nginx

上面启动脚本意思是:映射80端口,而后挂载配置文件目录和放html的目录到nginx里面
启动脚本和html位置在要和上面启动命令中的一致。

--- nginx 脚本
#由于容器内部须要访问到外部的接口,因此须要在容器内容用ip+端口访问本地接口
upstream seckill {
    server 192.168.5.3:8080;    
}
server {
    listen       80;
    server_name localhost;
    #把接口转发到本地起的项目,个人是192.168.5.3:8080;  
    location / {
    proxy_pass http://seckill;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    #页面转发地址是容器内部地址  
    location /page {
       index index.html;
       alias /usr/share/nginx/html/;
    }
}
监听80端口,访问/放转发到本地接口,访问page的时候,定位到nginx的html放置目录,而后以前挂载过这个目录,就能访问到本机的挂载目录。

两种方案总结:redis

  • 方案一把静态数据存入到redis,操做简单。可是仍是须要付出解析动态页面的时间。
  • 方案二很是复杂,须要生成全部html,耗费空间,可是理论上,直接加载html是最快的。

2.redis解决秒杀超卖

现象分析

使用redis以前的通常作法是,查询数据库,若是剩余大于0,就抢到商品,并把剩余数量减一,并更新数据库。算法

int num = goodDao.selectRate(num) ; 
if(num>0){
    //处理生成订单逻辑
    num = num -1; 
    //更新数据
}

可是很明显,查询数量和更新数量以前是有时差的,有的请求执行到了生成订单逻辑,东西抢到了,可是没来得及更新数据库,可是有的请求在这时获取了剩余数量,那么就会超卖。
选取redis由于它是单线程模型,内存存储,官网宣称支持10w qps。并且天生分布式支持。

解决方案:

逻辑流程

  • 每隔5秒定时器扫秒杀表t_promotion_seckill
  • 若是扫到开始秒杀,就构建一个商品的list队列,key为seckill:count:秒杀id ,有几个商品,就往list里面初始化几个数据。
  • 当用户点击了秒杀按钮就left pop出商品,并把抢到商品的用户存到一个redis set中。弹出一个商品,就存一个userId.

操做流程:

  • 切换代码分支到 step3-redis解决超卖
  • 相关新增代码在定时器和GoodController的doOrder方法。
  • 定时器会扫到 t_promotion_seckill , 若是扫不到,请查看sql,修改数据的状态为0

3.rabbitMQ削峰和限流

现象分析

在抢购之后,每每会发起存数据库,支付等操做,可是很明显,以前的从redis中取商品,并添加中奖人到redis这个操做是很快的,可是订单入库,支付等操做是很慢的。假设处理取商品,存中奖人每秒能够处理1000,可是处理订单,支付等一秒处理100个,那么,若是是一连串操做,很明显,后面瓶颈,形成前面操做等待。严重的状况,因为支付过慢崩溃,致使前面一系列流程所有崩溃。
总而言之就是前台业务处理能力和后台业务能力不对称所致使的

解决方案 - 使用rabbitMQ消息队列

由于能够分离业务,前台业务处理快,尽管处理,而后后面生成订单,支付等在mq中按本身能处理的量异步处理。
rabbitMQ是使用Erlang开发,也有本身的虚拟机,编译后处处运行,并且编写分布式应用简单。

逻辑流程

  • 当redis取出商品,添加中奖id后,往rabbitMQ队列发送一条消息,这里就是消息生成者
  • 而后在消息消费者那里执行订单入库等逻辑

操做流程

  • 切换分支到
  • docker启动一个rabbitMQ服务,并在rabbitMQ管理界面建立一个exchange-order的交换机,而且建立一个queue-order的队列,把他们绑定
  • 在yml编写rabbitMQ相关配置
  • 在PromotionSecKillService 编写消息生产者
  • 在service编写OrderConsumer消费者逻辑

docker起rabbitMQ

docker pull rabbitmq;
docker run -d -p 15672:15672  -p  5672:5672  
-e RABBITMQ_DEFAULT_USER=admin 
-e RABBITMQ_DEFAULT_PASS=admin 
--name rabbitmq 
rabbitmq

用admin和admin打开loaclhost:5672.

4.nginx负载均衡

LoadBalance:负载均衡,把任务分配到多个服务器上进行处理,进而提升效率,缩短执行时间。

当一台机器性能到达极限的状况下,横向拓展机器的架构是很是好的,能在不改动代码的状况下成倍的提高效率。

逻辑流程:

  • nginx里面配置各个服务地址,请求同一个api的时候,按必定的策略分发请求到不一样服务。
  • 在docker启动nginx

操做流程

  • 切换代码到 step5-nginx负载均衡 分支,其中新增了一个 LBController 测试负载均衡的控制器。
  • 而后在启动设置里面设置 -Dserver.port=8081,-Dserver.port=8082,-Dserver.port=8083三个配置,启动三个服务。
  • 而后启动nginx
  • 调用 localhost/LB 这个地址,会发现,请求到不一样的端口。

nginx的分发策略

  • Round-Robin,轮询调度算法,当配置未指明的时候,默认就是轮询算法。就想听歌列表循环。
  • least-connected,下一次连接将被连接到活跃连接最少的机器上
  • ip-hash,利用IP地址哈希进行分配 。固定ip的请求分发到固定ip的服务
  • weight-balancing,按照权重分配,也就是权重越大,分到的请求越大。能力越大,责任越大。

我比较喜欢使用权重策略,基本都是根据服务器性能来分配。

nginx的配置以及启动脚本。

#nginx.conf,配置中的server是本地ip,使用权重的策略
upstream lb {
    #least_conn ; 最少链接
    #ip hash ; ip策略
    #权重
    server 192.168.5.3:8081 weight=4;
    server 192.168.5.3:8082 weight=1;
    server 192.168.5.3:8083 weight=2;
}
location / {
    proxy_pass http://lb;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

nginx在docker中的启动脚本和上面相同。

5.解决负载均衡多个服务之间session不一样的问题

问题分析

在部署的多个服务中若是使用session传递数据,可能存在获取不到的状况,由于假设在8081中执行登录,session只会存在8081这个服务中,可是下次请求分发到8082或者8083就获取不到session了。

解决方式

  • 利用redis存储session的内容。
  • org.springframework.session 这个jar包能够解决问题。只须要引入这个包,并在启动类加上 @EnableRedisHttpSession 注解,无需修改任何代码。

操做流程。

  • 切换代码到 step6-解决各个服务间session不一样步问题 分支
  • 和上一步负载均衡同样启动三个服务,开启nginx
  • 首先把启动类上的 @EnableRedisHttpSession 注释掉,而后先请求 localhost/login?u=along,而后在屡次请求localhost/check发现,有时候能获取到session,有时不能获取到。
  • 而后把注解打开,再执行上面的操做,发现,每次都能获取到session,查看redis,发现,redis多了session的信息。

6.nginx静态资源缓存

现象分析

像js,css,图片,字体等静态资源,他们都是一个个的url,每次访问的tamcat都会进行url解析和绑定。可是上线之后,它们几乎是不会修改的。因此在高并发的状况下很浪费资源。

处理方案

nginx区分静态资源和接口url,把url接口送日后端服务器处理,静态资源在nginx端缓存处理。
主要是修改nginx的配置,配置以下

#在nginx容器内的/home下面定义缓存文件夹。并设定2层目录,缓存大小以及过时时间。
proxy_temp_path /home/nginx-temp;
proxy_cache_path /home/nginx-cache levels=1:2 keys_zone=lb-cache:100m inactive=7d max_size=20g;

upstream lb {
    #least_conn ; 最少链接
    #ip hash ; ip策略
    #权重
    server 192.168.5.3:8081 weight=4;
    server 192.168.5.3:8082 weight=1;
    server 192.168.5.3:8083 weight=2;
}
#用正则匹配静态资源,~*是不区分大小写。
location ~* \.(gif|jpg|css|png|js|woff|html)(.*){
       proxy_pass http://lb;
       proxy_set_header Host $host;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       #使用上面定义的缓存地址
       proxy_cache lb-cache;
       proxy_cache_valid 200 302 24h;
       proxy_cache_valid 301 5d;
       proxy_cache_valid any 5m;
       expires 90d;
    }
location / {
    proxy_pass http://lb;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

而后重启nginx就行了,访问之后,进入容器

docker exec -it nginx /bin/bash 
cd /home/nginx-cache 
若是上面的文件夹出现了两层目录的静态文件,说明设置成功。

7.nginx资源压缩

现象分析

当静态资源被压缩后,下降了后端tomcat的压力,可是在实际开发过程当中,大量的压力不在于处理速度,而是在于带宽。带宽决定了下载的总速度,在总速度不变的状况下,资源传到浏览器的大小约小越好。越快下载完资源,卡顿的越少。

解释一下带宽和下载速度的关系把

带宽是网络中某一点到另外一点所能经过的"最高数据率",也就是瞬时最高的下载速度。
关于带宽和下载速度:首先了解一下存储的单位换算

  • 1GB = 1024MB
  • 1MB = 1024KB
  • 1KB = 1024B
  • 1B = 8bits
    网络传输是以bits为单位,以1M带宽为例,也就是1M bits,也就是瞬时下载速度作多为:
1M = 10^6;
bits换成B除以8,B换成kb除以1024
1M bits/s = 1000000bits/s = 125000 B/s ≈ 128kb/s

解决方案

nginx将静态资源打包成gzip传给浏览器,而后浏览器解压缩gizp(这一步浏览器自动完成)获取到资源文件。最少能较少30%内存,最多减小80%的内存损耗。

nginx配置

#打开gzip配置
gzip on;
gzip_min_length 1k;
gzip_types text/plain application/javascript text/css application/x-javascript font/woff;
#禁止IE 1-6 
gzip_disable "MSIE [1-6]\.";
gzip_buffers 32 4k;
#压缩级别1-9,选1就好了,大小差异不大
gzip_comp_level 1;

#在nginx容器内的/home下面定义缓存文件夹。并设定2层目录,缓存大小以及过时时间。
proxy_temp_path /home/nginx-temp;
proxy_cache_path /home/nginx-cache levels=1:2 keys_zone=lb-cache:100m inactive=7d max_size=20g;

upstream lb {
    #least_conn ; 最少链接
    #ip hash ; ip策略
    #权重
    server 192.168.5.3:8081 weight=4;
    server 192.168.5.3:8082 weight=1;
    server 192.168.5.3:8083 weight=2;
}
#用正则匹配静态资源,~*是不区分大小写。
location ~* \.(gif|jpg|css|png|js|woff|html)(.*){
       proxy_pass http://lb;
       proxy_set_header Host $host;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       #使用上面定义的缓存地址
       proxy_cache lb-cache;
       proxy_cache_valid 200 302 24h;
       proxy_cache_valid 301 5d;
       proxy_cache_valid any 5m;
       expires 90d;
    }
location / {
    proxy_pass http://lb;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

在docker重启nginx。访问页面,当看到浏览器,检查看到css,js等的response header中,编码为gzip。就说明设置成功了。

8.cdn内容分发

现象分析

对静态文件完成了gzip压缩,可是对图片等已经被压缩过的资源gzip传输效果并很差,因此对图片的带宽消问题并无解决。

解决方案

图片能够考虑使用第三方cdn解决带宽问题。

cdn简介

cdn全称Content dekivery network,就是内容分发网络。
例如中国很大,北京和深圳就隔者2000多千米,若是北京访问深圳的网络,必然延时很高。cnd就是把内容放在一台中心服务器上,而后各地有不少节点服务器,中心服务器会把内容同步到各地的节点服务器,而后就进访问资源。

优势

  • 就近访问,下降网络延时
  • 把对带宽的压力转移到了第三方
  • 数据多备份,一个服务挂了能够切换到另外一个,就就是高可用。

使用

能够买阿里云,腾讯云,七牛的cdn服务。
主要流程是买服务,下载客户端,把图片等资源上传到客户端,自动同步,而后为oss配置cdn,进行域名解析。就能经过域名访问到图片,最后就去修改项目中静态资源的路径。

9.流量防刷以及反爬虫

流量防刷

有些人会恶意一直访问网站,甚至用脚本去刷页面。
因此在必定时间内只容许一个用户访问固定的次数,一旦超过了就不容许访问。

爬虫

会有人用爬虫,爬网站,利用超连接分析内容,提取网站的重要信息,不只拷贝走了数据,并且,还很是消耗服务器资源。

解决方案

  • 在代码中,编写拦截器
  • 对每一个用户,记录ip对应的访问次数,在redis中用ip+请求头信息做为key,存1分钟内访问次数,每次访问count++
  • 若是超过设置的访问次数,就再也不容许访问。
  • 每一分钟清空一次key
操做流程
  • 切换代码到step7-流量防刷与反爬虫分支
  • 启动nginx和三个端口分别启动项目
  • 访问localhost/lb,并屡次刷新
  • 观察redis是否会有key,以及是否会返回禁用的信息
总结

终于写完啦。这篇文章主要是对代码和网络等几个层面作了优化,主要使用了nginx,redis,rabbitMQ等热门工具,但愿对各位有帮助,若是有问题,欢迎指正,一块儿讨论。

相关文章
相关标签/搜索