|
|
|
@ -32,6 +32,22 @@ namespace OC\Memcache; |
|
|
|
use OCP\IMemcacheTTL; |
|
|
|
|
|
|
|
class Redis extends Cache implements IMemcacheTTL { |
|
|
|
/** name => [script, sha1] */ |
|
|
|
public const LUA_SCRIPTS = [ |
|
|
|
'dec' => [ |
|
|
|
'if redis.call("exists", KEYS[1]) == 1 then return redis.call("decrby", KEYS[1], ARGV[1]) else return "NEX" end', |
|
|
|
'720b40cb66cef1579f2ef16ec69b3da8c85510e9', |
|
|
|
], |
|
|
|
'cas' => [ |
|
|
|
'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("set", KEYS[1], ARGV[2]) return 1 else return 0 end', |
|
|
|
'94eac401502554c02b811e3199baddde62d976d4', |
|
|
|
], |
|
|
|
'cad' => [ |
|
|
|
'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end', |
|
|
|
'cf0e94b2e9ffc7e04395cf88f7583fc309985910', |
|
|
|
], |
|
|
|
]; |
|
|
|
|
|
|
|
/** |
|
|
|
* @var \Redis|\RedisCluster $cache |
|
|
|
*/ |
|
|
|
@ -54,18 +70,19 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
|
|
|
|
public function get($key) { |
|
|
|
$result = $this->getCache()->get($this->getPrefix() . $key); |
|
|
|
if ($result === false && !$this->getCache()->exists($this->getPrefix() . $key)) { |
|
|
|
if ($result === false) { |
|
|
|
return null; |
|
|
|
} else { |
|
|
|
return json_decode($result, true); |
|
|
|
} |
|
|
|
|
|
|
|
return self::decodeValue($result); |
|
|
|
} |
|
|
|
|
|
|
|
public function set($key, $value, $ttl = 0) { |
|
|
|
$value = self::encodeValue($value); |
|
|
|
if ($ttl > 0) { |
|
|
|
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, json_encode($value)); |
|
|
|
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, $value); |
|
|
|
} else { |
|
|
|
return $this->getCache()->set($this->getPrefix() . $key, json_encode($value)); |
|
|
|
return $this->getCache()->set($this->getPrefix() . $key, $value); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -82,6 +99,7 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
} |
|
|
|
|
|
|
|
public function clear($prefix = '') { |
|
|
|
// TODO: this is slow and would fail with Redis cluster
|
|
|
|
$prefix = $this->getPrefix() . $prefix . '*'; |
|
|
|
$keys = $this->getCache()->keys($prefix); |
|
|
|
$deleted = $this->getCache()->del($keys); |
|
|
|
@ -98,17 +116,14 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
* @return bool |
|
|
|
*/ |
|
|
|
public function add($key, $value, $ttl = 0) { |
|
|
|
// don't encode ints for inc/dec
|
|
|
|
if (!is_int($value)) { |
|
|
|
$value = json_encode($value); |
|
|
|
} |
|
|
|
$value = self::encodeValue($value); |
|
|
|
|
|
|
|
$args = ['nx']; |
|
|
|
if ($ttl !== 0 && is_int($ttl)) { |
|
|
|
$args['ex'] = $ttl; |
|
|
|
} |
|
|
|
|
|
|
|
return $this->getCache()->set($this->getPrefix() . $key, (string)$value, $args); |
|
|
|
return $this->getCache()->set($this->getPrefix() . $key, $value, $args); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -130,10 +145,8 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
* @return int | bool |
|
|
|
*/ |
|
|
|
public function dec($key, $step = 1) { |
|
|
|
if (!$this->hasKey($key)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
return $this->getCache()->decrBy($this->getPrefix() . $key, $step); |
|
|
|
$res = $this->evalLua('dec', [$key], [$step]); |
|
|
|
return ($res === 'NEX') ? false : $res; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -145,18 +158,10 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
* @return bool |
|
|
|
*/ |
|
|
|
public function cas($key, $old, $new) { |
|
|
|
if (!is_int($new)) { |
|
|
|
$new = json_encode($new); |
|
|
|
} |
|
|
|
$this->getCache()->watch($this->getPrefix() . $key); |
|
|
|
if ($this->get($key) === $old) { |
|
|
|
$result = $this->getCache()->multi() |
|
|
|
->set($this->getPrefix() . $key, $new) |
|
|
|
->exec(); |
|
|
|
return $result !== false; |
|
|
|
} |
|
|
|
$this->getCache()->unwatch(); |
|
|
|
return false; |
|
|
|
$old = self::encodeValue($old); |
|
|
|
$new = self::encodeValue($new); |
|
|
|
|
|
|
|
return $this->evalLua('cas', [$key], [$old, $new]) > 0; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -167,15 +172,9 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
* @return bool |
|
|
|
*/ |
|
|
|
public function cad($key, $old) { |
|
|
|
$this->getCache()->watch($this->getPrefix() . $key); |
|
|
|
if ($this->get($key) === $old) { |
|
|
|
$result = $this->getCache()->multi() |
|
|
|
->unlink($this->getPrefix() . $key) |
|
|
|
->exec(); |
|
|
|
return $result !== false; |
|
|
|
} |
|
|
|
$this->getCache()->unwatch(); |
|
|
|
return false; |
|
|
|
$old = self::encodeValue($old); |
|
|
|
|
|
|
|
return $this->evalLua('cad', [$key], [$old]) > 0; |
|
|
|
} |
|
|
|
|
|
|
|
public function setTTL($key, $ttl) { |
|
|
|
@ -185,4 +184,25 @@ class Redis extends Cache implements IMemcacheTTL { |
|
|
|
public static function isAvailable(): bool { |
|
|
|
return \OC::$server->getGetRedisFactory()->isAvailable(); |
|
|
|
} |
|
|
|
|
|
|
|
protected function evalLua(string $scriptName, array $keys, array $args) { |
|
|
|
$keys = array_map(fn ($key) => $this->getPrefix() . $key, $keys); |
|
|
|
$args = array_merge($keys, $args); |
|
|
|
$script = self::LUA_SCRIPTS[$scriptName]; |
|
|
|
|
|
|
|
$result = $this->getCache()->evalSha($script[1], $args, count($keys)); |
|
|
|
if (false === $result) { |
|
|
|
$result = $this->getCache()->eval($script[0], $args, count($keys)); |
|
|
|
} |
|
|
|
|
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
protected static function encodeValue(mixed $value): string { |
|
|
|
return is_int($value) ? (string) $value : json_encode($value); |
|
|
|
} |
|
|
|
|
|
|
|
protected static function decodeValue(string $value): mixed { |
|
|
|
return is_numeric($value) ? (int) $value : json_decode($value, true); |
|
|
|
} |
|
|
|
} |