MySQL/Redis等6大数据库,在7种Java业务中的选型与调优

24 阅读5分钟

微信图片_20251013140730_23_2.jpg

MySQL/Redis等6大数据库,在7种Java业务中的选型与调优---youkeit.xyz/898/

从单库到多模协同:Java业务如何用MySQL+Redis构建"关系+缓存"双引擎

引言:双引擎架构的必要性

在当今高并发、大数据的应用场景下,单纯依赖关系型数据库已难以满足业务需求。MySQL作为可靠的关系型数据库,与Redis这一高性能内存数据库的结合,形成了"关系+缓存"的双引擎架构,能够同时保证数据一致性和系统高性能。本文将深入探讨这种架构的设计思路,并通过完整代码示例展示实现方案。

一、基础架构设计

1.1 整体架构图

[客户端] 
   ↓ HTTP请求
[Spring Boot应用层] 
   ↓ 双引擎协同
[Redis缓存层][MySQL持久层]

1.2 依赖配置

首先在pom.xml中添加必要依赖:

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    
    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- Lettuce连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
</dependencies>

二、MySQL关系型存储实现

2.1 实体类设计

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private BigDecimal price;
    
    @Column(nullable = false)
    private Integer stock;
    
    @Column(updatable = false)
    private LocalDateTime createTime = LocalDateTime.now();
    
    @Version
    private Integer version; // 乐观锁版本号
}

2.2 JPA Repository

public interface ProductRepository extends JpaRepository<Product, Long> {
    
    // 自定义查询方法
    List<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
    
    @Modifying
    @Query("UPDATE Product p SET p.stock = p.stock - :quantity WHERE p.id = :id AND p.stock >= :quantity")
    int reduceStock(@Param("id") Long id, @Param("quantity") Integer quantity);
}

三、Redis缓存层实现

3.1 Redis配置类

@Configuration
@EnableCaching
public class RedisConfig {
    
    @Value("${spring.redis.host}")
    private String host;
    
    @Value("${spring.redis.port}")
    private int port;
    
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
        LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
        factory.setShareNativeConnection(false); // 每个操作独立连接
        return factory;
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        
        // 使用Jackson序列化
        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);
        
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30)) // 默认缓存30分钟
            .disableCachingNullValues() // 不缓存null值
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(redisConnectionFactory())
            .cacheDefaults(config)
            .transactionAware()
            .build();
    }
}

3.2 缓存服务封装

@Service
public class RedisService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 设置缓存
     */
    public <T> void set(String key, T value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
    
    /**
     * 获取缓存
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String key) {
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        return (T) ops.get(key);
    }
    
    /**
     * 删除缓存
     */
    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }
    
    /**
     * 批量删除
     */
    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }
    
    /**
     * 设置哈希结构
     */
    public <T> void hSet(String key, String hashKey, T value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }
    
    /**
     * 获取哈希结构
     */
    @SuppressWarnings("unchecked")
    public <T> T hGet(String key, String hashKey) {
        HashOperations<String, String, T> ops = redisTemplate.opsForHash();
        return (T) ops.get(key, hashKey);
    }
}

四、双引擎协同策略

4.1 缓存穿透解决方案

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisService redisService;
    
    private static final String PRODUCT_CACHE_PREFIX = "product:";
    
    /**
     * 获取商品详情 - 解决缓存穿透
     */
    public Product getProductDetail(Long id) {
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        
        // 1. 先查缓存
        Product product = redisService.get(cacheKey);
        if (product != null) {
            // 缓存命中
            return product;
        }
        
        // 2. 缓存未命中,查询数据库
        product = productRepository.findById(id).orElse(null);
        
        // 3. 数据库不存在,缓存空对象防止穿透
        if (product == null) {
            redisService.set(cacheKey, new NullValue(), 5, TimeUnit.MINUTES);
            return null;
        }
        
        // 4. 数据库存在,写入缓存
        redisService.set(cacheKey, product, 30, TimeUnit.MINUTES);
        return product;
    }
    
    // 空对象标识
    private static class NullValue implements Serializable {
        private static final long serialVersionUID = 1L;
    }
}

