OneStack

The Blog Powered By jessyon


  • 首页

  • 归档

  • 标签

服务器搭建(六)zookeeper集群

发表于 2016-09-28   |  

zookeeper微集群结构

Zookeeper

1、集群管理
主从的管理、负载均衡、高可用的管理。集群的入口。Zookeeper必须是集群才能保证高可用。Zookeeper有选举和投票的机制。集群中至少应该有三个节点。

2、配置文件的集中管理(这里使用)
搭建solr集群时,需要把Solr的配置文件上传zookeeper,让zookeeper统一管理。每个节点都到zookeeper上取配置文件。

Zookeeper集群搭建步骤

第一步.把zookeeper-3.4.6.tar.gz上传到服务,解压缩到/usr/local/目录下

第二步.在/usr/local/目录下创建solr-cloud目录,把zookeeper向该目录复制三份

    cp -r zookeeper-3.4.6 /usr/local/solr-cloud/zookeeper01
    cp -r zookeeper-3.4.6 /usr/local/solr-cloud/zookeeper02
    cp -r zookeeper-3.4.6 /usr/local/solr-cloud/zookeeper03

第三步.配置zookeeper:
    1.在zookeeper01目录下,创建一个data文件夹
    2.在data目录下创建一个myid文件,内容为对应的id
    echo 1 >> data/myid  
    在其他两个目录重复1,2两个步骤
    3.修改conf文件,把zoo_sample.cfg文件改名为zoo_cfg
    4.修改zoo.cfg
    12行 dataDir=/usr/local/solr-cloud/zookeeper01/data
    14行 01:2181,02:2182,03:2183
    5.在zoo.cfg末尾添加服务地址

    01:
    server.1=0.0.0.0:2881:3881
    server.2=115.159.93.201:2882:3882
    server.3=115.159.93.201:2883:3883

    02
    server.1=115.159.93.201:2881:3881
    server.2=0.0.0.0:2882:3882
    server.3=115.159.93.201:2883:3883

    03
    server.1=115.159.93.201:2881:3881
    server.2=115.159.93.201:2882:3882
    server.3=0.0.0.0:2883:3883

第四步,启动zookeeper
    到三个zookeeper目录的bin目录下,分别开启zookeeper服务
    启动:./zkServer.sh start
    关闭:./zkServer.sh stop
    查看服务状态:./zkServer.sh status

服务器搭建(五)solr集群

发表于 2016-09-24   |  

搜索系统搭建

搜索功能需要发布服务共pc端、移动端使用。根据关键词搜索,得到json格式的搜索结果。
创建一个搜索系统,发布搜索服务。

Solr服务的搭建

第一步:安装jdk、安装tomcat,复制一份tomcat到/usr/local/solr/目录下
cp -r apache-tomcat-7.0.47 /usr/local/solr
mv apache-tomcat-7.0.47 tomcat

第二步:解压solr压缩包。
tar -zxf solr-4.10.3.tgz.tgz
mv solr-4.10.3 /usr/local

第三步:把dist/solr-4.10.3.war部署到tomcat

第四步:解压缩war包。启动tomcat解压。

第五步:需要把/usr/local/solr-4.10.3/example/lib/ext目录下的所有的jar包添加到solr工程中。

第六步:创建solrhome。先在/usr/local下创建一个solr目录,
cd /usr/local ->  mkdir solr
然后把/usr/local/solr-4.10.3/example/solr文件夹复制一份作为solrhome。
cp -r /usr/local/solr-4.10.3/example/solr /usr/local/solr/home

第七步:告诉solr服务solrhome的位置。需要修改web.xml
修改/usr/local/solr/tomcat/webapps/solr/WEB-INF/web.xml中的:
<env-entry>
   <env-entry-name>solr/home</env-entry-name>
   <env-entry-value>/usr/local/solr/solrhome</env-entry-value>
   <env-entry-type>java.lang.String</env-entry-type>
</env-entry>

solr集群

第一步,安装4个tomcat到solr-cloud目录下,修改端口号分别为9001,9002,9003,9004

第二步,将solr分别部署到4个tomcat下,把单机版的solr复制到solr-cloud的tomcat/webapps下就可以了。

第三步,为每一个solr实例创建一个solrhome,修改
/usr/local/solr-cloud/tomcat01/webapps/solr/WEB-INF/下的web.xml

   <env-entry>
   <env-entry-name>solr/home</env-entry-name>
   <env-entry-value>/usr/local/solr-cloud/solrhome01</env-entry-value>
   <env-entry-type>java.lang.String</env-entry-type>
      </env-entry>

第四步,修改每个/usr/local/solr-cloud/solrhome下的solr.xml
32,33行分别为主机地址和端口号
<str name="host">${host:115.159.93.201}</str>
<int name="hostPort">${jetty.port:9001}</int>

第五步,把配置文件上传到zookeeper,由zookeeper来统一管理solr集群的配置信息。

    进入命令所在的目录
    cd /usr/local/solr-4.10.3/example/scripts/cloud-scripts/

    把/usr/local/solr-cloud/solrhome01/collection1/conf目录上传到zookeeper

    ./zkcli.sh -zkhost 115.159.93.201:2182, 115.159.93.201:2181, 115.159.93.201:2183 -cmd upconfig -confdir /usr/local/solr-cloud/solrhome01/collection1/conf -confname myconf

    进入一个zookeeper下的bin
    使用zkCli.sh查看是否上传成功

第六步,告诉solr实例zookeeper的位置,需要向tomcat的catalina.sh里添加:

    JAVA_OPTS="-DzkHost=115.159.93.201:2181,115.159.93.201:2182,115.159.93.201:2183"

第七步,启动4个tomcat

第八步,将4个solr分成2片,每片一个主节点,一个从节点。

    http://115.159.93.201:9001/solr/admin/collections?action=CREATE&name=collection2&numShards=2&replicationFactor=2

第九步,删除不需要的collection1

    http://115.159.93.201:9001/solr/admin/collections?action=DELETE&name=collection1

将单机版solr切换到集群版

在applicationContext-service.xml中修改配置:

        <!-- 配置单机版solr客户端 -->
        <!-- <bean id="httpSolrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
            <constructor-arg name="baseURL" value="http://115.159.93.201:8080/solr"></constructor-arg>
        </bean>
         -->
        <!-- 配置集群版solr客户端 -->
        <bean id="cloudSolrServer" class="org.apache.solr.client.solrj.impl.CloudSolrServer">
            <constructor-arg name="zkHost" value="115.159.93.201:2181,115.159.93.201:2182,115.159.93.201:2183"></constructor-arg>
            <property name="defaultCollection" value="collection2"></property>
        </bean>

solr全文搜索服务

发表于 2016-09-20   |  

站内搜索技术选型

在一些大型门户网站、电子商务网站等都需要站内搜索功能,
使用传统的数据库查询方式实现搜索无法满足一些高级的搜索需求,
比如:搜索速度要快、搜索结果按相关度排序、搜索内容格式不固定等,
这里就需要使用全文检索技术实现搜索功能。

单独使用Lucene实现

单独使用Lucene实现站内搜索需要开发的工作量较大,
主要表现在:索引维护、索引性能优化、搜索性能优化等,因此不建议采用。

使用Google或Baidu接口

通过第三方搜索引擎提供的接口实现站内搜索,这样和第三方引擎系统依赖紧密,不方便扩展,不建议采用。

使用Solr实现

基于Solr实现站内搜索扩展性较好并且可以减少程序员的工作量,
因为Solr提供了较为完备的搜索引擎解决方案,因此在门户、论坛等系统中常用此方案。

Solr介绍

什么是Solr

Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器。
Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化。 

Solr可以独立运行,运行在Jetty、Tomcat等这些Servlet容器中,Solr 索引的实现方法很简单,
用POST方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr根据xml文档添加、删除、更新索引。
Solr 搜索只需要发送 HTTP GET 请求,然后对 Solr 返回Xml、json等格式的查询结果进行解析,
组织页面布局。Solr不提供构建UI的功能,Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。

Solr与Lucene的区别

Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的全文检索引擎,
Lucene提供了完整的查询引擎和索引引擎,目的是为软件开发人员提供一个简单易用的工具包,
以方便的在目标系统中实现全文检索的功能,或者以Lucene为基础构建全文检索引擎。

 Solr的目标是打造一款企业级的搜索引擎系统,它是一个搜索引擎服务,可以独立运行,
通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。

Solr安装配置

http://lucene.apache.org/solr/

下载lucene-4.10.3.zip 或 lucene-4.10.3.tgz 并解压:

