系统设计核心组件选型

一个选错数据库的教训

2023年,我们团队为新项目选型数据库。

开发同学说:"MongoDB 灵活,直接用吧!"

三个月后,系统上线。问题来了:

  • 需要强事务的订单场景,MongoDB 不支持
  • 需要复杂 JOIN 的报表场景,MongoDB 不支持
  • 需要精确全文搜索的场景,MongoDB 全文索引不够用

最后,系统里出现了 MySQL + MongoDB + Elasticsearch 三种数据库,复杂度爆炸。

系统设计的核心能力之一是:正确选择每个组件。


二、数据库选型🔴

2.1 关系型 vs NoSQL

维度MySQLPostgreSQLMongoDBRedis
事务支持✅ ACID✅ ACID⚠️ 单文档⚠️ Lua 脚本
JOIN 查询
扩展性
写入性能极高
查询灵活性极高
适用场景业务数据复杂查询文档存储缓存/会话

2.2 选型决策

业务系统(订单、用户、支付):
  → MySQL / PostgreSQL

文档型数据(商品属性、用户画像):
  → MongoDB

搜索场景(全文检索、模糊查询):
  → Elasticsearch

配置、会话、缓存、排行榜:
  → Redis

2.3 分库分表策略

// 分片键选择
class ShardingStrategy {
    // 按用户 ID 分片(适合用户维度查询)
    public Long getDatabaseKey(Long userId) {
        return userId % 4; // 4 个库
    }

    // 按时间分片(适合时间维度查询)
    public String getDatabaseKey(LocalDate date) {
        return date.getYear() + "_" + (date.getMonthValue() / 3 + 1);
    }
}

三、缓存选型🔴

3.1 缓存对比

维度RedisMemcached本地缓存 (Caffeine)
数据结构String/Hash/List/Set/ZSet仅 String
持久化✅ RDB/AOF
集群Redis Cluster / Sentinel
性能极高最高
适用缓存 + 会话 + 队列纯缓存热点数据

3.2 缓存模式

// Cache-Aside(旁路缓存)
class CacheAsideService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private UserDao userDao;

    public User getUser(Long id) {
        String key = "user:" + id;

        // 1. 先查缓存
        String cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return JSON.parseObject(cached, User.class);
        }

        // 2. 缓存未命中,查数据库
        User user = userDao.findById(id);

        // 3. 写入缓存
        redisTemplate.opsForValue().set(
            key,
            JSON.toJSONString(user),
            Duration.ofHours(1)
        );

        return user;
    }
}

四、消息队列选型🔴

4.1 队列对比

维度KafkaRocketMQRabbitMQ
吞吐量百万级十万级万级
延迟毫秒级微秒级
消息可靠性At-least-onceExactly-onceAt-least-once
事务消息
延迟消息
适用场景日志、大数据交易、订单小消息

4.2 选型决策

日志收集 / 数据管道:
  → Kafka(高吞吐)

订单消息 / 交易系统:
  → RocketMQ(事务消息)

小消息 / 延迟队列:
  → RabbitMQ / RocketMQ

五、搜索引擎选型🟡

5.1 搜索对比

维度ElasticsearchMySQL Full-Text内存搜索
全文搜索✅ 强大⚠️ 弱
模糊匹配
相关性调优
实时性秒级秒级毫秒级
数据量TB 级GB 级MB 级

5.2 Elasticsearch 使用场景

@Service
class SearchService {
    @Autowired
    private ElasticsearchRestTemplate template;

    /**
     * 全文搜索商品
     */
    public List<Product> search(String keyword, int page, int size) {
        NativeQuery query = NativeQuery.builder()
            .withQuery(q -> q
                .multiMatch(m -> m
                    .query(keyword)
                    .fields("name^2", "description", "category")
                )
            )
            .withPageable(PageRequest.of(page, size))
            .build();

        SearchHits<Product> hits = template.search(query, Product.class);

        return hits.getSearchHits().stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
    }
}

六、存储选型总览🟡

6.1 决策框架

数据特征 → 存储选型:

结构化数据 + 强事务 → MySQL / PostgreSQL
文档型数据 + 灵活字段 → MongoDB
全文检索 + 模糊搜索 → Elasticsearch
配置/会话/缓存 → Redis
文件/大对象 → OSS / S3
时序数据 → InfluxDB / TimescaleDB
图数据 → Neo4j

6.2 成本估算

存储类型单价(参考)说明
MySQL 云数据库¥0.5/GB/月含备份
Redis 云缓存¥1.5/GB/月高可用
Elasticsearch¥2.0/GB/月含副本
OSS 对象存储¥0.12/GB/月低频存储更便宜

【架构权衡】 组件选型的核心原则是合适就好。不要为了一致性牺牲性能,也不要为了性能牺牲可靠性。每个组件都有其适用场景。


七、面试总结

级别期望回答
P5能说出各组件的基本特点
P6能根据场景选择合适的组件
P7能权衡多个组件的利弊