Redis の一般的なデータ型

この記事の内容は、Redis の開発と運用保守について説明します。

1、紐

        文字列型は Redis の最も基本的なデータ構造です。まず、キーはすべて文字列型であり、他のいくつかのデータ構造は文字列型に基づいて構築されているため、文字列型は他の 4 つのデータ構造を学習するための基礎を築くことができます。

        上の図に示すように、文字列型の値は実際には文字列 (単純な文字列、複雑な文字列 (JSON、XML など))、数値 (整数、浮動小数点数)、またはバイナリ (画像、音声、 video )、ただし最大値は 512MB を超えることはできません。

        注文

        文字列型のコマンドは数多くありますが、本記事ではよく使われるコマンドと一般的でないコマンドの二次元で説明しますが、ここでよく使われるコマンドと一般的でないコマンドは相対的なものであり、よく使われなければ理解する必要がないわけではありません。 。

         関連するコマンドの概要は、 「Redis の使用法」で参照できます

        共通コマンド

        (1) 設定値
set key value [ex seconds] [px milliseconds] [nx|xx]

         次の操作は hello に設定され、値は world のキーと値のペアです。戻り結果は OK で、設定が成功したことを意味します。

        set コマンドにはいくつかのオプションがあります。

  • ex 秒: キーの第 2 レベルの有効期限を設定します。
  • px ミリ秒: キーの有効期限をミリ秒で設定します。
  • nx: キーを正常に設定して追加に使用するには、そのキーが存在していない必要があります。
  • xx: nx とは異なり、キーを正常に設定して更新に使用するには、キーが存在している必要があります。
  • set オプションに加えて、Redis には setex と setnx という 2 つのコマンドも提供されます。
    setex key seconds value
    setnx key value

    それらの機能は ex および nx オプションと同じです。次の例は、set、setnx、set xx の違いを示しています。

         最初に setnx の例を示します。

        作成したいキーが存在しない場合は setnx コマンドを使用して正常に作成できますが、作成したいキーがすでに存在する場合は setnx コマンドを使用してキーを作成すると、作成が失敗することがわかります。失敗。

        セット:

        setnx と比較すると、set と setnx の違いは、作成したいキーがすでに存在する場合、setnx は正常に作成できませんが、set は正常に作成できることです。

        xx を設定します:

        図からわかるように、set xx を使用すると、既存のキーのみが正常に作成され、作成したいキーが存在しない場合は作成に失敗します。

        それでは、setnx と set xx の実際のアプリケーション シナリオは何でしょうか? いくつかの。

        Redis のシングルスレッドのコマンド処理メカニズムにより、複数のクライアントが setnx キー値を同時に実行した場合、setnx の特性に従って、1 つのクライアントのみが正常に設定できます。Setnx は分散ロックの実装ソリューションとして使用できます。 Redis は、setnx を使用して分散ロックを実装する方法を公式に提供しています: http://redis.io/topics/distlock

        場合によっては、キーが無効な場合に新しいキーが作成されないようにするために、キーがすでに存在する場合にのみキーを更新したい場合があります。SET key value XXこのコマンドにより、キーが存在する場合にのみ値が更新されます。

        (2) 値の取得
get key

        図より、キーが存在する場合はキーの値を取得でき、取得したキーが存在しない場合はnil(空)を返すことがわかります。

        (3) 値の一括設定と値の一括取得
mset key value [key value ...]
mget key1 key2 ....

        一部のキーが存在しない場合は nil (空) が返され、渡されたキーの順序で結果が返されることがわかります。

        開発効率の向上に効果的なバッチ操作コマンドですが、mget などのコマンドがない場合、get コマンドを n 回実行するには、下図のように実行する必要があり、具体的な所要時間は次のとおりです。

        n 回の取得時間 = n 回のネットワーク時間 + n 回のコマンド時間

        mget コマンドを使用した後、次のように get コマンドを n 回実行するだけで済み、具体的なメリットは次のとおりです。

        n 取得回数 = 1 ネットワーク時間 + n コマンド回数

        Redis は 1 秒あたり数万回の読み取りおよび書き込み操作を実行できますが、これは Redis サーバーの処理能力を指します。クライアントの場合、コマンド時間に加えて、コマンドにはネットワーク時間もあります。ネットワーク時間は1 ミリ秒、コマンド時間は 0.1 ミリ秒 (1 秒あたり 1w コマンドの処理に基づいて計算) とすると、1000 個の get コマンドを実行する場合と 1 個の mget コマンドを実行する場合の違いは次のようになります。

