在 Lucene 全文检索引擎 章中讲解了 Lucene 解决了全文检索的问题,但是Lucene是单机的,如果 并发量上来,处理速度和存储能力都是有问题的,搜索要求的延迟也是很低的
        这就是典型的单点问题,解决单点问题两种方式
                   垂直切分: 用几台相同的数据的机器来做主备切换,但是这样没解决处理速度问题,这几台机器同时提供服务吧,解决机器之间的数据同步问题有很麻烦。
                    水平切分:将数据切分到几个节点上去,不同的节点处理不同的数据,这样又解决内存受限与单点处理能力。(但是这样又有一个新问题,数据倾斜,这里不做讨论,解决办法参考Redis 3.0集群模式 
        讨论发现水平切分更适合解决此问题,如何切分数据呢? 当然是常用的 Hash取模方法,对每个要建立索引的 document 设置一个id,让id的hash值对机器数取模,这时document就被传递到了某一个节点上去了,那接下来的处理不就跟单机的Lucene一样了,每台节点搭建一个lucene,分布式只是外部封装的一层,提交一个document建立索引的过程是交给某一个lucene节点进行的。
        如何查询数据呢? 由于document是根据id的hash取模分发到不同的lucene节点的,所以每个节点的索引文件的是不同的,所以查找一个字符串的是需要在每一台节点都进行的,然后再拉取到一台节点上,做下归并排序合并成一个结果返回。
        这时还可以优化,用户提交一个查询,可不可以随机找一个节点处理这个请求呢?  随机受到请求的这个节点分发任务到其他的节点,然后将结果拉回汇总返回用户,这样就将用户的查询任务近似平均的分发到了不同的节点上。

     


   Elasticsearch 就是根据上面讨论的设计的

Elasticsearch

        Elasticsearch是一个基于Lucene的实时的 分布式  搜索和分析引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便,基于RESTful web接口。



ES VS SOLR


SOLR
ES
接口
类似webservice的接口
REST风格的访问接口
分布式存储
solrCloud  solr4.x才支持
es是为分布式而生的
支持的格式
 solr  xml   json
es  json
搜索延迟
 近实时搜索
 近实时搜索

        Es和solr百度指数对比

        

        Solr和elasticsearch的性能对比

        


Rest简介   Representational State Transfer

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

            
    Rest的操作分为以下几种:
            GET:获取对象的当前状态;
            PUT:改变对象的状态;
            POST:创建对象;
            DELETE:删除对象;
            HEAD:获取头信息。
    ES内置的REST接口:
        

Elasticsearch中的核心概念
    cluster
            代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。
            es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。
            主节点的职责是负责管理集群状态,包括管理分片的状态和副本的状态,以及节点的发现和删除。
            只需要在同一个网段之内启动多个es节点,就可以自动组成一个集群。默认情况下es会自动发现同一网段内的节点,自动组成集群。
    shards
            代表索引分片,es可以把一个完整的索引分成多个分片,
            这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。
            分片的数量只能在索引创建前指定,并且索引创建后不能更改。
                curl -XPUT 'localhost:9200/test1/' -d'{"settings":{"number_of_shards":3}}'
                number_of_shards : 默认是一个索引库有5个分片
    Elasticsearch的分片规则
             elasticsearch在建立索引时,根据id或id,类型进行hash,得到hash值与该索引的文档数量取余,取余的值即为存入的分片。
             具体源码为:根据OperationRouting类的shardId方法进行分片
    replicas
            代表索引副本,es可以给索引设置副本。
            副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。
            二是提高es的查询效率,es会自动对搜索请求进行负载均衡。
            可以在创建索引库的时候指定
                    curl -XPUT 'localhost:9200/test2/' -d'{"settings":{"number_of_replicas":2}}'
                    默认是一个分片有2个副本
    recovery 
            代表数据恢复或叫数据重新分布,
            es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
    gateway
             代表es索引的持久化存储方式,
            es默认是先把索引存放到内存中,当内存满了时再持久化到硬盘。
            当这个es集群关闭再重新启动时就会从gateway中读取索引数据。es支持多种类型的gateway,有本地文件系统(默认),分布式文件系统,Hadoop的HDFS和amazon的s3云存储服务。
                    如果需要将数据落地到hadoop的hdfs需要先安装插件 elasticsearch/elasticsearch-hadoop,然后再elasticsearch.yml配置
                    gateway:
                            hdfs:
                            uri: hdfs://localhost:9000
    discovery.zen
             代表es的自动发现节点机制,es是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。
            如果是不同网段的节点如何组成es集群
            禁用自动发现机制
                discovery.zen.ping.multicast.enabled: false
            设置新节点被启动时能够发现的主节点列表
                discovery.zen.ping.unicast.hosts: [“192.168.27.4", " 192.168.17.5"]
    Transport
             代表es内部节点或集群与客户端的交互方式,默认内部是使用tcp协议进行交互,
            同时它支持http协议(json格式)、thrift、servlet、memcached、zeroMQ等的传输协议(通过插件方式集成)。
    Mapping
             同 lucene中的 document
            就是对索引库中索引的字段名称及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型那样,
            (和solr中的schme类似)不过es的mapping比数据库灵活很多,它可以动态添加字段。一般不需要要指定mapping都可以,因为es会自动根据数据格式定义它的类型。
            如果你需要对某些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加mapping
            查询索引库的mapping信息 
                    curl -XGET http://localhost:9200/bjsxt/emp/_mapping?pretty
            mappings修改字段相关属性,例如:字段类型,使用哪种分词工具

Elasticsearch  环境部署
        java版本要求:最低1.7
        注: Elasticsearch 、 Kibana 的下载地址统一为 https://www.elastic.co/downloads/
                 问题排查可以登录 https://discuss.elastic.co/c 论坛查找相关信息
1.安装Elasticsearch集群  
        注意:
                  因为elasticsearch有远程执行脚本的功能所以容易中木马病毒,所以不允许用root用户启动,root用户是起不来的,赋权限,用一般的用户启动
                  建立新用户
useradd devin
echo "devin" | passwd --stdin devin

                  建立安装文件夹授予权限

mkdir /opt/es
chown -R  devin:devin /opt/es
chmod 777 /opt/es
su devin
                下载elasticsearch-2.0.0.tar.gz,执行tar -zxvf elasticsearch-2.0.0.tar.gz解压
                修改配置文件 config/elasticsearch.yml  ——注意配置yml结尾的配置文件都需要冒号后面加空格才行
                
                如果要配置集群需要两个节点上的elasticsearch配置的cluster.name相同,都启动可以自动组成集群,这里如果不改cluster.name则默认是cluster.name=elasticsearch,nodename随意取但是集群内的各节点不能相同。
                添加防脑裂配置,配置中只指定cluster.name,当一个机架上节点不能与另外一个机架上的节点通讯时,会形成两个集群
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping_timeout: 120s
client.transport.ping_timeout: 60s
discovery.zen.ping.unicast.hosts: ["192.168.57.4","192.168.57.5", "192.168.57.6"]
                 要配置 network.host才能别的机器或者网卡访问,否则只能是127.0.0.1或者localhost访问,这里配置成自己的局域网ip
                 最后同时启动可以组成集群
su devin
cd /opt/es/elasticsearch-2.2.0
./bin/elasticsearch
bin/elasticsearch -d  # 后台运行

2.安装插件

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)
简介:这个插件主要用来调试你的查询。

    安装head插件:

bin/plugin  install   mobz/elasticsearch-head

     如果你发现下不下来,那可以复制错误信息中的地址,在浏览器中下载下来后,上传到服务器中,

     解压到 ${es_home}/plugins 目录下(没有就新建),并重命名为head。

   整合ik分词器

            从地址 https://github.com/medcl/elasticsearch-analysis-ik 下载elasticsearch中文分词器

        

    这里默认的是master的  但是master的项目需要用gradle编译,这里选择1.8.0版本。而且从下面的介绍可以知道1.8.0正好对应elasticsearch的2.2.0版本    下载后的压缩包解压后进去发现是pom工程 分别执行如下命令:

mvn clean
mvn compile
mvn package

    当然这里是用maven对此工程进行编译,前提要安装maven。

   前面编译了插件以后会在target/releases目录下出现一个zip包

    

    在安装好的elasticsearch中在plugins目录下新建ik目录,将此zip包拷贝到ik目录下

    将权限修改为elasticsearch启动用户的权限,通过unzip命令解压缩

    例如在plugins/ik目录下执行 unzip  elasticsearch-analysis-ik-1.8.0.zip

    解压后查看 得到解压后的结果

    

    每台机器都这样操作,重新启动elasticsearch集群,就安装好了。

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

CURL命令
        简单认为是可以在命令行下访问url的一个工具
        curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求。
            -X   指定http请求的方法 HEAD  GET POST  PUT DELETE
            -d   指定要传输的数据
#创建索引库:
curl -XPUT http://192.168.9.11:9200/bjsxt/

#创建document:
curl -XPOST http://192.168.9.11:9200/bjsxt/employee -d '
{
	"first_name" : "bin",
	"last_name" : "tang",
	"age" : 33,
	"about" : "I love to go rock climbing",
	"interests": [ "sports", "music" ]
}'

#更新document:
curl -XPUT http://192.168.9.11:9200/bjsxt/employee/1 -d '
{
	"first_name" : "bin",
	"last_name" : "pang",
	"age" : 30,
	"about" : "I love to go rock climbing",
	"interests": [ "sports", "music" ]
}'


PUT 和 POST 都是新增,修改。PUT必须指定ID,所以PUT一般数据更新 。POST 可以指定ID,也可以不指定 做新增比较好。

#根据document的id来查询数据:
curl -XGET http://192.168.9.11:9200/bjsxt/employee/1?pretty

#根据field来查询数据:
curl -XGET http://192.168.239.3:9200/bjsxt/employee/_search?q=first_name="bin"

#根据field来查询数据:match
curl -XGET http://192.168.239.3:9200/bjsxt/book/_search -d '
{
	"query":
		{"match":
			{"name":"hadoop"}
		}
}'



#对多个field发起查询:multi_match
curl -XGET http://192.168.239.3:9200/bjsxt/employee/_search -d '
{
	"query":
		{"multi_match":
			{
				"query":"John",
				"fields":["last_name","first_name"],
				"operator":"or"
			}
		}
}'


#多个term对多个field发起查询:bool(boolean) 
#	组合查询,must,must_not,should 
#		must + must : 交集
#		must +must_not :差集
#		should+should  : 并集

curl -XGET http://192.168.239.3:9200/bjsxt/employee/_search -d '
{
	"query":
		{"bool" :
			{
				"must" : 
					{"match":
						{"first_name":"bin"}
					},
				"must" : 
					{"match":
						{"age":33}
					}
			}
		}
}'

##查询first_name=bin的,或者年龄在20岁到30岁之间的

curl -XGET http://192.168.239.3:9200/bjsxt/employee/_search -d '
{
	"query":
		{"bool" :
			{
			"must" :
				{"term" : 
					{ "first_name" : "bin" }
				}
			,
			"must_not" : 
				{"range":
					{"age" : { "from" : 20, "to" : 30 }
				}
			}
			}
		}
}'


#修改配置
curl -XPUT 'http://192.168.239.3:9200/test2/' -d'{"settings":{"number_of_replicas":2}}'



curl -XPOST http://192.168.239.3:9200/bjsxt/person/_mapping -d'
{
    "person": {
        "properties": {
            "content": {
                "type": "string",
                "store": "no",
                "term_vector": "with_positions_offsets",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word",
                "include_in_all": "true",
                "boost": 8
            }
        }
    }
}'


ES JAVA API

    添加maven依赖

 连接到es集

        1:通过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));

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

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

