SpringBoot电商秒杀项目优化知识点总结(第四章)

4-1 单机容量问题,水平扩展方案引入

nginx反向代理负载均衡
单机容量问题,水平扩展
nginx反向代理:可以代理后端tomcat服务器集群 以一个统一域名的方式暴露出去
负载均衡配置:轮询或IP touch的策略

单机容量问题,水平扩展
表象:单机cpu使用率增高,memory占用增加,网络带宽使用增加
cpu us :用户空间的cpu使用情况((用户层代码)
cpu sy :内核空间的cpu使用情况(系统调用)
load average : 1,5,15分钟load平均值,跟着核数系数,0代表通常,1代表打满1+代表等待阻塞
memory:free空闲内存,used使用内存

4-2 数据库远程开放端口连接

修改mysql server的ip白名单

MariaDB [mysql]> select host,user,password from user;
+-------------------------+------+-------------------------------------------+
| host                    | user | password                                  |
+-------------------------+------+-------------------------------------------+
| localhost               | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| iz8vbbrumg8hixw4h44ormz | root |                                           |
| 127.0.0.1               | root |                                           |
| ::1                     | root |                                           |
| localhost               |      |                                           |
| iz8vbbrumg8hixw4h44ormz |      |                                           |
+-------------------------+------+-------------------------------------------+

授予所有权限包括所有数据库的所有表给root用户所有的host但是要知道root密码

grant all privileges on *.* to root@'%' identified by 'root';

不会自动刷新 需要数据库重启后才能刷新

flush privileges;
+-------------------------+------+-------------------------------------------+
| host                    | user | password                                  |
+-------------------------+------+-------------------------------------------+
| localhost               | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| iz8vbbrumg8hixw4h44ormz | root |                                           |
| 127.0.0.1               | root |                                           |
| ::1                     | root |                                           |
| localhost               |      |                                           |
| iz8vbbrumg8hixw4h44ormz |      |                                           |
| %                       | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
+-------------------------+------+-------------------------------------------+

拷贝文件夹到服务器 使用内网更快

scp -r //var/www/ root@172.26.68.15:/var/

修改两台application服务器的数据库url 使用数据库服务器的内网地址

spring.datasource.url=jdbc:mysql://172.26.68.13:3306/seckill?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC

使用telnet检查端口是否接通 由于已修改数据库的权限 现在只需要输入密码即可连接 但是需要以二进制输入(java程序可以) 这里只要知道可以连接通就好

yum install telnet
telnet 172.26.68.13 3306
Trying 172.26.68.13...
Connected to 172.26.68.13.
Escape character is '^]'.
R
5.5.68-MariaDBY!q=xfv@�_San$fYh#o)|mysql_native_password

阿里云的ifconfig只能看到内网地址

在两台后端服务器上安装java环境 并启动程序 此时可以通过公网访问

4-3 修改前端资源用于部署nginx

nginx主要功能:
使用nginx作为web服务器:作为web静态服务器 对静态资源的访问
使用nginx作为动静分离服务器:作为静态服务器的同时 利用反向代理 将动态请求反向代理到后端完成动态资源请求
使用nginx作为反向代理服务器

NAS(网络附加存储)是一种专用文件存储和共享系统,可以同时连接到许多不同的设备并允许访问同一磁盘,可以在没有任何干预的情况下扩展到理论上无限的容量。

4-4 部署Nginx OpenResty

统一修改前端文件中的url

确认80端口没有被使用

netstat -an | grep 80

这里照着视频教程不成功 报错:

ERROR: failed to run command: gmake install TARGET_STRIP=@: CCDEBUG=-g XCFLAGS='-DLUAJIT_ENABLE_LUA52COMPAT -msse4.2' CC=cc PREFIX=/usr/local/openresty/luajit DESTDIR=/tmp/upload files/openresty-1.13.6.2/build/luajit-root/

按照这里下载安装成功 可改成视频所用版本:https://blog.csdn.net/A552112067/article/details/120032977
1)安装依赖库:

yum install libtermcap-devel ncurses-devel libevent-devel readline-devel pcre-devel gcc openssl openssl-devel per perl wget

2)下载安装包:

wget https://openresty.org/download/openresty-1.13.6.2.tar.gz

3)解压安装包

tar -xf openresty-1.13.6.2.tar.gz

4)进入安装包,并安装
#进入安装包

cd openresty-1.13.6.2

#安装

./configure --prefix=/usr/local/openresty --with-luajit --without-http_redis2_module --with-http_stub_status_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module

#编译并安装

make && make install

说明:

