你真的会用 RedisTemplate 吗?含有企业级 Redis 工具类封装

2,895 阅读6分钟

你真的会用 RedisTemplate 吗?

Redis 是现代微服务架构中不可或缺的组件。它不仅仅是一个缓存工具,更是一个功能强大的内存数据结构存储系统,支持丰富的数据结构与多种典型业务场景。Spring 提供的 RedisTemplate 是操作 Redis 的强大工具,但你真的用对了吗?

本文将结合 Redis 的四大核心特性:缓存、数据存储、分布式协调、实时数据处理,通过具体业务场景 + 实战代码,带你深入掌握 RedisTemplate 的正确用法。


一、缓存:提升读取性能的利器

📌 业务场景

电商系统中,商品详情页访问频率极高,若每次都访问数据库,会造成巨大压力。此时,使用 Redis 缓存商品详情有效减轻数据库压力。

✅ 实现思路

典型的 Cache Aside Pattern(旁路缓存模式) :先查缓存,缓存没有再查数据库,并写入缓存。

🧩 示例代码

@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private ProductRepository productRepository;

    private static final String PRODUCT_KEY_PREFIX = "product:";

    public Product getProductById(Long productId) {
        String key = PRODUCT_KEY_PREFIX + productId;

        // 1. 先查缓存
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }

        // 2. 缓存未命中,查询数据库
        product = productRepository.findById(productId)
                .orElseThrow(() -> new RuntimeException("商品不存在"));

        // 3. 写入缓存,设置过期时间
        redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(10));

        return product;
    }
}

注意事项:防止缓存穿透、雪崩等问题,可以设置 null 值缓存、添加随机过期时间等。


二、数据存储:结构化存储用户会话或统计信息

📌 业务场景

用户登录后需要存储会话信息或用户的偏好设置、购物车等状态信息。

✅ 实现思路

利用 Redis 的数据结构,如 Hash、List、Set 等,存储结构化数据。

🧩 示例代码(用户会话信息存储)

@Service
public class SessionService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String SESSION_KEY_PREFIX = "session:";

    public void saveUserSession(String sessionId, Map<String, Object> sessionData) {
        String key = SESSION_KEY_PREFIX + sessionId;
        redisTemplate.opsForHash().putAll(key, sessionData);
        redisTemplate.expire(key, Duration.ofHours(1));
    }

    public Map<Object, Object> getUserSession(String sessionId) {
        String key = SESSION_KEY_PREFIX + sessionId;
        return redisTemplate.opsForHash().entries(key);
    }
}

优势:Hash 结构能很好地存储对象属性,支持字段级别的读取与更新,节省内存。


三、分布式协调:实现分布式锁

📌 业务场景

多个服务节点处理订单,要保证同一个订单只能被处理一次,需要分布式锁控制。

✅ 实现思路

利用 Redis 的 SET key value NX PX timeout 命令实现互斥锁。

🧩 示例代码(简易分布式锁)

@Component
public class RedisDistributedLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String LOCK_PREFIX = "lock:";

    public boolean tryLock(String key, String value, long expireTimeMillis) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(
                LOCK_PREFIX + key, value, Duration.ofMillis(expireTimeMillis));
        return Boolean.TRUE.equals(success);
    }

    public void unlock(String key, String value) {
        String redisKey = LOCK_PREFIX + key;
        String currentValue = (String) redisTemplate.opsForValue().get(redisKey);
        if (value.equals(currentValue)) {
            redisTemplate.delete(redisKey);
        }
    }
}

🧪 使用示例

if (redisDistributedLock.tryLock("order:123", UUID.randomUUID().toString(), 5000)) {
    try {
        // 处理订单逻辑
    } finally {
        redisDistributedLock.unlock("order:123", uuid);
    }
}

⚠️ 注意:为防止误删锁,需确保只有锁的持有者才能释放。可使用 Lua 脚本增强安全性。


四、实时数据处理:秒杀系统中的库存扣减

📌 业务场景

秒杀活动中,商品库存需高并发扣减,要求实时、原子性操作,不能超卖。

✅ 实现思路

使用 Redis 原子操作,如 decr,配合消息队列处理异步后逻辑。

🧩 示例代码