4.2 缓存雪崩防护

/**
 * 获取商品列表 - 解决缓存雪崩
 */
public List<Product> listProducts() {
    String cacheKey = "product:list";
    
    // 1. 先查缓存
    List<Product> products = redisService.get(cacheKey);
    if (products != null) {
        return products;
    }
    
    // 2. 缓存未命中,查询数据库
    products = productRepository.findAll();
    
    // 3. 写入缓存,随机过期时间防止雪崩
    if (!products.isEmpty()) {
        Random random = new Random();
        int timeout = 30 + random.nextInt(15); // 30-45分钟随机
        redisService.set(cacheKey, products, timeout, TimeUnit.MINUTES);
    }
    
    return products;
}

4.3 热点数据缓存策略

/**
 * 获取热门商品 - 热点数据多级缓存
 */
public List<Product> getHotProducts() {
    // 一级缓存:本地缓存
    List<Product> hotProducts = localCache.get("hot_products");
    if (hotProducts != null) {
        return hotProducts;
    }
    
    // 二级缓存:Redis缓存
    hotProducts = redisService.get("hot_products");
    if (hotProducts != null) {
        // 刷新本地缓存
        localCache.put("hot_products", hotProducts);
        return hotProducts;
    }
    
    // 三级缓存:数据库
    hotProducts = productRepository.findTop10ByOrderBySalesDesc();
    
    // 写入多级缓存
    if (!hotProducts.isEmpty()) {
        redisService.set("hot_products", hotProducts, 10, TimeUnit.MINUTES);
        localCache.put("hot_products", hotProducts);
    }
    
    return hotProducts;
}

五、事务一致性保障

5.1 双写一致性方案

@Transactional
public void updateProduct(Product product) {
    // 1. 更新数据库
    productRepository.save(product);
    
    // 2. 删除缓存
    String cacheKey = PRODUCT_CACHE_PREFIX + product.getId();
    redisService.delete(cacheKey);
    
    // 3. 异步更新缓存
    CompletableFuture.runAsync(() -> {
        Product updated = productRepository.findById(product.getId()).orElse(null);
        if (updated != null) {
            redisService.set(cacheKey, updated, 30, TimeUnit.MINUTES);
        }
    });
}

5.2 分布式锁防并发

public boolean reduceStock(Long productId, Integer quantity) {
    String lockKey = "product:lock:" + productId;
    String requestId = UUID.randomUUID().toString();
    
    try {
        // 获取分布式锁
        boolean locked = redisTemplate.opsForValue().setIfAbsent(
            lockKey, requestId, 10, TimeUnit.SECONDS);
        
        if (!locked) {
            throw new RuntimeException("操作太频繁,请稍后再试");
        }
        
        // 查询商品
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new RuntimeException("商品不存在"));
        
        // 检查库存
        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
        
        // 扣减库存
        int affected = productRepository.reduceStock(productId, quantity);
        if (affected == 0) {
            throw new RuntimeException("库存扣减失败");
        }
        
        // 删除缓存
        redisService.delete(PRODUCT_CACHE_PREFIX + productId);
        
        return true;
    } finally {
        // 释放锁
        if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
            redisTemplate.delete(lockKey);
        }
    }
}

六、性能优化实践

6.1 批量操作优化