1000getと1mgetの比較表
操作する 時間
1000回ゲット 1000 × 1 + 1000 × 0.1 = 1100ms = 1.1s
1 mget (組み立てられた 1000 個のキーと値のペア) 1 × 1 + 1000 × 0.1 = 101ms = 0.101s

        Redis の処理能力は十分に高いため、開発者にとってはネットワークがパフォーマンスのボトルネックになる可能性があり、バッチ操作を習得することで業務処理の効率化が図れますが、注目すべきはバッチ操作ごとに送信されるコマンドです。数は無制限ではなく、大きすぎると Redis の混雑やネットワークの混雑が発生する可能性があります。

        (4) 計数
incr key

        incr コマンドは、値のインクリメント操作を実行するために使用され、返される結果は 3 つの状況に分けられます。

  • 値が整数ではない場合、エラーが返されます。
  • これは単なる整数であり、増分後の結果を返します。
  • キーが存在しない場合は、値 0 に従って増分され、返される結果は 1 になります。

        たとえば、存在しないキーに対して incr 操作を実行すると、戻り結果は 1 になります。そのキーに対して incr コマンドを再度実行すると、戻り結果は 2 になります。値が整数でない場合は、エラーが発生します。戻ってきた。図に示すように:

        incr コマンドに加えて、Redis は decr (自動デクリメント)、incrby (指定された数値への自動インクリメント)、decrby (指定された数値への自動デクリメント)、および incrbyfloat (浮動小数点数の自動インクリメント) を提供します。 :

decr key
incrby key increment
decrby key increment
incrbyfloat key increment

        多くのストレージ システムやプログラミング言語では、内部で CAS メカニズムを使用してカウント機能を実装しているため、ある程度の CPU オーバーヘッドが発生しますが、Redis はシングルスレッド アーキテクチャであり、すべてのコマンドが必要であるため、この問題は Redis には存在しません。 Redis サーバーに到達すると順次実行されます。

        あまり使用されないコマンド

        (1) 付加価値
append key value

        append は、文字列の末尾に値を追加できます。次に例を示します。

        (2) 文字列の長さ
strlen key

        この図から、Redis の漢字は 3 バイトを占有するため、キー redis が 6 バイトを占有していることがわかります。

        (3) 元の値を設定して返す
getset key value

        getset は set と同様に値を設定しますが、キーの元の値も返す点が異なります。次に例を示します。

        (4) 指定した位置に文字を設定します
setrange key offset value

        次の操作では、値が hello から fallo に変更されます。

        (5) 文字列の一部を取得する
getrange key start end

        start と end はそれぞれ start と end のオフセットで、オフセットは 0 から計算されます。

        文字列型コマンドの時間計算量

文字列型コマンドの時間計算量
注文 時間の複雑さ
キー値を設定する ○(1)
キーを取得します ○(1)
del キー [キー ...] O(k)、k はキーの数です
mset キー値 [キー値 ...] O(k)、k はキーの数です
mget キー [キー ...] O(k)、k はキーの数です
増分キー ○(1)
デクリキー ○(1)
incrby キーの増分 ○(1)
decrby キーの増分 ○(1)
incrbyfloat キーの増分 ○(1)
キー値を追加する ○(1)
ストレンキー ○(1)
setrange キーのオフセット値 ○(1)
getrange キーの開始終了 O(n)、nは文字列の長さであり、文字列の取得が非常に速いので、それほど長い文字列でなければO(1)とみなしてよい。

        内部エンコーディング

        文字列型には 3 つの内部エンコーディングがあります。

  • int: 8 バイト長の整数。
  • embstr: 39 バイト以下の文字列。
  • raw: 39 バイトを超える文字列。

        Redis は、現在の値の型と長さに基づいて、どの内部エンコード実装を使用するかを決定します。

        例は次のとおりです。

# 整数类型示例
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"

# 短字符串示例
# 小于等于39个字节的字符串:embstr
127.0.0.1:6379> set key "hello world"
OK
127.0.0.1:6379> object encoding key
"embstr"

