【Redis实战与高可用架构设计:从缓存到分布式锁的完整解决方案】

📚 前言

Redis作为高性能的内存数据库,在互联网应用中扮演着至关重要的角色。本文将深入探讨Redis的核心应用场景、性能优化技巧以及高可用架构设计。


🎯 一、Redis核心数据结构与应用场景

1.1 String:缓存与计数器

java 复制代码
@Service
public class CacheService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 缓存用户信息
     */
    public void cacheUser(User user) {
        String key = "user:" + user.getId();
        String value = JSON.toJSONString(user);
        // 设置过期时间30分钟
        redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
    }
    
    /**
     * 获取缓存
     */
    public User getUser(Long userId) {
        String key = "user:" + userId;
        String value = redisTemplate.opsForValue().get(key);
        return value != null ? JSON.parseObject(value, User.class) : null;
    }
    
    /**
     * 页面访问计数
     */
    public Long incrementPageView(String pageId) {
        String key = "page:view:" + pageId;
        return redisTemplate.opsForValue().increment(key);
    }
    
    /**
     * 分布式ID生成
     */
    public Long generateId(String bizType) {
        String key = "id:generator:" + bizType;
        return redisTemplate.opsForValue().increment(key);
    }
}

1.2 Hash:对象存储

java 复制代码
@Service
public class UserCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 存储用户信息(使用Hash)
     */
    public void saveUser(User user) {
        String key = "user:hash:" + user.getId();
        Map<String, Object> userMap = new HashMap<>();
        userMap.put("name", user.getName());
        userMap.put("age", user.getAge());
        userMap.put("email", user.getEmail());
        
        redisTemplate.opsForHash().putAll(key, userMap);
        redisTemplate.expire(key, 30, TimeUnit.MINUTES);
    }
    
    /**
     * 获取用户单个字段
     */
    public String getUserName(Long userId) {
        String key = "user:hash:" + userId;
        return (String) redisTemplate.opsForHash().get(key, "name");
    }
    
    /**
     * 更新用户单个字段
     */
    public void updateUserAge(Long userId, Integer age) {
        String key = "user:hash:" + userId;
        redisTemplate.opsForHash().put(key, "age", age);
    }
    
    /**
     * 获取用户所有信息
     */
    public Map<Object, Object> getUser(Long userId) {
        String key = "user:hash:" + userId;
        return redisTemplate.opsForHash().entries(key);
    }
}

1.3 List:消息队列与排行榜

java 复制代码
@Service
public class MessageQueueService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 生产者:发送消息
     */
    public void sendMessage(String queue, String message) {
        redisTemplate.opsForList().rightPush(queue, message);
    }
    
    /**
     * 消费者:接收消息(阻塞)
     */
    public String receiveMessage(String queue, long timeout) {
        return redisTemplate.opsForList().leftPop(queue, timeout, TimeUnit.SECONDS);
    }
    
    /**
     * 获取队列长度
     */
    public Long getQueueSize(String queue) {
        return redisTemplate.opsForList().size(queue);
    }
    
    /**
     * 最新文章列表(保留最新100条)
     */
    public void addArticle(String articleId) {
        String key = "articles:latest";
        redisTemplate.opsForList().leftPush(key, articleId);
        // 保留最新100条
        redisTemplate.opsForList().trim(key, 0, 99);
    }
}

1.4 Set:标签与好友关系

