前不久我在搭建舆情分析监测系统的原型时,最先接触的就是一堆号称免费的公共数据源。最开始我以为把这些 API 地址填进配置,就能像连本地数据库一样直接读写。结果第一周大部分时间都花在调多数据源:新闻源连接不稳定、评论源偶尔超时、统计源返回格式说变就变。真正让我意识到问题的,不是某个免费数据源本身能不能用,而是多个数据源同时存在之后,项目突然从“写一个接口”变成了“管理一组数据源”。这也就是为什么“多数据源”“Spring Boot + MyBatis Plus 多数据源”“将 Sharding 数据源注册到动态数据源中”这些词,会反复出现在这类项目的讨论里。
我的判断很明确:免费数据源项目的难点从来不在“免费”,也不在“数据源有多少”,而在当你需要聚合多个数据源时,能否在代码层把数据源切换、事务边界、故障隔离和长期维护都控住。数据源是免费的,但管理数据源的成本一点都不免费。这篇博客,我会从选型、代码演示、动态注册 Sharding 数据源、排查链路和工程化维护几个层面,把这件事拆开讲清楚。
1. 免费数据源不是“不要钱”,而是“有条件地免费”
1.1 免费数据源主要有哪几类
先把“免费数据源”这个概念收窄一点。它不是指盗版数据库或未经授权的接口,而是合法公开、允许被获取和使用的数据来源。常见类型有这几类:
- 开放平台 API:很多公共服务平台会提供开发接口,比如天气、地理编码、新闻资讯、指数行情等。这类接口通常会提供免费配额,用来做学习和轻量级验证是够的。
- RSS 订阅源:新闻站点、博客平台、部分媒体会提供 RSS 地址,适合做舆情监测和内容聚合。它的结构相对简单,更新频率取决于源站。
- 静态数据文件:很多政府数据开放平台、研究机构、企业开放数据集会提供 CSV、JSON 等格式的静态文件。这类数据质量通常较高,但更新不一定规律。
- 公共数据库镜像:部分社区或学术机构会提供数据库的只读镜像,适合做分析型实验,但不适合直接作为在线业务的实时数据源。
- 测试沙盒数据:一些云厂商或中间件产品会提供演示数据源,主要用于验证功能流程,不承担真实业务。
这些数据源看起来都“免费”。真正的问题在于,免费通常伴随一条隐性账本:接口频率限制、带宽配额、返回字段可能变更、更新不可预期、稳定性没有保障。所以选型时不能只看“能不能连上”,还要看“能不能长期稳定地连上”。
1.2 选择时不能只看免费,还要看四个指标
我建议把免费数据源当成“外部依赖”来看,而不是当成“免费接口”来看。外部依赖就意味着要做可用性评估。具体看四个指标:
| 指标 | 判断内容 | 实际影响 |
|---|---|---|
| 可用性 | 服务是否长期在线,响应是否稳定 | 上游挂了,下游再优雅也会出错 |
| 数据质量 | 字段是否规范,是否有明显空值、乱码、重复 | 清洗成本会超过接入成本 |
| 更新频率 | 数据多久更新一次,是否有固定节奏 | 决定你的缓存策略和任务调度方式 |
| 授权边界 | 是否允许存储、二次加工和对外展示 | 不看清授权,功能上线容易违规 |
其中“授权边界”最容易被忽略。很多人以为接口不收费,数据就可以拿来随意用。实际上不少免费接口会写明不允许存储、不允许向第三方提供原始数据、不能用于高并发生产环境。舆情分析监测系统尤其要小心,因为它会对文本内容做聚合、分类和展示。如果原始内容授权不清晰,你的系统做得越完善,风险越高。这个问题放在最后一章专门展开。
1.3 舆情分析场景下,数据源应该分层管理
舆情分析监测系统是典型的多源聚合场景。它通常需要三类数据:
- 原始数据源:新闻 RSS、公开评论、平台指数、热点榜单等。
- 业务数据源:清洗后的文章、舆情事件、情绪评分、统计指标。
- 结果数据源:用于前台展示和报表查询的聚合结果。
这三类数据的访问频率、数据量、一致性要求都不一样。原始数据源偏写,业务数据源偏读写,结果数据源偏读。如果全部塞进同一个数据库,业务一增长就会出现连接竞争。免费数据源项目虽然规模不大,但从一开始就按“原始、业务、结果”三层做划分,后面接多数据源就会轻松很多。
很多人在这一步就会问:既然数据源这么多,我直接在配置里多写几个数据源不就行了?想法没错,但连接、切换、事务和监控都会叠加在一起。下一章讲清楚这件事。
2. 单数据源满足不了业务时,多数据源方案就成了刚需
2.1 多数据源到底切的是什么
“多数据源”不是一个抽象概念,它切的是数据访问层。在 Spring Boot 项目里,数据源本质上是javax.sql.DataSource。单数据源时,整个应用只有一个 DataSource Bean,MyBatis 在里面执行 SQL。多数据源时,应用里同时存在多个 DataSource Bean,根据业务需要,让不同 Mapper 或不同 Service 方法使用不同 DataSource。
这个操作的关键在“切换”。切换不是把 JDBC URL 动态拼接,而是在运行时决定:当前操作应该走哪一个数据源。常见做法有两种:
- 不同 Mapper 绑定不同数据源,代码最直观。
- 同一个逻辑方法里,通过注解或上下文动态选择数据源,更灵活。
对于舆情分析监测系统,我更建议先使用“不同 Mapper 绑定不同数据源”的方式。理由很简单:舆情业务天然分模块,新闻采集、评论监控、统计报表对应不同表和数据源,边界清晰,绑定到 Mapper 上可读性最好。等后期出现“同一个 Mapper 需要的操作分布于不同数据源”时,再往更灵活的切换方式上迁移。
2.2 Spring Boot + MyBatis Plus 多数据源的实现逻辑
在 Spring Boot + MyBatis Plus 项目里,最常用的多数据源方案是dynamic-datasource-spring-boot-starter。它是一个基于 Spring 抽象出来的动态数据源路由组件,核心逻辑不算复杂:
- 启动时读取配置,构建多个真实数据源。
- 通过 AOP 拦截
@DS注解。 - 根据注解值把当前线程要用的数据源记录到上下文中。
- MyBatis 执行 SQL 前,从路由组件拿到对应的真实数据源。
这样做的好处是,业务代码不需要自己写DataSourceContextHolder这类底层代码。你只需要在配置里声明数据源,再在需要切换的地方加上@DS注解即可。
有一个易混淆的点:MyBatis Plus 本身并不负责多数据源。它提供的是增强的 CRUD 能力和分页插件。多数据源能力来自配套的dynamic-datasource组件。两者常组合使用,但不该画等号。
用这个方案接入多数据源后,你会发现它带来的不只是连接管理,还有一套相对清晰的路由规则。即使以后数据源数量增长,代码结构也能保持稳定。真正的挑战是规则之外的那些细节,比如事务、版本、扫描路径。
2.3 最常见的三个认知误区
这里说三个我在实际项目里反复见到过的误区,它们都不致命,但会在上线前突然冒出来。
误区一:多数据源等于多写几段配置文件。
配置文件只是第一步。多数据源引入后,事务管理器、连接池监控、Mapper 扫描路径、分页插件都需要重新评估。如果只改application.yml,其他部分还是单数据源的思维,大概率会在运行时翻车。
误区二:主数据源不能动,其他数据源都要绕着它走。
动态数据源通常有primary概念,也就是默认数据源。但如果项目从一开始就设定好每个 Mapper 使用哪个数据源,主数据源更多是一个兜底入口,而不是所有流量的必经之路。舆情系统的原始数据源如果来自外部 API,它和主业务库甚至可以做到互不影响。
误区三:加了 @DS 就等于所有代码自动切换。
@DS只对声明它所在层级生效。如果注解加在 Service 方法上,那么方法内调用的所有数据访问都会走这个数据源;但如果方法内部新开了事务,或者直接注入了一个已经绑定 Mapper 数据源的 Bean,实际路由可能和预期不同。所以“显式指定比依赖默认值更安全”。
注意:多数据源方案最重要的不是“能切”,而是“切得可控”。不要过度依赖默认回退逻辑,刚开始就让每个关键操作显式声明数据源。
3. 代码演示:从零接入多数据源并完成动态切换
3.1 环境准备和依赖
演示用的技术栈是常见的 Spring Boot + MyBatis Plus + dynamic-datasource。先准备一个最基本的 Maven 项目,Spring Boot 版本选择 2.7 或 3.x 时,依赖版本要做相应匹配。这里不写死版本号,因为依赖仓库会不断更新,落地前请以实际可用的稳定版本为准。
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>这里有一个容易踩的坑:版本不对会造成自动配置失效,常见表现是启动时找不到数据源、@DS注解不生效、或者出现循环依赖。所以不要一上来就把所有依赖都升级到最新版。先用一个你已经验证过的 Spring Boot 版本,再让多数据源组件版本匹配它。
3.2 配置多数据源
在application.yml里配置两个数据源,分别代表舆情系统的原始数据源和业务数据源。这里用news和monitor作示例名称。
spring: datasource: dynamic: primary: news strict: false datasource: news: url: jdbc:mysql://localhost:3306/news_db username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver monitor: url: jdbc:mysql://localhost:3306/monitor_db username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver参数含义需要理解到位:
primary:默认数据源名称。没有匹配到其他数据源时,请求会走这个数据源。strict:是否严格校验数据源。设为false时,找不到数据源会回退到主数据源;设为true时,找不到会直接报错。datasource:真实数据源列表,每个数据源的名称就是后续@DS里用的值。
我建议最开始把strict设为false,先把流程跑通,观察日志里是否出现了“未匹配到数据源”的提示。等确认无误后,再改成true,让错误尽早暴露,避免生产环境悄悄走了错误的数据源而没人发现。
3.3 用 @DS 实现数据源切换
先分别建两个 Mapper,用来操作不同数据库。
@DS("news") public interface NewsArticleMapper extends BaseMapper<NewsArticle> { } @DS("monitor") public interface MonitorEventMapper extends BaseMapper<MonitorEvent> { }这样,NewsArticleMapper的查询会走news数据源,MonitorEventMapper会走monitor数据源。这是最简单也最直观的多数据源绑定方式。
如果某个 Service 方法需要临时切换数据源,就把@DS加在方法上。
@Service public class DataAccessService { @Autowired private NewsArticleMapper newsArticleMapper; @Autowired private MonitorEventMapper monitorEventMapper; @DS("news") public List<NewsArticle> queryNews(Integer limit) { return newsArticleMapper.selectList( new LambdaQueryWrapper<NewsArticle>().last("limit " + limit) ); } @DS("monitor") public List<MonitorEvent> queryMonitorEvents(Integer limit) { return monitorEventMapper.selectList( new LambdaQueryWrapper<MonitorEvent>().last("limit " + limit) ); } }这里的@DS方法级注解优先级高于 Mapper 上的注解。如果在类和方法上都写了@DS,方法上的值优先生效。
3.4 一个舆情聚合查询的最小示例
现在把两个数据源的结果聚合在一起,做一个最简单的舆情概览查询。
@Service public class OpinionAggregationService { @Autowired private DataAccessService dataAccessService; public Map<String, Object> overview(Integer limit) { Map<String, Object> result = new HashMap<>(); result.put("news", dataAccessService.queryNews(limit)); result.put("events", dataAccessService.queryMonitorEvents(limit)); return result; } }这个示例足够小,但已经能说明多数据源的价值:一个接口可以同时读取两个库,再把结果组装返回。实际舆情分析系统里,queryNews可能来自新闻 RSS 的清洗结果,queryMonitorEvents可能来自评论监控的统计结果,两者存储位置不同,但对外表现为一个完整服务。
需要注意,示例只是为了跑通流程,不推荐直接在生产环境做这种同步聚合。当数据量变大或上游接口变慢时,同步查询会拖垮接口响应。更合理的做法是让采集任务把数据定时同步到统一的业务库,再在查询层只访问业务库和结果库。多数据源解决的是“数据在不同地方”的问题,不等于“所有地方的数据都在应用层实时合并”。
4. 进阶:把 Sharding 数据源注册到动态数据源
4.1 为什么需要 Sharding 数据源
舆情分析监测系统运行一段时间后,新闻表、评论表、事件流水表都可能快速增长。当单表数据量过亿时,分表分库是迟早要面对的问题。ShardingSphere 这类中间件可以把逻辑表映射到多个物理表或物理库,应用层看到的是一个数据源,底层已经做了分片。
问题来了:一个系统里往往既有普通业务库,又有做了分片的数据源。多数据源方案需要把所有数据源都管理在一个路由规则里,否则就会出现“普通数据源走动态路由,分片数据源走独立配置”这种割裂局面。最直接的办法,就是把 Sharding 数据源也注册到动态数据源中。
4.2 注册思路:不要把 Sharding 放在业务层外面
先说一个容易犯的方向性错误:不要为了用 Sharding 数据源,就在业务代码里单独写一套数据访问逻辑。那样不但绕开了多数据源的统一路由,还会让事务、分页、监控都变成两套体系。正确思路是,Sharding 数据源本质上也是一个 DataSource,只是内部结构更复杂,完全可以作为动态数据源的一个节点存在。
注册的核心思路分三步:
- 创建 Sharding 数据源,通常需要配置分片规则和数据源列表。
- 构建动态数据源实例,把普通数据源和 Sharding 数据源都放进同一个 Map。
- 在业务代码里通过
@DS("shardingDataSource")使用 Sharding 数据源。
用伪代码表达大概是这样的结构:
@Configuration public class DynamicDatasourceConfig { // 假设这个方法已经构建了 ShardingSphere 的 DataSource @Bean public DataSource shardingDataSource() { // 返回一个 ShardingDataSource,内部包括分片规则 return buildShardingDataSource(); } @Primary @Bean public DataSource dynamicDataSource() { DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource(); Map<String, DataSource> dataSourceMap = new HashMap<>(); dataSourceMap.put("news", newsDataSource()); dataSourceMap.put("monitor", monitorDataSource()); dataSourceMap.put("sharding", shardingDataSource()); dynamicDataSource.setPrimary("news"); dynamicDataSource.setDataSourceMap(dataSourceMap); return dynamicDataSource; } }不同版本的 API 名称可能不一样,这里只是表达结构。实际落地时,要以你使用的dynamic-datasource版本文档为准确认setDataSourceMap等方法的写法。关键不是记代码,而是理解“把所有数据源放进同一个路由 Map”这个思路。
4.3 注册完成后需要重新审视的三个问题
把 Sharding 数据源注册进动态数据源后,看起来只是一个数据源节点,实际上会给项目带入三层复杂度。
第一层:事务边界。
Sharding 数据源内部可能涉及多个分片库。当它作为动态数据源的一部分时,如果业务方法加了@Transactional,事务是否能覆盖所有分片,取决于 Sharding 本身的事务策略,而非 Spring 的本地事务。最稳妥的做法是,分片操作和非分片操作不要在同一个事务里强行合并。
第二层:分页和排序。
分片后,LIMIT和ORDER BY可能要在分片之上再做一次聚合。MyBatis Plus 的分页插件如果不知道 Sharding 数据源的存在,可能把分页逻辑落错位置。所以要确认分页插件是否能够识别 Sharding 数据源,或者为 Sharding 数据源单独设置分页处理器。
第三层:监控和告警。
普通数据源挂了,动态路由可以迅速切换或失败回滚。Sharding 数据源挂了,如果监控只看动态路由层的状态,不会发现底层分片库已经有一个节点异常。舆情系统对连续数据的要求比较高,建议把底层分片库的状态也纳入监控范围,而不是只关注顶层动态数据源。
5. 多数据源报错时,按这条链路排查最快
5.1 先分清报错是哪一层
多数据源项目里报错,表象往往相同,但根因差异很大。先用一个表格区分报错层级:
| 报错现象 | 可能层级 | 常见原因 |
|---|---|---|
| 启动失败,Bean 创建异常 | 配置层 | 数据源配置缺失、依赖冲突 |
| 查询时报无法获取连接 | 连接池/网络层 | 数据库地址不通、用户名密码错误、连接池耗尽 |
| 提示未找到数据源 | 路由层 | @DS注解名称与配置名称不一致,或 strict 校验没通过 |
| SQL 执行成功但数据不对 | SQL/数据层 | 路由到了错误数据源,或分片规则与实际表结构不匹配 |
| 事务回滚不生效 | 事务层 | 跨数据源事务没有使用分布式事务方案 |
拿到一个报错,先问自己:这是“连不上”、“找不到”、“切错了”还是“一致性坏了”。方向错了,后面都是白排查。
5.2 逐层排查的路线
在多数据源场景下,我习惯按下面这个顺序排查:
- 先看配置:检查
application.yml里数据源名称是否与@DS注解值完全一致,包括大小写。 - 再看依赖:确认 dynamic-datasource、MyBatis Plus、Spring Boot 三个版本之间的兼容性。特别是 Spring Boot 3 和 2 之间的差异。
- 然后看权限:免费数据源对应的库账号是否有远程访问权限,是否能执行当前 SQL。
- 然后看日志:启动日志是否打印了路由信息,运行日志里是否有“数据源未匹配”的提示。
- 接着看 SQL:把 SQL 拿到具体数据库执行一遍,确认不是 SQL 本身的问题。
- 最后看资源:连接池大小、慢查询、锁等待、磁盘空间,这些都会伪装成数据源故障。
为什么是这个顺序?因为多数据源的故障链通常是从配置到运行层层传递的。配置错,后面全是白跑;依赖错,运行期行为不确定;权限错,连接建立失败;日志错,问题被掩盖;SQL 错,路由再对也没用;资源错,系统会随机性失败。按这个链路走,可以避开大多数无意义的重启。
5.3 一个通用的验证清单
排查之后,更重要的是建立一套可复用的验证清单。每次新接入一个免费数据源,或者新增一个数据源节点,都跑一遍:
- [ ] 先只用该数据源跑一个最简单的 select 语句,确认它能独立运行。
- [ ] 在调用入口增加
@DS注解,确认路由到目标数据源。 - [ ] 查看启动日志,确认数据源初始化成功。
- [ ] 临时将
strict设为true,确认数据源丢失能被立即发现。 - [ ] 检查涉及该方法或 Mapper 的事务,确认没有在事务内做跨源切换。
- [ ] 查看连接池监控,确认连接数没有异常增长。
建议:交接给其他同事时,把这个清单留在项目 README 里。多数据源项目最怕的不是不会写代码,而是接手的人不知道哪些数据源是必须存在的。
6. 免费数据源项目的长期价值,不在代码里
6.1 合规和频率限制是第一道门槛
技术博客写到这里,通常都会收尾了。但免费数据源这个主题,如果不提合规,是对读者的不负责任。很多人以为“免费”就等于“没有限制”,实际上免费数据源恰恰更依赖服务条款来保护提供方。
舆情分析监测系统尤其容易踩线:它要存储文本、做分析、展示结果。如果原始数据源明确禁止存储,或者只允许个人学习使用,那么你的系统就只能做在线转发,不能做持久化分析。如果原始数据涉及个人信息,还要考虑脱敏、访问控制、删除策略。
在实际项目中,我会把数据源的授权边界写成一张表,放在项目 wiki 里。每个数据源都记录:来源、更新时间、是否允许存储、是否允许对外展示、频率限制是多少。这个动作看起来和代码无关,但它决定了系统能走多远。代码写得再漂亮,数据源不合规,产品也上不了线。
6.2 缓存、降级和故障隔离
免费数据源有天然的不稳定性。频率限制、临时维护、接口升级,都是常态。作为使用者,不能把整套业务逻辑建立在“上游永远稳定”的假设上。我建议至少做三层防护:
- 缓存层:上游数据更新不快时,用本地缓存或 Redis 缓存结果,降低对上游接口的请求次数。
- 降级层:上游不可用时,返回最近一次成功缓存的结果,并打上“数据时间”标记,而不是直接报错。
- 故障隔离层:一个数据源抖动,不能把整个舆情分析流程拖死。用独立的线程池或任务队列把采集逻辑和查询逻辑隔离,避免上游慢接口占用所有连接。
对免费数据源项目来说,这三层不是过度设计,而是基本生存能力。很多免费接口的平均响应时间可能很健康,但 P99 可能会慢到离谱。如果不做超时和隔离,一次上游抖动就能拖垮核心接口。
6.3 这类方案真正适合谁
最后说适用范围。免费数据源 + 多数据源 + Spring Boot + MyBatis Plus,是一套非常典型的“轻量级多源聚合开发组合”。它适合这几类场景:
- 学习如何做数据接入、多库路由、动态切换。
- 做舆情分析系统的原型,验证业务流程和分析算法。
- 企业内部工具,对数据实时性要求不高,可以接受缓存和延迟。
- 非核心业务模块,即使上游数据源抖动,也不会影响主流程。
它不适合的场景也很明确:对数据完整性要求极高的生产系统、金融交易级别的数据链路、需要承担法律责任的数据服务、大规模高并发的实时分析平台。在这些场景里,商业数据服务、自建采集管道、专业数据库和分布式事务方案才是更可靠的选择。
这不是说免费方案不行,而是说免费数据源的价值在于“用低成本验证不确定性”。当你还处在业务验证阶段时,用免费数据源快速把流程跑通,是很聪明的策略。但当系统要进入长期运营时,数据源的稳定性、合规性、可运维性,比“免费”这两个字重要得多。
如果让我给一句话建议,我会说:先用免费数据源把整个链路打通,但一定要在第一天就认识到,真正需要长期建设的不是数据源本身,而是管理数据源的能力。多数据源的路由、注册、排查和降级,才是这个项目沉淀下来最值钱的部分。