キーのRedisのリスト(データ構造論文)

一覧(リスト)

重複値の整然との複数記憶します。
リスト項目数(項目)の一つ以上を含んでもよく、これによれば、各項目がリスト内の位置に押し込まれるが配置されています。リストの左端から右端まで昇順に、0に開始インデックスとして、項目のインデックス(指標)の値を決定する各リスト項目の位置は、リストの一番左のインデックスエントリ(ヘッダ)が0であり、そしてN-1、Nはリストの長さに右端(テーブルの端部)のリストにおけるインデックスエントリ。
リストには、アイテムは、彼らがユニークである必要はありません、複製することができます含まれています。
ここに画像を挿入説明

プッシュとポップ操作

リストに項目を追加する方法、およびリストの中から項目を削除する方法を学びます。

リストの左端から値をプッシュ

LPUSHキー値[値...]
リストの左端にプッシュターン内の値のうちの1つまたは複数の後、コマンドが新しい値を押し込む返し、リスト内の項目数が現在含まれています。複雑さは、コマンドは、O(1)の複雑さであり、値に押し込む場合にのみ、値をプッシュNは数字であるO(N)です。

redis> LPUSH lst "Lua"
(integer) 1
redis> LPUSH lst "Python"
(integer) 2
redis> LPUSH lst "C"
(integer) 3

ここに画像を挿入説明

リストの左端からプッシュ複数の値

値の指定されたコマンド実行LPUSHの複数の場合、それぞれの値は左から右へのタイミングシーケンスに従ってカラムに押し込まれるであろう
テーブルの左端。
たとえば、次のコマンドを実行します。

redis> LPUSH lst "Lua" "Python" "C"
(integer) 3
和依次执行以下三个命令的效果一 样:
LPUSH lst "Lua"
LPUSH lst "Python"
LPUSH lst "C"

ここに画像を挿入説明

リストの右端から値をプッシュ

RPUSHキー値[値...]
1つ以上の値の数のリストの右端をプッシュするためには、新しい値が押し込まれるコマンドのリターンは、リスト内の項目数が現在含まれていた後。
複雑さは、コマンドは、O(1)の複雑さであり、値に押し込む場合にのみ、値をプッシュNは数字であるO(N)です。

redis> RPUSH lst "Clojure"
(integer) 1
redis> RPUSH lst "Ruby"
(integer) 2
redis> RPUSH lst "C"
(integer) 3

ここに画像を挿入説明

リストの右端からプッシュ複数の値

値の指定されたコマンド実行RPUSHの複数の場合、それぞれの値はタイミングシーケンスに従ってし、左から右へ順次リストの右端に押し込まれます。
たとえば、次のコマンドを実行します。

redis> RPUSH lst "Clojure" "Ruby" "C"
(integer) 3
和依次执行以下三个命令的效果一 样:
RPUSH lst "Clojure"
RPUSH lst "Ruby"
RPUSH lst "C

ここに画像を挿入説明

両端からのアイテムのポップアップリスト

ここに画像を挿入説明

LPOP / RPOP例
redis> RPUSH lst "Clojure" "Ruby" "C" "Python" "Lua"
(integer) 5
redis> LPOP lst
"Clojure"
redis> LPOP lst
"Ruby"
redis> RPOP lst
"Lua"
redis> RPOP lst
"Python"

ここに画像を挿入説明

長さ、及び操作のインデックス範囲

Llenaインデックス、LLRANGE

获取列表的长度

LLEN key
返回列表键 key 的长度,也即是,返回列表包含的列表 项数量。
因为 Redis 会记录每个列表的长度,所以这个命令无须遍历列表,它的复杂度为 O(1) 。

 redis> LLEN lst
 (integer) 5
 redis> LPOP lst
 "Clojure"
 redis> LLEN lst
 (integer) 4

ここに画像を挿入説明

返回给定索引上的项

LINDEX key index
返回列表键 key 中,指定索引 index 上的列表项。index 索引可以是正数或者负数。
复杂度为 O(N) ,N 列表的长度。

 redis> LINDEX lst 1
 "Ruby"
 redis> LINDEX lst 4
 "Lua"
 redis> LINDEX lst -3
 "C"

ここに画像を挿入説明

返回给定索引范围之内的所有项

