掘金 后端 ( ) • 2024-04-24 10:40

概述:

原理:

RScoredSortedSet 是 Redisson 的 Java 对象,它封装了 Redis 的有序集合(sorted set)功能。Redis 的有序集合是一种特殊的数据结构,它可以存储一组不重复的元素,并且每个元素都关联一个浮点数分数。这些元素根据分数进行排序,并且可以快速访问整个集合的任何一部分。

Redis 的有序集合使用了一种名为“跳跃列表”(skiplist)的数据结构,这使得它在保持元素排序的同时,能够提供对数据的快速访问。跳跃列表是一种概率平衡数据结构,它允许平均时间复杂度为 O(log N) 的搜索、插入和删除操作。

在 Redisson 中,RScoredSortedSet 提供了一系列方法来与 Redis 的有序集合交互,包括添加元素、删除元素、获取元素的排名、迭代元素等。Redisson 还提供了异步和反应式的 API,以支持非阻塞的编程模式。

RScoredSortedSet 优点

  1. 性能:由于 Redis 的有序集合使用跳跃列表实现,它能够在对数时间内完成插入、删除和查找操作,这对于大数据集来说非常高效。

  2. 排序和范围查询:RScoredSortedSet 自然地支持排序,可以快速检索分数在特定范围内的元素,这对于排行榜和范围查询非常有用。

  3. 唯一性:集合中的每个元素都是唯一的,即使是在并发环境下也能保证元素不会重复。

  4. 分布式环境:由于 Redis 是一个分布式缓存,RScoredSortedSet 可以很好地在分布式系统中使用,提供跨多个客户端的共享访问。

  5. 扩展性:Redis 的数据结构设计使得它可以很好地扩展到处理大量数据,同时保持高性能。

RScoredSortedSet 缺点

  1. 内存限制:由于 Redis 是基于内存的,存储在 RScoredSortedSet 中的数据集大小受限于服务器的内存容量。

  2. 持久性:虽然 Redis 提供了持久化机制,但它可能不如传统的关系型数据库系统那样健壮,特别是在处理大量写操作时。

  3. 复杂性:对于需要持久化复杂关系数据的应用,Redis 的数据模型可能需要开发者手动处理数据的关联和事务。

  4. 成本:对于需要大量内存来存储数据的应用,使用 Redis 可能会比使用传统的磁盘存储数据库系统更昂贵。

  5. 数据一致性:在分布式环境中,如果不是使用单实例模式,保持数据一致性可能需要额外的策略和配置。

在使用 RScoredSortedSet 时,需要根据应用场景权衡它的优缺点,并考虑是否适合您的具体需求。

RScoredSortedSet 是 Redisson 提供的一种数据结构,它对应 Redis 中的有序集合(sorted set)。有序集合是一种非常强大的数据结构,可以用来存储具有排序分数的唯一元素集合。这些元素根据分数进行排序,可以非常快速地执行插入、删除、更新和查找操作。

RScoredSortedSet 的实战应用场景和代码示例:

场景 1: 排行榜系统

在游戏或社交应用中,经常需要展示用户的排行榜,比如根据玩家的分数或者用户的影响力进行排名。

    @Autowired
    private RedissonClient redissonClient;

    public void addUserScore(String username, double score) {
        RScoredSortedSet<String> leaderboard = redissonClient.getScoredSortedSet("leaderboard");
        leaderboard.addScore(username, score);
    }

    public Collection<String> getTopUsers(int topN) {
        RScoredSortedSet<String> leaderboard = redissonClient.getScoredSortedSet("leaderboard");
        return leaderboard.entryRangeReversed(0, topN - 1).stream()
                .map(ScoredEntry::getValue)
                .collect(Collectors.toList());
    }

企业微信截图_17139215491426.png

在这个场景中,addUserScore 方法用于添加或更新用户的分数,而 getTopUsers 方法用于获取排名前 N 的用户列表。

场景 2: 时间序列数据

有序集合可以用来按时间排序记录事件,例如用户的登录时间或者商品的销售时间。

    @Autowired
    private RedissonClient redissonClient;

    public void recordEvent(String eventId, long timestamp) {
        RScoredSortedSet<String> timeSeries = redissonClient.getScoredSortedSet("events");
        timeSeries.add(timestamp, eventId);
    }

    public Collection<String> getRecentEvents(int count) {
        RScoredSortedSet<String> timeSeries = redissonClient.getScoredSortedSet("events");
        return timeSeries.entryRangeReversed(0, count - 1).stream()
                .map(ScoredEntry::getValue)
                .collect(Collectors.toList());
    }

