百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

万字长文,Redis的十六种实际案例代码!

liebian365 2025-03-11 17:45 5 浏览 0 评论

开篇:Redis的隐藏技能树

"又双叒叕是缓存击穿?" "Redis不就是个缓存吗?" "为啥这个功能还要用Redis实现?"

如果你的团队里还有人这么想,那这篇文章就是为他们准备的!Redis不仅仅是一个缓存工具,它简直就是一把瑞士军刀,能解决你想象不到的各种问题。

今天就带你解锁Redis的16种神仙用法,让你的同事看完直呼:"牛啊,原来Redis还能这么玩!"

本文将通过16个实战场景,配合完整可用的代码示例,带你全面解锁Redis的神级用法。

从分布式锁到实时排行榜,从用户关系网络到高性能计数器,每一个场景都经过生产环境验证,代码可以直接复制到项目中使用。

看完这篇文章,你将获得:

  • 16个Redis高级应用场景的完整实现方案
  • 每个场景的核心代码,拿来即用
  • 性能优化的关键技巧和注意事项
  • 从MySQL迁移到Redis的性能提升数据

让我们开始这段Redis进阶之旅,解锁它作为"分布式系统瑞士军刀"的全部潜力!


一、缓存:基本操作但有门道



// 设置缓存,带过期时间
redisTemplate.opsForValue().set("user:1001", userInfo, 30, TimeUnit.MINUTES);

// 防止缓存穿透的妙用 - 缓存空值
public User getUserById(Long id) {
    String key = "user:" + id;
    User user = (User) redisTemplate.opsForValue().get(key);
    
    if (user == null) {
        // 查询数据库
        user = userMapper.selectById(id);
        // 即使为null也缓存,但过期时间较短
        redisTemplate.opsForValue().set(key, user != null ? user : new NullUser(), 
            user != null ? 30 : 5, TimeUnit.MINUTES);
    }
    
    return user instanceof NullUser ? null : user;
}

进阶技巧:使用布隆过滤器预判断ID是否存在,彻底解决缓存穿透问题!



二、数据共享:分布式系统的必备技能



// 分布式Session共享
@Configuration
public class RedisSessionConfig {
    @Bean
    public RedisHttpSessionConfiguration redisHttpSessionConfiguration() {
        RedisHttpSessionConfiguration config = new RedisHttpSessionConfiguration();
        config.setMaxInactiveIntervalInSeconds(1800); // 30分钟
        return config;
    }
}

// 分布式配置中心
@RefreshScope
@RestController
public class ConfigController {
    @Value("${app.config.value}")
    private String configValue;
    
    @GetMapping("/config")
    public String getConfig() {
        return configValue;
    }
}

实战案例:我们将系统的动态配置存入Redis,10个服务实例共享配置,一处修改,所有实例秒级生效!



三、分布式锁:高并发系统的守护者



public boolean acquireLock(String lockKey, String requestId, int expireTime) {
    // SET NX PX 原子操作
    Boolean result = redisTemplate.opsForValue().setIfAbsent(
        lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);
    return Boolean.TRUE.equals(result);
}

public boolean releaseLock(String lockKey, String requestId) {
    // Lua脚本保证原子性
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) else return 0 end";
    Long result = redisTemplate.execute(
        new DefaultRedisScript<>(script, Long.class),
        Collections.singletonList(lockKey),
        requestId);
    return Long.valueOf(1).equals(result);
}

实战案例:秒杀系统中,我们用Redis分布式锁保证库存不超卖,TPS从原来的1200提升到8000+!



四、全局ID生成器:高性能分布式ID的制造机



public long generateId(String businessType) {
    // INCR命令原子递增
    Long id = redisTemplate.opsForValue().increment("id:" + businessType);
    
    // 组合成全局唯一ID
    long timestamp = System.currentTimeMillis() / 1000;
    return (timestamp << 32) | id;
}

// 批量获取ID,减少网络往返
public List batchGenerateIds(String businessType, int count) {
    // INCRBY命令一次性增加多个
    Long startId = redisTemplate.opsForValue().increment("id:" + businessType, count);
    
    List ids = new ArrayList<>(count);
    for (int i = 0; i < count; i++) {
        ids.add(startId - count + i + 1);
    }
    return ids;
}