LRANGE key start stop
返回列表键 key 中,从索引 start 至索引 stop 范围内的所有列表项。两个索引参数都可以是正数或者负数。
复杂度为 O(N) , N 为被返回的列表项数量。

 redis> LRANGE lst 0 2
 1) "Clojure"
 2) "Ruby"
 3) "C"
 redis> LRANGE lst -3 -1
 1) "C"
 2) "Python"
 3) "Lua

ここに画像を挿入説明

示例:使用列表实现用户时间线

ここに画像を挿入説明

更新时间线

ここに画像を挿入説明

获取消息

每当有人访问用户的时间线时,程序就会访问时间线列表,并根据列表中储存的 ID 来获取用户时间线上的消息。通过访问时间线列表中的不同范围,程序可以获取到不同时期的消息,越接近表头的消息就越新,越接近表尾的消息就越旧。
ここに画像を挿入説明

用户时间线的 API 及其实现

ここに画像を挿入説明
时间线的代码实现可以在 timeline.py 看到。

#timeline.py
# encoding: utf-8

def create_timeline_key(user_name):
    """
    创建 'user::<name>::timeline' 格式的时间线键名
    举个例子,输入 'huangz' 将返回键名 'user::huangz::timeline'
    """
    return 'user::' + user_name + '::timeline'


class Timeline:

    def __init__(self, user_name, client):
        self.key = create_timeline_key(user_name)
        self.client = client

    def push(self, message_id):
        return self.client.lpush(self.key, message_id)

    def fetch_recent(self, n):
        return self.client.lrange(self.key, 0, n-1)

    def fetch_from_index(self, start_index, n):
        return self.client.lrange(self.key, start_index, start_index+n-1)
用户时间线使用示例
# 为用户 peter 创建时间线
tl = Timeline('peter', client) 
# 将消息 10086 推入至时间线最前端
tl.push(10086)
tl.fetch_recent(5)
# [10086, 10025, 9251, 8769, 8213]
tl.fetch_from_index(5, 3)
# [7925, 7000, 6928]
# 之后只要不断地调用 fetch_from_index
# 就可以继续获取更早期的消息

ここに画像を挿入説明

插入和删除操作

LSET、LINSERT、LREM、LTRIM

设置指定索引上的列表项

LSET key index value
将列表键 key 索引 index 上的列表项设置为value ,设置成功时命令返回 OK 。
如果 index 参数超过了列表的索引范围,那么命令返回一个错误。
针对表头和表尾节点进行处理时(index 为 0 或者 -1),命令的复杂度为 O(1) ;其他情况下,命令的复杂度为 O(N) ,N 为列表的长度。

 redis> RPUSH lst "Clojure" "Ruby" "C" "Python" "Lua"
 (integer) 5
 redis> LSET lst 0 "Common Lisp"
 OK

ここに画像を挿入説明

在指定位置插入列表项

LINSERT key BEFORE|AFTER pivot value
根据命令调用时传递的是 BEFORE 选项还是 AFTER 选项,将值 value 插入到指定列表项 pivot 的之前或者之后。当 pivot 不存在于列表 key 时,不执行任何操作。返回 -1 表示 pivot 不存在;返回 0 表示键 key 不存在;插入成功时则返回列表当前的长度。
复杂度为 O(N) ,N 为列表长度。

redis> RPUSH lst "Clojure" "C" "Python" "Lua"
(integer) 4
redis> LINSERT lst BEFORE "C" "Ruby"
(integer) 5

ここに画像を挿入説明

从列表中删除指定的值

LREM key count value
根据参数 count 的值,移除列表中与参数 value 相等的列表项:
• 如果 count > 0 ,那么从表头开始向表尾搜索,移除最多 count 个值为 value 的列表项。
• 如果 count < 0 ,那么从表尾开始向表 头搜索,移除最多 abs(count) 个值为 value 的列表项。
• 如果 count = 0 ,那么移除列表中所有 值为 value 的列表项。
命令返回被移除列表项的数量。
命令的复杂度为 O(N) ,N 为列表的长度。

LREM 示例
redis> RPUSH lst "app" "zoo" "spam" 
 "app" "zoo" "egg" "app"
(integer) 7
redis> LREM lst 0 "zoo"
(integer) 2
redis> LREM lst 1 "app"
(integer) 1
redis> LREM lst -1 "app"
(integer) 1

ここに画像を挿入説明

修剪列表