企业微信截图_17139216034338.png

在这个例子中,recordEvent 方法用于记录事件及其发生的时间戳,而 getRecentEvents 方法用于获取最近发生的事件。

场景 3: 优先级队列

在任务调度或消息队列系统中,有序集合可以用来实现优先级队列。

@Autowired
private RedissonClient redissonClient;

public void submitTask(String taskId, double priority) {
    RScoredSortedSet<String> priorityQueue = redissonClient.getScoredSortedSet("tasks");
    priorityQueue.add(priority, taskId);
}

public String fetchNextTask() {
    RScoredSortedSet<String> priorityQueue = redissonClient.getScoredSortedSet("tasks");
    return priorityQueue.pollFirst();
}

在这个场景中,submitTask 方法用于将任务添加到优先级队列中,而 fetchNextTask 方法用于获取并移除具有最高优先级的任务。

场景 4: 商品价格排序

电商平台可能需要根据商品的价格进行排序,以便用户可以快速找到最便宜的商品。

@Autowired
private RedissonClient redissonClient;

![企业微信截图_1713921628760.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b679bf20e44e4cb6be8b9ee7e6e369ef~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1079&h=291&s=38282&e=png&b=2c2c2c)
public void addOrUpdateProduct(String productId, double price) {
    RScoredSortedSet<String> productPrices = redissonClient.getScoredSortedSet("productPrices");
	  productPrices.add(price, productId);
}

![企业微信截图_1713921628760.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c6e5c50fdac4894ad555c0e7fc85f1f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1079&h=291&s=38282&e=png&b=2c2c2c)
public Collection<String> getCheapestProducts(int count) {
    RScoredSortedSet<String> productPrices = redissonClient.getScoredSortedSet("productPrices");
    return productPrices.entryRange(0, count - 1).stream()
                        .map(Map.Entry::getValue)
                        .collect(Collectors.toList());
}

企业微信截图_1713921628760.png 在这个例子中,addOrUpdateProduct 方法用于添加或更新商品的价格,而 getCheapestProducts 方法用于获取价格最低的商品列表。

这些示例展示了 RScoredSortedSet 在不同场景下的应用。由于 Redisson 的 RScoredSortedSet 是基于 Redis 的有序集合,它可以高效地处理大量数据,并且由于 Redis 的性质,这些操作可以跨多个客户端和服务实例进行。

扩展:RScoredSortedSet 在实际应用中的例子:

实时排行榜

在游戏或社交应用中,根据用户的分数或者成就来显示实时排行榜。

@Autowired
private RedissonClient redissonClient;

    // 添加或更新用户分数
    public void updateScore(String user, double score) {
        RScoredSortedSet<String> scoredSortedSet = redissonClient.getScoredSortedSet("userScores");
        scoredSortedSet.add(score, user);
    }

    // 获取前10名用户及其分数
    public Map<String, Double> getTop10Users() {
        RScoredSortedSet<String> scoredSortedSet = redissonClient.getScoredSortedSet("userScores");
        return scoredSortedSet.entryRangeReversed(0, 9).stream()
                .collect(Collectors.toMap(ScoredEntry::getValue, ScoredEntry::getScore));
    }

image.png

延时队列

使用有序集合实现延时队列,元素根据需要执行的时间排序。

@Autowired
private RedissonClient redissonClient;

// 提交延时任务
public void scheduleTask(String taskId, long delayInSeconds) {
    RScoredSortedSet<String> delayQueue = redissonClient.getScoredSortedSet("delayQueue");
    double score = System.currentTimeMillis() / 1000.0 + delayInSeconds;
    delayQueue.add(score, taskId);
}

// 处理到期的任务
public void processDueTasks() {
    RScoredSortedSet<String> delayQueue = redissonClient.getScoredSortedSet("delayQueue");
    long now = System.currentTimeMillis() / 1000;
    Collection<String> dueTasks = delayQueue.valueRange(0, true, now, true);
    for (String taskId : dueTasks) {
        // 处理任务
        processTask(taskId);
        // 从队列中移除
        delayQueue.remove(taskId);
    }
}

image.png

地理位置服务

根据用户的地理位置信息,显示周围的兴趣点或者商家排名。

@Autowired
private RedissonClient redissonClient;

// 添加地点及其分数(例如根据距离计算)
public void addLocation(String locationId, double score) {
    RScoredSortedSet<String> locations = redissonClient.getScoredSortedSet("locations");
    locations.add(score, locationId);
}