# 长字符串示例
# 大于39个字节的字符串:raw
127.0.0.1:6379> set key "one string greater than 39 byte .................................."
OK
127.0.0.1:6379> object encoding key
"raw"
127.0.0.1:6379> strlen key
(integer) 66

        一般的な使用シナリオ

        1. キャッシュ機能

        上に示したように、Redis がキャッシュ層として機能し、MySQL がストレージ層として機能する典型的なキャッシュ利用シナリオであり、要求されたデータのほとんどは Redis から取得されます。Redis には高い同時実行性をサポートする特性があるため、通常、キャッシュは読み取りと書き込みを高速化し、バックエンドの負荷を軽減する役割を果たします。

        次の疑似コードは、上の図のアクセス プロセスをシミュレートします。

// 假设有一个函数getUserInfo,通过传入的id,获取用户信息
UserInfo getUserInfo(Long id){
    // 首先从Redis中获取用户信息:
    // 定义键
    userRedisKey = "user:info:" + id;
    // 从Redis中获取值
    value = redis.get(userRedisKey);
    // 如果没有从Redis中获取到用户信息,需要从MySQL中进行获取,并将结果回写到Redis,添加1小时(3600)过期时间;
    UserInfo userInfo;
    if (value != null) {
        userInfo = deserialize(value);
    } else{
        // 从MySQL中获取用户信息
        userInfo = mysql.get(id);
        if(userInfo != null)
            // 将userInfo序列化,并存入Redis中
            redis.setex(userRedisKey, 3600, serialize(userInfo));
    }
    // 返回结果
    return userInfo;
}

        2. 数を数える

        多くのアプリケーションは、カウントのための基本ツールとして Redis を使用しており、高速なカウント機能とクエリ キャッシュ機能を実装でき、データを他のデータ ソースに非同期で取り込むことができます。たとえば、ビデオ再生カウント システムで使用できます。ビデオ再生カウントをカウントするための基本コンポーネントとして Redis を使用できます。ユーザーがビデオを再生するたびに、対応するビデオ再生カウントが 1 ずつ増加します。

        3.制限速度

        多くの場合、セキュリティ上の理由から、アプリケーションはユーザーがログインするたびに、ユーザーが本人であるかどうかを判断するために携帯電話の確認コードの入力を求めます。ただし、SMS インターフェイスへの頻繁なアクセスを防ぐため、ユーザーが 1 分間に認証コードを取得する頻度は、たとえば 1 分間に 5 回以下に制限されます。図に示すように:

        この関数は Redis を使用して実装できます。次の疑似コードは、基本的な実装アイデアを示しています。

String phoneNum = "188xxxxxxxx"
String key = "shortMsg:limit:" + phoneNum
// SET Key EX 60 NX
isExists = redis.set(key, 1 "EX 60", "NX")
if (isExists != null || redis.incr(key)<=5){
    // 通过
}else{
    // 限速
}

        上記はRedisを利用した速度制限機能の実装ですが、例えばWebサイトで1秒間にn回以上のIPアドレスへのアクセスを制限する場合も同様の考え方が使えます。

        上記で紹介した一元化されたアプリケーション シナリオに加えて、文字列には多くの適用可能なシナリオがあり、開発者は文字列によって提供される関連コマンドを組み合わせることで想像力をフルに発揮できます。

2、ハッシュ

        ほとんどすべてのプログラミング言語は、ハッシュ、辞書、連想配列などと呼ばれるハッシュ タイプを提供します。 Redis では、ハッシュ タイプは、value={ { field1:value},....,{fieldn, valuen}}, Redis key-value の形式のキーと値のペア構造であるキー値自体を指します。 ペアとハッシュ 2 つのタイプの関係は、次の図で表すことができます。

       注文

        関連するコマンドの概要は、 「Redis の使用法」で参照できます

                (1) 設定値
hset key field value
                (2) 値の取得
hget key field
                (3) フィールドの削除
hdel key field [field ...]
                (4) フィールド数の計算
hlen key
                (5) フィールド値を一括で設定または取得する
hmget key field [field ...]
hmset key field value [field value ...]
                (6) フィールドが存在するかどうかを確認します
hexists key field
                (7) 全フィールドを取得
hkeys key
                (8) すべての値を取得する
hvals key
                 (9) すべてのフィールド値を取得する
hgetall key
                (10)hincrby bincrbyfloat
hincrby key field increment
hincrbyfloadt key field increment
                (11) valueの文字列長を計算する(Redis3.2以上が必要)
