基于单个实例的redis实现分布式锁

版权声明:原创文章,未经允许不得转载. https://blog.csdn.net/shengqianfeng/article/details/87914803

需求背景:在常见的多机负载均衡架构中,如果每台实例中都需要对一份共享资源进行处理,常常会有能够保证同步读写的需求,这样共享资源就能够协同处理了,保证了高并发环境下共享数据的原子性和一致性。
在这里插入图片描述
实现:本例子中使用redis来实现分布式锁来解决这个问题。
参考官方文档:https://redis.io/topics/distlock

1 Safety property: Mutual exclusion. At any given moment, only one client can hold a lock.
2 Liveness property A: Deadlock free. Eventually it is always possible to acquire a lock, even if the client that locked a resource crashes or gets partitioned.
3 Liveness property B: Fault tolerance. As long as the majority of Redis nodes are up, clients are able to acquire and release locks.

翻译:

1 安全属性,互斥性,同一时刻,只能有一个客户端持有某锁
2 生存属性,无死锁,最终总是可以获得锁,即使锁定资源的客户机崩溃或分区。
3 生存属性,容错性,只要大多数Redis节点处于启动状态,客户机就能够获取和释放锁。
其实还有一个,解铃还须系铃人,能够解锁的只能是加锁的客户端。

上代码:

package com.yzx.rest.util;

import java.util.Collections;

import redis.clients.jedis.Jedis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 这种实现方式在单实例redis下是可靠的
 * @author jeffSheng
 * 2019年2月21日
 */
public class RedisTool {
	private final static Logger logger = LoggerFactory.getLogger(RedisTool.class);
	
	private static final Long RELEASE_SUCCESS = 1L;
	 
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
 
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    	try{
    		String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
    		if (LOCK_SUCCESS.equals(result)) {
    			return true;
    		}
    	}catch(Exception e){
    		e.printStackTrace();
    		logger.error("lockKey:{},reqId:{}尝试获取锁异常!",lockKey, requestId);
    	}
    	return false;
 
    }
    
    
    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        try{
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
    	}catch(Exception e){
    		e.printStackTrace();
    		logger.error("lockKey:{},reqId:{}释放锁异常!",lockKey, requestId);
    	}
        return false;
 
    }

}

解释:这种实现方式时基于单Redis来实现的,缺点是会存在单点故障,假如redis挂掉,整个redis分布式锁将失效。

如果采用一主多从即Master-Slave架构的话,也会存在问题。即便是主机挂掉,从会自动进行故障转移切换为主!

 **

## Why failover-based implementations are not enough

**
To understand what we want to improve, let’s analyze the current state of affairs with most Redis-based distributed lock libraries.

The simplest way to use Redis to lock a resource is to create a key in an instance. The key is usually created with a limited time to live, using the Redis expires feature, so that eventually it will get released (property 2 in our list). When the client needs to release the resource, it deletes the key.

Superficially this works well, but there is a problem: this is a single point of failure in our architecture. What happens if the Redis master goes down? Well, let’s add a slave! And use it if the master is unavailable. This is unfortunately not viable. By doing so we can’t implement our safety property of mutual exclusion, because Redis replication is asynchronous.

There is an obvious race condition with this model:

Client A acquires the lock in the master.
The master crashes before the write to the key is transmitted to the slave.
The slave gets promoted to master.
Client B acquires the lock to the same resource A already holds a lock for. SAFETY VIOLATION!
Sometimes it is perfectly fine that under special circumstances, like during a failure, multiple clients can hold the lock at the same time. If this is the case, you can use your replication based solution. Otherwise we suggest to implement the solution described in this document.

翻译:

为什么基于故障转移的实现是不足的?
为了了解我们需要改进什么,让我们分析大多数基于redis的分布式锁库的当前状态,使用Redis锁定资源的最简单方法是在Redis实例中创建Key,Key通常在创建时会有个过期时间,使用Redis expires特性。所以这个Key当客户端要释放锁时,最终会被释放掉,删掉这个Key。
表面上看这个没啥问题,但其实不是:这是我们体系结构中的一个单点故障。->如果Redis master崩溃了怎么办?首先想到就是给Master一个Slave,当主机不可用时启用Slave作为主。但是很遗憾,这并不可行。
因为通过这样做,我们无法实现互斥的安全属性,因为Redis复制是异步的。
该模型存在明显的竞态条件:
1 客户端A获取Master服务器重的锁。
2 在Key的写入被同步传输到从服务器之前,主服务器崩溃。
3 Slave提升为Master
4 客户端B获取对A已经持有的同一资源的锁。安全属性被违反!

所以,官方的文档中给出了一种叫做Redlock的算法。有兴趣可以了解下!https://redis.io/topics/distlock

猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/87914803