redis之zset在实际项目中的应用

背景
最近接到个需求,要求汇总用户一个月内在不同的供应商处购买的商品sku(上限1000条),去重后按照购买时间去倒序排序,当用户再次在对应的供应商处购买商品时,弹出之前的购买sku列表。
分析
这个需求,简化后就是要求对用户(User)和供应商(Supplier) 做一个唯一键,在这个唯一键上,按照时间倒序去汇总好去重后的(Sku)。略微思考,使用redis的zset可以比Mysql,或者es更为方便处理。zset本身就能实现去重的功能,且对于同一元素进行重复赋值,会更新score;排序可以通过使用购买时间的timestamp作为score 来实现;保证一个唯一键只有一个月内1000条历史数据,可以通过zremrangebyscore和zremrangebyrank两个zset自带方法实现。
核心代码

简化后的商品sku购买历史数据库DO

public class SccPurchaseDetailDO implements Serializable {
        /**
         * ID
         */
        private Long id;

        /**
         * 订单号
         */
        private String bizOrderNo;

        /**
         * 商品行ID, 同一个订单号, 可以采购多条sku, itemId用于区分该订单号下不同sku购买排序
         */
        private Integer itemId;

        /**
         * 二级类目id
         */
        private Long skuId;

        /**
         * 创建时间
         */
        private Date gmtCreate;
    }

简化后的zset缓存计算逻辑

/**
     * 创建zset的key - 含供货商信息
     *
     * @param userId 用户id
     * @param supplierEntId 供应商企业id 
     * @return
     */
    private String makePayeeZSetKey(Long userId, Long supplierEntId) {
        String key = userId + "t" + supplierEntId;
        return RedisConstance.PURCHASE_DETAIL_ITEM_STATISTIC_ZSET_KEY + Md5Utils.getMD5(key.getBytes());
    }

    /**
     * 将购买记录存入zset
     * 
     * 对于同一个批次的购买记录, 支付时间是相同的
     * @param detailList  在一次购买中,所有sku购买记录历史列表
     * @param userId 购买人
     * @param supplierEntId 供应商企业id
     * @param paidTime 此次购买的支付时间
     */
    private void handlePurchaseRecord(List<SccPurchaseDetailDO> detailList, Long userId, Long supplierEntId, Date paidTime) {
        if (detailList.isEmpty()) {
            return;
        }

        String payeeKey = makePayeeZSetKey(userId, supplierEntId);

        long finishTime = paidTime.getTime();

        Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
        for (SccPurchaseDetailDO d : detailList) {
            // itemId 从1-999, 代表此次购买中, 不同sku先后购买顺序
            // 这里留了3个位给同一个购买批次中的不同sku
            double score = finishTime * 1000 + d.getItemId() + 0.0D;
            ZSetOperations.TypedTuple<String> r = new DefaultTypedTuple<>(d.getSkuId().toString(), score);
            tuples.add(r);
        }
        
        redisTemplate.opsForZSet().add(payeeKey, tuples);
        // 防止那些就买一次的人, 数据一直存在redis中
        redisTemplate.expire(payeeKey, 30, TimeUnit.DAYS);
        // 删除过期数据
        redisTemplate.opsForZSet().removeRangeByScore(payeeKey, 0, oneMonthAgo * 1000 + 999);
        Long size = redisTemplate.opsForZSet().zCard(payeeKey);
        if (size != null && size > maxSize) {
            // 排序是时间越小的 越前面
            redisTemplate.opsForZSet().removeRange(payeeKey, 0, size - maxSize);
        }
    }

简化后的数据获取代码

// score是按照时间戳去赋值的
// 按照时间从近到远, 获取对应条数的sku采购历史
redisTemplate.opsForZSet().reverseRange(makePayeeZSetKey(request), start, end)

总结
redis的zset,在进行一些去重排序的工作时,是非常有用的,特别是对一个相同的zset中的元素,重复赋值,会更新score,省去了我们对于元素是否已经存在的判断。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值