一、导语
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
(学习视频分享:redis视频教程)
由于其上手快,执行效率高,拥有多种数据结构,支持持久化以及集群等功能和特点被众多互联网公司所使用。但是,如果使用和操作不当,会引起内存浪费,甚至系统宕机等严重后果。
二、要点分析
2.1 使用正确的数据类型
在 Redis 5 种数据类型中,string 类型最为常用,也最为简单。但是,能解决问题不代表使用了正确的数据类型。
例如,将一个用户(name,age,city)信息保存到 Redis 中,下边有三种方案:
方案1:使用 string 类型,每个属性当作一个 key
set user:1:name laowang set user:1:age 40 set user:1:city shanghai
优点:简单直观,每个属性支持更新操作
缺点:使用过多的 key,占用的内存较大,同时用户信息的聚合性较差,管理和维护麻烦
方案2:使用 string 类型,将用户信息序列化成字符串保存
// 序列化用户信息 String userInfo = serialize(user) set user:1 userInfo
优点:简化存储步骤
缺点:序列化和反序列化存在一定开销
方案3:使用 hash 类型,每个属性使用一对 field-value,但只用一个 key
hmset user:1 name laowang age 40 city shanghai
优点:简单直观,合理使用可以减少内存空间
总结:尽量减少 Redis 中的 key。
2.2 警惕 Big Key
big key 一般指的是字符串类型 value 值非常大(大于10KB),或哈希、列表、集合、有序集合元素个数多(大于5000个)的 key。
big key 会对 Redis 造成很多负面影响:
内存不均:在集群环境下,big key 被分配到某个节点机器中,由于不知道被分配到哪个节点上且该节点内存占用大,不利于集群环境下内存的统一管理
超时阻塞:由于 Redis 是单线程操作,操作 big key 比较耗时,容易造成阻塞
过期删除:big key 不单读写慢,删除也慢,删除过期 big key 也比较耗时
迁移困难:由于数据庞大,备份和还原也容易造成阻塞,操作失败
知道了 big key 的危害,我们如何判断和查询 big key 呢?其实,redis-cli 提供了 --bigkeys 参数,键入 redis-cli --bigkeys 即可查询出 big key 。
找到 big key 后,我们一般会将 big key 拆分成多个小 key 进行存储。这种做法似乎与 2.1 的总结相矛盾,但任何方案都有优缺点,衡量利弊取决于实际情况。
总结:尽量减少 Redis 中的 big key。
补充:如果想查看某个 key 所占用的内存空间,可以使用 memory usage 命令。注意:该命令是 Redis 4.0+ 才开始提供的,如想使用必须将 Redis 升级至 4.0+。
2.3 内存消耗
即便我们合适使用正确的数据类型保存数据,将 Big Key 拆分小 key,还是会出现内存消耗问题,那么 Redis 内存消耗是如何产生的呢?一般由以下 3 种情况产生:
业务不断发展,存储的数据不断增多(不可避免)
无效/过期的数据没有及时处理(可优化)
没有对冷数据进行降级(可优化)
在优化情况 2 之前,我们得先知道为什么会出现没有及时处理过期数据的问题,这得说到 Redis 提供的 3 种过期删除策略:
定时删除:对于每个设置了过期时间的 key 都会创建一个定时器,一旦达到过期时间就立即删除
惰性删除:当访问一个 key 时,才判断该 key 是否已过期,如过期就删除
定期删除:每隔一段时间扫描 Redis 中过期 key 的字典,并清除部分过期的 key
由于定时删除需要创建定时器,会占用的大量内存,同时精准删除大量 key 也会消耗大量 CPU 资源,因此 Redis 同时采用的是惰性删除和定时删除两种策略。如果客户端没有请求过期的 key 或定期删除线程没有扫描到并清除这个 key,该 key 就会一直占用着内存,导致内存浪费。
知道了内存消耗的原因后,我们可以很快地想出优化方案:手动删除。
当使用完缓存后,缓存即使设置了过期时间,我们也要手动调用 del 方法/命令删除。如果不能当场删除,我们也可在代码中开启定时器定期删除这些过期的 key,相比较 Redis 的两种删除策略,手动清除数据要及时很多。
情况 3 的问题不算大,针对其优化的手段,我们可以调整 Redis 的淘汰策略。
2.4 多命令的执行
Redis は、1 つのリクエストと 1 つの応答に基づく同期リクエスト サービスです。つまり、複数のクライアントが Redis サーバーにコマンドを送信する場合、Redis サーバーはいずれかのクライアントのコマンドを受信して処理することしかできず、他のクライアントは Redis サーバーが現在のコマンドを処理して応答するまで待機してから続行することしかできません。他のコマンド要求を処理します。
Redis は、コマンドの受信、コマンドの処理、結果の返しという 3 つのプロセスでコマンドを処理します。処理されるデータはすべてメモリ内にあるため、処理時間は通常ナノ秒レベルであり、非常に高速です (大きなキーを除く)。したがって、時間がかかる状況のほとんどは、コマンドを受け入れて結果を返すときに発生します。クライアントが Redis サーバーに複数のコマンドを送信する場合、1 つのコマンドの処理に時間がかかると、他のコマンドは待機することしかできなくなり、全体のパフォーマンスに影響します。
この種の問題を解決するために、Redis はパイプラインを提供します。クライアントは複数のコマンドをパイプラインに入れ、パイプライン コマンドを Redis サーバーに送信して一度に処理できます。処理を行うと、結果が一度にクライアントに返されます。この処理により、クライアントと Redis サーバー間の対話の数が減り、それによってラウンドトリップ時間が短縮され、パフォーマンスが向上します。
追加:
Redis パイプラインとネイティブ バッチ コマンドの比較:
ネイティブ バッチ コマンドはアトミックですが、パイプラインは非アトミックです
ネイティブ バッチ コマンド1 回のみ使用可能 1 つのコマンドを実行でき、パイプラインは複数のコマンドの実行をサポートします
ネイティブ バッチ コマンドはサーバー側で実装され、パイプラインにはサーバーとクライアントの実装が必要です
注意事項Redis パイプラインを使用する:
パイプラインを使用してロードされるコマンドの数が多すぎることはできません
パイプライン内のコマンドはバッファリングの順序で実行されますが、コマンドによって送信されたコマンドが散在する可能性があります。他のクライアント、つまりタイミングは保証されません
パイプライン内の命令の実行中に例外が発生した場合、後続の命令は引き続き実行されます。つまり、アトミック性は保証されません
2.5 キャッシュの浸透
プロジェクトでキャッシュを使用する。通常の設計アイデアは次のとおりです。
データをクエリするリクエストを送信します。クエリ ルールでは、最初にキャッシュを確認し、キャッシュにデータがない場合は、再度データベースにクエリを実行し、見つかったデータをキャッシュに格納し、最後にデータをクライアントに返します。要求されたデータが存在しない場合、最終的に各要求はデータベースに要求されます (キャッシュの侵入)。
キャッシュの侵入は大きなセキュリティ リスクを引き起こします。誰かがツールを使用して存在しないデータに対して大量のリクエストを送信すると、大量のリクエストがデータベースに流れ込み、データベースへの負荷が増大し、ダウンタイムはアプリケーション全体の通常の動作に影響を与え、システムの麻痺を引き起こす可能性があります。
この種の問題を解決するには、データベースへのアクセスを減らすことに重点が置かれており、通常は次の解決策があります:
キャッシュの予熱: システムがオンラインでリリースされた後、関連するデータが直接保存されます。
システムにデフォルト値を設定する: リクエストがデータベースに到達し、データベースがデータを見つけられない場合は、キャッシュ キーのデフォルト値を設定し、それを注: このデフォルト値は無意味であるため、メモリ使用量を減らすために有効期限を設定する必要があります
ブルーム フィルター: 考えられるすべてのデータを十分な大きさのビットマップにハッシュし、存在しないデータは確実にビットマップによってインターセプトされます
2.6 キャッシュなだれ
キャッシュなだれ: 簡単に言うと、キャッシュされたデータにアクセスするがクエリできない、そしてその後リクエストが大量に発生することを指します。その結果、データベースの負荷が増大し、パフォーマンスが低下し、耐えられないほどの高負荷のダウンタイムが発生し、システム全体の通常の動作に影響を与えたり、場合によってはシステムが麻痺したりすることがあります。
たとえば、完全なシステムは、システム A、システム B、およびシステム C の 3 つのサブシステムで構成されており、それらのデータ要求チェーンは、システム A -> システム B -> システム C -> データベースです。キャッシュにデータがなく、データベースがダウンしている場合、システム C はデータを照会して応答することができず、再試行待機段階になることしかできないため、システム B とシステム A に影響します。雪山を吹き抜ける突風が雪崩を引き起こすのと同じように、1 つのノードの異常が一連の問題を引き起こします。
これを見て、キャッシュペネトレーションとキャッシュアバランシェの違いは何だろうと疑問に思う読者もいるかもしれません。
キャッシュペネトレーションは、キャッシュを介してデータベースを直接リクエストするのと同じように、リクエストされたデータがキャッシュにない場合にデータベースをリクエストすることに重点を置いています。
キャッシュ アバランチは大規模なリクエストに焦点を当てます。キャッシュ内のデータはクエリできないため、データベースにアクセスするとデータベースへの負荷が増大し、一連の例外が発生します。
キャッシュなだれの問題を解決するには、まず問題の原因を知る必要があります:
Redis 自体に問題があります
無効なホットスポット データ セット
理由 1 の場合、マスター/スレーブ、クラスターになり、すべてのリクエストでキャッシュ内のデータを検索し、データベースへのアクセスを減らすことができます。
理由 2 の場合、キャッシュの有効期限を変更する場合は、集中キャッシュの障害を避けるために有効期限をずらします (たとえば、基本時間にランダムな値を加算または減算します)。同時に、ローカル キャッシュ (ehcache など) を設定して、インターフェイスのフローを制限したり、サービスをダウングレードしたりすることもできます。これにより、データベースへのアクセス圧力も軽減できます。
3.参考資料
関連する推奨事項: redis データベース チュートリアル
以上がRedis のキーポイント分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。