本地用来练搜索链路的一个小项目
Go 实现,前缀补全用内存 Trie,全文检索优先 Elasticsearch 7,不配 ES 就退回内存匹配。要真实数据时,本机 MySQL 通过 mysql CLI 拉到 ES,顺带更新 Bloom 和兜底列表。标准库为主,没接 MySQL driver,ES 走 HTTP,本地能跑通。
cd video-search-go
go run .默认监听 :8080,浏览器打开 http://127.0.0.1:8080/demo 可以试搜和播 video_url。
| 组件 | 作用 | 不配时 |
|---|---|---|
| Elasticsearch 7.x | /search 走 DSL、高亮、综合分 |
用内存 simpleSearch |
| Redis | 搜索结果 L2 缓存 | 只有内存 L1 |
| MySQL | 把表里的视频同步进 ES,并刷新 Bloom / fallback | 不设 MYSQL_USER 时用内置 mock 数据 |
ES 默认 http://127.0.0.1:9200。Redis 设 REDIS_ADDR=127.0.0.1:6379 即可。
MySQL 同步要求本机 PATH 里能直接执行 mysql(和代码里 exec 一致),不会 go get driver。
仓库里有 docker-compose.yml,能拉镜像时:
docker compose up -dDocker Hub 拉不下来时,只能本机装 ES/Redis,或别的机器 docker save 再 docker load。
多半是 6379 没进程。临时不用 Redis 就别设 REDIS_ADDR,服务照样可用,只是没有 L2。
| 变量 | 默认 | 说明 |
|---|---|---|
MOCK_VIDEO_COUNT |
5000 |
未开 MySQL 时 mock 条数,调大注意内存 |
MYSQL_HOST / MYSQL_PORT |
127.0.0.1 / 3306 |
开同步时生效 |
MYSQL_USER |
空 | 非空则启用 MySQL 同步 |
MYSQL_PASSWORD |
空 | |
MYSQL_DB / MYSQL_TABLE |
video_search / videos |
|
MYSQL_INIT_LIMIT |
10000 |
初次同步最多拉多少条(启动会等这轮做完) |
MYSQL_BATCH_SIZE |
1000 |
增量每批 |
MYSQL_SYNC_INTERVAL_SEC |
30 |
后台轮询间隔(秒) |
MYSQL_CURSOR_FILE |
data/mysql_cursor.json |
当前游标(单文件覆盖),给重启续传用;相对路径相对进程工作目录 |
MYSQL_CURSOR_HISTORY_FILE |
data/mysql_cursor_history.jsonl |
游标历史(JSON Lines,只追加)。设成空、none、- 则关闭 |
MYSQL_SEARCH_SYNC_MAX_WAIT_SEC |
5 |
/search 里最多等几秒把 MySQL 增量追平 |
ES_URL / ES_INDEX |
http://127.0.0.1:9200 / videos |
|
ES_MIN_SCORE |
0.5 |
综合分下限,设 0 关闭 |
ES_W_REL 等 |
见代码 | 相关度与互动 log1p 权重 |
REDIS_ADDR / REDIS_PASSWORD |
空 | 设了才启用 L2 |
HTTP_ADDR |
:8080 |
监听地址 |
列顺序要和查询一致:id, updated_at, title, author, tags, likes, comments, shares, video_url, cover_url, source, source_video_id。tags 用英文逗号分隔的字符串。
CREATE TABLE videos (
id BIGINT PRIMARY KEY,
updated_at DATETIME NOT NULL,
title TEXT NOT NULL,
author TEXT NOT NULL,
tags TEXT NOT NULL,
likes INT NOT NULL,
comments INT NOT NULL,
shares INT NOT NULL,
video_url TEXT NULL,
cover_url TEXT NULL,
source VARCHAR(64) NULL,
source_video_id VARCHAR(128) NULL
);
CREATE INDEX idx_videos_updated_at_id ON videos(updated_at, id);
CREATE UNIQUE INDEX uk_source_video ON videos(source, source_video_id);- 先连上 ES、建好索引,再做初次同步:按
updated_at, id拉MYSQL_INIT_LIMIT条,bulk 进 ES,重建 Bloom 和按赞 Top 的 fallback,并 bump 缓存版本。 - 初次跑完才开 HTTP,避免搜到不全数据。
- 之后按间隔做增量:游标
(updated_at, id),每批写 ES、补 Bloom、刷新 fallback。
data/mysql_cursor.json 必须只保留当前游标,否则续传会乱;想看每次推进记录,看 data/mysql_cursor_history.jsonl(每行一条 JSON,reason 为 initial 或 incremental)。
注意:同步是 upsert,不会删 ES 里旧文档。如果之前用 mock 写过 ES,想跟 MySQL 条数对齐,要先删索引再同步(见下「脚本」)。
单元测试:
go test ./...服务起来后,Windows 下冒烟(先 go run .):
.\scripts\smoke_test.ps1换端口:$env:BASE='http://127.0.0.1:9090'; .\scripts\smoke_test.ps1
对齐 MySQL 与 ES 索引(删 ES 索引、删 cursor、起服务、等同步成功、打 /metrics 和一次搜索):配好 MYSQL_* 和 ES_* 后执行:
.\scripts\mysql_sync_rebuild_test.ps1调试想保留进程可加 -KeepRunning;端口被占用脚本会尝试结束监听进程,不需要可加 -NoKillPort 并自己改 HTTP_ADDR。
Ctrl+C 会走 graceful shutdown。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health |
存活 |
| GET | /healthz |
JSON,含 version、视频条数、ES/Redis 等 |
| GET | /demo |
简单页面搜一搜、有 video_url 可播放 |
| GET | /suggest?prefix=周 |
Trie 补全 |
| GET | /search?keyword=...&page=&page_size= |
搜索;响应头有 X-Cache、X-Bloom、X-CB 等 |
| GET | /metrics |
Prometheus 文本 |
| POST | /event |
投递事件(body JSON) |
| GET | /debug/top_clicks?limit= |
点击统计 |
/search 关键词最长 128 个 Unicode 字符。
改 mapping 或想清空重来:
DELETE http://localhost:9200/videos再启动服务(需要的话配合上面的 mysql_sync_rebuild_test.ps1)。
deploy/prometheus.yml、deploy/grafana-dashboard.json 是示例配置;Prometheus 跑在 Docker 里时,抓取地址可能要改成 host.docker.internal:8080 之类。
| 文件 | 内容 |
|---|---|
main.go |
入口、路由、信号关闭 |
handlers.go |
HTTP、热词加载 |
trie.go / search_engine.go |
前缀树、mock、内存搜、调 ES |
elasticsearch.go |
ES HTTP、索引、bulk、查询 |
mysql_sync.go / mysql_cli.go |
MySQL 游标同步、CLI 拉数据 |
search_cache.go / redis_lite.go |
缓存、可选 Redis |
bloom.go / fallback.go / circuitbreaker.go |
防穿透、兜底、熔断 |
metrics.go / ratelimit.go / events.go |
指标、限流、简单异步事件 |