索引index(四种json,map,bean,es helpers)

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

查询get

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

 更新update, 更新或者插入upsert

删除delete

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

总数count

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

查询search

        SearchType

        

        总结一下,从性能考虑 QUERY_AND_FETCH 是最快的,DFS_QUERY_THEN_FETCH是最慢的。但从搜索的准确度来说,DFS要比非DFS的准确度更高
        查询:query
 .setQuery(QueryBuilders.matchQuery("name", "test"))
        分页:from/size
.setFrom(0).setSize(1)
        排序:sort
.addSort("age", SortOrder.DESC)
        过滤:filter
.setPostFilter(FilterBuilders.rangeFilter("age").from(1).to(19))
        统计:facet(已废弃)使用aggregations 替代
                根据字段进行分组统计
                根据字段分组,统计其他字段的值
                size设置为0,会获取所有数据,否则,只会返回10条
        Elasticsearch的分页
                与SQL使用LIMIT来控制单“页”数量类似,Elasticsearch使用的是from以及size两个参数:
                size:每次返回多少个结果,默认值为10
                from:从哪条结果开始,默认值为0
                假设每页显示5条结果,那么1至3页的请求就是:
                GET /_search?size=5
                GET /_search?size=5&from=5
                GET /_search?size=5&from=10
                注意:
                       不要一次请求过多或者页码过大的结果,这么会对服务器造成很大的压力。
                       因为它们会在返回前排序。一个请求会经过多个分片。每个分片都会生成自己的排序结果。然后再进行集中整理,以确保最终结果的正确性。
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:根据多个节点进行查询