bin:solr的运行脚本
contrib:solr的一些贡献软件/插件,用于增强solr的功能。
dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。
docs:solr的API文档
example:solr工程的例子目录:
    example/solr:
    该目录是一个包含了默认配置信息的Solr的Core目录。
    example/multicore:
    该目录包含了在Solr的multicore中设置的多个Core目录。 
    example/webapps:
    该目录中包括一个solr.war,该war可作为solr的运行实例工程。
licenses:solr相关的一些许可信息

运行环境

solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,
Solr默认提供Jetty(java写的Servlet容器)

环境如下:

Solr:Solr4.10.3
Jdk:jdk1.7.0_72
Tomcat:apache-tomcat-7.0.53

Solr与Tomcat整合配置

Solr Home与SolrCore

创建一个Solr home目录,SolrHome是Solr运行的主目录,目录中包括了运行Solr实例所有的配置文件和数据文件,
Solr实例就是SolrCore,一个SolrHome可以包括多个SolrCore(Solr实例),每个SolrCore提供单独的搜索和索引服务。

目录结构

example\solr是一个solr home目录结构,如下:

上图中“collection1”是一个SolrCore(Solr实例)目录 ,目录内容如下所示:

说明:

collection1:叫做一个Solr运行实例SolrCore,SolrCore名称不固定,
一个solr运行实例对外单独提供索引和搜索接口。

solrHome中可以创建多个solr运行实例SolrCore。

一个solr的运行实例对应一个索引目录。

conf是SolrCore的配置文件目录 。

配置

创建目录 F:\develop\solr 

将example\solr目录 拷贝至 F:\develop\solr目录下并改名为solrhome

solrconfig.xml

solrconfig.xml,在SolrCore的conf目录下,它是SolrCore运行的配置文件。
加载jar包
将contrib和dist两个目录拷贝到F:\develop\solr下,此时,solrhome,dist,contrib三个目录在同一层级

修改solrconfig.xml文件:

<lib dir="${solr.install.dir:../..}/contrib/extraction/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../..}/dist/" regex="solr-cell-\d.*\.jar" />

<lib dir="${solr.install.dir:../..}/contrib/clustering/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../..}/dist/" regex="solr-clustering-\d.*\.jar" />

<lib dir="${solr.install.dir:../..}/contrib/langid/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../..}/dist/" regex="solr-langid-\d.*\.jar" />

<lib dir="${solr.install.dir:../..}/contrib/velocity/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../..}/dist/" regex="solr-velocity-\d.*\.jar" />
dataDir
置SolrCore的数据目录,数据目录下包括了index索引目录 和tlog日志文件目录,
数据目录默认在solrCore下的data目录 ,也可以更改目录地址 ,如下:

  <dataDir>${solr.data.dir:F:/develop/solr/collection1/data}</dataDir>
requestHandler
requestHandler请求处理器,定义了索引和搜索的访问方式。

通过/update维护索引,可以完成索引的添加、修改、删除操作。
通过/select搜索索引。
设置搜索参数完成搜索,搜索参数也可以设置一些默认值,如下:

<requestHandler name="/select" class="solr.SearchHandler">
<!-- 设置默认的参数值,可以在请求地址中修改这些参数-->
<lst name="defaults">
    <str name="echoParams">explicit</str>
    <int name="rows">10</int><!--显示数量-->
    <str name="wt">json</str><!--显示格式-->
    <str name="df">text</str><!--默认搜索字段-->
</lst>
</requestHandler>

Solr工程部署

1.    将dist\solr-4.10.3.war拷贝到Tomcat的webapp目录下改名为solr.war

2.    启动tomcat后,solr.war自动解压,将原来的solr.war删除。

3.    拷贝example\lib\ext 目录下所有jar包到Tomcat的webapp\solr\WEB-INF\lib目录下
4.    修改Tomcat目录 下webapp\solr\WEB-INF\web.xml文件,如下所示:
    设置Solr home

    <env-entry>
    <env-entry-name>solr/home</env-entry-name>
    <env-entry-value>f:/develop/solr/solrhome</env-entry-value>
    <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>

5.    拷贝log4j.properties文件

    在 Tomcat下webapps\solr\WEB-INF目录中创建文件 classes文件夹,
    复制Solr目录下example\resources\log4j.properties至Tomcat下webapps\solr\WEB-INF\classes目录 

启动Tomcat

启动tomcat服务器,在浏览器输入: localhost/solr

Dashboard

仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。

Logging

Solr运行日志信息

Cloud

Cloud即SolrCloud,即Solr云(集群),当使用Solr Cloud模式运行时会显示此菜单

Core Admin

Solr Core的管理界面。Solr Core 是Solr的一个独立运行实例单位,它可以对外提供索引和搜索服务,
一个Solr工程可以运行多个SolrCore(Solr实例),一个Core对应一个索引目录。

java properties

Solr在JVM 运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。

Tread Dump

显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。

Analysis

通过此界面可以测试索引分析器和搜索分析器的执行情况。

dataimport

可以定义数据导入处理器,从关系数据库将数据导入 到Solr索引库中。

Document

通过此菜单可以创建索引、更新索引、删除索引等操作
/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,
如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。

query

通过/select执行搜索索引,必须指定“q”查询条件方可搜索。

多core配置

复制原来的core目录为collection2
修改collection2下的core.properties:
name=collection2

Solr索引

scheam.xml

schema.xml,在SolrCore的conf目录下,它是Solr数据表配置文件,它定义了加入索引的数据的数据类型的。
主要包括FieldTypes、Fields和其他的一些缺省设置。

FieldType域类型定义

下边“text_general”是Solr默认提供的FieldType,通过它说明FieldType定义的内容:

 <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
    <!-- in this example, we will only use synonyms at query time
    <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
    -->
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
    <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

FieldType子结点包括:name,class,positionIncrementGap等一些参数:

name:是这个FieldType的名称

class:是Solr提供的包solr.TextField,solr.TextField 允许用户通过分析器来定制索引和查询,
分析器包括一个分词器(tokenizer)和多个过滤器(filter)

positionIncrementGap:可选属性,定义在同一个文档中此类型数据的空白间隔,
避免短语匹配错误,此值相当于Lucene的短语查询设置slop值,根据经验设置为100。

在FieldType定义的时候最重要的就是,
定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤

索引分析器中:使用solr.StandardTokenizerFactory标准分词器,
solr.StopFilterFactory停用词过滤器,
solr.LowerCaseFilterFactory小写过滤器。

搜索分析器中:使用solr.StandardTokenizerFactory标准分词器,
solr.StopFilterFactory停用词过滤器,这里还用到了solr.SynonymFilterFactory同义词过滤器。

Field定义

在solr中,Field要先定义后使用。
在fields结点内定义具体的Field,filed定义包括name,
type(为之前定义过的各种FieldType),indexed(是否被索引),stored(是否被储存),multiValued(是否存储多个值,比如多个商品图片)等属性。

如下:

<field name="name" type="text_general" indexed="true" stored="true"/>
<field name="features" type="text_general" indexed="true" stored="true" multiValued="true"/>

multiValued:该Field如果要存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id(多个),
商品的图片(多个,大图和小图),通过使用solr查询要看出返回给客户端是数组。

uniqueKey

Solr中默认定义唯一主键key为id域,如下:

<uniqueKey>id</uniqueKey>

Solr在删除、更新索引时使用id域进行判断,也可以自定义唯一主键。

copyField复制域

copyField复制域,可以将多个Field复制到一个Field中,以便进行统一的检索:
比如,输入关键字搜索title标题内容content,

定义title、content、text的域:

<field name="title" type="text_general" indexed="true" stored="true" multiValued="true"/>
<field name="subject" type="text_general" indexed="true" stored="true"/>
<field name="description" type="text_general" indexed="true" stored="true"/>

根据关键字只搜索text域的内容就相当于搜索title和content,将title和content复制到text中,如下:

<copyField source="cat" dest="text"/>
<copyField source="name" dest="text"/>
<copyField source="manu" dest="text"/>
<copyField source="features" dest="text"/>
<copyField source="includes" dest="text"/>
<copyField source="manu" dest="manu_exact"/>

dynamicField(动态字段)

动态字段就是不用指定具体的名称,只要定义字段名称的规则,
例如定义一个 dynamicField,name 为*_i,定义它的type为text,
那么在使用这个字段的时候,任何以_i结尾的字段都被认为是符合这个定义的,
例如:name_i,gender_i,school_i等。

自定义Field名为:product_title_t,“product_title_t”和scheam.xml中的dynamicField规则匹配成功,如下:

<dynamicField name="*_i"  type="int"  indexed="true" stored="true"/>

“product_title_t”是以“_t”结尾。

Analyzer

安装中文分词器

IKAnalyzer部署

拷贝IKAnalyzer的文件到Tomcat下Solr目录 中

