📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

在掌握了环境搭建之后,下一步就是学会如何与 INFINI Easysearch 进行“对话”。

作为一款基于 JSON 的分布式搜索引擎,Easysearch 的交互方式非常直观:使用 HTTP 动词(GET, PUT, POST, DELETE)来操作 JSON 文档。这与现代 Web 开发中的 RESTful API 风格完全一致。

本文将带你通过 Dev Tools 或 Curl 完成最核心的 CRUD(创建、读取、更新、删除)操作,并深入探讨版本控制机制。


1. 创建文档 (Create) #

在 Easysearch 中,写入数据被称为“索引(Indexing)”文档。我们有两种方式来指定文档的唯一标识(_id)。

1.1 自定义 ID 写入 (PUT) #

如果你正在从 MySQL 或其他数据库同步数据,通常希望保持 ID 一致。此时使用 PUT 方法。

# 语法:PUT /<索引名>/_doc/<ID>
PUT /users/_doc/1001
{
  "username": "zhangsan",
  "age": 28,
  "role": "admin",
  "tags": [
    "developer",
    "gamer"
  ]
}
  • 结果:如果 ID 不存在,则创建;如果已存在,则覆盖(版本号增加)。
  • 场景:用户画像、商品信息等具有明确主键的数据。

1.2 自动生成 ID 写入 (POST) #

如果你的数据是日志或流水,没有天然的主键,可以让 Easysearch 自动生成一个 Base64 编码的 UUID。注意这里必须用 POST

# 语法:POST /<索引名>/_doc/
POST /logs/_doc/
{
  "timestamp": "2023-10-27T10:00:00Z",
  "message": "User login success"
}
  • 结果:返回结果中会包含自动生成的 _id,如 W0tPsHcB...

2. 读取文档 (Read) #

读取操作非常简单,只需要知道 ID 即可。

2.1 根据 ID 获取 #

GET /users/_doc/1001

2.2 检查文档是否存在 (HEAD) #

如果你只想知道文档还在不在,不需要看具体内容(节省带宽),可以用 HEAD 方法。

curl -I HEAD "http://localhost:9200/users/_doc/1001"
  • 返回 200 OK:存在。
  • 返回 404 Not Found:不存在。

2.3 只获取部分字段 (_source) #

如果文档非常大(例如包含长文本),但你只需要查看用户名,可以使用 _source 参数过滤。

GET /users/_doc/1001?_source=username,role

3. 更新文档 (Update) #

这是初学者最容易混淆的部分。在 Easysearch 内部,文档是不可变的(Immutable)。所谓的“更新”,本质上都是“标记删除旧文档 + 写入新文档”。

3.1 全量替换 (PUT) #

如果你再次对同一个 ID 执行 PUT,Easysearch 会用新的 JSON 完全替换 旧的 JSON。

# 警告:如果不包含 tags 字段,原有的 tags 数据将丢失!
PUT /users/_doc/1001
{
  "username": "zhangsan",
  "age": 29
}

3.2 局部更新 (_update API) #

在实际业务中,我们往往只想修改一个字段(例如把年龄改为 30),而不希望重传整个文档。这时必须使用 _update 端点。

# 语法:POST /<索引名>/_update/<ID>
# 可以新增字段
POST /users/_update/1001
{
  "doc": {
    "age": 30,
    "email": "zhangsan@example.com"
  }
}
  • 原理:Easysearch 内部会自动先检索旧文档,修改内存中的 JSON,然后重新索引。
  • 优势:减少了网络传输量,且更符合业务逻辑。

3.3 脚本更新 (Scripted Update) #

Easysearch 支持使用 Painless 脚本进行复杂的逻辑更新。例如,给年龄 +1,而不是设置为固定值:

POST /users/_update/1001
{
  "script": {
    "source": "ctx._source.age += params.count",
    "lang": "painless",
    "params": {
      "count": 1
    }
  }
}

4. 删除文档 (Delete) #

DELETE /users/_doc/1001
  • 注意:删除是“逻辑删除”。文档只是被标记为 .del,数据依然占用磁盘空间,直到段合并(Segment Merge)发生时才会被物理清理。

5. 进阶:并发控制 (Optimistic Concurrency Control) #

在 Web 系统中,可能会出现这种情况:

  1. 管理员 A 读取了用户数据(Age=20)。
  2. 管理员 B 也读取了用户数据(Age=20)。
  3. A 把 Age 改为 21 并保存。
  4. B 把 Age 改为 30 并保存。

如果不做控制,B 的操作会覆盖 A 的操作,导致数据不一致(丢失更新问题)。

传统方案:_version (已不推荐用于并发控制) #

虽然每个文档都有 _version,但它是内部计数器,在分布式高并发环境下不可靠。

最佳实践:_seq_no 和 _primary_term #

Easysearch 使用 序列号(Sequence Number)主分片任期(Primary Term) 来实现乐观锁。

第一步:读取时获取当前状态

GET /users/_doc/1001

返回结果中包含:

{
  "_seq_no": 15,
  "_primary_term": 2,
  "_source": { ... }
}

第二步:更新时带上条件
只有当数据库里的 _seq_no_primary_term 与我传进去的一致时,才允许修改。

PUT /users/_doc/1001?if_seq_no=15&if_primary_term=2
{
  "username": "zhangsan",
  "age": 31
}

结果推演:

  • 如果在此期间没有其他人修改,更新成功,_seq_no 变为 16。
  • 如果已经被管理员 A 修改过了(_seq_no 变成了 16),你的请求会直接失败,返回 409 Conflict
  • 你的应用逻辑:捕获 409 错误 -> 重新读取最新数据 -> 提示用户或重新计算 -> 再次尝试提交。

总结 #

  1. PUT vs POST:有 ID 用 PUT,没 ID 用 POST。
  2. PUT vs _update:PUT 是全量覆盖(小心丢字段),_update 是局部修改**(推荐)**。
  3. Delete:只是打上删除标记,空间不会立即释放。
  4. 并发安全:在关键业务(如库存扣减、金额修改)中,务必使用 if_seq_noif_primary_term 防止数据冲突。

熟练掌握这些 CRUD 操作,是开发 Easysearch 应用的坚实基础。