黑帽联盟

 找回密码
 会员注册
查看: 1351|回复: 1
打印 上一主题 下一主题

[集群服务] 用Nginx+Redis实现session共享的均衡负载

[复制链接]
yun 黑帽联盟官方人员 

920

主题

37

听众

1364

积分

超级版主

Rank: 8Rank: 8

  • TA的每日心情
    奋斗
    2019-10-18 11:20
  • 签到天数: 678 天

    [LV.9]以坛为家II

    前言
    大学三年多,也做个几个网站和APP后端,老是被人问到,如果用户多了服务器会不会挂,总是很尴尬的回答:“哈哈,我们的用户还少,到了服务器撑不住的时候,估计都上市了吧”。说是这么说,但是对于有强迫症的我,这个问题一直回响在我脑海里,久久不散啊。如今大四下了,终于有时间来深入了解一下这个问题了。

    貌似解决大访问量的方案有硬件和软件两个大类的方法,硬件一般比较贵,学生党就不去考虑了。还是想想怎么用软件解决吧。于是乎,Google,Baidu,balabala... 搜到最多的词就是“均衡负载”,搭配的一般都是Nginx。找到了方向,那就撸起袖子干活吧。

    集群搭建
    首先在vmware12中安装3台debain,命名为debian1,debian2,debian3。一路默认就好(其实并不好,后面会说)。

    vmware有个问题,一旦窗口获得焦点,就自动关闭了小键盘,导致我设置root密码的时候输入为空(它也没提示)。后来我想用su命令才发现密码错误,输入空密码一样错误,就只有找回密码了。

    对于debian来说,这样改:在grub界面光标指向待启动的系统,然后按 e 键进行编辑,如图:
    31.png

    在 quiet 后面加个1(注意要有空格),按F10,你就可以以root身份进入命令行界面的。
    32.png

    这时候就用passwd修改密码,然后reboot就可以了。

    终于打开了,准备试试网络,发现无法访问外网,但是windows主机可以,如果一路默认的话不应该出现问题,最有可能就是杀毒软件把vmware的服务进程给关了(装了360...)。在windows中启动Vmware的DHCP服务
    33.png

    然后虚拟机要reboot一下来获取ip。好了,现在虚拟机可以访问外网了。
    安装nginx,才发现根本连不上,一看才发现是老美的源,应该是一路默认惹的祸啊,修改为科大源(我为母校自豪,哈哈)。
    vi /etc/apt/source.list

    修改为:
    1. deb http://mirrors.ustc.edu.cn/debian/ wheezy main non-free contrib
    2. deb http://mirrors.ustc.edu.cn/debian/ wheezy-proposed-updates main non-free contrib
    3. deb-src http://mirrors.ustc.edu.cn/debian/ wheezy main non-free contrib
    4. deb-src http://mirrors.ustc.edu.cn/debian/ wheezy-proposed-updates main non-free contrib

    5. deb http://mirrors.ustc.edu.cn/debian-security/ wheezy/updates main non-free contrib
    6. deb-src http://mirrors.ustc.edu.cn/debian-security/ wheezy/updates main non-free contrib
    复制代码
    然后执行这个命令来更新: apt-get update
    安装: apt-get install nginx
    启动: /etc/init.d/nginx start
    随便用一个虚拟机开启一个浏览器打开localhost,成功启动,如图:
    34.png

    vi用不惯安装vim: apt-get install vim 报错:
    1. The following packages have unmet dependencies:
    2. vim : Depends: vim-common (= 2:7.3.547-7) but 2:7.4.488-7 is to be installed
    3. E: Unable to correct problems, you have held broken packages.
    复制代码
    可见冲突了,解决方法:
    先执行 apt-get remove vim-common 卸载vim-common
    再进行安装vim,执行 apt-get install vim
    找找nginx的根目录,我们打开配置文件(和Apache一样,配置文件模块化的,不是一个单独的nginx.conf)看一看

    vim /etc/nginx/sites-enabled/default  

    中间有一行

    root /usr/share/nginx/www;
    这就是根目录啦
    修改index.html来区分三台主机
    用ipconfig 分别获得 ip 地址,在windows中访问
    1. debian1  http://192.168.182.128/

    2. debian2  http://192.168.182.129/

    3. debian3  http://192.168.182.130/
    复制代码
    基础尝试
    先来一个小例子,以便对均衡负载产生一个直观的感受吧。
    我们把debian1作为主服务器承担请求分发的任务,即外部访问的是debian1,然后debain1把请求发送给debian2或者debain3,如下图:
    35.png

    在debian1中修改配置文件 :vim /etc/nginx/nginx.conf
    在http配置项中加入如下
    1. upstream site {
    2.       server  192.168.182.129:80;
    3.       server  192.168.182.130:80;
    4. }
    5.   
    6. server{
    7.     listen 80;  
    8.     location / {
    9.         proxy_pass         http://site;
    10.     }
    11. }
    复制代码
    这是选择的轮询的模式保存重启nginx。
    现在在windows中访问debian1,http://192.168.182.128/。多次刷新 可见如下两图依次出现:
    36.png

    37.png

    说明发送给 debian1 的请求的确是均匀分配到 debian2和debian3了,亦即轮询。

    session共享
    上面的例子可以说简单到没有什么实用价值,大型网站一般不可能是纯静态的,一般都涉及到用户登录的问题,那么就涉及到session的问题了。你想用户在A登陆了,A记住了用户的登录状态,可是下一次用户请求被分配到B去了怎么办?显然不可能让用户再登陆一次。所以要实现session共享。一般有几个解决办法:
    • iphash,把特定ip发送给特定主机,就不存在session这个问题了,因为1个用户对应1台主机。但是某时刻当来自某个IP地址的请求特别多,那么将导致某台负载服务器的压力可能非常大,而其他负载服务器却空闲的不均衡情况,这就违背了我们负载均衡的初衷。
    • 搭建redis集群或者memcached集群,用集群自带的同步方法来帮我们在不同的主机中同步session,这样就相当于把原来的一份session变成了N分session(有点浪费,哈哈),session的同步就依赖于NoSql集群的同步了。
    • 不使用session,换作cookie。但是秉承着防御性编程的原则,我们不能相信用户输入,因为cookie可能被禁用,甚至篡改。
    • 单独设置一个session服务器,负载服务器得到一个sessionid过后,去session服务器获得会话状态,然后根据状态来响应用户请求,如果会话状态为空,则在session服务器中设置一个会话状态,然后返回给用户一个sessionid。

    我准备采用方案4,即用debian1作为分发服务器,同时作为session服务器(用redis实现),负载服务器每次都要向分发服务器请求用户的session对应的会话状态,以此决定响应方式。

    php 环境搭建
    • 在debain2,3中搭建php环境

    先在2中修改命令 :
    1. apt-get update    #更新源
    2. apt-get install php5        #安装php5
    3. apt-get install php5-cli        #安装php5 命令行工具
    4. apt-get install php5-fpm
    复制代码
    最后一句就报错了:
    1. The following packages have unmet dependencies:
    2. gnupg : Depends: libreadline6 (>= 6.0) but it is not going to be installed
    3.    Recommends: gnupg-curl but it is not going to be installed
    4.      php5-fpm : Depends: libssl1.0.0 (>= 1.0.0) but it is not going to be installed
    5.      Depends: php5-common (= 5.4.45-0+deb7u2) but it is not going to be installed
    6.      Depends: ucf but it is not going to be installed
    7.      Depends: tzdata but it is not going to be installed
    8.       PreDepends: dpkg (>= 1.16.1~) but it is not going to be installed
    复制代码
    搞了好久也没解决,还是换个fastcgi管理工具吧(回头再来啃一啃):
    1. apt-get install spawn-fcgi
    复制代码
    启动spawn-fcgi:
    1. /usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -C 5 -u www-data -g www-data -f /usr/bin/php-cgi
    复制代码
    说明:
    1. -a : PHP FastCGI 绑定IP地址

    2. -p : PHP FastCGI 指定端口

    3. -u : PHP FastCGI 用户名

    4. -g : PHP FastCGI 用户组

    5. -f : 指向 PHP5 fastcgi
    复制代码
    另外
    1. `vim /etc/rc.local`
    复制代码
    加入上述命令使得它开机自启
    配置nginx的php选项(还是看官网比较好,不要到处乱搜):
    1. location ~ [^/]\.php(/|$) {
    2.     fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    3.     if (!-f $document_root$fastcgi_script_name) {
    4.         return 404;
    5.     }

    6.     fastcgi_pass 127.0.0.1:9000;
    7.     fastcgi_index index.php;
    8.     include fastcgi_params;
    9. }
    复制代码
    重启:/etc/init.d/nginx restart
    在根目录中加入1.php
    1. <?php
    2. phpinfo();
    3. ?>
    复制代码
    访问,终于成功了,泪奔
    38.png

    接下来对debain3 如法炮制。总算是完成这一步了。

    redis 环境搭建
    • 在debain1中搭建redis服务器

    命令:
    1. wget http://download.redis.io/releases/redis-2.8.12.tar.gz
    2.     tar xzf redis-2.8.12.tar.gz
    3.     cd redis-2.8.12
    4.     make
    复制代码
    编译成功,运行: ./src/redis-server redis.conf
    修改配置 打开 redis.conf
    • 把 bind 127.0.0.1 修改为 bind 0.0.0.0 即任意主机可以访问
    • 找到“requirepass”字段,在后面加上密码 password

    重启redis服务器:
    1. ./src/redis-cli -h 127.0.0.1 -p 6379 shutdown #关闭
    2.     ./src/redis-server redis.conf 开启
    复制代码
    这时你会发现如果用redis客户端直接访问会报错要输入密码后在能正常使用,如图:
    39.jpg

    • 在debain2,3中配置phpredis

    命令:
    1.     apt-get install php5-dev #php开发者工具,后面编译需要
    2.     wget https://github.com/nicolasff/phpredis/archive/master.tar.gz
    3.     tar xvf master.tar.gz
    4.     cd phpredis-master/
    5.     phpize
    6.     ./configure --enable-redis
    7.     make && make install
    复制代码
    然后修改配置:
    1. vim /etc/php5/cgi/php.ini
    复制代码
    在Dynamic Extensions 后面添加extension=redis.so
    重启服务:还真没找到成熟的解决办法,只有采取笨办法了
    1. lsof -i :9000  #列出该端口相关信息,包含PID
    2. kill -9 pid  # 把上一步显示出来的pid挨个杀死
    3. /usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -C 5 -u www-data -g www-data -f /usr/bin/php-cgi  #启动
    复制代码
    测试:在debian3的nginx根目录添加1.php 代码如下:
    1. <?php
    2. $redis_host = '192.168.182.128';
    3. $redis_port = 6379;
    4. $redis_psw = 'password';

    5. $redis = new Redis ();
    6. $redis->connect ( $redis_host, $redis_port );
    7. $redis->auth ( $redis_psw );
    8. $redis->set('a',1);
    9. echo $redis->get('a');

    10. ?>
    复制代码
    结果如下:
    40.png

    可见是成功了,对debian2如法炮制,效果一样

    逻辑实现
    负载服务器查看客户端是否带有sessionid这个参数,如果有,则去session服务器获取会话状态并返回结果,否则产生一个session和会话状态存入session服务器并返回sessionid给客户端。这是一个大概的逻辑轮廓,细节就不讨论了,实现如下:
    • 修改debian2,3的nginx配置文件使得默认路径是index.php 而非 index.html,同时删掉原有的index.html,加入index.php。重启。
    • index.php 代码如下:
      <?php
      //初始化连接
      $redis_host = '192.168.182.128';
      $redis_port = 6379;
      $redis_psw = 'password';
      $redis = new Redis ();
      $redis->connect ( $redis_host, $redis_port );
      $redis->auth ( $redis_psw );
      $sessionid = ceil($_GET['sessionid']);
      $hostname = 'debian2';//debian3就要改成debian3
      if(empty($sessionid)){//没有sessionid
      $sessionid = rand(10000000,99999999);//简便起见产生8位数字作为有效id$status = '已经登陆,由 '.$hostname.' 设置session';$redis->set($sessionid,$status);//保存$data = array('当前站点'=>$hostname,'sessionid'=>$sessionid,'info'=>'这是您第一次登陆');echo json_encode($data,JSON_UNESCAPED_UNICODE);exit();
      }
      $status = $redis->get($sessionid);
      if(empty($status)){//sessionid无效
      $sessionid = rand(10000000,99999999);//简便起见产生8位数字作为有效id$status = '已经登陆,由 '.$hostname.' 设置session';$redis->set($sessionid,$status);$data = array('当前站点'=>$hostname,'sessionid'=>$sessionid,'info'=>'这是您第一次登陆');echo json_encode($data,JSON_UNESCAPED_UNICODE);exit();
      }
      $data = array('当前站点'=>$hostname,'sessionid'=>$sessionid,'info'=>$status);
      echo json_encode($data,JSON_UNESCAPED_UNICODE);
      exit();
      ?>
    • 测试

    首次访问debian1
    41.png
    再次访问debain1,这里就出了点问题,不知道为什么,一直发送到到debian2,连续尝试很多次,没有一次请求到debian3,根本就没有轮询啊。但是过了几分钟再次访问debian1
    42.png
    请求就发送到debian3了,我估计是用了php过后,nginx把一小段时间内的请求发送到同一主机了,但是一大段时间上还是轮询的。
    但是我换了个浏览器过后,又变成每次轮询了。一头汗...... 所以这还是和浏览器有关的?(暂时搞不定。回头再看看,先换个浏览器)
    首次访问debian1
    43.png

    再次访问debian1
    44.png

    带上sessionid首次访问debian1
    45.png

    带上sessionid再次次访问debian1
    46.png

    可见的确是达到了均衡负载同时session共享的目的。

    总结
    这篇文章写下来可真是费了些力气,中间出了好多错,不过一个一个有耐心的解决掉,最后出来的结果还是令人挺有成就感的。毕竟心里的一块大石算是落了。以后有空再尝试一下其他几种方法。

    PS : 修改配置文件的时候,一定要先备份再修改,不然出了问题都不能恢复。

    帖子永久地址: 

    黑帽联盟 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
    2、本站所有主题由该帖子作者发表,该帖子作者与黑帽联盟享有帖子相关版权
    3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和黑帽联盟的同意
    4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
    5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
    6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
    7、黑帽联盟管理员和版主有权不事先通知发贴者而删除本文

    52

    主题

    2

    听众

    310

    积分

    黑帽学员

    Rank: 3Rank: 3

  • TA的每日心情
    奋斗
    2019-9-27 16:27
  • 签到天数: 258 天

    [LV.8]以坛为家I

    好深奥啊,唉
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 会员注册

    发布主题 !fastreply! 收藏帖子 返回列表 搜索
    回顶部