public List<Product> batchGetProducts(List<Long> ids) {
    // 1. 先从缓存批量获取
    List<String> keys = ids.stream()
        .map(id -> PRODUCT_CACHE_PREFIX + id)
        .collect(Collectors.toList());
    
    List<Product> cachedProducts = redisTemplate.opsForValue().multiGet(keys);
    
    // 2. 找出未命中的ID
    List<Long> missingIds = new ArrayList<>();
    List<Product> result = new ArrayList<>();
    
    for (int i = 0; i < ids.size(); i++) {
        if (cachedProducts.get(i) != null) {
            result.add(cachedProducts.get(i));
        } else {
            missingIds.add(ids.get(i));
        }
    }
    
    // 3. 批量查询数据库
    if (!missingIds.isEmpty()) {
        List<Product> dbProducts = productRepository.findAllById(missingIds);
        
        // 4. 批量写入缓存
        Map<String, Product> cacheMap = new HashMap<>();
        for (Product product : dbProducts) {
            cacheMap.put(PRODUCT_CACHE_PREFIX + product.getId(), product);
        }
        redisTemplate.opsForValue().multiSet(cacheMap);
        
        // 5. 设置过期时间
        for (String key : cacheMap.keySet()) {
            redisTemplate.expire(key, 30, TimeUnit.MINUTES);
        }
        
        result.addAll(dbProducts);
    }
    
    return result;
}

6.2 管道技术提升吞吐量

public void refreshProductCache(List<Long> productIds) {
    redisTemplate.executePipelined(new RedisCallback<Object>() {
        @Override
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            for (Long id : productIds) {
                Product product = productRepository.findById(id).orElse(null);
                if (product != null) {
                    String key = PRODUCT_CACHE_PREFIX + id;
                    byte[] value = redisTemplate.getValueSerializer().serialize(product);
                    connection.set(key.getBytes(), value);
                    connection.expire(key.getBytes(), 1800); // 30分钟
                }
            }
            return null;
        }
    });
}

七、监控与运维

7.1 缓存命中率统计

@Aspect
@Component
public class CacheHitAspect {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String CACHE_HIT_KEY = "cache:stats:hit";
    private static final String CACHE_MISS_KEY = "cache:stats:miss";
    
    @Around("@annotation(org.springframework.cache.annotation.Cacheable)")
    public Object trackCacheHit(ProceedingJoinPoint joinPoint) throws Throwable {
        String cacheName = getCacheName(joinPoint);
        
        try {
            Object result = joinPoint.proceed();
            if (result != null) {
                redisTemplate.opsForValue().increment(CACHE_HIT_KEY + ":" + cacheName);
            }
            return result;
        } catch (Exception e) {
            redisTemplate.opsForValue().increment(CACHE_MISS_KEY + ":" + cacheName);
            throw e;
        }
    }
    
    private String getCacheName(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Cacheable cacheable = signature.getMethod().getAnnotation(Cacheable.class);
        return cacheable.value()[0];
    }
    
    public Map<String, Double> getCacheHitRate() {
        Set<String> cacheNames = redisTemplate.keys(CACHE_HIT_KEY + ":*");
        Map<String, Double> hitRates = new HashMap<>();
        
        for (String hitKey : cacheNames) {
            String cacheName = hitKey.substring(CACHE_HIT_KEY.length() + 1);
            String missKey = CACHE_MISS_KEY + ":" + cacheName;
            
            Long hitCount = (Long) redisTemplate.opsForValue().get(hitKey);
            Long missCount = (Long) redisTemplate.opsForValue().get(missKey);
            
            hitCount = hitCount != null ? hitCount : 0L;
            missCount = missCount != null ? missCount : 0L;
            
            double hitRate = (double) hitCount / (hitCount + missCount);
            hitRates.put(cacheName, hitRate);
        }
        
        return hitRates;
    }
}

结语:双引擎架构的最佳实践

MySQL+Redis的双引擎架构为Java业务系统提供了可靠的数据持久化和高性能的缓存能力。通过本文的代码示例,我们展示了:

  1. 基础配置:如何正确配置Spring Boot集成MySQL和Redis
  2. 协同策略:缓存穿透、雪崩、热点数据的解决方案
  3. 一致保障:事务一致性和分布式锁的实现
  4. 性能优化:批量操作和管道技术的应用
  5. 运维监控:缓存命中率的统计方法

在实际项目中,应根据业务特点选择合适的缓存策略,并通过监控不断优化缓存配置。记住:没有银弹,最适合业务场景的方案才是最好的方案。