--prefix=/usr/local/openresty:安装路径
--with-luajit:安装luajit相关库,luajit是lua的一个高效版,LuaJIT的运行速度比标准Lua快数十倍。
--without-http_redis2_module:现在使用的Redis都是3.x以上版本,这里不推荐使用Redis2,表示不安装redis2支持的lua库
--with-http_stub_status_module:Http对应状态的库
--with-http_v2_module:对Http2的支持
--with-http_gzip_static_module:gzip服务端压缩支持
--with-http_sub_module:过滤器,可以通过将一个指定的字符串替换为另一个字符串来修改响应

关于每个模块的具体作用,大家可以参考腾讯云的开发者手册:https://cloud.tencent.com/developer/doc/1158
安装完成后,在/usr/local/openrestry/nginx目录下是安装好的nginx,以后我们将在该目录的nginx下实现网站发布。

5)配置环境变量:

vi /etc/profile
export PATH=/usr/local/openresty/nginx/sbin:$PATH
source /etc/profile

openresty目录结构:

bin:openresty相关的可执行程序
luajit:基于luajit开发的so和.a库 
lualib/redis/parser.so:提供了对redis连接的支持 可以直接在nginx内写lua脚本做redis的连接
nginx/conf:跟nginx相关的所有配置
nginx/html:放置nginx的html文件 欢迎界面html
nginx/logs:日志
nginx/sbin:只有nginx可执行文件 可以在官网下载nginx  自定义配置并编译  替换此文件

在nginx目录下启动nginx 默认后台启动

sbin/nginx -c conf/nginx.conf

4-5 前端资源部署

在openresty上完成前端资源的部署 使得nginx容器能当成正常的web服务器使用

上传前端文件到html目录下

scp -r * root@8.142.73.233:/usr/local/openresty/nginx/html

注意:gethost.js文件中ip地址可能需要改 改动时重启nginx也不生效 需要在本地改完重新上传文件0

4-6 前端资源路由

修改conf/nginx.conf:alias 替换url

location /resources/ {
    alias  /user/local/openresty/nginx/html/resources/;
    index  index.html index.htm;
}

将前端文件转移至resources目录
重启nignx 发现master进程号不变 worker进程号改变

[root@iZ8vb7eyqiijfv2zr0kmatZ nginx]# ps -ef | grep nginx
nobody    1215 20055  0 07:16 ?        00:00:00 nginx: worker process
root      1375 10855  0 07:20 pts/0    00:00:00 grep --color=auto nginx
root     20055     1  0 06:25 ?        00:00:00 nginx: master process sbin/nginx -c conf/nginx.conf
[root@iZ8vb7eyqiijfv2zr0kmatZ nginx]# sbin/nginx -s reload
[root@iZ8vb7eyqiijfv2zr0kmatZ nginx]# ps -ef | grep nginx
nobody    1401 20055  0 07:20 ?        00:00:00 nginx: worker process
root      1419 10855  0 07:20 pts/0    00:00:00 grep --color=auto nginx
root     20055     1  0 06:25 ?        00:00:00 nginx: master process sbin/nginx -c conf/nginx.conf

4-7 配置nginx反向代理

nginx做反向代理服务
在nginx/conf/nginx.conf中设置upstream server:真正nginx的后端反向代理服务器节点 weight表示轮询为1:1

upstream backend_server{
    server 172.26.68.17 weight=1;
    server 172.26.68.15 weight=1;
}

设置动态请求location为proxy pass路径

