Redisを利用する際に注意すべき21のポイント(まとめ)

青灯夜游
リリース: 2021-03-24 11:34:23
転載
2840 人が閲覧しました

この記事では、Redis を使用する際に知っておくべき 21 の重要なポイントを紹介します。一定の参考値があるので、困っている友達が参考になれば幸いです。

Redisを利用する際に注意すべき21のポイント(まとめ)

1. Redis の使用仕様

1.1. 主な仕様 重要なポイント

Redis キーを設計するときは、次の点に注意する必要があります。

  • キーの先頭にビジネス名を付け、コロン キーの競合がカバーされないようにするために区切ります。たとえば、 live:rank:1
  • キーのセマンティクスを明確にするために、キーの長さは 30 文字程度に短くする必要があります。
  • key には、スペース、改行、一重引用符、二重引用符、その他のエスケープ文字などの特殊文字を含めることは禁止されています。
  • 未使用のキーを時間内にクリアまたは削除できるように、Redis キーはできる限り ttl に設定する必要があります。

1.2. 値指定のポイント

Redis の値は任意に設定できません。

最初のポイント は、大量の bigKey が保存されている場合に問題が発生し、クエリの遅延やメモリの過度の増加などにつながります。

  • String 型の場合、単一値のサイズは 10k 以内に制御する必要があります。
  • ハッシュ、リスト、セット、または zset タイプの場合、要素の数は通常 5000 を超えません。

2 番目のポイントは、適切なデータ型を選択することです。多くの友人は Redis の String 型のみを使用し、主に set と get を使用します。実際、Redis は 豊富なデータ構造タイプ を提供しており、一部のビジネス シナリオでは、hash、zset およびその他のデータ結果により適しています。 [関連する推奨事項: Redis ビデオ チュートリアル ]

Redisを利用する際に注意すべき21のポイント(まとめ)

反例:

set user:666:name jay
set user:666:age 18
ログイン後にコピー

肯定的な例

hmset user:666 name jay age 18
ログイン後にコピー

#1.3. キーの有効期限を設定し、さまざまなビジネスのキーに注意して、有効期限をできるだけ分散するようにします<span style="font-size: 18px;"></span>

Redis データはメモリに保存され、メモリ リソースは非常に貴重であるためです。
  • 通常、Redis をキャッシュとして使用しますが、
  • はデータベースではないので、キーのライフサイクルはそれほど長くならないはずです。
  • したがって、通常は、expire を使用してキーの有効期限を設定することをお勧めします。
  • 多数のキーが特定の時点で期限切れになる場合、Redis は期限切れの時点でスタックするか、キャッシュ雪崩
  • が発生する可能性があるため、通常は異なります。ビジネス キーの有効期限は分散する必要があります。同じビジネスを行っている場合は、有効期限を分散するために時間にランダムな値を追加することもできます。

1.4. 効率を向上させるためにバッチ操作を使用することをお勧めします

SQL を毎日作成するとき、バッチ操作が50 回ループして毎回 1 つの項目を更新するよりも、50 個の項目を更新する方が効率的です。実際、同じ原則が Redis 操作コマンドにも当てはまります。 Redis クライアントによるコマンドの実行は、4 つのプロセスに分割できます: 1. コマンドの送信 -> 2. コマンドのキューイング -> 3. コマンドの実行 -> 4. 結果を返す。 1と4はRRT(コマンド実行ラウンドトリップタイム)と呼ばれます。 Redis は、RRT を効果的に節約できる、mget、mset

などの

バッチ操作コマンドを提供します。ただし、ほとんどのコマンドは hgetall などのバッチ操作をサポートしておらず、mhgetall は存在しません。

パイプライン

はこの問題を解決できます。 #パイプラインとは? Redis コマンドのセットを組み立て、RTT 経由で Redis に送信し、この Redis コマンドのセットの実行結果を順番にクライアントに返すことができます。 まず、Pipeline を使用せずに n コマンドを実行するモデルを見てみましょう:

Pipeline を使用して n コマンドを実行すると、プロセス全体で 1 RTT が必要になります。モデルは次のとおりです。

Redisを利用する際に注意すべき21のポイント(まとめ)

2. Redis の落とし穴のあるコマンドRedisを利用する際に注意すべき21のポイント(まとめ)

2.1. との使用注意