LTRIM key start stop
对一个列表进行修剪(trim),让列表只保留指定索引范 围内的列表项,而将不在范围内的其他列表项全部删除。两个索引都可以是正数或者 负数。
命令执行成功时返回 OK ,复杂度为 O(N) ,N 为被移除列表项的数量。

redis> RPUSH lst "Clojure" "Ruby" "C" "Python" "Lua"
(integer) 5
redis> LTRIM lst 0 2
OK

ここに画像を挿入説明

示例:实现 LLOOGG.com 的记录储存功能

在之前介绍 LLOOGG.com 的时候,我们提到过, LLOOGG.com 允许用户储存最新的 5 条至 10,000 条浏览记录,以便进行查看。LLOOGG.com 使用列表来储存浏览记录,当列表的长度达到了用户指定的最大长度 之后,程序每向列表推入一个新的记录,就需要从列表中弹出一个最旧的记录。

ここに画像を挿入説明

定长先进先出队列

从数据结构的角度来看, LLOOGG.com 为每个被监视网站构建的都是一个定 长先进先出队列(FixedSize First In First Out Queue),这种结构具有以下特点:
• 固定长度(定长):队列的长度(也即是队列包含的项数量)不能超过一个给定的最大值。
• 先进先出:当队列的长度到达最大值时,每向队列推入一个新值,程序就需要从队列中弹出一个最早被推入到列表里面的 值。
通过使用 Redis 列表键,我们也可以构建一个这样的定长先进先出队列。

定长先进先出队列的 API 及其实现

ここに画像を挿入説明
这个定长先进先出队列的实现代码可以在 fixed_fifo.py 文件里找到。

#fixed_fifo.py
# encoding: utf-8

class FixedFIFO:

    def __init__(self, key, max_length, client):
        self.key = key
        self.max_length = max_length
        self.client = client

    def enqueue(self,item):
        # 这里存在一个竞争条件:
        # 如果客户端在 LPUSH 成功之后断线
        # 那么队列里将有超过最大长度数量的值存在
        # 等我们学习了事务之后就来修复这个竞争条件
        # 将值推入列表
        self.client.lpush(self.key, item)
        # 如果有必要的话,进行修剪以便让列表保持在最大长度之内
        self.client.ltrim(self.key, 0, self.max_length-1)
        # 返回 1 表示入队成功
        return 1

    def dequeue(self):
        return self.client.rpop(self.key)

    def get_all_items(self):
定长先进先出队列的使用示例

ここに画像を挿入説明

阻塞式弹出操作

BLPOP 和 BRPOP

阻塞弹出命令

ここに画像を挿入説明

BLPOP/BRPOP 示例
redis> BLPOP empty-1 empty-2 empty-3 5 # 命令依次访问三个列表,发现它们都为空,于是阻塞
(nil) # 返回 nil 表示等待超时
(5.07s) # 客户端被阻塞的时长
redis> RPUSH lst "one" "two" "three"
(integer) 3
redis> BLPOP empty-1 empty-2 lst empty-3 5 # 命令发现 lst 非空,于是弹出
1) "lst" # 执行弹出操作的列表
2) "one" # 被弹出的项
redis> BLPOP empty-1 empty-2 empty-3 5 # 在阻塞的过程中,有列表可以执行弹出操作
1) "empty-3" # 执行弹出操作的列表
2) "hello" # 被弹出的项
(1.84s) # 客户端被阻塞的时长
情形一:非阻塞

ここに画像を挿入説明
当发现给定的列表中有至少一个非空列表 时,BLPOP 或者 BRPOP 就会立即从那个列表里面 弹出元素,在这种情况下, BLPOP 就像一个接受多参数的 LPOP 命令,而 BRPOP 就像一个接受多参数的 RPOP 命令。

情形二:阻塞并超时

如果所有给定列表都是空的,那么 BLPOP/BRPOP 将被阻塞。如果在阻塞的过程中,给定的列表一直没有新项被推入,那么当设定的超时时间到达之后,命令将向被阻塞的客户端返回 nil 。
ここに画像を挿入説明

情形三:阻塞并弹出情况

如果在客户端 X 被阻塞的过程中,有另一个客户端 Y 给㐀成客户端 X 被阻塞的列表推入了新项,那么服务器会将这个新项返 回给客户端 X。
ここに画像を挿入説明

BLPOP/BRPOP 的先到先服务原则