timed_out
        告诉了我们查询是否超时
            curl -XGET http://localhost:9200/_search?timeout=10ms
        es会在10ms之内返回查询内容
        注意:timeout并不会终止查询,它只是会在你指定的时间内返回当时已经查询到的数据,然后关闭连接。在后台,其他的查询可能会依旧继续,尽管查询结果已经被返回了。



搜索引擎例子:

       先用爬虫将网站数据爬下来,在将数据导入ES中,建立索引,前端传来搜索关键字,返回结果(url,文章标题,关键字部分的摘要)

步骤:

    1、爬虫下载所有的网页文件(原始数据)

    2、把每个网页抽取其中有用的数据

    title

    content

    url

    3、把抽取之后的数据分词索引(ES)

    4、搜索(ES)

    分页,高亮,结果片段

    5、索引更新:

            1、重新创建新索引库,让后在某个时间点替换老的库

            file.rname()

            2、实时更新,要个有新的数据过来,通过API更新其中一个doc (协处理器)

爬数据:

       。。。。。。

处理数据与搜索:

IndexService  : 处理索引建立与搜索查询

package com.laoxiao.es;

import java.io.File;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryParser;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.Test;
import org.springframework.stereotype.Service;

import com.laoxiao.util.HtmlTool;

@Service
public class IndexService {