java 复制代码
@Service
public class SocialService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 添加用户标签
     */
    public void addUserTags(Long userId, String... tags) {
        String key = "user:tags:" + userId;
        redisTemplate.opsForSet().add(key, tags);
    }
    
    /**
     * 获取用户标签
     */
    public Set<String> getUserTags(Long userId) {
        String key = "user:tags:" + userId;
        return redisTemplate.opsForSet().members(key);
    }
    
    /**
     * 共同好友
     */
    public Set<String> getCommonFriends(Long userId1, Long userId2) {
        String key1 = "user:friends:" + userId1;
        String key2 = "user:friends:" + userId2;
        return redisTemplate.opsForSet().intersect(key1, key2);
    }
    
    /**
     * 可能认识的人(好友的好友,但不是自己的好友)
     */
    public Set<String> getSuggestedFriends(Long userId) {
        String myKey = "user:friends:" + userId;
        Set<String> myFriends = redisTemplate.opsForSet().members(myKey);
        
        Set<String> suggested = new HashSet<>();
        for (String friendId : myFriends) {
            String friendKey = "user:friends:" + friendId;
            // 好友的好友
            Set<String> friendsOfFriend = redisTemplate.opsForSet().members(friendKey);
            suggested.addAll(friendsOfFriend);
        }
        
        // 排除自己和已经是好友的人
        suggested.remove(userId.toString());
        suggested.removeAll(myFriends);
        
        return suggested;
    }
}

1.5 ZSet:排行榜

java 复制代码
@Service
public class RankingService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 更新用户积分
     */
    public void updateScore(Long userId, double score) {
        String key = "ranking:score";
        redisTemplate.opsForZSet().add(key, userId.toString(), score);
    }
    
    /**
     * 增加用户积分
     */
    public void incrementScore(Long userId, double delta) {
        String key = "ranking:score";
        redisTemplate.opsForZSet().incrementScore(key, userId.toString(), delta);
    }
    
    /**
     * 获取排行榜(前N名)
     */
    public Set<ZSetOperations.TypedTuple<String>> getTopN(int n) {
        String key = "ranking:score";
        // 降序获取前N名
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, n - 1);
    }
    
    /**
     * 获取用户排名
     */
    public Long getUserRank(Long userId) {
        String key = "ranking:score";
        // 降序排名
        return redisTemplate.opsForZSet().reverseRank(key, userId.toString());
    }
    
    /**
     * 获取用户积分
     */
    public Double getUserScore(Long userId) {
        String key = "ranking:score";
        return redisTemplate.opsForZSet().score(key, userId.toString());
    }
    
    /**
     * 获取指定分数范围的用户
     */
    public Set<String> getUsersByScoreRange(double min, double max) {
        String key = "ranking:score";
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }
}

🔐 二、分布式锁实现

2.1 基于SETNX的简单实现

java 复制代码
@Component
public class RedisLock {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    
    /**
     * 获取锁
     */
    public boolean tryLock(String key, String value, long expireTime) {
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(LOCK_PREFIX + key, value, expireTime, TimeUnit.MILLISECONDS);
        return Boolean.TRUE.equals(result);
    }
    
    /**
     * 释放锁(使用Lua脚本保证原子性)
     */
    public boolean releaseLock(String key, String value) {
        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(LOCK_PREFIX + key),
            value
        );
        
        return result != null && result > 0;
    }
}

2.2 Redisson分布式锁(推荐)

xml 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.5</version>
</dependency>
java 复制代码
@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setPassword("your_password")
            .setDatabase(0)
            .setConnectionPoolSize(50)
            .setConnectionMinimumIdleSize(10);
        
        return Redisson.create(config);
    }
}

@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 使用分布式锁扣减库存
     */
    public boolean deductStock(Long productId, Integer count) {
        String lockKey = "product:stock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试获取锁,最多等待10秒,锁自动释放时间30秒
            boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
            
            if (!locked) {
                return false;
            }
            
            // 业务逻辑:扣减库存
            Integer stock = getStock(productId);
            if (stock < count) {
                return false;
            }
            
            updateStock(productId, stock - count);
            return true;
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    private Integer getStock(Long productId) {
        // 从数据库或缓存获取库存
        return 100;
    }
    
    private void updateStock(Long productId, Integer stock) {
        // 更新库存到数据库
    }
}

2.3 RedLock算法(多Redis实例)

java 复制代码
@Service
public class RedLockService {
    
    @Autowired
    private RedissonClient redissonClient1;
    @Autowired
    private RedissonClient redissonClient2;
    @Autowired
    private RedissonClient redissonClient3;
    
