Class: Redlock

Inherits:
Object
  • Object
show all
Defined in:
lib/redlock.rb,
lib/redlock/version.rb

Constant Summary collapse

DEFAULT_RETRY_COUNT =
3
DEFAULT_RETRY_DELAY =
200
CLOCK_DRIFT_FACTOR =
0.01
UNLOCK_SCRIPT =
'
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end'
VERSION =
'0.0.2'

Instance Method Summary collapse

Constructor Details

#initialize(*server_urls) ⇒ Redlock

Returns a new instance of Redlock.



15
16
17
18
19
20
21
22
23
# File 'lib/redlock.rb', line 15

def initialize(*server_urls)
  @servers = []
  server_urls.each{|url|
    @servers << Redis.new(:url => url)
  }
  @quorum = server_urls.length / 2 + 1
  @retry_count = DEFAULT_RETRY_COUNT
  @retry_delay = DEFAULT_RETRY_DELAY
end

Instance Method Details

#get_unique_lock_idObject



52
53
54
55
56
57
58
59
# File 'lib/redlock.rb', line 52

def get_unique_lock_id
  val = ""
  bytes = SecureRandom.random_bytes(20)
  bytes.each_byte{|b|
    val << b.to_s(32)
  }
  val 
end

#load_scriptObject



25
26
27
28
29
# File 'lib/redlock.rb', line 25

def load_script
  @servers.each do |server|
    @unlock_sha = server.script(:load, UNLOCK_SCRIPT)
  end
end

#lock(resource, ttl, val = nil) ⇒ Object



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
# File 'lib/redlock.rb', line 61

def lock(resource,ttl,val=nil)
  val = get_unique_lock_id if val.nil?
  @retry_count.times {
    n = 0
    start_time = (Time.now.to_f*1000).to_i
    @servers.each{|s|
      n += 1 if lock_instance(s,resource,val,ttl)
    }
    # Add 2 milliseconds to the drift to account for Redis expires
    # precision, which is 1 milliescond, plus 1 millisecond min drift 
    # for small TTLs.
    drift = (ttl*CLOCK_DRIFT_FACTOR).to_i + 2
    validity_time = ttl-((Time.now.to_f*1000).to_i - start_time)-drift 
    if n >= @quorum && validity_time > 0
      return {
        :validity => validity_time,
        :resource => resource,
        :val => val
      }
    else
      @servers.each{|s|
        unlock_instance(s,resource,val)
      }
    end
    # Wait a random delay before to retry
    sleep(rand(@retry_delay).to_f/1000)
  }
  return false
end

#lock_instance(redis, resource, val, ttl) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/redlock.rb', line 36

def lock_instance(redis,resource,val,ttl)
  begin
    return redis.set(resource, val, nx: true, px: ttl)
  rescue
    return false
  end
end

#set_retry(count, delay) ⇒ Object



31
32
33
34
# File 'lib/redlock.rb', line 31

def set_retry(count,delay)
  @retry_count = count
  @retry_delay = delay
end

#unlock(lock) ⇒ Object



91
92
93
94
95
# File 'lib/redlock.rb', line 91

def unlock(lock)
  @servers.each{|s|
    unlock_instance(s,lock[:resource],lock[:val])
  }
end

#unlock_instance(redis, resource, val) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/redlock.rb', line 44

def unlock_instance(redis,resource,val)
  begin
    redis.evalsha(@unlock_sha, keys: [resource], argv: [val])
  rescue
    # Nothing to do, unlocking is just a best-effort attempt.
  end
end