Redis は、新しいデータ ストレージのアイデアを生み出しました。Redis を使用すると、単調な関数を持つデータベースに直面したときに、どうやって冷蔵庫に入れるかということに集中する必要がなくなります。私たちは Redis の柔軟なデータ構造とデータ操作を使用して、さまざまなゾウにさまざまな冷蔵庫を構築します。この比喩が気に入っていただければ幸いです。
1. Redis で一般的に使用されるデータ型 (推奨: redis ビデオ チュートリアル)
Redis で最も一般的に使用されるデータ型には、主に次の 5 つの型が含まれます:
String、Hash、List、Set、Sorted set
これらのデータ型を詳しく説明する前に、まずこれらのさまざまなデータ型が Redis の内部メモリ管理でどのように記述されるかを図で理解しましょう。
まず、Redis はすべてのキーと値を表すために内部的に redisObject オブジェクトを使用します。redisObject の主な情報は上の図に示すとおりです: type は特定のデータ型を表します値オブジェクト、エンコーディング これは、さまざまなデータ型を Redis 内に格納する方法です。
例: type=string は、値が通常の文字列として保存されることを意味し、対応するエンコーディングは raw または int になります。int の場合、実際の Redis が内部的にこの文字を保存して表すことを意味します数値型クラスによると、文字列 もちろん、文字列自体が「123」「456」などの数値で表現できることが前提となります。
ここでは vm フィールドについて特別な説明が必要です。Redis の仮想メモリ機能がオンになっている場合にのみ、このフィールドが実際にメモリを割り当てることができます。この機能はデフォルトではオフになっています。
上記の図から、Redis はすべてのキー/値データを表すために redisObject を使用していることがわかりますが、これはメモリの無駄です。もちろん、これらのメモリ管理コストは主に、さまざまなデータに統合された管理インターフェイスを提供するためのものです。 Redis のデータ型については、実際の作成者がメモリ使用量をできるだけ節約するためのさまざまな方法も提供していますが、これについては後ほど詳しく説明します。
2. さまざまなデータ型のアプリケーションと実装
まず、これら 5 つのデータ型の使用と内部実装を 1 つずつ分析しましょう:
1. String
String データ構造は単純な Key-Value 型ですが、実際には value は String だけでなく数値も含まれます。
一般的に使用されるコマンド: get、set、incr、decr、mget など。
アプリケーション シナリオ: 文字列は最も一般的に使用されるデータ型であり、通常のキー/値ストレージはこのカテゴリに分類できます。これは、Memcached の現在の機能を完全に実現し、より効率的にできることを意味します。 Redis のスケジュールされた永続化、操作ログ、レプリケーション機能もお楽しみいただけます。 Memcached と同じ get、set、incr、decr およびその他の操作を提供することに加えて、Redis は次の操作も提供します。
#文字列の長さの取得
文字列にコンテンツを追加します
文字列の特定のコンテンツを設定および取得します
文字列の特定の内容を設定および取得します文字列 (ビット)
一連の文字列の内容をバッチで設定します
使用シナリオ: 通常のキーと値のキャッシュ アプリケーション。定期的なカウント: Weibo 投稿数、ファン数。
実装方法: 文字列はデフォルトでは文字列としてredisに格納されており、redisObjectで参照されますが、incr、decrなどの演算があった場合は数値型に変換して計算します。 redisObject のエンコーディング フィールドは int です。
2. ハッシュ
一般的に使用されるコマンド: hget、hset、hgetall など。
アプリケーション シナリオ:
Hash のアプリケーション シナリオを説明するための簡単な例を示します。たとえば、次の情報を含むユーザー情報オブジェクト データを保存したいとします。 ##ユーザー ID 検索キーの場合、保存された値ユーザー オブジェクトには、名前、年齢、誕生日などの情報が含まれます。通常のキー/値構造を使用して保存する場合、主に次の 2 つの保存方法があります。
最初の方法は、ユーザー ID を検索キーとして使用し、他の情報をオブジェクトにカプセル化し、シリアル化して保存します。この方法の欠点は、シリアル化のオーバーヘッドが増加することです。情報の 1 つを取得する場合は、オブジェクト全体を取得する必要があり、変更操作では同時実行性を保護する必要があるため、CAS などの複雑な問題が発生します。
2 番目の方法は、ユーザー情報オブジェクトのメンバーと同じ数のキーと値のペアを格納し、ユーザー ID に対応する属性の名前を使用して、これを一意の識別子として取得します。対応する属性の値についてはシリアル化のオーバーヘッドと同時実行の問題は解消されますが、ユーザー ID は繰り返し保存されます。そのようなデータが大量にある場合は、依然としてかなりのメモリの浪費が発生します。
その後、Redis が提供するハッシュは、この問題を非常にうまく解決します。Redis のハッシュは、実際には値を HashMap として内部に保存し、以下に示すように、このマップのメンバーに直接アクセスするためのインターフェイスを提供します。
##つまり、キーは依然としてユーザー ID であり、値はマップです。このマップのキーはメンバーの属性名で、値は属性値です。このように、変更とデータへのアクセスは、内部マップのキーを通じて直接行うことができます (Redis では、内部マップのキーはフィールドと呼ばれます)。つまり、対応する属性データはキー (ユーザー ID) フィールドを通じて操作できます (属性ラベル) データを繰り返し保存する必要はなく、シリアル化や同時変更が発生することもなく、制御上の問題も発生しません。問題を非常にうまく解決しました。
ここで、Redis はすべての属性データを直接取得するためのインターフェイス (hgetall) を提供していることに注意してください。ただし、内部 Map のメンバーが多数ある場合、内部 Map 全体を走査する必要があります。シングルスレッド モデル このため、この走査操作には時間がかかり、他のクライアント リクエストはまったく応答しなくなる可能性があるため、特別な注意が必要です。
使用シナリオ: ユーザー情報などの変更データを保存します。
実装方法:
上で述べたように、Value に対応する Redis ハッシュは実際には HashMap です。実際には 2 つの異なる実装があります。ハッシュのメンバーが少ない場合、Redis はメモリを節約するために、実際の HashMap 構造を使用せず、1 次元配列に似た方法でコンパクトに格納されます。対応する値 redisObject のエンコードは zipmap です。メンバーの数が増加すると、自動的に zipmap に変換されますreal HashMap. このときのエンコードは ht です。
3. リスト
一般的に使用されるコマンド: lpush、rpush、lpop、rpop、lrange など。
アプリケーション シナリオ:
Redis リストには多くのアプリケーション シナリオがあり、Twitter のフォロー リスト、ファン リストなど、Redis の最も重要なデータ構造の 1 つでもあります。すべて Redis リスト構造を使用して作成できます。
List はリンクされたリストであり、データ構造の知識があれば誰でもその構造を理解できると思います。 List構造を利用することで、最新ニュースのランキングなどの機能を簡単に実装できます。 List のもう 1 つのアプリケーションはメッセージ キューです。
List の PUSH 操作を使用してタスクを List に保存し、ワーカー スレッドが POP 操作を使用してタスクを実行用に取り出します。 Redis はリスト内の特定のセグメントを操作するための API も提供しており、リスト内の特定のセグメントの要素を直接クエリして削除することができます。
実装方法:
Redis リストの実装は双方向のリンク リストであり、逆方向の検索とトラバーサルをサポートでき、操作がより便利ですが、追加のメモリ オーバーヘッドが発生します。 . Redis 内部 送信バッファ キューなどを含む多くの実装でも、このデータ構造が使用されます。
Redis のリストは、各サブ要素が String 型である二重リンク リストです。プッシュおよびポップ操作を通じてリストの先頭または末尾から要素を追加または削除できるため、リストをスタックまたはキューとして使用されます。
使用シナリオ:
メッセージ キュー システム
リストを使用するとキュー システムを構築でき、ソート セットを使用すると優先順位付きキュー システムを構築することもできます。
例: Redis をログ コレクターとして使用する
これは実際にはキューであり、複数のエンドポイントがログ情報を Redis に書き込み、ワーカーがすべてのログを均一にディスクに書き込みます。
最新 N 個のデータを取得する操作
最新の最初の N 個のログイン ユーザー ID のリストを記録します。範囲を超えたものはデータベースから取得できます。
//把当前登录人添加到链表里 ret = r.lpush("login:last_login_times", uid) //保持链表只有N位 ret = redis.ltrim("login:last_login_times", 0, N-1) //获得前N个最新登陆的用户Id列表 last_login_list = r.lrange("login:last_login_times", 0, N-1)
たとえば、sina Weibo:
Redis では、最新の Weibo ID は常駐キャッシュを使用し、常に更新されます。ただし、ID を 5000 個以下に制限しているため、ID 取得関数は常に Redis を要求します。 start/count パラメータがこの範囲を超えた場合にのみ、データベースにアクセスする必要があります。
私たちのシステムは従来の方法のようにキャッシュを「更新」せず、Redis インスタンス内の情報は常に一貫しています。 SQL データベース (またはハード ディスク上の他のタイプのデータベース) は、ユーザーが「遠い」データを取得する必要がある場合にのみトリガーされ、ホームページや最初のコメント ページはハード ディスク上のデータベースに影響を与えません。
4. Set
共通コマンド:
sadd、spop、smembers、sunion など。
アプリケーション シナリオ:
Redis セットによって提供される外部関数は、リスト関数である list に似ています。特別なことは、セットが自動的に重複排除できることです。リストを保存する必要がある場合data。重複データが不要な場合は、set が適切な選択です。set は、メンバーがセット コレクションに含まれるかどうかを判断するための重要なインターフェイスを提供しますが、リストでは提供できません。
セットはセットです。セットの概念は、一連の一意の値の組み合わせです。一部の集合データは、Redis が提供する Set データ構造を使用して保存できます。
ケース:
Weibo アプリケーションでは、ユーザーのすべてのフォロワーをコレクションに保存し、ユーザーのすべてのファンをコレクションに保存できます。 Redis は、コレクションの交差、和集合、差分などの操作も提供しており、共同注意、共通の設定、2 級友人などの機能を実装するのに非常に便利です。上記のすべてのコレクション操作では、別のコマンドを使用することもできます。結果をクライアントに返すか、新しいコレクションに保存します。
Set はセットであり、文字列型の順序付けされていないセットです。セットはハッシュテーブルを通じて実装されます。概念は基本的に数学のセットに似ています。交差、和集合、差分などが考えられます。セット内の要素秩序はありません。
実現方法:
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
使用场景:
交集,并集,差集:(Set)
//book表存储book名称 set book:1:name ”The Ruby Programming Language” set book:2:name ”Ruby on rail” set book:3:name ”Programming Erlang” //tag表使用集合来存储数据,因为集合擅长求交集、并集 sadd tag:ruby 1 sadd tag:ruby 2 sadd tag:web 2 sadd tag:erlang 3 //即属于ruby又属于web的书? inter_list = redis.sinter("tag.web", "tag:ruby") //即属于ruby,但不属于web的书? inter_list = redis.sdiff("tag.ruby", "tag:web") //属于ruby和属于web的书的合集? inter_list = redis.sunion("tag.ruby", "tag:web")
获取某段时间所有数据去重值
这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。
5、Sorted Set
常用命令:
zadd,zrange,zrem,zcard等
使用场景:
Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
和Set相比,Sorted Set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的Sorted Set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。
另外还可以用Sorted Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
实现方式:
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
三、Redis实际应用场景
1、显示最新的项目列表
下面这个语句常用来显示最新项目,随着数据多了,查询毫无疑问会越来越慢。
SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10
在Web应用中,“列出最新的回复”之类的查询非常普遍,这通常会带来可扩展性问题。这令人沮丧,因为项目本来就是按这个顺序被创建的,但要输出这个顺序却不得不进行排序操作。类似的问题就可以用Redis来解决。比如说,我们的一个Web应用想要列出用户贴出的最新20条评论。
在最新的评论边上我们有一个“显示全部”的链接,点击后就可以获得更多的评论。我们假设数据库中的每条评论都有一个唯一的递增的ID字段。我们可以使用分页来制作主页和评论页,使用Redis的模板,每次新评论发表时,我们会将它的ID添加到一个Redis列表:
LPUSH latest.comments <id></id>
我们将列表裁剪为指定长度,因此Redis只需要保存最新的5000条评论:
LTRIM latest.comments 0 5000
每次我们需要获取最新评论的项目范围时,我们调用一个函数来完成(使用伪代码):
FUNCTION get_latest_comments(start, num_items): id_list = redis.lrange("latest.comments",start,start+num_items - 1) IF id_list.length <p>这里我们做的很简单。在Redis中我们的最新ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。</p><p>我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。</p><p>2、排行榜应用,取TOP N操作</p><p>这个需求与上面需求的不同之处在于,取最新N个数据的操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。</p><p>热门,排行榜应用:</p><pre class="brush:php;toolbar:false">//将登录次数和用户统一存储在一个sorted set里 zadd login:login_times 5 1 zadd login:login_times 1 2 zadd login:login_times 2 3 //当用户登录时,对该用户的登录次数自增1 ret = r.zincrby("login:login_times", 1, uid) //那么如何获得登录次数最多的用户呢,逆序排列取得排名前N的用户 ret = r.zrevrange("login:login_times", 0, N-1)
另一个很普遍的需求是各种数据库的数据并非存储在内存中,因此在按得分排序以及实时更新这些几乎每秒钟都需要更新的功能上数据库的性能不够理想。典型的比如那些在线游戏的排行榜,比如一个Facebook的游戏,根据得分你通常想要:
- 列出前100名高分选手
- 列出某用户当前的全球排名
这些操作对于Redis来说小菜一碟,即使你有几百万个用户,每分钟都会有几百万个新的得分。模式是这样的,每次获得新得分时,我们用这样的代码:
ZADD leaderboard
你可能用userID来取代username,这取决于你是怎么设计的。得到前100名高分用户很简单:
ZREVRANGE leaderboard 0 99
用户的全球排名也相似,只需要:
ZRANK leaderboard
3、删除与过滤
我们可以使用LREM来删除评论。如果删除操作非常少,另一个选择是直接跳过评论条目的入口,报告说该评论已经不存在。 有些时候你想要给不同的列表附加上不同的过滤器。如果过滤器的数量受到限制,你可以简单的为每个不同的过滤器使用不同的Redis列表。毕竟每个列表只有5000条项目,但Redis却能够使用非常少的内存来处理几百万条项目。
4、按照用户投票和时间排序
排行榜的一种常见变体模式就像Reddit或Hacker News用的那样,新闻按照类似下面的公式根据得分来排序:score = points / time^alpha 因此用户的投票会相应的把新闻挖出来,但时间会按照一定的指数将新闻埋下去。下面是我们的模式,当然算法由你决定。
模式是这样的,开始时先观察那些可能是最新的项目,例如首页上的1000条新闻都是候选者,因此我们先忽视掉其他的,这实现起来很简单。每次新的新闻贴上来后,我们将ID添加到列表中,使用LPUSH + LTRIM,确保只取出最新的1000条项目。
有一项后台任务获取这个列表,并且持续的计算这1000条新闻中每条新闻的最终得分。计算结果由ZADD命令按照新的顺序填充生成列表,老新闻则被清除。这里的关键思路是排序工作是由后台任务来完成的。
5、处理过期项目
另一种常用的项目排序是按照时间排序。我们使用unix时间作为得分即可。 模式如下:
- 每次有新项目添加到我们的非Redis数据库时,我们把它加入到排序集合中。这时我们用的是时间属性,current_time和time_to_live。
- 另一项后台任务使用ZRANGE…SCORES查询排序集合,取出最新的10个项目。如果发现unix时间已经过期,则在数据库中删除条目。
6、计数
Redis是一个很好的计数器,这要感谢INCRBY和其他相似命令。我相信你曾许多次想要给数据库加上新的计数器,用来获取统计或显示新信息,但是最后却由于写入敏感而不得不放弃它们。
好了,现在使用Redis就不需要再担心了。有了原子递增(atomic increment),你可以放心的加上各种计数,用GETSET重置,或者是让它们过期。例如这样操作:
INCR user:<id> EXPIRE</id>
你可以计算出最近用户在页面间停顿不超过60秒的页面浏览量,当计数达到比如20时,就可以显示出某些条幅提示,或是其它你想显示的东西。
7、特定时间内的特定项目
另一项对于其他数据库很难,但Redis做起来却轻而易举的事就是统计在某段特点时间里有多少特定用户访问了某个特定资源。比如我想要知道某些特定的注册用户或IP地址,他们到底有多少访问了某篇文章。每次我获得一次新的页面浏览时我只需要这样做:
SADD page:day1:<page_id> <user_id></user_id></page_id>
当然你可能想用unix时间替换day1,比如time()-(time()%3600*24)等等。 想知道特定用户的数量吗?只需要使用
SCARD page:day1:<page_id></page_id>
需要测试某个特定用户是否访问了这个页面?
SISMEMBER page:day1:<page_id></page_id>
8、查找某个值所在的区间(区间无重合) :(Sorted Set)
例如有下面两个范围,10-20和30-40
A_start 10, A_end 20
B_start 30, B_end 40
我们将这两个范围的起始位置存在Redis的Sorted Sets数据结构中,基本范围起始值作为score,范围名加start和end为其value值:
redis 127.0.0.1:6379> zadd ranges 10 A_start (integer) 1 redis 127.0.0.1:6379> zadd ranges 20 A_end (integer) 1 redis 127.0.0.1:6379> zadd ranges 30 B_start (integer) 1 redis 127.0.0.1:6379> zadd ranges 40 B_end (integer) 1
这样数据在插入Sorted Sets后,相当于是将这些起始位置按顺序排列好了。现在我需要查找15这个值在哪一个范围中,只需要进行如下的zrangbyscore查找:
redis 127.0.0.1:6379> zrangebyscore ranges (15 +inf LIMIT 0 1 1) "A_end"
这个命令的意思是在Sorted Sets中查找大于15的第一个值。(+inf在Redis中表示正无穷大,15前面的括号表示>15而非>=15)查找的结果是A_end,由于所有值是按顺序排列的,所以可以判定15是在A_start到A_end区间上,也就是说15是在A这个范围里。至此大功告成。
9、交集,并集,差集:(Set)
//book表存储book名称 set book:1:name ”The Ruby Programming Language” set book:2:name ”Ruby on rail” set book:3:name ”Programming Erlang” //tag表使用集合来存储数据,因为集合擅长求交集、并集 sadd tag:ruby 1 sadd tag:ruby 2 sadd tag:web 2 sadd tag:erlang 3 //即属于ruby又属于web的书? inter_list = redis.sinter("tag.web", "tag:ruby") //即属于ruby,但不属于web的书? inter_list = redis.sdiff("tag.ruby", "tag:web") //属于ruby和属于web的书的合集? inter_list = redis.sunion("tag.ruby", "tag:web")
更多redis相关文章请关注redis数据库教程栏目。
以上がRedis アプリケーション シナリオの詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。