赞
踩
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使用内存
修改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环境 并启动程序 此时可以通过公网访问
nginx主要功能:
使用nginx作为web服务器:作为web静态服务器 对静态资源的访问
使用nginx作为动静分离服务器:作为静态服务器的同时 利用反向代理 将动态请求反向代理到后端完成动态资源请求
使用nginx作为反向代理服务器
NAS(网络附加存储)是一种专用文件存储和共享系统,可以同时连接到许多不同的设备并允许访问同一磁盘,可以在没有任何干预的情况下扩展到理论上无限的容量。
统一修改前端文件中的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
在openresty上完成前端资源的部署 使得nginx容器能当成正常的web服务器使用
上传前端文件到html目录下
scp -r * root@8.142.73.233:/usr/local/openresty/nginx/html
注意:gethost.js文件中ip地址可能需要改 改动时重启nginx也不生效 需要在本地改完重新上传文件0
修改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
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
客户端和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模型
父子进程:master进程可以管理worker进程的内存空间 master进程用于管理worker进程 worker进程用于处理客户端连接 每个worker进程中只有一个线程 所以不允许有阻塞操作(若内部没有阻塞 实际上单线程比多线程快)
sbin/nginx -s reload:当重启时, master进程会收回所有worker的所有socket句柄 再load新的配置文件 启动新的worker进程 将socket句柄交给新worker进程管理
nodejs中单线程实现并发的方式是异步 缺点是处理顺序控制流麻烦 必须异步回调函数内嵌套异步回调函数
依附于线程的内存模型,切换开销小(线程切换有CPU开销 协程切换只有内存开销) CPU执行的依旧是线程 而不是协程
协程遇阻塞及归还执行权给线程,调用另一个不阻塞的协程来执行,代码同步 协程的开发是顺序编写代码 无需关注socket.read会返回 用来执行之后的操作 一旦返回了 epoll多路复用模型会发送通知 并回调协程的处理方式
无需加锁 串行执行
单机会话管理:(存储在内嵌的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对应的持久化磁盘 只需要使用其内存数据库存放缓存的功能
<!--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
redis序列化的方式与jdk相同 所有需要存入redis的数据均需要实现此接口
将所有的model类都implements Serializable
另一种方法是修改为Json的序列化方式 在跨系统中尤为好用
在数据库服务器上安装redis并修改redis.conf
bind 自己的内网IP
默认绑定在0.0.0.0 即所有所有能访问这台服务器的 不管是内网IP还是外网IP都可以
后台启动redis
src/redis-server ./redis.conf &
修改后端服务器的外挂配置文件 将其redis的IP改为redis服务器的内网IP
此时对于一次登录 不管nginx轮询到哪台后端服务器都能正常下单 解决了session存储的后端服务器和执行后续操作的后端服务器不同的问题
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命令可以清楚缓存
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。