    /**
     * 使用RedLock算法
     */
    public void executeWithRedLock(String lockKey, Runnable task) {
        RLock lock1 = redissonClient1.getLock(lockKey);
        RLock lock2 = redissonClient2.getLock(lockKey);
        RLock lock3 = redissonClient3.getLock(lockKey);
        
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        
        try {
            // 尝试获取锁
            boolean locked = redLock.tryLock(10, 30, TimeUnit.SECONDS);
            
            if (locked) {
                task.run();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            redLock.unlock();
        }
    }
}

💾 三、缓存设计模式

3.1 Cache Aside(旁路缓存)

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 查询用户(先查缓存,再查数据库)
     */
    public User getUserById(Long userId) {
        String key = "user:" + userId;
        
        // 1. 查询缓存
        String cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return JSON.parseObject(cached, User.class);
        }
        
        // 2. 查询数据库
        User user = userMapper.selectById(userId);
        if (user != null) {
            // 3. 写入缓存
            redisTemplate.opsForValue().set(
                key, 
                JSON.toJSONString(user), 
                30, 
                TimeUnit.MINUTES
            );
        }
        
        return user;
    }
    
    /**
     * 更新用户(先更新数据库,再删除缓存)
     */
    public void updateUser(User user) {
        // 1. 更新数据库
        userMapper.updateById(user);
        
        // 2. 删除缓存
        String key = "user:" + user.getId();
        redisTemplate.delete(key);
    }
}

3.2 Read Through / Write Through

java 复制代码
@Service
public class CacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * Read Through:缓存层自动加载数据
     */
    public User getUserById(Long userId) {
        String key = "user:" + userId;
        
        return (User) redisTemplate.opsForValue().get(key);
    }
    
    /**
     * Write Through:同时更新缓存和数据库
     */
    @Transactional
    public void updateUser(User user) {
        String key = "user:" + user.getId();
        
        // 1. 更新数据库
        userMapper.updateById(user);
        
        // 2. 更新缓存
        redisTemplate.opsForValue().set(
            key, 
            user, 
            30, 
            TimeUnit.MINUTES
        );
    }
}

3.3 Write Behind(异步写入)

java 复制代码
@Service
public class AsyncCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    
    /**
     * 异步写入数据库
     */
    public void updateUser(User user) {
        String key = "user:" + user.getId();
        
        // 1. 立即更新缓存
        redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
        
        // 2. 异步更新数据库
        executor.submit(() -> {
            try {
                userMapper.updateById(user);
            } catch (Exception e) {
                // 记录日志,后续补偿
                log.error("异步更新数据库失败", e);
            }
        });
    }
}

🚨 四、缓存常见问题与解决方案

4.1 缓存穿透

问题: 查询不存在的数据,导致每次都查询数据库

解决方案1:缓存空值

java 复制代码
@Service
public class CachePenetrationService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    public User getUserById(Long userId) {
        String key = "user:" + userId;
        
        // 1. 查询缓存
        String cached = redisTemplate.opsForValue().get(key);
        
        // 缓存中存在
        if (cached != null) {
            // 空值标记
            if ("NULL".equals(cached)) {
                return null;
            }
            return JSON.parseObject(cached, User.class);
        }
        
        // 2. 查询数据库
        User user = userMapper.selectById(userId);
        
        if (user != null) {
            // 3. 缓存数据
            redisTemplate.opsForValue().set(
                key, 
                JSON.toJSONString(user), 
                30, 
                TimeUnit.MINUTES
            );
        } else {
            // 4. 缓存空值(设置较短过期时间)
            redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
        }
        
        return user;
    }
}

解决方案2:布隆过滤器

java 复制代码
@Configuration
public class BloomFilterConfig {
    
    @Bean
    public BloomFilter<Long> userBloomFilter() {
        // 预期元素数量100万,误判率0.01
        BloomFilter<Long> bloomFilter = BloomFilter.create(
            Funnels.longFunnel(),
            1000000,
            0.01
        );
        
        // 初始化:将所有用户ID加入布隆过滤器
        // userMapper.selectAllIds().forEach(bloomFilter::put);
        
        return bloomFilter;
    }
}

@Service
public class BloomFilterService {
    
