Redis Advanced — сценарии использования и применения строкового типа

Redis
Redis Advanced — сценарии использования и применения строкового типа

redis — использование строкового типа и сценарии приложений

В последнее время я изучаю Redis в Jingjin и учусь, пока пишу.

Как перед чтением, выработайте привычку

1. Описание использования памяти типа String

  • Строка Redis — это динамическая строка.
  • использовать预分配избыточное пространство减少объем памяти频繁分配
  • Пространство содержимого обычно больше, чем фактическая длина строки len
    • Когда длина строки меньше 1M, расширение должно удвоить существующее пространство.
    • Если он превышает 1 М, во время расширения за один раз будет расширяться только еще 1 М пространства.
  • Обратите внимание, что строка最大长度составляет 512М.

2. Общие команды типа String:

set

SET expire60 "will expire in a minute" EX 60 # 设置值60秒后过期

Множественное хранение и множественный доступ

local_redis:0> mset name1 boy name2 girl name3 unknown
"OK"

local_redis:0> mget name1 name2 name3
 1)  "boy"
 2)  "girl"
 3)  "unknown"

Истекший

setex name 5 codehole  # 5s 后过期,等价于 set+expire

Распределенная блокировка

setnx name codehole  # 如果 name 不存在就执行 set 创建

считать

У автоинкремента есть диапазон, и его диапазон — это максимальное и минимальное значения signed long, при превышении этого значения Redis сообщит об ошибке.

> set codehole 9223372036854775807  # Long.Max
OK

> incr codehole
(error) ERR increment or decrement would overflow

В-третьих, сцена общего типа String

1. Код подтверждения: когда вы входите в систему, регистрируетесь и получаете коды подтверждения на некоторых веб-сайтах, вы получите несколько кодов подтверждения и скажите, что срок их действия истекает через 10 минут.

set phone_num code ex 600

Используйте номер мобильного телефона в качестве ключа, проверочный код в качестве значения и таймаут 6 минут. Таким образом, когда вы вводите код подтверждения и отправляете его, фон может сначала получить phone_num, а затем сравнить ваш ввод со значением, хранящимся в базе данных, для завершения проверки личности.

2. Функция кэширования: String является наиболее часто используемым типом данных, не только Redis, но и самым основным типом на каждом языке, поэтому используйте Redis в качестве кэша, взаимодействуйте с другими базами данных в качестве уровня хранения и используйте Redis для поддержки высокие функции параллелизма.Это может значительно повысить скорость чтения и записи в системе и снизить нагрузку на серверную базу данных.

3. Счетчик: многие системы будут использовать Redis в качестве счетчика системы в реальном времени, который может быстро реализовать функции подсчета и запроса. И окончательный результат данных может быть сохранен в базе данных или другом носителе для постоянного хранения в соответствии с определенным временем.

4. Растровое изображение. Его можно широко использовать для статистики сцены, такой как регистрация, активность и врезка. Серый всегда прост в использовании

5, распределенный замок Убедитесь, что распределенный замок доступен, по крайней мере, мы хотим обеспечить замок одновременно соответствовать следующим четырем условиям:

  • Занятие ресурса: Взаимное исключение. В любой момент только один клиент может удерживать блокировку.
  • Жизненный цикл: взаимоблокировки не будет. Даже если клиент выйдет из строя, удерживая блокировку, и не разблокирует ее активно, другие клиенты могут быть гарантированно заблокированы в будущем.
  • Multi-redis: отказоустойчивый. Клиенты могут блокировать и разблокировать, пока большинство узлов Redis запущены и работают.
  • Один и тот же человек запирает и открывает: человек, который открывает звонок, также должен быть подключен к звонку. Блокировку и разблокировку должен выполнять один и тот же клиент. Клиент не может разблокировать блокировки, добавленные другими.

Конкретная реализация PHP:

class RedLock
{
    private $retryDelay;    // 重试间隔
    private $retryCount;    // 重试次数
    private $clockDriftFactor = 0.01;
    private $quorum;
    private $servers = array();
    private $instances = array();
    function __construct(array $servers, $retryDelay = 200, $retryCount = 3)
    {
        $this->servers = $servers;
        $this->retryDelay = $retryDelay;
        $this->retryCount = $retryCount;
        $this->quorum  = min(count($servers), (count($servers) / 2 + 1));
    }

    public function lock($resource, $ttl)
    {
        $this->initInstances();
        $token = uniqid();
        $retry = $this->retryCount;

        do {
            $n = 0;
            $startTime = microtime( true ) * 1000;
            foreach ($this->instances as $instance) {
                if ($this->lockInstance($instance, $resource, $token, $ttl)) {
                    $n++;
                }
            }

            // 将 2 毫秒添加到漂移中,以考虑 Redis 过期精度,即 1 毫秒,加上小型 TTL 的 1 毫秒最小漂移。
            $drift = ( $ttl * $this->clockDriftFactor ) + 2;
            $validityTime = $ttl - ( microtime( true ) * 1000 - $startTime ) - $drift;
            if ($n >= $this->quorum && $validityTime > 0) {
                return [
                    'validity' => $validityTime,
                    'resource' => $resource,
                    'token'    => $token,
                ];
            } else {
                foreach ( $this->instances as $instance ) {
                    $this->unlockInstance( $instance, $resource, $token );
                }
            }
            // Wait a random delay before to retry
            $delay = mt_rand( floor( $this->retryDelay / 2 ), $this->retryDelay );
            usleep( $delay * 1000 );
            $retry--;
        } while ($retry > 0);
        return false;
    }

    public function unlock(array $lock)
    {
        $this->initInstances();
        $resource = $lock['resource'];
        $token    = $lock['token'];
        foreach ($this->instances as $instance) {
            $this->unlockInstance($instance, $resource, $token);
        }
    }

    private function initInstances()
    {
        if (empty($this->instances)) {
            foreach ($this->servers as $server) {
                list($host, $port, $timeout) = $server;
                $redis = new \Redis();
                $redis->connect($host, $port, $timeout);
                $this->instances[] = $redis;
            }
        }
    }

    private function lockInstance($instance, $resource, $token, $ttl)
    {
        return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
    }

    private function unlockInstance($instance, $resource, $token)
    {
        // 不但实现了 同一人加锁解锁;而且如果解锁不成功就回滚能够保证 资源的占用
        $script = '
            if redis.call("GET", KEYS[1]) == ARGV[1] then
                return redis.call("DEL", KEYS[1])
            else
                return 0
            end
        ';
        return $instance->eval($script, [$resource, $token], 1);
    }
}

$servers = [
    ['127.0.0.1', 6379, 0.01],
];
$redLock = new RedLock($servers);
$i2Count = 0;echo '<pre>';

while ( $i2Count < 10 ) {
    $lock = $redLock->lock('test', 10000);
    if ($lock) {
        print_r($lock);
        $ret2Unlock = $redLock->unlock( $lock );

        // !$ret2Unlock 则回滚所有操作
    } else {
        print "Lock not acquired\n";
    }

    $i2Count++;
}

Четвертое и, наконец,

Из-за нехватки места эта статья оставляет проблему

  • Идеи реализации распределенной блокировки, обсуждение решения
  • Волшебное использование растрового изображения

Дополнение сегодня

Справочное чтение:

  • «Redis Deep Adventure» — старые деньги
  • «Проектирование Redis 5 и анализ исходного кода» — под редакцией Чена Лэя и других.
  • «Битва Redis» — [США] Джозайя Л. Карлсон