Redisのビットマップ(ビットマップ)を使用してデータを保存できます。
1.Redisビットマップとは
つまり、Stringデータ構造のキーに格納されている文字列の指定されたオフセットでビットを操作し、元の位置の値を返します。
1.1利点:
スペースの節約:ビットは、要素に対応する値または状態を示すために使用されます。ここで、キーは対応する要素の値です。実際、8ビットでバイトを形成できるため、スペースを大幅に節約できます。
高効率:setbitとgetbitの時間計算量はどちらもO(1)であり、他のビット演算も効率的です。
1.2短所:
本質的に、0と1の違いしかないため、ビジネスデータの記録にビットを使用する場合は、値の値を気にする必要はありません。
2.Redisビットマップコマンド
2.1 setbitコマンド
は、キーのオフセット(オフセット)の値を設定または変更します。
構文:setbit key offset value
戻り値:オフセット(オフセット)の元の格納値を指定します。
注:オフセットが大きすぎる場合、0
オフセットは中央から最大値の2 ^ 32-1まで埋められ、最大の文字列は512M
ビットマップ
setkeyコマンド2.2 getbitコマンド
で、キーに格納されている文字列値を照会して取得します。オフセット少しの量。
構文:getbit key offset
戻り値:指定されたキーのオフセットを返します。キーが存在しない場合は、0を返します。
ビットマップのgetbitコマンド
2.3 BITCOUNTコマンド
計算与えられたキーの文字列値に1に設定されたビットの数
構文:bitcount key [start] [end]
戻り値:1ビットの数
注:setbitは、ビット位置を設定またはクリアするためのものです。これは、キーが表示された回数のカウントです1。
注:[start] [end](unit)は実際にはバイトですが、これはどういう意味ですか?redisを入力すると、実際には8が乗算されます。
// 计算长度为 count 的二进制数组指针 s 被设置为 1 的位数量
// 这个函数只能在最大为 512 MB 的字符串上使用
size_t redisPopcount(void *s, long count) {
size_t bits = 0;
unsigned char *p = s;
uint32_t *p4;
// 通过查表来计算,对于 1 字节所能表示的值来说
// 这些值的二进制表示所带有的 1 的数量
// 比如整数 3 的二进制表示 0011 ,带有两个 1
// 正好是查表 bitsinbyte[3] == 2
static const unsigned char bitsinbyte[256] = {
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
/* Count initial bytes not aligned to 32 bit. */
while((unsigned long)p & 3 && count) {
bits += bitsinbyte[*p++];
count--;
}
/* Count bits 16 bytes at a time */
// 每次统计 16 字节
// 关于这里所使用的优化算法,可以参考:
// http://yesteapea.wordpress.com/2013/03/03/counting-the-number-of-set-bits-in-an-integer/
p4 = (uint32_t*)p;
while(count>=16) {
uint32_t aux1, aux2, aux3, aux4;
aux1 = *p4++;
aux2 = *p4++;
aux3 = *p4++;
aux4 = *p4++;
count -= 16;
aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
aux2 = aux2 - ((aux2 >> 1) & 0x55555555);
aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333);
aux3 = aux3 - ((aux3 >> 1) & 0x55555555);
aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
((((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
}
/* Count the remaining bytes. */
// 不足 16 字节的,剩下的每个字节通过查表来完成
p = (unsigned char*)p4;
while(count--) bits += bitsinbyte[*p++];
return bits;
}
bitcount命令の使用2.4bitopコマンド
1つ以上のバイナリ文字列キーでメタ演算を実行し、結果をdestkeyに保存します。
構文:演算は、and、or、not、およびxorのいずれかになります。
bitop and destkey key [key...]
、1つ以上のキーを論理的にマージし、結果をdestkeyに保存します。
bitop or destkey key [key...]
、1つ以上のキーの論理ORを実行し、結果をdestkeyに保存します。
bitop xor destkey key [key...]
、1つ以上のキーを論理的にXORし、結果をdestkeyに保存します。
bitop xor destkey key
、1つ以上のキーの論理否定の場合、結果はdestkeyに保存されます。
NOTに加えて、他のほとんどの操作は、入力として1つ以上のキーを受け入れることができます。
(黒板をノックして要点を強調)BITOPの時間計算量はO(N)です。大きな行列や大量のデータ統計を処理する場合は、メインノードのブロックを回避するためにスレーブノードにタスクを割り当てるのが最適です。
利点
1.ストレージは最小単位ビットに基づいているため、非常に省スペースです。
2.設定の時間計算量はO(1)、読み取りの時間計算量はO(1)であり、操作は非常に高速です。
3.関連する計算を実行する場合、バイナリデータの保存は非常に高速です。
4.便利な拡張
redisでのビットマッピングの制限は512MBに制限されているため、最大は2 ^ 32ビットです。
3.ビットマップの使用シナリオ
さまざまなビジネスニーズに応じて、さまざまな使用方法がありますが、一般に、ユーザーを例にとると2種類あります。
1. 1つは、特定のユーザーの水平方向の拡張です。つまり、現在のユーザーのさまざまなステータス値がこのキー値に記録され、無制限の拡張が可能になります(2 ^ 32以内)
コメント:各キーはuid情報を保持するため、この使用法は基本的にほとんど使用されません。格納されたキーのスペースが値よりも大きい場合、スペースの観点から特定の最適化スペースがあります。ロングテールを記録する場合は、それは考えることができます。
2. 1つはユーザーの垂直展開です。つまり、各キーは現在のビジネス属性のステータスのみを記録し、各uidは情報を記録するためのビットとして使用されます(2 ^ 32を超えるユーザーはフラグメントに格納する必要があります)
コメント:基本的に、プロジェクトで使用されるシーンはこの方法に基づいています。ビジネスに応じてリソースをリサイクルするのに便利です。キー値は1つだけです。uidのストレージはビットストレージに変換されます。対応する値を見つけることができます。 uidによって非常に巧妙に。主なストレージ容量は値であり、これは期待に沿ったものです。
1.视频属性的无限延伸
需要分析:
数十億のデータ量を持つ短いビデオアプリであるビデオには、さまざまな属性(ロックされているかどうか、特殊効果かどうかなど)があり、マークを付ける必要があります。
可能な解決策:
1. mysqlに保存されますが、絶対にそうではありません。1つは、ビジネスの成長に伴って属性が増加し続け、時間制限のある属性があることです。データベースにフィールドを直接追加および削除することは非常に不合理です。jsonのような圧縮技術のあるフィールドがあっても、読み取り効率の問題があり、何億ものデータの場合、破棄されたフィールドをリサイクルするのは非常に面倒です。
2. redisに直接記録し、ビジネス属性+ uidをキーとして保存します。読み取りと書き込みの効率に問題はありませんが、ストレージの観点からは、キー内のデータの量が値よりも多く、スペースを消費しすぎます。jsonなどの圧縮技術を使用して保存されている場合でも。問題もあります。解凍には時間がかかり、何億ものデータ復旧も問題です。
設計:
ストレージにredisビットマップを使用します。
キーは、属性ID +ビデオセグメントIDで構成されます。この値は、ビデオIDに従ってセグメンテーション範囲を変調し、オフセットオフセットを決定します。属性が約1億2000万の10億本の動画は、非常に費用対効果が高くなります。
偽のコード:
function set($business_id , $media_id , $switch_status=1){
$switch_status = $switch_status ? 1 : 0;
$key = $this->_getKey($business_id, $media_id);
$offset = $this->_getOffset($media_id);
return $this->redis->setBit($key, $offse, $switch_status);
}
function get($business_id , $media_id){
$key = $this->_getKey($business_id,$media_id);
$offset = $this->_getOffset($media_id);
return $this->redis->getBit($key , $offset);
}
function _getKey($business_id, $media_id){
return 'm:'.$business_id.':'.intval($media_id/10000);
}
function _getOffset($media_id){
return $media_id % 10000;
}
これは基本的に属性の格納を実現し、その後の新しい属性の追加はbusiness_idの単なる別の値です。
なぜ断片化するのですか?断片化の粒度を測定する方法は?
断片化には2つの理由があります:1。非集中的な配布では、長さが長すぎ、メモリリソースを占有する0の役に立たない値が多数あります。2。ビットマップには2 ^ 32の長さ制限があります。
断片化の粒度を測定する方法:1。主キーIDに障害がある場合は、このセグメントのID範囲を避け、スペースの浪費を防ぐために、可能な限り粒度を選択してください。 9999 0 ... 01、属性の保存のためにすべてを保存すると無駄になります。2.断片化の粒度は、特定の単位時間の成長値を参照することで判断できます。これは、スペースはそれほど占有されませんが、予算が占めるスペースの量にも役立ちます。
2.用户在线状态
需要分析:
ユーザーがオンラインであるかどうかを提供するために、サブプロジェクトへのインターフェイスを提供する必要がありますか?
設計:
ビットマップの使用は、省スペースで効率的な方法です。キーのみが必要で、ユーザーIDはオフセットオフセットです。オンラインの場合は1に設定され、オフラインの場合は0に設定されます。3億ユーザーは36MBのスペースしか必要としません。
偽のコード:
$status = 1;
$redis->setBit('online', $uid, $status);
$redis->getBit('online', $uid);
例1と同じフラグメンテーション方法を追加する必要があります。10億は本当に多すぎます。10wを1つに分割。
3.统计活跃用户
需要分析:
アクティブユーザーのデータ状況を計算する必要があります。
設計:
キャッシュのキーとして時間を使用すると、ユーザーIDがオフセットされ、その日にアクティブだった場合は1に設定されます。その後、bitOpを介してバイナリ計算が実行され、特定の期間におけるユーザーのアクティビティが計算されます。
偽のコード:
$status = 1;
$redis->setBit('active_20170708', $uid, $status);
$redis->setBit('active_20170709', $uid, $status);
$redis->bitOp('AND', 'active', 'active_20170708', 'active_20170709');
例1のように、何億人ものユーザーが断片化を追加する必要があります。数十万以下の場合、複雑さを軽減するために事業を分割する必要はありません。
その他の同様の状況:
キー:日付;
オフセット:ユーザーID [数値またはバイナリ];
値:ログインするか/操作を行うか;
日付でビットマップを生成する
計算月活
:30日間のすべてのビットマップをORで計算し、ビットカウントの計算を実行します。
計算留存率
:昨日保持=昨日と今日を継続してログインした人の数/昨日、つまり昨日のログインをした人の数ビットマップと今日のビットマップが計算され、昨日のビットカウント数で除算されます。
4.用户签到
需要分析:
ユーザーはサインインする必要があり、サインインデータを分析して対応する運用戦略を立てる必要があります。
設計:
redisビットマップを使用すると、ロングテールレコードであるため、キーは主にuidで構成され、初期時刻が設定されます。その後、1日が追加されない場合は、値のオフセットの位置に対応します。
偽のコード:
$start_date = '20170708';
$end_date = '20170709';
$offset = floor((strtotime($start_date) - strtotime($end_date)) / 86400);
$redis->setBit('sign_123456', $offset, 1);
//算活跃天数
$redis->bitCount('sign_123456', 0, -1)
シャーディングは不要です。1年365日、3億人のユーザーが約300000000 * 365/8/1000/1000/1000 = 13.68gを占めています。ストレージコストは非常に低いですか?
使用bitmap过程中可能会遇到的坑
1. bitcoutの罠
前の使用法を注意深く読むと、「指定されたキーのビット数を値1(ビットではなくバイト)で返す」というメモがあることがわかります。これはここにあります。ピットは...
写真と真実があります:
ビットカウント0 0の場合、最初のバイトの1の数である必要があります。これはバイトであることに注意してください。最初のバイトは0、1、2、3、4、5、6、7です。これらの8つの位置。
4.セットストレージとビットマップストレージの使用の比較、
上記の比較から、独立したユーザーが多い場合は、ビットマップを使用する方が明らかに有利であり、多くのメモリを節約できることがわかります。ただし、独立したユーザーの数が少ない場合は、ビットマップが冗長なストレージオーバーヘッドを生成するため、セットストレージを使用することをお勧めします。
エクスペリエンス
タイプ=文字列を使用します。ビットマップは刺すタイプで、最大は512MBです。
セット
ビット中のオフセットに注意してください。これには時間がかかる場合があります。ビットマップは完全に優れているわけではありません。