黑馬點評redis改 part 6

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&current=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,往下滑也會出現其他的餐廳

?

  1. 判斷是否需要地理查詢
    首先檢查用戶是否提供了經緯度參數x和y。如果任意一個為空,說明不需要地理范圍查詢,直接按傳統方式從數據庫分頁查詢。根據類型ID(typeId)篩選記錄,使用MyBatis-Plus的分頁功能,每頁大小采用系統常量默認值(如5條),返回當前頁的店鋪數據。

  2. 計算分頁范圍
    當需要地理查詢時,根據當前頁碼(current)和固定每頁大小(如5條),計算需要獲取的數據區間。例如第1頁取0-5條,第2頁取5-10條。通過(current-1)*pageSize確定起始位置,current*pageSize確定結束位置。

  3. Redis地理搜索
    構建Redis的Geo查詢:

    • 使用固定前綴(如shop:geo:)拼接類型ID作為Redis鍵
    • 以用戶提供的經緯度(x,y)作為圓心
    • 設置半徑范圍(如5000米)
    • 要求返回結果包含距離信息
    • 通過limit參數限制返回數量(取結束位置的值,如第2頁取10條)
  4. 解析搜索結果
    檢查Redis返回結果是否為空。若結果不足一個完整分頁(如第3頁只有2條數據但請求5條),直接返回空列表。否則遍歷結果,提取店鋪ID并記錄對應的距離值,同時截取當前分頁區間的數據(如第2頁取索引5到10的條目)。

  5. 數據庫精準查詢
    根據提取的店鋪ID集合,使用SQL的IN語句批量查詢數據庫。特別添加ORDER BY FIELD語句,確保返回順序與Redis地理搜索結果的順序一致,避免數據錯位。

  6. 補充距離信息
    將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代碼來實現這個統計連續簽到次數的功能。我們需要從最后一次簽到開始向前統計,直到遇到第一次未簽到為止,然后計算總的簽到次數,即連續簽到次數。

我們可以通過以下步驟實現這個功能:

  1. 獲取當前月份所有簽到數據。
  2. 從最后一次簽到開始向前統計,直到遇到第一次未簽到為止。
  3. 計算總的簽到次數,即連續簽到次數。