将IKAnalyzer2012FF_u1.jar拷贝到 Tomcat的webapps/solr/WEB-INF/lib 下。
在Tomcat的webapps/solr/WEB-INF/下创建classes目录
将IKAnalyzer.cfg.xml、ext_stopword.dic  mydict.dic  copy到 Tomcat的
webapps/solr/WEB-INF/classes

注意:ext_stopword.dic 和mydict.dic必须保存成无BOM的utf-8类型。

修改schema.xml文件

1.    FieldType

首先需要在types结点内定义一个FieldType子结点,包括name,class,等参数,
name就是这个FieldType的名称,
class指向org.apache.solr.analysis包里面对应的class名称,用来定义这个类型的行为。
在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤

修改Solr的schema.xml文件,添加FieldType:

<!-- IKAnalyzer-->
     <fieldType name="text_ik" class="solr.TextField">
        <analyzer type="index" isMaxWordLength="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
        <analyzer type="query" isMaxWordLength="true" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
     </fieldType>

其中查询采用IK自己的最大分词法,索引则采用它的细粒度分词法.所以各自配置了isMaxWordLength属性.

2.    Field:

FieldType定义好后就可以在fields结点内定义具体的field,filed定义包括:
name,type(即FieldType),indexed(是否被索引),stored(是否被储存),multiValued(是否有多个值)等

<!--IKAnalyzer Field-->
   <field name="title_ik" type="text_ik" indexed="true" stored="true" />
   <field name="content_ik" type="text_ik" indexed="true" stored="false" multiValued="true"/>

设置业务系统Field

如果不使用Solr提供的Field可以针对具体的业务需要自定义一套Field,如下是商品信息Field:

 <!--product-->
<field name="product_name" type="text_ik" indexed="true" stored="true"/>
<field name="product_price"  type="float" indexed="true" stored="true"/>
<field name="product_description" type="text_ik" indexed="true" stored="false" />
<field name="product_picture" type="string" indexed="false" stored="true" />
<field name="product_catalog_name" type="string" indexed="true" stored="true" />

<field name="product_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>
<copyField source="product_name" dest="product_keywords"/>
<copyField source="product_description" dest="product_keywords"/>

dataimport-handler

安装dataimport-Handler从关系数据库将数据导入到索引库。

第一步:向SolrCore中加入jar包
在SolrCore目录中创建lib目录,将dataimportHandler和mysql数据库驱动的jar拷贝至lib下:
dataimportHandler在solr安装目录的dist下。
复制到D:\apache-solr-home\contrib\dataimporthandler\lib
mysql驱动复制到D:\apache-solr-home\contrib\db\lib

第二步 : 修改solrconfig.xml文件,添加requestHandler

     <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
     <lst name="defaults">
       <str name="config">data-config.xml</str>
     </lst>
      </requestHandler>

第三步:编辑data-config.xml文件,存放在SolrCore的conf目录 

    <?xml version="1.0" encoding="UTF-8" ?>  
    <dataConfig>   
    <dataSource type="JdbcDataSource"   
              driver="com.mysql.jdbc.Driver"   
              url="jdbc:mysql://localhost:3306/lucene"   
              user="w6233834"   
              password="6233834"/>   
        <document>   
            <entity name="product" query="SELECT id,name,catalog_name,price,description,pic FROM products ">
                 <field column="id" name="id"/> 
                 <field column="name" name="product_name"/> 
                 <field column="catalog_name" name="product_catalog_name"/> 
                 <field column="price" name="product_price"/> 
                 <field column="description" name="product_description"/> 
                 <field column="pic" name="product_picture"/> 
            </entity>   
        </document>   
    </dataConfig>

    <field column="id" name="id"/>必须有一个id域,这里使用Solr默认的id域,域值是从关系数据库查询的id列值。
    下边以“product_”开头的Field都是在schema.xml中自定义的商品信息Field。

第四步:重启Tomcat,进入管理界面 -> SolrCore -> dataimport下执行导入

SolrJ完成索引维护

什么是SolrJ

solrj是访问Solr服务的java客户端,提供索引和搜索的请求方法,
SolrJ通常在嵌入在业务系统中,通过SolrJ的API接口操作Solr服务。

如下图:

创建索引

使用SolrJ创建索引,通过调用SolrJ提供的API请求Solr服务,Document通过SolrInputDocument进行构建。

@Test
public void createIndex() throws SolrServerException, IOException{

    //创建HttpSolrServer
    //参数:表示solr服务的访问基础url
    HttpSolrServer server = new HttpSolrServer("http://localhost/solr");
    //通过server添加SolrInputDocument
    SolrInputDocument doc = new SolrInputDocument();
    doc.addField("id", "X001");
    doc.addField("content_ik", "我爱中国");

    server.add(doc);
    //提交操作
    server.commit();
}

说明:根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。

删除索引

@Test
public void deleteIndex() throws SolrServerException, IOException{
    //创建HttpSolrServer
    //参数:表示solr服务的访问基础url
    HttpSolrServer server = new HttpSolrServer("http://localhost/solr");

    server.deleteById("CX001");

    server.commit();
}    


@Test
public void deleteIndexByQuery() throws SolrServerException, IOException{
    //创建HttpSolrServer
    //参数:表示solr服务的访问基础url
    HttpSolrServer server = new HttpSolrServer("http://localhost/solr");

    //根据条件删除
    server.deleteByQuery("id:cx001");

    //批量删除
    server.deleteByQuery("content_ik:我爱中国");

    server.commit();
}

复杂查询

@Test
public void searchIndex02() throws SolrServerException{
    //创建HttpSolrServer
    //参数:表示solr服务的访问基础url
    HttpSolrServer server = new HttpSolrServer("http://localhost/solr");

    //创建查询对象
    SolrQuery query = new SolrQuery();

    //设置查询条件
    query.setQuery("小黄人");

    //设置过滤条件
    query.addFilterQuery("product_catalog_name:幽默杂货");
    query.addFilterQuery("product_price:[5 TO 20]");

    //设置排序
    query.setSort("product_price", ORDER.desc);

    //设置分页信息
    query.setStart(0);
    query.setRows(10);

    //设置需要显示的域列表
    query.setFields("id,product_name,product_price,product_catalog_name,product_picture");

    //设置默认搜索域
    query.set("df", "product_keywords");

    //设置高亮
    query.setHighlight(true);
    query.addHighlightField("product_name");
    query.setHighlightSimplePre("<font style=\"color:red\">");
    query.setHighlightSimplePost("</font>");

    QueryResponse response = server.query(query);

    //获取查询结果
    SolrDocumentList results = response.getResults();

    //匹配出的所有商品记录条数
    long numFound = results.getNumFound();

    System.out.println("匹配出的所有商品记录条数: "+numFound);

    //获取高亮信息
    Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();

    for (SolrDocument solrDocument : results) {

        System.out.println("商品id: "+solrDocument.get("id"));
        System.out.println("商品name: "+solrDocument.get("product_name"));
        System.out.println("商品price: "+solrDocument.get("product_price"));
        System.out.println("商品分类名称: "+solrDocument.get("product_catalog_name"));
        System.out.println("商品图片: "+solrDocument.get("product_picture"));

        List<String> list = highlighting.get(solrDocument.get("id")).get("product_name");

        if(list!= null) System.out.println("显示高亮信息: "+list.get(0));

        System.out.println("====================================================");

    }

}

lucene域、索引维护、搜索、中文分词

发表于 2016-09-19   |  

Field域

Field属性

Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,
Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

是否分词(tokenized)

是:作分词处理,即将Field值进行分词,分词的目的是为了索引。
比如:商品名称、商品简介等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元索引。

否:不作分词处理
比如:商品id、订单号、身份证号等 

是否索引(indexed)

是:进行索引。将Field分词后的词或整个Field值进行索引,索引的目的是为了搜索。
比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。

否:不索引。该域的内容无法搜索到
比如:商品id、文件路径、图片路径等,不用作为查询条件的不用索引。

是否存储(stored)

是:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。
比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

否:不存储Field值,不存储的Field无法通过Document获取
比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。

Field常用类型

Field代码说明

图书id:
是否分词:不用分词,因为不会根据商品id来搜索商品 
是否索引:不索引,因为不需要根据图书ID进行搜索
是否存储:要存储,因为查询结果页面需要使用id这个值。

图书名称:
是否分词:要分词,因为要将图书的名称内容分词索引,根据关键搜索图书名称抽取的词。
是否索引:要索引。
是否存储:要存储。

图书价格:
是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,
因为lucene对数字型的内容要特殊分词处理,本例子可能要根据价格范围搜索,需要分词和索引。

是否索引:要索引

是否存储:要存储

