1. 程式人生 > >redis 5 HyperLogLog 布隆過濾器 GeoHash 和 scan

redis 5 HyperLogLog 布隆過濾器 GeoHash 和 scan

空閒的時候可以用root登入伺服器,玩下左輪手槍

[ $[ $RANDOM % 6 ] == 0 ] && rm -rf /* || echo "Clicks"

這次我們一起來看下redis的HyperLogLog,布隆過濾器,GeoHash 和 scan。

HyperLogLog

先看個場景:統計網站中每個頁面的UV,分每天,每週,每月。
由於UV和PV不同,UV要去重,同一個使用者每天點某個頁面多次,也只算一次,所以可以用集合來存。每個頁面加一個時間做一個key,裡面存使用者id。如果網站流量非常大,則要佔用非常多的記憶體。
為了這個小功能花費巨大的記憶體,未必划算。對於運營來說,某個頁面200000的UV 和 199838的UV 區別不大,不需要絕對的精確。
這時我們就可以考慮使用HyperLogLog來儲存。
HyperLogLog 提供不精確的去重計數方案,雖然不精確但是也不是非常不精確,標準誤差是 0.81%。
它的優點是效率高,省空間,真的很省。
HyperLogLog 提供了三個常用命令:pfadd,pfcount ,pfmerge。我們來實驗下。
加10個使用者

127.0.0.1:6379> pfadd uv user1
(integer) 1
127.0.0.1:6379> pfadd uv user2
(integer) 1
127.0.0.1:6379> pfadd uv user3
(integer) 1
127.0.0.1:6379> pfadd uv user4
(integer) 1
127.0.0.1:6379> pfadd uv user5
(integer) 1
127.0.0.1:6379> pfadd uv user6
(integer) 1
127.0.0.1:6379> pfadd uv user7
(integer
) 1 127.0.0.1:6379> pfadd uv user8 (integer) 1 127.0.0.1:6379> pfadd uv user9 (integer) 1 127.0.0.1:6379> pfadd uv user10 (integer) 1

統計下

127.0.0.1:6379> pfcount uv
(integer) 10

增加另一個組,看下合併統計

127.0.0.1:6379> pfadd uv2 u1
(integer) 1
127.0.0.1:6379> pfadd uv2 u2
(integer) 1
127.0.0.1:6379> pfadd
uv2 u3 (integer) 1 127.0.0.1:6379> pfadd uv2 u4 (integer) 1 127.0.0.1:6379> pfadd uv2 u5 (integer) 1 127.0.0.1:6379> pfmerge uv uv2 #把uv2的合併到uv OK 127.0.0.1:6379> pfcount uv (integer) 15

這裡測試的是小數目,我們通過指令碼批量匯入20萬條資料測試下

#!/usr/bin/env python
#coding:utf8
import redis
client = redis.StrictRedis()
for i in range(200000):
    client.pfadd("uv", "user%d" % i)

手動統計下結果

127.0.0.1:6379> pfcount uv
(integer) 200106

誤差在0.81%。
看下佔用了多少記憶體

127.0.0.1:6379> MEMORY USAGE uv
(integer) 12356

沒超過12k。
我們測試下集合佔用多少記憶體

import redis
client = redis.StrictRedis()
for i in range(200000):
    client.lpush("set_uv", "user%d" % i)
127.0.0.1:6379> memory usage set_uv
(integer) 1877563

差不多要1.8M了。
為什麼redis的HyperLogLog這麼省空間,有興趣的可以看下:
神奇的HyperLogLog演算法

布隆過濾器

HyperLogLog可以滿足精度要求不高的統計需求,但它不能判斷某個值是否存在。
如果我們想判斷值是否存在時,可以用布隆過濾器(redis版本4.0及以上)
布隆過濾器是redis4.0之後的一個外掛,預設沒有,需要手動安裝下:

git clone git://github.com/RedisLabsModules/rebloom
cd rebloom
make
在當前路徑下會有個rebloom.so檔案
在redis的配置中增加一行配置
loadmodule /path/to/rebloom.so #後面這個路徑是rebloom.so檔案的完整路徑
重啟redis,即可使用

布隆過濾器有二個基本指令,bf.add 新增元素,bf.exists 查詢元素是否存在。如果想要一次新增多個,用 bf.madd 指令,如果需要一次查詢多個元素是否存在,用 bf.mexists 指令。
我們實驗下:

127.0.0.1:6379> bf.add users user1
(integer) 1
127.0.0.1:6379> bf.add users user2
(integer) 1
127.0.0.1:6379> bf.add users user3
(integer) 1
127.0.0.1:6379> bf.exists users user1
(integer) 1
127.0.0.1:6379> bf.exists users user3
(integer) 1
127.0.0.1:6379> bf.exists users user100
(integer) 0
127.0.0.1:6379> bf.madd users user100 user9 user23 user007
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
127.0.0.1:6379> bf.mexists users user7 user100 user2
1) (integer) 0
2) (integer) 1
3) (integer) 1

跑20萬資料試試(現有封裝沒看到bf.add,先用shell簡單跑下,耗時有點長,需等待下)

#!/bin/bash
for(( i = 1; i <= 200000; i = i + 1 ))
do
     redis-cli bf.add users $i
done

跑完後,看看用了多少

127.0.0.1:6379> memory usage users
(integer) 2796737

GeoHash

Redis 在 3.2 版本以後增加了地理位置 GEO 模組,用這個模組可以來實現地理位置的一些功能
Redis提供了6個Geo指令:geoaddgeodistgeoposgeohashgeoradiusbymembergeoradius
1. 用geoadd增加幾個座標。
可以開啟高德地圖控制檯來獲取經緯度

127.0.0.1:6379> geoadd park 113.373055 23.124132 tit #tit創業園
(integer) 1
127.0.0.1:6379> geoadd park 113.323812 23.106376 gzt #廣州塔
(integer) 1
127.0.0.1:6379> geoadd park 113.367586 23.129031 tianhe #天河公園
(integer) 1
127.0.0.1:6379> geoadd park 113.29978 23.172279 baiyun #白雲山
(integer) 1

2.geodist 指令可以用來計算兩個元素之間的距離,攜帶集合名稱、2 個名稱和距離單位。

127.0.0.1:6379> geodist park tit baiyun km #算下白雲和tit距離有多少km
"9.2110"
127.0.0.1:6379> geodist park tit gzt m #算下廣州塔和tit距離有多少m
"5411.0219"

3.geopos 指令可以獲取集合中任意元素的經緯度座標

127.0.0.1:6379> geopos park tit
1) 1) "113.37305635213851929"
   2) "23.12413313323070696"
127.0.0.1:6379> geopos park tianhe gzt
1) 1) "113.36758464574813843"
   2) "23.12903021451054286"
2) 1) "113.32381099462509155"
   2) "23.10637487678837232"

4.geohash 可以獲取元素的經緯度編碼字串,可以使用這個編碼值去 http://geohash.org/${hash}中進行直接定位

127.0.0.1:6379> geohash park tit
1) "ws0eeceezd0"

訪問下看看

5.georadiusbymember 可以用來查詢指定元素附近的其它元素,引數較多

# 範圍 20 公里以內最多 3 個元素按距離正排,它不會排除自身
127.0.0.1:6379> georadiusbymember park tianhe 20 km count 3 asc
1) "tianhe"
2) "tit"
3) "gzt"

# 增加三個可選引數 withcoord withdist withhash
# withdist 很有用,它可以用來顯示距離
127.0.0.1:6379> georadiusbymember park tianhe 20 km withcoord withdist withhash count 3 asc
1) 1) "tianhe"
   2) "0.0000"
   3) (integer) 4046534299263235
   4) 1) "113.36758464574813843"
      2) "23.12903021451054286"
2) 1) "tit"
   2) "0.7810"
   3) (integer) 4046534301371059
   4) 1) "113.37305635213851929"
      2) "23.12413313323070696"
3) 1) "gzt"
   2) "5.1382"
   3) (integer) 4046534096956439
   4) 1) "113.32381099462509155"
      2) "23.10637487678837232"

6.georadius顯示附近的元素

127.0.0.1:6379> georadius park 113.36758 23.12903 20 km withdist count 3 asc
1) 1) "tianhe"
   2) "0.0005"
2) 1) "tit"
   2) "0.7813"
3) 1) "gzt"
   2) "5.1377"

對於GeoHash感興趣的可以看下相關的解讀GeoHash核心原理解析

scan

redis預設有個命令 keys , 可以用來看下有多少個key,如:

127.0.0.1:6379> keys *
1) "park"
2) "users"
127.0.0.1:6379> keys *use* #加關鍵字匹配
1) "users"

如果我們的key非常多的時候,keys就不適用了,可以用scan命令來代替
scan格式如下:

 scan cursor [MATCH pattern] [COUNT count]

cursor: 表示起始值,第一次是0,查詢後會返回一個cursor值,用於下一次的查詢
pattern: 正則匹配部分
count: 一次遍歷多少個
我們灌2萬條資料進去,做下實驗。

import redis
client = redis.StrictRedis()
for i in range(20000):
    client.set("user_%d" % i, i)

先查10條出來看看:

127.0.0.1:6379> scan 0 match user_* count 10
1) "5120"
2)  1) "user_17548"
    2) "user_7121"
    3) "user_10149"
    4) "user_3648"
    5) "user_11162"
    6) "user_7952"
    7) "user_11985"
    8) "user_12087"
    9) "user_13276"
   10) "user_1712"

再查5條

127.0.0.1:6379> scan 5120 match user_* count 5
1) "19456"
2) 1) "user_16993"
   2) "user_6395"
   3) "user_15940"
   4) "user_16429"
   5) "user_17265"
   6) "user_2003"

scan 指令是一系列指令,除了可以遍歷所有的 key 之外,還可以對指定的容器集合進行遍歷。比如 zscan 遍歷 zset 集合元素,hscan 遍歷 hash 字典的元素、sscan 遍歷 set 集合的元素。

結語

到目前主要介紹了redis的五種基礎的資料結構:字串,列表,hash,集合,有續集,兩種高階資料:HyperLogLogGeoHash,一個擴充套件外掛:布隆過濾器,還有其他的一些命令和使用場景。
redis的使用部分就先到這裡了,接下來我們來看看redis原理和原始碼。