@Service
public class SeckillService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String STOCK_KEY_PREFIX = "seckill:stock:";

    public boolean trySeckill(Long productId) {
        String stockKey = STOCK_KEY_PREFIX + productId;

        Long stock = redisTemplate.opsForValue().decrement(stockKey);
        if (stock != null && stock >= 0) {
            // 发送订单消息到 MQ(略)
            return true;
        } else {
            // 回滚库存(避免负库存)
            redisTemplate.opsForValue().increment(stockKey);
            return false;
        }
    }
}

优化建议

  • 秒杀前先将库存预热到 Redis
  • 使用 Lua 脚本保证原子性更强
  • 异步下单处理+数据库最终一致性

总结:RedisTemplate 不止是“缓存工具”

特性场景核心 API 用法
缓存商品详情、用户信息缓存opsForValue().get/set
数据存储用户会话、购物车opsForHash().putAll/entries
分布式协调分布式锁、幂等控制setIfAbsent, delete
实时数据处理秒杀扣库存、排行榜decrement, increment, Lua 脚本

RedisTemplate 是一个强大但容易被误用的工具。理解 Redis 的数据结构与原子性操作,结合实际业务场景,才能真正发挥它的威力。


如果你读到这里,不妨回头问问自己:

你真的会用 RedisTemplate 吗?

欢迎点赞 + 收藏 + 转发,别忘了把这份指南分享给你正在“误用 Redis”的同事 😄


进阶用法

📦 1. Redis 工具类封装

避免每次都写 opsForX(),我们可以封装一个通用的 RedisUtils 工具类,提升开发效率和代码可读性。

🔧 RedisUtils 示例

@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // ========== String ==========
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public Boolean setIfAbsent(String key, Object value, Duration timeout) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout);
    }

    // ========== Hash ==========
    public void hSet(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    // ========== List ==========
    public Long lPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    public Object rPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    // ========== Set ==========
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    // ========== 通用 ==========
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }
}

🔐 2. 使用 Lua 脚本实现原子性分布式锁

Redis 的分布式锁要确保解锁操作具备“原子性”,避免误删其他线程的锁,Lua 是唯一方式。

📜 Lua 脚本解锁逻辑

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

✅ Java 实现

@Component
public class RedisLockWithLua {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String LOCK_PREFIX = "lock:";

    // 加锁
    public boolean tryLock(String key, String value, Duration expire) {
        return Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + key, value, expire)
        );
    }

    // 解锁(Lua 脚本)
    public boolean unlock(String key, String value) {
        String luaScript =
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(luaScript);
        script.setResultType(Long.class);

        Long result = redisTemplate.execute(script, Collections.singletonList(LOCK_PREFIX + key), value);
        return result != null && result == 1L;
    }
}

🧩 3. 自定义缓存注解(类似 Spring Cache)

更进一步,我们可以自定义注解 + AOP 拦截,实现自动缓存功能。

🧾 自定义注解

java

复制

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCacheable {
    String key(); // 缓存 key
    int ttl() default 300; // 缓存时间(秒)
}

🛠 AOP 实现自动缓存处理

@Aspect
@Component
public class RedisCacheAspect {

    @Autowired
    private RedisUtils redisUtils;

    @Around("@annotation(redisCacheable)")
    public Object around(ProceedingJoinPoint joinPoint, RedisCacheable redisCacheable) throws Throwable {
        String key = redisCacheable.key();
        int ttl = redisCacheable.ttl();

        // 1. 先查 Redis
        Object cache = redisUtils.get(key);
        if (cache != null) {
            return cache;
        }

        // 2. 执行业务方法
        Object result = joinPoint.proceed();

        // 3. 写入缓存
        redisUtils.set(key, result, ttl, TimeUnit.SECONDS);

        return result;
    }
}

📌 使用示例

@Service
public class UserService {

    @RedisCacheable(key = "user:info:#{#userId}", ttl = 600)
    public User getUserInfo(Long userId) {
        // 模拟数据库查询
        return userRepository.findById(userId).orElse(null);
    }
}

🧠 提示:上面 #{#userId} 是 SpEL 表达式,默认不支持,需要额外解析器支持,可以扩展 MethodBasedEvaluationContext 实现。


✅ 总结:打造 RedisTemplate 的企业级用法

功能模块技术点优势
Redis 工具类封装通用 API 封装提高代码可读性、复用性
Lua 分布式锁Redis + Lua 脚本解锁解锁原子性保障,防止误删
注解缓存AOP + 注解 + RedisTemplate自动缓存,提高开发效率

这些进阶用法能够帮助你将 RedisTemplate 真正用于生产级场景,打造高性能、可维护的服务系统。