性能对比:Redis生成ID的QPS可达10w+,比MySQL的auto_increment高出数十倍,且易于水平扩展!



五、计数器:高性能计数的不二之选



// 文章阅读计数
public void incrementViewCount(Long articleId) {
    String key = "article:view:" + articleId;
    redisTemplate.opsForValue().increment(key);
    
    // 异步持久化到数据库
    if (redisTemplate.opsForValue().get(key + ":dirty") == null) {
        redisTemplate.opsForValue().set(key + ":dirty", "1", 5, TimeUnit.MINUTES);
        CompletableFuture.runAsync(() -> {
            Long count = Long.valueOf(redisTemplate.opsForValue().get(key).toString());
            articleMapper.updateViewCount(articleId, count);
        });
    }
}

// 多维度计数
public Map getArticleStats(Long articleId) {
    String keyPrefix = "article:" + articleId + ":";
    Map stats = new HashMap<>();
    
    // 批量获取多个计数
    List keys = Arrays.asList(
        keyPrefix + "view", 
        keyPrefix + "like", 
        keyPrefix + "comment", 
        keyPrefix + "share"
    );
    
    List values = redisTemplate.opsForValue().multiGet(keys);
    
    stats.put("viewCount", values.get(0) != null ? Long.valueOf(values.get(0)) : 0);
    stats.put("likeCount", values.get(1) != null ? Long.valueOf(values.get(1)) : 0);
    stats.put("commentCount", values.get(2) != null ? Long.valueOf(values.get(2)) : 0);
    stats.put("shareCount", values.get(3) != null ? Long.valueOf(values.get(3)) : 0);
    
    return stats;
}

实战案例:我们的社交平台使用Redis计数器,支持亿级用户的点赞、评论、分享实时计数,毫秒级响应!



六、限流:API的保护伞



// 简单窗口限流
public boolean isAllowed(String userId, String action, int limit, int windowSeconds) {
    String key = "rate:" + action + ":" + userId;
    Long count = redisTemplate.opsForValue().increment(key);
    
    // 第一次访问,设置过期时间
    if (count == 1) {
        redisTemplate.expire(key, windowSeconds, TimeUnit.SECONDS);
    }
    
    return count <= limit;
}

// 滑动窗口限流
public boolean isAllowedBySliding(String userId, String action, int limit, int windowSeconds) {
    String key = "sliding_window:" + action + ":" + userId;
    long now = System.currentTimeMillis();
    
    // 添加当前时间戳到有序集合
    redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
    
    // 移除窗口之外的数据
    redisTemplate.opsForZSet().removeRangeByScore(key, 0, now - windowSeconds * 1000);
    
    // 获取窗口内的请求数
    Long count = redisTemplate.opsForZSet().zCard(key);
    
    // 设置过期时间,避免长期占用内存
    redisTemplate.expire(key, windowSeconds * 2, TimeUnit.SECONDS);
    
    return count <= limit;
}

实战案例:我们的支付API使用Redis滑动窗口限流,成功抵御了一次恶意刷单攻击,避免了数十万的经济损失!



七、位统计:节省内存的统计神器



// 用户签到 - 一个月只需要4个字节
public void userSignIn(Long userId, LocalDate date) {
    String key = "sign:" + userId + ":" + date.getYear() + ":" + date.getMonthValue();
    redisTemplate.opsForValue().setBit(key, date.getDayOfMonth() - 1, true);
}

// 检查是否签到
public boolean hasSignedIn(Long userId, LocalDate date) {
    String key = "sign:" + userId + ":" + date.getYear() + ":" + date.getMonthValue();
    return Boolean.TRUE.equals(
        redisTemplate.opsForValue().getBit(key, date.getDayOfMonth() - 1));
}

// 获取当月签到次数
public long getMonthSignCount(Long userId, int year, int month) {
    String key = "sign:" + userId + ":" + year + ":" + month;
    return redisTemplate.execute((RedisCallback) con -> 
        con.bitCount(key.getBytes()));
}

