【搜索客社区日报】第2017期 (2024-04-08)
社区日报 • God_lockin 发表了文章 • 0 个评论 • 720 次浏览 • 4 天前
https://faun.pub/how-to-config ... 29bb4
2. 一个月撸150亿的日志,你来你也行(需要梯子)
https://medium.com/%40lunguh/h ... 33ab4
3. 无痛更新ES证书可行不?(需要梯子)
https://medium.com/%40ashishof ... b2a7c
编辑:斯蒂文
更多资讯:http://news.searchkit.cn
【搜索客社区日报】第2018期 (2025-04-09)
社区日报 • kin122 发表了文章 • 0 个评论 • 485 次浏览 • 3 天前
https://zhuanlan.zhihu.com/p/26631768854
2.RAG结合上下文提升召回成功率|Claude
https://zhuanlan.zhihu.com/p/721306620
3.RAG探索之路的血泪史及曙光
https://zhuanlan.zhihu.com/p/664921095
4.加速 HNSW 图的合并
https://mp.weixin.qq.com/s/5s2xuXKaTmznTo7bijeVqA
编辑:kin122
更多资讯:http://news.searchkit.cn
Easysearch S3 备份实战
Easysearch • INFINI Labs 小助手 发表了文章 • 0 个评论 • 470 次浏览 • 3 天前
Easysearch 内置了 S3 插件,这意味着用户可以直接使用该功能而无需额外安装任何插件。通过这一内置支持,用户能够方便快捷地执行 Amazon S3 上的数据快照操作。这种设计不仅简化了配置流程,也提高了工作效率,使得数据备份或迁移等任务变得更加简单易行。对于需要频繁与 S3 存储服务交互的应用场景来说,这是一个非常实用且高效的功能特性。
## Minio
MinIO 是一款高性能的开源对象存储系统,专为存储大量的非结构化数据而设计。它提供了与 Amazon S3 兼容的 API,本次测试我们使用 MinIO 作为存储仓库。
## 建立 Bucket
进入 MinIO 管理界面,创建测试用的 bucket。


## 创建 Access key
测试的 Access Key 设置的比较简单。

