在 Redis 上,将一个带有 JSON 数据的键作为值存储还是将多个键作为一个值存储更好?

use*_*081 3 php redis

我正在开发一个 Web 应用程序(Nginx+PHP7.3),它将使用 Redis 数据库来存储一些数据(主要是计算事物),我必须决定如何存储数据。在我的情况下,重要的是速度和性能,并且还要保持每秒低操作数,以便能够处理与 Web 应用程序的许多并发连接。

选项 1:将 JSON 数据存储在单个键上

为了保存数据,我将使用单个 SET 操作,即:

$redis->set("MyKey-UserID", '{"clicks":123,"downloads":1234,"views":123}');
Run Code Online (Sandbox Code Playgroud)

然后更新数据我会使用两个操作(GET + SET),即:

$array = json_decode($redis->get("MyKey-UserID"), true);

$array['clicks']++;
$array['downloads']++;
$array['views']++;

$redis->set("MyKey-UserID", json_encode($array));
Run Code Online (Sandbox Code Playgroud)

选项 2:具有单个值的多个键

为了保存数据,我将使用多个 SET 操作,即:

$redis->set("MyKey-UserID-Clicks", 123);
$redis->set("MyKey-UserID-Downloads", 1234);
$redis->set("MyKey-UserID-Views", 123);
Run Code Online (Sandbox Code Playgroud)

然后更新数据我会使用多个 INCR 操作,即:

$redis->incr("MyKey-UserID-Clicks");
$redis->incr("MyKey-UserID-Downloads");
$redis->incr("MyKey-UserID-Views");
Run Code Online (Sandbox Code Playgroud)

我选择的选项 + 问题

我个人会使用选项 1,您有什么看法?

您认为使用 GET + SET 仍然会像使用 INCR 一样快吗?

您如何看待选项 2?

我对选项 1 的利弊

选项 1 优点:

  • 更好的数据库组织,因为我将为每个用户只有一个密钥
  • 通过单个 GET 操作,我将拥有所有 JSON 数据
  • 要更新所有 JSON 字段,我将只使用两个操作 (GET + SET)
  • 数据库的文件大小会更小

选项 1 缺点:

  • 为了只增加“点击次数”,我需要两个操作(GET + SET)而不是一个 INCR
  • 也许选项 1 程序 (GET+SET) 比选项 2 中的多个 INCR 慢?

一些有用的答案

@Samveen(链接)

如果预期会同时修改 JSON 负载,则选项 1 不是一个好主意(非原子读-修改-写的经典问题)

我们有很多并发连接,所以也许选项 2 是赢家。

use*_*081 6

我在听取@TheDude 的建议后添加了我的答案

选项 3:使用哈希(赢家)

为了保存数据,我将使用一个 hMSet,即:

$redis->hMSet('MyKey-UserID', array('clicks' => 123, 'downloads' => 123, 'views' => 123));
Run Code Online (Sandbox Code Playgroud)

然后要更新所有字段,我将使用多个 hIncrBy,即:

$redis->hIncrBy('MyKey-UserID', 'clicks', 2);
$redis->hIncrBy('MyKey-UserID', 'downloads', 2);
$redis->hIncrBy('MyKey-UserID', 'views', 2);
Run Code Online (Sandbox Code Playgroud)

使用这种方法,我可以拥有一个哈希值 (MyKey-UserID),然后添加自定义字段。

所以数据库仍然很小(与选项 2 相比)并且并发写入会很好(与选项 1 相比)。

根据phpredis,我还可以使用 multi() 在一个操作中运行多个命令:

Redis::MULTI 命令块作为单个事务运行

https://github.com/phpredis/phpredis#multi-exec-discard

所以我可以像这样更新多个字段或所有字段,只执行一项操作:

$ret = $redis->multi()
     ->hIncrBy('MyKey-UserID', 'clicks', 2)
     ->hIncrBy('MyKey-UserID', 'downloads', 2)
     ->hIncrBy('MyKey-UserID', 'views', 2)
     ->exec();
Run Code Online (Sandbox Code Playgroud)

哈希与 SET/GET (key=value) 数据类型

根据这个答案:https : //stackoverflow.com/a/24505485/2972081

尽可能使用哈希

小散列被编码在一个非常小的空间中,因此您应该尝试在每次可能时使用散列来表示您的数据。例如,如果您在 Web 应用程序中有代表用户的对象,而不是对姓名、姓氏、电子邮件、密码使用不同的键,而是使用包含所有必填字段的单个哈希。

我做了一些基准测试,结果如下:

hset myhash rand_string rand_int: 31377.47 requests per second
hget myhash rand_string: 30750.31 requests per second
hincrby myhash rand_string: 30312.21 requests per second
set rand_string: 30703.10 requests per second
get rand_string: 30969.34 requests per second
incrby rand_string: 30581.04 requests per second
Run Code Online (Sandbox Code Playgroud)

我用于基准测试的命令是这样的:

redis-benchmark -n 100000 -q hset myhash rand_string rand_int
Run Code Online (Sandbox Code Playgroud)

所以哈希和 Get/Set(字符串)一样快。