hstrlen key field

        次の表は、ハッシュ タイプのコマンドの時間計算量を示しています。

ハッシュ型コマンドの計算量
注文 時間の複雑さ
hset キーフィールドの値 ○(1)
hgetキーフィールド ○(1)
hdel キー フィールド [フィールド ...] O(k)、k はフィールドの数です
レンキー ○(1)
hgetallキー O(n)、n はフィールドの総数です
hmget フィールド [フィールド ...] O(k)、k はフィールドの数です
hmset フィールド値 [フィールド値 ...] O(k)、k はフィールドの数です
hexists キー フィールド ○(1)
hkeysキー O(n)、n はフィールドの総数です
hvalsキー O(n)、n はフィールドの総数です
hsetnxキーフィールドの値 ○(1)
hincrby キー フィールドの値 ○(1)
hincrbyfloat キー フィールドの増分 ○(1)
hstrlen キーフィールド ○(1)

        内部エンコーディング

        ハッシュ タイプには 2 つの内部エンコーディングがあります。

  • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
  • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

        1、当field个数比较少且没有大的value时,内部编码为ziplist。

        2、当有value大于64字节,内部编码会有ziplist变为hashtable。

        3、当field个数超过512,内部编码也会有ziplist变为hashtable。

        但是在实际操作中我们会发现哈希内部的编码是listpack,既不是ziplist,也不是hashtable,如图:

        这是因为Redis 在内部自动进行了编码转换,将哈希键的编码方式从哈希表(hashtable)转换为 Listpack。这是 Redis 为了优化内存使用和性能而采取的一种策略。

        当哈希键的大小和字段的大小满足一定条件时,Redis 会自动选择将其编码方式从哈希表转换为 Listpack。Listpack 是一种紧凑的二进制格式,用于存储键值对,类似于 Redis 中的列表(List)数据结构。这种转换可以减少内存占用并提高一些操作的性能。

        虽然哈希键的内部编码方式显示为 "listpack",但用户仍然可以使用通常的哈希操作命令来访问和操作哈希键的数据,就像它们仍然是哈希表一样。这种编码方式的转换是 Redis 内部的优化策略,对于用户来说,不需要直接操作 Listpack。

        那什么情况下会发生这样的事情呢?

        Redis 会自动选择将哈希键(Hash Key)的编码方式从哈希表(hashtable)转换为 Listpack 编码方式的情况通常涉及以下条件:

  1. 哈希键的大小:当哈希键包含的字段数量相对较少,并且字段的大小适中时,Redis 可能会考虑将其编码方式转换为 Listpack。具体的阈值可能因 Redis 的版本和配置而有所不同。

  2. 字段的大小:如果哈希键的字段的键和值的大小都比较小,那么 Redis 更有可能选择 Listpack 编码方式。

  3. 哈希键的使用模式:哈希键的使用模式也会影响 Redis 的编码选择。如果哈希键主要用于插入、删除或迭代操作,并且不需要频繁的哈希键查找操作,那么 Listpack 编码方式可能更合适。

  4. 内存优化策略:Redis 会考虑系统的内存状况和性能,以决定是否切换编码方式。它的目标是提高内存使用效率和执行操作的速度。

        需要注意的是,Redis 的编码方式转换是自动进行的,用户无需干预。Redis 会根据上述条件自动选择合适的编码方式以提高性能和内存利用率。这个转换是 Redis 的内部优化机制,它使 Redis 能够在不同情况下充分利用内存,并提供高性能。

        如果您想了解特定版本的 Redis 在何时选择转换编码方式以及如何调整这些条件,请参考该版本的 Redis 文档或源代码。不同版本的 Redis 可能会在这方面有些许不同。

        使用场景

        相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操作上会更加便捷。可以将每个用户的id定义为键后缀,多对field-value对应每个用户的属性。

        但是需要注意的是哈希类型和关系数据库有两点不同之处:

  • 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的field,二关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL)。
  • 关系型数据库可以做复杂的关系查询,去模拟关系型复杂查询开发困难,维护成本高。

        到目前为止,我们已经给出三种方法缓存用户信息,如下:

        1、原生字符串类型:每个属性一个键

set user:1:name tom
set user:1:age 23
set user:1:city beijing

        优点:简单直观,每个属性都支持更新操作。

        缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以这种方式一般不会在生产环境中使用。

        2、序列化字符串类型:将用户信息序列化后用一个键保存。