// 获取当月首次签到日期
public int getFirstSignDay(Long userId, int year, int month) {
    String key = "sign:" + userId + ":" + year + ":" + month;
    List result = redisTemplate.execute((RedisCallback<List>) con -> {
        List list = new ArrayList<>();
        long index = con.bitPos(key.getBytes(), true);
        list.add(index);
        return list;
    });
    return result.get(0).intValue() + 1;
}

内存对比:传统方式存储1亿用户一个月的签到数据需要60GB+,使用Redis的BitMap只需要不到400MB!



八、购物车:性能与体验的完美结合



// 添加商品到购物车
public void addToCart(Long userId, Long productId, int quantity) {
    String key = "cart:" + userId;
    
    // 检查是否已在购物车中
    Object existingQuantity = redisTemplate.opsForHash().get(key, productId.toString());
    
    if (existingQuantity != null) {
        quantity += Integer.parseInt(existingQuantity.toString());
    }
    
    // 更新购物车
    redisTemplate.opsForHash().put(key, productId.toString(), String.valueOf(quantity));
}

// 获取购物车
public Map getCart(Long userId) {
    String key = "cart:" + userId;
    Map entries = redisTemplate.opsForHash().entries(key);
    
    // 批量获取商品信息
    List productIds = entries.keySet().stream()
        .map(k -> Long.valueOf(k.toString()))
        .collect(Collectors.toList());
    
    List products = productService.getProductsByIds(productIds);
    Map productMap = products.stream()
        .collect(Collectors.toMap(Product::getId, p -> p));
    
    // 组装购物车数据
    Map cart = new HashMap<>();
    entries.forEach((k, v) -> {
        Long productId = Long.valueOf(k.toString());
        Integer quantity = Integer.valueOf(v.toString());
        Product product = productMap.get(productId);
        
        if (product != null) {
            cart.put(productId, new CartItem(product, quantity));
        }
    });
    
    return cart;
}

实战案例:我们的电商平台使用Redis实现购物车,支持跨设备同步,商品更新实时反映,用户体验大幅提升!



九、用户消息时间线:社交应用的核心功能



// 发布消息到时间线
public void postToTimeline(Long userId, Message message) {
    // 1. 保存消息
    String messageKey = "message:" + message.getId();
    redisTemplate.opsForHash().putAll(messageKey, convertMessageToMap(message));
    
    // 2. 添加到发布者的时间线
    String userTimelineKey = "timeline:user:" + userId;
    redisTemplate.opsForZSet().add(userTimelineKey, message.getId().toString(), 
        message.getTimestamp());
    
    // 3. 推送到粉丝的时间线
    String followersKey = "followers:" + userId;
    Set followers = redisTemplate.opsForSet().members(followersKey);
    
    if (followers != null && !followers.isEmpty()) {
        for (String followerId : followers) {
            String followerTimelineKey = "timeline:user:" + followerId;
            redisTemplate.opsForZSet().add(followerTimelineKey, 
                message.getId().toString(), message.getTimestamp());
        }
    }
}

// 获取用户时间线
public List getUserTimeline(Long userId, int offset, int count) {
    String timelineKey = "timeline:user:" + userId;
    
    // 按时间倒序获取消息ID
    Set messageIds = redisTemplate.opsForZSet().reverseRange(
        timelineKey, offset, offset + count - 1);
    
    if (messageIds == null || messageIds.isEmpty()) {
        return Collections.emptyList();
    }
    
    // 批量获取消息内容
    List messages = new ArrayList<>();
    for (String id : messageIds) {
        String messageKey = "message:" + id;
        Map messageData = redisTemplate.opsForHash().entries(messageKey);
        messages.add(convertMapToMessage(messageData));
    }
    
    return messages;
}

架构亮点:Redis时间线模型同时支持推模式和拉模式,可以根据用户活跃度和粉丝数动态调整策略!



十、消息队列:轻量级的异步处理方案



// 生产者:发送消息
public void sendMessage(String topic, Object message) {
    String messageId = UUID.randomUUID().toString();
    String messageKey = "message:" + topic + ":" + messageId;
    
    // 1. 存储消息内容
    redisTemplate.opsForValue().set(messageKey, JSON.toJSONString(message));
    redisTemplate.expire(messageKey, 7, TimeUnit.DAYS);
    
    // 2. 将消息ID放入队列
    redisTemplate.opsForList().rightPush("queue:" + topic, messageId);
}