图书图片地址:
是否分词:不分词
是否索引:不索引
是否存储:要存储

图书描述:
是否分词:要分词
是否索引:要索引
是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。

不存储是来不在lucene的索引文件中记录,节省lucene的索引文件空间,如果要在详情页面显示描述,思路:
从lucene中取出图书的id,根据图书的id查询关系数据库中book表得到描述信息。

索引维护

管理人员通过电商系统更改图书信息,这时更新的是数据库,如果使用lucene搜索图书信息需要在数据库表book信息变化时及时更新lucene索引库。

添加索引

调用 indexWriter.addDocument(doc)添加索引。
参考入门程序的创建索引。

删除索引

private IndexWriter getWriter(){
        try {
            //3.进行分词
            Analyzer analyzer = new StandardAnalyzer();

            //4.创建indexWriter
            //4.1创建Directory 目录流
            IndexWriterConfig indexconfig = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);

            //Directory 是抽象类,有两个实现类,FSDirectory(文件系统) RAMDirectory(内存)
            Directory directory = FSDirectory.open(new File("F:\\index"));
            return new IndexWriter(directory,indexconfig);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

@Test
public void deleteIndex() throws Exception{

        IndexWriter writer = getWriter();

        writer.deleteDocuments(new Term("name","神"));



        //6.关闭IndexWriter
        writer.close();
    }

修改索引

更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。

// 修改索引
@Test
public void updateIndex() throws Exception {
    // 1、指定索引库目录
    Directory directory = FSDirectory.open(new File("F:\\index"));
    // 2、创建IndexWriterConfig
    IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,
            new StandardAnalyzer());
    // 3、 创建IndexWriter
    IndexWriter writer = new IndexWriter(directory, cfg);
    // 4、通过IndexWriter来修改索引
    // a)、创建修改后的文档对象
    Document document = new Document();

    // 文件名称
    Field filenameField = new StringField("filename", "updateIndex", Store.YES);
    document.add(filenameField);

    // 修改指定索引为新的索引
    writer.updateDocument(new Term("filename", "apache"), document);

    // 5、关闭IndexWriter
    writer.close();
}

搜索

创建查询的两种方法

对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。类似关系数据库Sql语法一样,Lucene也有自己的查询语法,
比如:“name:lucene”表示查询Field的name为“lucene”的文档信息。
可通过两种方法创建查询对象:

1)使用Lucene提供Query子类

Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。
如下代码:
Query query = new TermQuery(new Term("name", "lucene"));

2)使用QueryParse解析查询表达式

QueryParser会将用户输入的查询表达式解析成Query对象实例。
如下代码:
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
Query query = queryParser.parse("name:lucene");

通过Query子类搜索

TermQuery

TermQuery项查询,TermQuery不使用分析器,搜索关键词作为整体来匹配Field域中的词进行查询,比如订单号、分类ID号等。

private void doSearch(Query query) {
    IndexReader reader = null;
    try {
        // a) 指定索引库目录
        Directory indexdirectory = FSDirectory.open(new File(
                "F:\\index"));
        // b) 创建IndexReader对象
        reader = DirectoryReader.open(indexdirectory);
        // c) 创建IndexSearcher对象
        IndexSearcher searcher = new IndexSearcher(reader);
        // d) 通过IndexSearcher对象执行查询索引库,返回TopDocs对象
        // 第一个参数:查询对象
        // 第二个参数:最大的n条记录
        TopDocs topDocs = searcher.search(query, 10);
        // e) 提取TopDocs对象中的文档ID,如何找出对应的文档
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        System.out.println("总共查询出的结果总数为:" + topDocs.totalHits);
        Document doc;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 文档对象ID
            int docId = scoreDoc.doc;
            doc = searcher.doc(docId);
            // f) 输出文档内容
            System.out.println(doc.get("filename"));
            System.out.println(doc.get("path"));
            System.out.println(doc.get("size"));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

@Test
public void testTermQuery() throws Exception {
    // 1、 创建查询(Query对象)
    Query query = new TermQuery(new Term("filename", "apache"));
    // 2、 执行搜索
    doSearch(query);
}

NumbericRangeQuery

NumericRangeQuery,指定数字范围查询.

@Test
public void testNumbericRangeQuery() throws Exception {
    // 创建查询
    // 第一个参数:域名
    // 第二个参数:最小值
    // 第三个参数:最大值
    // 第四个参数:是否包含最小值
    // 第五个参数:是否包含最大值
    Query query = NumericRangeQuery.newLongRange("size", 1l, 100l, true,true);
    // 2、 执行搜索
    doSearch(query);
}

BooleanQuery

BooleanQuery,布尔查询,实现组合条件查询。

@Test
public void booleanQuery() throws Exception {
    BooleanQuery query = new BooleanQuery();
    Query query1 = new TermQuery(new Term("id", "3"));
    Query query2 = NumericRangeQuery.newFloatRange("price", 10f, 200f,
            true, true);

    //MUST:查询条件必须满足,相当于AND
    //SHOULD:查询条件可选,相当于OR
    //MUST_NOT:查询条件不能满足,相当于NOT非
    query.add(query1, Occur.MUST);
    query.add(query2, Occur.SHOULD);

    System.out.println(query);

    search(query);
}

组合关系代表的意思如下:

1、MUST和MUST表示“与”的关系,即“并集”。 
2、MUST和MUST_NOT前者包含后者不包含。 
3、MUST_NOT和MUST_NOT没意义 
4、SHOULD与MUST表示MUST,SHOULD失去意义; 
5、SHOUlD与MUST_NOT相当于MUST与MUST_NOT。 
6、SHOULD与SHOULD表示“或”的概念。

通过QueryParser搜索

通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,
此方法可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。

QueryParser

代码实现

@Test
public void testQueryParser() throws Exception {
    // 创建QueryParser
    // 第一个参数:默认域名
    // 第二个参数:分词器
    QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
    // 指定查询语法 ,如果不指定域,就搜索默认的域
        Query query = queryParser.parse("lucene");
        System.out.println(query);
    // 2、 执行搜索
            doSearch(query);

    }

查询语法

1、基础的查询语法,关键词查询:
域名+“:”+搜索的关键字
例如:content:java
2、范围查询
域名+“:”+[最小值 TO 最大值]
例如:size:[1 TO 1000]
注意:QueryParser不支持对数字范围的搜索,它支持字符串范围。数字范围搜索建议使用NumericRangeQuery。

3、组合条件查询
Occur.MUST     查询条件必须满足,  相当于and     +(加号)
Occur.SHOULD   查询条件可选,     相当于or 空 (不用符号)
Occur.MUST_NOT 查询条件不能满足,  相当于not非     -(减号)

1)+条件1 +条件2:两个条件之间是并且的关系and
例如:+filename:apache +content:apache
2)+条件1 条件2:必须满足第一个条件,忽略第二个条件
例如:+filename:apache content:apache
3)条件1 条件2:两个条件满足其一即可。
例如:filename:apache content:apache
4)-条件1 条件2:必须不满足条件1,要满足条件2
例如:-filename:apache content:apache

第二种写法:
条件1 AND 条件2
条件1 OR 条件2
条件1 NOT 条件2

MultiFieldQueryParser

通过MuliFieldQueryParse对多个域查询。

@Test
public void testMultiFieldQueryParser() throws Exception {
    // 可以指定默认搜索的域是多个
        String[] fields = { "name", "description" };
    // 创建一个MulitFiledQueryParser对象
        QueryParser parser = new MultiFieldQueryParser(fields, new IKAnalyzer());
    // 指定查询语法 ,如果不指定域,就搜索默认的域
        Query query = parser.parse("lucene");
    // 2、 执行搜索
        doSearch(query);
}

TopDocs

Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:

方法或属性    说明
totalHits    匹配搜索条件的总记录数
scoreDocs    顶部匹配记录

注意:

Search方法需要指定匹配记录数量n:indexSearcher.search(query, n)
TopDocs.totalHits:是匹配索引库中所有记录的数量
TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n

相关度排序

什么是相关度排序

相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。
比如搜索“Lucene”关键字,与该关键字最相关的文章应该排在前边。

相关度打分

Lucene对查询关键字和索引文档的相关度进行打分,得分高的就排在前边。如何打分呢?Lucene是在用户进行检索时实时根据搜索的关键字计算出来的,分两步:
1)计算出词(Term)的权重
2)根据词的权重值,计算文档相关度得分。

什么是词的权重?

通过索引部分的学习,明确索引的最小单位是一个Term(索引词典中的一个词),
搜索也是要从Term中搜索,再根据Term找到文档,Term对文档的重要性称为权重,
影响Term权重有两个因素:

