一、负载均衡-session会话保持

一、cookie、session、jwt

1.1 http协议的问题

http协议无状态,浏览器基于http协议与服务端通信,问题是:用户登录后,下次还要再登录,因为无状态,每次请求都是从头开始
http协议为啥就不能设计成有状态呢?效率低

1.2 cookie的诞生与运行原理

2、为了解决http协议状态的问题?浏览器诞生了cookie机制
允许服务端发送响应头,在响应头里设置存放到浏览器里的一些数据,这个机制称之为cookie,服务端生成的、通过响应头里Set-Cookie参数设置/存储到浏览器里的数据也称之为cookie数据

file

查看cookie数据
file

下次请求某个域名时,浏览器会找到该域名,然后携带上所有的cookie信息,这便你补了http协议无状态的缺陷

=======cookie机制本身并没有问题,但只用cookie会有安全问题!
在cookie机制诞生之初,最开始的网站,在服务端产生的一些数据,例如用户信息、权限相关等等,都存到了浏览器cookie里
后来发现不安全
1、cookie信息里存放着明文,容易泄露
2、cookie信息存放在客户端,容易被篡改,比如一个普通用户把自己的权限改成了root

1.3 session机制的诞生

1、为了弥补单方面使用cookie而造成的安全问题,大家选择把一个用户会话中需要保存的状态信息例如账号、权限、登录状态等等,都存放到了服务端,这些信息称之为会话数据,也通常被简称为session数据
2、然后服务端会对session数据进行加密,得到一个session的id号,然后只把这个id号设置到客户的cookie里,这样便解决了泄露与篡改的问题

=====session的问题是,限制了集群的规划,服务端集群需要做会话共享
session信息保存于服务端,而负载均衡代理的多个服务端,第一次会话可能打到了web01,然后会话信息存放到了web01机器上
第二次负载均衡将请求转发到了web02,便无法读取该session信息了

解决方法就是将会话信息存放到一个共享的地方,这个共享的地方当然可以是mysql但效率低,用redis会快一些

于是有相当长 一段时间内大家都会有redis来存放共享的session信息,为了防止单点,还要为redis做集群,整个网站硬性依赖redis,是硬性依赖,于是这个点便极大地限制了集群的规模无法分布式扩展

1.4 jwt的诞生

jwt:json web token,一个jwt如下图

file

jwt分成三部分,三部分由.拼接在一起
1、header: 指定hasah算法与类型

{
  "alg": "HS256",
  "typ": "JWT"
}
将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

2、payload:状态信息

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

3、signatrue:存放得是“header+payload+盐/密钥”三者通过头里存放得hash算法计算出来得hash值

# secret只有服务端知道
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

最后把1、2、3得到的值用.拼在一起就行了。
注意:
1、jwt本身是不加密的,存放在header、payload里得内容虽然都经过base64转换过,但base64并不是一种加密算法,可以非常简单的解开,但是日常开发中,你也可以在产生jwt后,再自己加密一次。
2、jwt得机制可以防止篡改,因为只有服务端才知道secret是什么,服务端收到jwt后会对header+payload+secret再做一次hash校验,与seginature对应上了才证明没有被篡改过

工作流程:
1、服务端不存状态信息,会把会话相关的状态信息经过处理后存入jwt,然后把这个jwt存入客户端的cookie或localstorage等任何前端可以拿到的地方
2、每次客户端带着这个token来到服务端的时候,服务端用自己才有的secret,重写对内容做一次hash校验,与发来的jwt中的signatrue对应上了,就证明了没有被篡改过

有了jwt这种机制,服务端便不再需要存状态信息了
服务端当然还会用redis来当缓存,但redis的作用不是用来存放应用强依赖的会话信息,整个应用不是硬性依赖redis,单从会话保持这个点上来看是很大程度释放了集群的扩展能力

jwt详解:https://egonlin.com/?p=9584

二、负载均衡会话保持案例

理论上讲,现在大多数新网站应用都会采用类似jwt的模式,而不是session的模式,但是针对一些旧的应用依然会采用session的存储方式
即将会话信息存放到服务端

所以我们还是来看一下这种方式

2.1 什么是会话保持

会话保持,即一个用户的请求被负载均衡无论分配到哪个web服务器上,会话信息依然存在,比如用户始终保持该有的登录状态

详解:

我们在访问网站的时候,进行登陆以后,服务器上回生成一个session,然后服务器会携带着session_id返回给浏览器记录一个cookie值,当第二次访问时,cookie会来服务器上与session进行对比,如果对比成功,则不需要重新登录

2.2 实现会话保持的思路