#除了resources目录外的所有请求都当做动态请求
location / {
    #proxy_pass表示当路径命中在该节点上时  nginx不处理请求  而是反向代理到遵循http协议的backend_server	
     proxy_pass http://backend_server;
    #修改http请求中的host字段   Host 请求头指明了请求将要发送到的服务器主机名和端口号
    proxy_set_header Host $http_host:$proxy_port;
    #在多级代理中,X-Real-Ip用于记录请求的最初的客户端地址
    proxy_set_header X-Real-IP $remote_addr;
    #在多级代理中,X-Forwarded-For用于记录从客户端地址到最后一个代理服务器的所有地址
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

重新加载配置

sbin/nginx -s reload

开启tomcat access log验证
日志记录为异步处理 不会过多影响线程处理时间

%h:remote host name  远端请求的ip地址
%l:remote logical username from identity   默认return -
%u:remote user 用来看远端请求的具体地方
%t:处理时长
"%r":请求方法 请求url
%s:http的返回状态码
%b:请求response的大小
%D:处理请求的时长
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.directory=/var/www/seckill/tomcat
//var/www/seckill/tomca
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D

4-8 分布式扩展后的性能压测

客户端和nginx已配置好长链接
nginx和后端服务器默认是短连接
后端服务器和数据库服务器采用druid默认长链接

#查看80端口所有的线程数

netstat -an | grep 80 | wc -l

#查看跟其中一个后端服务器连接的线程数 启动压测时发现线程数变化 说明是间联

netstat -an | grep 172.26.68.17 | wc -l

在nginx/conf/nginx.conf中添加配置

upstream backend_server{
    server 172.26.68.17 weight=1;
    server 172.26.68.15 weight=1;
    #设置连接时长为30秒
    keepalive 30;
}

location / {
    proxy_pass http://backend_server;
    proxy_set_header Host $http_host:$proxy_port;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #默认采用http1.0协议 不支持keepalive 且Connection为close 修改完成默认使用keepalive
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

重新加载配置

sbin/nginx -s reload

#查看与后端服务器连接的所有端口号 发现不会变化 为长链接

netstat -an | grep 172.26.68.15 | grep ESTABLISH

分布式架构解决了单机容量瓶颈的问题 原来对三合一服务器的巨大压力被分流了 可以给CPU做无限制的扩展
分布式架构能提升TPS 10%左右 nginx和application server的keepalive可以降低延迟 20%左右

4-9 Nginx高性能原因—epoll多路复用:解决了I/O阻塞的回调通知问题
java bio模型:缺点是阻塞进程式
java的client和server通过TCP/IP的socket长链接完成间连的操作,每当client向server发送数据, 则有socket.write操作 java client只有等到socket.write所有的字节流input到TCP/IP的缓冲区以后 对应的java client才会返回 若网络发送很慢 缓冲区被塞满 java client就只能等到上条数据传输完成 缓冲区空出来才能传输下一条数据

linux select模型:缺点是变更触发轮训查找,有1024数量上限
一个java server 监听多个java client客户端连接在内存的句柄上且阻塞自己 每当有变化时唤醒自己 循环遍历所有连接 找到发生变化的一个或多个client 执行socket.read操作 一旦java server被唤醒 且有变化 就表示一定有空间可以写 或者有数据可以读 此模型没有阻塞 会立马返回

epoll模型,变更触发回调直接读取,理论上无上限
linux2.6内核以后才有 监听多个客户端并设置回调函数 若有变化则唤醒自己并执行回调函数

java nio模型借用了linux的select和epoll的设计思想 其select函数在linux2.6及以上内核的jdk环境中会转为epoll模型

4-10 Nginx高性能原因—master-worker进程模型:允许平滑的重启和重新加载配置 不会断开与客户端的连接 依赖于进程模型完成对应操作

父子进程:master进程可以管理worker进程的内存空间 master进程用于管理worker进程 worker进程用于处理客户端连接 每个worker进程中只有一个线程 所以不允许有阻塞操作(若内部没有阻塞 实际上单线程比多线程快)

sbin/nginx -s reload:当重启时, master进程会收回所有worker的所有socket句柄 再load新的配置文件 启动新的worker进程 将socket句柄交给新worker进程管理

4-11 Nginx高性能原因—协程机制:非阻塞式编程机制 在单进程单线程模型上支持并发调用的接口

nodejs中单线程实现并发的方式是异步 缺点是处理顺序控制流麻烦 必须异步回调函数内嵌套异步回调函数

依附于线程的内存模型,切换开销小(线程切换有CPU开销 协程切换只有内存开销) CPU执行的依旧是线程 而不是协程
协程遇阻塞及归还执行权给线程,调用另一个不阻塞的协程来执行,代码同步 协程的开发是顺序编写代码 无需关注socket.read会返回 用来执行之后的操作 一旦返回了 epoll多路复用模型会发送通知 并回调协程的处理方式
无需加锁 串行执行

4-12 分布式会话课题引入

单机会话管理:(存储在内嵌的tomcat中)
基于cookie传输sessionid : java tomcat容器session实现 (缺点:很多移动端app会禁止掉cookie)
基于token传输类似sessionid : java代码session实现

分布式会话管理:
基于cookie传输sessionid : java tomcat容器session实现迁移到redis
基于token传输类似sessionid : java代码session实现迁移到redis
redis在分布式会话的场景下 无需开启redis对应的持久化磁盘 只需要使用其内存数据库存放缓存的功能

4-13 分布式会话实现(上)

<!--springbot对redis的依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring将自己对于session的管理方式存储在redis 依赖上一个依赖-->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
  <version>2.0.5.RELEASE</version>
</dependency>

修改配置:默认连接时长

@Component
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisConfig {
}

安装redis
本人在win上用msi安装redis3.0.504后启动报错:
[80712] 09 Jul 19:15:01.713 # Creating Server TCP listening socket *:6379: bind: No error
解决方式:

redis-cli.exe
shutdown
exit
redis-server.exe redis.windows.conf

在application.properties中配置springboot对redis的依赖
#配置springboot对redis的依赖

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=10

#设置jedis连接池
#最大最小的连接数量

spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.min-idle=20

4-14 分布式会话实现(中)

redis序列化的方式与jdk相同 所有需要存入redis的数据均需要实现此接口
将所有的model类都implements Serializable

另一种方法是修改为Json的序列化方式 在跨系统中尤为好用

4-15 分布式会话实现(下)

在数据库服务器上安装redis并修改redis.conf
bind 自己的内网IP
默认绑定在0.0.0.0 即所有所有能访问这台服务器的 不管是内网IP还是外网IP都可以

后台启动redis
src/redis-server ./redis.conf &

修改后端服务器的外挂配置文件 将其redis的IP改为redis服务器的内网IP
此时对于一次登录 不管nginx轮询到哪台后端服务器都能正常下单 解决了session存储的后端服务器和执行后续操作的后端服务器不同的问题

4-16 基于token的分布式会话实现(上)

UserController.java

 @Autowired
    private RedisTemplate redisTemplate;

    //用户登录接口
    @RequestMapping(value = "/login", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType login(@RequestParam(name = "telphone") String telphone,
                                  @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException {
        //入参校验
        if (StringUtils.isEmpty(telphone) || StringUtils.isEmpty(password)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }

        //用户登录服务,用来校验用户登录是否合法
        //用户加密后的密码
        UserModel userModel = userService.validateLogin(telphone, this.EncodeByMd5(password));

        //修改为将token存入redis中
        //将登陆凭证加入到用户登录成功的session内
//        this.httpServletRequest.getSession().setAttribute("IS_LOGIN", true);
//        this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userModel);

        //生成登录凭证UUID
        String uuidToken = UUID.randomUUID().toString();
        uuidToken = uuidToken.replace("-","");
        //建立token和登录态之间的联系
        redisTemplate.opsForValue().set(uuidToken, userModel);
        redisTemplate.expire(uuidToken,1, TimeUnit.HOURS);

        return CommonReturnType.create(uuidToken);

    }

OrderController.java

    @Autowired
    private RedisTemplate redisTemplate;
    
    //封装下单请求
    @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId,
                                        @RequestParam(name = "promoId",required = false) Integer promoId,
                                        @RequestParam(name = "amount") Integer amount) throws BusinessException {

        //获取用户登录信息
        //Boolean isLogin = (Boolean) httpServletRequest.getSession().getAttribute("IS_LOGIN");
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "the user has not logged in and cannot place an order.");
        }
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if (userModel == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "the user has not logged in and cannot place an order.");
        }
        //UserModel userModel = (UserModel) httpServletRequest.getSession().getAttribute("LOGIN_USER");
        OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId, promoId, amount);

        return CommonReturnType.create(null);
    }