    @Autowired
    private BloomFilter<Long> userBloomFilter;
    
    @Autowired
    private UserMapper userMapper;
    
    public User getUserById(Long userId) {
        // 1. 布隆过滤器判断
        if (!userBloomFilter.mightContain(userId)) {
            // 一定不存在
            return null;
        }
        
        // 2. 可能存在,查询数据库
        return userMapper.selectById(userId);
    }
    
    /**
     * 新增用户时,加入布隆过滤器
     */
    public void createUser(User user) {
        userMapper.insert(user);
        userBloomFilter.put(user.getId());
    }
}

4.2 缓存击穿

问题: 热点数据过期,大量请求同时查询数据库

解决方案:互斥锁

java 复制代码
@Service
public class CacheBreakdownService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private RedissonClient redissonClient;
    
    public User getUserById(Long userId) {
        String key = "user:" + userId;
        
        // 1. 查询缓存
        String cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return JSON.parseObject(cached, User.class);
        }
        
        // 2. 缓存未命中,使用分布式锁
        String lockKey = "lock:user:" + userId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 获取锁
            boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
            
            if (locked) {
                // 双重检查
                cached = redisTemplate.opsForValue().get(key);
                if (cached != null) {
                    return JSON.parseObject(cached, User.class);
                }
                
                // 查询数据库
                User user = userMapper.selectById(userId);
                
                if (user != null) {
                    // 写入缓存
                    redisTemplate.opsForValue().set(
                        key, 
                        JSON.toJSONString(user), 
                        30, 
                        TimeUnit.MINUTES
                    );
                }
                
                return user;
            } else {
                // 获取锁失败,等待后重试
                Thread.sleep(100);
                return getUserById(userId);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

4.3 缓存雪崩

问题: 大量缓存同时过期,导致数据库压力骤增

解决方案:

java 复制代码
@Service
public class CacheAvalancheService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    private static final Random RANDOM = new Random();
    
    /**
     * 方案1:过期时间加随机值
     */
    public void cacheUser(User user) {
        String key = "user:" + user.getId();
        
        // 基础过期时间30分钟 + 随机0-5分钟
        long expireTime = 30 + RANDOM.nextInt(5);
        
        redisTemplate.opsForValue().set(
            key, 
            JSON.toJSONString(user), 
            expireTime, 
            TimeUnit.MINUTES
        );
    }
    
    /**
     * 方案2:热点数据永不过期(后台定时刷新)
     */
    @Scheduled(fixedRate = 600000)  // 每10分钟执行
    public void refreshHotData() {
        List<Long> hotUserIds = getHotUserIds();
        
        for (Long userId : hotUserIds) {
            User user = userMapper.selectById(userId);
            if (user != null) {
                String key = "user:hot:" + userId;
                redisTemplate.opsForValue().set(
                    key, 
                    JSON.toJSONString(user)
                    // 不设置过期时间
                );
            }
        }
    }
    
    private List<Long> getHotUserIds() {
        // 获取热点用户ID列表
        return Arrays.asList(1L, 2L, 3L);
    }
}

🔥 五、Redis性能优化

5.1 Pipeline批量操作

java 复制代码
@Service
public class PipelineService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * ❌ 逐个操作(性能差)
     */
    public void badBatchSet(Map<String, String> data) {
        for (Map.Entry<String, String> entry : data.entrySet()) {
            redisTemplate.opsForValue().set(entry.getKey(), entry.getValue());
        }
    }
    
    /**
     * ✅ 使用Pipeline批量操作
     */
    public void goodBatchSet(Map<String, String> data) {
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                for (Map.Entry<String, String> entry : data.entrySet()) {
                    operations.opsForValue().set(entry.getKey(), entry.getValue());
                }
                return null;
            }
        });
    }
    
    /**
     * Pipeline批量查询
     */
    public List<String> batchGet(List<String> keys) {
        List<Object> results = redisTemplate.executePipelined(
            new SessionCallback<Object>() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    for (String key : keys) {
                        operations.opsForValue().get(key);
                    }
                    return null;
                }
            }
        );
        
        return results.stream()
            .map(obj -> obj != null ? obj.toString() : null)
            .collect(Collectors.toList());
    }
}