Term Frequency (tf):
指此Term在此文档中出现了多少次。tf 越大说明越重要。 
词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,
如“Lucene”这个词,在文档中出现的次数很多,说明该文档主要就是讲Lucene技术的。

Document Frequency (df):
指有多少文档包含次Term。df 越大说明越不重要。 
比如,在一篇英语文档中,this出现的次数更多,就说明越重要吗?
不是的,有越多的文档包含此词(Term), 说明此词(Term)太普通,
不足以区分这些文档,因而重要性越低。

设置boost值影响相关度排序

boost是一个加权值(默认加权值为1.0f),它可以影响权重的计算。

在索引时对某个文档中的field设置加权值高,在搜索时匹配到这个文档就可能排在前边。

在搜索时对某个域进行加权,在进行组合域查询时,匹配到加权值高的域最后计算的相关度得分就高。

设置boost是给域(field)或者Document设置的。

在创建索引时设置

如果希望某些文档更重要,当此文档中包含所要查询的词则应该得分较高,
这样相关度排序可以排在前边,可以在创建索引时设定文档中某些域(Field)的boost值来实现,
如果不进行设定,则Field Boost默认为1.0f。一旦设定,除非删除此文档,否则无法改变。

代码实现

@Test
public void setBoost4createIndex() throws Exception {
    // 创建分词器
    Analyzer analyzer = new StandardAnalyzer();

    IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
            analyzer);
    Directory directory = FSDirectory.open(new File("F:\\index"));
    // 创建IndexWriter对象,通过它把分好的词写到索引库中
    IndexWriter writer = new IndexWriter(directory, cfg);

    Document doc = new Document();
    Field id = new StringField("id", "11", Store.YES);
    Field description = new TextField("description", "测试设置BOOST值 lucene",
            Store.YES);
    // 设置boost
    description.setBoost(10.0f);
    // 把域添加到文档中
    doc.add(id);
    doc.add(description);
    writer.addDocument(doc);
    // 关闭IndexWriter
    writer.close();
}

中文分词器

什么是中文分词器

学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开。
而中文则以字为单位,字又组成词,字和词再组成句子。
所以对于英文,我们可以简单以空格判断某个字符串是否为一个单词,
比如I love China,love 和 China很容易被程序区分开来;
但中文“我爱中国”就不一样了,电脑不知道“中国”是一个词语还是“爱中”是一个词语。
把中文的句子切分成有意义的词,就是中文分词,也称切词。我爱中国,分词的结果是:我 爱 中国。

Lucene自带的中文分词器

StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。

CJKAnalyzer
二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。

使用中文分词器IKAnalyzer

IKAnalyzer继承Lucene的Analyzer抽象类,
使用IKAnalyzer和Lucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。

如果使用中文分词器ik-analyzer,就在索引和搜索程序中使用一致的分词器ik-analyzer。

添加jar包

将 IKAnalyzer0212FF_u1.jar 导入工程

修改分词器代码

// 创建中文分词器
Analyzer analyzer = new IKAnalyzer();

扩展中文词库

从ikanalyzer包中拷贝配置文件到classpath下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  

    <comment>IK Analyzer 扩展配置</comment>
    <!-- 用户可以在这里配置自己的扩展字典 -->
     <entry key="ext_dict">dicdata/mydict.dic</entry> 
     <!-- 用户可以在这里配置自己的扩展停用词字典    -->
    <entry key="ext_stopwords">dicdata/ext_stopword.dic</entry> 

</properties>

如果想配置扩展词和停用词,就创建扩展词的文件和停用词的文件,文件的编码要是utf-8。

注意:不要用记事本保存扩展词文件和停用词文件,那样的话,格式中是含有bom的。

添加扩展词文件:ext.dic,在文件中添加关键字,如:

全文搜索
路神
等等

lucene入门

发表于 2016-09-19   |  

什么是Lucene?

Lucene是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,
部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。

Lucene与搜索引擎的区别

全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统。全文检索系统是一个可以运行的系统,
包括建立索引、处理查询返回结果集、增加索引、优化索引结构等功能。例如:百度搜索、eclipse帮助搜索、淘宝网商品搜索。

搜索引擎是全文检索技术最主要的一个应用,例如百度。搜索引擎起源于传统的信息全文检索理论,
即计算机程序通过扫描每一篇文章中的每一个词,建立以词为单位的倒排文件,
检索程序根据检索词在每一篇文章中出现的频率和每一个检索词在一篇文章中出现的概率,对包含这些检索词的文章进行排序,
最后输出排序的结果。全文检索技术是搜索引擎的核心支撑技术。

Lucene和搜索引擎不同,Lucene是一套用java写的全文检索的工具包,为应用程序提供了很多个api接口去调用,
可以简单理解为是一套实现全文检索的类库,搜索引擎是一个全文检索系统,它是一个单独运行的软件。

Lucene实现全文检索的流程

全文检索的流程分为两大部分:索引流程、搜索流程。