在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);}
  1. 可在Redis客戶端使用BITFIELD sign:5:202504 GET u14 0驗證數據
  2. 使用SETBIT sign:5:202504 0 1模擬簽到數據
  3. 注意月份切換時的鍵變化(如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博客

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78472.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78472.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78472.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring_MVC 中的 JSON 數據處理與 REST 風格開發

Spring_MVC 中的 JSON 數據處理與 REST 風格開發 一、JSON 格式參數 1. 格式布置 依賴導入 為了處理 JSON 數據&#xff0c;需要在項目中引入 Jackson 庫&#xff0c;它是 Spring_MVC 默認使用的 JSON 處理工具。 <dependency><groupId>com.fasterxml.jackson…

藍橋杯 8. 移動距離

移動距離 原題目鏈接 題目描述 X 星球居民小區的樓房全是一樣的&#xff0c;并且按矩陣樣式排列。樓房的編號為 1, 2, 3, ??。 當排滿一行時&#xff0c;從下一行相鄰的樓往反方向排號。 例如&#xff0c;當小區排號寬度為 6 時&#xff0c;排列如下&#xff1a; 1 2 …

第11章 安全網絡架構和組件(一)

11.1 OSI 模型 協議可通過網絡在計算機之間進行通信。 協議是一組規則和限制&#xff0c;用于定義數據如何通過網絡介質&#xff08;如雙絞線、無線傳輸等&#xff09;進行傳輸。 國際標準化組織(ISO)在20世紀70年代晚期開發了開放系統互連(OSI)參考模型。 11.1.1 OSI模型的…

文獻分享:一種四價雙特異性抗體的功能性和IgG樣穩定性、藥理學和可開發特性研究

背景 雙特異性抗體&#xff08;bsAb&#xff09;是一種有前途的藥物形式&#xff0c;能夠同時結合相同或不同抗原上的兩個不同表位。迄今為止&#xff0c;已有14個雙特異性抗體藥物獲得上市批準&#xff0c;盡管取得了這些成功并且迄今為止設計了多種形式&#xff0c;但具有高…

英文中數字讀法規則

以下是英文中數字讀法的詳細規則&#xff0c;涵蓋基本數字、大數字、小數、分數、序數詞及特殊場景&#xff08;如電話號碼、年份、金額等&#xff09;&#xff1a; 一、基本數字&#xff08;0-10&#xff09; 數字基數詞&#xff08;Cardinal&#xff09;序數詞&#xff08;O…

32BIT的SPI主機控制

SPI傳輸位數可參數化配置。 SPI_MASTER: timescale 1ns / 1ps module SPI_Master #(parameter CLK_FREQ 50,parameter SPI_CLK 1000,parameter CPOL 0,parameter CPHA 0 )(input clk,input rst_n,input WrRdReq, //讀/寫數據請求output …

vue響應式原理——vue2和vue3的響應式實現區別

Vue的核心功能點之一是響應式&#xff1a;Vue 會自動跟蹤 JavaScript 狀態并在其發生變化時響應式地更新 DOM。 簡單的來說就是&#xff0c;頁面的渲染效果會隨著數據變化而變化&#xff0c;不用我們去手動操作DOM樹進行數據變化后的渲染。為了實現這一目的&#xff0c;我們最簡…

Kaamel白皮書:2025版COPPA落地實操指南

COPPA簡介 《兒童在線隱私保護法案》&#xff08;COPPA&#xff09;于1998年在美國頒布&#xff0c;其最初的動因源于人們日益增長的對互聯網上收集兒童個人信息的擔憂。為了響應這一問題&#xff0c;聯邦貿易委員會&#xff08;FTC&#xff09;被授權制定并執行相關法規。COP…

測試基礎筆記第十四天

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 一、字符串1.字符串2.字符串切片3.查找find&#xff08;&#xff09;4.去除兩端空白字符 strip5.字符串轉換大小寫 lower、upper5.拆分 split()6.字符串的其他常見方…

什么是Lua模塊?你會如何使用NGINX的Lua模塊來定制請求處理流程?

大家好&#xff0c;我是鋒哥。今天分享關于【什么是Lua模塊&#xff1f;你會如何使用NGINX的Lua模塊來定制請求處理流程&#xff1f;】面試題。希望對大家有幫助&#xff1b; 什么是Lua模塊&#xff1f;你會如何使用NGINX的Lua模塊來定制請求處理流程&#xff1f; 1000道 互聯…

ubuntu擴展邏輯卷并調整文件系統大小步驟

安裝好ubuntu如果沒有調整磁盤空間,一般默認給你100G的空間,在用完時再調整也還來得及,下面是 ubuntu擴展邏輯卷并調整文件系統大小步驟&#xff1a; 1. 擴展邏輯卷 運行以下命令來擴展邏輯卷 /dev/ubuntu-vg/ubuntu-lv&#xff0c;使其使用卷組中所有未分配的空間&#xff…

復雜背景下無人機影像小目標檢測:MPE-YOLO抗遮擋與抗背景干擾設計

目錄 一、引言 二、挑戰和貢獻 密集小目標和遮擋 實時性要求與精度權衡 復雜背景 三、MPE-YOLO模型細節 多級特征集成器&#xff08;MFI&#xff09; 感知增強卷積&#xff08;PEC&#xff09; 增強范圍C2f模塊&#xff08;ES-C2f&#xff09; 四、Coovally AI模型訓…

【C++】13.list的模擬實現

首先&#xff0c;我們需要把鏈表管理起來&#xff0c;也就是把一個個節點管理起來&#xff0c;但是每個節點的信息我們也需要管理&#xff0c;例如節點的前驅指針和后驅指針&#xff0c;以及節點的值&#xff0c;所以我們這里先封裝兩個類來管理節點和鏈表。 namespace Ro {te…

TinyVue v3.22.0 正式發布:深色模式上線!集成 UnoCSS 圖標庫!TypeScript 類型支持全面升級!

我們非常高興地宣布&#xff0c;2025年4月7日&#xff0c;TinyVue發布了v3.22.0&#x1f389;。 本次 3.22.0 版本主要有以下重大變更&#xff1a; 支持深色模式增加基于 UnoCSS 的圖標庫更豐富的 TypeScript 類型聲明支持 XSS 配置 詳細的 Release Notes 請參考&#xff1a…

超級創新思路:基于CBAM-Transformer的強化學習時間序列預測模型(Python\matlab實現)

首先聲明,該模型為原創!原創!原創!且該思路還未有成果發表,感興趣的小伙伴可以借鑒!需要完整代碼可私信或評論! 本方案可用于醫療、金融、交通、零售、光伏功率預測、估計預測、天氣預測、流量預測、故障檢測等領域! 目錄 首先聲明,該模型為原創!原創!原創!且該思…

Apache Sqoop數據采集問題

Sqoop數據采集格式問題 一、Sqoop工作原理二、Sqoop命令格式三、Oracle數據采集格式問題四、Sqoop增量采集方案 Apache Sqoop是一款開源的工具&#xff0c;主要用于在Hadoop(Hive)與傳統的數據庫(mysql、postgresql…)間進行數據的傳遞&#xff0c;可以將一個關系型數據庫&…

Grok發布了Grok Studio 和 Workspaces兩個強大的功能。該如何使用?如何使用Grok3 API?

最近Grok又更新了幾個功能&#xff1a;Grok Studio 和 Workspaces。 其中 Grok Studio 主要功能包括&#xff1a; 代碼執行&#xff1a;在預覽標簽中運行 HTML 片段、Python、JavaScript 等。 Google Drive 集成&#xff1a;附加并處理 Docs、Sheets、Slides等文件。 協作工…

Vue選項式 API 與組合式 API

選項式 API 與組合式 API 選項式 API 選項式 API 是 Vue 2 中常用的開發方式&#xff0c;在 Vue 3 里依舊得到支持。它把組件邏輯劃分為不同的選項&#xff0c;像 data、methods、computed 等。 <template><div><p>Count: {{ count }}</p><button…

SiamMask中的分類分支、回歸分支與Mask分支,有何本質差異?

SiamMask中的分類分支、回歸分支與Mask分支&#xff0c;有何本質差異&#xff1f; 一、引言二、分支定位與任務目標三、網絡結構與感受野設計3.1 分類分支&#xff08;Classification Head&#xff09;3.2 回歸分支&#xff08;Regression Head&#xff09;3.3 Mask分支&#x…

threejs學習day02

場景、相機、渲染器 一、創建3D場景 // 引入threejs import * as THREE from three// 創建一個三維場景scene const scene new THREE.Scene();// 給三維場景添加物品 const geometry new THREE.BoxGeometry(100,100,100) // 形狀 const meterial new THREE.MeshBasicMat…