如果有多个客户端同时因为某个列表而被阻塞,那么当有新 值被推入到这个列表时,服务器会按照先到先服务(first in first service)原则,优先向最早被阻塞的客户端返回新值。 举个例子,假设列表 lst 为空,那么当客户端 X 执行命令 BLPOP lst timeout 时,客户端 X 将被阻塞。在此之后,客户端 Y 也执行命令 BLPOP lst timeout ,也因此被阻塞。如果这时,客户端 Z 执行命令 RPUSH lst “hello” ,将值 “hello” 推入列表 lst ,那么这个 “hello” 将被返回给客户端 X ,而不是客户端 Y ,因为客户端 X 的被阻塞时间要早于客户端 Y 的被阻塞时间。

示例:使用列表构建消息队列

每当用户(发布者)在 Twitter 上发布一条新消息时,程序需要将这条消息推送给所有关注者,也即是,将 这条消息放入到每个关注者的用户时间线里面:
• 当关注者的数量比较少时(比如一百几十个),这个操作可以立即完成;
• 相反地,当关注者的数量比 较巨大时(比如几十万个、几百万个),那么推送操作需要花 费大量时间才能完成,发送消息的用户需要等待很久才能获得响应,这也会对web 服务器的性能㐀成影响。
ここに画像を挿入説明

使用消息队列解耦消息发布和消息推送操作

为了解决这个问题,每当用户发送消息的时候,程序都会将这条消息放入到一个消息 队列(message queue)里面,然后由专门的服务器在后台负责将这条消息推送给所有关注者。注意消息队列也是一个 FIFO 队列,因为它需要优先处理推入时间最长的消息。
ここに画像を挿入説明
这种做法有几个好处:

  1. web 服务器可以在保存好用户发布的消息、并将消息推入到 队列之后,立即返回,这样 web 服务器就可以以最快的㏿度 对用户进行响应。
  2. Webサーバは、効率を改善し、プログラムロジックを簡素化するのに役立ちますプッシュメッセージの動作に影響を与えません。
  3. 専用のニュースサーバによってプッシュ操作が担当するので、私たちは、最適化の対象とすることができます。
    キーを使用して、ブロッキングイジェクト操作により、Redisのリストには、我々は同様のPRINTメッセージキューを構築することができます。
メッセージキューAPIとその実装

ここに画像を挿入説明
この実装では、メッセージキューmessage_queue.pyで見つけることができます。

#message_queue.py
# encoding: utf-8

class MessageQueue:

    def __init__(self, key, client):
        self.key = key
        self.client = client

    def enqueue(self, item):
        self.client.lpush(self.key, item)

    def dequeue(self, timeout):
        result = self.client.brpop(self.key, timeout)
        if result:
            poped_list, poped_item = result
            return poped_item

    def length(self):
        return self.client.llen(self.key)

    def get_all_items(self):
        return self.client.lrange(self.key, 0, -1)
メッセージキューの使用例
# web 服务器
q = MessageQueue('user::message::queue', client)
message_id = create_new_message(...) # 创建并储存用户发布的新消息,并返回消息的 ID
q.enqueue(message_id) # 将消息 ID 推入队列里面
# 消息服务器
q = MessageQueue('user::message::queue', client)
while server.is_running(): # 循环
 message_id = q.dequeue(0) # 等待新消息出现
 # 找到消息的所有接收者
 # 将消息 ID 推入他们的时间线
 # ...
メッセージキュー現実の世界

それは、バックエンドとしてRedisのメッセージキューリストを使用してプロジェクトをたくさん持っているし、彼らの可用性と安定性がいくつかの時間のためにテストされているとして、我々はこれらの既製のメッセージキューの使用を考慮することができるように、メッセージキューは、システムPRINTを所有する必要はありません。
Pythonのをhttp://python-rq.org/ RQ
Resque https://github.com/resque/resque
セロリhttp://www.celeryproject.org/

レビュー

リコール知識は、このセクションで説明した
キーのリスト項目(アイテム)の複数のいずれかを含むことができる、それらは位置に押し込まれているリストの各アイテムに配置され、これらの項目が重複してもよいです。
ここに画像を挿入説明

公開された252元の記事 ウォンの賞賛151 ・は 10000 +を見て

おすすめ

転載: blog.csdn.net/qq_39885372/article/details/104240393