GEO數據結構
GEO就是Geolocation的簡寫形式,代表地理坐標。Redis在3.2版本中加入了對GEO的支持,允許存儲地理坐標信息,幫助我們根據經緯度來檢索數據。常見的命令有:
GEOADD:添加一個地理空間信息,包含:經度(longitude)、緯度(latitude)、值(member)
GEODIST:計算指定的兩個點之間的距離并返回
GEOHASH:將指定member的坐標轉為hash字符串形式并返回
哈希降維:先將經緯度坐標值 轉換成 二進制的數字,然后再利用特殊的編碼 轉換成 對應的字符串[1.轉換成字符串后,占用的空間就會小一點,節省內存。
GEOPOS:返回指定member的坐標
GEORADIUS:指定圓心、半徑,找到該圓內包含的所有member,并按照與圓心之間的距離排序后返回。6.2以后已廢棄
GEOSEARCH:在指定范圍內搜索member,并按照與指定點之間的距離排序后返回。范圍可以是圓形或矩形。6.2.新功能
GEOSEARCHSTORE:與GEOSEARCH功能一致,不過可以把結果存儲到一個指定的key。6.2.新功能
在首頁中點擊某個頻道,即可看到頻道下的商戶:Request URL:http://localhost:8080/api/shop/of/type?&typeId=1¤t=1&x=120.149993&y=30.334229
請求方式GET
請求路徑/shop/of/type
typeId:商戶類型
請求參數current:頁碼,滾動查詢;x:經度;y:緯度
返回值List<Shop>:符合要求的商戶信息
我們可以看到這個請求,它的請求方式是GET,路徑叫`/shop/tap`。我們在這個地方其實暗含了一個條件,就是要根據類型搜索啊,他點美食,你搜出來肯定是美食,點KTV出來的就是KTV。要求根據類型做搜索。傳了第一個參數叫`typeId`商戶的類型。然后第二個參數呢叫`count`,頁碼這里要做滾動查詢啊,注意這個滾動查詢就是說這個地方可能一頁展示不完嘛,那我就滾動一次,你就多查一頁,滾動一次查一頁。而在分頁的時候呢,他這里有頁碼,那肯定就會有每一頁的大小,所以他不就是一個傳統分頁嗎?所以這塊呢我們不用太擔心,實現起來也不復雜。然后呢,既然要去實現附近商戶搜索,那你是不是得有一個圓心。所以呢,這里接下來的兩個參數x y,那大家一看就知道了啊,看這個值是經緯度的,那這個經緯度啊就是當前登錄這個用戶他的一個坐標,將來我們要搜的呢就是它附近的這些商戶,按照距離去做個排序。那這個用戶的坐標哪兒來的呢?如果是在真實的案例當中,肯定是由咱們的app從后臺獲取,當前的這個手機所在的位置啊,咱們這兒呢就直接在前臺寫死了好。
那么我們拿到這些信息了以后傳到后臺了,那后臺就可以根據`typeId`做過濾,去搜索到相同類型的這些商家,然后再利用current頁碼去做分頁,再利用這種經緯度去做一個排序就行了。最后呢返回的自然就是查詢到的所有商家,形成的一個集合了啊。那到這里呢,這個接口的分析就完成了。但是我們別著急啊,去實現這個接口啊,同學們想想看,現在我們的商家的信息都保存在哪里?哎,是不是保存在數據庫啊?那我們可以來看一下,我們打開數據庫,在這里啊,`tb_shop`,就是我們的商戶表了。那這張表里面呢有很多的字段啊,其中有一個就是`typeId`代表的是店鋪的類型。在這里邊呢我們現在數據量并不多啊,其中呢他把id為一的,就是所有的美食有關的店鋪啊,他把id為二的就是所有KTV有關的
那現在這個接口怎么是驗證的,就是前端發過來的`typeId`,他會來這基于`typeId`做過濾,然后呢找到對應的店鋪,然后做分頁返回到前端。不過我們將來要實現的呀,除了根據`typeId`過濾以外,是不是還要按照地理坐標去搜索附近的商家呀,唉還要按距離排序。那這樣的功能在我們的數據庫里能實現嗎?唉顯然不能啊。那要想實現我們必須干嘛,我們必須要把店鋪當中的這些經緯度坐標啊等等信息導入到Redis當中的Geo類型的數據結構當中。不過呀這里有一點需要注意啊,我們的Geo類型他在存儲的時候,不知道大家記不記得它里邊的參數呢,其實主要就是一個member,一個經度和一個緯度。經緯度對應到我們數據庫表里就是xy,而member我們存什么呢,是不是把整個店鋪的信息都塞到那個member里去呢,顯然不合適吧。那Redis畢竟是內存存儲啊,它的空間占用了太多了也不太好。所以說呢在這個地方我的建議是啊,這個member啊,我們直接存一個店鋪的id就可以了。也就是說我們將來把這些數據導入list的時候,存店鋪id和經緯度坐標,將來我們要去搜索附近店鋪的時候,根據經緯度坐標去進行篩選,篩選以后得到的是什么,然后我們再拿id來數據庫里,根據id查我們的數據庫效率上還是可以接受的,對不對?唉這是我們將來實現的一個思路啊。但是事情到這還沒完啊,這里啊還有一個問題需要我們去分析,我們回到PPT看一下,大家別忘了這里我們的搜索有一個限制條件,就是要根據類型id做過濾,你要知道我們剛才商量了,我們存到Redis里面的僅僅是坐標,還有店鋪的id,根本就沒有類型id,所以你能過濾嗎?過濾不了。
附近商戶搜索
按照商戶類型做分組,類型相同的商戶作為同一組,以typeld為key存入同一個GEO集合中即可??
?
@Test
void loadShopData(){// 1. 查詢所有店鋪信息(若數據量大可分批查詢,此處因數據量小直接查詢全部)List<Shop> list = shopService.list();// 2. 按類型ID分組,使用Stream API優雅實現分組邏輯// key: 店鋪類型ID,value: 同類型店鋪集合Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));// 3. 分批寫入Redis GEO,優化網絡延遲map.forEach((typeId, shops) -> {// 3.1 創建Redis GEO鍵,格式:shop:geo:{typeId}String key = "shop:geo:" + typeId;// 3.2 構建地理位置集合,批量操作提升效率List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shops.size());shops.forEach(shop -> {// 將店鋪ID作為member,經緯度封裝為Point對象locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(), new Point(shop.getX(), shop.getY())));});// 3.3 批量寫入Redis,單次請求完成所有同類型店鋪的地理位置存儲// 對比單條插入,批量操作顯著減少網絡往返時延stringRedisTemplate.opsForGeo().add(key, locations);});
}
運行test,可以看到geo中存在1和2,對應的類型是餐廳和ktv
SpringDataRedis的2.3.9版本并不支持Redis6.2提供的GEOSEARCH命令,因此我們需要提示其版本,修改自己的POM文件,內容如下:?按照老師視頻中可以按照mavenhelper插件,
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><artifactId>lettuce-core</artifactId><groupId>io.lettuce</groupId></exclusion><exclusion><artifactId>spring-data-redis</artifactId><groupId>org.springframework.data</groupId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>2.6.2</version></dependency><dependency><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId><version>6.1.6.RELEASE</version></dependency>
?修改shopcontroller,service和impl
@GetMapping("/of/type")public Result queryShopByType(@RequestParam("typeId") Integer typeId,@RequestParam(value = "current", defaultValue = "1") Integer current,@RequestParam(value = "x",required = false) Double x,@RequestParam(value = "y",required = false) Double y) {return shopService.queryShopByType(typeId,current,x,y);}
@Overridepublic Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {// 1. 判斷是否需要根據坐標查詢if (x == null || y == null) {// 不需要坐標查詢,按數據庫查詢Page<Shop> page = query().eq("type_id", typeId).page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));// 返回數據return Result.ok(page.getRecords());}// 2. 計算分頁參數int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;int end = current * SystemConstants.DEFAULT_PAGE_SIZE;// 3. 查詢redis,按照距離排序,分頁。結果:shopId.distanceString key = SHOP_GEO_KEY + typeId;GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() // GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE.search(key,GeoReference.fromCoordinate(x, y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end));// 4. 解析出idif (results == null) {return Result.ok(Collections.emptyList());}List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();if (list.size() <= from) {//沒有下一頁了,結束return Result.ok(Collections.emptyList());}// 4.1. 截取 from ~ end 的部分Map<String, Distance> distanceMap = new HashMap<>(list.size());List<Long> ids = new ArrayList<>(list.size());list.stream().skip(from).forEach(result -> {// 4.2. 獲取店鋪idString shopIdStr = result.getContent().getName();ids.add(Long.valueOf(shopIdStr));// 4.3. 獲取距離Distance distance = result.getDistance();distanceMap.put(shopIdStr, distance);});// 5. 根據id查詢ShopString idStr = StrUtil.join(",", ids);List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();for(Shop shop:shops){shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());}return Result.ok(shops);}
不再像原來那樣第一個是101茶餐廳了, 第一個是烤肉了,距離170m,往下滑也會出現其他的餐廳
?
-
判斷是否需要地理查詢
首先檢查用戶是否提供了經緯度參數x和y。如果任意一個為空,說明不需要地理范圍查詢,直接按傳統方式從數據庫分頁查詢。根據類型ID(typeId)篩選記錄,使用MyBatis-Plus的分頁功能,每頁大小采用系統常量默認值(如5條),返回當前頁的店鋪數據。 -
計算分頁范圍
當需要地理查詢時,根據當前頁碼(current)和固定每頁大小(如5條),計算需要獲取的數據區間。例如第1頁取0-5條,第2頁取5-10條。通過(current-1)*pageSize
確定起始位置,current*pageSize
確定結束位置。 -
Redis地理搜索
構建Redis的Geo查詢:- 使用固定前綴(如
shop:geo:
)拼接類型ID作為Redis鍵 - 以用戶提供的經緯度(x,y)作為圓心
- 設置半徑范圍(如5000米)
- 要求返回結果包含距離信息
- 通過limit參數限制返回數量(取結束位置的值,如第2頁取10條)
- 使用固定前綴(如
-
解析搜索結果
檢查Redis返回結果是否為空。若結果不足一個完整分頁(如第3頁只有2條數據但請求5條),直接返回空列表。否則遍歷結果,提取店鋪ID并記錄對應的距離值,同時截取當前分頁區間的數據(如第2頁取索引5到10的條目)。 -
數據庫精準查詢
根據提取的店鋪ID集合,使用SQL的IN
語句批量查詢數據庫。特別添加ORDER BY FIELD
語句,確保返回順序與Redis地理搜索結果的順序一致,避免數據錯位。 -
補充距離信息
將Redis獲取的距離值回填到店鋪對象中。遍歷數據庫查詢結果,根據每個店鋪的ID從距離映射表中查找對應值,設置到對象的distance屬性。
關鍵注意事項:
- Redis的limit參數需要取分頁結束位置的值(如第2頁取10),因為Redis會返回從第1條到第10條的數據,后續需自行截取5-10條部分
- 數據庫查詢必須使用
ORDER BY FIELD
保持與Redis結果順序一致 - 距離單位需與Redis存儲時的單位(米)保持一致
- 需要處理邊界情況(如最后一頁數據不足、無搜索結果等)
if (list.size() <= from) { //沒有下一頁了,結束return Result.ok(Collections.emptyList()); }
用戶簽到?
BitMap用法
假如我們用一張表來存儲用戶簽到信息,其結構應該如下:
CREATE TABLE `tb_sign` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',`user_id` bigint unsigned NOT NULL COMMENT '用戶id',`year` year NOT NULL COMMENT '簽到的年',`month` tinyint NOT NULL COMMENT '簽到的月',`date` date NOT NULL COMMENT '簽到的日期',`is_backup` tinyint unsigned DEFAULT NULL COMMENT '是否補簽',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT;
假如有1000萬用戶,平均每人每年簽到次數為10次,則這張表一年的數據量為1億條
每簽到一次需要使用(8+8+1+1+3+1)共22字節的內存,一個月則最多需要600多字節
因此模擬簽到卡,BitMap用法,我們按月來統計用戶簽到信息,簽到記錄為1,未簽到則記錄為0.?
把每一個bit位對應當月的每一天,形成了映射關系。用0和1標示業務狀態,這種思路就稱為位圖(BitMap)。Redis中是利用string類型數據結構實現BitMap,因此最大上限是512M,轉換為bit則是2^32個bit位。(有點像牛逼的狀態壓縮dp)
Redis中是利用string類型數據結構實現BitMap,因此最大上限是512M,轉換為bit則是2^32個bit位。
BitMap的操作命令有:
SETBIT:向指定位置(offset)存入一個0或1
GETBIT:獲取指定位置(offset)的bit值
BITCOUNT:統計BitMap中值為1的bit位的數量
BITFIELD:操作(查詢、修改、自增)BitMap中bit數組中的指定位置(offset)的值
BITFIELD_RO:獲取BitMap中bit數組,并以十進制形式返回
BITOP:將多個BitMap的結果做位運算(與、或、異或)
BITPOS:查找bit數組中指定范圍內第一個0或1出現的位置
好同學們,在剛才我們已經熟悉了`bitmap`的基本用法,下面呢我們就利用它來實現一下簽到的功能啊。在這兒我已經把需求給羅列出來了,需求說讓我們實現簽到的接口,將當前用戶當天的簽到信息保存到Redis當中。而這里請求的方式呢用了POST代表新增,請求的路徑呢是`/user/sign`,那代表就是簽到了啊。請求的參數是沒有返回值,什么參數都沒有,讓我去簽到。大家應該還記得吧,我們之前講過實驗簽到,最終格式是不是這樣子的,我們會與用戶結合這個年和月作為key,因為呢簽到往往是以月為統計單位的,所以我們希望每個用戶每個月的簽到情況,放到一個bitmap里,方便我們將來去做統計了。所以呢我們的key呢就有用戶和日期啊兩部分組成,那這樣一來要想實現這樣的功能,是不是至少要知道用戶信息,還有年和月的信息,還有你讓我簽到簽的是第幾個比特位啊,是不是還得知道對應的日期信息啊,年月日都得知道,結果呢你現在啥都不給我,那我怎么去實現這個簽到唉,他說了,是將當前用戶,是我們登錄的這個用戶,當天,就是現在,所以讓他現在時間,可以用代碼直接獲取,因此這幾個東西都不需要前端,是直接就搞定了。
所以啊簽到功能是無參的,那將來如果你要實現什么補簽之類的,你再讓他傳日期是不是就ok了。好,這是我們整個接口的一些要求啊,那我們之前講的時候,bitmap講的是命令啊,那現在咱們用java代碼實現了呀,這里呢有一個提示告訴我們了,說bitmap底層是spring類型實現的啊,是我們redis里面的spring實現的啊,所以說呢我們的spring在做api封裝的時候,把對于bitmap的操作一起封裝到字符串里面去了,那我們知道在redis templating,它是不是有open for value ops for hash哈,希set呀,哎但是他沒有opposable bitmap啊,因為呢相關的都封裝到了字符串操作了,叫a value,因此這里拿的是value operation,所以我們將來操作并web的時候一定要記住啊,拿的是value相關操作字符串操作,然后里面提供了set bit get bit field的三個函數,那set bit是不是就可以實現簽到功能,然后這里的三個參數分別就是我們的key,然后我們的offset,然后就是true和false代表零和一啊,就是k是誰給哪一天簽到啊,簽的是true還是false。
?UserController和service還有impl
@PostMapping("/sign") public Result sign(){return userService.sign(); }
@Overridepublic Result sign() {// 1. 獲取當前登錄用戶Long userId = UserHolder.getUser().getId();// 2. 獲取日期LocalDateTime now = LocalDateTime.now();// 3. 拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));String key = USER_SIGN_KEY + userId + keySuffix;// 4. 獲取今天是本月的第幾天int dayOfMonth = now.getDayOfMonth();// 5. 寫入Redis SETBIT key offset 1stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);return Result.ok();}
數據庫應該存一份,遲到功能,簽到時間還沒實現?
我們在redis中檢驗一下, 正好今天是4.27號,一個字節8位,因此為了存儲一共32個,所以后面還有5個0
??問題1:什么叫做連續簽到天數?
從最后一次簽到開始向前統計,直到遇到第一次未簽到為止,計算總的簽到次數,就是連續簽到天數。
111000110110001011101111101111
問題2:如何得到本月到今天為止的所有簽到數據?
BITFIELD key GET u[dayOfMonth] 0
問題3:如何從后向前遍歷每個bit位?
與1做與運算,就能得到最后一個bit位。隨后右移1位,下一個bit位就成為了最后一個bit位。
需求:實現下面接口,統計當前用戶截止當前時間在本月的連續簽到天數
說明 | |
請求方式 | GET |
請求路徑 | /user/sign/count |
請求參數 | 無 |
返回值 | 連續簽到天數 |
在本節中,我們將實現一個簽到功能,該功能將當前用戶當天的簽到信息保存到Redis中。請求方式使用POST,代表新增,請求路徑是/user/sign
,代表簽到操作。請求參數沒有,返回值也沒有,意味著這個接口是無參的。我們可以通過獲取當前登錄用戶的信息以及當前日期來實現這個功能。
首先,我們需要了解bitmap
的基本用法。在Redis中,bitmap
可以用來存儲簽到信息。我們將用戶ID和年月作為key,因為簽到通常以月為統計單位,所以我們希望每個用戶每個月的簽到情況都放在一個bitmap
里,方便將來進行統計。
要實現簽到功能,我們需要知道用戶信息、年和月的信息,以及對應的日期信息。這些信息都不需要前端傳遞,我們可以直接在后端獲取。簽到功能是無參的,但如果未來需要實現補簽之類的功能,可以讓用戶傳遞日期參數。
在Redis中,我們可以使用SETBIT
命令來實現簽到功能,其中三個參數分別是key、offset和value。這里的key是我們的bitmap
key,offset是偏移量,value是簽到狀態(0或1)。
接下來,我們將使用Java代碼來實現這個統計連續簽到次數的功能。我們需要從最后一次簽到開始向前統計,直到遇到第一次未簽到為止,然后計算總的簽到次數,即連續簽到次數。
我們可以通過以下步驟實現這個功能:
- 獲取當前月份所有簽到數據。
- 從最后一次簽到開始向前統計,直到遇到第一次未簽到為止。
- 計算總的簽到次數,即連續簽到次數。
在Java代碼中,我們可以使用for
循環和if
語句來實現這個邏輯。首先,我們需要獲取當前用戶當月的簽到記錄,然后從最后一個簽到記錄開始向前遍歷,直到遇到未簽到的記錄。在遍歷過程中,我們需要一個計數器來記錄簽到次數,每遇到一個簽到記錄,計數器加一,直到遇到未簽到的記錄為止。
最后,我們將這個計數器的值返回,它就代表了用戶在本月的連續簽到天數。?
@GetMapping("/sign/count")public Result signCount(){return userService.signCount();}
?UserControl修改如下,連帶service,impl一起
@Overridepublic Result signCount() {Long userId = UserHolder.getUser().getId();LocalDateTime now = LocalDateTime.now();String keySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));String key = USER_SIGN_KEY + userId + keySuffix;int dayOfMonth = now.getDayOfMonth();//5,獲取本月截止今天為止的所有的簽到記錄,返回的是一個十進制的數字 BITFIELD sign:5:202504 GET u14 0List<Long>result =stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));Long num =result.get(0);if(result.isEmpty() || result ==null){//沒有任何簽到return Result.ok(0);}//6.循環遍歷int count=0;while(true){//6.1.讓這個數字與1做與運算,得到數字的最后一個bit位if((num&1) == 0){//如果為0,說明未簽到,結束//如果不為0,說明已簽到,計數器+1break;}else{count++;}//把數字右移一位,拋棄最后一個bit位,繼續下一個bit位num >>>= 1;}return Result.ok(count);}
- 可在Redis客戶端使用
BITFIELD sign:5:202504 GET u14 0
驗證數據 - 使用
SETBIT sign:5:202504 0 1
模擬簽到數據 - 注意月份切換時的鍵變化(如4月->5月會生成新鍵)
@Override
public Result signCount() {// 獲取當前用戶ID(從線程上下文獲取登錄用戶信息)Long userId = UserHolder.getUser().getId();// 獲取當前日期時間LocalDateTime now = LocalDateTime.now();// 生成Redis鍵的后綴(格式:yyyyMM,例如"202504")String keySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));// 構建完整的Redis鍵(格式:sign:用戶ID:yyyyMM)String key = USER_SIGN_KEY + userId + keySuffix;// 獲取當前日期是本月的第幾天(用于確定查詢的bit位數)int dayOfMonth = now.getDayOfMonth();// 使用Redis BitField命令獲取本月截止今天的簽到記錄 [[7]]List<Long> result = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create()// 定義BITFIELD GET子命令:無符號整數,長度為當前日期天數,從第0位開始讀取.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));// 處理Redis返回結果的邊界情況if (result.isEmpty() || result.get(0) == null) {return Result.ok(0); // 無簽到記錄時返回0}// 將結果轉換為二進制處理(每位代表一天的簽到狀態)Long num = result.get(0);int count = 0; // 計數器:連續簽到天數// 通過位運算遍歷每個bit位(從低位到高位對應從當月1號到當前日期)while (true) {// 1. 檢查最低位是否為1(已簽到) [[7]]if ((num & 1) == 0) {break; // 遇到未簽到天(0)終止循環}count++; // 累計連續簽到天數// 2. 右移一位繼續檢查下一天(高位->低位移動)[[7]]num >>>= 1;}return Result.ok(count); // 返回連續簽到天數
}
簽到一天,那么return 1,你可以改改redis,自行測試是否可以5天,15天?
UV統計
HyperLogLog用法
首先我們搞懂兩個概念:
●UV:全稱UniqueVisitor,也叫獨立訪客量,是指通過互聯網訪問、瀏覽這個網頁的自然人。1天內同一個用戶多次訪問該網站,只記錄1次。
●PV:全稱PageView,也叫頁面訪問量或點擊量,用戶每訪問網站的一個頁面,記錄1次PV,用戶多次打開頁面,則記錄多次PV。往往用來衡量網站的流量。?
?UV統計在服務端做會比較麻煩,因為要判斷該用戶是否已經統計過了,需要將統計過的用戶信息保存。但是如果每個訪問的用戶都保存到Redis中,數據量會非常恐怖。
?Hyperloglog(HLL)是從Loglog算法派生的概率算法,用于確定非常大的集合的基數,而不需要存儲其所有值。相關算法原理大家可以參考:https://juejin.cn/post/6844903785744056333#heading-0
Redis中的HLL是基于string結構實現的,單個HLL的內存永遠小于16kb,內存占用低的令人發指!作為代價,其測量結果 是概率性的,有小于0.81%的誤差。不過對于UV統計來說,這完全可以忽略。
PFADD key element [element ...]
summary: Adds the specified elements to the specified HyperLogLog.
since: 2.8.9PFCOUNT key [key ...]
summary: Return the approximated cardinality of the set(s) observed by
since: 2.8.9PFMERGE destkey sourcekey [sourcekey ...]
summary: Merge N different HyperLogLogs into a single one.
since: 2.8.9
127.0.0.1:6379> PFADD hl1 e1 e2 e3 e4 e5
(integer) 1
127.0.0.1:6379> PFCOUNT hl1
(integer) 5
127.0.0.1:6379> PFADD hl1 e1 e2 e3 e4 e5
(integer) 0
127.0.0.1:6379> PFADD hl1 e1 e2 e3 e4 e5
(integer) 0
127.0.0.1:6379> PFCOUNT hl1
(integer) 5
127.0.0.1:6379>
?test一下
@Testvoid testHyperLogLog(){String[] values = new String[1000];int j = 0;for (int i = 0; i < 1000000; i++) {j = i % 1000;values[j] = "user_" + i;if (j == 999) {// 發送到RedisstringRedisTemplate.opsForHyperLogLog().add("hl2", values);}} // 統計數量Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");System.out.println("count = " + count);}
?
?
?黑馬點評完整代碼(RabbitMQ優化)+簡歷編寫+面試重點 ?_黑馬點評簡歷-CSDN博客