• php 实现redis分布式锁


    前言

    分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。

    可靠性

    首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

    1. 互斥性。在任意时刻,只有一个客户端能持有锁。
    2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了

    代码实现

    <?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++;
                    }
                }
                # Add 2 milliseconds to the drift to account for Redis expires
                # precision, which is 1 millisecond, plus 1 millisecond min drift
                # for small TTLs.
                $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);
        }
    }

    使用示例

    <?php
    require_once __DIR__ . '/../src/RedLock.php';
    $servers = [
        ['127.0.0.1', 6379, 0.01],
        ['127.0.0.1', 6389, 0.01],
        ['127.0.0.1', 6399, 0.01],
    ];
    $redLock = new RedLock($servers);
    while (true) {
        $lock = $redLock->lock('test', 10000);
        if ($lock) {
            print_r($lock);
        } else {
            print "Lock not acquired
    ";
        }
    }

    参考文档

    https://www.cnblogs.com/linjiqin/p/8003838.html

    https://github.com/ronnylt/redlock-php

  • 相关阅读:
    linux磁盘挂载
    3个方法解决百度网盘限速 (2018-07-20)
    mysql状态分析之show global status
    Cgroups子系统介绍
    Go语言 关键字:defer
    Go语言 map的实现
    Go语言 基本类型
    MySQL 监控指标
    sshpass的使用方法
    C++11 std::ref使用场景
  • 原文地址:https://www.cnblogs.com/syhx/p/9753433.html
Copyright © 2020-2023  润新知