4. Feed流

Redis07 Feed流与两种数据类型 - 图1

Redis07 Feed流与两种数据类型 - 图2

Redis07 Feed流与两种数据类型 - 图3

Redis07 Feed流与两种数据类型 - 图4

Redis07 Feed流与两种数据类型 - 图5

Redis07 Feed流与两种数据类型 - 图6

Redis07 Feed流与两种数据类型 - 图7

Redis07 Feed流与两种数据类型 - 图8

  1. 127.0.0.1:6379> ZREVRANGEBYSCORE z1 1000 0 WITHSCORES LIMIT 0 3
  2. 根据分数查询z1 最大当前时间 最小时间 显示分数 0开始查3
  3. 127.0.0.1:6379> ZREVRANGEBYSCORE z1 5 0 WITHSCORES LIMIT 1 3
  4. 上次查询最小的是 5 所以最大从5 开始最小值不管 然后 LIMIE 后面找下一个 不包括5 所以为1 查询3

根据Zset查询 因为第一次查询没有上一次的最小值 所以为当前时间戳 然后LIMIT开始给0

滚动查询 每一次查询上一次查询的最小值(分数)开始 防止Feed新消息导致消息混乱

但是当查询到的元素有score一样的情况 任然会混乱 所以LIMIT跳过的应当为上一次查询的最小值重复的个数

Redis07 Feed流与两种数据类型 - 图9

Redis07 Feed流与两种数据类型 - 图10

Controller
  1. @GetMapping("of/follow")
  2. public Result getFeedBlog(
  3. @RequestParam("lastId") Long max, // 上一次查询的最小值的时间戳
  4. @RequestParam(value = "offset", defaultValue = "0") Integer offset// 偏移量 第一次没有 默认0
  5. ) {
  6. return followService.queryFeedById(max, offset);
  7. }

Service
  1. public Result queryFeedById(Long max, Integer offset) {
  2. // 获取当前用户id
  3. Long userId = UserHolder.getUser().getId();
  4. if (userId == null) {
  5. return Result.fail("请登录!");
  6. }
  7. String key = BlogConst.FAN_INBOX + userId;
  8. // 查询收件箱
  9. Set<ZSetOperations.TypedTuple<String>> blogIdByScores = stringRedisTemplate.opsForZSet()
  10. .reverseRangeByScoreWithScores(key, 0, max, offset, 3);
  11. if (blogIdByScores == null || blogIdByScores.isEmpty()) {
  12. return Result.ok();
  13. }
  14. // 解析数据
  15. List<Long> ids = new ArrayList<>(blogIdByScores.size());
  16. long minTime = 0; // 这个Set底层是TreeSet
  17. int os = 1; // 初始化为1
  18. for (ZSetOperations.TypedTuple<String> blogIdByScore : blogIdByScores) {
  19. String idStr = blogIdByScore.getValue();
  20. long time = blogIdByScore.getScore().longValue(); // 5 4 4 2 2 不置为1的话offset为 4 就错了
  21. if (time == minTime) {
  22. os++;
  23. } else {
  24. minTime = time;
  25. os = 1;
  26. }
  27. ids.add(Long.valueOf(idStr));
  28. }
  29. // 根据id查询blog
  30. List<Blog> blogs = query()
  31. .in("id",ids)
  32. .last("ORDER BY FIELD( id"+StrUtil.join(",",ids)+")")
  33. .list();
  34. blogs.forEach(blog -> {
  35. queryBlogUser(blog); // 查询有关用户
  36. isBlogLiked(blog); // 查询是否被点赞
  37. });
  38. // 封装返回
  39. ScoreResult<Blog> result = new ScoreResult<>(blogs,minTime,offset);
  40. return Result.ok(result);
  41. }

4 附近商铺

4.1 GEO数据类型

Redis07 Feed流与两种数据类型 - 图11