5.2 Lua脚本原子操作

java 复制代码
@Service
public class LuaScriptService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 限流器(令牌桶算法)
     */
    public boolean tryAcquire(String key, int maxCount, int windowSeconds) {
        String script = 
            "local key = KEYS[1] " +
            "local maxCount = tonumber(ARGV[1]) " +
            "local windowSeconds = tonumber(ARGV[2]) " +
            "local current = tonumber(redis.call('get', key) or '0') " +
            "if current < maxCount then " +
            "    redis.call('incr', key) " +
            "    if current == 0 then " +
            "        redis.call('expire', key, windowSeconds) " +
            "    end " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key),
            String.valueOf(maxCount),
            String.valueOf(windowSeconds)
        );
        
        return result != null && result > 0;
    }
    
    /**
     * 扣减库存(防止超卖)
     */
    public boolean deductStock(String productId, int count) {
        String script = 
            "local key = KEYS[1] " +
            "local count = tonumber(ARGV[1]) " +
            "local stock = tonumber(redis.call('get', key) or '0') " +
            "if stock >= count then " +
            "    redis.call('decrby', key, count) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";
        
        String key = "product:stock:" + productId;
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key),
            String.valueOf(count)
        );
        
        return result != null && result > 0;
    }
}

5.3 大Key优化

java 复制代码
@Service
public class BigKeyOptimization {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * ❌ 大Key:一次性存储大量数据
     */
    public void badSaveUserList(List<User> users) {
        String key = "users:all";
        String value = JSON.toJSONString(users);
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * ✅ 拆分大Key:使用Hash存储
     */
    public void goodSaveUserList(List<User> users) {
        String key = "users:hash";
        
        Map<String, String> userMap = users.stream()
            .collect(Collectors.toMap(
                user -> user.getId().toString(),
                user -> JSON.toJSONString(user)
            ));
        
        redisTemplate.opsForHash().putAll(key, userMap);
    }
    
    /**
     * 分页获取数据
     */
    public List<User> getUsersByPage(int page, int size) {
        String key = "users:hash";
        
        // 使用HSCAN分页获取
        ScanOptions options = ScanOptions.scanOptions()
            .count(size)
            .build();
        
        Cursor<Map.Entry<Object, Object>> cursor = 
            redisTemplate.opsForHash().scan(key, options);
        
        List<User> users = new ArrayList<>();
        while (cursor.hasNext() && users.size() < size) {
            Map.Entry<Object, Object> entry = cursor.next();
            User user = JSON.parseObject(entry.getValue().toString(), User.class);
            users.add(user);
        }
        
        return users;
    }
}

🏗️ 六、Redis高可用架构

6.1 主从复制配置

yaml 复制代码
# application.yml
spring:
  redis:
    # 主节点
    host: 192.168.1.100
    port: 6379
    password: your_password
    # 从节点(读写分离)
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
      read-from: REPLICA_PREFERRED  # 优先从从节点读取

6.2 哨兵模式配置

yaml 复制代码
spring:
  redis:
    sentinel:
      master: mymaster
      nodes:
        - 192.168.1.100:26379
        - 192.168.1.101:26379
        - 192.168.1.102:26379
    password: your_password
    lettuce:
      pool:
        max-active: 8

6.3 集群模式配置

yaml 复制代码
spring:
  redis:
    cluster:
      nodes:
        - 192.168.1.100:7000
        - 192.168.1.100:7001
        - 192.168.1.101:7000
        - 192.168.1.101:7001
        - 192.168.1.102:7000
        - 192.168.1.102:7001
      max-redirects: 3
    password: your_password
    lettuce:
      pool:
        max-active: 16
        max-idle: 8
        min-idle: 4

集群模式下的批量操作:

java 复制代码
@Service
public class ClusterBatchService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 集群模式下的批量操作(按slot分组)
     */
    public void clusterBatchSet(Map<String, String> data) {
        // 按slot分组
        Map<Integer, Map<String, String>> slotMap = new HashMap<>();
        
        for (Map.Entry<String, String> entry : data.entrySet()) {
            int slot = calculateSlot(entry.getKey());
            slotMap.computeIfAbsent(slot, k -> new HashMap<>())
                .put(entry.getKey(), entry.getValue());
        }
        
        // 每个slot使用Pipeline批量操作
        for (Map<String, String> slotData : slotMap.values()) {
            redisTemplate.executePipelined(new SessionCallback<Object>() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    for (Map.Entry<String, String> entry : slotData.entrySet()) {
                        operations.opsForValue().set(entry.getKey(), entry.getValue());
                    }
                    return null;
                }
            });
        }
    }
    
    /**
     * 计算key的slot
     */
    private int calculateSlot(String key) {
        return CRC16.crc16(key.getBytes()) % 16384;
    }
}

