疯狂餐厅
86.88M · 2026-03-21
在实现 多线程网络爬虫与 Elasticsearch 新闻搜索引擎 项目时,数据存储与搜索性能是系统设计的核心。本节将围绕以下几个关键技术展开:
通过这些技术的组合,可以构建一个 既能高效存储结构化数据,又能快速完成全文检索的新闻搜索系统。
在 MyBatis 中,SQL 语句通常写在 Mapper.xml 文件中。使用时需要注意以下几个规则:
在 MyBatis 的 XML SQL 中,如果是字符串常量,需要使用 单引号。
例如:
where tableName == 'links_already_processed'
MyBatis 在解析参数时遵循 JavaBean 约定:
例如:
#{link}
parameterType 是对象 → 调用 getLink()HashMap → 读取 map.get("link")<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.github.hcsp.MyMapper">
<select id="selectNextAvailableLink" resultType="String">
select link
from LINKS_TO_BE_PROCESSED
LIMIT 1
</select>
<delete id="deleteLink" parameterType="String">
DELETE
FROM LINKS_TO_BE_PROCESSED
where link = #{link}
</delete>
<insert id="insertNews" parameterType="com.github.hcsp.News">
insert into news (url, title, content, CREATED_AT, MODIFIED_AT)
values (#{url}, #{title}, #{content}, now(), now())
</insert>
<select id="countLink" resultType="int">
select count(link)
from LINKS_TO_BE_PROCESSED
where link = #{link}
</select>
<insert id="insertLink" parameterType="HashMap">
insert into
<choose>
<when test="tableName == 'links_already_processed'">
links_already_processed
</when>
<otherwise>
links_to_be_processed
</otherwise>
</choose>
(link)
values #{link}
</insert>
</mapper>
这里使用了 MyBatis 的 动态 SQL 标签 <choose> 来根据参数决定插入哪张表。
为了让系统支持 不同的数据访问实现(JDBC 或 MyBatis) ,可以抽象出统一接口。
public interface CrawlerDao {
String getNextLinkThenDelete() throws SQLException;
void insertNewsIntoDatabase(String url, String title, String content) throws SQLException;
boolean isLinkProcessed(String link) throws SQLException;
void insertProcessedLink(String link) throws SQLException;
void insertLinkToBeProcessed(String href) throws SQLException;
}
然后实现两个版本:
JdbcCrawlerDaoMyBatisCrawlerDao这种方式遵循 面向接口编程原则,实现了:
为了方便开发环境管理,可以使用 Docker 运行 MySQL。
docker run --name mysql
-e MYSQL_ROOT_PASSWORD=root
-p 3306:3306
-d mysql:5.7.27
删除容器:
docker rm -f mysql
如果不做持久化,删除容器后数据会丢失。
创建数据目录:
mkdir mysql-data
运行容器:
docker run --name mysql
-e MYSQL_ROOT_PASSWORD=root
-p3306:3306
-v "`pwd`/mysql-data":/var/lib/mysql
-d mysql:5.7.27
参数说明:
| 参数 | 含义 |
|---|---|
| -d | daemon 模式后台运行 |
| -v | 挂载本地目录 |
| -p | 端口映射 |
Flyway 用于 数据库版本管理与自动建表。
运行迁移:
mvn flyway:migrate
如果失败可以清理:
mvn flyway:clean
mvn flyway:migrate
如果 Flyway 执行失败,可能是缺少 MySQL JDBC 驱动。
Maven 依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
MyBatis 配置:
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/news"/>
建议添加编码参数:
jdbc:mysql://localhost:3306/news?characterEncoding=UTF-8
推荐使用 utf8mb4,可以完整支持 Unicode。
创建数据库:
create database news;
修改字符集:
ALTER DATABASE news
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
批量插入数据时可以使用 MyBatis 的 批处理模式:
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
}
定期提交避免内存占用过大:
if (count % 2000 == 0) {
session.flushStatements();
}
这样可以显著提升批量写入性能。
MySQL 的索引底层结构通常是 B+树。
相比 平衡二叉树:
| 数据结构 | 特点 |
|---|---|
| 平衡二叉树 | 树高较高 |
| B树 | 多叉树 |
| B+树 | 所有数据在叶子节点 |
B+树优势:
例如:
SELECT * FROM NEWS
WHERE created_at BETWEEN '2021-01-01' AND '2021-02-01'
id
例如:
id
url
因为比较成本更高。
例如:
CREATE INDEX created_at_modified_at_index
ON NEWS (created_at, modified_at);
遵循 最左匹配原则:
(a,b,c)
可以使用
a
(a,b)
(a,b,c)
例如:
可以使用索引:
WHERE created_at = '2019-01-01'
AND modified_at < '2019-02-01'
无法完全使用索引:
WHERE created_at > '2019-01-01'
AND modified_at = '2019-02-01'
可以使用:
EXPLAIN SELECT * FROM NEWS
WHERE created_at = '2021-08-29';
重点关注字段:
| 字段 | 含义 |
|---|---|
| table | 查询表 |
| type | 查询类型 |
| key | 使用的索引 |
最差情况:
type = ALL
表示 全表扫描。
MySQL 适合:
但对于 文本搜索:
title like '%外交部%'
效率较低。
因此需要引入专业搜索引擎。
Elasticsearch 使用 倒排索引(Inverted Index) 。
与传统 B 树不同:
传统结构:
文档 → 单词
倒排索引:
单词 → 文档
例如:
外交部 → 文档1 文档3 文档5
这使得 全文搜索效率极高。
docker run -d
--name elasticsearch
-p 9200:9200
-p 9300:9300
-e "discovery.type=single-node"
elasticsearch:7.4.0
持久化数据:
mkdir esdata
docker run -d
-v "`pwd`/esdata":/usr/share/elasticsearch/data
--name elasticsearch
-p 9200:9200
-p 9300:9300
-e "discovery.type=single-node"
elasticsearch:7.4.0
访问:
http://localhost:9200
Java 示例:
for (News news : newsFromMySQL) {
IndexRequest request = new IndexRequest("news");
Map<String, Object> data = new HashMap<>();
data.put("content",
news.getContent().length() > 10
? news.getContent().substring(0,10)
: news.getContent());
data.put("url", news.getUrl());
data.put("title", news.getTitle());
data.put("createdAt", news.getCreatedAt());
data.put("modifiedAt", news.getModifiedAt());
request.source(data, XContentType.JSON);
bulkRequest.add(request);
}
使用 Bulk API 批量写入可以大幅提升性能。
全部查询:
关键字查询:
:外交部
统计文档数量:
访问:
Elasticsearch 采用 分布式架构,主要原因:
节点故障时数据不会丢失。
可以通过增加机器提升性能。
在分布式系统领域有一句经典的话:
能通过增加机器解决的问题,通常都不是问题。
本节完成了新闻爬虫系统 数据层与搜索层的关键架构设计:
最终形成了一个典型的 搜索系统架构:
爬虫
↓
MySQL(结构化存储)
↓
数据同步
↓
Elasticsearch(全文检索)
↓
搜索接口
这种架构也是许多大型互联网系统(新闻、博客、商品搜索等)常用的设计模式。