Redis07 Feed流与两种数据类型 - 图12

  1. $ 1
  2. GEOADD g1 116.378248 39.865275 bjn 116.42803 39.903738 bj 116.322287 39.893729 bjx
  3. $ 2
  4. 127.0.0.1:6379> GEODIST g1 bjx bj
  5. "9091.5648"
  6. 127.0.0.1:6379> GEODIST g1 bjx bj KM
  7. "9.0916"
  8. $ 3
  9. 127.0.0.1:6379> GEOSEARCH g1 FROMLONLAT 116.397904 39.909005 BYRADIUS 10 km WITHDIST
  10. 1) 1) "bj"
  11. 2) "2.6361"
  12. 2) 1) "bjn"
  13. 2) "5.1452"
  14. 3) 1) "bjx"
  15. 2) "6.6723"
  16. GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius M|KM|FT|MI] [BYBOX width height M|KM|FT|MI] [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
  17. summary: Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle.
  18. since: 6.2.0

4.2 java

  1. @Test
  2. void shopImportGeo() {
  3. Map<Long, List<Shop>> listMap = shopService.list().stream().collect(Collectors.groupingBy(Shop::getTypeId));
  4. for (Map.Entry<Long, List<Shop>> entry : listMap.entrySet()) {
  5. Long typeId = entry.getKey();
  6. List<Shop> shopList = entry.getValue();
  7. List<RedisGeoCommands.GeoLocation<String>> loctions = shopList
  8. .stream()
  9. .map(shop ->
  10. new RedisGeoCommands.GeoLocation<>(shop.getId().toString(), new Point(shop.getX(), shop.getY())))
  11. .collect(Collectors.toList());
  12. redisTemplate.opsForGeo().add(CacheConst.SHOP_GEO_PREFIX + typeId, loctions);
  13. }
  14. }

Redis07 Feed流与两种数据类型 - 图13

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <groupId>org.springframework.data</groupId>
  7. <artifactId>spring-data-redis</artifactId>
  8. </exclusion>
  9. <exclusion>
  10. <groupId>io.lettuce</groupId>
  11. <artifactId>lettuce-core</artifactId>
  12. </exclusion>
  13. </exclusions>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework.data</groupId>
  17. <artifactId>spring-data-redis</artifactId>
  18. <version>2.6.2</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>io.lettuce</groupId>
  22. <artifactId>lettuce-core</artifactId>
  23. <version>6.1.8.RELEASE</version>
  24. </dependency>
  1. @Override
  2. public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
  3. // 1.判断是否需要根据坐标查询
  4. if (x == null || y == null) {
  5. // 不需要坐标查询,按数据库查询
  6. Page<Shop> page = query()
  7. .eq("type_id", typeId)
  8. .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
  9. // 返回数据
  10. return Result.ok(page.getRecords());
  11. }
  12. // 2.计算分页参数
  13. int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
  14. int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
  15. // 3.查询redis、按照距离排序、分页。结果:shopId、distance
  16. String key = SHOP_GEO_KEY + typeId;
  17. GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() // GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE
  18. .search(
  19. key,
  20. GeoReference.fromCoordinate(x, y),
  21. new Distance(5000),
  22. // RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs() e
  23. RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
  24. );
  25. // 4.解析出id
  26. if (results == null) {
  27. return Result.ok(Collections.emptyList());
  28. }
  29. List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
  30. if (list.size() <= from) {
  31. // 没有下一页了,结束
  32. return Result.ok(Collections.emptyList());
  33. }
  34. // 4.1.截取 from ~ end的部分
  35. List<Long> ids = new ArrayList<>(list.size());
  36. Map<String, Distance> distanceMap = new HashMap<>(list.size());
  37. list.stream().skip(from).forEach(result -> {
  38. // 4.2.获取店铺id
  39. String shopIdStr = result.getContent().getName();
  40. ids.add(Long.valueOf(shopIdStr));
  41. // 4.3.获取距离
  42. Distance distance = result.getDistance();
  43. distanceMap.put(shopIdStr, distance);
  44. });
  45. // 5.根据id查询Shop
  46. String idStr = StrUtil.join(",", ids);
  47. List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
  48. for (Shop shop : shops) {
  49. shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
  50. }
  51. // 6.返回
  52. return Result.ok(shops);
  53. }

5 BitMap

Redis07 Feed流与两种数据类型 - 图14

Redis07 Feed流与两种数据类型 - 图15

  1. 11110000
  2. BITFIELD b1 GET u2 0
  3. # BITFIELD key GET u(无符号)2(获取几位) 0(从0开始)
  4. # 获取到 11 转换10进制就是3

5.1 Java

Redis07 Feed流与两种数据类型 - 图16

5.2 统计签到

Redis07 Feed流与两种数据类型 - 图17

从当前时间判断之前的时间里是否为0 为0表示断签

运算符 https://cloud.tencent.com/developer/article/1338265

  1. Long userId = user.getId();
  2. LocalDate now = LocalDate.now();
  3. String key = CacheConst.getCurrentUserSign(now, userId);
  4. // 返回签到记录 为十进制数字
  5. List<Long> result = redisTemplate.opsForValue().bitField(
  6. key,
  7. BitFieldSubCommands
  8. .create()
  9. .get(BitFieldSubCommands.BitFieldType.unsigned(now.getDayOfMonth()))
  10. .valueAt(0)
  11. );
  12. if (result == null || result.isEmpty()) {
  13. return Result.ok(0);
  14. }
  15. // 循环遍历
  16. Long num = result.get(0);
  17. int count = 0;
  18. if (num == null || num == 0) {
  19. return Result.ok(0);
  20. }
  21. // 与1做与运算 得到数字最后一个Bit位
  22. while (true) {
  23. if ((num & 1) == 0) {
  24. // 为0 直接结束
  25. break;
  26. }else {
  27. // 不为0 计数加一
  28. count++;
  29. }
  30. // 数字右移动
  31. num >>>= 1;
  32. }
  33. // 判断bit位是否为0 不为0 加1
  34. return Result.ok(count);

& 按位与运算 将两个数字的二进制位做对比 都为1 才是一

110111 55

111000 56

=

110000 48 55&56=48

\n

1100011 99

10100 20 [不足左边补0]

=

0000000 0 99&20=0

| 按位或 有一边为1就为1

1010 10

001 1

=

1011 11

5 101

9 1000

=

13 1101

6 HyperLogLog

Redis07 Feed流与两种数据类型 - 图18

Redis07 Feed流与两种数据类型 - 图19

Redis07 Feed流与两种数据类型 - 图20