login.html

$.ajax({
    type:"POST",
    contentType:"application/x-www-form-urlencoded",
    url:"http://"+g_host+"/user/login",
    data:{
        "telphone":telphone,
        "password":password
    },
    //允许跨域请求
    xhrFields:{withCredentials:true},
    success:function (data) {
        if (data.status=="success") {
            alert("login successful");
            var token = data.data;
            window.localStorage["token"] = token;
            //window.localStorage:H5才有  比cookie更安全 容量更大 没有4kb的限制 本质是key-value的数据库
            window.location.href = "listitem.html";
        }else {
            alert("Login failed due to " + data.data.errMsg);
        }
    },
    error:function (data) {
        alert("Login failed due to "+data.responseText);
    }
});

getitem.html

$("#createOrder").on("click", function() {
    var token = window.localStorage["token"];
    if (token == null) {
        alert("Not logged in, can't place an order.");
        window.location,href="login.html";
        return false;
    }
    $.ajax({
        type: "POST",
        url: "http://"+g_host+"/order/createorder?token="+token,
        contentType: "application/x-www-form-urlencoded",
        data: {
            "itemId": g_itemVO.id,
            "promoId": g_itemVO.promoId,
            "amount": 1,//暂时写死为一件
        },
        xhrFields:{
            withCredentials:true
        },
        success: function(data) {
            if (data.status == "success") {
                alert("successfully ordered");
                window.location.reload();
            } else {
                alert("The order failed, the reason is " + data.data.errMsg);
                if (data.data.errCode == 20003) {
                    window.location.href="login.html";
                }
            }
        },
        error: function(data) {
            alert("The order failed, the reason is " + data.responseText);
        }
    });
});

使用window.localStorage命令在chrome能查看到token firefox不行 估计和设置有关
redis使用flushall命令可以清楚缓存

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值