Redis とローカル キャッシュを使用した高同時実行技術の共有
この記事では、Redis に関する関連知識を提供します。主に、キャッシュ タイプの紹介、さまざまな使用シナリオ、使用方法など、分散キャッシュとローカル キャッシュの使用スキルを紹介し、最後に実践的な事例を挙げますので、ぜひ参考にしてみてください。
推奨される学習: Redis ビデオ チュートリアル
ご存知のとおり、キャッシュの主な目的は、アクセスを高速化し、アクセスを軽減することです。データベースのプレッシャー。最も一般的に使用されるキャッシュは、redis などの分散キャッシュです。ほとんどの同時実行シナリオや、一部の中小企業のトラフィックがそれほど高くない状況に直面した場合、基本的には redis で問題を解決できます。ただし、トラフィックが多い場合は、guava の LoadingCache や Kuaishou のオープン ソース ReloadableCache などのローカル キャッシュを使用する必要がある場合があります。
3 つのキャッシュ使用シナリオ
このパートでは、guava の LoadingCache や Kuaishou のオープン ソース ReloadableCache などの Redis の使用シナリオと制限事項を紹介します。ビジネス シナリオでどのキャッシュを使用する必要があるか、またその理由を説明します。
Redis の使用シナリオと制限
Redis をいつ使用するかについて大まかに話すと、ユーザーのアクセス数が多すぎる場所で自然に使用され、それによってアクセスとアクセスが高速化されます。データベースの負担を軽減します。細分化すると、単一ノードの問題と非単一ノードの問題に分けることができます。
ページへのユーザーのアクセス数が多いにもかかわらず、ユーザーが同じリソースにアクセスしていない場合。たとえば、ユーザーの詳細ページはアクセス数が比較的多いですが、ユーザーごとにデータが異なります。この場合、分散キャッシュしか使用できないことは明らかです。Redis を使用する場合、キーはユーザー固有のキーになります。キー、値はユーザー情報です。
redis によるキャッシュの故障。
ただし、注意すべき点は、有効期限を設定する必要があり、同じ時点で有効期限が切れるように設定することはできないことです。たとえば、ユーザーがアクティビティ ページを持っている場合、そのアクティビティ ページでは、アクティビティ中にユーザーの賞のデータが表示されます。不注意な人は、ユーザー データの有効期限をアクティビティの終了時点に設定してしまう可能性があります。
# (ホット) ポイントの問題
単一ノードの問題は、同じキーが Redis の同じノードに存在するため、Redis の単一ノードの同時実行性の問題を指します。 Redis クラスターなので、キーが次の場合、訪問数が多すぎる場合、この Redis ノードには同時実行のリスクがあり、このキーはホット キーと呼ばれます。 すべてのユーザーが同じリソースにアクセスする場合、たとえば、Xiao Ai アプリのホームページが (最初は) すべてのユーザーに同じコンテンツを表示し、サーバーが同じ大きな JSON を h5 に返す場合、明らかに次のようにする必要があります。キャッシュに使用されます。まずはredisの利用が可能か検討しますが、redisにはシングルポイントの問題があるため、トラフィックが大きすぎるとユーザーリクエストがすべてredisの同じノードに到達してしまい、そのノードが耐えられるかを評価する必要があります。こんなに大きな流れ。私たちのルールは、単一ノードの QPS が 1,000 レベルに達した場合、単一点問題を解決する必要があるというものです (たとえ Redis が 10 万レベルの QPS に耐えられると主張しているとしても)。最も一般的な方法はローカル キャッシュを使用することです。 。明らかに、Xiaoai アプリのホームページのトラフィックは 100 未満であるため、redis を使用しても問題ありません。 LoadingCache の使用シナリオと制限事項上記のホット キーの問題に対して、最も直接的なアプローチは、最もよく知られている guava の LoadingCache などのローカル キャッシュを使用することです。ローカル キャッシュを使用するホームページを更新してもローカル キャッシュは更新されないため、キャッシュは一定量のダーティ データを受け入れることができる必要があります。特定の有効期限ポリシーに従ってキャッシュをリロードするだけですが、一度ホームページがバックグラウンドにプッシュされると、再度変更されることはないため、このシナリオはまったく問題ありません。変更されても問題ありません。書き込み有効期限を 30 分に設定し、30 分後にキャッシュをリロードすることもできます。このような短期間でダーティ データを受け入れることができます。LoadingCache によるキャッシュの故障
ローカル キャッシュはマシンに強く関連していますが、コード レベルは 30 分で期限切れになるように書かれています。起動時間が異なると、キャッシュの読み込み時間や有効期限も異なるため、キャッシュの有効期限が同時に切れると、マシン上のすべてのリクエストがデータベースをリクエストすることはなくなります。ただし、キャッシュの侵入は 1 台のマシンでも発生するため、それぞれ 1,000 qps のマシンが 10 台ある場合、1 つのキャッシュが期限切れになると、これらの 1,000 リクエストが同時にデータベースにヒットする可能性があります。この種の問題は、実際には解決するのが簡単ですが、無視されやすいため、LoadingCache を設定する際には、cache.getIfPresent()== null を直接判定してから、 db; 前者は仮想マシンを追加します レイヤ ロックにより、データベースに送信されるリクエストは 1 つだけになるため、この問題は完全に解決されます。 ただし、一定期間にわたる頻繁なアクティビティなど、高いリアルタイム要件がある場合は、アクティビティ ページをほぼリアルタイム、つまり操作後に更新できるようにしたいと考えています。バックグラウンドでアクティビティ情報を設定するには、設定されたアクティビティ情報を C 側でほぼリアルタイムで表示する必要がありますが、LoadingCache を使用するだけでは明らかに十分ではありません。ReloadableCache の使用シナリオと制限事項
LoadingCache では解決できない上記のリアルタイム問題については、Kuaishou がオープンソース化しているローカル キャッシュ フレームワークである ReloadableCache の使用を検討できます。同時に複数のマシンをサポートしていることです キャッシュを更新します ホームページの情報を変更すると、リクエストがマシン A に到達するとします。このとき、ReloadableCache が再ロードされ、通知が送信されます。 その他同じ zk ノードをリッスンしているマシンは、通知を受信した後にキャッシュを更新します。このキャッシュを使用するための一般的な要件は、全量のデータをローカル キャッシュにロードすることです。そのため、データ量が大きすぎると確実に gc を圧迫するため、この場合は使用できません。 Xiao Ai のホームページにはステータスがあり、通常オンライン ステータスは 2 つだけなので、ReloadableCache を使用してオンライン ステータスのホームページのみを読み込むことができます。
概要
3 種類のキャッシュについては基本的にここで紹介しましたが、概要は次のとおりです:
- ユーザー次元などの非ホットスポット データ アクセスの場合データ、直接 redis を使用してください;
- ホット データ アクセスの場合、トラフィックがそれほど多くない場合は、何も考えずに redis を使用してください;
- ホット データの場合、特定の範囲内でダーティ データが許可されている場合一定の期間、LoadingCache を使用するだけで十分です。
- ホット データの場合、一貫性要件が高く、データ量が大きくない場合は、ReloadableCache を使用します。
ヒント
どのような種類のローカル キャッシュにも、故障の問題を解決するために仮想マシン レベルのロックが備わっていますが、予期せぬ形で事故が発生する可能性は常にあります。念のため、2 レベルのキャッシュ、つまりローカル キャッシュを使用できます。 Redis データベース。
キャッシュの使用法についての簡単な紹介
ここでは Redis の使用法については説明しませんが、多くの人が私よりも API の使用法に精通していると思います
LoadingCache の使い方
これは guava online で提供されていますが、注意点が 2 点あります。
V get(K key, Callable extends V>loader)- ; を使用するか、ビルドを使用する場合は
- build(CacheLoader super K1, V1>loader)
を使用してください。 、 get( ) を使用できます。さらに、getIfPresent==null の場合はデータベースをチェックする代わりに、load-miss を使用することをお勧めします。これにより、キャッシュが破損する可能性があります。
スレッドセーフであるため、load-miss を使用してください。キャッシュが失敗した場合は、複数のスレッドが get を呼び出す場合、1 つのスレッドだけがデータベースにクエリを実行し、他のスレッドは待機する必要があります。これは、スレッドセーフであることを意味します。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(1000L) .expireAfterAccess(Duration.ofHours(1L)) // 多久不访问就过期 .expireAfterWrite(Duration.ofHours(1L)) // 多久这个key没修改就过期 .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // 数据装载方式,一般就是loadDB return key + " world"; } }); String value = cache.get("hello"); // 返回hello world
サードパーティ依存関係のインポート<dependency>
<groupId>com.github.phantomthief</groupId>
<artifactId>zknotify-cache</artifactId>
<version>0.1.22</version>
</dependency>
ログイン後にコピー
ドキュメントを読む必要があります。読まないと機能しません。興味があれば、自分で書いてみることもできます。 <dependency> <groupId>com.github.phantomthief</groupId> <artifactId>zknotify-cache</artifactId> <version>0.1.22</version> </dependency>
public interface ReloadableCache<T> extends Supplier<T> { /** * 获取缓存数据 */ @Override T get(); /** * 通知全局缓存更新 * 注意:如果本地缓存没有初始化,本方法并不会初始化本地缓存并重新加载 * * 如果需要初始化本地缓存,请先调用 {@link ReloadableCache#get()} */ void reload(); /** * 更新本地缓存的本地副本 * 注意:如果本地缓存没有初始化,本方法并不会初始化并刷新本地的缓存 * * 如果需要初始化本地缓存,请先调用 {@link ReloadableCache#get()} */ void reloadLocal(); }
キャッシュの内訳
簡単に言うと、キャッシュが失敗し、多数のリクエストが同時にデータベースにヒットします。キャッシュの故障の問題については、上で多くの解決策が示されています。
#たとえば、ローカル キャッシュを使用しますローカル キャッシュはロードミス方式を使用します- サードパーティ サービスを使用してキャッシュをロードします 1.2 と先ほども言いましたが、主に 3 を見てください。たとえば、企業が Redis を使用するつもりでもローカル キャッシュを使用できない場合は、データ量が多すぎてリアルタイム要件が比較的高いことが考えられます。次に、キャッシュに障害が発生した場合、少数のリクエストのみがデータベースにヒットするようにする方法を見つける必要があります。分散ロックの使用を考えるのは自然であり、理論的には実現可能ですが、実際には隠れた危険性があります。私たちは、多くの人が redis lua を使用して分散ロックを実装し、その間にローテーション トレーニングを実行していると考えています。そのような多数のリクエストとデータにより、redis は隠れた危険となり、多くのビジネスを占有してしまいます。スレッドは実際には、単にそれだけで複雑さを増大させます。分散ロックの導入 私たちの原則は、可能な限り分散ロックを使用しないことです。 それでは、分散ロックに似ているが、より信頼性の高い rpc サービスを設計できるでしょうか? get メソッドを呼び出すと、この rpc サービスは同じキーが同じノードにヒットすることを確認し、synchronized を使用してロックし、データのロードを完了します。 Kuaishou は、cacheSetter と呼ばれるフレームワークを提供します。以下に簡略版を示します。自分で作成することで簡単に実装できます。
import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; /** * @Description 分布式加载缓存的rpc服务,如果部署了多台机器那么调用端最好使用id做一致性hash保证相同id的请求打到同一台机器。 **/ public abstract class AbstractCacheSetterService implements CacheSetterService { private final ConcurrentMap<String, CountDownLatch> loadCache = new ConcurrentHashMap<>(); private final Object lock = new Object(); @Override public void load(Collection<String> needLoadIds) { if (CollectionUtils.isEmpty(needLoadIds)) { return; } CountDownLatch latch; Collection<CountDownLatch> loadingLatchList; synchronized (lock) { loadingLatchList = excludeLoadingIds(needLoadIds); needLoadIds = Collections.unmodifiableCollection(needLoadIds); latch = saveLatch(needLoadIds); } System.out.println("needLoadIds:" + needLoadIds); try { if (CollectionUtils.isNotEmpty(needLoadIds)) { loadCache(needLoadIds); } } finally { release(needLoadIds, latch); block(loadingLatchList); } } /** * 加锁 * @param loadingLatchList 需要加锁的id对应的CountDownLatch */ protected void block(Collection<CountDownLatch> loadingLatchList) { if (CollectionUtils.isEmpty(loadingLatchList)) { return; } System.out.println("block:" + loadingLatchList); loadingLatchList.forEach(l -> { try { l.await(); } catch (InterruptedException e) { e.printStackTrace(); } }); } /** * 释放锁 * @param needLoadIds 需要释放锁的id集合 * @param latch 通过该CountDownLatch来释放锁 */ private void release(Collection<String> needLoadIds, CountDownLatch latch) { if (CollectionUtils.isEmpty(needLoadIds)) { return; } synchronized (lock) { needLoadIds.forEach(id -> loadCache.remove(id)); } if (latch != null) { latch.countDown(); } } /** * 加载缓存,比如根据id从db查询数据,然后设置到redis中 * @param needLoadIds 加载缓存的id集合 */ protected abstract void loadCache(Collection<String> needLoadIds); /** * 对需要加载缓存的id绑定CountDownLatch,后续相同的id请求来了从map中找到CountDownLatch,并且await,直到该线程加载完了缓存 * @param needLoadIds 能够正在去加载缓存的id集合 * @return 公用的CountDownLatch */ protected CountDownLatch saveLatch(Collection<String> needLoadIds) { if (CollectionUtils.isEmpty(needLoadIds)) { return null; } CountDownLatch latch = new CountDownLatch(1); needLoadIds.forEach(loadId -> loadCache.put(loadId, latch)); System.out.println("loadCache:" + loadCache); return latch; } /** * 哪些id正在加载数据,此时持有相同id的线程需要等待 * @param ids 需要加载缓存的id集合 * @return 正在加载的id所对应的CountDownLatch集合 */ private Collection<CountDownLatch> excludeLoadingIds(Collection<String> ids) { List<CountDownLatch> loadingLatchList = Lists.newArrayList(); Iterator<String> iterator = ids.iterator(); while (iterator.hasNext()) { String id = iterator.next(); CountDownLatch latch = loadCache.get(id); if (latch != null) { loadingLatchList.add(latch); iterator.remove(); } } System.out.println("loadingLatchList:" + loadingLatchList); return loadingLatchList; } }
ビジネス実装
import java.util.Collection; public class BizCacheSetterRpcService extends AbstractCacheSetterService { @Override protected void loadCache(Collection<String> needLoadIds) { // 读取db进行处理 // 设置缓存 } }
簡単に言えば、要求されたデータがデータベースに存在しないため、無効なリクエストがデータベースに侵入することになります。 。 解決策も非常に簡単で、db からデータを取得するメソッド (getByKey(K key)) でデフォルト値を指定する必要があります。
たとえば、上限が 1W の賞金プールがあるとします。ユーザーがタスクを完了したら、お金を送り、redis を使用して記録し、テーブルにログインします。ユーザーは次のことを確認できます。タスクページ上で賞金プールの残高をリアルタイムで確認できます タスク開始時点では、賞金プールの金額が変化していないことがわかります redisとdbには発行額の記録がありません毎回 DB をチェックする必要があるため、DB からデータが見つからない場合は値をキャッシュする必要があります。0 を指定するとキャッシュされます。
キャッシュ雪崩
これは、大量のキャッシュ障害がデータベースに発生したことを意味します。もちろん、それらはすべてビジネス キャッシュである必要があります。最終的には、次の問題があります。コードの書き方。キャッシュ無効化の有効期限を分割して、中央で失敗させないようにすることができます。
推奨される学習: Redis ビデオ チュートリアル
以上がRedis とローカル キャッシュを使用した高同時実行技術の共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











Redisクラスターモードは、シャードを介してRedisインスタンスを複数のサーバーに展開し、スケーラビリティと可用性を向上させます。構造の手順は次のとおりです。異なるポートで奇妙なRedisインスタンスを作成します。 3つのセンチネルインスタンスを作成し、Redisインスタンスを監視し、フェールオーバーを監視します。 Sentinel構成ファイルを構成し、Redisインスタンス情報とフェールオーバー設定の監視を追加します。 Redisインスタンス構成ファイルを構成し、クラスターモードを有効にし、クラスター情報ファイルパスを指定します。各Redisインスタンスの情報を含むnodes.confファイルを作成します。クラスターを起動し、CREATEコマンドを実行してクラスターを作成し、レプリカの数を指定します。クラスターにログインしてクラスター情報コマンドを実行して、クラスターステータスを確認します。作る

Redisデータをクリアする方法:Flushallコマンドを使用して、すべての重要な値をクリアします。 FlushDBコマンドを使用して、現在選択されているデータベースのキー値をクリアします。 [選択]を使用してデータベースを切り替え、FlushDBを使用して複数のデータベースをクリアします。 DELコマンドを使用して、特定のキーを削除します。 Redis-CLIツールを使用してデータをクリアします。

Redisのキューを読むには、キュー名を取得し、LPOPコマンドを使用して要素を読み、空のキューを処理する必要があります。特定の手順は次のとおりです。キュー名を取得します:「キュー:キュー」などの「キュー:」のプレフィックスで名前を付けます。 LPOPコマンドを使用します。キューのヘッドから要素を排出し、LPOP Queue:My-Queueなどの値を返します。空のキューの処理:キューが空の場合、LPOPはnilを返し、要素を読む前にキューが存在するかどうかを確認できます。

Centosシステムでは、Redis構成ファイルを変更するか、Redisコマンドを使用して悪意のあるスクリプトがあまりにも多くのリソースを消費しないようにすることにより、LUAスクリプトの実行時間を制限できます。方法1:Redis構成ファイルを変更し、Redis構成ファイルを見つけます:Redis構成ファイルは通常/etc/redis/redis.confにあります。構成ファイルの編集:テキストエディター(VIやNANOなど)を使用して構成ファイルを開きます:sudovi/etc/redis/redis.conf luaスクリプト実行時間制限を設定します。

Redisコマンドラインツール(Redis-Cli)を使用して、次の手順を使用してRedisを管理および操作します。サーバーに接続し、アドレスとポートを指定します。コマンド名とパラメーターを使用して、コマンドをサーバーに送信します。ヘルプコマンドを使用して、特定のコマンドのヘルプ情報を表示します。 QUITコマンドを使用して、コマンドラインツールを終了します。

Redisカウンターは、Redisキー価値ペアストレージを使用して、カウンターキーの作成、カウントの増加、カウントの減少、カウントのリセット、およびカウントの取得など、カウント操作を実装するメカニズムです。 Redisカウンターの利点には、高速速度、高い並行性、耐久性、シンプルさと使いやすさが含まれます。ユーザーアクセスカウント、リアルタイムメトリック追跡、ゲームのスコアとランキング、注文処理などのシナリオで使用できます。

Redisデータの有効期間戦略には2つのタイプがあります。周期削除:期限切れのキーを削除する定期的なスキャン。これは、期限切れの時間帯-remove-countおよび期限切れの時間帯-remove-delayパラメーターを介して設定できます。怠zyな削除:キーが読み取られたり書かれたりした場合にのみ、削除の有効期限が切れたキーを確認してください。それらは、レイジーフリーレイジーエビクション、レイジーフリーレイジーエクスピア、レイジーフリーラジーユーザーのパラメーターを介して設定できます。

Debian Systemsでは、Directoryコンテンツを読み取るためにReadDirシステム呼び出しが使用されます。パフォーマンスが良くない場合は、次の最適化戦略を試してください。ディレクトリファイルの数を簡素化します。大きなディレクトリをできる限り複数の小さなディレクトリに分割し、Readdirコールごとに処理されたアイテムの数を減らします。ディレクトリコンテンツのキャッシュを有効にする:キャッシュメカニズムを構築し、定期的にキャッシュを更新するか、ディレクトリコンテンツが変更されたときに、頻繁な呼び出しをreaddirに削減します。メモリキャッシュ(memcachedやredisなど)またはローカルキャッシュ(ファイルやデータベースなど)を考慮することができます。効率的なデータ構造を採用する:ディレクトリトラバーサルを自分で実装する場合、より効率的なデータ構造(線形検索の代わりにハッシュテーブルなど)を選択してディレクトリ情報を保存およびアクセスする