// 获取附近的地点
public Collection<String> getNearbyLocations(double minScore, double maxScore) {
    RScoredSortedSet<String> locations = redissonClient.getScoredSortedSet("locations");
    return locations.valueRange(minScore, true, maxScore, true);
}

企业微信截图_1713921812340.png

限流器

实现 API 请求的限流,确保在一定时间内只允许一定数量的请求。

@Autowired
private RedissonClient redissonClient;

// 尝试获取访问权限
public boolean tryAcquire(String api, int maxCalls, long timePeriodSeconds) {
    String key = "rateLimiter:" + api;
    RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);
    long now = System.currentTimeMillis();
    long clearBefore = now - timePeriodSeconds * 1000;
    
    // 移除时间窗口之前的数据
    scoredSortedSet.removeRangeByScore(0, true, clearBefore, true);
    
    // 检查当前调用数是否超过限制
    if (scoredSortedSet.size() < maxCalls) {
        // 没有超过限制,记录当前请求
        scoredSortedSet.add(now, now);
        return true;
    }
    return false;
}

企业微信截图_17139218356635.png

扩展总结:

这些实战例子展示了 RScoredSortedSet 在不同应用场景中的灵活性和实用性。通过 Redisson 提供的 API,开发者可以方便地实现这些功能,同时利用 Redis 的性能和可扩展性。

RScoredSortedSet 的高级应用通常涉及复杂的业务逻辑和数据处理,这些应用的特点是它们不仅仅使用基本的添加和检索操作,而是结合了有序集合的特性来实现特定的业务需求。

高级实战应用的示例:

实时数据分析和监控

在金融或物联网(IoT)领域,实时监控和分析数据流非常重要。RScoredSortedSet 可以用来存储时间序列数据,并实时计算滑动窗口内的统计信息。

@Autowired
private RedissonClient redissonClient;

// 记录交易数据
public void recordTransaction(String transactionId, double amount, long timestamp) {
    RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
    transactions.add(new Transaction(transactionId, amount, timestamp), timestamp);
}

// 获取最近一分钟内的平均交易额
public double getAverageTransactionAmountLastMinute() {
    long oneMinuteAgo = System.currentTimeMillis() - 60000;
    RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
    Collection<Transaction> recentTransactions = transactions.valueRange(oneMinuteAgo, true, Double.POSITIVE_INFINITY, true);
    
    return recentTransactions.stream()
            .mapToDouble(Transaction::getAmount)
            .average()
            .orElse(0.0);
}

在这个例子中,我们使用 RScoredSortedSet 来存储交易数据,并利用分数(这里是时间戳)来获取最近一分钟内的交易,然后计算平均交易额。

虚拟货币交易平台的订单簿

虚拟货币交易平台需要管理大量的买卖订单,RScoredSortedSet 可以用来作为订单簿,其中买单和卖单根据价格排序。

@Autowired
private RedissonClient redissonClient;

// 提交买单
public void submitBuyOrder(String orderId, double price) {
    RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
    buyOrders.add(price, new Order(orderId, price));
}

// 提交卖单
public void submitSellOrder(String orderId, double price) {
    RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");
    sellOrders.add(price, new Order(orderId, price));
}

// 匹配订单
public void matchOrders() {
    RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
    RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");
    
    Order highestBuyOrder = buyOrders.first();
    Order lowestSellOrder = sellOrders.first();
    
    while (highestBuyOrder != null && lowestSellOrder != null && highestBuyOrder.getPrice() >= lowestSellOrder.getPrice()) {
        // 执行交易逻辑
        executeTrade(highestBuyOrder, lowestSellOrder);
        // 移除已匹配的订单
        buyOrders.remove(highestBuyOrder);
        sellOrders.remove(lowestSellOrder);
        // 更新订单
        highestBuyOrder = buyOrders.first();
        lowestSellOrder = sellOrders.first();
    }
}

在这个例子中,我们使用了两个 RScoredSortedSet,一个用于买单,一个用于卖单。通过比较最高买价和最低卖价,我们可以匹配订单并执行交易。

社交网络中的动态时间线

社交网络中,用户的时间线通常需要根据动态的发布时间来排序。

@Autowired
private RedissonClient redissonClient;

// 发布动态
public void postStatus(String userId, String statusId, long timestamp) {
    RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
    timeline.add(timestamp, statusId);
}

// 获取用户的时间线
public Collection<String> getUserTimeline(String userId, int page, int pageSize) {
    RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
    int startIndex = page * pageSize;
    int endIndex = (page + 1) * pageSize - 1;
    return timeline.entryRangeReversed(startIndex, endIndex).stream()
                   .map(Map.Entry::getValue)
                   .collect(Collectors.toList());
}