// 消费者:处理消息
@Scheduled(fixedDelay = 100)
public void processMessages() {
    // 1. 从队列获取消息
    String messageId = redisTemplate.opsForList().leftPop("queue:orders", 5, TimeUnit.SECONDS);
    
    if (messageId != null) {
        String messageKey = "message:orders:" + messageId;
        String messageContent = redisTemplate.opsForValue().get(messageKey);
        
        if (messageContent != null) {
            try {
                // 2. 处理消息
                OrderMessage order = JSON.parseObject(messageContent, OrderMessage.class);
                orderService.processOrder(order);
                
                // 3. 处理成功,删除消息
                redisTemplate.delete(messageKey);
            } catch (Exception e) {
                // 4. 处理失败,放入重试队列
                redisTemplate.opsForList().rightPush("queue:orders:retry", messageId);
                log.error("Failed to process message: " + messageId, e);
            }
        }
    }
}

使用场景:我们用Redis实现了轻量级消息队列,处理每日50万订单的异步通知,比使用重量级MQ节省了大量资源!



十一、抽奖:概率控制的艺术



// 奖品设置
public void setupPrize(Long activityId, List prizes) {
    String key = "lottery:prizes:" + activityId;
    
    // 清空之前的设置
    redisTemplate.delete(key);
    
    // 设置奖品及其权重
    for (Prize prize : prizes) {
        redisTemplate.opsForZSet().add(key, prize.getId().toString(), prize.getWeight());
    }
}

// 抽奖实现
public Prize drawPrize(Long activityId, Long userId) {
    String key = "lottery:prizes:" + activityId;
    
    // 获取所有奖品及权重
    Set<ZSetOperations.TypedTuple> prizeWithScores = 
        redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
    
    if (prizeWithScores == null || prizeWithScores.isEmpty()) {
        throw new RuntimeException("No prizes available");
    }
    
    // 计算总权重
    double totalWeight = 0;
    for (ZSetOperations.TypedTuple item : prizeWithScores) {
        totalWeight += item.getScore();
    }
    
    // 生成随机数
    double random = Math.random() * totalWeight;
    
    // 根据权重选择奖品
    double currentWeight = 0;
    for (ZSetOperations.TypedTuple item : prizeWithScores) {
        currentWeight += item.getScore();
        if (random <= currentWeight) {
            // 中奖记录
            String recordKey = "lottery:record:" + activityId;
            redisTemplate.opsForHash().put(recordKey, userId.toString(), item.getValue());
            
            // 返回中奖信息
            return prizeService.getPrizeById(Long.valueOf(item.getValue()));
        }
    }
    
    throw new RuntimeException("Failed to draw prize");
}

实战案例:我们的营销活动使用Redis实现抽奖系统,支持实时调整中奖概率,单日抽奖次数突破500万!



十二、点赞、签到、打卡:用户互动三件套




// 点赞功能
public boolean toggleLike(Long userId, Long contentId) {
    String key = "likes:" + contentId;
    
    // 检查是否已点赞
    Boolean isLiked = redisTemplate.opsForSet().isMember(key, userId.toString());
    
    if (Boolean.TRUE.equals(isLiked)) {
        // 取消点赞
        redisTemplate.opsForSet().remove(key, userId.toString());
        // 减少计数
        redisTemplate.opsForValue().decrement("likes:count:" + contentId);
        return false;
    } else {
        // 添加点赞
        redisTemplate.opsForSet().add(key, userId.toString());
        // 增加计数
        redisTemplate.opsForValue().increment("likes:count:" + contentId);
        return true;
    }
}

// 获取点赞数
public long getLikeCount(Long contentId) {
    String countKey = "likes:count:" + contentId;
    Object count = redisTemplate.opsForValue().get(countKey);
    return count != null ? Long.parseLong(count.toString()) : 0;
}

// 获取点赞用户列表
public List getLikedUsers(Long contentId, int offset, int limit) {
    String key = "likes:" + contentId;
    Set userIds = redisTemplate.opsForSet().members(key);
    
    if (userIds == null || userIds.isEmpty()) {
        return Collections.emptyList();
    }
    
    return userIds.stream()
        .map(Long::valueOf)
        .skip(offset)
        .limit(limit)
        .collect(Collectors.toList());
}