O(n)<span style="font-size: 18px;"></span> 複雑なコマンド (<span style="font-size: 18px;"></span>hgetall<span style="font-size: 18px;"></span>、## など) <span style="font-size: 18px;"></span>#smember<span style="font-size: 18px;"></span>,<span style="font-size: 18px;"></span>lrange<span style="font-size: 18px;"></span>etc<span style="font-size: 18px;"></span> Redis は単一スレッドでコマンドを実行するためです。 hgetall や smember などのコマンドの時間計算量は O(n) であり、n が増加し続けると、Redis CPU の負荷が増大し続け、他のコマンドの実行がブロックされます。

hgetall、smember,lrange等这些命令不是一定不能使用,需要综合评估数据量,明确n的值,再去决定。 比如hgetall,如果哈希元素n比较多的话,可以优先考虑使用hscan

2.2 慎用Redis的monitor命令

Redis Monitor 命令用于实时打印出Redis服务器接收到的命令,如果我们想知道客户端对redis服务端做了哪些命令操作,就可以用Monitor 命令查看,但是它一般调试用而已,尽量不要在生产上用!因为monitor命令可能导致redis的内存持续飙升。

monitor的模型是酱紫的,它会将所有在Redis服务器执行的命令进行输出,一般来讲Redis服务器的QPS是很高的,也就是如果执行了monitor命令,Redis服务器在Monitor这个客户端的输出缓冲区又会有大量“存货”,也就占用了大量Redis内存。

Redisを利用する際に注意すべき21のポイント(まとめ)

2.3、生产环境不能使用 keys指令

Redis Keys 命令用于查找所有符合给定模式pattern的key。如果想查看Redis 某类型的key有多少个,不少小伙伴想到用keys命令,如下:

keys key前缀*
ログイン後にコピー

但是,redis的keys是遍历匹配的,复杂度是O(n),数据库数据越多就越慢。我们知道,redis是单线程的,如果数据比较多的话,keys指令就会导致redis线程阻塞,线上服务也会停顿了,直到指令执行完,服务才会恢复。因此,一般在生产环境,不要使用keys指令。官方文档也有声明:

Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using sets.

其实,可以使用scan指令,它同keys命令一样提供模式匹配功能。它的复杂度也是 O(n),但是它通过游标分步进行,不会阻塞redis线程;但是会有一定的重复概率,需要在客户端做一次去重

scan支持增量式迭代命令,增量式迭代命令也是有缺点的:举个例子, 使用 SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于 SCAN 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。

2.4 禁止使用flushall、flushdb

  • Flushall 命令用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
  • Flushdb 命令用于清空当前数据库中的所有 key。

这两命令是原子性的,不会终止执行。一旦开始执行,不会执行失败的。

2.5 注意使用del命令

删除key你一般使用什么命令?是直接del?如果删除一个key,直接使用del命令当然没问题。但是,你想过del的时间复杂度是多少嘛?我们分情况探讨一下:

  • 如果删除一个String类型的key,时间复杂度就是O(1)可以直接del
  • 如果删除一个List/Hash/Set/ZSet类型时,它的复杂度是O(n), n表示元素个数。

因此,如果你删除一个List/Hash/Set/ZSet类型的key时,元素越多,就越慢。当n很大时,要尤其注意,会阻塞主线程的。那么,如果不用del,我们应该怎么删除呢?

  • 如果是List类型,你可以执行lpop或者rpop,直到所有元素删除完成。
  • 如果是Hash/Set/ZSet类型,你可以先执行hscan/sscan/scan查询,再执行hdel/srem/zrem依次删除每个元素。

2.6 避免使用SORT、SINTER等复杂度过高的命令。

执行复杂度较高的命令,会消耗更多的 CPU 资源,会阻塞主线程。所以你要避免执行如SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE等聚合命令,一般建议把它放到客户端来执行。

3、项目实战避坑操作

3.1 分布式锁使用的注意点

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁。我们经常使用Redis作为分布式锁,主要有这些注意点:

3.1.1 两个命令SETNX + EXPIRE分开写(典型错误实现范例)

if(jedis.setnx(key_resource_id,lock_value) == 1){ //加锁
    expire(key_resource_id,100); //设置过期时间
    try {
        do something  //业务请求
    }catch(){
  }
  finally {
       jedis.del(key_resource_id); //释放锁
    }
}
ログイン後にコピー

如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,别的线程永远获取不到锁啦,所以一般分布式锁不能这么实现。

3.1.2 SETNX + value值是过期时间 (有些小伙伴是这么实现,有坑)

long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间
String expiresStr = String.valueOf(expires);

// 如果当前锁不存在,返回加锁成功
if (jedis.setnx(key_resource_id, expiresStr) == 1) {
        return true;
} 
// 如果锁已经存在,获取锁的过期时间
String currentValueStr = jedis.get(key_resource_id);

// 如果获取到的过期时间,小于系统当前时间,表示已经过期
if (currentValueStr != null && Long.parseLong(currentValueStr) <p>这种方案的<strong>缺点</strong>:</p><blockquote><ul>
<li>过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步</li>
<li>没有保存持有者的唯一标识,可能被别的客户端释放/解锁。</li>
<li>锁过期的时候,并发多个客户端同时请求过来,都执行了<code>jedis.getSet()</code>,最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。</li>
</ul></blockquote><p><strong>3.1.3: SET的扩展命令(SET EX PX NX)(注意可能存在的问题)</strong></p><pre class="brush:php;toolbar:false">if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加锁
    try {
        do something  //业务处理
    }catch(){
  }
  finally {
       jedis.del(key_resource_id); //释放锁
    }
}
ログイン後にコピー

这个方案还是可能存在问题:

  • 锁过期释放了,业务还没执行完。
  • 锁被别的线程误删。

3.1.4 SET EX PX NX + 校验唯一随机值,再删除(解决了误删问题,还是存在锁过期,业务没执行完的问题)

if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加锁
    try {
        do something  //业务处理
    }catch(){
  }
  finally {
       //判断是不是当前线程加的锁,是才释放
       if (uni_request_id.equals(jedis.get(key_resource_id))) {
        jedis.del(lockKey); //释放锁
        }
    }
}
ログイン後にコピー

