基于SpringBoot + RedisJSON + RedisSearch:用 Redis 替代部分 MySQL,实现高性能文档查询
今天咱们聊聊一个在高并发场景下很有意思的话题:用Redis做文档查询。
传统关系型数据库的局限
在我们的日常开发工作中,经常会遇到这样的场景:
- 用户表有几百万条数据,复杂的联合查询响应时间过长
- 电商商品信息查询需要全文搜索功能,MySQL性能不佳
- 配置信息、缓存数据需要结构化存储和快速查询
- 频繁的分页查询导致数据库压力过大
传统的MySQL等关系型数据库在处理半结构化数据查询时,性能往往不尽如人意。今天我们就来聊聊如何用RedisJSON + RedisSearch来解决这些问题。
为什么选择RedisJSON + RedisSearch
相比传统的数据库方案,RedisJSON + RedisSearch有以下优势:
- 文档存储:原生支持JSON文档存储和查询
- 全文搜索:内置强大的全文搜索引擎
- 高性能:内存存储,查询速度快
- 灵活Schema:支持动态字段,无需预定义表结构
- 丰富索引:支持文本、数值、地理等多种索引类型
解决方案思路
今天我们要解决的,就是如何用SpringBoot + RedisJSON + RedisSearch构建一个高性能的文档查询系统。
核心思路是:
- JSON文档存储:使用RedisJSON存储结构化数据
- 索引构建:利用RedisSearch创建高效索引
- 复合查询:支持多条件组合查询
- 性能优化:合理设计文档结构和索引策略
Redis环境准备
1. 安装Redis模块
# 需要安装RedisJSON和RediSearch模块
docker run -p 6379:6379 \
-v redis-data:/data \
redislabs/redisearch:latest
2. 基础配置
spring:
redis:
host: localhost
port: 6379
timeout: 2000ms
jedis:
pool:
max-active: 20
max-idle: 10
min-idle: 5
SpringBoot集成实现
1. 依赖配置
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>com.redis</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.0</version>
</dependency>
2. 文档实体定义
@Document(value = "users")
@Data
public class UserDoc {
@Id
private String id;
@Indexed
private String userId;
@Indexed
private String name;
@Indexed
private String email;
@Indexed
private Integer age;
@Indexed
private String department;
@Indexed
private String position;
@Indexed
private LocalDateTime createTime;
@Indexed
private LocalDateTime updateTime;
// 动态字段,支持灵活扩展
@Indexed
private Map<String, Object> extraInfo;
}
3. Repository接口
@Repository
public interface UserDocRepository extends DocumentRepository<UserDoc, String> {
// 基础查询
List<UserDoc> findByNameContaining(String name);
// 复合查询
List<UserDoc> findByDepartmentAndAgeGreaterThan(String department, Integer minAge);
// 范围查询
List<UserDoc> findByCreateTimeBetween(LocalDateTime start, LocalDateTime end);
// 排序查询
@Query(sortBy = @SortBy(field = "createTime", direction = Sort.Direction.DESC))
Page<UserDoc> findByDepartment(String department, Pageable pageable);
// 聚合查询
@Aggregation(pipeline = {
@Aggregation.Group(value = "department",
fields = {@Aggregation.Field(name = "count", expression = "COUNT(*)")})
})
List<Map<String, Object>> countByDepartment();
}
高级查询功能
1. 全文搜索
@Service
public class FullTextSearchService {
@Autowired
private RedisModulesCommands commands;
public List<UserDoc> searchUsers(String query) {
// 使用RediSearch进行全文搜索
SearchQuery searchQuery = new SearchQuery(query)
.limit(0, 100)
.sortBy("createTime", Order.DESC);
return commands.ftSearch("idx:users", searchQuery)
.getDocuments()
.stream()
.map(this::convertToUserDoc)
.collect(Collectors.toList());
}
public List<UserDoc> advancedSearch(UserSearchCriteria criteria) {
StringBuilder queryBuilder = new StringBuilder("*");
if (StringUtils.hasText(criteria.getName())) {
queryBuilder.append(" @name:")
.append(criteria.getName());
}
if (StringUtils.hasText(criteria.getEmail())) {
queryBuilder.append(" @email:")
.append(criteria.getEmail());
}
if (criteria.getMinAge() != null) {
queryBuilder.append(" @age:[")
.append(criteria.getMinAge())
.append(" ")
.append(criteria.getMaxAge() != null ? criteria.getMaxAge() : "+inf")
.append("]");
}
SearchQuery searchQuery = new SearchQuery(queryBuilder.toString())
.limit(0, criteria.getPageSize())
.sortBy(criteria.getSortField(),
"desc".equalsIgnoreCase(criteria.getSortOrder()) ? Order.DESC : Order.ASC);
return commands.ftSearch("idx:users", searchQuery)
.getDocuments()
.stream()
.map(this::convertToUserDoc)
.collect(Collectors.toList());
}
}
2. 地理位置查询
@Data
public class LocationDoc {
@Id
private String id;
@Indexed
private String name;
@Indexed
private Point location; // Redis支持的地理坐标
@Indexed
private String city;
}
@Service
public class LocationSearchService {
public List<LocationDoc> searchNearby(Point center, double radiusInKm) {
// 搜索指定范围内的地点
SearchQuery searchQuery = new SearchQuery("*")
.geFilter("location", center.getX(), center.getY(), radiusInKm, GeoUnit.KILOMETERS)
.limit(0, 50);
return commands.ftSearch("idx:locations", searchQuery)
.getDocuments()
.stream()
.map(this::convertToLocationDoc)
.collect(Collectors.toList());
}
}
性能优化策略
1. 索引设计
@Component
public class IndexCreationService {
@PostConstruct
public void createIndexes() {
// 创建复合索引
Schema schema = new Schema()
.addField(new TextField("name", 1.0))
.addField(new TextField("email", 1.0))
.addField(new NumericField("age"))
.addField(new TextField("department", 1.0))
.addField(new NumericField("createTime"));
commands.ftCreate("idx:users", FTCreateParams.createParams().on(IndexDataType.HASH), schema);
}
}
2. 批量操作
@Service
public class BatchOperationService {
public void batchUpsertUsers(List<UserDoc> users) {
// 批量插入/更新
users.forEach(user -> {
String key = "user:" + user.getUserId();
commands.jsonSet(key, Path.ROOT_PATH, user);
});
}
public List<UserDoc> batchGetUsers(List<String> userIds) {
// 批量获取
List<String> keys = userIds.stream()
.map(id -> "user:" + id)
.collect(Collectors.toList());
return commands.jsonMGet(keys, Path.ROOT_PATH)
.stream()
.map(json -> JSON.parseObject((String) json[0], UserDoc.class))
.collect(Collectors.toList());
}
}
数据同步策略
1. 实时同步
@Service
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public class DataSyncService {
public void handleUserUpdated(UserUpdatedEvent event) {
User user = event.getUser();
// 同步到Redis
UserDoc userDoc = convertToDoc(user);
String key = "user:" + user.getId();
commands.jsonSet(key, Path.ROOT_PATH, userDoc);
}
public void handleUserDeleted(UserDeletedEvent event) {
String key = "user:" + event.getUserId();
commands.del(key);
}
}
2. 增量同步
@Component
public class IncrementalSyncService {
@Scheduled(fixedRate = 300000) // 每5分钟执行一次
public void syncRecentChanges() {
LocalDateTime syncTime = getLastSyncTime();
LocalDateTime currentTime = LocalDateTime.now();
// 获取最近变更的数据
List<User> recentUsers = userService.findUpdatedAfter(syncTime, currentTime);
// 批量同步到Redis
batchUpsertUsers(recentUsers.stream()
.map(this::convertToDoc)
.collect(Collectors.toList()));
updateLastSyncTime(currentTime);
}
}
查询优化技巧
1. 分页查询优化
@Service
public class OptimizedQueryService {
public Page<UserDoc> paginatedSearch(UserSearchCriteria criteria, Pageable pageable) {
// 构建查询条件
String query = buildQuery(criteria);
SearchQuery searchQuery = new SearchQuery(query)
.limit((int) pageable.getOffset(), pageable.getPageSize())
.sortBy(pageable.getSort().get().findFirst().get().getProperty(),
pageable.getSort().get().findFirst().get().isDescending() ? Order.DESC : Order.ASC);
// 执行查询
SearchResults<Document> results = commands.ftSearch("idx:users", searchQuery);
List<UserDoc> content = results.getDocuments()
.stream()
.map(this::convertToUserDoc)
.collect(Collectors.toList());
// 获取总数(这里可以考虑缓存总数以提高性能)
long total = getTotalCount(query);
return new PageImpl<>(content, pageable, total);
}
private long getTotalCount(String query) {
SearchQuery countQuery = new SearchQuery(query).limit(0, 0);
SearchResults<Document> results = commands.ftSearch("idx:users", countQuery);
return results.getTotalResults();
}
}
2. 缓存策略
@Service
public class CachedSearchService {
@Cacheable(value = "userSearch", key = "#criteria.hashCode()")
public List<UserDoc> searchWithCache(UserSearchCriteria criteria) {
return advancedSearch(criteria);
}
@CacheEvict(value = "userSearch", allEntries = true)
@EventListener
public void handleUserChanged(AbstractUserEvent event) {
// 用户数据变更时清除相关缓存
}
}
实际应用场景
1. 用户信息查询
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserDocRepository userRepository;
@GetMapping("/search")
public Page<UserDoc> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) String department,
@RequestParam(required = false) Integer minAge,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size);
if (StringUtils.hasText(name)) {
return userRepository.findByNameContaining(name, pageable);
} else if (StringUtils.hasText(department)) {
return userRepository.findByDepartment(department, pageable);
} else {
return userRepository.findAll(pageable);
}
}
}
2. 配置信息管理
@Document(value = "configs")
@Data
public class ConfigDoc {
@Id
private String id;
@Indexed
private String configKey;
@Indexed
private String configType;
@Indexed
private String tenantId;
@Indexed
private Boolean enabled = true;
@Indexed
private LocalDateTime expireTime;
private Object configValue; // 支持任意类型配置值
}
注意事项
在使用RedisJSON + RedisSearch时,需要注意以下几点:
- 数据持久化:合理配置Redis持久化策略,避免数据丢失
- 内存管理:监控内存使用情况,设置合理的过期时间
- 索引维护:定期清理不必要的索引,避免性能下降
- 数据一致性:确保Redis与主数据库的数据同步
- 安全配置:生产环境要配置密码认证和网络隔离
总结
通过SpringBoot + RedisJSON + RedisSearch的组合,我们可以构建一个高性能的文档查询系统。这种方案特别适合半结构化数据的快速查询场景,能够显著提升查询性能,减轻关系型数据库的压力。
当然,Redis并不是银弹,需要根据具体业务场景选择合适的存储方案。在某些对事务一致性要求极高的场景,仍然需要依赖传统的关系型数据库。
希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。
原文首发于 www.jiangyi.space
转载请注明出处
标题:基于SpringBoot + RedisJSON + RedisSearch:用 Redis 替代部分 MySQL,实现高性能文档查询
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/21/1768974100295.html
0 评论