在Redis上最好将一个带有JSON数据的键存储为值,或者将多个键存储为单个值?

时间:2019-02-12 00:10:02

标签: php redis

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

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

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

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

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

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

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

$redis->set("MyKey-UserID", json_encode($array));

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

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

$redis->set("MyKey-UserID-Clicks", 123);
$redis->set("MyKey-UserID-Downloads", 1234);
$redis->set("MyKey-UserID-Views", 123);

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

$redis->incr("MyKey-UserID-Clicks");
$redis->incr("MyKey-UserID-Downloads");
$redis->incr("MyKey-UserID-Views");

我选择的选项+问题

我个人会使用选项1,您对此有何看法?

您认为使用GET + SET还是使用INCR仍然会很快?

您对方案2有何看法?

我在选项1中的优点/缺点

选项1优点:

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

选项1缺点:

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

一些有用的答案

@Samveen(Link

  

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

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

2 个答案:

答案 0 :(得分:1)

如果您需要更新单个字段并重新保存它,那么选项1并不理想,因为它不能正确处理并发写入。

您应该能够在Redis中用作HASH,并使用HINCRBY来增加哈希中的各个键。将其与管道结合使用,更新多个密钥时,您只会向Redis发出一个请求。

您可以使用HGETALL来获取哈希中的所有键/值对。

答案 1 :(得分:0)

我在接受@TheDude的建议后添加了答案

选项3:使用哈希(优胜者)

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

$redis->hMSet('MyKey-UserID', array('clicks' => 123, 'downloads' => 123, 'views' => 123));

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

$redis->hIncrBy('MyKey-UserID', 'clicks', 2);
$redis->hIncrBy('MyKey-UserID', 'downloads', 2);
$redis->hIncrBy('MyKey-UserID', 'views', 2);

使用这种方法,我可以拥有一个哈希(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();

哈希与SET / GET(键=值)数据类型

根据此答案: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

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

redis-benchmark -n 100000 -q hset myhash rand_string rand_int

所以散列与Get / Set(字符串)一样快。