1、ip_hash:
负载均衡使用ip_hash算法,此时会话就存在各个web服务器的本地就行,一个用户对应自己唯一的ip,每次负载都打到同一台机器上

2、会话共享
把会话信息存放在一个所有web服务共享的存储空间里,这个共享的存储空间可以是

1、nfs共享存储(太慢):把多台后端服务器session文件目录挂载到NFS同一目录
2、mysql(也慢):通过程序将session存储到mysql数据库,需要程序本身支持
3、redis(块):通过程序将session存储到redis缓存,需要程序本身支持

2.3 搭建测试环境

(1)找一台机器ip地址为192.168.71.16

安装步骤见:https://egonlin.com/?p=9154 第1.3小节

(2)搭建两个一模一样的web:

2.0 关闭selinux、防火墙

sed -ri 's/enforcing/disabled/g' /etc/sysconfig/selinux 
systemctl stop firewalld

setenforce 0
iptables -t filter -F

2.1、安装php-fpm

执行以下命令,安装epel源。
yum -y install epel-release

执行以下命令,安装remi源。
yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm # 如果是centos9则将左边的数字7改为9

执行以下命令,安装Yum源管理工具。
yum -y install yum-utils

执行以下命令,安装PHP 7.4。
yum -y install php74-php-gd php74-php-pdo php74-php-mbstring php74-php-cli php74-php-fpm php74-php-mysqlnd

执行以下命令,验证PHP的安装版本。
php74 -v

回显如下类似信息:

PHP 7.4.33 (cli) (built: Jun  6 2023 15:55:08) ( NTS )
Copyright (c)  The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

# 配置信息(如果是centos9默认以socket方式启动,想用端口方式,需要改listen=127.0.0.1:9000)
cat /etc/opt/remi/php74/php-fpm.d/www.conf |grep -v '^;' |grep -v '^$'

# 执行以下命令,启动PHP服务并设置开机自启动。
systemctl start php74-php-fpm

systemctl enable php74-php-fpm
systemctl status php74-php-fpm

2.2、安装nginx对接php-fpm

cat > /etc/nginx/conf.d/web.conf << "EOF"
    server {
        listen       8888;
        server_name  localhost;

        #access_log /var/log/nginx/host.access.log  main;

    # 非.php结尾请求从root指定的目录里直接拿,例如/、/1.jpg、/a/b/2.css
    location / {
        root   /usr/share/nginx/html;
        index index.php index.html index.htm a.txt;
    }

    # 以.php结尾的请求,交给fastcgi程序处理,下面的配置没有一点是多余的
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_param  SCRIPT_FILENAME /usr/share/nginx/html$fastcgi_script_name;
        include        fastcgi_params;
    }
}
EOF

补充

将 EOF 用引号括起来,这样 shell 就不会在 EOF 中的文本中进行变量替换或命令替换。
cat > 文件 << "EOF"
This is a sample text with a dollar sign: $
EOF

2.3 部署应用程序

测试程序为phpmyadmin: 下载地址:https://www.phpmyadmin.net/downloads/
**phpMyAdmin **是一个用PHP编写的软件工具,可以通过web方式控制和操作MySQL数据库。通过phpMyAdmin 可以完全对数据库进行操作,例如建立、复制和删除数据等等
# 1、下载web应用包包
wget https://files.phpmyadmin.net/phpMyAdmin/5.2.1/phpMyAdmin-5.2.1-all-languages.zip
unzip phpMyAdmin-5.2.1-all-languages.zip

# 2、移动到nginx网站根目录
mv phpMyAdmin-5.2.1-all-languages /usr/share/nginx/html/php

# 3、修改数据库连接地址
cd /usr/share/nginx/html/php
cp config.sample.inc.php config.inc.php

vi config.inc.php # 修改连接数据库的代码
$cfg['Servers'][$i]['host'] = '192.168.71.16';

# 4、授权
chown -R nginx.nginx /usr/share/nginx/html/php

测试两台web都可以正常访问

file

file

(3)搭建一个负载均衡

[root@web03 ~]# cat /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
    worker_connections 1024;
}
http {
    upstream webserver {
    server 192.168.71.14:8888 max_fails=3 fail_timeout=5s;
    server 192.168.71.15:8888 max_fails=3 fail_timeout=5s;
    }
    server {
        listen       9090;
        server_name 192.168.71.12;
        location / {
           proxy_pass  http://webserver;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header Host $http_host;
           proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_403 http_404;
        }
    }
}

通过负载均衡访问

file

(4) 创建登录密码