在这里,判断是不是当前线程加的锁和释放锁不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁。

Redisを利用する際に注意すべき21のポイント(まとめ)

一般也是用lua脚本代替。lua脚本如下:

if redis.call('get',KEYS[1]) == ARGV[1] then 
   return redis.call('del',KEYS[1]) 
else
   return 0
end;
ログイン後にコピー

3.1.5 Redisson框架 + Redlock算法 解决锁过期释放,业务没执行完问题+单机问题

Redisson 使用了一个Watch dog解决了锁过期释放,业务没执行完问题,Redisson原理图如下:

Redisを利用する際に注意すべき21のポイント(まとめ)

以上的分布式锁,还存在单机问题: 

Redisを利用する際に注意すべき21のポイント(まとめ)

如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。

针对单机问题,可以使用Redlock算法。有兴趣的朋友可以看下我这篇文章哈,七种方案!探讨Redis分布式锁的正确使用姿势

3.2 缓存一致性注意点

  • 如果是读请求,先读缓存,后读数据库
  • 如果写请求,先更新数据库,再写缓存
  • 每次更新数据后,需要清除缓存
  • 缓存一般都需要设置一定的过期失效
  • 一致性要求高的话,可以使用biglog+MQ保证。

有兴趣的朋友,可以看下我这篇文章哈:并发环境下,先操作数据库还是先操作缓存?

3.3 合理评估Redis容量,避免由于频繁set覆盖,导致之前设置的过期时间无效。

我们知道,Redis的所有数据结构类型,都是可以设置过期时间的。假设一个字符串,已经设置了过期时间,你再去重新设置它,就会导致之前的过期时间无效。

Redisを利用する際に注意すべき21のポイント(まとめ)

Redis setKey源码如下:

void setKey(redisDb *db,robj *key,robj *val) {
    if(lookupKeyWrite(db,key)==NULL) {
       dbAdd(db,key,val);
    }else{
    dbOverwrite(db,key,val);
    }
    incrRefCount(val);
    removeExpire(db,key); //去掉过期时间
    signalModifiedKey(db,key);
}
ログイン後にコピー

实际业务开发中,同时我们要合理评估Redis的容量,避免频繁set覆盖,导致设置了过期时间的key失效。新手小白容易犯这个错误。

3.4 缓存穿透问题

先来看一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。

Redisを利用する際に注意すべき21のポイント(まとめ)

キャッシュペネトレーション: 存在してはいけないデータのクエリを指します。キャッシュがヒットしないため、データベースからクエリする必要があります。データが見つからない場合、データは書き込まれません。データが要求されるたびにデータベースにクエリを実行する必要があるため、データベースに負担がかかります。

平たく言えば、読み取りリクエストがアクセスされるとき、キャッシュにもデータベースにも特定の値がないため、この値に対する各クエリ リクエストがデータベースに侵入します。これがキャッシュの侵入です。

キャッシュの侵入は通常、次の状況によって発生します:

  • 不合理なビジネス設計、たとえば、ほとんどのユーザーは警戒をしていませんが、すべてのリクエストが作成したクエリはキャッシュに移動し、特定のユーザー ID クエリが保護されているかどうかを確認します。
  • ビジネス/運用/メンテナンス/開発エラー (キャッシュやデータベース データが誤って削除されるなど)。
  • ハッカーによる違法なリクエスト攻撃 たとえば、ハッカーは、存在しないビジネス データを読み取るための大量の違法なリクエストを意図的にでっち上げます。

