개발 꿀팁/PHP

php redis 잠금을 사용하여 동시 액세스 제한

Jammie 2022. 8. 17. 12:22
반응형

1. 동시접속 제한 문제
어떤 것에 대해서는 같은 것을 제한해야 한다.사용자가 동시 접속하는 장면. 사용자가 여러 번 동시 접속 요청을 하고 서버 처리에 잠금 제한이 없으면 사용자는 여러 번 성공을 요청할 수 있습니다.

예를 들어 교환쿠폰, 사용시동일한 시간에 교환번호를 발급받아 제출하며, 잠금장치가 없는 경우에는 동일한 교환번호를 사용하여 여러 장의 쿠폰으로 동시에 교환할 수 있습니다.

더미 코드는 다음과 같습니다

if A (교환 가능)
B(교환 실행)
C(변경됨으로 업데이트됨수령)
D(끝)

만약 사용자가 교환번호를 발급받아 제출한 경우 교환(A)이 가능하다고 판단됩니다. 교환(B)을 수행한 후에 교환(C)으로 갱신됩니다.따라서 사용자가 갱신하기 전에 얼마나 많은 요청을 수행하느냐에 따라 이 요청을 성공적으로 실행할 수 있습니다.


2. 동시접속 제한방법
파일 잠금을 사용하면 동시 접속 제한이 가능하지만 분산된 아키텍처의 환경에서는 여러 서버의 동시 접속 제한을 보장할 수 없습니다.

레디스는 오픈소스로 ANSIC 언어로 작성, 네트워크 지원, 메모리 기반이나 영구화가 가능한 로그형, Key-Value 데이터베이스로 다양한 언어로 API를 제공한다.
본 명세서는 그 setnx 방식을 이용하여 분산 잠금 기능을 구현하고자 한다.setnx는 Set it NoteXists이다.
키 값이 존재하지 않을 때 삽입 성공 (잠금 성공) 키 값이 이미 있으면 삽입 실패 (잠금 실패)

RedisLock.class.php

<?php
/**
 *  Redis 잠금 작업 클래스
 *  Date:   2016-06-30
 *  Author: fdipzone
 *  Ver:    1.0
 *
 *  Func:
 *  public  lock    잠금 가져오기
 *  public  unlock  잠금 해제
 *  private connect 연결해
 */
class RedisLock { // class start

    private $_config;
    private $_redis;

    /**
     * 초기화
     * @param Array $config redis연결 설정
     */
    public function __construct($config=array()){
        $this->_config = $config;
        $this->_redis = $this->connect();
    }

    /**
     * 잠금 가져오기
     * @param  String  $key    자물쇠표지
     * @param  Int     $expire 만료 시간 잠금
     * @return Boolean
     */
    public function lock($key, $expire=5){
        $is_lock = $this->_redis->setnx($key, time()+$expire);

        // 잠금을 가져올 수 없음
        if(!$is_lock){

            // 자물쇠가 기한이 지났는지 아닌지를 판단하다
            $lock_time = $this->_redis->get($key);

            // 자물쇠가 만료되었습니다. 자물쇠를 지우기
            if(time()>$lock_time){
                $this->unlock($key);
                $is_lock = $this->_redis->setnx($key, time()+$expire);
            }
        }

        return $is_lock? true : false;
    }

    /**
     * 잠금 해제
     * @param  String  $key 자물쇠표지
     * @return Boolean
     */
    public function unlock($key){
        return $this->_redis->del($key);
    }

    /**
     * redis 연결 만들기
     * @return Link
     */
    private function connect(){
        try{
            $redis = new Redis();
            $redis->connect($this->_config['host'],$this->_config['port'],$this->_config['timeout'],$this->_config['reserved'],$this->_config['retry_interval']);
            if(empty($this->_config['auth'])){
                $redis->auth($this->_config['auth']);
            }
            $redis->select($this->_config['index']);
        }catch(RedisException $e){
            throw new Exception($e->getMessage());
            return false;
        }
        return $redis;
    }

} // class end

?>

demo.php

<?php
require 'RedisLock.class.php';

$config = array(
    'host' => 'localhost',
    'port' => 6379,
    'index' => 0,
    'auth' => '',
    'timeout' => 1,
    'reserved' => NULL,
    'retry_interval' => 100,
);

// redislock 개체 만들기
$oRedisLock = new RedisLock($config);

// 잠금 ID 정의
$key = 'mylock';

// 잠금 가져오기
$is_lock = $oRedisLock->lock($key, 10);

if($is_lock){
    echo 'get lock success<br>';
    echo 'do sth..<br>';
    sleep(5);
    echo 'success<br>';
    $oRedisLock->unlock($key);

// 잠금 가져오기 실패
}else{
    echo 'request too frequently<br>';
}

?>

테스트 방법:
두 개의 서로 다른 브라우저를 열고 동시에 A, B에서 demo.php에 액세스합니다.
만약 먼저 접속했다면 자물쇠가 주어라
출력
get lock success
도스..
success

다른 잠금 장치가 실패하면 request too frequently 출력

동일한 시간에 하나의 접속만 유효하고 동시 접속을 효과적으로 제한한다.


시스템 오류로 인한 교착 상태를 방지하려면, 잠금 장치를 가져올 때 만료 시간을 늘립니다.유통기한이 지났습니다. 자물쇠라도상태를 정하면 자물쇠가 풀려 교착상태로 인한 문제를 피할 수 있다

반응형