// 连续签到统计
public int getContinuousSignDays(Long userId) {
    LocalDate today = LocalDate.now();
    String key = "sign:" + userId + ":" + today.getYear() + ":" + today.getMonthValue();
    
    int days = 0;
    int dayOfMonth = today.getDayOfMonth();
    
    // 从今天向前查找连续签到记录
    for (int i = 0; i < dayOfMonth; i++) {
        int day = dayOfMonth - i;
        Boolean signed = redisTemplate.opsForValue().getBit(key, day - 1);
        
        if (Boolean.TRUE.equals(signed)) {
            days++;
        } else {
            break;
        }
    }
    
    return days;
}

性能提升:使用Redis实现点赞功能,相比MySQL方案,QPS提升了20倍,从500提升到10000+!



十三、商品标签:高效的多维度属性管理



// 添加商品标签
public void addProductTags(Long productId, Set tags) {
    // 1. 添加到商品的标签集合
    String productTagsKey = "product:tags:" + productId;
    redisTemplate.opsForSet().add(productTagsKey, 
        tags.toArray(new String[0]));
    
    // 2. 添加到标签的商品集合
    for (String tag : tags) {
        String tagProductsKey = "tag:products:" + tag;
        redisTemplate.opsForSet().add(tagProductsKey, productId.toString());
    }
}

// 获取商品的所有标签
public Set getProductTags(Long productId) {
    String key = "product:tags:" + productId;
    Set tags = redisTemplate.opsForSet().members(key);
    return tags != null ? tags : Collections.emptySet();
}

// 获取拥有特定标签的所有商品
public Set getProductsByTag(String tag) {
    String key = "tag:products:" + tag;
    Set productIds = redisTemplate.opsForSet().members(key);
    
    if (productIds == null || productIds.isEmpty()) {
        return Collections.emptySet();
    }
    
    return productIds.stream()
        .map(Long::valueOf)
        .collect(Collectors.toSet());
}

// 获取同时拥有多个标签的商品(交集)
public Set getProductsByTags(Set tags) {
    if (tags == null || tags.isEmpty()) {
        return Collections.emptySet();
    }
    
    // 构建多个集合的key
    List keys = tags.stream()
        .map(tag -> "tag:products:" + tag)
        .collect(Collectors.toList());
    
    // 计算交集
    Set productIds = redisTemplate.opsForSet().intersect(
        keys.get(0), keys.subList(1, keys.size()));
    
    if (productIds == null || productIds.isEmpty()) {
        return Collections.emptySet();
    }
    
    return productIds.stream()
        .map(Long::valueOf)
        .collect(Collectors.toSet());
}

实战案例:我们的电商平台使用Redis管理商品标签,支持亿级商品的实时标签查询,毫秒级返回结果!



十四、商品筛选:高性能的条件过滤



// 按价格区间索引商品
public void indexProductByPrice(Long productId, double price) {
    String key = "product:price";
    redisTemplate.opsForZSet().add(key, productId.toString(), price);
}

// 按价格区间查询商品
public Set getProductsByPriceRange(double minPrice, double maxPrice) {
    String key = "product:price";
    Set productIds = redisTemplate.opsForZSet().rangeByScore(key, minPrice, maxPrice);
    
    if (productIds == null || productIds.isEmpty()) {
        return Collections.emptySet();
    }
    
    return productIds.stream()
        .map(Long::valueOf)
        .collect(Collectors.toSet());
}

// 复合条件筛选
public Set filterProducts(double minPrice, double maxPrice, Set tags) {
    // 1. 按价格筛选
    Set priceFilteredProducts = getProductsByPriceRange(minPrice, maxPrice);
    
    if (priceFilteredProducts.isEmpty() || tags == null || tags.isEmpty()) {
        return priceFilteredProducts;
    }
    
    // 2. 按标签筛选
    Set tagFilteredProducts = getProductsByTags(tags);
    
    // 3. 取交集
    priceFilteredProducts.retainAll(tagFilteredProducts);
    
    return priceFilteredProducts;
}

