ElasticSearch知识汇总
发布日期:2021-05-06 23:33:02 浏览次数:25 分类:精选文章

本文共 14649 字,大约阅读时间需要 48 分钟。

文章目录

一、简介

Elasticsearch是一个基于Lucene的实时的分布式搜索和分析引擎,基于Java/Lucene构建,可以用于全文搜索,结构化搜索以及近实时分析。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。同时,ElasticSearch也是基于RESTful接口的。比如普通请求是“…get?a=1”,rest请求就是“…get/a/1”,和Android开发中用于网络请求的框架Retrofit一样。

二、ES对比Solr

  • Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能
  • Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch
  • Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用
  • Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式
  • Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供

三、基本概念

在了解ES之前,我们先来熟悉一下它的基本概念!

1.索引(Index)

ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合,类比传统关系型数据库的一个数据库(database),或者一个数据存储方案(schema)。索引由其名称(必须、补习、必须全小写字符!)进行标识,并通过引用此名称完成文档的创建、搜索、更新及删除操作。

2.类型(Type)

类型是索引内部的逻辑分区(category/partition),一个索引内部可定义一个或多个类型(type)。类比传统关系型数据库的一张表。

3.文档(Document)

文档是索引和搜索的原子单位,它是包含了一个或多个域(field)的容器,采用JSON格式表示。文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,类比传统关系型数据库的一条记录。

4.倒排索引(Inverted Index)

每个文档都对应一个ID,倒排索引会按照指定语法对每一个文档进行分词,然后维护一张表,列举所有文档中出现的terms以及它们出现的文档ID和出现频率,它是实现"单词-文档矩阵"的一种具体存储形式。倒排索引主要由两部分组成:“单词词典"和"倒排文件”。

  • 单词词典(Lexicon):单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向"倒排列表"的指针。

  • 倒排列表(PostingList):倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项。

  • 倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。

5. 节点(Node)

单个 Elastic 实例称为一个节点,一组节点构成一个集群。换言之,集群由一个或多个拥有相同cluster.name配置的节点组成,ES集群中的节点有三种不同的类型:

  • 主节点:负责管理集群范围内的所有变更,主节点并不需要涉及到文档级别的变更和搜索等操作,可通过属性node.master进行设置。

  • 数据节点:存储数据和其对应的倒排索引,可通过属性node.data属性进行设置。

  • 协调节点:如果node.master和node.data属性均为false,则此节点称为协调节点,用来响应客户请求,均衡每个节点的负载。

6.分片(Shard)

一个索引中的数据保存在多个分片中,一个分片便是一个Lucene的实例,它本身就是一个完整的搜索引擎。分片是数据的容器,Document保存在分片内,分片又被分配到集群内的各个节点,当集群的规模扩大或缩小时,ES自动在各节点中迁移分片,使得数据均衡分布。一个分片可以是主分片或者从分片(也叫副本分片),索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量,一个从分片只是一个主分片的拷贝,并为搜索和返回文档的读操作提供服务,且从片的数量绝对不能大于节点的数量。

四、ES工作流程

Document交给lucene要过滤、分词,建立倒排索引页。根据content来分词,来源也会来自网络,path要写url。建立完倒排索引后,Document就丢弃不要了。此时要建立新的Document(多了一个属性:id),有些内容必须保存,如:path。有些不必保存,如:content,目的就是为了节约存储空间。新Document和index是相关联的,一个index对应一个Document的id。之后Client查询“中国人”,先找es中的倒排索引,然后就被链接到了新Document,这里面有Path,也可能有content。

单lucene是有问题的,要以集群的形式运行。数据的发送镜像、切片。这里要用切片方式,因为快!一堆doc面对3台es服务器,按照什么原则分配?hash取模,拿document的id来进行hash,模的是服务器的节点数量。放在服务器上的:document+倒排索引index。这里3台服务器,各自都是主节点,谁都不服谁。假若client要查询“husky”,极有可能每个服务器都有这个关键字,这里就得汇聚!

es随机派发给空闲的节点,让它成为“主”。当client的请求来到,master就会发号施令,其余的slave都要服从。然后其他节点向master汇报,经过master向client返回。
lucene做了横向扩展,还要纵向高可用。主负责计算、做倒排索引。从做数据备份,分担主的查询压力。
hash取模是稳定算法,但是如果加了机器,就要涉及到数据迁移。(做假设:可能%槽位吗?不能,redis可以,因为redis存的是静态数据。lucene不是,来一个document就得起一个lucene进程,服务器承受不住)怎么解决?3台服务器可以来10个lucene进程,单节点布置多个lucene。lucene片一旦确立,就改不了了。而且主片和从片不能出现在一个节点上,否则就失去了意义。

