前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用 Redis 散列实现短网址生成器|文末福利

用 Redis 散列实现短网址生成器|文末福利

作者头像
每天晒白牙
发布2020-08-21 15:30:26
9480
发布2020-08-21 15:30:26
举报
文章被收录于专栏:每天晒白牙

散列简介

Redis 的散列键会将一个键和一个散列在数据库里关联起来,用户可以在散列中为任意多个字段(field)设置值。与字符串键一样,散列的字段和值既可以是文本数据,也可以是二进制数据。

通过使用散列键,用户可以把相关联的多项数据存储到同一个散列里面,以便对这些数据进行管理,或者针对它们执行批量操作。比如图 3-2 就展示了一个使用散列存储文章数据的例子,在这个例子中,散列的键为article::10086,而这个键对应的散列则包含了 4 个字段,其中:

"title" 字段存储文章的标题 "greeting"。 "content" 字段存储文章的内容 "hello world"。 "author" 字段存储文章的作者名字 "peter"。 "create_at" 字段存储文章的创建时间 "1442744762.631885"。

使用散列存储文章数据

与之前使用字符串键存储文章数据的做法相比,使用散列存储文章数据只需要在数据库里面创建一个键,并且因为散列的字段名不需要添加任何前缀,所以它们可以直接反映字段值存储的是什么数据。

Redis 为散列键提供了一系列操作命令,通过使用这些命令,用户可以:

为散列的字段设置值,或者只在字段不存在的情况下为它设置值。 从散列里面获取给定字段的值。 对存储着数字值的字段执行加法操作或者减法操作。 检查给定字段是否存在于散列当中。 从散列中删除指定字段。 查看散列包含的字段数量。 一次为散列的多个字段设置值,或者一次从散列中获取多个字段的值。 获取散列包含的所有字段、所有值或者所有字段和值。

本章接下来将对以上提到的散列操作进行介绍,说明如何使用这些操作去构建各种有用的应用程序,并在最后详细地说明散列键与字符串键之间的区别。

HSET:为字段设置值

用户可以通过执行 HSET 命令为散列中的指定字段设置值:

代码语言:javascript
复制
HSET hash field value

根据给定的字段是否已经存在于散列中,HSET 命令的行为也会有所不同:

如果给定字段并不存在于散列当中,那么这次设置就是一次创建操作,命令将在散列里面关联起给定的字段和值,然后返回 1。 如果给定的字段原本已经存在于散列里面,那么这次设置就是一次更新操作,命令将使用用户给定的新值去覆盖字段原有的旧值,然后返回 0。

举个例子,通过执行以下 HSET 命令,我们可以创建出一个包含了 4 个字段的散列,这 4 个字段分别存储了文章的标题、内容、作者以及创建日期:

代码语言:javascript
复制
redis> HSET article::10086 title "greeting"
(integer) 1
redis> HSET article::10086 content "hello world"
(integer) 1
redis> HSET article::10086 author "peter"
(integer) 1
redis> HSET article::10086 created_at "1442744762.631885"
(integer) 1

图 3-3 展示了以上 HSET 命令对散列 article::10086 进行设置的整个过程。

HSET前

HSET后

提示:散列包含的字段就像数据库包含的键一样,在实际中都是以无序方式进行排列的,不过本书为了展示方便,一般都会把新字段添加到散列的末尾,排在所有已有字段的后面。

使用新值覆盖旧值

正如之前所说,如果用户在调用 HSET 命令时给定的字段已经存在于散列当中,那么 HSET 命令将使用用户给定的新值去覆盖字段已有的旧值,并返回 0 表示这是一次更新操作。

比如,以下代码就展示了如何使用 HSET 命令去更新 article::10086 散列的 title 字段以及 content 字段:

代码语言:javascript
复制
redis> HSET article::10086 title "Redis Tutorial" 
(integer) 0
redis> HSET article::10086 content "Redis is a data structure store, ..."
(integer) 0

图 3-4 展示了被更新之后的 article::10086 散列。

更新后的散列

其他信息

复杂度:O (1)。

版本要求:HSET 命令从 Redis 2.0.0 版本开始可用。

HSETNX:只在字段不存在的情况下为它设置值

