Балансировка нагрузки с использованием совместного сеанса Redis

Redis задняя часть сервер балансировки нагрузки

В последнее время я изучал веб-архитектуру, включая разделение чтения и записи базы данных, кэш и очередь Redis, кластеризацию и балансировку нагрузки (LVS).Сегодня я впервые узнаю о проблемах, с которыми я столкнулся при балансировке нагрузки, т. , совместный доступ к сеансу. проблема.

1. Балансировка нагрузки

балансировки нагрузки: Делитесь большим количеством посещений других серверов, чтобы уменьшить нагрузку на каждый сервер.

Популярное объяснение: передача задачи разработчику всегда будет иметь верхний предел вычислительной мощности. В настоящее время вы можете рассмотреть возможность добавления разработчиков для совместной обработки этой задачи. Когда несколько человек работают с одной и той же задачей, проблемы с планированием будут то есть распределение задач, что согласуется с концепцией многопоточности. Роль nginx здесь эквивалентна диспетчеру задач.

как наш первый визитwww.baidu.comЭто доменное имя может соответствовать этому IP111.13.101.208сервер, то при втором посещении IP может измениться на111.13.101.209Это использование Baidu для балансировки нагрузки, одно доменное имя соответствует нескольким серверам, а трафик распределяется между другими серверами, что значительно снижает трафик на каждом сервере.

Однако здесь есть проблема.Если мы войдем в учетную запись Baidu, например, сетевой диск Baidu на веб-странице, но каждый раз можно запросить другой сервер, мы знаем, что каждый сервер будет иметь свою собственную сессию. , поэтому это приведет к тому, что пользователю придется снова входить в систему каждый раз, когда он обновляет веб-страницу, что является очень плохим опытом.Поэтому, в соответствии с вышеуказанными проблемами, мы надеемся, что сеансы могут быть разделены, что может решить проблему одно и то же доменное имя и разные серверы, соответствующие разным сеансам балансировки нагрузки.

2. Введение в Redis

В настоящее время общий сеанс нескольких серверов в основном используется Redis.

Для оснований Redis вы можете увидеть мой предыдущий пост блогаRedis развитие обучения.

Если кратко разобраться:

  1. Redis — это система хранения данных типа «ключ-значение», представляющая собой нереляционную базу данных.
  2. Особенности: Поддержка сохранения данных, позволяющая сохранять данные в памяти на диск (memcached: данные хранятся в памяти, при перезапуске службы данные будут потеряны)
  3. Поддержка 5 типов данных: строка, хеш, список, набор, zset
  4. Два формата файлов (т.е. постоянство данных)
    (1) RDB (общий объем данных): время/частота, данные кисти в памяти на диск, чтобы облегчить следующую загрузку файла чтения. (2) AOF (добавочный запрос): аналогично двоичному журналу mysql, сохранять операторы изменений в записях базы данных в журнале, следующий сервис перезапуска, основанный на данных двоичного журнала, перезаписанных один раз и загруженных в память, сохранение данных
  5. место хранения
    (1) Хранилище памяти (2) Хранилище на диске (RDB) (3) Файл журнала (AOF)

3. Основная идея реализации

В первую очередь необходимо уточнить разницу между сессией и куки. Браузер сохраняет файл cookie.Каждый раз, когда браузер отправляет запрос на сервер, заголовок HTTP автоматически добавляет информацию о вашем файле cookie. Сервер использует cookie пользователя в качестве ключа для поиска соответствующего значения (сессии) в хранилище.

Веб-сайты под одним и тем же доменным именем имеют одинаковые файлы cookie. Таким образом, сколько бы серверов ни было, на какой бы сервер ни был назначен запрос, куки одного и того же пользователя остаются неизменными. То есть сеанс, соответствующий куки, также уникален.

Поэтому достаточно обеспечить доступ нескольких бизнес-серверов к одному и тому же серверу Redis (или кластеру).