キャッシュの侵入を回避するにはどうすればよいですか? 一般に 3 つの方法があります。

  • 不正なリクエストの場合は、API 入口でパラメータを検証し、不正な値を除外します。
  • クエリ データベースが空の場合は、キャッシュに null 値またはデフォルト値を設定できます。ただし、書き込み要求が来た場合は、キャッシュの整合性を確保するためにキャッシュを更新する必要があり、同時に、最終的にキャッシュに適切な有効期限が設定されます。 (ビジネスでよく使用され、シンプルで効果的です)
  • ブルーム フィルターを使用して、データが存在するかどうかをすばやく判断します。つまり、クエリリクエストが来ると、まずブルームフィルターを通して値が存在するかどうかを判断し、その後も存在するかどうかを確認し続けます。
ブルーム フィルターの原理: 初期値 0 のビットマップ配列と N 個のハッシュ関数で構成されます。キーに対して N 個のハッシュ アルゴリズムを実行して N 個の値を取得します。これらの N 個の値をビット配列でハッシュし、1 に設定します。次に、チェック時に、これらの特定の位置がすべて 1 であれば、ブルーム フィルター処理が行われます。サーバーはキーが存在すると判断します。 。

3.5 キャッシュ スノー ランの問題

キャッシュ スノー ラン: は、大量のデータ バッチの有効期限を指します。クエリ データの量は膨大であり、すべてのリクエストはデータベースに直接アクセスするため、データベースに過剰な負荷がかかり、ダウンタイムさえも発生します。

  • キャッシュスノースノーは一般に、大量のデータが同時に期限切れになることで発生するため、有効期限を均等に設定する、つまり有効期限を比較的離散的に設定することで解決できます。 。たとえば、より大きな固定値とより小さなランダム値 (5 時間、0 ~ 1800 秒) を使用します。
  • Redis の障害により、キャッシュ スノー雪が発生する可能性もあります。これには、Redis 高可用性クラスターを構築する必要があります。

3.6 キャッシュの内訳の問題

キャッシュの内訳: は、ある時点のホットスポット キーを指します。有効期限が切れると、この時点でこのキーに対して多数の同時リクエストがあり、大量のリクエストがデータベースにヒットします。

キャッシュのブレークダウンは少し似ています。実際、それらの違いは、キャッシュの降雪は、データベースに過度の圧力がかかっているか、さらにはダウンしていることを意味することです。キャッシュのブレークダウンは、単に DB データベースへの同時リクエストの数が多いだけです。レベル。ブレークダウンはキャッシュ スノーランのサブセットであると考えることができます。一部の記事では、この 2 つの違いは、ブレークダウンが特定のホット キー キャッシュを対象としているのに対し、Xuebeng は多くのキーを対象としている点であると考えられています。

解決策は 2 つあります:

  • 1. ミューテックス ロック スキームを使用します。キャッシュが失敗した場合、データベース データをすぐにロードするのではなく、まず (Redis の setnx) など、成功した場合のアトミック操作コマンドを使用して操作し、成功したら db データベース データをロードしてキャッシュを設定します。それ以外の場合は、キャッシュを再度取得してみてください。
  • 2.「期限切れにならない」 は、有効期限が設定されていないが、ホットスポット データの有効期限が近づくと、非同期スレッドが更新されて有効期限を設定することを意味します。

3.7. キャッシュ ホット キーの問題

Redis では、アクセス頻度の高いキーをホット キーと呼びます。特定のホットスポット キーのリクエストがサーバー ホストに送信されると、リクエスト量が特に多いため、ホスト リソースが不足したり、ダウンタイムが発生したりして、通常のサービスに影響を与える可能性があります。

ホットスポット キーはどのように生成されますか?主な理由は 2 つあります。

  • ユーザーが消費するデータは、速報セール、話題のニュース、その他のシナリオなど、より多くの読み取りとより少ない書き込みが必要なシナリオなど、生成されるデータよりもはるかに大きいためです。
  • リクエストシャーディングが集中し単一のRediサーバーの性能を超え、例えば固定名のキーとハッシュが同一サーバーに落ちてしまうとインスタントアクセス量が膨大になりマシンのボトルネックを超えてしまい、ホットキーの問題を引き起こします。

では、日々の開発においてホットキーを特定するにはどうすればよいでしょうか?

  • どのホット キーがエクスペリエンスに基づいているかを決定する;
  • クライアント統計をレポートする;
  • サービス エージェント層にレポートする