HSETNX 命令的作用和 HSET 命令的作用非常相似,它们之间的区别在于,HSETNX 命令只会在指定字段不存在的情况下执行设置操作:

代码语言:javascript
复制
HSETNX hash field value

HSETNX 命令在字段不存在并且成功为它设置值时返回 1,在字段已经存在并导致设置操作未能成功执行时返回0。

举个例子,对于图 3-5 所示的 article::10086 散列来说,执行以下 HSETNX 命令将不会对散列产生任何影响,因为 HSETNX 命令想要设置的 title 字段已经存在:

代码语言:javascript
复制
redis> HSETNX article::10086 title "Redis Performance Test"
(integer) 0   -- 设置失败

HSETNX前

相反,如果我们使用 HSETNX 命令去对尚未存在的 view_count 字段进行设置,那么这个命令将会顺利执行,并将 view_count 字段的值设置为 100:

代码语言:javascript
复制
redis> HSETNX article::10086 view_count 100
(integer) 1   -- 设置成功

图 3-6 展示了 HSETNX 命令成功执行之后的 article::10086 散列。

HSETNX命令后

其他信息

复杂度:O (1)。

版本要求:HSETNX 命令从 Redis 2.0.0 版本开始可用。

HGET:获取字段的值

HGET 命令可以根据用户给定的字段,从散列中获取该字段的值:

代码语言:javascript
复制
HGET hash field

例如,对于图 3-7 所示的两个散列键来说,执行以下命令可以从 article::10086 散列中获取 author 字段的值:

代码语言:javascript
复制
redis> HGET article::10086 author
"peter"

而执行以下命令则可以从 article::10086 散列中获取 created_at 字段的值:

代码语言:javascript
复制
redis> HGET article::10086 created_at
"1442744762.631885"

两个散列

再例如,如果我们想要从 account::54321 散列中获取 email 字段的值,那么可以执行以下命令:

代码语言:javascript
复制
redis> HGET account::54321 email
"peter1984@spam_mail.com"

处理不存在的字段或者不存在的散列

如果用户给定的字段并不存在于散列当中,那么 HGET 命令将返回一个空值。

举个例子,在以下代码中,我们尝试从 account::54321 散列里面获取 location 字段的值,但由于 location 字段并不存在于 account::54321 散列当中,所以 HGET 命令将返回一个空值:

代码语言:javascript
复制
redis> HGET account::54321 location
(nil)

尝试从一个不存在的散列里面获取一个不存在的字段值,得到的结果也是一样的:

代码语言:javascript
复制
redis> HGET not-exists-hash not-exists-field
(nil)

其他信息

复杂度:O (1)。

版本要求:HGET 命令从 Redis 2.0.0 版本开始可用。

示例:实现短网址生成程序

为了给用户提供更多发言空间,并记录用户在网站上的链接点击行为,大部分社交网站都会将用户输入的网址转换为相应的短网址。比如,如果我们在新浪微博中发言时输入网址 http://redisdoc.com/geo/index.html,那么微博将把这个网址转换为相应的短网址 http://t.cn/RqRRZ8n,当用户访问这个短网址时,微博在后台就会对这次点击进行一些数据统计,然后再引导用户的浏览器跳转到 http://redisdoc.com/geo/index.html上面。

创建短网址本质上就是要创建出短网址 ID 与目标网址之间的映射,并在用户访问短网址时,根据短网址的 ID 从映射记录中找出与之相对应的目标网址。比如在前面的例子中,微博的短网址程序就将短网址 http://t.cn/RqRRZ8n 中的 ID 值 RqRRZ8n 映射到了 http://redisdoc.com/geo/index.html 这个网址上面,当用户访问短网址 http://t.cn/RqRRZ8n 时,程序就会根据这个短网址的 ID 值 RqRRZ8n 找出与之对应的目标网址 http://redisdoc.com/geo/index.html,并将用户引导至目标网址上面去。

作为示例,图 3-8 展示了几个微博短网址 ID 与目标网址之间的映射关系。

映射关系

因为 Redis 的散列非常适合用来存储短网址 ID 与目标网址之间的映射,所以我们可以基于 Redis 的散列实现一个短网址程序,代码清单 3-1 展示了一个这样的例子。

代码清单 3-1 使用散列实现的短网址程序:/hash/shorty_url.py