В-четвертых, конфигурация сеанса сеанса PHP изменяется на Redis

Мы видим, что конфигурация сеанса PHP по умолчанию сохраняется во временной директории сервера в виде файла.Для сохранения сеанса нам нужен Redis в качестве драйвера.Поэтому файл конфигурации необходимо изменить здесь, а Механизм пользовательского сеанса PHP изменен на Redis.

Здесь есть три модификации:

1. Измените файл конфигурации php.ini

найти файл конфигурацииphp.ini, измените его на следующее, сохраните и перезапустите службу

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"

2. Динамическая модификация конфигурации в коде

Direct Добавьте следующий код:

ini_set("session.save_handler", "redis");
ini_set("session.save_path", "tcp://127.0.0.1:6379");

Примечание. Если в конфигурационном файле redis.conf установлен пароль на подключение requirepass, то save_path необходимо записать как tcp://127.0.0.1:6379?auth=authpwd, иначе при сохранении сессии будет выдано сообщение об ошибке.

контрольная работа:

<?php
//ini_set("session.save_handler", "redis");
//ini_set("session.save_path", "tcp://127.0.0.1:6379");

session_start();

//存入session
$_SESSION['class'] = array('name' => 'toefl', 'num' => 8);

//连接redis
$redis = new redis();
$redis->connect('127.0.0.1', 6379);

//检查session_id
echo 'session_id:' . session_id() . '<br/>';

//redis存入的session(redis用session_id作为key,以string的形式存储)
echo 'redis_session:' . $redis->get('PHPREDIS_SESSION:' . session_id()) . '<br/>';

//php获取session值
echo 'php_session:' . json_encode($_SESSION['class']);

3. Механизм пользовательского сеанса

использоватьsession_set_save_handleМетод настраивает механизм сеанса, и мы нашли в Интернете очень хорошо инкапсулированный класс, который мы можем напрямую использовать для реализации наших общих операций сеанса.

<?php
class redisSession{
    /**
     * 保存session的数据库表的信息
     */
    private $_options = array(
        'handler' => null, //数据库连接句柄
        'host' => null,
        'port' => null,
        'lifeTime' => null,
        'prefix'   => 'PHPREDIS_SESSION:'
    );

    /**
     * 构造函数
     * @param $options 设置信息数组
     */
    public function __construct($options=array()){
        if(!class_exists("redis", false)){
            die("必须安装redis扩展");
        }
        if(!isset($options['lifeTime']) || $options['lifeTime'] <= 0){
            $options['lifeTime'] = ini_get('session.gc_maxlifetime');
        }
        $this->_options = array_merge($this->_options, $options);
    }

    /**
     * 开始使用该驱动的session
     */
    public function begin(){
        if($this->_options['host'] === null ||
           $this->_options['port'] === null ||
           $this->_options['lifeTime'] === null
        ){
            return false;
        }
        //设置session处理函数
        session_set_save_handler(
            array($this, 'open'),
            array($this, 'close'),
            array($this, 'read'),
            array($this, 'write'),
            array($this, 'destory'),
            array($this, 'gc')
        );
    }
    /**
     * 自动开始回话或者session_start()开始回话后第一个调用的函数
     * 类似于构造函数的作用
     * @param $savePath 默认的保存路径
     * @param $sessionName 默认的参数名,PHPSESSID
     */
    public function open($savePath, $sessionName){
        if(is_resource($this->_options['handler'])) return true;
        //连接redis
        $redisHandle = new Redis();
        $redisHandle->connect($this->_options['host'], $this->_options['port']);
        if(!$redisHandle){
            return false;
        }

        $this->_options['handler'] = $redisHandle;
//        $this->gc(null);
        return true;

    }

    /**
     * 类似于析构函数,在write之后调用或者session_write_close()函数之后调用
     */
    public function close(){
        return $this->_options['handler']->close();
    }