phpMyAdmin就是管理数据库的,账号密码就是数据库里的账号密码,所以登录mysql创建

create user 'root'@'192.168.71.%';
alter user 'root'@'192.168.71.%' identified by '123';
grant all on *.* to 'root'@'192.168.71.%';
flush privileges;

(5) 输入账号密码登录

我们用负载均衡的地址http://192.168.71.12:9090/php登录,输入账号密码会报如下错误(因为涉及到会话共享的问题)

file

你直接用web服务器的地址http://192.168.71.15:8888/php/来登录是可以直接登录进去的,因为没有会话共享的问题,所有会话都存在这一台机器的本地
file

(6)查看php-fpm默认保存session的方式与路径

file

如果就采用默认的files来存储,那么我们首先就可以通过nfs共享存储来实现session共享

(6) 解决方式一

解放决方式一:挂载session文件的目录

# 1、nfs服务器
yum -y install nfs* -y
[root@lb ~]# cat /etc/exports 
/data 192.168.71.0/24(rw,sync,no_root_squash)

[root@lb ~]# mkdir /data
[root@lb ~]# exportfs -a

# 2、所有web
yum install nfs* -y

# 3、web01挂载
[root@web01 ~]# mount -t nfs 192.168.71.11:/data/ /var/opt/remi/php74/lib/php/session
[root@web01 ~]# chmod o+rwx /var/opt/remi/php74/lib/php/session

[root@web02 ~]# mount -t nfs 192.168.71.11:/data/ /var/opt/remi/php74/lib/php/session
[root@web02 ~]# chmod o+rwx /var/opt/remi/php74/lib/php/session

访问测试ok

file

(7)解决方式二:

找一台机器安装redis

yum install -y redis

vim /etc/redis/redis.conf

找到配置项 bind 127.0.0.1,然后将 127.0.0.1 修改为 0.0.0.0 或者你想让其绑定的具体IP,以允许远程访问。
找到protected-mode yes,并将其更改为 protected-mode no 这样可以允许非本地客户端连接。
增加设置密码:requirepass 123

重启redis,用客户端命令测试

systemctl restart redis # 端口默认6379
# 我们用redis-cli客户端命令测试,redis-cli并部署php程序要使用的客户端,php程序中有自己的客户端模块,需要在程序运行环境为php语言准备好相应的模块
[root@lb ~]# redis-cli -h 192.168.71.11 -p 6379 -a 123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.71.11:6379> keys *
(empty array)
192.168.71.11:6379> set name egon
OK
192.168.71.11:6379> get name
"egon"
192.168.71.11:6379> keys *
1) "name"
192.168.71.11:6379> 

修改两个web服务器上的php配置文件

[root@localhost session]# cat /etc/opt/remi/php74/php-fpm.d/www.conf 
..............
php_value[session.save_handler] = redis
php_value[session.save_path]    = "tcp://192.168.71.11:6379?auth=123"
;php_value[session.save_path]   = "tcp://192.168.2.11:6379"  # 如果redis不带密码,则使用这种配置
;php_value[session.save_handler] = files
;php_value[session.save_path]    = /var/opt/remi/php74/lib/php/session
..............

重新访问负载均衡:http://192.168.71.12:9090/php/报错

file

web服务器上没有为php环境安装php程序依赖的连接redis的模块

centos7.9、centos9通用安装方法

systemctl list-unit-files|grep php

wget http://pecl.php.net/get/redis-5.3.5.tgz
tar -zxvf redis-5.3.5.tgz
cd redis-5.3.5
# 1、必须下面命令
yum -y install php74-php-devel
/opt/remi/php74/root/usr/bin/phpize  

----查找php-config的位置
#find / -name php-config
/opt/remi/php74/root/usr/bin/php-config

----运行configure
./configure --with-php-config=/opt/remi/php74/root/usr/bin/php-config --enable-redis

----编译
make
----编译安装:提示安装后的目标目录
[root@web01 ~/redis-5.3.5]# make install
Installing shared extensions:     /opt/remi/php74/root/usr/lib64/php/modules/
[root@web01 ~/redis-5.3.5]# ls /opt/remi/php74/root/usr/lib64/php/modules/ |grep redis
redis.so

---增加配置加载redis.so
[root@web01 ~/redis-5.3.5]# cat /etc/opt/remi/php74/php.d/redis.ini
;redis
extension=redis.so

---重启
systemctl restart php74-php-fpm
---查看
[root@web01 ~/redis-5.3.5]# php74 -m |grep redis
redis

然后重新登录http://192.168.71.12:9090/php

file

发现session信息存入redis
file

上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术