代码语言:javascript
复制
from base36 import base10_to_base36

ID_COUNTER = "ShortyUrl::id_counter"
URL_HASH = "ShortyUrl::url_hash"

class ShortyUrl:
  def __init__(self, client):
    self.client = client

  def shorten(self, target_url):
    """
    为目标网址创建并存储相应的短网址ID
    """
    # 为目标网址创建新的数字ID
    new_id = self.client.incr(ID_COUNTER)
    # 通过将十进制数字转换为三十六进制数字来创建短网址ID,
    # 比如,十进制数字10086将被转换为三十六进制数字7S6
    short_id = base10_to_base36(new_id)
    # 把短网址ID用作字段,目标网址用作值,将它们之间的映射关系存储到散列里面
    self.client.hset(URL_HASH, short_id, target_url)
    return short_id

  def restore(self, short_id):
    """
    根据给定的短网址ID,返回与之对应的目标网址
    """
    return self.client.hget(URL_HASH, short_id)

ShortyUrl 类的 shorten() 方法负责为输入的网址生成短网址 ID,它的工作包括以下 4 个步骤:

1)为每个给定的网址创建一个十进制数字 ID。 2)将十进制数字ID转换为三十六进制,并将这个三十六进制数字用作给定网址的短网址 ID,这种方法在数字 ID 长度较大时可以有效地缩短数字 ID 的长度。代码清单 3-2 展示了将数字从十进制转换成三十六进制的 base10_to_base36 函数的具体实现。 3)将短网址 ID 和目标网址之间的映射关系存储到散列中。 4)向调用者返回刚刚生成的短网址 ID。

代码清单 3-2 将十进制数字转换成三十六进制数字的程序:/hash/base36.py

代码语言:javascript
复制
def base10_to_base36(number):
  alphabets = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  result = ""

  while number != 0 :
    number, i = divmod(number, 36)
    result = (alphabets[i] + result)
return result or alphabets[0]

restore() 方法要做的事情和 shorten() 方法正好相反,它会从存储着映射关系的散列里面取出与给定短网址ID相对应的目标网址,然后将其返回给调用者。

以下代码简单地展示了使用 ShortyUrl 程序创建短网址 ID 的方法,以及根据短网址 ID 获取目标网址的方法:

>>> from redis import Redis >>> from shorty_url import ShortyUrl >>> client = Redis(decode_responses=True) >>> shorty_url = ShortyUrl(client) >>> shorty_url.shorten("RedisGuide.com") # 创建短网址 ID '1' >>> shorty_url.shorten("RedisBook.com") '2' >>> shorty_url.shorten("RedisDoc.com") '3' >>> shorty_url.restore("1") # 根据短网址 ID 查找目标网址 'RedisGuide.com' >>> shorty_url.restore("2") 'RedisBook.com'

图 3-9 展示了上面这段代码在数据库中创建的散列结构。

散列结构

HINCRBY:对字段存储的整数值执行加法或减法操作

与字符串键的 INCRBY 命令一样,如果散列的字段里面存储着能够被 Redis 解释为整数的数字,那么用户就可以使用 HINCRBY 命令为该字段的值加上指定的整数增量:

代码语言:javascript
复制
HINCRBY hash field increment

HINCRBY 命令在成功执行加法操作之后将返回字段当前的值作为命令的结果。

比如,对于图 3-10 所示的 article::10086 散列,我们可以通过执行以下命令为 view_count 字段的值加上 1:

代码语言:javascript
复制
redis> HINCRBY article::10086 view_count 1
(integer) 101

也可以通过执行以下命令,为 view_count 字段的值加上 30:

代码语言:javascript
复制
redis> HINCRBY article::10086 view_count 30
(integer) 131

存储文章数量的散列

本文摘选自《Redis 使用手册》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天晒白牙 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 散列简介
  • HSET:为字段设置值
  • 使用新值覆盖旧值
  • 其他信息
  • HSETNX:只在字段不存在的情况下为它设置值
  • 其他信息
  • HGET:获取字段的值
  • 处理不存在的字段或者不存在的散列
  • 其他信息
  • 示例:实现短网址生成程序
  • HINCRBY:对字段存储的整数值执行加法或减法操作
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档