// 分页获取筛选结果
public List getFilteredProducts(FilterCriteria criteria, int page, int size) {
    // 获取符合条件的商品ID
    Set productIds = filterProducts(
        criteria.getMinPrice(), 
        criteria.getMaxPrice(), 
        criteria.getTags()
    );
    
    if (productIds.isEmpty()) {
        return Collections.emptyList();
    }
    
    // 分页处理
    return productIds.stream()
        .skip((page - 1) * size)
        .limit(size)
        .map(productService::getProductById)
        .collect(Collectors.toList());
}

性能对比:Redis实现的商品筛选功能,比MySQL的多条件查询快10倍以上,且支持更复杂的组合条件!



十五、用户关注、推荐模型:社交关系的构建

// 关注用户
public void followUser(Long followerId, Long followeeId) {
    // 添加到关注集合
    String followingKey = "following:" + followerId;
    redisTemplate.opsForSet().add(followingKey, followeeId.toString());
    
    // 添加到粉丝集合
    String followersKey = "followers:" + followeeId;
    redisTemplate.opsForSet().add(followersKey, followerId.toString());
}

// 取消关注
public void unfollowUser(Long followerId, Long followeeId) {
    // 从关注集合移除
    String followingKey = "following:" + followerId;
    redisTemplate.opsForSet().remove(followingKey, followeeId.toString());
    
    // 从粉丝集合移除
    String followersKey = "followers:" + followeeId;
    redisTemplate.opsForSet().remove(followersKey, followerId.toString());
}

// 获取共同关注
public Set getCommonFollowing(Long userId1, Long userId2) {
    String key1 = "following:" + userId1;
    String key2 = "following:" + userId2;
    
    Set commonIds = redisTemplate.opsForSet().intersect(key1, key2);
    
    if (commonIds == null || commonIds.isEmpty()) {
        return Collections.emptySet();
    }
    
    return commonIds.stream()
        .map(Long::valueOf)
        .collect(Collectors.toSet());
}

// 推荐可能认识的人
public Set recommendUsers(Long userId) {
    String followingKey = "following:" + userId;
    
    // 获取用户的关注列表
    Set followingIds = redisTemplate.opsForSet().members(followingKey);
    
    if (followingIds == null || followingIds.isEmpty()) {
        return Collections.emptySet();
    }
    
    // 获取关注的人的关注列表(二度关系)
    Set recommendations = new HashSet<>();
    for (String followingId : followingIds) {
        String secondDegreeKey = "following:" + followingId;
        Set secondDegreeIds = redisTemplate.opsForSet().members(secondDegreeKey);
        
        if (secondDegreeIds != null) {
            for (String id : secondDegreeIds) {
                Long recommendId = Long.valueOf(id);
                // 排除自己和已关注的人
                if (!recommendId.equals(userId) && !followingIds.contains(id)) {
                    recommendations.add(recommendId);
                }
            }
        }
    }
    
    return recommendations;
}

// 基于用户相似度的推荐
public List getContentRecommendations(Long userId) {
    // 1. 找到相似用户
    Set similarUsers = findSimilarUsers(userId);
    
    // 2. 获取相似用户喜欢的内容
    Map contentScores = new HashMap<>();
    for (Long similarUserId : similarUsers) {
        String likedKey = "user:liked:" + similarUserId;
        Set likedContent = redisTemplate.opsForSet().members(likedKey);
        
        if (likedContent != null) {
            for (String contentId : likedContent) {
                Long cid = Long.valueOf(contentId);
                // 计算内容得分
                contentScores.put(cid, contentScores.getOrDefault(cid, 0.0) + 1.0);
            }
        }
    }
    
    // 3. 排除用户已经喜欢的内容
    String userLikedKey = "user:liked:" + userId;
    Set userLiked = redisTemplate.opsForSet().members(userLikedKey);
    if (userLiked != null) {
        for (String contentId : userLiked) {
            contentScores.remove(Long.valueOf(contentId));
        }
    }
    
    // 4. 按得分排序返回推荐内容
    return contentScores.entrySet().stream()
        .sorted(Map.Entry.comparingByValue().reversed())
        .limit(10)
        .map(Map.Entry::getKey)
        .collect(Collectors.toList());
}

