Redis 是一类盛行的缓存数组资料库,全力支持各式各样正则表达式和内部结构,主要就用作缓存中长期统计数据和继续执行小规模动态操作方式。
假如您对 PHP 实例钟爱,请跳到责任编辑顶部(模版)。
必要条件
redis — 你须要在你的电脑上加装 redis。
次序集
Redis 全力支持次序集——几组惟一数组(称作集核心成员)与两个称作平均分的值密切相关,这是两个实例集:
top-dinosaurs└── Brachiosaurus└── Velociraptor└── T-Rex└── Canada_Goose
那时让他们为他们子集中的每一核心成员加进两个平均分top-dinosaurs,比如,两个残暴平均分:
top-dinosaurs└── Brachiosaurus 1└── Velociraptor 3└── T-Rex 5└── Canada_Goose 10
让他们将下面的实例读取到 redis 储存中。在您最喜欢的终端产品中,运转redis-cli以连接到您已经运转的 redis 服务器,然后继续执行以下操作方式:
# 他们将每一核心成员及其平均分加进到他们的子集中127.0.0.1:6379> zadd top-dinosaurs 1 Brachiosaurus(integer) 1127.0.0.1:6379> zadd top-dinosaurs 3 Velociraptor(integer) 1127.0.0.1:6379> zadd top-dinosaurs 5 T-Rex(integer) 1127.0.0.1:6379> zadd top-dinosaurs 10 Canada_Goose(integer) 1# verify that we have all our set members present127.0.0.1:6379> zrange top-dinosaurs 0 -1 withscores1) “Brachiosaurus”2) “1”3) “Velociraptor”4) “3”5) “T-Rex”6) “5”7) “Canada_Goose”8) “10”
那时从下面的例子来看,他们所做的是明确地为成员指定两个平均分,假如核心成员不存在,这实际上是建立核心成员,否则覆盖其现有平均分。
不,他们将采用的是ZINCRBYAPI 来增加核心成员在子集中的平均分,从而使他们能够提高其在集合中的存在:
# increment score127.0.0.1:6379> zincrby top-dinosaurs 1 Canada_Goose”11″# read the set127.0.0.1:6379> zrange top-dinosaurs 0 -1 withscores1) “Brachiosaurus”2) “1”3) “Velociraptor”4) “3”5) “T-Rex”6) “5”7) “Canada_Goose”8) “11”
正如您在子集预览中看到的那样,子集预览zrange向他们展示了根据平均分按升序次序的值。在他们的例子中他们须要的是两个反转的范围,它首先显示最高分,为此,他们将采用zrevrangebyscore:
127.0.0.1:6379> zrevrangebyscore top-dinosaurs +inf -inf withscores 1) “Canada_Goose” 2) “11” 3) “T-Rex” 4) “5” 5) “Velociraptor” 6) “3” 7) “Test” 8) “1” 9) “Brachiosaurus”10) “1”
设置名称 ( top-dinosaurs)最高分(+inf将使其无限)最小平均分(-inf以相同的方式工作,尽管两个简单的0对他们有用,因为他们希望他们的平均分是无符号的并且大于0)withscores— 当然,他们希望平均分与子集核心成员一起返回。
到目前为止,他们已经学会了建立和预览子集、向子集加进核心成员以及增加同一子集内核心成员的平均分。那时让他们回到他们的案例。
采用 Redis 次序集检测趋势内容
假设他们正在建立两个类似于于 Twitter 的众包内容网站。用户将能够向他们的资料库提交各式各样内容类型,包括文本。他们可以将这些文本输入分解为单词列表,从而提高每一主题的趋势平均分。这是两个例子:
# user inputHappy #Caturday! Hope youre all #feline good. # topics extracted, based on hashtags usedCaturday: 1feline: 1
127.0.0.1:6379> zincrby hot-topics 1 Caturday”1″127.0.0.1:6379> zincrby hot-topics 1 feline”1″
这为他们建立了两个子集:hot-topics。他们可以继续增加同几组主题,为他们的热门话题建立两个成熟的商店。
只有两个问题——他们没有统计数据清理计划。他们建立的子集将永远存在,这意味着它永远不会过期:
127.0.0.1:6379> ttl hot-topics(integer) -1
不用担心,您也可以过期子集,就像键一样,并采用相同的EXPIRE命令:
# 这将使设置在 1 小时后过期127.0.0.1:6379> expire hot-topics 3600(integer) 1# read the TTL (time-to-live) again127.0.0.1:6379> ttl hot-topics(integer) 3584
所以,计划是只调用EXPIRE一次命令,否则,他们只会继续延长子集的 TTL。在您的插件中,只需验证 TTL 是否为-1,然后您才能expire拨打电话:
if redis.ttl(key) == -1: redis.expire(key, 3600)
提高效率
正如他们目前所见,他们只建立了两个子集并为其分配了特定的 TTL(在他们的实例中为两个小时)。这意味着他们将继续向子集中加进内容,直到 TTL 时钟重置并且子集被删除为止。那时,他们只剩下0热点话题了。
问题是您不能独立于整个子集使 redis 子集核心成员过期,因此两个小时前加进的主题将过期,而不会留下子集中最近加进的主题。
他们将遵循与上述相同的方法,但是,他们将建立一定数量的子集而不是两个子集。因此,1 小时的时间窗口将导致每分钟几组(允许他们最多坚持 60 组),或每 2 分钟几组(最多 30 组),或每 5 分钟,等等。
这是两个解释上述场景的实例——采用当前时间戳为他们的子集加进后缀是有意义的:
# this will create 60 sets every hourhot-topics-{hour}:{minute}# this will create 13 sets every hourhot-topics-{hour}-{round(minute/5)}
该解决方案的工作方式是他们避免建立许多子集,这会在稍后组合子集时导致大量读取操作方式。
随着每一子集自行过期,他们不必担心进行任何统计数据清理,他们将始终在缓存资料库中显示最新的子集,他们的工作是将它们组合起来。
组合集
查询缓存储存中存在的最新子集列表
要查询尚未过期的子集,他们可以采用keys带有模式的 API,因此理想情况下结果不会与您可能拥有的其他键重叠:
# 不要这样做127.0.0.1:6379> keys *1) “hot-topics”2) “top-dinosaurs”3) “hot-topics-22:8″# do this, being more precise127.0.0.1:6379> keys hot-topics-*1) “hot-topics-22:8”
然后应该迭代返回的子集列表,每一子集核心成员都zrevrangebyscore像他们之前看到的那样采用读取。
理想情况下,假如您将统计数据储存在同一台服务器中,则采用 and,这样您将在单个事务中发送所有命令,而不是 N=keys.length MULTI:EXEC
127.0.0.1:6379> keys hot-topics-*1) “hot-topics-22:8″2) “hot-topics-22:9″127.0.0.1:6379> multiOK127.0.0.1:6379> zrevrangebyscore hot-topics-22:8 +inf -inf withscoresQUEUED127.0.0.1:6379> zrevrangebyscore hot-topics-22:9 +inf -inf withscoresQUEUED127.0.0.1:6379> exec1) 1) “Canada_Goose” 2) “11” 3) “T-Rex” 4) “5” 5) “Velociraptor” 6) “3” 7) “Test” 8) “1” 9) “Brachiosaurus” 10) “1”2) 1) “feline” 2) “1” 3) “Caturday” 4) “1”
还有两个改进,假如你知道你钟爱的主题的最大数量,你可以加进两个偏移量和限制zrevrangebyscore来避免它包括任何冗余键,只有前 N 个键,比如:
# get top 3 members127.0.0.1:6379> zrevrangebyscore hot-topics-22:8 +inf -inf withscores limit 0 3 1) “Canada_Goose” 2) “11” 3) “T-Rex” 4) “5” 5) “Velociraptor” 6) “3”
建立类似于 Twitter 的热点话题插件 – PHP 实例
此实例采用扩展Redis提供的类php-redis,您可以加装它,或稍微重构代码以采用另两个客户端,比如predis/predis.
<?phpfunction get_redis() : \Redis { static $redis; if ( null === $redis ) { $redis = new \Redis(); $redis->connect(0.0.0.0, 6379); register_shutdown_function([$redis, close]); } return $redis;}function boost_topic(string $setId, string $member, int $increment_by=1) : void{ // create a set for 5 minutes, with a TTL of 1 hour // reason: cannot expire set members without expiring the whole set $setId .= : . date(H.) . round(date(i)/5); // increment set member score get_redis()->zincrby($setId, $increment_by, $member); if ( get_redis()->ttl($setId) == -1 ) // is this a new set? if so, set TTL to 60min to we dont keep stale data get_redis()->expire($setId, 3600);}function get_top_topics(string $setId, int $limit=10) : array{ $items = []; // query all sets created in the past 30min if ( $sets = get_redis()->keys(“{$setId}:*”) ) { $meta = [ withscores => true, limit => [0, $limit] ]; // begin transaction get_redis()->multi(); foreach ( $sets as $set ) { // get top N (=$limit) winners of each set and merge them together get_redis()->zrevrangebyscore($set, +inf, -inf, $meta); } // commit transaction $result = get_redis()->exec(); foreach ( $sets as $i => $set ) { if ( $list = array_map(intval, $result[$i] ?? []) ) { foreach ( $list as $k=>$v ) { $items[$k] = ($items[$k] ?? 0) + $v; } } } arsort($items); // return top winners $items = array_slice($items, 0, $limit); } return $items;}// boost a topics scoreboost_topic(hot-topics, Caturday);boost_topic(hot-topics, Feline);boost_topic(hot-topics, Thursday);// boost a topics score by 2 pointsboost_topic(hot-topics, Feline, 2);// get a list of top 2 trendsget_top_topics(hot-topics, 2); // [ Feline => 3, Caturday => 1 ]