1 /* 2 * Hunt - A redis client library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.redis.RedisLock; 13 14 import std.array; 15 import std.string; 16 import std.random; 17 import std.conv; 18 import std.stdio; 19 import std.uuid; 20 21 import core.time; 22 import core.thread; 23 import core.stdc.stdlib; 24 25 import hunt.logging.ConsoleLogger; 26 import hunt.redis.Redis; 27 import hunt.redis.params.SetParams; 28 29 30 struct LockedObject { 31 string key; 32 string uniqueId; 33 size_t validTime; 34 } 35 36 /** 37 https://redis.io/topics/distlock 38 https://github.com/abelaska/jedis-lock 39 */ 40 class RedisLock { 41 42 private Redis[] _redisClients; 43 private LockedObject _lock; 44 private string _lockKey; 45 46 47 this(Redis client, string lockKey, int delaytime = 10, float clockFactor = 0.01f) { 48 this([client], lockKey, delaytime, clockFactor); 49 } 50 51 this(Redis[] client, string lockKey, int delaytime = 10, float clockFactor = 0.01f) { 52 _redisClients = client; 53 _lockKey = lockKey; 54 55 _quornum = _redisClients.length / 2 + 1; 56 _delaytime = delaytime; 57 _clockFactor = clockFactor; 58 } 59 60 bool lock(uint timeout = uint.max, uint ttl = 60000) { 61 string key = _lockKey; 62 string val = to!string(randomUUID()); 63 auto end_tick = nsecsToTicks(cast(long) timeout * 1000 * 1000) + MonoTime.currTime.ticks(); 64 synchronized (this) { 65 _lock.key = key; 66 _lock.uniqueId = val; 67 68 do { 69 size_t n = 0; 70 auto t1 = MonoTime.currTime.ticks(); 71 foreach (c; _redisClients) { 72 if (lockInstance(c, key, val, ttl)) 73 ++n; 74 } 75 76 auto t2 = MonoTime.currTime.ticks(); 77 auto clockdrat = cast(size_t)(_clockFactor * ttl) + 2; 78 auto validtime = ttl - ticksToNSecs(t2 - t1) / 1000 - clockdrat; 79 80 version(HUNT_REDIS_DEBUG) { 81 tracef("validtime=%d, n=%d, _quornum=%d", validtime, n, _quornum); 82 } 83 84 85 if (validtime > 0 && n >= _quornum) { 86 _lock.validTime = cast(size_t)validtime; 87 return true; 88 } else { 89 unlock(); 90 } 91 size_t delay = rand() % _delaytime + _delaytime / 2; 92 Thread.sleep(dur!"msecs"(delay)); 93 } while (MonoTime.currTime.ticks() < end_tick); 94 95 return false; 96 } 97 98 } 99 100 void unlock() { 101 synchronized (this) { 102 foreach (c; _redisClients) { 103 unlockInstance(c, _lock.key, _lock.uniqueId); 104 } 105 } 106 } 107 108 private static bool lockInstance(Redis redis, string key, string value, long ttl) { 109 try { 110 SetParams para = new SetParams(); 111 string r = redis.set(_prefix ~ key, value, para.nx().px(ttl)); 112 return r == "OK"; 113 } catch (Throwable e) { 114 warning(e); 115 return false; 116 } 117 } 118 119 private static void unlockInstance(Redis redis, string key, string value) { 120 try { 121 Object r = redis.eval(`if redis.call('get', KEYS[1]) == ARGV[1] 122 then return redis.call('del', KEYS[1]) 123 else 124 return 0 125 end`, 126 [_prefix ~ key], [value]); 127 } catch (Throwable e) { 128 warning(e); 129 } 130 131 } 132 133 134 immutable size_t _quornum; 135 immutable size_t _delaytime = 10; 136 immutable float _clockFactor = 0.01; 137 138 static immutable string _prefix = "hunt_redlock_"; 139 } 140