Chat专栏专题电子书
桃花惜春风
CSDN博客专家。曾负责大数据平台日TB级实时高并发数据流架构,曾人工智能创业团队大数据开发组负责人,万方搜索引擎负责人,近几年专注Elasticsearch底层技术,高级Elasticsearch工程师。
查看本场Chat
本篇文章我们主要讲述一些生产上的简单实战场景。主要涉及一些我们常见的一些应用,技术难点虽然不高,但很实用。文章共分九章,并且每一章都是相互独立的,读者可根据自己情况选读。
Elasticsearch 作为当下最火的搜索引擎,可以说是继 Hadoop、Spark 之后,第三代红利,互联网行业刚需技术。Elasticsearch 讲起来有太多的东西,每次去官网查找资料都会看到未知的东西,只有不断的学习才能不断的进步,才能不被新鲜血液超越。有幸接触 Elasticsearch 的时间比较早,从 2.x 版本开始到现在 6.x 版本,Elasticsearch 迭代更新特别快,无论你是初学者还是已经接触 Elasticsearch 有段时间的开发人员,我都建议去官网学习,有问题找官网,最后你会发现,你的问题都能在官网找到答案。
由于篇幅原因,本篇文章会尽量讲清楚文章的核心知识点,对于文章中有不懂的地方可以随时交流。作者现在已经在考虑写一个关于 Elasticsearch 深入浅出的达人课,达人课前半部分针对初学者讲述一些基础性东西,后半部分针对开发运维人员讲述内部原理和优化的进阶知识。
这里不介绍单机版安装,不过Elasticsearch单节点一样可以实现基本功能。
安装 Java
sudo apt-get install openjdk-8-jre
说明:Elasticsearch 从 5.x 版本开始使用 JDK 8,低版本 JDK 是无法启动的。
自动安装
这里拿 6.4.2 版本举例。
下载:
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.4.2.deb
安装:
sudo dpkg -i elasticsearch-6.4.2.deb
启动:
systemctl start elasticsearch.service
说明:这里使用自动安装是要安装在 root 用户下的,可能有些人有一些误解,Elasticsearch 并非不能安装在 root 用户下,只是不能以 root 用户去启动。这里使用的启动方式默认是以 Elasticsearch 用户去启动,也可以自定义启动用户(/usr/lib/systemd/system 路径下修改 elasticsearch.service)。如果不想把 Elasticsearch 安装在 root 下,下面会介绍手动安装到指定用户下。
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/deb.html
手动安装
有些使用场景让我们不得不使用手动安装这种方式,比如机器处在内网环境。
创建好自己想要安装 Elasticsearch 的用户,并进入该用户下你想要安装的路径下。
下载:
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.4.2.tar.gz
解压:
tar -xzf elasticsearch-6.4.2.tar.gz
启动:
bin/elasticsearch
或(长期运行):
bin/elasticsearch -d
配置文件
前面介绍了两种安装方式,可以根据自己喜欢选择。但要启动服务,还需要更改一些基础配置。
如果使用的是自动安装方式,配置文件在 /etc/elasticsearch 下,安装路径为 /usr/share/elasticsearch。
如果使用的是手动安装方式,配置文件在你安装路径的 conf 文件夹下。
好,配置文件路径我们找到了,现在开始修改配置。
elasticsearch.yml
##集群名,现版本的ES已经不是通过集群名来发现节点了
cluster.name: elasticsearch
##节点名,每个节点不相同即可
node.name: node-1
##是否有选举成Master的资格
node.master: true
##是否作为数据节点
node.data: true
##数据存储路径,注意保证挂在正确磁盘上
path.data: /var/lib/elasticsearch
##日志路径
path.logs: /var/log/elasticsearch
##本机地址
network.host: node-1
##发现Master列表,只写有Master资格的节点即可,注意空格
discovery.zen.ping.unicast.hosts: ["192.168.56.101", "192.168.56.102", "192.168.56.103"
jvm.options
## 分配给ES内存,生产上一般设置不大于32
-Xms31g
-Xmx31g
测试
http://node-1:9092
本章小结
这一章主要介绍了关于 Elasticsearch 的两种安装方式以及一些必要的基础配置。关于本章有什么问题可以在读者圈交流。下一章我们详细介绍 Elasticsearch 的常用插件安装。
本章主要介绍 Elasticsearch 的一些常用插件安装,主要包括 IK、pinyin、stconvert。同样介绍自动安装和手动安装两种方式。下面介绍的均为单节点的安装方式,实际应用中必须所有节点全部安装成功。篇幅比较啰嗦,主要是照顾一些初学者,对于插件安装已经很熟练的可以越过此章。
IK 分词器
相信使用 Elasticsearch 的小伙伴对 IK 一点都不陌生,目前大多数需要分词的场景都是使用 IK 来处理的。
自动安装
进入到你的 Elasticsearch 安装路径下安装:
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip
注意:
手动安装
进入到你的 Elasticsearch 安装路径下的 plugins 文件夹中:
mkdir ik
选择合适版本安装包:
https://github.com/medcl/elasticsearch-analysis-ik/releases
配置
自动安装方式配置文件路径:
/etc/elasticsearch/analysis-ik
手动安装方式配置文件路径:
/ELASTICSEARCH_PATH/plugins/ik/config
IKAnalyzer.cfg.xml
IK Analyzer 扩展配置
custom/mydict.dic;custom/single_word_low_freq.dic
custom/ext_stopword.dic
location
http://xxx.com/xxx.dic
注意事项
ik_smart
和 ik_max_word
两种分词方式。ikmaxword
表示最细粒度拆分。优点是查询效果比较好。缺点是会产生很多碎片,对于大文本字段不建议使用 ik_max_word
。
例:
将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合。
ik_smart
表示最粗粒度拆分,优点是降低了索引存储。缺点是查询效果不好。
例:将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。这个时候输入“中华”是匹配不到的,只能匹配“中华人民共和国”或“国歌”。
Mapping 中使用 IK
"content": {
"type": "text",
"analyzer": "ik_max_word", ##索引分词
"search_analyzer": "ik_max_word"。 ##查询分词
}
想了解 IK 分词与其他分词组合使用,建立自定义分词器的可以去我博客有相关文章。
关于热更新词库,medcl 大神讲的很清楚,需要的小伙伴可以参考一下。
参考:https://github.com/medcl/elasticsearch-analysis-ik
pinyin 分词器
对于很多的搜索场景,用户输入的有时候并非汉字,可能是拼音或者拼音首字母,这个时候我们同样要匹配到数据,就需要引入 pinyin 分词器。
自动安装
进入到你的 Elasticsearch 安装路径下:
安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v6.3.0/elasticsearch-analysis-pinyin-6.3.0.zip
注意:
手动安装
进入到你的 Elasticsearch 安装路径下的 plugins 文件夹中
mkdir pinyin
选择合适版本安装包:
https://github.com/medcl/elasticsearch-analysis-pinyin/releases
参考:https://github.com/medcl/elasticsearch-analysis-pinyin
stconvert 分词器
stconvert 主要是用来简体繁体字互换使用的,主要是针对港澳的一些用户使用。
自动安装
进入到你的 Elasticsearch 安装路径下安装:
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-stconvert/releases/download/v6.3.0/elasticsearch-analysis-stconvert-6.3.0.zip
注意:
手动安装
进入到你的 Elasticsearch 安装路径下的 plugins 文件夹中
mkdir stconvert
选择合适版本安装包:
https://github.com/medcl/elasticsearch-analysis-stconvert/releases
说明
参考:https://github.com/medcl/elasticsearch-analysis-stconvert
本章小结
本章主要介绍了 Elasticsearch 常用的插件安装。当然还包括很多没提到的。像 sql、hanlp、jieba 等等。新版本的 x-pack 已经支持 sql,有需要的同学可以在读者圈交流。
在我们实际应用中某些场景不免要使用索引模版,其实模版是一把双刃剑,用得好会给我们带来很大方便,用不好一样会成为我们的负担。
使用场景
在 Elasticsearch 我们都知道 Mapping 属于集群状态信息,是由 Master 节点来维护的,所以一旦 Mapping 发生变化,就要请求等待 Master 处理。这个时候写入阻塞。所以我们一般设置 dynamic=true
来禁止动态生成新字段,这样即使写入一个陌生字段也不会对 Mapping 进行修改,只是该文档插入失败。
但是,有另外一种场景,就像日志类的索引,一般我们需要每天,甚至更细粒度的创建索引。这种情况下我们一般都会选择使用模版来预先制定好索引的 Mapping。这也是模版使用的主要场景,但有些负载比较大的集群,要考虑慎用。因为我们在使用模版的场景一般是每天的凌晨或者某个时间点,按照模版建立新的索引,这也就导致了某个时间点会有大量新的 index 生成,这个时候集群的阻塞期可能会很长。
对于这种场景给出两种解决方案:
示例
创建模版
PUT _template/server-log ##指定模版名:server-log
{
"template" : "server*", ##匹配所有以server开头的索引名:server*
"settings": { ##指定索引的setting,索引创建后可以修改
"index.number_of_replicas": "1",
"index.number_of_shards": "5",
"index.translog.flush_threshold_size": "512mb",
"index.translog.sync_interval": "60s",
"index.codec": "best_compression"
},
"mappings": { ##指定索引mapping,索引创建后不可修改
"doc": {
"dynamic": "strict",
"_all": {
"enabled": false
},
"properties": {
"@timestamp": {
"type": "date"
},
"logdate": {
"type": "date",
"format": "yyyyMMdd||yyyy-MM-dd||yyyy/MM/dd||yyyyMMddHHmmss||yyyy-MM-dd HH:mm:ss||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss'Z'||yyyy/MM/dd'T'HH:mm:ss'Z'||yyyy/MM/dd HH:mm:ss'Z'||yyyy-MM-dd HH:mm:ss'Z'"
},
"message": {
"norms": false,
"type": "keyword"
}
}
}
}
}
本章小结
本章我们讲述了关于索引模版的使用和使用模版会产生哪些弊端以及解决方案,大家在使用的过程中有什么问题可以随时交流。
我们在刚刚接触 Elasticsearch 的时候,网上铺天盖地地说 “Elasticsearch 只能分配 32G,分再多也没用啦,甚至会更慢!” 等等这些。可能很多人只是知其然不知其所以然。这里给大家分享一个(链接),这里详细讲述了,为什么分配给 Elasticsearch 内存不能超过 32G。
回过头来,为什么不能分配超过 32 不是本章的重点,你只要记得有这么个事就行了。一般情况下我们分给 Elasticsearch32 个 G,还需要留给 Lucene32 个 G,所以一个节点 64G 内存足够我们使用了。但实际情况是,像笔者之前所在的公司,有自己专门的机房,具体有多少服务器我没了解过,光我们项目就有 1000 多台服务器。服务器越多维护成本越高,所以统一都是采购的高配服务器。这种情况下一台服务器的配置肯定远远高于 64G,这样就造成了大量的资源浪费。但是 Elasticsearch 并非像 kafka 那样一个 broker 就是一台机器,它可以在一台机器上启动两个甚至更多实例,这样就可以做到资源的充分利用。
配置方法
下面按照之前讲述的手动和自动两种安装方式分别讲解。
自动安装方式
自动安装方式的配置文件默认在 /etc/elasticsearch 下,但这只是单实例情况,我们要对每个实例有单独的配置文件。
1. 建立两个文件夹存放两个实例的配置文件:
mkdir /etc/elasticsearch/example-1
mkdir /etc/elasticsearch/example-2
2. 将以下 4 个配置文件分别拷贝到两个目录下:
elasticsearch.yml
jvm.options
log4j2.properties
scripts
3. 分别修改两个目录下的 elasticsearch.yml:
##集群名一致
cluster.name: elasticsearch
##节点名不一致
node.name: node-1
##不建议一台机器多个实例同时都可以选Master,一般Master需要单台机器
node.master: true
##是否作为数据节点
node.data: true
##数据存储路径,每个实例要对应不同的路径
path.data: /var/lib/elasticsearch
##日志路径,每个实例要对应不同的路径
path.logs: /var/log/elasticsearch
##本机地址,这个不用改,因为还是在一台机器上ip都一样
network.host: node-1
##指定外部端口号,每个实例不一致,一般设置9200、9201...,必须显式配置
http.port: 9200
##指定transport端口号,每个实例不一致,一般设置9300、9301...,必须显式配置
transport.tcp.port: 9300
##发现Master列表,注意不同实例以端口号区分
discovery.zen.ping.unicast.hosts: ["192.168.56.101:9300", "192.168.56.102:9301", "192.168.56.103"
##单机上最大允许运行的ES实例个数,默认为1
node.max_local_storage_nodes: 2
##禁止副本分片分配到同一个机器
cluster.routing.allocation.same_shard.host: true
4. 其他三个配置文件可根据实际情况自行修改
5. 复制启动文件
cd /usr/lib/systemd/system/
cp elasticsearch.service elasticsearch-example1.service
cp elasticsearch.service elasticsearch-example2.service
6. 修改启动文件
##分别指定两个实例的配置文件路径和pid文件路径,目录需要提前创建
Environment=ES_PATH_CONF=/etc/elasticsearch/example-1
Environment=PID_DIR=/var/run/elasticsearch/example-1
7. 启动
systemctl start elasticsearch-example1.service
systemctl start elasticsearch-example2.service
手动安装方式
./elasticsearch -d -Des.path.conf=/etc/elasticsearch/example-1 -p /var/run/elasticsearch/example-1/example1.pid
./elasticsearch -d -Des.path.conf=/etc/elasticsearch/example-2 -p /var/run/elasticsearch/example-1/example2.pid
本章小结
本章我们讲述了单机多实例的配置方法。关于本章有疑问的可以在读者圈交流。
冷热数据呢是我们很多业务场景都会遇到的问题,数据分离其实是分为冷热分离和读写分离。本章主要从两个场景出发,谈谈具体的解决方案。
冷热分离
我们在使用 Elasticsearch 过程中经常会遇到一种场景,每天会有新的数据不停的在写入,并且历史数据期限很长,可能是很久很久以前的老数据,对于这些老数据查询请求非常低。这种情况很容易导致负载不均衡的情况。我们可以通过以下操作方案来解决这一问题。
1. 修改配置 elasticsearc.yml
在热数据节点上配置:
node.attr.zone: hot
在冷数据节点上配置:
node.attr.zone: stale
注:5.0 版本开始节点属性配置必须加上前缀 node.attr。
2. 新建索引或创建模版时加上索引settings,表明新建的 index 指向 hot 节点,这样新的热数据的分片就只会存储在 hot 属性的节点上。
"settings" : {
"index.routing.allocation.include.zone" : "hot"
}
3. 定期更新索引属性 hot 为 stale,这样索引数据会从 hot 节点转移到 stale 节点上成为冷数据。
PUT /index_name/_settings
{
"index.routing.allocation.include.zone" : "stale"
}
4. [选] hot 节点使用高配,stale 节点低配。
读写分离
上面的冷热分离操作解决了我们冷热数据的问题,但是还有一种场景,我们每天写入热数据,同时热数据的查询请求又特别高,这样读写请求全部都集中在 hot 节点空间上了,stale 几乎是闲置状态,这同样不是我们想看到的。
修改配置 elasticsearc.yml
指定节点属性:
cluster.routing.allocation.awareness.attributes:zone
指定强制分配分片规则:
cluster.routing.allocation.awareness.force.zone.values: zone1,zone2,zone3
说明:
手动分配
如果上述的冷热分离和读写分离操作依然没有达到我们想要的效果的话,我们可以尝试手动分配分区。
POST /_cluster/reroute
{
"commands" : [
{
"move" : {
"index" : "test", "shard" : 0,
"from_node" : "node1", "to_node" : "node2"
}
},
{
"allocate_replica" : {
"index" : "test", "shard" : 1,
"node" : "node3"
}
}
]
}
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html
查询分离
如果如果如果上述还是没法实现我们的要求,我们一样可以通过查询来分担集群的压力。但是重要的事说三遍(如果),这种方式要慎用,用不好的话很有可能导致查询数据不全或查不到数据。
指定节点进行查询:
POST /_search?preference=_only_nodes:zone:stale
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-preference.html?q=preference
本章小结
本章我们分两种场景介绍了关于数据的冷热分离和读写分离,以及备选方案手动分配和查询分离。最终目的还是实现集群的有效负载,避免资源的浪费。机架感知的实现其实也是同样的道理,让副本分片分布在不同的机架上。
在测试环境和生产环境中,Elasticsearch 集群都不会再我们本机上,所以远程连接 Elasticsearch 是必然的。内网还好说,如果是外网的话会涉及到一些问题。本章从两种场景探讨。
访问 AWS
有些公司会把服务器部署在 AWS 上。用过 AWS 的小伙伴都知道,AWS 的网络是分内网和外网的。这个时候如果我们在 AWS 内部访问 Elasticsearch 要指定内网 IP,如果是在外网访问 Elasticsearch,就要指定外网 ip。同时配置文件要配置内网 IP。如果使用主机名访问,服务器 hosts 映射内网 IP,客户端 hosts 映射外网 IP。
## 内网ip或内网主机名
network.host: node-1
访问虚拟机
对于虚拟机中的 Elasticsearch 访问,桥接模式不用说了,IP 和端口都是独立的,直接访问就行了。如果是 NAT 模式,对外只开放物理机的 IP,这个时候要访问 Elasticsearch 需要做如下配置:
elasticsearc.yml
network.host: node-3
http.host: 0.0.0.0
说明:
还有一种简单粗暴的方法:
network.host: 0.0.0.0
不过这种方式只能启动单个节点。
本章小结
本章我们介绍如何在外网访问 AWS 和虚拟机中的 Elasticsearch。对于本章内容有什么问题可以在读者圈交流。
集群升级是我们生产上的常见需求了,其实我们不要走入一个误区,这里的集群升级不单单指的是 Elasticsearch 版本的升级,实际上操作系统和硬件系统升级,这些都可以认为是集群升级,这些操作都需要集群机器重启。
我们都知道重启节点有可能会带来分片重新分配操作,那么如果重新分配的数据量比较大的话,集群负担会非常大。所以对于集群升级我们一般有两种选择。
停机升级
顾名思义,把 Elasticsearch 集群所有节点全部停掉,然后该升级版本升级版本,该升级系统升级系统,这些都升级完之后,重启集群就可以了。这种方式固然简单好用,但弊端大家都知道,离线停机是生产上所不允许的。
在线升级
生产上在线升级是我们常用的解决方案,Elasticsearch 可以实现零停机在线升级。下面以升级 Elasticsearch 版本详细介绍:
1. 禁用集群分片分配操作,如果可能最好同时停掉数据的写入和修改请求。
PUT _cluster/settings
{
"persistent": {
"cluster.routing.allocation.enable": "none"
}
}
2. 停止一个节点服务
sudo systemctl stop elasticsearch.service
3. 执行升级操作
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v6.4.2/elasticsearch-analysis-pinyin-6.4.2.zip
注意:
4. 删除插件
../bin/elasticsearch-plugin remove analysis-ik
5. 安装新插件
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.2/elasticsearch-analysis-ik-6.4.2.zip
6. 启动服务
sudo systemctl start elasticsearch.service
7. 开启分片分配
PUT /_cluster/settings
{
"transient" : {
"cluster.routing.allocation.enable" : "all"
}
}
8. 至此单个节点升级完毕,等待集群恢复绿色之后,重复上述步骤,以完成所有节点升级。
本章小结
本章我们主要介绍了 Elasticsearch 的升级操作,分停机和在线两种方式。对于本章内容如果有疑问,可以在读者圈交流。
大家都知道,Mapping 里的信息一旦创建是不能更改的,只能是重新建立索引指定新 Mapping。但是在线上重新建索引,对业务是有影响的,不过我们可以通过索引别名也解决这个问题。
索引别名
生产上一般是不直接指定索引库名进行查询的,局限性非常大。会使用别名进行检索。
别名好处:
关于索引别名不是本章重点,由于篇幅原因就不过多介绍了,后续出文章的话涉及到这块会详细介绍。
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/indices-aliases.html
reindex
有了别名之后我们一般都是指定别名进行查询操作(别名只能用来查询),这个时候如果我们需要对某个索引的 mapping 进行修改,就要使用 reindex 来新建索引,并且通过别名机制不影响线上业务操作。
举例:
我们有个 alias:apple,有一个索引库 apple1,API 查询 apple1 索引库指定别名 apple 进行查询。
1. 设置 apple1 的别名为 apple
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "apple1", "alias" : "apple" } }
]
}
2. 建立新 Mapping 的索引库或者模版,创建新索引库 apple2
3. 使用 reindex 接口复制数据到新的索引库
POST _reindex
{
"source": {
"index": "apple1"
},
"dest": {
"index": "apple2"
}
}
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/docs-reindex.html
4. 设置 apple2 的别名也为 apple
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "apple2", "alias" : "apple" } }
]
}
注意:在第 2 步建立索引时可直接指定 alias,但建议 reindex 之后再进行别名操作,因为一旦别名指向 apple 之后,业务查询的是 apple,这个时候 apple 下是同时有 apple1 和 apple2 两个索引库,会造成数据混乱。
5. 删除 apple1 索引库。
本章小结
本章我们介绍了如何使用 reindex 来复制索引数据到新的索引中,并且通过使用别名来实现线上业务的无缝衔接。关于本章如有疑问,可以在读者圈交流。
Elasticsearch 的监控工具目前有很多,列举常用的几款:
主流的就这些,其他的还有很多就不一一列举了。head 是一款比较早期的工具,功能基本满足需求,就是页面太糙了。
本章小结
最后一章了,本章介绍了一些主流的监控工具。强烈建议 cerebro、x-pack。
文章到这里就结束了。言尽于此,要说的还有很多,Elasticsearch 的知识还有很多很多。篇幅有限,也写了两天时间了,说是 5000 字的文章写了 14000 字。小弟不才,有哪些没能令您满意的,希望多多指点,您的支持就是我的动力。后续可能还会写一些深度的东西,像原理和优化经验这些。这篇文章比较浅啦。
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
拓展阅读:《高可用 Elasticsearch 集群 21 讲》。
150
互动评论
评论
Louis1 年前
希望分享更多ES优化相关的内容
骇1 年前
dynamic=true貌似是可以写入文档吧,并且新曾字段也可以被索引。 dynamic=false是可以写入文档,但新字段不能被索引。 dynamic=strict 才是不能写入文档。 不知道是不是这样呢?
桃花惜春风(作者)1 年前
是的,非常感谢指正,是我笔误了。 dynamic=true,是默认值,会根据新出现的字段修改mapping,文档完整信息全部会写入。但不建议这种方式。 dynamic=false,不会根据新字段修改mapping,但是符合mapping部分字段会被写入,新字段会丢失。同样不建议。 dynamic=strict,禁止修改mapping,同时对不符合mapping的文档会插入失败。建议使用。
查看更多
写点什么...
成为创作者,免费或收费发布内容
Chat 是什么?了解更多
Chat 是一种全新的阅读/写作互动体验产品。一场 Chat 包含一篇文章和一场为该文章的读者和作者准备的专属线上交流。
关注 GitChat 微信公众号
创建一场 Chat成为专栏作者说出你的需求
加入我们|常见问题|联系客服
京ICP备16004941号-1京公网安备 11010502038640 号