📊 七、Redis监控与运维

7.1 性能监控

java 复制代码
@Component
@Slf4j
public class RedisMonitor {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Scheduled(fixedRate = 60000)  // 每分钟执行
    public void monitorRedis() {
        Properties info = redisTemplate.execute((RedisCallback<Properties>) connection -> {
            return connection.info();
        });
        
        if (info != null) {
            log.info("=== Redis监控信息 ===");
            log.info("已使用内存: {}", info.getProperty("used_memory_human"));
            log.info("内存峰值: {}", info.getProperty("used_memory_peak_human"));
            log.info("连接的客户端数: {}", info.getProperty("connected_clients"));
            log.info("阻塞的客户端数: {}", info.getProperty("blocked_clients"));
            log.info("键总数: {}", info.getProperty("db0"));
            log.info("命中率: {}%", calculateHitRate(info));
        }
    }
    
    private double calculateHitRate(Properties info) {
        long hits = Long.parseLong(info.getProperty("keyspace_hits", "0"));
        long misses = Long.parseLong(info.getProperty("keyspace_misses", "0"));
        
        if (hits + misses == 0) {
            return 0;
        }
        
        return (double) hits / (hits + misses) * 100;
    }
    
    /**
     * 慢查询监控
     */
    @Scheduled(fixedRate = 300000)  // 每5分钟执行
    public void monitorSlowLog() {
        List<Object> slowLogs = redisTemplate.execute((RedisCallback<List<Object>>) connection -> {
            return connection.slowLogGet(10);  // 获取最近10条慢查询
        });
        
        if (slowLogs != null && !slowLogs.isEmpty()) {
            log.warn("=== Redis慢查询 ===");
            slowLogs.forEach(log::warn);
        }
    }
}

7.2 内存优化

java 复制代码
@Service
public class MemoryOptimization {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 清理过期键
     */
    @Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点执行
    public void cleanExpiredKeys() {
        // Redis会自动清理过期键,这里可以手动触发
        redisTemplate.execute((RedisCallback<Object>) connection -> {
            connection.bgRewriteAof();  // 触发AOF重写
            return null;
        });
    }
    
    /**
     * 分析大Key
     */
    public void analyzeBigKeys() {
        Set<String> keys = redisTemplate.keys("*");
        
        if (keys != null) {
            Map<String, Long> bigKeys = new HashMap<>();
            
            for (String key : keys) {
                Long size = redisTemplate.execute((RedisCallback<Long>) connection -> {
                    DataType type = connection.type(key.getBytes());
                    
                    switch (type) {
                        case STRING:
                            return connection.strLen(key.getBytes());
                        case LIST:
                            return connection.lLen(key.getBytes());
                        case SET:
                            return connection.sCard(key.getBytes());
                        case ZSET:
                            return connection.zCard(key.getBytes());
                        case HASH:
                            return connection.hLen(key.getBytes());
                        default:
                            return 0L;
                    }
                });
                
                if (size != null && size > 10000) {  // 大于10000认为是大Key
                    bigKeys.put(key, size);
                }
            }
            
            // 输出大Key
            bigKeys.entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                .limit(10)
                .forEach(entry -> 
                    log.warn("大Key: {}, 大小: {}", entry.getKey(), entry.getValue())
                );
        }
    }
}