## Easysearch
为了能够使用 S3 存储,Easysearch 要进行必要的配置。
## easyearch.yml
修改 easysearch.yml 配置 S3 信息。
```plain
s3.client.default.endpoint: 172.17.0.4:9000
s3.client.default.protocol: http
```
⚠️ 注意:修改了 easysearch.yml 需要重启生效。
## keystore
为了安全,我们把 S3 的 Access key 信息加入 keystore 中。
```plain
bin/easysearch-keystore add s3.client.default.access_key #输入easysearch
bin/easysearch-keystore add s3.client.default.secret_key #输入easysearch
bin/easysearch-keystore list
```
## 注册存储库
在 INFINI Console 的开发工具中,使用命令注册 s3 存储库。
```plain
PUT /_snapshot/easysearch_s3_repo?verify=true&pretty
{
"type": "s3",
"settings": {
"bucket": "easysearch-bucket",
"compress": true
}
}
```
更多参数请查看[文档](https://infinilabs.cn/docs/lat ... zon-s3)。
## 创建快照
在 [INFINI Console](https://infinilabs.cn/products/console/) 的开发工具中,使用命令创建快照。


备份执行完成。
## S3 查看快照
我们在 INFINI Console 中通过命令创建了快照,可以在 MinIO 的 bucket 中进行进一步确认是否有相关文件。


## 快照还原测试
删除以备份索引 .infini_metrics-0001,删除前先查看下索引情况,文档数 557953。

删除 .infini_metrics-0001 索引。

确认 .infini_metrics-0001 索引已被删除。

进行快照还原。

验证恢复索引。

索引 .infini_metrics-0001 已经还原了,文档数也一致。
## 小结
Easysearch 使用 S3 存储备份的步骤如下:
1. S3 服务建立 Bucket、Access Key。
2. Easysearch 编辑 easysearch.yml 添加 S3 服务 endpoint 信息。
3. easysearch-keystore 添加 S3 的 Access key 信息,加密保存。
4. Easysearch 注册 S3 存储仓库。
5. 执行快照备份。
## 关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch/main/>
> 作者:**杨帆**,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
Easysearch 自动备份:快照生命周期管理
Easysearch • INFINI Labs 小助手 发表了文章 • 0 个评论 • 465 次浏览 • 3 天前
之前介绍了 Easysearch 如何[使用 S3 进行快照备份](http://infinilabs.cn/blog/2025 ... ackup/),毕竟那是手工操作。Easysearch 还提供了[快照生命周期管理](https://docs.infinilabs.com/ea ... m_api/),能够按照策略自动创建、删除快照,极大地方便了用户的日常管理。
快照生命周期管理计划由创建计划、删除计划以及快照配置组成。
- 创建计划和删除计划包含一个 cron 表达式,指定任务的频率和时间。
- 删除计划可以指定快照保留策略,以保留过去 30 天的快照或仅保留最近的 10 个快照。
- 快照配置包括快照的索引和存储库,并支持所有通过 API 创建快照时的参数。此外,还可以指定快照名称中使用的日期的格式和时区。
快照生命周期创建的快照名称格式为 `
比如, 计划每 2 分钟对索引 .infini_metrics-00001 创建一个快照,并且只保留最近的 2 个快照。
```plain
curl -XPOST -uadmin:admin -H 'Content-Type: application/json' 'https://localhost:9200/_slm/policies/daily-policy' -d '
{
"description": "测试快照策略",
"creation": {
"schedule": {
"cron": {
"expression": "*/2 * * * *",
"timezone": "Asia/Shanghai"
}
},
"time_limit": "1h"
},
"deletion": {
"schedule": {
"cron": {
"expression": "*/1 * * * *",
"timezone": "Asia/Shanghai"
}
},
"condition": {
"max_count": 2
},
"time_limit": "1h"
},
"snapshot_config": {
"date_format": "yyyy-MM-dd-HH:mm",
"date_format_timezone": "Asia/Shanghai",
"indices": ".infini_metrics-00001",
"repository": "easysearch_s3_repo",
"ignore_unavailable": "true",
"include_global_state": "false"
}
}'
```
自动创建的快照如下图,一个 16 点 34 分创建的,另一个 16 点 36 分创建的。

⚠️ 注意:虽然指定只保留最近两个快照,但因为创建和删除其实是两个独立的任务,所以会短暂出现存在 3 个快照的现象,等删除任务调度一次就会删除多余的快照了。
如果遇到维护需要停止自动备份,也有相应的 API 来启停快照策略。
停止策略
```plain
curl -XPOST -uadmin:admin 'https://localhost:9200/_slm/policies/daily-policy/_start'
```
启动策略
```plain
curl -XPOST -uadmin:admin 'https://localhost:9200/_slm/policies/daily-policy/_stop'
```
查看策略
```plain
curl -XGET -uadmin:admin 'https://localhost:9200/_slm/policies'
```
删除策略
```plain
curl -XDELETE -uadmin:admin 'https://localhost:9200/_slm/policies/daily-policy?pretty'
```
更多详细信息请参考[官方文档](https://docs.infinilabs.com/ea ... m_api/)。
## 关于 Easysearch

INFINI Easysearch 是一个分布式的搜索型数据库,实现非结构化数据检索、全文检索、向量检索、地理位置信息查询、组合索引查询、多语种支持、聚合分析等。Easysearch 可以完美替代 Elasticsearch,同时添加和完善多项企业级功能。Easysearch 助您拥有简洁、高效、易用的搜索体验。
官网文档:<https://docs.infinilabs.com/easysearch/main/>
> 作者:**杨帆**,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
Operator 开发入门系列(一):Hello World!
默认分类 • INFINI Labs 小助手 发表了文章 • 0 个评论 • 361 次浏览 • 2 天前

## 背景
我们公司最近计划将产品迁移到 Kubernetes 环境。 为了更好地管理和自动化我们的应用程序,我们决定使用 Kubernetes Operator。 本系列博客将记录我们学习和开发 Operator 的过程,希望能帮助更多的人入门 Operator 开发。
## 目标读者
- 对 Kubernetes 有一定了解的开发人员和运维人员
- 希望使用 Operator 自动化管理应用程序的人员
- 对 Go 语言有基本了解的人员
## 准备工作
在开始之前,你需要准备以下环境:
- **Go 语言环境 (>= 1.23):** Operator 通常使用 Go 语言开发,你需要安装 Go 语言环境。 建议使用 Go 1.21 或更高版本。 可以从 [https://go.dev/dl/](https://go.dev/dl/) 下载安装包。 安装完成后,请配置好 `GOPATH` 和 `PATH` 环境变量。
- **Kubernetes 集群:** 你需要一个可用的 Kubernetes 集群来部署和测试 Operator。 可以使用 Minikube、Kind 或其他的 Kubernetes 发行版。
- **kubectl 命令行工具:** `kubectl` 是 Kubernetes 的命令行工具,用于与 Kubernetes 集群交互。 请确保你已经安装并配置了 `kubectl`, 并且能够连接到你的 Kubernetes 集群。
- **Kubebuilder (>= 3.0):** Kubebuilder 是一个用于快速构建 Kubernetes Operator 的框架。 使用 Kubebuilder 可以简化 Operator 的开发流程,并生成一些必要的代码框架。 可以使用以下命令安装 Kubebuilder:
```bash
cd $HOME/go/bin
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder
```
> 请确保 `$HOME/go/bin` 目录在你的 `PATH` 环境变量中。 可以运行 `kubebuilder version` 命令来验证 Kubebuilder 是否安装成功。
- **Docker (可选):** 如果你需要构建 Operator 的 Docker 镜像,你需要安装 Docker。
> 我的环境是 MacOS(arm64) + Orbstack
## 什么是 Operator?
简单来说,Operator 是 Kubernetes 的扩展,它利用自定义资源(Custom Resources, CRs)来自动化管理应用程序。Operator 允许我们像管理 Kubernetes 内置资源一样管理复杂的应用程序,例如数据库、消息队列等。
## 为什么选择 Operator?
Operator 提供了一种声明式的方式来管理应用程序的生命周期,包括部署、升级、备份、恢复等。它可以简化运维流程,提高自动化程度,并确保应用程序的状态符合预期。
## 我们的第一个 Operator:Hello World
这个 Operator 将监听一个名为 `HelloWorld` 的自定义资源,并在 Kubernetes 中创建一个 Pod,该 Pod 运行一个简单的 "Hello World" 应用程序。
### 1. 初始化 Kubebuilder 项目
首先,我们需要使用 Kubebuilder 创建一个新的项目。 在你的 `GOPATH` 目录下创建一个新的目录,例如 `hello-world-operator`,然后进入该目录,运行以下命令
```bash
kubebuilder init --domain infini.cloud --repo github.com/infinilabs/hello-world-operator
```
这个命令会创建一个新的 Kubebuilder 项目,并生成一些必要的文件和目录。
### 2. 创建自定义资源(Custom Resource Definition, CRD)
接下来,我们需要定义 `HelloWorld` 资源的结构。 运行以下命令
```bash
kubebuilder create api --group example --version v1alpha1 --kind HelloWorld
```
这个命令会创建一个新的 API 定义,包括 `api/v1alpha1/helloworld_types.go` 和 `controllers/helloworld_controller.go` 两个文件。
编辑 `api/v1alpha1/helloworld_types.go` 文件,修改 `HelloWorldSpec` 的定义,添加 `name` 和 `message` 字段:
```go
// HelloWorldSpec defines the desired state of HelloWorld
type HelloWorldSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Name is the name of the HelloWorld resource
Name string `json:"name,omitempty"`
// Message is the message to be printed by the pod
Message string `json:"message,omitempty"`
}
```
### 3. 实现 Reconcile 逻辑
编辑 `controllers/helloworld_controller.go` 文件,实现 `Reconcile` 函数, 创建一个 Pod,该 Pod 运行一个 `busybox` 镜像,并输出 `HelloWorld` 资源中定义的 `message`。
```go
package controllers
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
examplev1alpha1 "github.com/infinilabs/hello-world-operator/api/v1alpha1"
)
// HelloWorldReconciler reconciles a HelloWorld object
type HelloWorldReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=example.com,resources=helloworlds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=example.com,resources=helloworlds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=example.com,resources=helloworlds/finalizers,verbs=update
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io ... ncile
func (r *HelloWorldReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. Fetch the HelloWorld instance
helloWorld := &examplev1alpha1.HelloWorld{}
err := r.Get(ctx, req.NamespacedName, helloWorld)
if err != nil {
if apierrors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
// For additional cleanup logic use finalizers.
log.Info("HelloWorld resource not found. Ignoring since object must be deleted")
return ctrl.Result{}, nil
}
// Error reading the object - requeue the request.
log.Error(err, "Failed to get HelloWorld")
return ctrl.Result{}, err
}
// 2. Define the desired Pod
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: helloWorld.Name + "-pod",
Namespace: helloWorld.Namespace,
Labels: map[string]string{
"app": helloWorld.Name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "hello-world",
Image: "busybox",
Command: []string{"sh", "-c", fmt.Sprintf("echo '%s' && sleep 3600", helloWorld.Spec.Message)},
},
},
},
}
// 3. Set HelloWorld instance as the owner and controller
if err := ctrl.SetControllerReference(helloWorld, pod, r.Scheme); err != nil {
log.Error(err, "Failed to set controller reference")
return ctrl.Result{}, err
}
// 4. Check if the Pod already exists
found := &corev1.Pod{}
err = r.Get(ctx, client.ObjectKey{Name: pod.Name, Namespace: pod.Namespace}, found)
if err != nil && apierrors.IsNotFound(err) {
log.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
err = r.Create(ctx, pod)
if err != nil {
log.Error(err, "Failed to create new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
return ctrl.Result{}, err
}
// Pod created successfully - return and requeue
return ctrl.Result{Requeue: true}, nil
} else if err != nil {
log.Error(err, "Failed to get Pod")
return ctrl.Result{}, err
}
// 5. Pod already exists - don't requeue
log.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *HelloWorldReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&examplev1alpha1.HelloWorld{}).
Owns(&corev1.Pod{}).
Complete(r)
}
```
### 4. 安装 CRD 到 Kubernetes 集群
运行以下命令安装 CRD 到 Kubernetes 集群:
```bash
make install
```
### 5. 运行 Operator
运行以下命令在本地运行 Operator:
```bash
make run
```
### 6. 创建 HelloWorld 资源
创建一个名为 `my-hello-world.yaml` 的文件,内容如下:
```yaml
apiVersion: example.com/v1alpha1
kind: HelloWorld
metadata:
name: my-hello-world
spec:
name: my-hello-world
message: "Hello World from Operator!"
```
使用 `kubectl apply -f my-hello-world.yaml` 创建资源。
### 7. 验证
使用 `kubectl get pods` 命令查看是否创建了名为 `my-hello-world-pod` 的 Pod。 使用 `kubectl logs my-hello-world-pod` 查看 Pod 的日志,确认是否输出了 "Hello World from Operator!"。
## 总结
恭喜你完成了第一个 Operator! 虽然这个 Operator 非常简单,但它展示了 Operator 的基本原理:监听自定义资源,并根据资源的状态来管理 Kubernetes 资源。 在接下来的系列中,我们将深入探讨 Operator 的更多高级特性。
敬请期待下一篇博客!
> 作者:罗厚付,极限科技(INFINI Labs)云上产品设计与研发负责人,拥有多年安全风控及大数据系统架构经验,主导过多个核心产品的设计与落地,日常负责运维超大规模 ES 集群(800+节点/1PB+数据)。
> 原文:https://infinilabs.cn/blog/202 ... rt-1/
【搜索客社区日报】第2019期 (2025-04-10)
社区日报 • Se7en 发表了文章 • 0 个评论 • 318 次浏览 • 1 天前
https://mp.weixin.qq.com/s/R_X0qxSiA3X4FqhWzyQM1g
2. MCP,竟然是两位年轻工程师的杰作
https://mp.weixin.qq.com/s/4F9PokMiKTaobeKQaw7Wcg
3.为 Kubernetes 提供智能的 LLM 推理路由:Gateway API Inference Extension 深度解析
https://mp.weixin.qq.com/s/jRxY4GJgnvzk-o3nBmjP4g
4.基于 MCP 实现 AI 应用架构新范式的一线实践(含78页架构图下载)
https://mp.weixin.qq.com/s/r1wKHJDzUgWncZvNko2O2Q
编辑:Se7en
更多资讯:http://news.searchkit.cn
【搜索客社区日报】第2020期 (2025-04-11)
社区日报 • Fred2000 发表了文章 • 0 个评论 • 239 次浏览 • 1 天前
https://www.infoq.cn/article/y9gcWYGXUhRYqvXqsy1f
2、Easysearch 索引备份之 Clone API
https://blog.csdn.net/yangmf20 ... 88006
3、Elasticsearch 8.X 如何利用嵌入向量提升搜索能力?
https://mp.weixin.qq.com/s/Vv2i3FOSUTAhnTiKJM2h3A
4、ES 集群日增数据统计难?手把手教你精准计算文档数&存储量!
https://cloud.tencent.com/deve ... 09449
5、INFINI Console:助力 Elasticsearch 集群平滑升级,保障业务零中断
https://infinilabs.cn/case/ind ... nsole
编辑:Fred
更多资讯:http://news.searchkit.cn
代理 Elasticsearch 服务:INFINI Gateway VS Nginx
Elasticsearch • INFINI Labs 小助手 发表了文章 • 0 个评论 • 215 次浏览 • 1 天前

## INFINI Gateway 简介
[INFINI Gateway](https://infinilabs.cn/products/gateway/) 是一款面向 Elasticsearch 的高性能应用网关,专为提升 Elasticsearch 集群的性能、安全性和可管理性而设计。它作为 Elasticsearch 的前置网关,能够处理所有客户端请求,并将其转发到后端的 Elasticsearch 集群,同时提供丰富的功能来优化请求处理和管理。此外还支持代理 Opensearch、[Easysearch](https://infinilabs.cn/products/easysearch/) 服务。
## Nginx 简介
Nginx 是一个高性能的 HTTP 和反向代理服务器,以其高并发处理能力、低内存消耗和稳定性著称,广泛应用于 Web 服务器、负载均衡和反向代理等场景。在 Elasticsearch 的使用场景里,也有小伙伴使用 Nginx 来代理 Elasticsearch 的服务,利用 Nginx 的负载均衡能力,将请求转发到多个 Elasticsearch 节点。
这两个软件都能代理 Elasticsearch 服务,但是他们有什么区别?我们来一起分析分析。
## 负载均衡
Elasticsearch 是分布式系统,提倡使用 round-robin 方式将请求发送到多个节点。不管是 Nginx 还是 INFINI Gateway 都默认使用 round-boin 方式转发请求,也都支持 weighted round-robin(加权轮询)方式进行请求转发,这点两者相当。
## 节点自动更新
Elasticsearch 集群可能会遇到添加、删除节点的情况,代理程序能否感知 Elasticsearch 节点的变化将变得非常关键。
在 Nginx 中,所有转发节点的 IP 地址都必须写入到配置文件中。如果 Elasticsearch 集群加入了新的节点进行请求处理,则需要 Nginx 编辑配置文件把新节点的 IP 地址加入其中,然后重启或重载 Nginx 服务,才能将请求分发到新的节点。反之如果有节点下线,也要编辑 Nginx 配置文件并重载服务。
INFINI Gateway 是面向 Elasticsearch 设计的应用网关,具有后端节点发现和更新的功能,能够感知 Elasticsearch 集群节点加入、离开的情况。开启节点发现和更新功能后,Gateway 会定期自动更新节点列表,将请求均匀转发到列表中的节点。可参考之前的[博客](https://infinilabs.cn/blog/202 ... teway/)开启节点自动更新。
⚠️ 注意:INFINI Gateway 默认后端节点发现和更新的功能为关闭状态。
## 定向转发请求
使用 Elasticsearch 集群的场景多种多样,如果想对转发的节点做进一步控制,可能需要根据不同条件进行节点筛选:
- IP 地址
- 节点角色
- 节点标签
## Nginx
Nginx 支持根据 IP 地址进行转发的,将需要转发的节点 IP 地址写入配置文件即可。
```plain
upstream es-cluster {
server 192.168.56.102:9200;
server 192.168.56.102:9201;
server 192.168.56.102:9202;
}
```
但不支持按节点角色、节点标签进行筛选,因为 Nginx 中并没有这种概念。
## INFINI Gateway
INFINI Gateway 支持按 IP 地址进行筛选:
- 不开节点发现:只转发到配置文件指定的节点(IP 地址)
- 开启节点发现:转发到所有发现的节点
```plain
flow:
- name: cache_first
filter:
- elasticsearch:
elasticsearch: prod
refresh:
enabled: true
interval: 30s
filter:
hosts:
exclude:
- 192.168.3.201:9200
include:
- 192.168.3.202:9200
- 192.168.3.203:9200
```
此外 Gateway 还支持通过节点角色、节点标签筛选转发节点。
```plain
flow:
- name: cache_first
filter:
- elasticsearch:
elasticsearch: prod
refresh:
enabled: true
interval: 30s
filter:
tags:
exclude:
- temp: cold
include:
- disk: ssd
roles:
exclude:
- master
include:
- data
- ingest
```
多种筛选条件可以同时使用,详细信息请查看官方[文档](https://docs.infinilabs.com/ga ... earch/)。
> 作者:**杨帆**,极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。
> 原文:https://infinilabs.cn/blog/202 ... ginx/