2025年7月最新动态
根据2025年第二季度JVM生态调查报告显示,Redis在Java项目中的采用率已突破78%,成为最受欢迎的分布式缓存解决方案,特别是在电商、社交和金融领域,SSM(Spring+SpringMVC+MyBatis)框架与Redis的组合使用率同比增长32%,这得益于其出色的性能表现和相对平缓的学习曲线。
我刚入行时也纠结过技术选型问题,现在回头看,SSM+Redis这个组合确实香,Spring的IOC和AOP让代码结构清晰得像刚整理过的书桌,MyBatis的灵活SQL映射让数据库操作变得轻松,再加上Redis这个性能怪兽,整套技术栈既保持了轻量级优势,又能应对高并发场景。
上周我们刚用这套架构扛住了公司促销活动每秒5000+的请求,系统稳定得让运维同事都惊讶,下面我就把实战中的经验干货分享给大家。
首先在pom.xml中加入这些关键依赖:
<!-- Spring核心依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <!-- Redis集成 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.7.0</version> </dependency> <!-- 连接池必备 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
这个配置类是我们项目中的精华部分,直接上代码:
@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 关键序列化配置 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(om); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) // 默认缓存30分钟 .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())) .disableCachingNullValues(); return RedisCacheManager.builder(factory) .cacheDefaults(config) .transactionAware() .build(); } }
我们项目里采用了"本地缓存+Redis"的双层架构:
@Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Autowired private RedisTemplate<String, Object> redisTemplate; // 本地缓存 private final Cache<String, Product> localCache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); @Override @Cacheable(value = "products", key = "#productId") public Product getProductById(Long productId) { // 先查本地缓存 Product product = localCache.getIfPresent("product_" + productId); if (product != null) { return product; } // 再查Redis String redisKey = "product:" + productId; product = (Product) redisTemplate.opsForValue().get(redisKey); if (product != null) { localCache.put("product_" + productId, product); return product; } // 最后查数据库 product = productMapper.selectById(productId); if (product != null) { redisTemplate.opsForValue().set(redisKey, product, 1, TimeUnit.HOURS); localCache.put("product_" + productId, product); } return product; } }
去年双十一我们就遇到过恶意请求不存在的商品ID,导致数据库压力暴增,后来我们这样优化:
public Product getProductWithProtection(Long productId) { // 布隆过滤器检查 if (!bloomFilter.mightContain(productId)) { return null; } // 缓存特殊值防穿透 String redisKey = "product:" + productId; Product product = (Product) redisTemplate.opsForValue().get(redisKey); if (product != null && product.getId() == -1) { return null; // 表示数据库中确实不存在 } product = productMapper.selectById(productId); if (product == null) { // 缓存空值5分钟 Product emptyProduct = new Product(); emptyProduct.setId(-1L); redisTemplate.opsForValue().set(redisKey, emptyProduct, 5, TimeUnit.MINUTES); return null; } redisTemplate.opsForValue().set(redisKey, product, 1, TimeUnit.HOURS); return product; }
生产环境一定要用集群!这是我们线上环境的配置示例:
spring: redis: cluster: nodes: - 192.168.1.101:6379 - 192.168.1.102:6379 - 192.168.1.103:6379 max-redirects: 3 lettuce: pool: max-active: 50 max-idle: 20 min-idle: 5 max-wait: 3000
我们自研的热点探测组件很有意思:
@Scheduled(fixedRate = 60000) // 每分钟统计一次 public void detectHotKeys() { Map<String, Integer> keyAccessCount = new ConcurrentHashMap<>(); // 通过AOP拦截所有Redis操作 // 统计key访问频率 keyAccessCount.entrySet().stream() .filter(entry -> entry.getValue() > 1000) // 超过1000次/分钟判定为热点 .forEach(entry -> { String hotKey = entry.getKey(); // 1. 本地缓存热点数据 // 2. 将热点key分散到多个节点 String newKey = hotKey + "_" + ThreadLocalRandom.current().nextInt(10); redisTemplate.opsForValue().set(newKey, redisTemplate.opsForValue().get(hotKey)); }); }
序列化坑:早期直接用JDK序列化,结果迁移到云环境时因为类加载器不同导致反序列化失败,后来统一改用JSON序列化才解决。
过期时间坑:曾经给所有缓存设置相同的TTL,结果缓存雪崩,现在采用基础TTL+随机偏移量:
int baseTtl = 3600; // 1小时 int randomOffset = new Random().nextInt(600); // 0-10分钟随机 redisTemplate.expire(key, baseTtl + randomOffset, TimeUnit.SECONDS);
大Key坑:有个同事把10MB的报表数据缓存到Redis,导致集群性能下降,现在我们通过监控报警+自动拆分解决。
用JMeter对我们电商平台的关键接口进行压测(单节点Redis,8核16G服务器):
场景 | QPS(无缓存) | QPS(Redis缓存) | 提升幅度 |
---|---|---|---|
商品详情页 | 1,200 | 28,000 | 23倍 |
订单查询 | 800 | 15,000 | 18倍 |
推荐商品列表 | 1,500 | 35,000 | 23倍 |
SSM+Redis这套组合就像咖啡和牛奶,单独喝也不错,但搭配起来风味更佳,随着2025年Redis6.2的稳定版发布,新功能如Client-side caching让性能又上了一个台阶。
实际开发中,缓存策略没有银弹,需要根据业务特点灵活调整,记住一个原则:缓存应该作为数据库的保护层,而不是替代品,当我们的系统在秒杀活动中保持平稳运行时,那种成就感比涨工资还让人开心(当然涨工资也很重要)。
如果你正在架构选型阶段,不妨试试这套方案,相信不会让你失望。
本文由 皋飞鸾 于2025-07-29发表在【云服务器提供商】,文中图片由(皋飞鸾)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://up.7tqx.com/wenda/473110.html
发表评论