1 Redis 简介
Redis 是一个非关系型内存数据库,它提供了 字符串、列表、集合、散列、有序集合 5 种数据结构,它可以把内存的键值数据持久化到磁盘,除此之外还可以通过复制类扩展读性能,通过客户端分片来扩展写性能。
与缓存服务器 memcached 的对比:两者都可以用来存储键值映射,且性能相差不大。但是 Redis 可以将数据持久,并且 Redis 除了能存储字符串类型值外,还可以存储其他 4 种数据结构,而 memcached 只能存储普通字符串。
memcached 只能使用 append 命令将数据添加到已有字符串末尾,并且通过黑名单来隐藏元素实现假删除,从而避免堆元素指向读取、更新、写入。而 Redis 是直接对元素进行添加或者删除。
2 Redis 数据据结构简介
2.1 字符串
字符串类型数据支持 GET(获取值)、SET(设置 值)和DEL(删除值)等操作。当字符串存储数值型数据时,还可以进行自增、自减操作。字符串类型也可以当作 bitmap 使用。
2.2 列表
列表可以有序的存储多个字符串,列表可以包含相同的元素。LPUSH 命令和 RPUSH 命令用于向列表推入元素,LPOP 命令和 RPOP 命令用于从列表弹出元素,LINDEX 用于获取列表在给定位置上的一个元素,LRANGE 用于获取列表在给定范围上的所有元素。
2.3 集合
集合使用散列表(只有键没有值的散列表)来保证存储的字符串各不相同,集合的元素是无序的。集合使用 SADD 添加元素,SREM 移除元素,SISMEMBER 检查一个元素是否存在于集合中,SMEMBERS 获取集合中所有元素,但是如果元素过多,这个命令的执行速度可能会很慢。多个集合之间可以通过 SINTER、SUNION、SDIFF 进行交集、并集、差集计算。
2.4 散列
散列可存储多个键值对之间的映射,散列的值也可以是字符串或数值,也可以对数值进行自增或者自减操作。HSET 在散列里设置键值对, HGET 获取指定散列键的值,HGETALL 获取散列包含的所有键值对, HDEL 删除指定散列里的键。
2.5 有序集合
有序集合和散列一样,都用于存储键值对:有序集合的键被称为成员(member)每个成员都是独立不相同的。而值则被称为分值(score),分值必须是浮点数。有序集合既可以通过成员访问元素,又可以根据分值的排序来访问元素。ZADD 将一个带有分值的成员添加到有序集合里,ZRANGE 根据元素在有序集合中所处的位置,从有序集合中获取多个元素,ZRANGEBYSCORE 获取有序集合在给定的分值范围内的所有元素,ZREM 删除成员
3 你好 Redis
3.1 对文章进行投票
我们希望通过文章获取到的投票数量和文章的发布时间给文章进行评分,这个评分会随着时间的流逝而减少,具体的算法:文章获得的投票数 * 常量 + 发布时间。根据这个算法,文章每获得一票,就需要加一次常数,得到最新的分数。然后根据分数来决定文章列表前 100 的文章。
为了实现投票功能,我们首先需要一个散列存储文件信息:
然后再用两个有序集合,两个有序集合的键都是文章 ID,其中一个有序集合值存文件发布时间,另一个有序集合值存文章的分数。这样我们就能实现根据文件发布时间排序,或者根据文章的评分排序。
为了防止重复投票,我们还需要为每一篇文章创建一个集合,保存已经投票的用户。
为了节约内存,我们规定文章发布满一周之后,用户就不能再投票,文章评分将会固定下来。
当用户尝试投票时,下面是投票的实现过程:
- 使用 ZSCORE 检查文章发布时间有序集合,看文章发布时间是否在一周之内。
- 使用 SADD 将用户添加到文章已投票用户集合中。
- 使用 ZINCRBY 对文章分数有序集合的分数进行自增。
- 使用 HINCRBY 对文章信息散列中的投票数进行更新。
2 发布并获取文章
发布文章的步骤:
- 使用计数器自增生成一个文章 ID。
- 使用 HMSET 来存储文章相关的信息。
- 执行两个 ZADD 命令,将文章的初始评分和发布时间分别添加到两个相应的有序集合里。
对于获取文章:
- 使用 ZREVRANGE 命令取出多个文章 ID,可以取按照时间和评分排序的有序集合。
- 对每个文章 ID 使用 HGETALL 取出所有文章信息。
3 对文章进行分组
我们可以创建多种分组(标签),比如:动物、编程、情感等等。为此我们需要给每个分组创建一个集合,并将同属于一个分组的文章 ID 记录在集合里面,同时我们可以向集合里添加或者移除文章。
为了能够根据评分对分组里的文章进行排序和分页,需要需要将同一个组里所有文章按照评分有序的存在在有序集合里。ZINTERSTORE 命令可以接受多个集合和多个有序集合作为输入,找出它们的交集,并对它们分值进行合并。在合并时,集合的分值被视为 1,有序集合则根据实际分值,将集合分值加起来作为最终的分值。执行完 ZINTERSTORE 后,我们就能得到分组的有序集合。由于 ZINTERSTORE 比较耗时,我们可以将结果缓存 60 秒。
我们还可以把文章所属的组,存到文章信息的散列中。
下面是对分组排序查询过程:
- 为每个分组的每种排序创建一个 key。
- 如果存在缓存,使用缓存的值查询文章。
- 如果不存在缓存,使用 ZINTERSTORE 计算并缓存结果,并设置过期时间。
def get_group_artcles():
key = order + group
if not conn.exiets(key):
conn.zinterstore(key, ['group:' + group, order], aggregate = 'max')
conn.expire(key, 60)
return get_artcles()