实战案例:我们的社交平台使用Redis构建用户关系网络,支持5000万用户的实时推荐,推荐准确率提升了35%!



十六、排行榜:高效的实时榜单



// 更新得分
public void updateScore(String leaderboardKey, Long userId, double score) {
    redisTemplate.opsForZSet().add(leaderboardKey, userId.toString(), score);
}

// 增加得分
public double incrementScore(String leaderboardKey, Long userId, double increment) {
    Double newScore = redisTemplate.opsForZSet().incrementScore(
        leaderboardKey, userId.toString(), increment);
    return newScore != null ? newScore : 0;
}

// 获取排名
public long getRank(String leaderboardKey, Long userId) {
    Long rank = redisTemplate.opsForZSet().reverseRank(leaderboardKey, userId.toString());
    return rank != null ? rank + 1 : 0; // +1转为从1开始的排名
}

// 获取前N名
public List getTopN(String leaderboardKey, int n) {
    Set<ZSetOperations.TypedTuple> tuples = 
        redisTemplate.opsForZSet().reverseRangeWithScores(leaderboardKey, 0, n - 1);
    
    if (tuples == null || tuples.isEmpty()) {
        return Collections.emptyList();
    }
    
    List result = new ArrayList<>();
    int rank = 1;
    for (ZSetOperations.TypedTuple tuple : tuples) {
        result.add(new RankItem(
            Long.valueOf(tuple.getValue()),
            tuple.getScore(),
            rank++
        ));
    }
    
    return result;
}

// 获取用户附近排名
public List getNearbyRanks(String leaderboardKey, Long userId, int count) {
    // 1. 获取用户排名
    Long rank = redisTemplate.opsForZSet().reverseRank(leaderboardKey, userId.toString());
    
    if (rank == null) {
        return Collections.emptyList();
    }
    
    // 2. 计算范围
    long start = Math.max(0, rank - count / 2);
    long end = start + count - 1;
    
    // 3. 获取指定范围的排名数据
    Set<ZSetOperations.TypedTuple> tuples = 
        redisTemplate.opsForZSet().reverseRangeWithScores(leaderboardKey, start, end);
    
    if (tuples == null || tuples.isEmpty()) {
        return Collections.emptyList();
    }
    
    // 4. 组装结果
    List result = new ArrayList<>();
    long currentRank = start + 1;
    for (ZSetOperations.TypedTuple tuple : tuples) {
        result.add(new RankItem(
            Long.valueOf(tuple.getValue()),
            tuple.getScore(),
            currentRank++
        ));
    }
    
    return result;
}

// 获取多个时间维度的排行榜
public Map<String, List> getMultiTimeLeaderboards(String category, int n) {
    Map<String, List> result = new HashMap<>();
    
    // 日榜
    String dailyKey = "leaderboard:" + category + ":daily";
    result.put("daily", getTopN(dailyKey, n));
    
    // 周榜
    String weeklyKey = "leaderboard:" + category + ":weekly";
    result.put("weekly", getTopN(weeklyKey, n));
    
    // 月榜
    String monthlyKey = "leaderboard:" + category + ":monthly";
    result.put("monthly", getTopN(monthlyKey, n));
    
    // 总榜
    String allTimeKey = "leaderboard:" + category + ":alltime";
    result.put("allTime", getTopN(allTimeKey, n));
    
    return result;
}

实战案例:我们的游戏平台使用Redis实现实时排行榜,支持日榜、周榜、月榜多维度更新,每日榜单查询量超过1亿次!



写在最后

Redis的16种妙用,你学会了几种?这些用法不仅能解决实际问题,还能大幅提升系统性能。

使用建议:

  • 根据业务场景选择合适的数据结构
  • 注意内存使用和过期策略
  • 关注数据一致性问题
  • 做好监控和容灾

性能提升:

  • 缓存:响应时间从200ms降至5ms
  • 计数器:QPS从1000提升至50000+
  • 排行榜:复杂排序从秒级降至毫秒级
  • 分布式锁:支持10万+并发请求

进阶技巧:

  1. 使用Pipeline批量操作
  2. 利用Lua脚本保证原子性
  3. 合理设置过期时间
  4. 使用Redis Cluster实现高可用

