淘客返利APP性能优化实战:从MySQL分库分表到Redis缓存穿透解决方案
淘客返利APP性能优化实战:从MySQL分库分表到Redis缓存穿透解决方案
大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!
一、背景介绍
在淘客返利APP的开发过程中,随着用户量和数据量的不断增长,系统性能面临巨大挑战。尤其是数据库和缓存的性能瓶颈,直接影响到用户体验和系统的稳定性。本文将分享我们在MySQL分库分表以及Redis缓存穿透问题上的实战经验,希望能为类似项目提供参考。
二、MySQL分库分表优化
(一)分库分表的必要性
随着业务的快速发展,单表数据量不断增加,导致查询和更新操作的性能大幅下降。分库分表可以有效缓解这一问题,通过将数据分散到多个数据库或表中,减少单表的数据量,提升查询效率。
(二)分库分表策略
我们采用水平分表的方式,将佣金记录表按时间维度进行拆分。例如,每月生成一个新的表,表名格式为commission_record_YYYYMM。同时,为了进一步提升性能,我们还对用户信息表进行了分库操作,将用户数据按照用户ID的哈希值分配到不同的数据库中。
分表代码示例:
package cn.juwatech.common.utils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class TableNameUtil {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
public static String getCommissionRecordTableName(LocalDate date) {
return "commission_record_" + date.format(FORMATTER);
}
}
分表查询代码示例:
package cn.juwatech.commission.repository;
import cn.juwatech.common.utils.TableNameUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
@Repository
public class CommissionRecordRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<CommissionRecord> getCommissionRecords(LocalDate date) {
String tableName = TableNameUtil.getCommissionRecordTableName(date);
String sql = "SELECT * FROM " + tableName;
return jdbcTemplate.query(sql, new CommissionRecordRowMapper());
}
}
(三)分库操作
对于用户信息表,我们采用分库策略,将用户数据按照用户ID的哈希值分配到不同的数据库中。例如,我们创建了10个数据库,用户ID的哈希值对10取模的结果决定了用户数据存储在哪个数据库中。
分库代码示例:
package cn.juwatech.common.utils;
public class DatabaseUtil {
public static int getDatabaseIndex(Long userId) {
return Math.abs(userId.hashCode()) % 10;
}
}
分库查询代码示例:
package cn.juwatech.user.repository;
import cn.juwatech.common.utils.DatabaseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUserById(Long userId) {
int dbIndex = DatabaseUtil.getDatabaseIndex(userId);
String sql = "SELECT * FROM user WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{userId}, new UserRowMapper());
}
}
三、Redis缓存穿透解决方案
(一)缓存穿透问题
缓存穿透是指查询一个不存在的数据,由于缓存不会保存不存在的数据,每次都会查询数据库,导致数据库压力增大。在淘客返利APP中,用户可能会频繁查询不存在的返利规则或订单信息,导致缓存穿透问题。
(二)解决方案
我们采用以下几种方式解决缓存穿透问题:
- 缓存空对象:对于查询不到的数据,将空对象或特殊标记存储到缓存中,避免后续重复查询数据库。
- 布隆过滤器:使用布隆过滤器预先存储可能存在的数据ID,查询时先通过布隆过滤器判断是否存在,从而减少对数据库的无效查询。
缓存空对象代码示例:
package cn.juwatech.common.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public <T> T getFromCacheOrDb(String key, Class<T> clazz, Supplier<T> dbSupplier) {
T value = (T) redisTemplate.opsForValue().get(key);
if (value == null) {
value = dbSupplier.get();
if (value != null) {
redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);
} else {
// 缓存空对象
redisTemplate.opsForValue().set(key, "NOT_FOUND", 1, TimeUnit.MINUTES);
}
}
return value;
}
}
布隆过滤器代码示例:
package cn.juwatech.common.filter;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.util.concurrent.ConcurrentHashMap;
public class BloomFilterService {
private static final ConcurrentHashMap<String, BloomFilter<Long>> filters = new ConcurrentHashMap<>();
public static void createFilter(String key, int expectedInsertions, double fpp) {
BloomFilter<Long> filter = BloomFilter.create(Funnels.longFunnel(), expectedInsertions, fpp);
filters.put(key, filter);
}
public static boolean mightContain(String key, Long id) {
BloomFilter<Long> filter = filters.get(key);
return filter != null && filter.mightContain(id);
}
public static void put(String key, Long id) {
BloomFilter<Long> filter = filters.get(key);
if (filter != null) {
filter.put(id);
}
}
}
使用布隆过滤器优化查询代码示例:
package cn.juwatech.commission.service;
import cn.juwatech.common.cache.RedisCacheService;
import cn.juwatech.common.filter.BloomFilterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CommissionService {
@Autowired
private RedisCacheService redisCacheService;
public Commission getCommissionById(Long id) {
if (!BloomFilterService.mightContain("commission_ids", id)) {
return null;
}
return redisCacheService.getFromCacheOrDb("commission_" + id, Commission.class, () -> {
// 查询数据库
return queryCommissionFromDb(id);
});
}
private Commission queryCommissionFromDb(Long id) {
// 数据库查询逻辑
return new Commission();
}
}
(三)缓存雪崩与失效问题
缓存雪崩是指大量缓存同时失效,导致大量请求直接打到数据库,引发数据库崩溃。我们通过以下方式解决:
- 设置不同的过期时间:为缓存设置随机的过期时间,避免大量缓存同时失效。
- 本地缓存兜底:使用本地缓存(如Guava Cache)作为二级缓存,当Redis缓存失效时,本地缓存可以提供临时数据。
本地缓存代码示例:
package cn.juwatech.common.cache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.TimeUnit;
public class LocalCacheService {
private static final Cache<String, Object> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public static <T> T getFromLocalCache(String key, Class<T> clazz, Supplier<T> dbSupplier) {
T value = (T) cache.getIfPresent(key);
if (value == null) {
value = dbSupplier.get();
if (value != null) {
cache.put(key, value);
}
}
return value;
}
}
结合本地缓存与Redis缓存的查询代码示例:
package cn.juwatech.commission.service;
import cn.juwatech.common.cache.LocalCacheService;
import cn.juwatech.common.cache.RedisCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CommissionService {
@Autowired
private RedisCacheService redisCacheService;
public Commission getCommissionById(Long id) {
return LocalCacheService.getFromLocalCache("commission_" + id, Commission.class, () -> {
return redisCacheService.getFromCacheOrDb("commission_" + id, Commission.class, () -> {
// 查询数据库
return queryCommissionFromDb(id);
});
});
}
private Commission queryCommissionFromDb(Long id) {
// 数据库查询逻辑
return new Commission();
}
}
四、总结
通过MySQL分库分表和Redis缓存穿透解决方案的实践,我们成功优化了淘客返利APP的性能,显著提升了系统的响应速度和稳定性。分库分表有效缓解了数据库的压力,而缓存穿透解决方案则减少了不必要的数据库查询,保障了系统的高可用性。未来,我们还将继续探索更多优化手段,进一步提升系统的性能和用户体验。
本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!
更多推荐



所有评论(0)