在这个例子中,我们为每个用户创建了一个 RScoredSortedSet,用于存储他们的动态。当需要获取用户时间线时,我们可以根据时间戳分数反向查询动态。

这些高级应用示例展示了 RScoredSortedSet 在解决实际问题中的强大功能。通过 Redisson 提供的丰富API,开发者可以构建出高效、可扩展的数据处理解决方案。

RScoredSortedSet 的高级应用通常涉及复杂的业务逻辑和数据处理,这些应用的特点是它们不仅仅使用基本的添加和检索操作,而是结合了有序集合的特性来实现特定的业务需求。以下是一些高级实战应用的示例:### 实时数据分析和监控

在金融或物联网(IoT)领域,实时监控和分析数据流非常重要。RScoredSortedSet 可以用来存储时间序列数据,并实时计算滑动窗口内的统计信息。

@Autowired
private RedissonClient redissonClient;

// 记录交易数据
public void recordTransaction(String transactionId, double amount, long timestamp) {
    RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
    transactions.add(new Transaction(transactionId, amount, timestamp), timestamp);
}

// 获取最近一分钟内的平均交易额
public double getAverageTransactionAmountLastMinute() {
    long oneMinuteAgo = System.currentTimeMillis() - 60000;
    RScoredSortedSet<Transaction> transactions = redissonClient.getScoredSortedSet("transactions");
    Collection<Transaction> recentTransactions = transactions.valueRange(oneMinuteAgo, true, Double.POSITIVE_INFINITY, true);
    
    return recentTransactions.stream()
            .mapToDouble(Transaction::getAmount)
            .average()
            .orElse(0.0);
}

在这个例子中,我们使用 RScoredSortedSet 来存储交易数据,并利用分数(这里是时间戳)来获取最近一分钟内的交易,然后计算平均交易额。

虚拟货币交易平台的订单簿

虚拟货币交易平台需要管理大量的买卖订单,RScoredSortedSet 可以用来作为订单簿,其中买单和卖单根据价格排序。

@Autowired
private RedissonClient redissonClient;

// 提交买单
public void submitBuyOrder(String orderId, double price) {
    RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
    buyOrders.add(price, new Order(orderId, price));
}

// 提交卖单
public void submitSellOrder(String orderId, double price) {
    RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");
    sellOrders.add(price, new Order(orderId, price));
}

// 匹配订单
public void matchOrders() {
    RScoredSortedSet<Order> buyOrders = redissonClient.getScoredSortedSet("buyOrders");
    RScoredSortedSet<Order> sellOrders = redissonClient.getScoredSortedSet("sellOrders");
    
    Order highestBuyOrder = buyOrders.first();
    Order lowestSellOrder = sellOrders.first();
    
    while (highestBuyOrder != null && lowestSellOrder != null && highestBuyOrder.getPrice() >= lowestSellOrder.getPrice()) {
        // 执行交易逻辑
        executeTrade(highestBuyOrder, lowestSellOrder);
        // 移除已匹配的订单
        buyOrders.remove(highestBuyOrder);
        sellOrders.remove(lowestSellOrder);
        // 更新订单
        highestBuyOrder = buyOrders.first();
        lowestSellOrder = sellOrders.first();
    }
}

在这个例子中,我们使用了两个 RScoredSortedSet,一个用于买单,一个用于卖单。通过比较最高买价和最低卖价,我们可以匹配订单并执行交易。

社交网络中的动态时间线

社交网络中,用户的时间线通常需要根据动态的发布时间来排序。

@Autowired
private RedissonClient redissonClient;

// 发布动态
public void postStatus(String userId, String statusId, long timestamp) {
    RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
    timeline.add(timestamp, statusId);
}

// 获取用户的时间线
public Collection<String> getUserTimeline(String userId, int page, int pageSize) {
    RScoredSortedSet<String> timeline = redissonClient.getScoredSortedSet("timeline:" + userId);
    int startIndex = page * pageSize;
    int endIndex = (page + 1) * pageSize - 1;
    return timeline.entryRangeReversed(startIndex, endIndex).stream()
                   .map(Map.Entry::getValue)
                   .collect(Collectors.toList());
}

在这个例子中,我们为每个用户创建了一个 RScoredSortedSet,用于存储他们的动态。当需要获取用户时间线时,我们可以根据时间戳分数反向查询动态。

这些高级应用示例展示了 RScoredSortedSet 在解决实际问题中的强大功能。通过 Redisson 提供的丰富API,开发者可以构建出高效、可扩展的数据处理解决方案。