Redis实际应用4

文章的推送

在这里插入图片描述
在这里插入图片描述
由于智能排序需要算法等 ,该项目只能适用于最简单的Timeline模式

Timeline模式

拉模式

实现思路:
就是关注的对象发布了动态,粉丝在去他们的发件箱拉取出来,按时间进行排序,显示到自己的收件箱里面
在这里插入图片描述

推模式

实现思路:
博主发布动态直接推给关注了自己的粉丝
在这里插入图片描述

推拉结合模式

实现思路:
主要是区分普通粉丝和活跃粉丝,活跃的粉丝采取推的模式(因为拉的模式有延迟,而活跃粉丝喜欢经常刷新),普通的粉丝采取拉的模式,比如一些僵尸粉丝就完全可以不消耗任何内存来进行存放。
在这里插入图片描述

三种模式的对比

在这里插入图片描述
由于该项目,用户量达不到千万级别,所以采用推的模式相对更好。

推模式的代码实现:

看需求:

代码就不写了 逻辑还是那样
在这里插入图片描述
根据需求选择合适的数据类型,涉及到排序我们可以选择Listsortedlist.

选取的依据:

我们后续涉及到分页的实现:我们在进行分页查询的时候,会有up主进行发布消息,如果你关注的很多,那这个频率是很快的,所以如果采用传统的分页模式会有很大的影响。如下图所示:
在这里插入图片描述
应该采用滚动的方式,进行数据的读取
在这里插入图片描述

原因

如果采用list 只能左右出去,然后你取的时候是给下标的,此时如果有新的进来,必然会扰乱你的下标。而List又没有其他的方式进行读取。所以不合适。
相对的采用soterlist,他可以根据你传的分值进行排序,并且取得时候,可以根据分值得范围进行选取,只要记录你上次取得分值(时间戳)从他下面取对应得size即可。

解决:

这是采用角标进行查询:如果遇到更新则会发生这种情况 不可取
在这里插入图片描述
这是采用分数进行查询得:避免了重复刷新得问题。
语法
在这里插入图片描述

1000到时候 换成当时得时间戳即可
limit 最后那个是 数量嘛 前面得那个是偏移量
比如 0表示 1000-0 左闭右闭
1得话 就是偏移一个 左开右闭。

在这里插入图片描述
还是有可能出现错误
如果出现了相同值 ,比如下面这个两个6 偏移量1 他还会出现6
因为他检索到第一个6就停止了 +偏移量输出后面得
在这里插入图片描述
参数得规律:
在这里插入图片描述

代码
   @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        String key = FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 3.非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        // 4.解析数据:blogId、minTime(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0; // 2
        int os = 1; // 2
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
            // 4.1.获取id
            ids.add(Long.valueOf(tuple.getValue()));
            // 4.2.获取分数(时间戳)
            long time = tuple.getScore().longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }

        // 5.根据id查询blog
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

        for (Blog blog : blogs) {
            // 5.1.查询blog有关的用户
            queryBlogUser(blog);
            // 5.2.查询blog是否被点赞
            isBlogLiked(blog);
        }

        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);

        return Result.ok(r);
    }

附近商店

采用得数据结构

在这里插入图片描述
GEO底层就是一个sortedSet,输进去得经纬度它通过自己得算法,转换成分值,需要计算得时候在转换成经纬度。
在这里插入图片描述

代码:

 @Override
    public 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、distance
        String 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.解析出id
        if (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的部分
        List<Long> ids = new ArrayList<>(list.size());
        Map<String, Distance> distanceMap = new HashMap<>(list.size());
        list.stream().skip(from).forEach(result -> {
            // 4.2.获取店铺id
            String shopIdStr = result.getContent().getName();
            ids.add(Long.valueOf(shopIdStr));
            // 4.3.获取距离
            Distance distance = result.getDistance();
            distanceMap.put(shopIdStr, distance);
        });
        // 5.根据id查询Shop
        String 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());
        }
        // 6.返回
        return Result.ok(shops);
    }

签到

采用得数据结构:

由于使用数据库进行存放得话,会有过多的内存消耗,所以采用redis缓存进行存放:
在这里插入图片描述

BitMap:
采用年月一起作为key进行存放,记录31位1,0即可。
在这里插入图片描述

基本用法:

在这里插入图片描述

需求:

在这里插入图片描述

签到得代码:

 @Override
    public Result sign() {
        // 1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3.拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;
        // 4.获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.写入Redis SETBIT key offset 1
        stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
        return Result.ok();
    }

签到统计

就是统计从指定得天数开始,连续签了几天 qq达人那种断了就没了
在这里插入图片描述

代码:

@Override
    public Result signCount() {
        // 1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3.拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;
        // 4.获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
        List<Long> result = stringRedisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (result == null || result.isEmpty()) {
            // 没有任何签到结果
            return Result.ok(0);
        }
        Long num = result.get(0);
        if (num == null || num == 0) {
            return Result.ok(0);
        }
        // 6.循环遍历
        int count = 0;
        while (true) {
            // 6.1.让这个数字与1做与运算,得到数字的最后一个bit位  // 判断这个bit位是否为0
            if ((num & 1) == 0) {
                // 如果为0,说明未签到,结束
                break;
            }else {
                // 如果不为0,说明已签到,计数器+1
                count++;
            }
            // 把数字右移一位,抛弃最后一个bit位,继续下一个bit位
            num >>>= 1;
        }
        return Result.ok(count);
    }

UV统计(用户访问量,一个人记一次)

就像那QQ空间得那个访问量一样
与之相对得是PV统计:
在这里插入图片描述

采用得数据结构

也可以用set进行存放,但是由于访问量这个基数会很大,所以采用HyperLogLog进行存放,但是这个方法虽然内存占用小但是存在0.81%得误差,如果需求是要求不允许出现这么大误差得话,建议使用set。
在这里插入图片描述
基本得用法实列,和set差不多也是唯一性。
在这里插入图片描述

代码实现:

模拟插入

    @Test
    void 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){
                // 发送到Redis
                stringRedisTemplate.opsForHyperLogLog().add("Code-zyc", values);
            }
        }
        // 统计数量
        Long count = stringRedisTemplate.opsForHyperLogLog().size("Code-zyc");
        System.out.println("count = " + count);
    }

HyperLogLog插入之前的内存:

在这里插入图片描述

HyperLogLog插入之后的内存以及结果:

运行结果:
在这里插入图片描述
运行后内存:
在这里插入图片描述
对于一个key而言 确实只占用了14kb的内存
在这里插入图片描述

Set插入之前的内存:

代码看一下 证明真测试了
在这里插入图片描述
内存情况:
在这里插入图片描述

Set插入之后的内存以及结果:

结果:完全正确 没有统计错误
在这里插入图片描述

内存情况:
在这里插入图片描述
这个就不用计算机算了吧,这完全以及很大了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值