五、RESTful

Representational State Transfer,一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

对于es来讲,它认为所有的访问都是在访问资源。
在这里插入图片描述

5.1 REST的操作

  • GET:获取对象的当前状态;
  • PUT:改变对象的状态;
  • POST:创建对象;
  • DELETE:删除对象;
  • HEAD:获取头信息。

创建数据要严格遵守规则,要以Document的形式往里放。命令的执行要在Linux上,因为curl命令是属于Linux的命令。现在我要使用Linux的curl命令,以rest的方式为搜索引擎创建数据。

5.2 ES内置RESTful接口

在这里插入图片描述

5.3 ElasticSearc插件

站点插件(以网页形式展现):

  • BigDesk Plugin (作者 Lukáš Vlček):监控es状态的插件,推荐!
  • Elasticsearch Head Plugin (作者 Ben Birch):很方便对es进行各种操作的客户端。
  • Paramedic Plugin (作者 Karel Minařík):es监控插件
  • SegmentSpy Plugin (作者 Zachary Tong):查看es索引segment状态的插件
  • Inquisitor Plugin (作者 Zachary Tong):这个插件主要用来调试你的查询。

5.4 ES和关系型数据库的数据对比

在这里插入图片描述

六、CURL命令

简单认为是可以在命令行下访问url的一个工具,curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求。

curl :

  • -X 指定http请求的方法
    • HEAD
    • GET
    • POST
    • PUT
    • DELETE
  • -d 指定要传输的数据

6.1 创建索引库

curl -XPUT http://192.168.16.111:9200/husky/

我们可以很清晰的看到,5个主片,5个从片,且主从绝对不会位于同一个节点。

在这里插入图片描述如果把node1上的ES停掉,node2和node3就会处于短暂的选主阶段。node1上面可能散列这node2、node3节点上的某些主从,node1挂了,就意味着node2、node3的主从不全了,于是在剩余两个节点上重新分配(从的数量不能超过节点数)。

6.2 创建Document:-XPOST(支持动态列的增长)

employee表示的是type,-d表示Document,first_name表示的是field,bin表示的是value

curl -XPOST http://192.168.16.111:9200/husky/employee -d '{    "first_name" : "bin", "age" : 33, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ]}'

返回的状态是:

{   "_index":"husky","_type":"employee","_id":"AWt6NtZ-47ZIMoq0QFtP","_version":1,"_shards":{   "total":2,"successful":2,"failed":0},"created":true}

在这里插入图片描述

6.3 -XGET

6.3.1 根据document的id来获取数据(without pretty)

curl -XGET http://192.168.16.111:9200/husky/employee/1?pretty

返回结果:

{     "_index" : "husky",  "_type" : "employee",  "_id" : "1",  "_version" : 1,  "found" : true,  "_source" : {       "first_name" : "god bin",    "last_name" : "pang",    "age" : 42,    "about" : "I love to go rock climbing",    "interests" : [ "sports", "music" ]  }}

6.3.2 根据field来查询数据

[root@node1 ~]# curl -XGET http://192.168.16.111:9200/husky/employee/_search?q=first_name="bin"{   "took":61,"timed_out":false,"_shards":{   "total":5,"successful":5,"failed":0},"hits":{   "total":3,"max_score":0.06356779,"hits":[{   "_index":"husky","_type":"employee","_id":"AWt6QSJS47ZIMoq0QFtQ","_score":0.06356779,"_source":{    "first_name" : "gob bin", "age" : 43, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ]}},{   "_index":"husky","_type":"employee","_id":"AWt6NtZ-47ZIMoq0QFtP","_score":0.030777402,"_source":{    "first_name" : "bin", "age" : 33, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ]}},{   "_index":"husky","_type":"employee","_id":"1","_score":0.024621923,"_source":{    "first_name" : "god bin", "last_name" : "pang", "age" : 42, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ]}}]}}[root@node1 ~]#

6.3.3 根据field来查询数据(match)

curl -XGET http://192.168.16.111:9200/husky/employee/_search?pretty -d '{    "query":  {   "match":   {   "first_name":"bin"}  }}'

返回结果:

{     "took" : 70,  "timed_out" : false,  "_shards" : {       "total" : 5,    "successful" : 5,    "failed" : 0  },  "hits" : {       "total" : 3,    "max_score" : 0.625,    "hits" : [ {         "_index" : "husky",      "_type" : "employee",      "_id" : "AWt6QSJS47ZIMoq0QFtQ",      "_score" : 0.625,      "_source" : {           "first_name" : "gob bin",        "age" : 43,        "about" : "I love to go rock climbing",        "interests" : [ "sports", "music" ]      }    }, {         "_index" : "husky",      "_type" : "employee",      "_id" : "AWt6NtZ-47ZIMoq0QFtP",      "_score" : 0.5945348,      "_source" : {           "first_name" : "bin",        "age" : 33,        "about" : "I love to go rock climbing",        "interests" : [ "sports", "music" ]      }    }, {         "_index" : "husky",      "_type" : "employee",      "_id" : "1",      "_score" : 0.37158427,      "_source" : {           "first_name" : "god bin",        "last_name" : "pang",        "age" : 42,        "about" : "I love to go rock climbing",        "interests" : [ "sports", "music" ]      }    } ]  }}

6.3.4 对多个field发起查询(multi_match)

curl -XGET http://192.168.16.111:9200/husky/employee/_search?pretty -d '{    "query":  {   "multi_match":   {       "query":"bin",    "fields":["last_name","first_name"],    "operator":"and"   }  }}'

6.3.5 多个term对多个field发起查询:bool(boolean)

  • must + must : 交集
  • must +must_not :差集
  • should+should : 并集
6.3.5.1must+must:
curl -XGET http://192.168.16.111:9200/husky/employee/_search?pretty -d '{    "query":  {   "bool" :   {       "must" :      {   "match":      {   "first_name":"bin"}     },    "must" :      {   "match":      {   "age":33}     }   }  }}'
6.3.5.2must+must_not:
curl -XGET http://192.168.16.111:9200/husky/employee/_search?pretty -d '{ "query":  {"bool" :   {    "must" :      {"match":      {"first_name":"bin"}     },    "must_not" :      {"match":      {"age":33}     }   }  }}'
6.3.5.3must_not+must_not:
curl -XGET http://192.168.16.111:9200/husky/employee/_search?pretty -d '{    "query":  {   "bool" :   {       "must_not" :      {   "match":      {   "first_name":"bin"}     },    "must_not" :      {   "match":      {   "age":33}     }   }  }}'
6.3.5.4查询first_name=bin的,或者年龄在20岁到33岁之间的:
curl -XGET http://192.168.16.111:9200/husky/employee/_search -d '{    "query":  {   "bool" :   {      "must" :    {   "term" :      {    "first_name" : "bin" }    }   ,   "must_not" :     {   "range":     {   "age" : {    "from" : 20, "to" : 33 }    }   }   }  }}'

6.4 -XPUT

XPOST和XPUT都能创建和修改,只不过XPOST可不指定id,会分配一个随机id;XPUT必须指定id,如果没有就创建,如果有就修改。

curl -XPUT http://192.168.16.111:9200/husky/employee/1 -d '{    "first_name" : "god bin", "last_name" : "pang", "age" : 42, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ]}'

6.4.1 设置2个从

curl -XPUT 'http://192.168.16.111:9200/test2/' -d'{"settings":{"number_of_replicas":2}}'

在这里插入图片描述

6.4.2 设置3个从

curl -XPUT 'http://192.168.16.111:9200/test3/' -d'{"settings":{"number_of_shards":3,"number_of_replicas":3}}'

节点只有3个,定义了1主3从,第3个从没地方放了。而且,集群健康值变为了yello,意味着不健康了。

在这里插入图片描述

七、核心概念

7.1 cluster

  • 代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。
  • 主节点的职责是负责管理集群状态,包括管理分片的状态和副本的状态,以及节点的发现和删除。

只需要在同一个网段之内启动多个es节点,就可以自动组成一个集群。默认情况下es会自动发现同一网段内的节点,自动组成集群。集群状态查看:

7.2 shards

代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。

可以在创建索引库的时候指定:

curl -XPUT 'localhost:9200/test1/' -d'{"settings":{"number_of_shards":3}}'

默认是一个索引库有5个分片

7.3replicas

代表索引副本,es可以给索引设置副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。

可以在创建索引库的时候指定:

curl -XPUT 'localhost:9200/test2/' -d'{"settings":{"number_of_replicas":2}}'

默认是一个分片有2个副本

7.4 recovery

代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

7.5gateway

代表es索引的持久化存储方式,es默认是先把索引存放到内存中,当内存满了时再持久化到硬盘。当这个es集群关闭再重新启动时就会从gateway中读取索引数据。es支持多种类型的gateway,有本地文件系统(默认),分布式文件系统,Hadoop的HDFS和amazon的s3云存储服务。

如果需要将数据落地到hadoop的hdfs需要先安装插件elasticsearch/elasticsearch-hadoop,然后在elasticsearch.yml配置:

gateway下:

  • type: hdfs
  • hdfs: uri: hdfs://localhost:9000

7.6 discovery.zen

代表es的自动发现节点机制,es是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。

7.7 Transport

代表es内部节点或集群与客户端的交互方式,默认内部是使用tcp协议进行交互,同时它支持http协议(json格式)、thrift、servlet、memcached、zeroMQ等的传输协议(通过插件方式集成)。

7.8 Mapping

就是对索引库中索引的字段名称及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型那样,(和solr中的schme类似)不过es的mapping比数据库灵活很多,它可以动态添加字段。一般不需要要指定mapping都可以,因为es会自动根据数据格式定义它的类型,如果你需要对某些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加mapping。

查询索引库的mapping信息

curl -XGET http://localhost:9200/myindex/emp/_mapping?pretty

mappings修改字段相关属性

例如:字段类型,使用哪种分词工具

八、ElasticSearch的JAVA API

添加maven依赖,连接到es集群

8.1 TransportClient接口

通过TransportClient这个接口,我们可以不启动节点就可以和es集群进行通信,它需要指定es集群中其中一台或多台机的ip地址和端口:

TransportClient client = new TransportClient().addTransportAddress(new InetSocketTransportAddress("host1", 9300)).addTransportAddress(new InetSocketTransportAddress("host2", 9300));

如果需要使用其他名称的集群(默认是elasticsearch),需要如下设置:

Settings settings = ImmutableSettings.settingsBuilder().put("cluster.name", "myClusterName").build();TransportClient client = new TransportClient(settings).addTransportAddress(new InetSocketTransportAddress("host1", 9300));

8.2 TransportClient接口

通过TransportClient这个接口,自动嗅探整个集群的状态,es会自动把集群中其它机器的ip地址加到客户端中

Settings settings = ImmutableSettings.settingsBuilder().put("client.transport.sniff", true).build();TransportClient client = new TransportClient(settings).addTransportAddress(new InetSocketTransportAddress("host1", 9300));

8.3索引index

四种json,map,bean,es helpers

IndexResponse response = client.prepareIndex(“myindex", "emp", "1")                              .setSource().execute().actionGet();

8.4 查询get

GetResponse response = client.prepareGet(“myindex", "emp", "1")                             .execute().actionGet();

8.5 删除delete

类似的更新update、插入upsert

DeleteResponse response = client.prepareDelete(“myindex", "emp", "1")                                .execute().actionGet();

8.6 总数count

long count = client.prepareCount(“myindex").execute().get().getCount();

8.7 es的搜索类型

  • query and fetch(速度最快)(返回N倍数据量)
  • query then fetch(默认的搜索方式)
  • DFS query and fetch(可以更精确控制搜索打分和排名。)
  • DFS query then fetch

从性能考虑QUERY_AND_FETCH是最快的,DFS_QUERY_THEN_FETCH是最慢的。从搜索的准确度来说,DFS要比非DFS的准确度更高。

8.8 Elasticsearch的查询

8.8.1查询query

.setQuery(QueryBuilders.matchQuery("name", "test"))

8.8.2 分页:from/size

.setFrom(0).setSize(1)

8.8.3 排序:sort

.addSort("age", SortOrder.DESC)

8.8.4 排序:sort

.addSort("age", SortOrder.DESC)

8.8.5 过滤:filter

.setPostFilter(FilterBuilders.rangeFilter("age").from(1).to(19))

8.8.6 高亮

highlight

8.9 Elasticsearch分片查询

默认是randomize across shards;随机选取,表示随机的从分片中取数据

  • _local:指查询操作会优先在本地节点有的分片中查询,没有的话再在其它节点查询。
  • _primary:指查询只在主分片中查询
  • _primary_first:指查询会先在主分片中查询,如果主分片找不到(挂了),就会在副本中查询。
  • _only_node:指在指定id的节点里面进行查询,如果该节点只有查询索引的部分分片,就只在这部分分片中查找,所以查询结果可能不完整。如_only_node:123在节点id为123的节点中查询。
  • _prefer_node:nodeid 优先在指定的节点上执行查询
  • _shards:0 ,1,2,3,4:查询指定分片的数据
  • 自定义:_only_nodes:根据多个节点进行查询

九、ElasticSearch的脑裂问题

所谓脑裂问题(类似于精神分裂),就是同一个集群中的不同节点,对于集群的状态有了不一样的理解。

discovery.zen.minimum_master_nodes:用于控制选举行为发生的最小集群节点数量。推荐设为大于1的数值,因为只有在2个以上节点的集群中,主节点才是有意义的

正常情况下,集群中的所有的节点,应该对集群中master的选择是一致的,这样获得的状态信息也应该是一致的,不一致的状态信息,说明不同的节点对master节点的选择出现了异常——也就是所谓的脑裂问题。这样的脑裂状态直接让节点失去了集群的正确状态,导致集群不能正常工作。

9.1 造成脑裂问题的原因

  • 1.网络:由于是内网通信,网络通信问题造成某些节点认为master死掉,而另选master的可能性较小

  • 2.节点负载:由于master节点与data节点都是混合在一起的,所以当工作节点的负载较大时,导致对应的ES实例停止响应,而这台服务器如果正充当着master节点的身份,那么一部分节点就会认为这个master节点失效了,故重新选举新的节点,这时就出现了脑裂;同时由于data节点上ES进程占用的内存较大,较大规模的内存回收操作也能造成ES进程失去响应。

9.3 脑裂问题的解决

主节点

  • node.master: true
  • node.data: false

从节点

  • node.master: false
  • node.data: true

所有节点

  • discovery.zen.ping.multicast.enabled: false
  • discovery.zen.ping.unicast.hosts: [“master”, “slave1”, “slave2"]

十、 ElasticSearch的优化

1.调大系统的"最大打开文件数",建议32K甚至是64K

  • ulimit -a (查看)
  • ulimit -n 32000(设置)

2.修改配置文件调整ES的JVM内存大小

  • 修改bin/elasticsearch.in.sh中ES_MIN_MEM和ES_MAX_MEM的大小,建议设置一样大,避免频繁的分配内存,根据服务器内存大小,一般分配60%左右(默认256M)
  • 如果使用searchwrapper插件启动es的话则修改bin/service/elasticsearch.conf(默认1024M)

3.设置mlockall来锁定进程的物理内存地址

  • 避免交换(swapped)来提高性能
  • 修改文件conf/elasticsearch.yml
  • boostrap.mlockall: true

4.分片多的话,可以提升建立索引的能力,5-20个比较合适。如果分片数过少或过多,都会导致检索比较慢。分片数过多会导致检索时打开比较多的文件,另外也会导致多台服务器之间通讯。而分片数过少会导至单个分片索引过大,所以检索速度慢。建议单个分片最多存储20G左右的索引数据,所以,分片数量=数据总量/20G

5.副本多的话,可以提升搜索的能力,但是如果设置很多副本的话也会对服务器造成额外的压力,因为需要同步数据。所以建议设置2-3个即可。

6.要定时对索引进行优化,不然segment越多,查询的性能就越差。

7.索引量不是很大的话情况下可以将segment设置为1

  • curl -XPOST ‘’
  • java代码:client.admin().indices().prepareOptimize(“myindex").setMaxNumSegments(1).get();

8.删除文档,在Lucene中删除文档,数据不会马上在硬盘上除去,而是在lucene索引中产生一个.del的文件,而在检索过程中这部分数据也会参与检索,lucene在检索过程会判断是否删除了,如果删除了在过滤掉。这样也会降低检索效率。所以可以执行清除删除文档

curl -XPOST ‘’
client.admin().indices().prepareOptimize(" elasticsearch ").setOnlyExpungeDeletes(true).get();

9.如果在项目开始的时候需要批量入库大量数据的话,建议将副本数设置为0。因为es在索引数据的时候,如果有副本存在,数据也会马上同步到副本中,这样会对es增加压力。待索引完成后将副本按需要改回来。这样可以提高索引效率

10.去掉mapping中_all域,Index中默认会有_all的域,(相当于solr配置文件中的拷贝字段text),这个会给查询带来方便,但是会增加索引时间和索引尺寸

“_all”:{“enabled”:“false”}

11.log输出的水平默认为trace,即查询超过500ms即为慢查询,就要打印日志,造成cpu和mem,io负载很高。把log输出水平改为info,可以减轻服务器的压力。修改ES_HOME/conf/logging.yaml文件或者修改ES_HOME/conf/elasticsearch.yaml

12.使用反射获取Elasticsearch客户端,这种方式效率明显高于new客户端,并可避免线上环境内存溢出和超时等问题

上一篇:Storm知识总结
下一篇:ContentProvider使用场景解读

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月12日 21时18分20秒