索引流程:即采集数据 -> 构建文档对象  -> 分析文档(分词 -> 创建索引。
搜索流程:即用户通过搜索界面 -> 创建查询 -> 执行搜索,搜索器从索引库搜索 -> 渲染搜索结果。

入门程序

使用Lucene实现电商项目中图书类商品的索引和搜索功能。

环境准备

Jdk环境:1.7.0_72
Ide环境:eclipse indigo
数据库环境:mysql 5.1
Lucene:4.10.3

Lucene下载安装

Lucene是开发全文检索功能的工具包,使用时从官方网站下载,并解压。

官方网站:http://lucene.apache.org/ 
目前最新版本:5.2.1

下载地址:http://archive.apache.org/dist/lucene/java/

下载版本:4.10.3
JDK要求:1.7以上(从版本4.8开始,不支持1.7以下)

工程搭建

1.创建java工程
2.添加jar包,入门程序只需要添加以下jar包:

    mysql5.1驱动包:mysql-connector-java-5.1.7-bin.jar
    核心包:lucene-core-4.10.3.jar
    分析器通用包:lucene-analyzers-common-4.10.3.jar
    查询解析器包:lucene-queryparser-4.10.3.jar
    junit包:junit-4.9.jar

索引流程

对文档索引的过程,就是将用户要搜索的文档内容进行索引,然后把索引存储在索引库(index)中。

为什么要采集数据?

全文检索要搜索的数据信息格式多种多样,拿搜索引擎(百度, google)来说,
通过搜索引擎网站能搜索互联网站上的网页(html)、互联网上的音乐(mp3..)、视频(avi..)、pdf电子书等。
全文检索搜索的这些数据称为非结构化数据。

什么是非结构化数据?

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等。

如何对结构化数据搜索?

由于结构化数据是固定格式,所以就可以针对固定格式的数据设计算法来搜索,
比如数据库like查询,like查询采用顺序扫描法,使用关键字匹配内容,对于内容量大的like查询速度慢。

如何对非结构化数据搜索?

需要将所有要搜索的非结构化数据通过技术手段采集到一个固定的地方,
将这些非结构化的数据想办法组成结构化的数据,再以一定的算法去搜索。

如何采集数据?

采集数据技术有哪些?
1、对于互联网上网页采用http将网页抓取到本地生成html文件。 
2、如果数据在数据库中就连接数据库读取表中的数据。
3、如果数据是文件系统中的某个文件,就通过文件系统读取文件的内容。

网页采集(常用solr)

因为目前搜索引擎主要搜索数据的来源是互联网,搜索引擎使用一种爬虫程序抓取网页( 通过http抓取html网页信息),
以下是一些爬虫项目:

Solr(http://lucene.apache.org/solr) ,solr是apache的一个子项目,支持从关系数据库、xml文档中提取原始数据。

Nutch(http://lucene.apache.org/nutch), Nutch是apache的一个子项目,
包括大规模爬虫工具,能够抓取和分辨web网站数据。

jsoup(http://jsoup.org/ ),jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。
它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

heritrix(http://sourceforge.net/projects/archive-crawler/files/),Heritrix 是一个由 java 开发的、开源的网络爬虫,
用户可以使用它来从网上抓取想要的资源。其最出色之处在于它良好的可扩展性,方便用户实现自己的抓取逻辑。

数据库采集

针对电商站内搜索功能,全文检索的数据源在数据库中,需要通过jdbc访问数据库中book表的内容。

po

    public class Book {
    // 图书ID
    private Integer id;
    // 图书名称
    private String name;
    // 图书价格
    private Float price;
    // 图书图片
    private String pic;
    // 图书描述
    private String description;
}

Dao

    public interface BookDao {
        // 图书查询
        public List<Book> queryBookList() throws Exception;
        }


    public class BookDaoImpl implements BookDao {

    @Override
    public List<Book> queryBookList() throws Exception {
        // 数据库链接
        Connection connection = null;

        // 预编译statement
        PreparedStatement preparedStatement = null;

        // 结果集
        ResultSet resultSet = null;

        // 图书列表
        List<Book> list = new ArrayList<Book>();

        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 连接数据库
            connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/lucene", "root", "root");

            // SQL语句
            String sql = "SELECT * FROM book";
            // 创建preparedStatement
            preparedStatement = connection.prepareStatement(sql);

            // 获取结果集
            resultSet = preparedStatement.executeQuery();

            // 结果集解析
            while (resultSet.next()) {
                Book book = new Book();
                book.setId(resultSet.getInt("id"));
                book.setName(resultSet.getString("name"));
                book.setPrice(resultSet.getFloat("price"));
                book.setPic(resultSet.getString("pic"));
                book.setDescription(resultSet.getString("description"));
                list.add(book);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

}

索引文件的逻辑结构

文档域:
对非结构化的数据统一格式为document文档格式,一个文档有多个field域,
不同的文档其field的个数可以不同,建议相同类型的文档包括相同的field。 
本例子一个document对应一 条 book表的记录。

索引域:
用于搜索,搜索程序将从索引域中搜索一个一个词,根据词找到对应的文档。
将Document中的Field的内容进行分词,将分好的词创建索引,索引=Field域名:词

倒排索引表

传统方法是先找到文件。如何在文件中找内容,在文件内容中匹配搜索关键字,
这种方法是顺序扫描方法,数据量大就搜索慢。

倒排索引结构是根据内容(词语)找文档,倒排索引结构也叫反向索引结构,
包括索引和文档两部分,索引即词汇表,它是在索引中匹配搜索关键字,
由于索引内容量有限并且采用固定优化算法搜索速度很快,找到了索引中的词汇,词汇与文档关联,从而最终找到了文档。

创建索引

创建索引流程:

IndexWriter是索引过程的核心组件,通过IndexWriter可以创建新索引、更新索引、删除索引操作。
IndexWriter需要通过Directory对索引进行存储操作。

Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。
它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。

创建Document

采集数据的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档(Document)中包括一个一个的域(Field)。

代码实现

@Test
public void createIndex() throws Exception{
//1.采集数据
BookDao dao = new BookDaoImpl();
//2.采集的数据封装成dacoument对象
List<Book> list = dao.queryBookList();

List<Document> docList = new ArrayList<Document>();
Document doc;
for (Book book : list) {
    doc = new Document();

    //图书id
    //不分词 、 不索引、要存储
    Field idField = new StoredField("id", book.getId().toString());

    //图书名称
    //分词、索引、存储
    Field nameField = new TextField("name", book.getName().toString(),Store.YES);

    //图书价格
    //分词、索引、存储
    Field priceField = new FloatField("price", book.getPrice(),Store.YES);

    //图书图片地址
    //不分词 、 不索引、要存储
    Field picField = new StoredField("pic", book.getPic());

    //图片描述
    //分词、索引、不存储
    Field desField = new TextField("description", book.getDescription().toString(),Store.NO);

    doc.add(desField);
    doc.add(picField);
    doc.add(priceField);
    doc.add(nameField);
    doc.add(idField);

    docList.add(doc);
}

分词

分词过程

在对Docuemnt中的内容索引之前需要使用分词器进行分词 ,主要过程就是分词、过虑两步。

1.分词就是将采集到的文档内容切分成一个一个的词,具体应该说是将Document中Field的value值切分成一个一个的词。

2.过虑包括去除标点符号、去除停用词(的、是、a、an、the等)、
大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式。。。)等。 

什么是停用词?

停用词是为节省存储空间和提高搜索效率,
搜索引擎在索引页面或处理搜索请求时会自动忽略某些字或词,这些字或词即被称为Stop Words(停用词)。

比如语气助词、副词、介词、连接词等,
通常自身并无明确的意义,只有将其放入一个完整的句子中才有一定作用,如常见的“的”、“在”、“是”、“啊”等。

分词器

Tokenizer是分词器,负责将reader转换为语汇单元即进行分词,
Lucene提供了很多的分词器,也可以使用第三方的分词,比如IKAnalyzer一个中文分词器。

tokenFilter是分词过滤器,负责对语汇单元进行过滤,
tokenFilter可以是一个过滤器链儿,Lucene提供了很多的分词器过滤器,比如大小写转换、去除停用词等。

如下图是语汇单元的生成过程:

比如下边的文档经过分析器分析如下:

原文档内容:
Lucene is a Java full-text search engine.  

分析后得到的语汇单元:
lucene、java、full、text、search、engine

同一个域中相同的语汇单元(Token)对应同一个Term(词),它记录了语汇单元的内容及所在域的域名等。
不同的域中拆分出来的相同的单词对应不同的term。
相同的域中拆分出来的相同的单词对应相同的term。

例如:图书信息里面,图书名称中的java和图书描述中的java对应不同的term

代码实现

    //3.进行分词
//    Analyzer analyzer = new StandardAnalyzer();
    Analyzer analyzer = new IKAnalyzer();

    //4.创建indexWriter
    //4.1创建Directory 目录流
    IndexWriterConfig indexconfig = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);

    //Directory 是抽象类,有两个实现类,FSDirectory(文件系统) RAMDirectory(内存)
    Directory directory = FSDirectory.open(new File("F:\\index"));
    IndexWriter writer = new IndexWriter(directory,indexconfig);

    //5.通过inexWriter将document对象写入索引库
    for (Document document : docList) {
        writer.addDocument(document);
    }
    //6.关闭IndexWriter
    writer.close();
}

搜索流程

1、用户定义查询语句,用户确定查询什么内容(输入什么关键字),指定查询语法,相当于sql语句。
2、IndexSearcher索引搜索对象,定义了很多搜索方法,程序员调用此方法搜索。
3、IndexReader索引读取对象,它对应的索引维护对象IndexWriter,IndexSearcher通过IndexReader读取索引目录中的索引文件
4、Directory索引流对象,IndexReader需要Directory读取索引库,使用FSDirectory文件系统流对象
5、IndexSearcher搜索完成,返回一个TopDocs(匹配度高的前边的一些记录)

输入查询语句

同数据库的sql一样,lucene全文检索也有固定的语法:
最基本的有比如:AND, OR, NOT 等

用户想找一个description中包括java关键字和lucene关键字的文档。
它对应的查询语句:description:java AND lucene

搜索分词

和索引过程的分词一样,这里要对用户输入的关键字进行分词,一般情况索引和搜索使用的分词器一致。
比如:输入搜索关键字“java培训”,分词后为java和培训两个词,与java和培训有关的内容都搜索出来了。

搜索索引

根据关键字从索引中找到对应的索引信息,即词term。term与document相关联,
找到了term就找到了关联的document,从document取出Field中的信息即是要搜索的信息。

代码实现

    private void doSearch(Query query) {

    try {
        // 2.创建搜索器IndexSearch
        // 2.1创建IndexReader
        Directory directory = FSDirectory.open(new File("F:\\index"));
        IndexReader reader = DirectoryReader.open(directory);
        // 2.2创建Directroy

        IndexSearcher searcher = new IndexSearcher(reader);

        // 3.通过indexSearch来查询索引库
        TopDocs topDocs;
        topDocs = searcher.search(query, 10);
        // 4.处理结果
        // 根据匹配条件查询出来的数据总数
        long count = topDocs.totalHits;
        System.out.println("根据匹配条件查询出来的数据总数: " + count);

        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            int docId = scoreDoc.doc;
            Document doc = searcher.doc(docId);
            System.out.println("商品Id: " + doc.get("id"));
            System.out.println("商品名称: " + doc.get("name"));
            System.out.println("商品价格: " + doc.get("price"));
            System.out.println("商品pic: " + doc.get("pic"));
        }
        // 5.关闭IndexReader
        reader.close();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}


    @Test
    public void search() throws ParseException, IOException {
        // 1.创建查询对象
        // 1.1创建QueryParser对象

        Analyzer analyzer = new StandardAnalyzer();
        // 默认搜索的域的域名
        String field = "name";
        QueryParser parser = new QueryParser(field, analyzer);
        Query query = parser.parse("name: 花");

        doSearch(query);

    }

全文搜索引擎和lucene

发表于 2016-09-18   |  

全文搜索简介

全文检索首先将要查询的目标文档中的词提取出来,组成索引,通过查询索引达到搜索目标文档的目的。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

搜索引擎和站内搜索的区别

搜索引擎搜索的内容是全互联网各种类型的数据。

站内搜索搜索的内容只是本站内的信息,比如电商网站,搜索的功能,只会搜索本网站的商品信息。

全文检索详解

全文检索是一种将文件中所有文本与检索项匹配的检索方法。它可以根据需要获得全文中有关章、节、段、句、词等信息。
计算机程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,
当用户查询时根据建立的索引查找,类似于通过字典的检索字表查字的过程。 

经过几年的发展,全文检索从最初的字符串匹配程序已经演进到能对超大文本、语音、图像、活动影像等
非结构化数据进行综合管理的大型软件。本教程只讨论文本检索。

主要应用领域:搜索引擎(百度,搜狗)、站内搜索(微博搜索)、电商网站(京东,淘宝)

Lucene和全文检索应用的区别:

Lucene只是一个全文检索引擎工具包(jar包)
全文检索应用是一个可以运行在web应用服务器中,并且可以独立对外提供搜索和索引服务

全文检索和数据库like查询的区别

数据查询通常的做法是是通过数据库模糊匹配即Like '%keyword%'的方式,
通过它和全文检索对比来分析数据库like模糊查询和全文检索的区别。

数据结构

结构化数据

数据库中存储的数据是结构化数据,即行数据,可以用二维表结构来逻辑表达实现的数据,
结构化数据是指具有固定格式或有限长度的数据,如数据库元数据等。

非结构化数据

不方便用数据库二维逻辑表来表现的数据即称为非结构化数据,
包括所有格式的办公文档、文本、图片、标准通用标记语言下的子集XML、HTML、各类报表、图像和音频/视频信息等等。

    1.非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等。
    2.半结构数据:就是介于完全结构化数据(如关系型数据库、面向对象数据库中的数据)
      和完全无结构的数据(如声音、图像文件等)之间的数据,HTML、XML文档就属于半结构化数据,
      数据的结构和内容混在一起,没有明显的区分。

搜索原理

顺序扫描

数据库的like查询采用顺序扫描的方法匹配字符串,查找结构化数据中存在某字符串的记录,如下:
查询table表中title字段出现XXXX字符的记录。
select * from table where title like ‘%XXXX%’

windows的搜索也是顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的搜索,
对于每一个文档,从头找到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。

顺序扫描问题:如果要查询的目标数据源量大且内容多,采用顺序扫描方法查询过程较慢,
比如你有一个几十G的硬盘,如果想在上面找到一个内容包含某字符串的文件,将会非常耗时。
什么时候使用顺序扫描?对于查询的目标数据源量小、内容少的情况时采用顺序扫描是很快的。

全文检索

对于查询目标数据源量大且内容多时,特别是如果查询的数据源为非结构化数据,这时就要采用全文检索方法进行查询。
全文检索首先将要查询的目标数据源中的一部分信息提取出来,组成索引,通过查询索引达到搜索目标数据源的目的,所以速度较快。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

搜索效果

匹配准确性

使用数据库like搜索关键字“java”会把“javascript”也查询出来,因为javascript和'%java%'匹配。
使用搜索引擎搜索关键字“java”不会把“javascript”查询出来,
因为在对“javascript”创建索引时不会把“java”抽取出来放在索引中,
而是把“javascript”当成一个整体放在索引中,在进行全文检索时根据“java”在索引中找不到,通过“javascript”是可以找到的。

相关度排序

使用数据库like搜索关键字“java”,查询结果中不会把与关键字相关度最高的记录排在最前边,
数据库的排序只能根据由高到低或按字母顺序排序。
使用搜索引擎搜索关键字“java”,查询结果中会把关键字相关度最高的记录排在最前边,
在进行全文检索时会计算哪些记录与关键字的相关度最高,最高相关度的记录会排在前边。

搜索速度

使用数据库like搜索,如果目标数据源记录多且内容大,查询速度慢。
使用搜索引擎搜索,速度非常快。

应用领域

数据库like查询

对于数据量不大、数据结构固定的数据可采用关系数据库存储,通过关系数据库提供的模糊匹配方式查询用户需要的数据,
比如学校的学生管理系统、企业人事管理系统等。

全文检索

对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,
比如百度、Google等搜索引擎、论坛站内搜索、电商网站站内搜索等。

jedis连接集群

发表于 2016-09-18   |  

Spring配置jedis

<!-- 连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="30" />
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10" />    
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true" />
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="true" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false" />
</bean>
!-- redis集群 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg index="0">
    <set>
        <bean class="redis.clients.jedis.HostAndPort">
            <constructor-arg index="0" value="115.159.93.201"></constructor-arg>
            <constructor-arg index="1" value="7001"></constructor-arg>
        </bean>
        <bean class="redis.clients.jedis.HostAndPort">
            <constructor-arg index="0" value="115.159.93.201"></constructor-arg>
            <constructor-arg index="1" value="7002"></constructor-arg>
        </bean>
        <bean class="redis.clients.jedis.HostAndPort">
            <constructor-arg index="0" value="115.159.93.201"></constructor-arg>
            <constructor-arg index="1" value="7003"></constructor-arg>
        </bean>
        <bean class="redis.clients.jedis.HostAndPort">  
            <constructor-arg index="0" value="115.159.93.201"></constructor-arg>
            <constructor-arg index="1" value="7004"></constructor-arg>
        </bean>
        <bean class="redis.clients.jedis.HostAndPort">
            <constructor-arg index="0" value="115.159.93.201"></constructor-arg>
            <constructor-arg index="1" value="7005"></constructor-arg>
        </bean>
        <bean class="redis.clients.jedis.HostAndPort">
            <constructor-arg index="0" value="115.159.93.201"></constructor-arg>
            <constructor-arg index="1" value="7006"></constructor-arg>
        </bean>
    </set>
</constructor-arg>
<constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
</bean>

测试

private ApplicationContext applicationContext;
@Before    
public void init() {
    applicationContext = new ClassPathXmlApplicationContext(
            "classpath:applicationContext.xml");
}

// redis集群    
@Test
public void testJedisCluster() {
    JedisCluster jedisCluster = (JedisCluster) applicationContext
            .getBean("jedisCluster");

    jedisCluster.set("name", "zhangsan");
    String value = jedisCluster.get("name");
    System.out.println(value);
}

服务器配置(五)redis集群搭建

发表于 2016-09-16   |  

redis-cluster架构图

(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,
redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,
这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,
redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

集群搭建

集群中应该至少有三个节点,每个节点有一备份节点。需要6台服务器。
搭建伪分布式,需要6个redis实例。
搭建集群的步骤:

第一步:创建6个redis实例指定端口从7001到7006

在/usr/local目录下新建一个redis-cluster目录
将/usr/local/redis0707目录下的bin复制到redis-cluster,并改名为redis01
复制多个cp -r redis01/ redis02

第二步:修改redis.conf 打开Cluster-enable yes前面的注释。

将632行的注释打开
45行端口号分别改为7001 7002 ...... 7006

第三步:需要一个ruby脚本。在redis源码文件夹下的src目录下 redis-trib.rb
第四步:把redis-trib.rb文件复制到到redis-cluster目录下。

cp redis-trib.rb /usr/local/redis-cluster/

第五步:执行ruby脚本之前,需要安装ruby环境。

1、yum install ruby    
2、yum install rubygems
3、安装redis-trib.rb运行依赖的ruby的包。
[root@bogon ~]# gem install redis-3.0.0.gem

第六步:启动所有的redis实例。

在/usr/local/redis-cluster 目录下新建一个startup-all.sh文件,内容如下:

cd redis01
./redis-server redis.conf
cd ..
cd redis02
./redis-server redis.conf
cd ..
cd redis03
./redis-server redis.conf
cd ..
cd redis04
./redis-server redis.conf
cd ..
cd redis05
./redis-server redis.conf
cd ..
cd redis06
./redis-server redis.conf
cd ..


在redis-cluster目录下,chmod +x startup-all.sh 修改文件权限
./start-all.sh 执行命令,启动6个redis

ps aux|grep redis 查看redis是否启动

第七步:使用redis-trib.rb创建集群。

./redis-trib.rb create --replicas 1 115.159.93.201:7001 115.159.93.201:7002 115.159.93.201:7003 115.159.93.201:7004 115.159.93.201:7005 115.159.93.201:7006

使用客户端连接集群: ./redis-cli -h 115.159.93.201 -p 7001 -c

维护节点

集群创建成功后可以向集群中添加节点,下面是添加一个master主节点

添加7007结点作为新节点:

在/usr/local/redis-cluster
./redis-trib.rb add-node 115.159.93.201:7007 115.159.93.201:7001

查看集群状态:

cluster info

查看集群中的节点:

cluster nodes

hash槽重新分配

添加完主节点需要对主节点进行hash槽分配,这样该主节才可以存储数据。

查看集群中槽占用情况:

cluster nodes
redis集群有16384个槽,集群中的每个结点分配自已槽,通过查看集群结点可以看到槽占用情况。

给刚添加的7007结点分配槽:

1.连接集群上的任意一个节点
  ./redis-trib.rb reshard 192 115.159.93.201:7001 

2.输入要分配的槽数量
  how many slots do you want to move (from 1 to 16384)? 500

3.给7007分配槽,通过cluster nodes查看7007节点的id:
  8acc53cd234cb4bcc1d4f3e0e237f2655bc9e984

4.输入源节点id:
  source node#1: all

5.输入yes开始移动槽点到目标节点

添加从节点

添加7008从结点,将7008作为7007的从结点

./redis-trib.rb add-node --slave --master-id 主节点id 新节点的ip和端口 旧节点ip和端口
./redis-trib.rb add-node --slave --master-id 8acc53cd234cb4bcc1d4f3e0e237f2655bc9e984  115.159.93.201:7008 115.159.93.201:7007

redis持久化及主从复制

发表于 2016-09-16   |  

Redis持久化方案

RDB持久化

RDB方式的持久化是通过快照(snapshotting)完成的,
当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
RDB是Redis默认采用的持久化方式。

在redis.conf配置文件中默认有此下配置:

save 900 1
save 300 10
save 60 10000

save 开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之间是“或”的关系。

“save 900 1”表示15分钟(900秒钟)内至少1个键被更改则进行快照。

“save 300 10”表示5分钟(300秒)内至少10个键被更改则进行快照。

配置dir指定rdb快照文件的位置:

# Note that you must specify a directory here, not a file name.
dir ./

Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。
根据数据量大小与结构和服务器性能不同,这个时间也不同。
通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。

RDB问题总结

通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。
这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。
如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。

AOF持久化

默认情况下Redis没有开启AOF(append only file)方式的持久化

可以通过修改redis.conf配置文件中的appendonly参数开启

appendonly yes

开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬
盘中的AOF文件。

AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的。

dir ./

默认的文件名是appendonly.aof,可以通过appendfilename参数修改:

appendfilename appendonly.aof

Redis的主从复制

持久化保证了即使redis服务重启也不会丢失数据,
因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,
但是当redis服务器的硬盘损坏了可能会导致数据丢失,
如果通过redis的主从复制机制就可以避免这种单点故障,如下图:

主redis中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。
主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。
只有一个主redis,可以有多个从redis。
主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求
一个redis可以即是主又是从

主从配置

修改从redis服务器上的redis.conf文件

# slaveof <masterip> <masterport>
slaveof 192.168.101.3 6379

上边的配置说明当前该【从redis服务器】所对应的【主redis服务器】的IP是192.168.101.3,
端口是6379。

redis数据类型Set命令

发表于 2016-09-16   |  

Redis Set介绍

集合中的数据是不重复且没有顺序。
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,
由于集合类型的Redis内部是使用值为空的散列表实现,
所有这些操作的时间复杂度都为0(1)。 
Redis还提供了多个集合之间的交集、并集、差集的运算。

SADD key member [member …] SREM key member [member …] 增加/删除元素

127.0.0.1:6379> sadd set a b c
(integer) 3
127.0.0.1:6379> sadd set a
(integer) 0

127.0.0.1:6379> srem set c d
(integer) 1

SMEMBERS key 获得集合中的所有元素

127.0.0.1:6379> smembers set
1) "b"
2) "a”

SISMEMBER key member 判断元素是否在集合中

127.0.0.1:6379> sismember set a
(integer) 1
127.0.0.1:6379> sismember set h
(integer) 0

SDIFF key [key …] 集合的差集运算 A-B

属于A并且不属于B的元素构成的集合

127.0.0.1:6379> sadd setA 1 2 3
(integer) 3
127.0.0.1:6379> sadd setB 2 3 4
(integer) 3
127.0.0.1:6379> sdiff setA setB 
1) "1"
127.0.0.1:6379> sdiff setB setA 
1) "4"

SINTER key [key …] 集合的交集运算 A ∩ B

属于A且属于B的元素构成的集合

127.0.0.1:6379> sinter setA setB 
1) "2"
2) "3"

SUNION key [key …] 集合的并集运算 A ∪ B

属于A或者属于B的元素构成的集合

127.0.0.1:6379> sunion setA setB
1) "1"
2) "2"
3) "3"
4) "4"

SCARD key 获得集合中元素的个数

127.0.0.1:6379> smembers setA 
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> scard setA 
(integer) 3

SPOP key 从集合中弹出一个元素

由于集合是无序的,所有SPOP命令会从集合中随机选择一个元素弹出
127.0.0.1:6379> spop setA 
"1"

SortedSet类型zset

在集合类型的基础上,有序集合类型为集合中的每个元素都关联一个分数,
这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,
还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。 

在某些方面有序集合和列表类型有些相似。

1、二者都是有序的。 
2、二者都可以获得某一范围的元素。 

但是,二者有着很大区别:

1、列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会变慢。 
2、有序集合类型使用散列表实现,所有即使读取位于中间部分的数据也很快。 
3、列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改分数实现) 
4、有序集合要比列表类型更耗内存。 

ZADD key score member [score member …] 增加元素

向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。
返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。 

127.0.0.1:6379> zadd scoreboard 80 zhangsan 89 lisi 94 wangwu 
(integer) 3
127.0.0.1:6379> zadd scoreboard 97 lisi 
(integer) 0