	//存放html文件的目录
	public static String DATA_DIR="D:\\data\\";
	
	public static Client client;

	static {
		Settings settings = Settings.settingsBuilder()
				.put("cluster.name", "bjsxt").build();
		try {
			client = TransportClient
					.builder()
					.settings(settings)
					.build()
					.addTransportAddress(
							new InetSocketTransportAddress(InetAddress
									.getByName("node1"), 9300))
					.addTransportAddress(
							new InetSocketTransportAddress(InetAddress
									.getByName("node2"), 9300))
					.addTransportAddress(
							new InetSocketTransportAddress(InetAddress
									.getByName("node3"), 9300));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * admin():管理索引库的。client.admin().indices()
	 * 
	 * 索引数据的管理:client.prepare
	 * 
	 */
	@Test
	public void createIndex() throws Exception {
		IndicesExistsResponse resp = client.admin().indices().prepareExists("bjsxt").execute().actionGet();
		if(resp.isExists()){
			client.admin().indices().prepareDelete("bjsxt").execute().actionGet();
		}
		client.admin().indices().prepareCreate("bjsxt").execute().actionGet();

		new XContentFactory();

		XContentBuilder builder = XContentFactory.jsonBuilder().startObject()
				.startObject("htmlbean").startObject("properties")
				.startObject("title").field("type", "string")
				.field("store", "yes").field("analyzer", "ik_max_word")
				.field("search_analyzer", "ik_max_word").endObject()
				.startObject("content").field("type", "string")
				.field("store", "yes").field("analyzer", "ik_max_word")
				.field("search_analyzer", "ik_max_word").endObject()
//				.startObject("url").field("type", "string")
//				.field("store", "yes").field("analyzer", "ik_max_word")
//				.field("search_analyzer", "ik_max_word").endObject()
				.endObject().endObject().endObject();
		PutMappingRequest mapping = Requests.putMappingRequest("bjsxt").type("htmlbean").source(builder);
		client.admin().indices().putMapping(mapping).actionGet();

	}
	
	/**
	 * 把源数据html文件添加到索引库中(构建索引文件)
	 */
	@Test
	public void addHtmlToES(){
		readHtml(new File(DATA_DIR));
	}
	
	/**
	 * 遍历数据文件目录d:/data ,递归方法
	 * @param file
	 */
	public void readHtml(File file){
		if(file.isDirectory()){
			File[]  fs =file.listFiles();
			for (int i = 0; i < fs.length; i++) {
				File f = fs[i];
				readHtml(f);
			}
		}else{
			HtmlBean bean;
			try {
				bean = HtmlTool.parserHtml(file.getPath());
				if(bean!=null){
					Map<String, String> dataMap =new HashMap<String, String>();
					dataMap.put("title", bean.getTitle());
					dataMap.put("content", bean.getContent());
					dataMap.put("url", bean.getUrl());
					//写索引
					client.prepareIndex("bjsxt", "htmlbean").setSource(dataMap).execute().actionGet();
				}
			} catch (Throwable e) {
				e.printStackTrace();
			}
			
		}
	}
	
	/**
	 * 搜索
	 * @param kw
	 * @param num
	 * @return
	 */
	public PageBean<HtmlBean> search(String kw,int num,int count){
		PageBean<HtmlBean> wr =new PageBean<HtmlBean>();
		wr.setIndex(num);
//		//构建查询条件
//		MatchQueryBuilder q1 =new MatchQueryBuilder("title", kw);
//		MatchQueryBuilder q2 =new MatchQueryBuilder("content", kw);
//		
//		//构建一个多条件查询对象
//		BoolQueryBuilder q =new BoolQueryBuilder(); //组合查询条件对象
//		q.should(q1);
//		q.should(q2);
		
//		RangeQueryBuilder q1 =new RangeQueryBuilder("age");
//		q1.from(18);
//		q1.to(40);
		
		MultiMatchQueryBuilder q =new MultiMatchQueryBuilder(kw, new String[]{"title","content"});
		SearchResponse resp=null;
		if(wr.getIndex()==1){
			resp = client.prepareSearch("bjsxt")
					.setTypes("htmlbean")
					.setQuery(q)
					.addHighlightedField("title")
					.addHighlightedField("content")
					.setHighlighterPreTags("<font color=\"red\">")
					.setHighlighterPostTags("</font>")
					.setHighlighterFragmentSize(14)//设置显示结果中一个碎片段的长度
					.setHighlighterNumOfFragments(3)//设置显示结果中每个结果最多显示碎片段,每个碎片段之间用...隔开
					.setFrom(0)
					.setSize(10)
					.execute().actionGet();
			
		}else{
			wr.setTotalCount(count);
			resp = client.prepareSearch("bjsxt")
					.setTypes("htmlbean")
					.setQuery(q)
					.addHighlightedField("title")
					.addHighlightedField("content")
					.setHighlighterPreTags("<font color=\"red\">")
					.setHighlighterPostTags("</font>")
					.setHighlighterFragmentSize(14)
					.setHighlighterNumOfFragments(3)
					.setFrom(wr.getStartRow())
					.setSize(10)
					.execute().actionGet();
		}
		SearchHits hits= resp.getHits();
		wr.setTotalCount((int)hits.getTotalHits());
		
		for(SearchHit hit : hits.getHits()){
			HtmlBean bean =new HtmlBean();
			if(hit.getHighlightFields().get("title")==null){//title中没有包含关键字
				bean.setTitle(hit.getSource().get("title").toString());//获取原来的title(没有高亮的title)
			}else{
				bean.setTitle(hit.getHighlightFields().get("title").getFragments()[0].toString());
			}
			if(hit.getHighlightFields().get("content")==null){//title中没有包含关键字
				bean.setContent(hit.getSource().get("content").toString());//获取原来的title(没有高亮的title)
			}else{
				StringBuilder sb =new StringBuilder();
				for(Text text: hit.getHighlightFields().get("content").getFragments()){
					sb.append(text.toString()+"...");
				}
				bean.setContent(sb.toString());
			}
			
			bean.setUrl(hit.getSource().get("url").toString());
			wr.setBean(bean);
			
		}
		
		
		return wr;
	}
}

HtmlBean : 一个html里数据的封装类

package com.laoxiao.es;

public class HtmlBean {

	private int id;
	private String title;
	private String content;
	private String url;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	
}

HtmlTool : html工具类

package com.laoxiao.util;

import java.io.File;

import com.laoxiao.es.HtmlBean;
import com.laoxiao.es.IndexService;

import net.htmlparser.jericho.CharacterReference;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.HTMLElementName;
import net.htmlparser.jericho.Source;

public class HtmlTool {

	
	/**
	 * 
	 * @param path html 文件路径
	 */
	public static HtmlBean parserHtml(String path)throws Throwable{
		HtmlBean bean  =new HtmlBean();
		Source source=new Source(new File(path));
		// Call fullSequentialParse manually as most of the source will be parsed.
		source.fullSequentialParse();
		Element titleElement=source.getFirstElement(HTMLElementName.TITLE);
		if(titleElement==null){
			return null;
		}else{
			String title=CharacterReference.decodeCollapseWhiteSpace(titleElement.getContent());
			bean.setTitle(title);
		}
		String content =source.getTextExtractor().setIncludeAttributes(true).toString();
		
		String url =path.substring(IndexService.DATA_DIR.length());
		bean.setContent(content);
		bean.setUrl(url);
		return bean;
  }
	
	public static void main(String[] args) {
		try {
			parserHtml("D:/data/www.sxt.cn/blog-category-7.html");
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}



Elasticsearch的优化

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

                    ulimit -a (查看)

                    ulimit -n 32000(设置)

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

                    1:修改bin/elasticsearch.in.sh中ES_MIN_MEM和ES_MAX_MEM的大小,

                        建议设置一样大,避免频繁的分配内存,根据服务器内存大小,一般分配60%左右(默认256M)

                    2:如果使用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越多,查询的性能就越差,索引量不是很大的话情况下可以将segment设置为1

                    curl -XPOST 'http://localhost:9200/bjsxt/_optimize?max_num_segments=1'

                    java代码:client.admin().indices().prepareOptimize(“bjsxt").setMaxNumSegments(1).get();

       7、删除文档:在Lucene中删除文档,数据不会马上在硬盘上除去,而是在lucene索引中产生一个.del的文件,而在检索过程中这部分数据也会参与检索,lucene在检索过程会判断是否删除了,如果删除了在过滤掉。这样也会降低检索效率。所以可以执行清除删除文档
                    curl -XPOST 'http://localhost:9200/elasticsearch/_optimize?only_expunge_deletes=true'
                    client.admin().indices().prepareOptimize(" elasticsearch ").setOnlyExpungeDeletes(true).get();
       8、如果在项目开始的时候需要批量入库大量数据的话,建议将副本数设置为0
                    因为es在索引数据的时候,如果有副本存在,数据也会马上同步到副本中,这样会对es增加压力。待索引完成后将副本按需要改回来。这样可以提高索引效率。
       9、 去掉mapping中_all域,
                    Index中默认会有_all的域,(相当于solr配置文件中的拷贝字段text),这个会给查询带来方便,但是会增加索引时间和索引尺寸
                    "_all":{"enabled":"false"}
       10、log降低级别
                     输出的水平默认为trace,即查询超过500ms即为慢查询,就要打印日志,造成cpu和mem,io负载很高。把log输出水平改为info,可以减轻服务器的压力。
                    修改ES_HOME/conf/logging.yaml文件或者修改ES_HOME/conf/elasticsearch.yaml
       11、使用反射获取Elasticsearch客户端
                    可以使用前面讲的方式通过new获取client
                    使用反射方式:网上反映这种方式效率明显高于new客户端,并可避免线上环境内存溢出和超时等问题

添加新评论