ホットキーの問題を解決するにはどうすればよいですか?

  • Redis クラスターの拡張: シャード コピーを追加して読み取りトラフィックのバランスをとる;
  • キーを key1、key2...keyN としてバックアップするなど、同じホット キーをハッシュします。データの N 個のバックアップ、および N 個のバックアップは異なるシャードに分散されます。アクセス中に、N 個のバックアップの 1 つにランダムにアクセスして、読み取りトラフィックをさらに共有できます。
  • 2 次レベル キャッシュを使用します。つまり、 JVM ローカル キャッシュを使用して、Redis 読み取りリクエストを削減します。

4. Redis の設定と操作

4.1 短い接続ではなく長い接続を使用する、クライアントの接続プールを適切に構成します

  • 短い接続を使用する場合は、毎回 TCP スリーウェイ ハンドシェイクと 4 つのウェーブを実行する必要があるため、時間が増加します。消費。ただし、長時間の接続の場合は、一度接続を確立すれば、redis コマンドは永久に使用できるため、Jiangzi を使用すると、redis 接続の確立にかかる時間を短縮できます。
  • 接続プールは、クライアント上で複数の接続を解放せずに確立できるため、接続が必要なときに毎回接続を作成する必要がなく、時間を節約できます。ただし、パラメータを適切に設定する必要があり、Redis を長時間動作させない場合には、適切なタイミングで接続リソースを解放する必要があります。

4.2 db0 のみを使用する

Redis スタンドアロン アーキテクチャでは、db0 以外の使用が禁止されています。理由は 2 つあります。

    接続の場合、Redis はコマンド select 0 と select 1 を実行して切り替えるため、新しいエネルギーが消費されます。
  • Redis クラスターは db0 のみをサポートします。移行する場合はコストが高くなります。

4.3 maxmemory の適切な削除戦略を設定します。 <span style="font-size: 18px;"></span>

メモリ バックログの拡大を防ぐため。たとえば、業務量が増加すると、Redis キーが頻繁に使用され、単純にメモリが不足し、運用保守担当者がメモリの増設を忘れる場合があります。 Redis はこのままハングアップしてしまうのでしょうか?したがって、maxmemory-policy (最大メモリ削除ポリシー) を選択し、実際の業務に基づいて有効期限を設定する必要があります。合計 8 つのメモリ削除戦略があります。

    volatile-lru: メモリが新しく書き込まれたデータを収容するのに十分でない場合、LRU (最も最近使用されていない) アルゴリズムを使用してキーを削除します。有効期限が設定されています。;
  • allkeys-lru: メモリが新しく書き込まれたデータを収容するのに十分でない場合、LRU (最も最近使用されていない) アルゴリズムを使用してすべてのキーが削除されます。
  • volatile-lfu: バージョン 4.0 で新たに追加され、メモリが新しく書き込まれたデータを収容するのに不十分な場合、LFU アルゴリズムを使用して期限切れのキーの中からキーを削除します。
  • allkeys-lfu: バージョン 4.0 で新たに追加され、メモリが新しく書き込まれたデータを収容するのに十分でない場合、LFU アルゴリズムを使用してすべてのキーが削除されます;
  • volatile-random:メモリが不足している場合 新しく書き込まれたデータを収容する場合、有効期限を設定してキーからデータをランダムに削除します。
  • allkeys-random: メモリが新しく書き込まれたデータを収容するのに不十分な場合、データはすべてのキーからランダムに削除されます。
  • volatile-ttl: メモリが新しく書き込まれたデータを収容するのに十分ではない場合、有効期限が設定されているキーは有効期限に従って削除され、有効期限の早いキーが最初に削除されます。
  • noeviction: デフォルト ポリシー。メモリが新しく書き込まれたデータを収容するのに十分でない場合、新しい書き込み操作はエラーを報告します。

4.4 レイジーフリー メカニズムをオンにする<span style="font-size: 18px;"></span>

Redis に bigKey がまだある場合、Redis4.0 バージョンはレイジーフリー メカニズムをサポートします。このようなものが存在するため、lazy-free をオンにすることをお勧めします。これをオンにすると、Redis が bigkey を削除すると、時間のかかるメモリ解放操作がバックグラウンド スレッドで実行され、メイン スレッドへのブロックの影響が軽減されます。

Redisを利用する際に注意すべき21のポイント(まとめ)

プログラミング関連の知識について詳しくは、

プログラミング ビデオをご覧ください。 !

以上がRedisを利用する際に注意すべき21のポイント(まとめ)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート