一、水平扩展简单介绍


二、单机垂直扩容
直接增加硬件资源。

三、水平扩展:集群化
会话保持本身不是集群化,它是集群化之后必须解决的问题。

比直接买新设备更便宜,也更常见。


概念理解
后端的服务器负责提供数据,是上游服务器。
后端 Nginx 是下游,用 keepalived 保持高可用。
最前端的用户是中端用户。
用户请求转发到上游服务器,每一台上游服务器的代码都需要一样,达到集群的效果,也就是请求到任一服务器的效果一样,通过 proxy_pass 和 upstream 中转。
如下图,后端服务器不一样的是分布式。

本块理解 ip_hash
保持会话,使得用户一直和一台服务器会话,除了 ip_hash 也可以用 Redis + SpringSession,借助第三方存储,不在每一台独立存储 session,都去 redis 找,之前基础篇提到过。但是它也有缺点,java 的性能不如 nginx,而且增加服务器会加大开发和运维的成本。
ip_hash 保持会话
nginx 官方原生自带的一种方式。
通过 hash 算法把用户的 IP 取 hash 值,根据后端服务器取余数,由此定位后端服务器。
(例:假设用户 IP 为 x.x.0.1,ip_hash(0.1)=125,125 和后端服务器个数取余数,假设后端有 2 台就 125/2,余 1,请求指向服务器 1,假设是 0 就转发到第一台服务器。hash 是十六进制,方便演示用此表示)
(相同 IP 指向相同服务器的问题:很多用户都走公网 IP,这时候大量的请求都走一个服务器,会有流量倾斜,压力会大,所以 ip_hash 不适用于需要在局域网用的项目,但是它效率高啊,满足中小项目在初期的快速扩容需求)
问题:
- IP 过于集中
- 后端服务器宕机,用户会话就会消失
具体配置
现在有三台如图所示。

现在做测试:将 nginx 的请求负载到两台上,配置如下
# 在nginx上配置默认页面转发规则
upstream httpget {
ip_hash;
server 172.16.0.9;
server 10.1.4.16;
}
server {
listen 80;
server_name 168kaguyachi.site;
root /var/www/html;
index index.html;
location / {
proxy_pass http://httpget;
proxy_set_header Host $host;
add_header Content-Type "text/html; charset=utf-8";# 中文不乱码
resolver 127.0.0.1 valid=30s;# 本地DNS响应更快
}
}
# 在两台机器分别写入一句话
# 172.16.0.9 上执行
echo "分压2" > /tmp/index.html
cd /tmp && python3 -m http.server 80 &
# 10.1.4.16 上执行
echo "分压1" > /tmp/index.html
cd /tmp && python3 -m http.server 80 &
请求走一趟大概是这个顺序:
浏览器
↓ 访问 168kaguyachi.site
Nginx 网关 (VM-0-12-ubuntu)
↓ proxy_pass http://httpget;
↓ ip_hash 根据你的IP选择后端
后端 Python 服务 (10.1.4.16)
↓ 读取 /tmp/index.html 的内容
↓ 返回 "这是服务器16"
Nginx 网关
↓ 把后端的响应原样返回
你的浏览器 → 显示 "这是服务器16"
当访问默认端口会一直显示一个后端的标识,因为用了 ip_hash:

当然在 upstream 去掉 ip_hash 就变成轮询了。
补充内容,后端服务器仅对内网 nginx 开放,配置如下
sudo ufw allow ssh
sudo ufw allow from 10.1.0.12 to any port 80 proto tcp
sudo ufw --force enable
sudo ufw status numbered
URI 保持会话
访问相同的 url 被转发到同一服务器,有的情况会话用户不支持 cookie,直接把会话保持密码写到 url,如 http://IP:8080/index.jsp?pageNum=100&jsessionid=??,直接在 url 加入 jsessionid。
或者 url 请求了一个文件,根据文件定位到服务器上。
上述情况都需要有哈希算法,如果后端服务器上缓存的内容不一样,比如:
- 服务器 9 缓存了
/video/1.mp4 - 服务器 16 缓存了
/video/2.mp4
就用 url_hash,所有访问 /video/1.mp4 的请求全部打到服务器 9,命中缓存,不用去磁盘读。如果用了 ip_hash,两个不同的客户端访问同一个视频,可能会被分配到不同的服务器,缓存就浪费了。
具体配置
ip_hash 配置的部分不变,只改一下 upstream 即可(nginx 原生支持 urihash 算法)。
upstream httpget {
hash $request_uri;# 只修改这里
server 172.16.0.9;
server 10.1.4.16;
}
server {
listen 80;
server_name 168kaguyachi.site;
root /var/www/html;
index index.html;
location / {
proxy_pass http://httpget;
proxy_set_header Host $host;
add_header Content-Type "text/html; charset=utf-8";
resolver 127.0.0.1 valid=30s;
}
}
至此,负载均衡的方式已经学的差不多了,可以解决大部分的实际需求。
利用 java 的 cookie 保持
第一次登录服务器给用户下发文件,记录 cookie,下次登录携带此文件请求服务器,可以通过此文件取哈希值(不止 IP 和文件,很多东西都能取哈希值,因为哈希算法的本质就是把任意长度的数据映射成固定长度的数字,关键是如何辨别不同用户),与服务器会话连接。
写语法加 $ 的意思就是需要用到 nginx 的内部变量。
直接在 upstream 块配置好 hash $cookie_jsessionid,其余的与 ip_hash 一样。如果使用 java 程序,下发的就是 jsessionid,如果没有部署 tomcat 则不能用 jsessionid,也就是取的 hash 值是数据包 cookie 中 name 字段的值(value),所以里面 name 的值只要有都可以用。

好处:每个用户根据会话信息做负载均衡,请求不会一直打到一台服务器。
第三方模块 sticky
视频里的技术被淘汰了,该项目无人维护,现在商业版内置了 sticky 功能,开源版用之前讲的玩就行。
如果一个模块网上搜不到一些教程,可能已经被淘汰了。
upstream httpget {
sticky; # 商业版直接写,自动生成路由Cookie
server 172.16.0.9:8080;
server 10.1.4.16:8080;
}
商业版的 sticky 会自动给客户端生成一个名为 route 的 Cookie,后面的请求只要带着这个 Cookie,就会被转发到同一台后端服务器。如果某台后端挂了,它会自动把请求切换到另一台,并更新客户端的 Cookie。还有更精细的 sticky learn 模式,可以抓后端应用自己发的 JSESSIONID 或 PHPSESSID 这种 Cookie,和 Tomcat、PHP 的 Session 机制直接对接,这部分用到再看。

这个 route 可以改,cookie 的过期时间也可以变。
upstream httpget {
sticky cookie qianzaoaiyin expires=1h;
server 172.16.0.9:8080;
server 10.1.4.16:8080;
}
这个名字不能和后端下发的 jsessionid 冲突,否则会失效。
多标签页会共享 cookie。
sticky 还能保持后端是静态服务器下的状态维持。