分布式锁

image

什么是分布式

将系统差分成不同的服务然后将这些服务放在不同服务器减轻单台服务的压力,提高性能和并发量。

并发并行?

concurrency
parallel

顺序执行:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。    
并行:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。    
并发:你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。    
并发强调的是一起出发(交替执行),并行强调的是一起执行。    
并发的反义是顺序,并行的反义是串行。
并发并行并不是互斥概念,只不过并发强调任务的抽象调度,并行强调任务的实际执行。    

分布式=高并发=多线程?

分布式:是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段。    
        该领域需要解决的问题极多,在不同技术层面上,包括:分布式文件系统、缓存、数据库、计算等。   
        水平拓展:当一台机器扛不住流量时,通过添加机器的方式,将流量均分。    
        垂直拆分:前端有多种需求时,一台机器扛不住,可以将不同需求分发到不同机器上。    
高并发:反应的是 [同时有多少量],例如直播服务,同时可能有上万人观看。    
        高并发可以用分布式技术去解决,将并发流量分到不同物理机器上。    
        例如使用缓存技术和前端将静态资源放在CDN等;还可以用多线程技术将一台服务器的能力最大化。    
多线程:是指从软件或硬件上实现多个线程并发执行的技术。    
        多线程聚焦于如何使用编程语言将CPU能力最大化。    

什么是锁

为了实现多个线程在同一时刻同一代码块只能有一个线程可执行,需要在某个地方做标记,这个标记必须满足所有的线程可见,标记不存在的时候设置标记,后续的线程发现已标记则等待拥有标记的线程结束,同步代码块取消标记后,在尝试设置标记,这个标记可以理解为锁。

什么是分布式锁

单体单机部署的系统被演化成分布式集群系统后,系统可能会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程之间共享的资源。
指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问。

分布式的 CAP 理论:

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。

分布式锁要满足哪些要求呢

  • 互斥性:在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 容错性:获取或释放锁的机制必须高可用且性能佳
  • 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

分布式锁实现方式

为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;

基于数据库

思路:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

基于Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.util.Collections;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class XttblogLock {
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";
private static final Long RELEASE_SUCCESS = 1L;
private static void validParam(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {
if (null == jedisPool) {
throw new IllegalArgumentException("jedisPool obj is null");
}
if (null == lockKey || "".equals(lockKey)) {
throw new IllegalArgumentException("lock key is blank");
}
if (null == requestId || "".equals(requestId)) {
throw new IllegalArgumentException("requestId is blank");
}
if (expireTime < 0) {
throw new IllegalArgumentException("expireTime is not allowed less zero");
}
}

public static boolean tryLock(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {
validParam(jedisPool, lockKey, requestId, expireTime);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
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) {
throw e;
} finally {
if (null != jedis) {
jedis.close();
}
}
return false;
}

public static boolean unLock(JedisPool jedisPool, String lockKey, String requestId) {
validParam(jedisPool, lockKey, requestId, 1);
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Object result = jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
} catch (Exception e) {
throw e;
} finally {
if (null != jedis) {
jedis.close();
}
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.concurrent.TimeUnit;
import org.redisson.Redisson;
import org.redisson.core.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RedissonLockUtil {
private static final Logger logger = LoggerFactory.getLogger(RedissonLockUtil.class);

private static Redisson redisson = RedissonManager.getRedisson();

private static final String LOCK_FLAG = "recruitlock_";

/**
* 根据name对进行上锁操作,redissonLock 阻塞的,采用的机制发布/订阅
* @param key
*/
public static void lock(String key){
String lockKey = LOCK_FLAG + key;
RLock lock = redisson.getLock(lockKey);
//lock提供带timeout参数,timeout结束强制解锁,防止死锁 :1分钟
lock.lock(1, TimeUnit.MINUTES);
logger.info("lock key:{}",lockKey);
}

/**
* 根据name对进行解锁操作
* @param key
*/
public static void unlock(String key){
String lockKey = LOCK_FLAG + key;
RLock lock = redisson.getLock(lockKey);
if (lock.isHeldByCurrentThread())
{
lock.unlock();
logger.info("unlock , key:{}"+lockKey);
}
}

/**
* @param key
* @param millisToWait 等待获取锁的时间--单位:秒
*/
public static boolean tryLock(String key, long millisToWait) {
String lockKey = LOCK_FLAG + key;
logger.info("get redis lock start , key:{}"+lockKey);
RLock lock = redisson.getLock(lockKey);
logger.info("get redis lock end , key:"+lockKey);
try {
return lock.tryLock(millisToWait,5000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
logger.error("try lock error,key is:{}", lockKey, e);
}
logger.info("get redis lock false , key:"+lockKey);
return false;
}
}

错误示例

1
2
3
4
5
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
jedis.expire(lockKey, expireTime);
}

https://www.cnblogs.com/seesun2012/p/9214653.html

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

https://www.xttblog.com/?p=3171

https://juejin.im/post/5c01532ef265da61362232ed

image

-------------本文结束感谢您的阅读-------------
0%