redis除了咱們經常使用的5中基本數據類型,在此基礎上,還提供了一些特殊的功能模塊。這裏介紹如下三種:bitmaps,hyperloglog,geo。java
概述git
redis中的bitmaps(位圖)不是實際的數據類型,而是在String類型上定義的一組面向位的操做。因爲字符串是二進制安全blob,而且它們的最大長度爲512 MB,所以它們適合設置最多2^32個不一樣的位。位圖的最大優點之一是它們在存儲信息時一般能夠節省大量空間。例如,在經過增量用戶ID表示不一樣用戶的系統中,可使用僅512MB的存儲器記住40億用戶的單個位信息redis
使用場景算法
活躍用戶數統計:假設一個系統天天有100萬獨立用戶登陸。數組
需求1:統計每週的活躍用戶數。安全
需求2:統計一週天天都登陸了用戶數。bash
分析,須要存儲每一個用戶的每一天的登陸信息數據結構
經常使用命令dom
setbit key offset value
bitop 在不一樣的字符串之間執行逐位操做。提供的操做是AND,OR,XOR和NOT。
bitcount 執行填充計數,報告設置爲1的位數。
bitpos 查找具備指定值0或1的第一個位。
複製代碼
代碼學習
//TODO
public class BitMapTest {
public static void main(String[] args) {
//鏈接本地的 Redis 服務
Jedis jedis = new Jedis("localhost");
//初始化id爲0-99的簽到狀況
IntStream.range(0, 100).forEach((id) -> {
jedis.setbit("星期" + id % 7, id, true);
}
);
//id的爲10的用戶,天天都登陸了的。
for (int i = 0; i < 7; i++) {
jedis.setbit("星期" + i , 10, true);
}
for (int i = 0; i < 7; i++) {
System.out.println(String.format("星期%s 的活躍用戶數爲%s",i, jedis.bitcount("星期" + i)));
}
jedis.bitop(BitOP.OR, "week","星期0", "星期1", "星期2", "星期3", "星期4", "星期5", "星期6");
System.out.println(String.format("本週的活躍用戶數爲%s",jedis.bitcount("week")));
jedis.bitop(BitOP.AND, "week1","星期0", "星期1", "星期2", "星期3", "星期4", "星期5", "星期6");
System.out.println(String.format("本週天天都登陸的用戶數%s",jedis.bitcount("week1")));
}
}
複製代碼
總結
學習bitmap,咱們知道bitmap經過一個bit數組來存儲特定數據的一種數據結構,每個bit位都能獨立包含信息,bit是數據的最小存儲單位,所以能大量節省空間。bitmap有一個很明顯的優點是能夠輕鬆合併多個統計結果,只須要對多個結果求與,或,異或等操做就能夠。也能夠大大減小存儲內存,能夠作個簡單的計算,若是要統計1億個數據的基數值,大約須要內存。
數據類型 | 佔用空間 | 儲存的用戶量 | 內存量 |
---|---|---|---|
set | 32位 | 100000000 | 32*100000000/8/1024/1024≈ 381M |
bitMap | 1位 | 100000000 | 100000000/8/1024/1024 ≈ 12M |
概述
什麼是基數? 假設有個集合爲(1,4,2,7,8,7)那麼這個集合的基數爲去重以後的元素個數,即爲5。
bitmap對於內存的節約量是顯而易見的,但仍是不夠。統計一個對象的基數值須要12M,若是統計10000個對象,就須要將近120G了,一樣不能普遍用於大數據場景。
redis中實現的HyperLogLog,只須要12K內存,在標準偏差0.81%的前提下,可以統計2^{64}個數據。HyperLogLog是一種機率算法,機率算法不直接存儲數據集合自己,經過必定的機率統計方法預估基數值,這種方法能夠大大節省內存,同時保證偏差控制在必定範圍內。
經常使用命令
pfadd key val1 val2 ...
pfcount key 統計基數
複製代碼
使用場景
統計頁面獨立的UV,UV即對於某個頁面,天天獨立訪問的用戶數量,若是同一個用戶屢次訪問,只能算一次。
代碼
public class HyperLogLogTest {
public static void main(String[] args) {
//鏈接本地的 Redis 服務
Jedis jedis = new Jedis("localhost");
//初始化id爲0-99的簽到狀況
IntStream.range(0, 10000).forEach((id) -> {
String userId = UUID.randomUUID().toString();
jedis.pfadd("datetime:page1",userId);
}
);
System.out.println("經過hyperloglog機率算法估算出的UV:"+jedis.pfcount("datetime:page1"));
}
}
複製代碼
總結
hyperloglog是一種機率算法,是經過局部推算總體的一種算法,在不存儲元素的狀況下,用計算集合的基數,可是有必定的偏差。若是偏差在業務容忍的範圍內。那麼這一種很是節省內存的高效算法。
概述
在redis3.2版本,增長了Geo地理空間位置的計算功能。經過GEO咱們能夠計算兩個地理位置的距離,以及給定地理位置獲取指定範圍內的地理位置集合等
經常使用命令(使用redis3.2以後的版本纔有geo功能)
一、GEOADD:增長某個地理位置的座標 二、GEOPOS:獲取某個地理位置的座標 三、GEODIST:獲取兩個地理位置的距離 四、GEORADIUS:根據給定地理位置座標獲取指定範圍內的地理位置集合 五、GEORADIUSBYMEMBER:根據給定地理位置獲取指定範圍內的地理位置集合 六、GEOHASH:獲取某個地理位置的 geohash 值
使用場景
計算用戶和商家的距離
代碼
public class GeoTest {
public static void main(String[] args) {
//鏈接本地的 Redis 服務
Jedis jedis = new Jedis("localhost");
//初始化4個門店的位置
Map<String, GeoCoordinate> map=new HashMap<>();
map.put("成華新風路專營店",new GeoCoordinate(104.11117,30.6846));
map.put("青羊區東門街營業廳",new GeoCoordinate(104.05983,30.66685));
map.put("武侯區一環路南三段營業廳",new GeoCoordinate(104.0614,30.63354));
map.put("金牛區三洞橋專營店",new GeoCoordinate(104.04903,30.67408));
jedis.geoadd("shop",map);
GeoRadiusParam param=GeoRadiusParam.geoRadiusParam()
.withDist()//返回距離
.withCoord() //返回經緯度
.sortAscending();//根據距離升序排序;
double userLongitude=104.045181;//用戶的經度
double userLatitude=30.688663;//用戶的緯度
//查詢距離用戶10千米範圍內的營業點
List<GeoRadiusResponse> shop = jedis.georadius("shop", userLongitude, userLatitude, 10, GeoUnit.KM,param);
for (GeoRadiusResponse geoRadiusResponse : shop) {
System.out.println(String.format("用戶距離 %s (經度:%s,緯度:%s) 有%s 公里 ",
geoRadiusResponse.getMemberByString(),
geoRadiusResponse.getCoordinate().getLongitude(),
geoRadiusResponse.getCoordinate().getLatitude(),
geoRadiusResponse.getDistance()));
}
}
}
複製代碼
總結
redis的geo是經過 它只是假設地球是一個球體,由於使用的距離公式是Haversine公式。這個公式只是在應用於地球時的近似值,而地球不是一個完美的球體。在做爲不是特別高的精度狀況下,使用geo是一個不錯的選擇。