Redis-07-常见Redis使用场景

发布于:2025-04-18 ⋅ 阅读:(28) ⋅ 点赞:(0)

01.缓存数据(Cache)

将数据库查询结果缓存到Redis,减轻数据库压力

示例:在电商网站中,可以将热门商品的信息存储在Redis中,当用户访问这些商品时,首先从Redis中读取,如果Redis中没有,再从数据库中读取并更新到Redis中。

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private static final String PRODUCT_CACHE_PREFIX = "product:";
    public Product getProductById(Long id) {
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
    
        // 1. 先查缓存
        Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
        if (product != null) {
            return product;
        }
        
        // 2. 缓存没有则查数据库
        product = productRepository.findById(id).orElse(null);
        if (product != null) {
            // 3. 写入缓存,设置过期时间
            redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
        }
        return product;
    }
}

02.布式锁(Distributed Lock)

在分布式系统中,Redis可以用于实现分布式锁,可以在分布式系统中协调多节点对共享资源的访问,确保操作的原子性。

示例:分布式系统中用户购买商品,同一时间只能一个用户创建订单。

@Service
public class OrderService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    private static final long LOCK_EXPIRE = 30; // 30秒
    
    public boolean createOrder(Long productId, Long userId) {
        String lockKey = LOCK_PREFIX + "order_" + productId;
        String requestId = UUID.randomUUID().toString();
    
        try {
            // 尝试获取锁
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, LOCK_EXPIRE, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(locked)) {
                // 获取锁成功,执行业务逻辑
                return doCreateOrder(productId, userId);
            }
            return false;
        } finally {
            // 释放锁 - 使用Lua脚本保证原子性
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
            redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(lockKey),
                requestId
            );
        }
    }
}

03.计数器(Counter)

Redis的原子增减操作非常适合用于计数器和排行榜应用,如社交媒体的点赞数、阅读数、排名等。

示例:实现文章阅读量、商品点击量等统计

@Service
public class ArticleService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String VIEW_COUNT_PREFIX = "article:view:";
    
    // 增加阅读量
    public void incrementViewCount(Long articleId) {
        String key = VIEW_COUNT_PREFIX + articleId;
        redisTemplate.opsForValue().increment(key);
    }
    
    // 获取阅读量
    public Long getViewCount(Long articleId) {
        String key = VIEW_COUNT_PREFIX + articleId;
        String count = redisTemplate.opsForValue().get(key);
        return count == null ? 0L : Long.parseLong(count);
    }
    
    // 批量获取阅读量
    public Map<Long, Long> getBatchViewCounts(List<Long> articleIds) {
        List<String> keys = articleIds.stream().map(id -> VIEW_COUNT_PREFIX + id).collect(Collectors.toList());
        List<String> counts = redisTemplate.opsForValue().multiGet(keys);
        Map<Long, Long> result = new HashMap<>();
        for (int i = 0; i < articleIds.size(); i++) {
            Long articleId = articleIds.get(i);
            String count = counts.get(i);
            result.put(articleId, count == null ? 0L : Long.parseLong(count));
        }
        return result;
    }
}

04.排行榜(Leaderboard)

Redis的原子增减操作非常适合用于计数器和排行榜应用,如社交媒体的点赞数、阅读数、排名等。

示例:实现商品销量排行、游戏积分排行等

@Service
public class RankingService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String SALES_RANK_KEY = "product:sales:rank";
    
    // 更新商品销量
    public void updateProductSales(Long productId, int sales) {
        redisTemplate.opsForZSet().add(SALES_RANK_KEY, productId, sales);
    }
    
    // 获取销量前N的商品
    public List<Long> getTopSalesProducts(int topN) {
        Set<Object> productIds = redisTemplate.opsForZSet().reverseRange(SALES_RANK_KEY, 0, topN - 1);
        return productIds.stream().map(id -> Long.parseLong(id.toString())).collect(Collectors.toList());
    }
    
    // 获取商品排名
    public Long getProductRank(Long productId) {
        // ZREVRANK返回的是从0开始的排名
        Long rank = redisTemplate.opsForZSet().reverseRank(SALES_RANK_KEY, productId);
        return rank == null ? null : rank + 1; // 转换为1-based排名
    }
    
    // 获取商品销量
    public Double getProductSales(Long productId) {
        return redisTemplate.opsForZSet().score(SALES_RANK_KEY, productId);
    }
}

05.消息队列(Message Queue)

Redis支持发布/订阅模式,可以用作轻量级的消息队列系统,用于异步任务处理、事件处理等。

示例:实现简单的消息队列系统

@Service
public class MessageQueueService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String QUEUE_KEY = "message:queue";
    
    // 发送消息
    public void sendMessage(String message) {
        redisTemplate.opsForList().rightPush(QUEUE_KEY, message);
    }
    
    // 接收消息 - 阻塞式
    public String receiveMessage() throws InterruptedException {
        return (String) redisTemplate.opsForList().leftPop(QUEUE_KEY, 30, TimeUnit.SECONDS);
    }
    
    // 批量发送消息
    public void batchSendMessages(List<String> messages) {
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (String message : messages) {
                connection.listCommands().rPush(QUEUE_KEY.getBytes(), message.getBytes());
            }
            return null;
        });
    }
}