🎯 八、实战案例

8.1 秒杀系统

java 复制代码
@Service
@Slf4j
public class SeckillService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 初始化秒杀库存
     */
    public void initSeckillStock(Long productId, Integer stock) {
        String key = "seckill:stock:" + productId;
        redisTemplate.opsForValue().set(key, stock.toString());
    }
    
    /**
     * 秒杀下单
     */
    public boolean seckill(Long productId, Long userId) {
        String stockKey = "seckill:stock:" + productId;
        String userKey = "seckill:user:" + productId + ":" + userId;
        
        // 1. 检查用户是否已经购买
        Boolean exists = redisTemplate.hasKey(userKey);
        if (Boolean.TRUE.equals(exists)) {
            log.warn("用户{}已经购买过商品{}", userId, productId);
            return false;
        }
        
        // 2. 使用Lua脚本扣减库存
        String script = 
            "local stock = tonumber(redis.call('get', KEYS[1]) or '0') " +
            "if stock > 0 then " +
            "    redis.call('decr', KEYS[1]) " +
            "    redis.call('setex', KEYS[2], 86400, '1') " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Arrays.asList(stockKey, userKey)
        );
        
        if (result != null && result > 0) {
            // 3. 异步创建订单
            CompletableFuture.runAsync(() -> {
                try {
                    orderService.createOrder(productId, userId);
                } catch (Exception e) {
                    log.error("创建订单失败", e);
                    // 回滚库存
                    redisTemplate.opsForValue().increment(stockKey);
                    redisTemplate.delete(userKey);
                }
            });
            
            return true;
        }
        
        return false;
    }
    
    /**
     * 查询剩余库存
     */
    public Integer getRemainStock(Long productId) {
        String key = "seckill:stock:" + productId;
        String stock = redisTemplate.opsForValue().get(key);
        return stock != null ? Integer.parseInt(stock) : 0;
    }
}

8.2 分布式Session

java 复制代码
@Configuration
public class SessionConfig {
    
    @Bean
    public RedisTemplate<String, Object> sessionRedisTemplate(
        RedisConnectionFactory connectionFactory
    ) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        // 使用Jackson序列化
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        return template;
    }
}

@Component
public class SessionManager {
    
    @Autowired
    private RedisTemplate<String, Object> sessionRedisTemplate;
    
    private static final String SESSION_PREFIX = "session:";
    private static final long SESSION_TIMEOUT = 1800; // 30分钟
    
    /**
     * 创建Session
     */
    public String createSession(Long userId, Map<String, Object> attributes) {
        String sessionId = UUID.randomUUID().toString();
        String key = SESSION_PREFIX + sessionId;
        
        Map<String, Object> sessionData = new HashMap<>(attributes);
        sessionData.put("userId", userId);
        sessionData.put("createTime", System.currentTimeMillis());
        
        sessionRedisTemplate.opsForHash().putAll(key, sessionData);
        sessionRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);
        
        return sessionId;
    }
    
    /**
     * 获取Session
     */
    public Map<Object, Object> getSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        
        Map<Object, Object> session = sessionRedisTemplate.opsForHash().entries(key);
        
        if (!session.isEmpty()) {
            // 刷新过期时间
            sessionRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);
        }
        
        return session;
    }
    
    /**
     * 更新Session
     */
    public void updateSession(String sessionId, String field, Object value) {
        String key = SESSION_PREFIX + sessionId;
        sessionRedisTemplate.opsForHash().put(key, field, value);
        sessionRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);
    }
    
    /**
     * 删除Session
     */
    public void removeSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        sessionRedisTemplate.delete(key);
    }
}

8.3 限流器