    /**
     * 读取session信息
     * @param $sessionId 通过该Id唯一确定对应的session数据
     * @return session信息/空串
     */
    public function read($sessionId){
        $sessionId = $this->_options['prefix'].$sessionId; 
        return $this->_options['handler']->get($sessionId);
    }

    /**
     * 写入或者修改session数据
     * @param $sessionId 要写入数据的session对应的id
     * @param $sessionData 要写入的数据,已经序列化过了
     */
    public function write($sessionId, $sessionData){
        $sessionId = $this->_options['prefix'].$sessionId; 
        return $this->_options['handler']->setex($sessionId, $this->_options['lifeTime'], $sessionData);
    }

    /**
     * 主动销毁session会话
     * @param $sessionId 要销毁的会话的唯一id
     */
    public function destory($sessionId){
        $sessionId = $this->_options['prefix'].$sessionId; 
//        $array = $this->print_stack_trace();
//        log::write($array);
        return $this->_options['handler']->delete($sessionId) >= 1 ? true : false;
    }

    /**
     * 清理绘画中的过期数据
     * @param 有效期
     */
    public function gc($lifeTime){
        //获取所有sessionid,让过期的释放掉
        //$this->_options['handler']->keys("*");
        return true;
    }
    //打印堆栈信息
    public function print_stack_trace()
    {
        $array = debug_backtrace ();
        //截取用户信息
        $var = $this->read(session_id());
        $s = strpos($var, "index_dk_user|");
        $e = strpos($var, "}authId|");
        $user = substr($var,$s+14,$e-13);
        $user = unserialize($user);
        //print_r($array);//信息很齐全
        unset ( $array [0] );
        if(!empty($user)){
          $traceInfo = $user['id'].'|'.$user['user_name'].'|'.$user['user_phone'].'|'.$user['presona_name'].'++++++++++++++++\n';
        }else{
          $traceInfo = '++++++++++++++++\n';
        }
        $time = date ( "y-m-d H:i:m" );
        foreach ( $array as $t ) {
            $traceInfo .= '[' . $time . '] ' . $t ['file'] . ' (' . $t ['line'] . ') ';
            $traceInfo .= $t ['class'] . $t ['type'] . $t ['function'] . '(';
            $traceInfo .= implode ( ', ', $t ['args'] );
            $traceInfo .= ")\n";
        }
        $traceInfo .= '++++++++++++++++';
        return $traceInfo;
    }

}

Вызовите вышеуказанный класс при входе вашего проекта: вышеуказанный метод эквивалентен переписать метод приема сеанса в файл и написание данных для Redis.

файл инициализацииinit.php

<?php
require_once("redisSession.php");
$handler = new redisSession(array(
                'host' => "127.0.0.1",
                'port' => "6379"
        ));
$handler->begin();

// 这也是必须的,打开session,必须在session_set_save_handler后面执行
session_start();

тест test.php

<?php
// 引入初始化文件
include("init.php");
$_SESSION['isex'] = "Hello";  
$_SESSION['sex']  = "Corwien";

// 打印文件
print_r($_SESSION);
// ( [sex] => Corwien [isex] => Hello )

Используйте команду в клиенте Redis, чтобы проверить, существуют ли наши данные:

27.0.0.1:6379> keys *
 1) "first_key"
 2) "mylist"
 3) "language"
 4) "mytest"
 5) "pragmmer"
 6) "good"
 7) "PHPREDIS_SESSION:29a111bcs120sv48ibmmjqdag4"
 8) "user:1"
 9) "counter:__rand_int__"
10) "key:__rand_int__"
11) "tutorial-list"
12) "id:1"
13) "name"
127.0.0.1:6379> get PHPREDIS_SESSION:29a111bcs120sv48ibmmjqdag4
"sex|s:7:\"Corwien\";isex|s:5:\"Hello\";"
127.0.0.1:6379>

Мы видим, что наши данные сохраняются на стороне Redis, и ключ:PHPREDIS_SESSION:29a111bcs120sv48ibmmjqdag4.