分布式应用进行逻辑处理时经常会遇到并发问题。
比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状态这两个操作不是原子的。(Wiki 解释:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。)
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
安全和可靠性保证
互斥性
保证同一时间只有一个客户端可以拿到锁,也就是可以对共享资源进行操作
安全性
只有加锁的服务才能有解锁权限,也就是不能让a加的锁,bcd都可以解锁,如果都能解锁那分布式锁就没啥意义了
可能出现的情况就是a去查询发现持有锁,就在准备解锁,这时候忽然a持有的锁过期了,然后b去获得锁,因为a锁过期,b拿到锁,这时候a继续执行第二步进行解锁如果不加校验,就将b持有的锁就给删除了
避免死锁
出现死锁就会导致后续的任何服务都拿不到锁,不能再对共享资源进行任何操作了
保证加锁与解锁操作是原子性操作
这个其实属于是实现分布式锁的问题,假设a用redis实现分布式锁
假设加锁操作,操作步骤分为两步:
1,设置key set(key,value)2,给key设置过期时间
假设现在a刚实现set后,程序崩了就导致了没给key设置过期时间就导致key一直存在就发生了死锁
代码实现
使用redis命令 set key value NX EX max-lock-time 实现加锁
使用redis命令 EVAL 实现解锁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#java代码
public class RedisDistributeLock {
private Jedis jedis;
private static final String LOCK_SUCCESS = "OK";
private static final Long UNLOCK_SUCCESS = 1L;
public Boolean lock(String key,String value,Long timeout){
String result = jedis.set(key,value,"NX","EX",timeout);
if(LOCK_SUCCESS.equals(result)){
return true;
}
return false;
}
public Boolean unlock(String key,String value){
String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
Object result = jedis.eval(luaScript, Collections.singletonList(key),Collections.singletonList(value));
if (UNLOCK_SUCCESS == result){
return true;
}
return false;
}
public Boolean lockRetry(String key,String value,Long timeout,Integer retryTime,Long sleepTime){
Boolean flag = false;
try{
for(int i=0;i<retryTime;i++){
flag = lock(key,value,timeout);
if(flag){
break;
}
Thread.sleep(sleepTime);
}
}catch(Exception e){
e.printStackTrace();
}
return flag;
}
}
go版本代码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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90package main
import (
"github.com/garyburd/redigo/redis"
"crypto/rand"
"encoding/base64"
"errors"
"time"
"fmt"
)
type RedisLock struct {
lockKey string
value string
}
var Redispool *redis.Pool
var delScript = redis.NewScript(1, `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end`)
func init() {
Redispool = &redis.Pool{
MaxIdle: 10,
IdleTimeout: 300 * time.Second,
Dial : func() (redis.Conn, error) {
tcp := fmt.Sprintf("%s:%s","127.0.0.1","6379")
c,err := redis.Dial("tcp",tcp)
if err != nil{
return nil,err
}
return c,err
},
TestOnBorrow : func(c redis.Conn, t time.Time) error {
_,err := c.Do("PING")
return err
},
}
}
func(this *RedisLock) Lock(rd *redis.Conn,timeout int)error{
{
b := make([]byte,16)
_,err := rand.Read(b)
if err !=nil {
return err
}
this.value = base64.StdEncoding.EncodeToString(b)
lockReply,err := (*rd).Do("SET",this.lockKey,this.value,"ex",timeout,"nx")
if err !=nil{
return errors.New("redis fail")
}
if lockReply == "OK"{
return nil
}else{
return errors.New("lock fail")
}
}
}
func(this *RedisLock)Unlock(rd *redis.Conn){
delScript.Do(*rd,this.lockKey,this.value)
}
func main() {
rd := Redispool.Get()
defer rd.Close()
var channel chan bool
go func() {
lock1 :=RedisLock{"lockKey","**1**"}
err := lock1.Lock(&rd,5)
time.Sleep(2*time.Second)
lock2 := RedisLock{"lockKey","**2**"}
err1 := lock2.Lock(&rd,5)
fmt.Println(err,err1)
lock1.Unlock(&rd)
channel <- true
}()
if <-channel {
fmt.Println("结束")
}
}