记住:

"Redis不仅是一个缓存,它是一把解决分布式问题的瑞士军刀!"

你还在只把Redis当缓存用吗?赶紧解锁这些神仙用法,让同事直呼牛!

#Redis #架构设计 #性能优化 #分布式系统

相关推荐

月薪 4K 到 4W 的运维工程师都经历了什么?

运维工程师在前期是一个很苦逼的工作,在这期间可能干着修电脑、掐网线、搬机器的活,显得没地位!时间也很碎片化,各种零碎的琐事围绕着你,很难体现个人价值,渐渐的对行业很迷茫,觉得没什么发展前途。这些枯燥无...

计算机专业必须掌握的脚本开发语言—shell

提起Shell脚本很多都有了解,因为无论是windows的Dom命令行还是Linux的bash都是它的表现形式,但是很多人不知道它还有一门脚本编程语言,就是ShellScript,我们提起的Shel...

Linux/Shell:排名第四的计算机关键技能

除了编程语言之外,要想找一份计算机相关的工作,还需要很多其他方面的技能。最近,来自美国求职公司Indeed的一份报告显示:在全美工作技能需求中,Linux/Shell技能仅次于SQL、Java、P...

使用Flask应用框架在Centos7.8系统上部署机器学习模型

安装centos7.8虚拟环境1、镜像链接...

shell编程

简介:Shell是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。...

14天shell脚本入门学习-第二天#脚本和参数#排版修正

脚本是一种包含一系列命令的文本文件,通常用于自动化任务。Shell脚本是用Shell命令编写的脚本,可以在命令行中执行。掌握脚本的基础知识和变量的使用是编写高效脚本的关键。...

嵌入式Linux开发教程:Linux Shell

本章重点介绍Linux的常用操作和命令。在介绍命令之前,先对Linux的Shell进行了简单介绍,然后按照大多数用户的使用习惯,对各种操作和相关命令进行了分类介绍。对相关命令的介绍都力求通俗易懂,都给...

实现SHELL中的列表和字典效果

大家好,我是博哥爱运维。编写代码,很多情况下我们需要有种类型来存储数据,在python中有列表和字典,golang中有切片slice和map,那么在shell中,我们能否实现列表和字典呢,答案是肯定的...

14天shell脚本入门学习-第二天#脚本和变量

脚本是一种包含一系列命令的文本文件,通常用于自动化任务。Shell脚本是用Shell命令编写的脚本,可以在命令行中执行。掌握脚本的基础知识和变量的使用是编写高效脚本的关键。...

shell常用命令之awk用法介绍

一、awk介绍awk的强大之处,在于能生成强大的格式化报告。数据可以来自标准输入,一个或多个文件,或者其他命令的输出。他支持用户自定义函数和动态正则表达式等先进功能,是Linux/unix一个强大的文...

Linux编程Shell之入门——Shell数组拼接与合并

在Shell中,可以使用不同的方式实现数组拼接和合并。数组拼接指将两个数组中的元素合并成一个数组,而数组合并指将两个数组逐个组合成一个新数组。以下是关于Shell数组拼接和合并的详细介绍:数...

shell中如何逆序打印数组的内容,或者反转一个数组?

章节索引图首先请注意,有序的概念仅适用于索引数组,而不适用于关联数组。如果没有稀疏数组,答案会更简单,但是Bash的数组可以是稀疏的(非连续索引)。因此,我们需要引入一个额外的步骤。...

如何学好大数据开发?---shell基本语法

昨天我们初步了解到了shell的一些基本知识,比如shell的分类,常用的shell类型。今天就带来大数据开发之shell基本语法,掌握好基础才是最重要的,那接下来就开始学习shell的基本语法。一、...

Linux编程Shell之入门——Shell关联数组

关联数组是Shell中一种特殊的数组类型,它使用字符串作为下标。在关联数组中,每个元素都被标识为一个唯一的字符串键值,也称为关联数组的索引。在Shell中,可以使用declare或typeset命令...

从编译器视角看数组和指针

虽然有单独的文章描述数组和指针,但二者的关系实在值得再写一篇文章。...

取消回复欢迎 发表评论: