这篇文章主要介绍ElasticSearch客户端,包括Transport客户端、Jest客户端和Spring Data ElasticSearch,首先来看几个基本概念。
官方的介绍:
ElasticSearch是一个分布式的、基于Json的检索引擎,具有水平扩展、高可靠性和易于管理等优点。
为了更好的理解ealsticsearch的作用,我们可以看下Github的搜索页面:
我们在输入框输入一个单词,会列出许多查询结果,搜索引擎和数据库的区别就是相关性,
我们可以看到elasticsearch的项目排在第一位,很有可能别人在搜索这个关键词时想要访问这个项目。对于不同的应用影响排名的因素可能各不一样。
如果你想建立一个像这样的搜索引擎,首先你的安装elasticsearch,elasticsearch很容易安装,你只需要先安装好Java虚拟机就可以了。
ElasticSearch默认绑定9200端口,你可以通过http://localhost:9200来访问,你也可以用命令行客户端来执行HTTP请求,这里我用curl来执行
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.0.0.zip
unzip elasticsearch-5.0.0.zip
elasticsearch-5.0.0/bin/elasticsearch
curl -XGET "http://localhost:9200"
你会收到一个Json Document响应,这个响应包含ElasticSearch的安装信息,如下所示:
{
"name" : "LI8ZN-t",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "UvbMAoJ8TieUqugCGw7Xrw",
"version" : {
"number" : "5.0.0",
"build_hash" : "253032b",
"build_date" : "2016-10-26T04:37:51.531Z",
"build_snapshot" : false,
"lucene_version" : "6.2.0"
},
"tagline" : "You Know, for Search"
}
最重要的信息就是服务器已经启动成功,还包括ElasticSearch和Lucene的版本信息,Lucene是整个搜索功能的核心库。
现在我们开始给ElasticSearch发送一个Json文档来存储数据,这时候我们要用POST请求,这里我就以一个食物搜索系统为例来讲解怎么添加数据到索引。
curl -XPOST "http://localhost:9200/food/dish" -d'
{
"food": "Hainanese Chicken Rice",
"tags": ["chicken", "rice"],
"favorite": {
"location": "Tian Tian",
"price": 5.00
}
}'
我们还是用之前的端口,这时候我们加了两个字段在URL后面,food和dish,第一个是索引(index)的名称,这是所有文档的集合,第二个是类型(type),相当于关系型数据库里的表。
Dish用文档来建模,Elasticsearch支持多种数据类型,比如string,boolean,数值类型numerics以及嵌套的数据类型,比如上面的favorite。
接下来我们再通过一个POST请求添加一个文档:
curl -XPOST "http://localhost:9200/food/dish" -d'
{
"food": "Ayam Penyet",
"tags": ["chicken", "indonesian"],
"spicy": true
}'
这一次文档的结构有啥不同,它没有包含faorite元素,但添加了另一个spicy属性。同个类型的文档可能差异很大。
建立索引之后,我们就可以来搜索关键字了。我们可以在链接的后面加一个_search,然后添加一个查询参数:
curl -XGET "http://localhost:9200/food/dish/_search?q=chicken"
这个请求在dish类型里搜索包含chicken字段的文档,结果如下:
{"took":57,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":2,"max_score":0.3666863,"hits":[{"_index":"food","_type":"dish","_id":"AVq9cnkMZAUVR2HS07Sa","_score":0.3666863,"_source":
{
"food": "Hainanese Chicken Rice",
"tags": ["chicken", "rice"],
"favorite": {
"location": "Tian Tian",
"price": 5.00
}
}},{"_index":"food","_type":"dish","_id":"AVq9cqoiZAUVR2HS07Sb","_score":0.2876821,"_source":
{
"food": "Ayam Penyet",
"tags": ["chicken", "indonesian"],
"spicy": true
}}]}}
搜索结果包含了找到文档的数量,最重要的属性是hits数组,这里面包含索引到的source原始数据。Elasticsearch提供基于JSON结构的查询DSL,如下所示:
curl -XPOST "http://localhost:9200/food/dish/_search" -d'
{
"query": {
"bool": {
"must": {
"match": {
"_all": "rice"
}
},
"filter": {
"term": {
"tags.keyword": "chicken"
}
}
}
}
}'
上面这个JSON是在搜索包含rice字段以及tags字段里有chicken的文档,elasticsearch 5.0提供了.keyword字段来精确查询。
到目前为止我们只访问了单个ElasticSearch实例,ElasticSearch具有水平伸缩的特性,我们可以添加更多的node,我们仍然可以连接第一个node,它可以将请求转发到集群中的其他节点。
Transport客户端从ElasticSearch第一个版本就有了,也是使用最广泛的客户端,使用它需要在你的构建工具中添加依赖,我这里用的Gradle:
dependencies {
compile group: 'org.elasticsearch.client',
name: 'transport',
version: '5.0.0'
}
通过客户端你可以使用ElasticSearch的所有功能,你可以通过Settings对象来初始化一个TransportClient实例,你可以绑定多个节点的地址:
TransportAddress address =
new InetSocketTransportAddress(
InetAddress.getByName("localhost"), 9300);
Client client = new PreBuiltTransportClient(Settings.EMPTY)
addTransportAddress(address);
对于之前的那条查询:
curl -XPOST "http://localhost:9200/food/dish/_search" -d'
{
"query": {
"bool": {
"must": {
"match": {
"_all": "rice"
}
},
"filter": {
"term": {
"tags.keyword": "chicken"
}
}
}
}
}'
我们将它转换成Java代码:
SearchResponse searchResponse = client
.prepareSearch("food")
.setQuery(
boolQuery().
must(matchQuery("_all", "rice")).
filter(termQuery("tags.keyword", "chicken")))
.execute().actionGet();
assertEquals(1, searchResponse.getHits().getTotalHits());
SearchHit hit = searchResponse.getHits().getAt(0);
String food = hit.getSource().get("food").toString();
我这里调用prepareSearch来请求SearchSourceBuilder,然后我们可以通过静态帮助方法来设置一个查询,上面这个例子用的是bool查询,这个查询的must节点有个match查询,filter节点有个term查询。
调用execute方法会返回一个future对象,actionGet是个阻塞调用,SearchResponse的结果和我们用HTTP访问的结果是等价的。
我们可以用不同的方法来添加索引,其中之一就是使用jsonBuilder来创建一个Json表达式:
XContentBuilder builder = jsonBuilder()
.startObject()
.field("food", "Roti Prata")
.array("tags", new String [] {"curry"})
.startObject("favorite")
.field("location", "Tiong Bahru")
.field("price", 2.00)
.endObject()
.endObject();
上面的表达式使用不同的method来创建JSON文档,这个文档可以作为IndexRequest的source:
IndexResponse resp = client.prepareIndex("food","dish")
.setSource(builder)
.execute()
.actionGet();
除了使用jsonBuilder我们还可以用其他的选项,如下所示:
对于比较简单的数据结构你可以使用Map来构造数据,可以结合序列化插件入jackson来完成对象的序列化。
从前面的例子我们可以看到Transport客户端接收一个或多个节点的地址,你可能注意到这里用的是9300而不是9200端口,因为Transport客户端不使用HTTP来通信,它内部通过传输层协议来通信。
前面只连接了一个节点,一旦这个节点挂了我们就不能访问数据了,如果你需要高可用性你可以启用sniffing选项来允许你的客户端连接集群中的其他节点,只需要把client.transport.sniff选项为true即可。
TransportAddress address =
new InetSocketTransportAddress(
InetAddress.getByName("localhost"), 9300);
Settings settings = Settings.builder()
.put("client.transport.sniff", true)
.build();
Client client = new PreBuiltTransportClient(settings)
addTransportAddress(address);
更多关于sniffing特性可以参考elasticsearch的官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html
jest客户端能够发送请求给ElasticSearch,首先添加依赖:
dependencies {
compile group: 'io.searchbox',
name: 'jest',
version: '2.0.0'
}
我们可以用过工厂方法来创建一个JestClient:
JestClientFactory factory = new JestClientFactory();
factory.setHttpClientConfig(new HttpClientConfig
.Builder("http://localhost:9200")
.multiThreaded(true)
.build());
JestClient client = factory.getObject();
和普通的Rest客户端一样,Jest不支持生成查询,你可以使用string模板或者复用ElasticSearch builders,builder可以用来创建搜索请求:
String query = jsonStringThatMagicallyAppears;
Search search = new Search.Builder(query)
.addIndex("library")
.build();
SearchResult result = client.execute(search);
assertEquals(Integer.valueOf(1), result.getTotal());
获取查询结果:
JsonObject jsonObject = result.getJsonObject();
JsonObject hitsObj = jsonObject.getAsJsonObject("hits");
JsonArray hits = hitsObj.getAsJsonArray("hits");
JsonObject hit = hits.get(0).getAsJsonObject();
// ... more boring code
上面这个不是正常使用Jest的方法,Jest支持搜索和索引Java Bean,比如说我用下面这个Java Bean来表示Dish类:
public class Dish {
private String food;
private List<String> tags;
private Favorite favorite;
@JestId
private String id;
// ... getters and setters
}
可以自动将查询结果转换成Dish对象:
Dish dish = result.getFirstHit(Dish.class).source;
assertEquals("Roti Prata", dish.getFood());
当通过HTTP来访问ElasticSearch时Jest是个不错的选择。
Spring Data项目提供了通用的编程模型来访问不同的数据源,吸引人的特性是Spring Data允许你使用接口来定义查询,比如比较流行的用来访问关系型数据库的Spring Data JPA以及Spring Data MongoDB。
首先添加依赖:
dependencies {
compile group: 'org.springframework.data',
name: 'spring-data-elasticsearch',
version: '2.0.4.RELEASE'
}
使用自定义注解来表示要索引的文档:
@Document(indexName = "spring_dish")
public class Dish {
@Id
private String id;
private String food;
private List<String> tags;
private Favorite favorite;
// more code
}
我们可以定义一个接口来访问文档,这里使用ElasticsearchCrudRepository,它提供通用的索引和查询操作:
public interface DishRepository
extends ElasticsearchCrudRepository<Dish, String> {
}
Spring Data ElasticSearch支持XML配置:
<elasticsearch:transport-client id="client" />
<bean name="elasticsearchTemplate"
class="o.s.d.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
<elasticsearch:repositories
base-package="ezlippi.elasticsearch.springdata" />
transport-client用于实例化transport客户端,elasticsearchTemplate提供访问ElasticSearch的通用操作,最后repositories元素告诉Spring Data扫描继承自Spring Data接口的接口,Spring会自动为这些接口创建实例。
接下来就可以在Java代码中使用repository来执行索引操作:
Dish mie = new Dish();
mie.setId("hokkien-prawn-mie");
mie.setFood("Hokkien Prawn Mie");
mie.setTags(Arrays.asList("noodles", "prawn"));
repository.save(Arrays.asList(hokkienPrawnMie));
// one line ommited
Iterable<Dish> dishes = repository.findAll();
Dish dish = repository.findOne("hokkien-prawn-mie");
根据ID来查询文档没什么意思,你可以添加更多方法到你的接口里:
public interface DishRepository
extends ElasticsearchCrudRepository<Dish, String> {
List<Dish> findByFood(String food);
List<Dish> findByTagsAndFavoriteLocation(String tag, String location);
List<Dish> findByFavoritePriceLessThan(Double price);
@Query("{\"query\": {\"match_all\": {}}}")
List<Dish> customFindAll();
}
大部分方法都是以findBy+属性名开头,比如findByFood会查询food属性,结构化查询也是支持的,比如上面的lessThan,上面这个接口会查询比给定价格少的dishes,最后这个查询使用不同的方式,可以使用Query注解来添加查询。
更多关于Spring Data ElasticSearch的知识可以参考Spring的文档:http://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#project
上面介绍了几种常用的ElasticSearch Java客户端,可以针对实际使用场景来选择。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。