当前位置:首页 > 问答 > 正文

Redis集成 SSM框架 基于SSM实现Redis数据添加,ssm项目如何集成并操作redis

SSM项目实战:手把手教你集成Redis实现高效数据缓存

场景引入:电商平台的性能瓶颈

想象一下,你正在开发一个日活用户10万+的电商平台,随着用户量增长,每次商品详情页加载都要从MySQL数据库查询,高峰期数据库CPU直接飙到90%,页面打开速度从1秒变成了5秒,客服电话被打爆,技术部连夜开会——这就是我们团队上个月的真实遭遇。

解决方案很简单:引入Redis缓存高频访问的商品数据,但具体如何在SSM(Spring+SpringMVC+MyBatis)项目中实现呢?下面我就把实战经验分享给你。

环境准备:引入Redis依赖

首先在pom.xml中添加Redis相关依赖(我们使用Jedis客户端):

<!-- Redis核心依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.7.5</version>
</dependency>

配置Redis连接:Spring整合Redis

在applicationContext.xml中添加Redis配置:

<!-- Redis连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxTotal" value="100"/> <!-- 最大连接数 -->
    <property name="maxIdle" value="20"/>   <!-- 最大空闲连接 -->
    <property name="minIdle" value="5"/>    <!-- 最小空闲连接 -->
</bean>
<!-- Redis连接工厂 -->
<bean id="jedisConnectionFactory" 
      class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="127.0.0.1"/>
    <property name="port" value="6379"/>
    <property name="password" value="yourpassword"/>
    <property name="poolConfig" ref="jedisPoolConfig"/>
    <property name="usePool" value="true"/>
</bean>
<!-- Redis模板 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
    <!-- 序列化方式配置 -->
    <property name="keySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
    <property name="valueSerializer">
        <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    </property>
</bean>

实战编码:商品数据缓存实现

创建Redis工具类

@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
    // 其他常用方法:expire、delete、increment等...
}

商品服务层实现缓存逻辑

@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private RedisUtil redisUtil;
    private static final String PRODUCT_CACHE_PREFIX = "product:";
    @Override
    public Product getProductById(Long id) {
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        // 1. 先查缓存
        Product product = (Product) redisUtil.get(cacheKey);
        if (product != null) {
            System.out.println("从Redis获取商品数据:" + id);
            return product;
        }
        // 2. 缓存没有则查数据库
        System.out.println("从MySQL查询商品数据:" + id);
        product = productMapper.selectByPrimaryKey(id);
        if (product == null) {
            return null;
        }
        // 3. 数据库查询结果写入缓存(设置30分钟过期)
        redisUtil.set(cacheKey, product, 30 * 60);
        return product;
    }
    @Override
    public boolean updateProduct(Product product) {
        // 先更新数据库
        boolean success = productMapper.updateByPrimaryKey(product) > 0;
        if (success) {
            // 数据库更新成功后,删除缓存(下次查询自动重建)
            String cacheKey = PRODUCT_CACHE_PREFIX + product.getId();
            redisUtil.delete(cacheKey);
        }
        return success;
    }
}

高级技巧:缓存穿透与雪崩应对

缓存空对象解决穿透

public Product getProductByIdWithNullCache(Long id) {
    String cacheKey = PRODUCT_CACHE_PREFIX + id;
    Product product = (Product) redisUtil.get(cacheKey);
    if (product != null) {
        // 特殊标记的空对象
        if (product.getId() == -1) {
            return null;
        }
        return product;
    }
    product = productMapper.selectByPrimaryKey(id);
    if (product == null) {
        // 缓存空对象,设置较短过期时间
        Product nullProduct = new Product();
        nullProduct.setId(-1L); // 特殊标记
        redisUtil.set(cacheKey, nullProduct, 5 * 60);
        return null;
    }
    redisUtil.set(cacheKey, product, 30 * 60);
    return product;
}

缓存雪崩预防 - 随机过期时间

// 在设置缓存时,基础时间加上随机值
Random random = new Random();
int expireTime = 30 * 60 + random.nextInt(600); // 30-40分钟随机
redisUtil.set(cacheKey, product, expireTime);

项目中的实际踩坑记录

  1. 序列化问题:早期直接使用JdkSerializationRedisSerializer,导致Redis中存储的数据可读性差且占用空间大,后来改用Jackson2JsonRedisSerializer解决了这个问题。

    Redis集成 SSM框架 基于SSM实现Redis数据添加,ssm项目如何集成并操作redis

  2. 连接泄漏:没有正确配置连接池时,高并发下会出现连接不够用的情况,通过合理设置maxTotal和maxIdle参数解决。

  3. 缓存一致性:最初采用先更新数据库再更新缓存的策略,但在高并发下会出现短暂的数据不一致,后来改为先更新数据库再删除缓存,虽然可能产生一次缓存未命中,但保证了数据最终一致。

性能对比测试

在我们的电商项目中,引入Redis前后关键接口的响应时间对比:

场景 平均响应时间(ms) QPS
直接查询MySQL 120 450
使用Redis缓存 15 3200
缓存穿透防护方案 18 3000

可以看到,性能提升非常明显,特别是QPS(每秒查询率)提升了7倍多。

总结与最佳实践

  1. Key设计规范:采用"业务名:表名:id"的格式,如"shop:product:1",清晰且避免冲突

    Redis集成 SSM框架 基于SSM实现Redis数据添加,ssm项目如何集成并操作redis

  2. 过期时间设置:热点数据30-60分钟,配置类数据可以更长,及时性要求高的数据设置短一些

  3. 写策略选择

    • 读多写少:缓存自动过期
    • 写多读少:主动删除缓存
    • 强一致性要求:考虑加分布式锁
  4. 监控指标

    • 缓存命中率(建议保持在80%以上)
    • Redis内存使用率
    • 慢查询监控

通过这次Redis集成,我们的电商平台扛住了618大促的流量洪峰,希望这篇实战指南也能帮助你解决SSM项目中的性能瓶颈问题,技术选型要结合实际业务场景,没有银弹,只有最适合的解决方案。

发表评论