set user:1 serialize(userInfo)

        优点:简化编程,如果合理的使用序列化可以提高内存的使用率。

        缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出来进行反序列化,更新后再序列化到Redis中。

        3、哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。

hmset user:1 name tom age 23 city beijing

        优点:简单直观,如果使用合理可以减少内存空间的使用。

        缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

3、List

        列表(list)类型是用来存储多个有序的字符串的,可以理解为是一个双向链表。

        如上图中第一个列表所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表,列表中的每个字符串被称为元素,一个列表最多可以存储2^32-1个元素。在Redis中,可以对列表两端插入和弹出,还可以获取指定范围内的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,他可以充当栈和队列的角色,在实际开发上有很多应用场景。

        列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标来获取某个元素或者某个范围内的元素列表如上图的第二个列表所示。第二,列表中的元素可以是重复的。

        命令

        相关命令的汇总可以前往Redis的使用进行查看

                基本命令
# 从左/右边插入元素
lpush/rpush key value [value ...]

# 向某个元素前或者后插入元素
linsert key before/after pivot value

# 获取指定范围内的元素列表
lrange key start end

# 获取列表指定索引下标的元素
lindex key index

# 获取列表长度
llen key

# 从左/右侧弹出元素
lpop/rpop key

# 按照索引范围修剪列表
ltrim key start end

# 修改指定索引下标的元素
lset key index newValue

        删除元素命令
# 删除指定元素
lrem key count value

        lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为如下情况:

  • count>0,从左到右,删除最多count个元素。
  • count<0,从右到左,删除最多count绝对值个元素。
  • count=0,删除所有。

       阻塞式弹出
# 阻塞式弹出
blpop key [key ...] timeout
brpop key [key ...] timeout

        blpop与brpop是lpop和rpop的阻塞版本,它们除了弹出的方向不同,使用方法基本相同,所以下面以brpop命令进行说明,brpop命令包含2个参数:

  • key [key ...]:多个列表的键。
  • timeout:阻塞时间(单位:秒)。
  1. 列表为空:如果timeout=3,那么客户要等到3秒结束后返回,如果timeout=0,那么客户端一直阻塞下去。
    client1:

    此时如果另一个客户端在该列表里添加了数据则:
  2. 列表不为空:客户端会立即返回。

        在使用brpop时需要注意两个点:

  1. 如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端会立即返回。
  2. 如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。

         下表描述的是列表类型命令的时间复杂度

列表类型命令的时间复杂度
操作类型 命        令 时间复杂度
添加 rpush key value [value ....] O(k),k是元素个数
lpush  key value [value ....] O(k),k是元素个数
linsert key before | after pivot value O(n),n是pivot距离列表头或尾的距离
查找 lrange key start end O(s+n),s是start偏移量,n是start到end的范围
lindex key index O(n),n是索引的偏移量
llen key O(1)
删除 lpop key O(1)
rpop key O(1)
lrem count value O(n),n是列表长度
ltrim key start end O(n),n是要裁剪的元素总数
修改 lset key index value O(n),n是索引的偏移量
阻塞操作 blpop brpop O(1)

        内部编码

        列表的类型的内部编码有两种:

  • ziplist(压缩列表):列表内部的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表内部实现来减少内存的使用。
  • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
  • quicklist(快速列表):考虑到链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。因此Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist
        快速列表

        quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。

详情介绍可以前往https://www.cnblogs.com/hunternet/p/12624691.html

        使用场景

        1)消息队列

        Redis中的lpush+brpop命令组合可以实现阻塞队列,生产者客户端用lrpish从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

        2)文章列表

        每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

  1. 每篇文章使用户哈希结构存储,例如每篇文章有三个属性,title、timestamp、content。

  2. 向文章列表添加文章,user:{id}:articles作为用户文章列表的键。

  3. 分页获取用户文章列表。

# 伪代码
articles = lrange user:1:article 0 9
for article in {articles}:
    hgetall {article}
  • lpush + lpop = Stack(栈)

  • lpush + rpop = Queue(队列)

  • lpush + ltrim = Capped Collection(集合)

  • lpush + brpop = Message Queue(消息队列)

4、Set

待更新

5、ZSet

待更新

6、BitMaps

待更新

7、ハイプログログ

アップグレード保留中

8、ジオ

アップグレード保留中

おすすめ

転載: blog.csdn.net/Deikey/article/details/132639340