java 复制代码
@Component
public class RateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 固定窗口限流
     */
    public boolean tryAcquireFixedWindow(String key, int maxCount, int windowSeconds) {
        String redisKey = "rate_limit:fixed:" + key;
        
        String script = 
            "local current = tonumber(redis.call('get', KEYS[1]) or '0') " +
            "if current < tonumber(ARGV[1]) then " +
            "    redis.call('incr', KEYS[1]) " +
            "    if current == 0 then " +
            "        redis.call('expire', KEYS[1], ARGV[2]) " +
            "    end " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(redisKey),
            String.valueOf(maxCount),
            String.valueOf(windowSeconds)
        );
        
        return result != null && result > 0;
    }
    
    /**
     * 滑动窗口限流
     */
    public boolean tryAcquireSlidingWindow(String key, int maxCount, int windowSeconds) {
        String redisKey = "rate_limit:sliding:" + key;
        long now = System.currentTimeMillis();
        long windowStart = now - windowSeconds * 1000L;
        
        String script = 
            "redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1]) " +
            "local count = redis.call('zcard', KEYS[1]) " +
            "if count < tonumber(ARGV[3]) then " +
            "    redis.call('zadd', KEYS[1], ARGV[2], ARGV[2]) " +
            "    redis.call('expire', KEYS[1], ARGV[4]) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(redisKey),
            String.valueOf(windowStart),
            String.valueOf(now),
            String.valueOf(maxCount),
            String.valueOf(windowSeconds)
        );
        
        return result != null && result > 0;
    }
    
    /**
     * 令牌桶限流(使用Redisson)
     */
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean tryAcquireTokenBucket(String key, int rate, int capacity) {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        
        // 初始化限流器
        rateLimiter.trySetRate(RateType.OVERALL, rate, 1, RateIntervalUnit.SECONDS);
        
        // 尝试获取令牌
        return rateLimiter.tryAcquire();
    }
}

// 使用示例
@RestController
@RequestMapping("/api")
public class ApiController {
    
    @Autowired
    private RateLimiter rateLimiter;
    
    @GetMapping("/data")
    public Result<String> getData(HttpServletRequest request) {
        String clientIp = request.getRemoteAddr();
        
        // 限流:每个IP每秒最多10次请求
        if (!rateLimiter.tryAcquireSlidingWindow(clientIp, 10, 1)) {
            return Result.fail("请求过于频繁,请稍后重试");
        }
        
        return Result.success("数据内容");
    }
}

🎓 总结

本文全面介绍了Redis的核心应用技术:

  1. 数据结构应用:String、Hash、List、Set、ZSet的实战场景
  2. 分布式锁:基于SETNX、Redisson、RedLock的实现
  3. 缓存设计:Cache Aside、Read/Write Through、Write Behind
  4. 常见问题:缓存穿透、击穿、雪崩的解决方案
  5. 性能优化:Pipeline、Lua脚本、大Key优化
  6. 高可用架构:主从复制、哨兵模式、集群模式
  7. 监控运维:性能监控、内存优化、慢查询分析
  8. 实战案例:秒杀系统、分布式Session、限流器

掌握这些技术,你将能够构建高性能、高可用的Redis应用!


💬 欢迎讨论:你在Redis实践中遇到过哪些问题?欢迎在评论区分享!

🌟 觉得有帮助? 点赞、收藏、关注三连支持一下吧!

相关推荐
川贝枇杷膏cbppg11 小时前
Redis 的 RDB 持久化
前端·redis·bootstrap
源代码•宸12 小时前
goframe框架签到系统项目(BITFIELD 命令详解、Redis Key 设计、goframe 框架教程、安装MySQL)
开发语言·数据库·经验分享·redis·后端·mysql·golang
川贝枇杷膏cbppg12 小时前
Redis 的 AOF
java·数据库·redis
Wang's Blog14 小时前
Kafka: 消费者核心机制
分布式·kafka
今晚务必早点睡14 小时前
Redis——快速入门第二课:Redis 常用命令 + 能解决实际问题
数据库·redis·bootstrap
墨者阳16 小时前
数据库的自我修炼
数据库·sql·缓存·性能优化
学海_无涯_苦作舟16 小时前
分布式事务的解决方案
分布式
ZePingPingZe17 小时前
秒杀-库存超卖&流量削峰
java·分布式
horizon727418 小时前
【Redis】Redis 分片集群搭建与故障转移实战指南
java·redis