es搜索引擎
1、es简介
2、es优缺点
3、es使用
4、es可以解决的问题
5、es举例
6、es执行结果截图
7、es数据增量方案
8、使用es搜索
一、es简介
es是一个是一个实时的分布式搜索和分析引擎。它可以帮助你用前所未有的速度去处理大规模数据。
它可以用于全文搜索,结构化搜索以及分析,当然你也可以将这三者进行组合。
es是一个建立在全文搜索引擎 Apache Lucene™ 基础上的搜索引擎,可以说Lucene是当今最先进,最高效的全功能开源搜索引擎框架。
es使用Lucene作为内部引擎,但是在使用它做全文搜索时,只需要使用统一开发好的API即可,而不需要了解其背后复杂的Lucene的运行原理。
es除了做全索引外,还可以做如下工作:
分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
实时分析的分布式搜索引擎。
可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
以上功能可以通过你喜欢的编程语言或客户端与es的restful api进行通讯。
二、概念说了半天了,说下优缺点吧
优点:
1、es是分布式的,不需要其它组件,分发是实时由es主节点内部自动完成的。
2、处理多组用户,而不需特殊配置。
3、es擦用gateway的概念,(gateway:网关是网络连接设备的重要组成部分,它不仅具有路由的功能,而且能在两个不同的协议集之间进行转换,从而使不同的网络之间进行互联。例如:一个Netware局域网通过网关可以访问IBM的SNA网络,这样使用IPX协议的PC就可和SNA网络上的IBM主机进行通信。)是得备份更简单。
4、es节点发生故障时,可以进行自动分配其它节点替代。
缺点:
1、文档太少,不易维护。
2、目前觉得,建索引的速度不够快,期待有更好的方法。
三、es使用(包括创建和搜索以及关闭)
es的获取和关闭方法:
package com.elasticsearch.config;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
/**
* 初始化连接es服务端,这里相当于dao层..
*
* @author:jackkang
*
* 2013-1-12 下午11:27:37
*/
public class InitES {
static Log log = LogFactory.getLog(InitES.class);
/**
* 静态,单例...
*/
private static TransportClient client;
public static TransportClient initESClient() {
try {
if (client == null) {
// 配置你的es,现在这里只配置了集群的名,默认是elasticsearch,跟服务器的相同
Settings settings = ImmutableSettings
.settingsBuilder()
.put("cluster.name", "elasticsearch")
.put("discovery.type", "zen")//发现集群方式
.put("discovery.zen.minimum_master_nodes", 2)//最少有2个master存在
.put("discovery.zen.ping_timeout", "200ms")//集群ping时间,太小可能会因为网络通信而导致不能发现集群
.put("discovery.initial_state_timeout", "500ms")
.put("gateway.type", "local")//(fs, none, local)
.put("index.number_of_shards", 1)
.put("action.auto_create_index", false)//配置是否自动创建索引
.put("cluster.routing.schedule", "50ms")//发现新节点时间
.build();
// 从属性文件中获取搜索服务器相对域地址
String transportAddresses = Config.getProperty(
"transportAddresses", "");
// 集群地址配置
List<InetSocketTransportAddress> list = new ArrayList<InetSocketTransportAddress>();
if (StringUtils.isNotEmpty(transportAddresses)) {
String[] strArr = transportAddresses.split(",");
for (String str : strArr) {
String[] addressAndPort = str.split(":");
String address = addressAndPort[0];
int port = Integer.valueOf(addressAndPort[1]);
InetSocketTransportAddress inetSocketTransportAddress = new InetSocketTransportAddress(
address, port);
list.add(inetSocketTransportAddress);
}
}
// 这里可以同时连接集群的服务器,可以多个,并且连接服务是可访问的
InetSocketTransportAddress addressList[] = (InetSocketTransportAddress[]) list
.toArray(new InetSocketTransportAddress[list.size()]);
// Object addressList[]=(Object [])list.toArray();
client = new TransportClient(settings)
.addTransportAddresses(addressList);
// 这里可以同时连接集群的服务器,可以多个,并且连接服务是可访问的 192.168.1.102
// client = new TransportClient(settings).addTransportAddresses(
// new InetSocketTransportAddress("192.168.1.103", 9300));
//
// Client client = new TransportClient()
// .addTransportAddress(new
// InetSocketTransportAddress("192.168.0.149", 9300))
// .addTransportAddress(new
// InetSocketTransportAddress("192.168.0.162", 9300));
// 改变shards数目:
/*client.admin().indices().prepareUpdateSettings("test")
.setSettings(ImmutableSettings.settingsBuilder().put("index.number_of_replicas", 2)).execute().actionGet();*/
}
} catch (Exception e) {
// if (log.isDebugEnabled()) {
// log.debug("方法AppCommentAction-deleteAppComment,参数信息:commentid" );
// }
log.error("获取客户端对象异常:" + e.getMessage());
}
return client;
}
public static void closeESClient() {
if (client != null) {
client.close();
}
}
}
搜索:
package com.elasticsearch.action;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import com.elasticsearch.config.ElasticsearchUtil;
import com.elasticsearch.pojo.Pager;
import com.opensymphony.xwork2.ActionSupport;
public class SearchAction extends ActionSupport {
private static final long serialVersionUID = 1L;
/** 关键字 **/
private String wd;
/** 消耗时间 **/
private double spendTime;
/** 查询结果集对象 **/
private List<Map<String, Object>> pageList = new ArrayList<Map<String, Object>>();
/** 分页对象 **/
private Pager pager;
/** 总记录数 使用静态变量的方式缓存 **/
private Long total;
private SearchResponse response;
/**
* 条件检索action
*
* @throws MalformedURLException
* @throws SolrServerException
* @throws UnsupportedEncodingException
**/
public String search() throws MalformedURLException,
UnsupportedEncodingException {
/** 检索开始时间 **/
long startTime = System.currentTimeMillis();
/** 获取页面封装好的分页对象 **/
if (pager == null) {
pager = new Pager();
pager.setMaxPageItems(10);
}
wd = new String(wd.getBytes("ISO-8859-1"), "UTF-8"); // 解决乱码
pager.setDefaultMaxPageItems(1);
/**高亮字段**/
String[] highFields=new String[]{"content","title"};
response = ElasticsearchUtil.searcher("medcl", "news",
pager.getOffset(), pager.getMaxPageItems(), wd,highFields);
/** 总记录数 **/
total = response.getHits().totalHits();
System.out.println("命中总数:" + total);
SearchHits searchHits = response.getHits();
SearchHit[] hits = searchHits.getHits();
for (int i = 0; i < hits.length; i++) {
Map<String, Object> map = new HashMap<String, Object>();
SearchHit hit = hits[i];
String id=hit.getId();
String content = ElasticsearchUtil.getHighlightFields(hit,"content");
String title = ElasticsearchUtil.getHighlightFields(hit,"title");
map.put("id", hit.getSource().get("id"));
map.put("content", content);
map.put("title", title);
map.put("create_time", hit.getSource().get("create_time"));
map.put("links", hit.getSource().get("link"));
pageList.add(map);
}
/** 检索完成时间 **/
long endTime = System.currentTimeMillis();
/** 检索花费时间 **/
//spendTime = (double) (endTime - startTime) / 1000;
Calendar c = Calendar.getInstance();
c.setTimeInMillis(endTime - startTime);
spendTime = c.get(Calendar.MILLISECOND);
return SUCCESS;
}
public static String Html2Text(String inputString) {
String htmlStr = inputString; // 含html标签的字符串
String textStr = "";
java.util.regex.Pattern p_script;
java.util.regex.Matcher m_script;
java.util.regex.Pattern p_style;
java.util.regex.Matcher m_style;
java.util.regex.Pattern p_html;
java.util.regex.Matcher m_html;
try {
String regEx_script = "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>"; // 定义script的正则表达式{或<script[^>]*?>[\\s\\S]*?<\\/script>
// }
String regEx_style = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>"; // 定义style的正则表达式{或<style[^>]*?>[\\s\\S]*?<\\/style>
// }
String regEx_html = "<[^>]+>"; // 定义HTML标签的正则表达式
p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE);
m_script = p_script.matcher(htmlStr);
htmlStr = m_script.replaceAll(""); // 过滤script标签
p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE);
m_style = p_style.matcher(htmlStr);
htmlStr = m_style.replaceAll(""); // 过滤style标签
p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
m_html = p_html.matcher(htmlStr);
htmlStr = m_html.replaceAll(""); // 过滤html标签
textStr = htmlStr;
} catch (Exception e) {
System.err.println("Html2Text: " + e.getMessage());
}
return textStr;// 返回文本字符串
}
public String getWd() {
return wd;
}
public void setWd(String wd) {
this.wd = wd;
}
public double getSpendTime() {
return spendTime;
}
public void setSpendTime(double spendTime) {
this.spendTime = spendTime;
}
public List<Map<String, Object>> getPageList() {
return pageList;
}
public void setPageList(List<Map<String, Object>> pageList) {
this.pageList = pageList;
}
public Pager getPager() {
return pager;
}
public void setPager(Pager pager) {
this.pager = pager;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
}
四、可以决绝基金的问题
随着基金系统的逐渐完善,数据量的增加,使用es可以缓解检索数据给数据库带来的压力。
比如,基金中报销的病例列表,报销记录,消费记录等
五、es举例
es使用java链接,创建mapping,保存数据
demo的javabean
package com.elasticsearch;
import com.google.common.collect.Lists;
import com.util.date.Joda_Time;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import java.io.IOException;
import java.util.Date;
import java.util.List;
/**
* javabean
*/
public class User {
private String name;
private String home;//家乡
private double height;//身高
private int age;
private Date birthday;
public User() {
}
public User(String name, String home, double height, int age, Date birthday) {
this.name = name;
this.home = home;
this.height = height;
this.age = age;
this.birthday = birthday;
}
/**
* 随机生成一个用户信息
*
* @return
*/
public static User getOneRandomUser() {
return new User("葫芦" + (int) (Math.random() * 1000) + "娃", "山西省太原市" + (int) (Math.random() * 1000) + "街道", (Math.random() * 1000), (int) (Math.random() * 100), new Date(System.currentTimeMillis() - (long) (Math.random() * 100000)));
}
/**
* 随机生成num个用户信息
*
* @param num 生成数量
* @return
*/
public static List<User> getRandomUsers(int num) {
List<User> users = Lists.newArrayList();
if (num < 0) num = 10;
for (int i = 0; i < num; i++) {
users.add(new User("葫芦" + (int) (Math.random() * 1000) + "娃", "山西省太原市" + (int) (Math.random() * 1000) + "街道", (Math.random() * 1000), (int) (Math.random() * 100), new Date(System.currentTimeMillis() - (long) (Math.random() * 100000))));
}
return users;
}
/**
* 封装对象的Json信息
*
* @param user
* @return
* @throws IOException
*/
public static XContentBuilder getXContentBuilder(User user) throws IOException {
return XContentFactory.jsonBuilder()
.startObject()
.field("name", user.getName())//该字段在上面的方法中mapping定义了,所以该字段就有了自定义的属性,比如 age等
.field("home", user.getHome())
.field("height", user.getHeight())
.field("age", user.getAge())
.field("birthday", user.getBirthday())
.field("state", "默认属性,mapping中没有定义")//该字段在上面方法中的mapping中没有定义,所以该字段的属性使用es默认的.
.endObject();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHome() {
return home;
}
public void setHome(String home) {
this.home = home;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
2、java与es交互demo
package com.framework_technology.elasticsearch;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistRequestBuilder;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.stats.IndexStats;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* mapping创建
* 添加记录到es
*/
public class Es_BuildIndex {
/**
* 索引的mapping
* <p>
* 预定义一个索引的mapping,使用mapping的好处是可以个性的设置某个字段等的属性
* Es_Setting.INDEX_DEMO_01类似于数据库
* mapping 类似于预设某个表的字段类型
* <p>
* Mapping,就是对索引库中索引的字段名及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型那样,
* 不过es的 mapping比数据库灵活很多,它可以动态添加字段。
* 一般不需要要指定mapping都可以,因为es会自动根据数据格式定义它的类型,
* 如果你需要对某 些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加mapping。
* 有两种添加mapping的方法,一种是定义在配 置文件中,一种是运行时手动提交mapping,两种选一种就行了。
*
* @throws Exception Exception
*/
protected static void buildIndexMapping() throws Exception {
Map<String, Object> settings = new HashMap<>();
settings.put("number_of_shards", 4);//分片数量
settings.put("number_of_replicas", 0);//复制数量
settings.put("refresh_interval", "10s");//刷新时间
//在本例中主要得注意,ttl及timestamp如何用java ,这些字段的具体含义,请去到es官网查看
CreateIndexRequestBuilder cib = Es_Utils.client.admin().indices().prepareCreate(Es_Utils.LOGSTASH_YYYY_MM_DD);
cib.setSettings(settings);
XContentBuilder mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("we3r")//
.startObject("_ttl")//有了这个设置,就等于在这个给索引的记录增加了失效时间,
//ttl的使用地方如在分布式下,web系统用户登录状态的维护.
.field("enabled", true)//默认的false的
.field("default", "5m")//默认的失效时间,d/h/m/s 即天/小时/分钟/秒
.field("store", "yes")
.field("index", "not_analyzed")
.endObject()
.startObject("_timestamp")//这个字段为时间戳字段.即你添加一条索引记录后,自动给该记录增加个时间字段(记录的创建时间),搜索中可以直接搜索该字段.
.field("enabled", true)
.field("store", "no")
.field("index", "not_analyzed")
.endObject()
//properties下定义的name等等就是属于我们需要的自定义字段了,相当于数据库中的表字段 ,此处相当于创建数据库表
.startObject("properties")
.startObject("@timestamp").field("type", "long").endObject()
.startObject("name").field("type", "string").field("store", "yes").endObject()
.startObject("home").field("type", "string").field("index", "not_analyzed").endObject()
.startObject("now_home").field("type", "string").field("index", "not_analyzed").endObject()
.startObject("height").field("type", "double").endObject()
.startObject("age").field("type", "integer").endObject()
.startObject("birthday").field("type", "date").field("format", "YYYY-MM-dd").endObject()
.startObject("isRealMen").field("type", "boolean").endObject()
.startObject("location").field("lat", "double").field("lon", "double").endObject()
.endObject()
.endObject()
.endObject();
cib.addMapping(Es_Utils.LOGSTASH_YYYY_MM_DD_MAPPING, mapping);
cib.execute().actionGet();
}
/**
* 给 []index 创建别名
* 重载方法可以按照过滤器或者Query 作为一个别名
*
* @param aliases aliases别名
* @param indices 多个 index
* @return 是否完成
*/
protected static boolean createAliases(String aliases, String... indices) {
IndicesAliasesRequestBuilder builder = Es_Utils.client.admin().indices().prepareAliases();
return builder.addAlias(indices, aliases).execute().isDone();
}
/**
* 查询此别名是否存在
*
* @param aliases aliases
* @return 是否存在
*/
protected static boolean aliasesExist(String... aliases) {
AliasesExistRequestBuilder builder =
Es_Utils.client.admin().indices().prepareAliasesExist(aliases);
AliasesExistResponse response = builder.execute().actionGet();
return response.isExists();
}
/**
* 添加记录到es
* <p>
* 增加索引记录
*
* @param user 添加的记录
* @throws Exception Exception
*/
protected static void buildIndex(User user) throws Exception {
// INDEX_DEMO_01_MAPPING为上个方法中定义的索引,prindextype为类型.jk8231为id,以此可以代替memchche来进行数据的缓存
IndexResponse response = Es_Utils.client.prepareIndex(Es_Utils.LOGSTASH_YYYY_MM_DD, Es_Utils.LOGSTASH_YYYY_MM_DD_MAPPING)
.setSource(
User.getXContentBuilder(user)
)
.setTTL(8000)//这样就等于单独设定了该条记录的失效时间,单位是毫秒,必须在mapping中打开_ttl的设置开关
.execute()
.actionGet();
}
/**
* 批量添加记录到索引
*
* @param userList 批量添加数据
* @throws java.io.IOException IOException
*/
protected static void buildBulkIndex(List<User> userList) throws IOException {
BulkRequestBuilder bulkRequest = Es_Utils.client.prepareBulk();
// either use Es_Setting.client#prepare, or use Requests# to directly build index/delete requests
for (User user : userList) {
//通过add批量添加
bulkRequest.add(Es_Utils.client.prepareIndex(Es_Utils.LOGSTASH_YYYY_MM_DD, Es_Utils.LOGSTASH_YYYY_MM_DD_MAPPING)
.setSource(
User.getXContentBuilder(user)
)
);
}
BulkResponse bulkResponse = bulkRequest.execute().actionGet();
//如果失败
if (bulkResponse.hasFailures()) {
// process failures by iterating through each bulk response item
System.out.println("buildFailureMessage:" + bulkResponse.buildFailureMessage());
}
}
}
数据查看、
通过第三方工具查看
七、es数据增量方案
1、定时任务轮训,bulk方式操作
批量添加操作:(可以把以前的数据导进来)
/**
* 批量添加记录到索引
*
* @param userList 批量添加数据
* @throws java.io.IOException IOException
*/
protected static void buildBulkIndex(List<User> userList) throws IOException {
BulkRequestBuilder bulkRequest = InitES.buildClient().prepareBulk();
// either use Es_Setting.client#prepare, or use Requests# to directly build index/delete requests
for (User user : userList) {
//通过add批量添加
bulkRequest.add(InitES.buildClient().prepareIndex(LOGSTASH_YYYY_MM_DD, LOGSTASH_YYYY_MM_DD_MAPPING)
.setSource(
User.getXContentBuilder(user)
)
);
}
BulkResponse bulkResponse = bulkRequest.execute().actionGet();
//如果失败
if (bulkResponse.hasFailures()) {
// process failures by iterating through each bulk response item
System.out.println("buildFailureMessage:" + bulkResponse.buildFailureMessage());
}
}
2、用队列做数据同步,异步的方式,生产一条,放在队列里去消费一条。
增量使用队列,当新增一条记录时往es中添加一条记录
protected static void buildIndex(User user) throws Exception {
// INDEX_DEMO_01_MAPPING为上个方法中定义的索引,prindextype为类型.jk8231为id,以此可以代替memchche来进行数据的缓存
IndexResponse response = InitES.buildClient().prepareIndex(LOGSTASH_YYYY_MM_DD, LOGSTASH_YYYY_MM_DD_MAPPING)
.setSource(
User.getXContentBuilder(user)
)
.setTTL(8000)//这样就等于单独设定了该条记录的失效时间,单位是毫秒,必须在mapping中打开_ttl的设置开关
.execute()
.actionGet();
}
es中的QueryBuilders的termQuery查询,
1. 若value为汉字,则大部分情况下,只能为一个汉字;
2. 若value为英文,则是一个单词;
queryString支持多个中文查询
TYPE:
{"mappings":{"carecustomerlog_type_all":{"properties":{"applyrate":{"type":"double"},"careAccountId":{"type":"long"},"careCustomId":{"type":"string","index":"not_analyzed"},"careaccountid":{"type":"long"},"cdate":{"type":"long"},"content":{"type":"string","index":"not_analyzed"},"customerid":{"type":"string","index":"not_analyzed"},"customername":{"type":"string","index":"not_analyzed"},"id":{"type":"long"},"orderid":{"type":"string","index":"not_analyzed"},"preapplyrate":{"type":"long"},"type":{"type":"long"},"watenum":{"type":"string","index":"not_analyzed"}}},"careCustomerLogByCustomerId":{"properties":{"customerid":{"type":"string","index":"not_analyzed"}}}}}