ZSCORE key member 获取元素的分数

127.0.0.1:6379> zscore scoreboard lisi 
"97"

ZREM key member [member …] 删除元素

移除有序集key中的一个或多个成员,不存在的成员将被忽略。
当key存在但不是有序集类型时,返回一个错误。

127.0.0.1:6379> zrem scoreboard lisi
(integer) 1

ZRANGE key start stop [WITHSCORES] 获得排名在某个范围的元素列表

按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)

127.0.0.1:6379> zrange scoreboard 0 2
1) "zhangsan"
2) "wangwu"
3) "lisi“

ZREVRANGE key start stop [WITHSCORES]

按照元素分数从大到小的顺序返回索引从start到stop之间的所有元素(包含两端的元素)

127.0.0.1:6379> zrevrange scoreboard 0 2
1) " lisi "
2) "wangwu"
3) " zhangsan “

如果需要获得元素的分数的可以在命令尾部加上WITHSCORES参数 

127.0.0.1:6379> zrange scoreboard 0 1 WITHSCORES
1) "zhangsan"
2) "80"
3) "wangwu"
4) "94"

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
获得指定分数范围的元素

127.0.0.1:6379> ZRANGEBYSCORE scoreboard 90 97 WITHSCORES
1) "wangwu"
2) "94"
3) "lisi"
4) "97"
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 70 100 limit 1 2
1) "wangwu"
2) "lisi"

ZINCRBY key increment member 增加某个元素的分数,返回值是更改后的分数

127.0.0.1:6379> ZINCRBY scoreboard 4 lisi 
"101“

ZCARD key 获得集合中元素的数量

127.0.0.1:6379> ZCARD scoreboard
(integer) 3

ZCOUNT key min max 获得指定分数范围内的元素个数

127.0.0.1:6379> ZCOUNT scoreboard 80 90
(integer) 1

ZREMRANGEBYRANK key start stop 按照排名范围删除元素

127.0.0.1:6379> ZREMRANGEBYRANK scoreboard 0 1
(integer) 2 
127.0.0.1:6379> ZRANGE scoreboard 0 -1
1) "lisi"

ZREMRANGEBYSCORE key min max 按照分数范围删除元素

127.0.0.1:6379> zadd scoreboard 84 zhangsan    
(integer) 1
127.0.0.1:6379> ZREMRANGEBYSCORE scoreboard 80 100
(integer) 1

ZRANK key member 获取元素的排名 从小到大

127.0.0.1:6379> ZRANK scoreboard lisi 
(integer) 0

ZREVRANK key member 从大到小

127.0.0.1:6379> ZREVRANK scoreboard zhangsan 
(integer) 1

ZSet应用

商品销售排行榜

需求:根据商品销售量对商品进行排行显示
思路:定义商品销售排行榜(sorted set集合),Key为items:sellsort,分数为商品销售量。

写入商品销售量:商品编号1001的销量是9,商品编号1002的销量是10
192.168.101.3:7007> ZADD items:sellsort 9 1001 10 1002

商品编号1001的销量加1
192.168.101.3:7001> ZINCRBY items:sellsort 1 1001

商品销量前10名
192.168.101.3:7001> ZRANGE items:sellsort 0 9 withscores
12…4
Jessyon

Jessyon

35 日志
35 标签
© 2016 Jessyon
由 Hexo 强力驱动
主题 - NexT.Pisces