06.限流(Rate Limiting)

Redis 适合用于限流(Rate Limiting)场景。限流的目的是控制某个操作在特定时间内的访问频率,比如 API 请求、短信发送、登录尝试等。Redis 的原子操作和高效性能使其成为实现限流的理想工具。

示例:实现API访问限流

@Service
public class RateLimitService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String RATE_LIMIT_PREFIX = "rate_limit:";
    
    /**
     * 限流检查
     * @param key 限流key(如用户ID或IP)
     * @param limit 时间窗口内允许的最大请求数
     * @param windowInSeconds 时间窗口大小(秒)
     * @return true-允许访问 false-拒绝访问
     */
    public boolean allowRequest(String key, int limit, int windowInSeconds) {
        String redisKey = RATE_LIMIT_PREFIX + key;
        // 使用Lua脚本保证原子性
        String script = 
            "local current = redis.call('incr', KEYS[1])\n" +
            "if current == 1 then\n" +
            "    redis.call('expire', KEYS[1], ARGV[1])\n" +
            "end\n" +
            "return current <= tonumber(ARGV[2])";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(redisKey),
            String.valueOf(windowInSeconds),
            String.valueOf(limit)
        );
        
        return result != null && result == 1;
    }
}

07.会话存储(Session Storage)

在Web应用中,Redis常用于存储用户会话信息,如登录状态、购物车内容等。由于其快速的读写速度,Redis非常适合这种需要频繁访问和更新的数据。

示例:分布式系统中的会话存储

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 30分钟过期
public class RedisSessionConfig {
    // Spring Session会自动配置Redis连接
}

// 使用示例
@RestController
public class UserController {
    @PostMapping("/login")
    public String login(@RequestParam String username, HttpSession session) {
        // 存储用户信息到session
        session.setAttribute("username", username);
        return "登录成功";
    }
    
    @GetMapping("/userinfo")
    public String getUserInfo(HttpSession session) {
        // 从session获取用户信息
        String username = (String) session.getAttribute("username");
        return "当前用户: " + (username != null ? username : "未登录");
    }
}

08.地理位置(Geo)

Redis支持地理空间数据,可以用于构建地理位置应用,如附近的人、地点推荐等功能。

示例:实现附近的人、附近的商家等功能

@Service
public class LocationService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String GEO_KEY = "shop:locations";
    
    // 添加商家位置
    public void addShopLocation(Long shopId, double lng, double lat) {
        redisTemplate.opsForGeo().add(GEO_KEY, new Point(lng, lat), shopId.toString());
    }
    
    // 获取附近商家
    public List<Long> getNearbyShops(double lng, double lat, double radius) {
        Circle within = new Circle(new Point(lng, lat),  new Distance(radius, Metrics.KILOMETERS));
       
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
            .newGeoRadiusArgs()
            .includeDistance()
            .sortAscending()
            .limit(10);
        
        GeoResults<RedisGeoCommands.GeoLocation<Object>> results = redisTemplate.opsForGeo()
            .radius(GEO_KEY, within, args);
        
        return results.getContent().stream()
            .map(geoResult -> Long.parseLong(geoResult.getContent().getName().toString()))
            .collect(Collectors.toList());
    }
    
    // 计算两个商家距离
    public Distance calculateDistance(Long shopId1, Long shopId2) {
        List<String> ids = Arrays.asList(shopId1.toString(), shopId2.toString());
        return redisTemplate.opsForGeo()
            .distance(GEO_KEY, ids.get(0), ids.get(1), Metrics.KILOMETERS);
    }
}

09. 发布订阅(Pub/Sub)

示例:实现实时消息通知、事件广播等

// 配置消息监听容器
@Configuration
public class RedisPubSubConfig {
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            MessageListenerAdapter orderEventListenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(orderEventListenerAdapter, 
                                  new ChannelTopic("order:events"));
        return container;
    }
    
    @Bean
    public MessageListenerAdapter orderEventListenerAdapter(OrderEventListener listener) {
        return new MessageListenerAdapter(listener, "handleMessage");
    }
}

// 消息监听器
@Component
public class OrderEventListener {
    public void handleMessage(OrderEvent event) {
        switch (event.getType()) {
            case CREATED:
                handleOrderCreated(event);
                break;
            case PAID:
                handleOrderPaid(event);
                break;
            // 其他事件处理...
        }
    }
    
    private void handleOrderCreated(OrderEvent event) {
        // 处理订单创建事件
    }
}

// 消息发布服务
@Service
public class OrderEventPublisher {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public void publishOrderEvent(OrderEvent event) {
        redisTemplate.convertAndSend("order:events", event);
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到