Coreseek 使用指南

Coreseek 使用指南

coreseek实际就是集成了中文分词组件mmseg的sphinx0.9/1.x的版本。

安装

1.mmseg

先安装mmseg

cd mmseg-3.2.14
./configure --prefix=/usr/local/mmseg

需要automake、libtool支持,如果报错

config.status: error: cannot find input file: src/Makefile.in

那么依次:

aclocal
libtoolize --force
automake --add-missing
autoconf
autoheader
make clean
./configure --prefix=/usr/local/mmseg
make && make install

这个prefix就是安装路径,后面安装csft要用。

gcc版本高,编译会报错,在2个cpp文件中有初始化字符数组越界。

应该是设置了-Werror选项,尝试编辑了一下configure文件,找到ac_cxx_werror_flag=yes这一行,改成no,结果还是不行。所以只好改下代码,找到这两个文件,src/mmseg_main.cpp和src/css/SynonymsDict.cpp,其中的char txtHead[3]这个列表初始化,改成txtHead[0]=239,txtHead[1]=187这样,至此可以编译通过。

2.csft-3.2.14

安装csft-3.2.14

./configure --prefix=/usr/local/coreseek --with-mysql --with-python --with-mmseg=/usr/local/mmseg --with-mmseg-includes=/usr/local/mmseg/include/mmseg/ --with-mmseg-libs=/usr/local/mmseg/lib/
make && make install

这个prefix是sphinx的路径,安装成功以后,二进制文件都在/usr/local/coreseek/bin下。with-mysql是提供mysql支持,如果你的mysql头文件不在系统路径里,你需要–with-mysql=…来选择mysql的路径。后面的就是mmseg文件的路径。

但编译一定会失败,需要修改代码:

  1. sphinxexpr.cpp这个文件,所有的
    T val = ExprEval ( this->m_pArg, tMatch );
    

    替换成

    T val = this->ExprEval ( this->m_pArg, tMatch );
    

    这个也是gcc版本的问题。

  2. indexer.cpp这个文件里,有一个返回值的错误,用false返回了空指针(C++03的语法),现在被视作不可用的类型转换,会报错。找到SpawnSourceXMLPipe这个函数,里面调用了LOC_CHECK这个宏定义,这个定义返回的是bool,和函数返回类型不一致。修改调用的地方,找到LOC_CHECK定义的地方,然后直接复制过来,把返回值false改成nullptr,不用这个宏定义了。接下来就能过编译了。

至此安装就成功了,不过这个版本是没有实时索引的。

3.csft 4.1

这个版本要通过脚本自动生成configure文件

首先修改configure.ac

找到

AM_INIT_AUTOMAKE([-Wall -Werror foreign]) 

去掉 -Werror,然后在文件末尾添加

AM_PROG_AR

其次修改Makefile.am,加上

AUTOMAKE_OPTIONS = subdir-objects

根目录和libstemmer_c目录下的Makefile.am都要修改。

然后./buildconf.sh,可能会报错,缺少某些组件,执行

automake --add-missing

然后应该就能生成configure了。

接下来编译,已知的错误如下:

  1. 和3.2.14一样,几个文件里的ExprEval报错,按照上面给出的方法修改。

  2. sphinx.cpp 3446行,修改返回值为nullptr。

  3. sphinxsort.cpp中,找到

    #define LOC_CHECK(cond,msg) if (!(_cond)) { sError = "invalid schema: " _msg; return false; }
    

    这个宏定义,返回值改成nullptr。

  4. py_layer.cpp,还是LOC_CHECK宏定义,返回值改成nullptr。发现在这个原文件里很多用bool的false作对象指针的返回值,这是c++03的语法,现在已经无法通过编译。所以多次编译,修改所有的这些语法错误,返回值都改成nullptr。

  5. 最后indexer.cpp的SpawnSourceXMLPipe方法,宏定义替换掉。方法上面介绍过。

现在能通过编译了。

./configure --prefix=/usr/local/coreseek --with-mysql --with-mmseg=/usr/local/mmseg --with-mmseg-includes=/usr/local/mmseg/include/mmseg/ --with-mmseg-libs=/usr/local/mmseg/lib/
make && make install

我提供了已经修改好的一份csft-4.1+csft-3.2.14+mmseg3代码,在gcc 7.3通过编译:coreseek-fixed.tar。这里面有已经编译好的二进制文件,不依赖环境,应该可以直接运行。可以直接根据上面给出的configure命令,把二进制文件复制到相应的位置使用。

使用

经过上面的步骤,coreseek+mmseg已经安装完成,接下来我将根据上面的安装选项介绍如何使用。

1.配置文件

配置文件的路径,默认为/usr/local/coreseek/etc/csft.conf。可以从testpack中给出的测试用例里面,复制一份然后改写。每项配置的具体说明已写在注释中供参考。

#MySQL数据源配置,详情请查看:http://www.coreseek.cn/products-install/mysql/
#请先将var/test/documents.sql导入数据库,并配置好以下的MySQL用户密码数据库

#源定义
source mysql
{
    type                    = mysql
    sql_host                = localhost
    sql_user                = root
    sql_pass                = sa
    sql_db                  = test
    sql_port                = 3306
    sql_query_pre           = SET NAMES utf8
    sql_query               = SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS ate_added, title, content FROM documents
    #sql_query第一列id需为整数
    #title、content作为字符串/文本字段,被全文索引
    #接下来是属性的设置,属性是存在索引中的,它不进行全文索引,但是可以用于过滤和排序。
    sql_attr_uint            = group_id           #从SQL读取到的值必须为整数
    sql_attr_timestamp        = date_added #从SQL读取到的值必须为整数,作为时间属性,经常被用于做排序
    sql_attr_multi = uint tag from query;\
        SELECT id,tag FROM label
    #MVA属性
    sql_query_info_pre      = SET NAMES utf8                                        #命令行查询时,设置正确的字符集,如果不设置的话,无法通过test.py和search二进制程序进行中文的查询
    sql_query_info            = SELECT * FROM documents WHERE id=$id #命令行查询时,从数据库读取原始数据信息
    #数值类型uint,bool,timestamps,float,string,MVA(前面都是sql_attr_的前缀),被列出来的属性会作为查询的返回值。在这个例子中,查询会返回id,weight,group_id和date_added三个字段,id和weight是系统自动返回的,group_id和date_added则是我们设定的。只有字符串/文本字段会被索引。
    #设定了sql_attr_的字段,会直接被存储,不参与索引,并出现在sphinx提供的mysql接口中的表里,成为一个表项。通过python/php的api进行查询时,会返回被设定sql_attr_的表项。
    #MVA是多值属性用于将多个附加值赋给同一个属性。
}

#index定义
index mysql
{
    source            = mysql             #对应的source名称
    path            = /usr/local/coreseek/var/data/mysql #请修改为实际使用的绝对路径,例如:/usr/local/coreseek/var/...
    docinfo            = extern #这个类型下面会解释
    mlock            = 0
    morphology        = none
    min_word_len        = 1
    html_strip                = 0

    #中文分词配置,详情请查看:http://www.coreseek.cn/products-install/coreseek_mmseg/
    charset_dictpath = /usr/local/mmseg3/etc/ #BSD、Linux环境下设置,/符号结尾
    #charset_dictpath = etc/                             #Windows环境下设置,/符号结尾,最好给出绝对路径,例如:C:/usr/local/coreseek/etc/...
    charset_type        = zh_cn.utf-8
}

#实时索引
index rt
{
    type            = rt
    path            = /usr/local/coreseek/var/data/rtindex #请修改为实际使用的绝对路径,例如:/usr/local/coreseek/var/...
    docinfo            = extern
    mlock            = 0
    morphology        = none
    min_word_len        = 1
    html_strip                = 0

    #中文分词配置,详情请查看:http://www.coreseek.cn/products-install/coreseek_mmseg/
    charset_dictpath = /usr/local/mmseg/etc/ #BSD、Linux环境下设置,/符号结尾
    #charset_dictpath = etc/                             #Windows环境下设置,/符号结尾,最好给出绝对路径,例如:C:/usr/local/coreseek/etc/...
    charset_type        = zh_cn.utf-8

    #RT实时索引字段配置,详情请查看:http://www.coreseek.cn/products-install/rt-indexes/
    #字段设置顺序:field, uint, bigint, float, timestamp, string;顺序不可颠倒,否则产生混乱
    #使用后,不可更改字段设置,除非删除所有索引文件重新建立,否则产生混乱

    #文档编号字段
    #id                                               #系统自动处理

    #全文索引字段
    rt_field                  = title               #全文索引字段
    rt_field                  = content         #全文索引字段

    #属性字段
    rt_attr_uint            = groupid
    rt_attr_bigint         = biguid
    rt_attr_float           = score
    rt_attr_timestamp  = date_added

    #存储内容字段
    rt_attr_string          = author          #存储author的内容

    #已设置全文索引,并需要同时存储内容的字段
    rt_attr_string          = title              #同时存储title的内容
    rt_attr_string          = content        #同时存储content的内容

    #RT实时索引内存设置
    rt_mem_limit = 512M
}

}

#全局index定义
indexer
{
    mem_limit            = 128M
}

#searchd服务定义
searchd
{
    listen                  =   9312
    read_timeout        = 5
    max_children        = 30
    max_matches            = 1000
    seamless_rotate        = 0
    preopen_indexes        = 0
    unlink_old            = 1
    pid_file = /usr/local/coreseek/var/log/searchd_mysql.pid  #请修改为实际使用的绝对路径,例如:/usr/local/coreseek/var/...
    log = /usr/local/coreseek/var/log/searchd_mysql.log        #请修改为实际使用的绝对路径,例如:/usr/local/coreseek/var/...
    query_log = /usr/local/coreseek/var/log/query_mysql.log #请修改为实际使用的绝对路径,例如:/usr/local/coreseek/var/...
    binlog_path =  /usr/local/coreseek/var/log/rtindex                     #二进制日志
    binlog_flush = 2
    binlog_max_log_size = 16M
}

详细的设置比较复杂。我在网上找到了一篇比较详细的中文的sphinx3.x的配置文件:

## 数据源src1
source src1
{
    ## 说明数据源的类型。数据源的类型可以是:mysql,pgsql,mssql,xmlpipe,odbc,python
    ## 有人会奇怪,python是一种语言怎么可以成为数据源呢?
    ## python作为一种语言,可以操作任意其他的数据来源来获取数据,更多数据请看:(http://www.coreseek.cn/products-install/python/)
    type            = mysql

    ## 下面是sql数据库特有的端口,用户名,密码,数据库名等。
    sql_host        = localhost
    sql_user        = test
    sql_pass        =
    sql_db          = test
    sql_port        = 3306

    ## 如果是使用unix sock连接可以使用这个。
    # sql_sock      = /tmp/mysql.sock

    ## indexer和mysql之间的交互,需要考虑到效率和安全性。
    ## 比如考虑到效率,他们两者之间的交互需要使用压缩协议;考虑到安全,他们两者之间的传输需要使用ssl
    ## 那么这个参数就代表这个意思,0/32/2048/32768  无/使用压缩协议/握手后切换到ssl/Mysql 4.1版本身份认证。
    # mysql_connect_flags   = 32

    ## 当mysql_connect_flags设置为2048(ssl)的时候,下面几个就代表ssl连接所需要使用的几个参数。
    # mysql_ssl_cert        = /etc/ssl/client-cert.pem
    # mysql_ssl_key     = /etc/ssl/client-key.pem
    # mysql_ssl_ca      = /etc/ssl/cacert.pem

    ## mssql特有,是否使用windows登陆
    # mssql_winauth     = 1

    ## mssql特有,是使用unicode还是单字节数据。
    # mssql_unicode     = 1 # request Unicode data from server

    ## odbc的dsn串
    # odbc_dsn      = DBQ=C:\data;DefaultDir=C:\data;Driver={Microsoft Text Driver (*.txt; *.csv)};

    ## sql某一列的缓冲大小,一般是针对字符串来说的。
    ## 为什么要有这么一种缓冲呢?
    ## 有的字符串,虽然长度很长,但是实际上并没有使用那么长的字符,所以在Sphinx并不会收录所有的字符,而是给每个属性一个缓存作为长度限制。
    ## 默认情况下非字符类型的属性是1KB,字符类型的属性是1MB。
    ## 而如果想要配置这个buffer的话,就可以在这里进行配置了。
    # sql_column_buffers    = content=12M, comments=1M

    ## indexer的sql执行前需要执行的操作。
    # sql_query_pre     = SET NAMES utf8
    # sql_query_pre     = SET SESSION query_cache_type=OFF

    ## indexer的sql执行语句
    sql_query       = \
        SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content \
        FROM documents

    ## 有的时候有多个表,我们想要查询的字段在其他表中。这个时候就需要对sql_query进行join操作。
    ## 而这个join操作可能非常慢,导致建立索引的时候特别慢,那么这个时候,就可以考虑在sphinx端进行join操作了。
    ## sql_joined_field是增加一个字段,这个字段是从其他表查询中查询出来的。
    ## 这里封号后面的查询语句是有要求的,如果是query,则返回id和查询字段,如果是payload-query,则返回id,查询字段和权重。
    ## 并且这里的后一个查询需要按照id进行升序排列。
    # sql_joined_field  = tags from query; SELECT docid, CONCAT('tag',tagid) FROM tags ORDER BY docid ASC
    # sql_joined_field  = wtags from payload-query; SELECT docid, tag, tagweight FROM tags ORDER BY docid ASC

    ## 外部文件字段,意思就是一个表中,有一个字段存的是外部文件地址,但是实际的字段内容在文件中。比如这个字段叫做content_file_path。
    ## 当indexer建立索引的时候,查到这个字段,就读取这个文件地址,然后加载,并进行分词和索引建立等操作。
    # sql_file_field        = content_file_path

    ## 当数据源数据太大的时候,一个sql语句查询下来往往很有可能锁表等操作。
    ## 那么我么就可以使用多次查询,那么这个多次查询就需要有个范围和步长,sql_query_range和sql_range_step就是做这个使用的。
    ## 获取最大和最小的id,然后根据步长来获取数据。比如下面的例子,如果有4500条数据,这个表建立索引的时候就会进行5次sql查询。 
    ## 而5次sql查询每次的间隔时间是使用sql_ranged_rhrottle来进行设置的。单位是毫秒。
    # sql_query_range       = SELECT MIN(id),MAX(id) FROM documents
    # sql_range_step        = 1000
    # sql_ranged_throttle   = 0

    ## 下面都是些不同属性的数据了
    ## 先要了解属性的概念:属性是存在索引中的,它不进行全文索引,但是可以用于过滤和排序。

    ## uint无符号整型属性
    sql_attr_uint       = group_id

    ## bool属性
    # sql_attr_bool     = is_deleted

    ## 长整型属性
    # sql_attr_bigint       = my_bigint_id

    ## 时间戳属性,经常被用于做排序
    sql_attr_timestamp  = date_added

    ## 字符串排序属性。一般我们按照字符串排序的话,我们会将这个字符串存下来进入到索引中,然后在查询的时候比较索引中得字符大小进行排序。
    ## 但是这个时候索引就会很大,于是我们就想到了一个方法,我们在建立索引的时候,先将字符串值从数据库中取出,暂存,排序。
    ## 然后给排序后的数组分配一个序号,然后在建立索引的时候,就将这个序号存入到索引中去。这样在查询的时候也就能完成字符串排序的操作。
    ## 这,就是这个字段的意义。
    # sql_attr_str2ordinal  = author_name

    ## 浮点数属性,经常在查询地理经纬度的时候会用到。
    # sql_attr_float        = lat_radians
    # sql_attr_float        = long_radians

    ## 多值属性(MVA)
    ## 试想一下,有一个文章系统,每篇文章都有多个标签,这个文章就叫做多值属性。
    ## 我要对某个标签进行查询过滤,那么在建立查询的时候就应该把这个标签的值放入到索引中。
    ## 这个字段,sql_attr_multi就是用来做这个事情的。
    # sql_attr_multi        = uint tag from query; SELECT docid, tagid FROM tags
    # sql_attr_multi        = uint tag from ranged-query; \
    #   SELECT docid, tagid FROM tags WHERE id>=$start AND id<=$end; \
    #   SELECT MIN(docid), MAX(docid) FROM tags

    ## 字符串属性。
    # sql_attr_string       = stitle

    ## 文档词汇数记录属性。比如下面就是在索引建立的时候增加一个词汇数的字段
    # sql_attr_str2wordcount    = stitle

    ## 字符串字段,可全文搜索,可返回原始文本信息。
    # sql_field_string  = author

    ## 文档词汇数记录字段,可全文搜索,可返回原始信息
    # sql_field_str2wordcount   = title

    ## 取后查询,在sql_query执行后立即操作。
    ## 它和sql_query_post_index的区别就是执行时间不同
    ## sql_query_post是在sql_query执行后执行,而sql_query_post_index是在索引建立完成后才执行。
    ## 所以如果要记录最后索引执行时间,那么应该在sql_query_post_index中执行。
    # sql_query_post        =

    ## 参考sql_query_post的说明。
    # sql_query_post_index  = REPLACE INTO counters ( id, val ) \
    #   VALUES ( 'max_indexed_id', $maxid )

    ## 命令行获取信息查询。
    ## 什么意思呢?
    ## 我们进行索引一般只会返回主键id,而不会返回表中的所有字段。
    ## 但是在调试的时候,我们一般需要返回表中的字段,那这个时候,就需要使用sql_query_info。
    ## 同时这个字段只在控制台有效,在api中是无效的。
    sql_query_info      = SELECT * FROM documents WHERE id=$id

    ## 比如有两个索引,一个索引比较旧,一个索引比较新,那么旧索引中就会有数据是旧的。
    ## 当我要对两个索引进行搜索的时候,哪些数据要按照新的索引来进行查询呢。
    ## 这个时候就使用到了这个字段了。
    ## 这里的例子(http://www.coreseek.cn/docs/coreseek_4.1-sphinx_2.0.1-beta.html#conf-sql-query-killlist)给的非常清晰了。
    # sql_query_killlist    = SELECT id FROM documents WHERE edited>=@last_reindex

    ## 下面几个压缩解压的配置都是为了一个目的:让索引重建的时候不要影响数据库的性能表现。
    ## SQL数据源解压字段设置
    # unpack_zlib       = zlib_column
    ## MySQL数据源解压字段设置
    # unpack_mysqlcompress  = compressed_column
    # unpack_mysqlcompress  = compressed_column_2
    ## MySQL数据源解压缓冲区设置
    # unpack_mysqlcompress_maxsize  = 16M


    ## xmlpipe的数据源就是一个xml文档
    # type          = xmlpipe

    ## 读取数据源的命令
    # xmlpipe_command       = cat /home/yejianfeng/instance/coreseek/var/test.xml

    ## 字段
    # xmlpipe_field     = subject
    # xmlpipe_field     = content

    ## 属性
    # xmlpipe_attr_timestamp    = published
    # xmlpipe_attr_uint = author_id

    ## UTF-8修复设置
    ## 只适用xmlpipe2数据源,数据源中有可能有非utf-8的字符,这个时候解析就有可能出现问题
    ## 如果设置了这个字段,非utf-8序列就会全部被替换为空格。
    # xmlpipe_fixup_utf8    = 1
}

## sphinx的source是有继承这么一种属性的,意思就是除了父source之外,这个source还有这个特性
source src1throttled : src1
{
    sql_ranged_throttle = 100
}

## 索引test1
index test1
{
    ## 索引类型,包括有plain,distributed和rt。分别是普通索引/分布式索引/增量索引。默认是plain。
    # type          = plain

    ## 索引数据源
    source          = src1
    ## 索引文件存放路径
    path            = /home/yejianfeng/instance/coreseek/var/data/test1

    ## 文档信息的存储模式,包括有none,extern,inline。默认是extern。
    ## docinfo指的就是数据的所有属性(field)构成的一个集合。
    ## 首先文档id是存储在一个文件中的(spa)
    ## 当使用inline的时候,文档的属性和文件的id都是存放在spa中的,所以进行查询过滤的时候,不需要进行额外操作。
    ## 当使用extern的时候,文档的属性是存放在另外一个文件(spd)中的,但是当启动searchd的时候,会把这个文件加载到内存中。
    ## extern就意味着每次做查询过滤的时候,除了查找文档id之外,还需要去内存中根据属性进行过滤。
    ## 但是即使这样,extern由于文件大小小,效率也不低。所以不是有特殊要求,一般都是使用extern
    docinfo         = extern

    ## 缓冲内存锁定。
    ## searchd会讲spa和spi预读取到内存中。但是如果这部分内存数据长时间没有访问,则它会被交换到磁盘上。
    ## 设置了mlock就不会出现这个问题,这部分数据会一直存放在内存中的。
    mlock           = 0

    ## 词形处理器
    ## 词形处理是什么意思呢?比如在英语中,dogs是dog的复数,所以dog是dogs的词干,这两个实际上是同一个词。
    ## 所以英语的词形处理器会讲dogs当做dog来进行处理。
    morphology      = none

    ## 词形处理有的时候会有问题,比如将gps处理成gp,这个设置可以允许根据词的长度来决定是否要使用词形处理器。
    # min_stemming_len  = 1

    ## 词形处理后是否还要检索原词?
    # index_exact_words = 1

    ## 停止词,停止词是不被索引的词。
    # stopwords     = /home/yejianfeng/instance/coreseek/var/data/stopwords.txt

    ## 自定义词形字典
    # wordforms     = /home/yejianfeng/instance/coreseek/var/data/wordforms.txt

    ## 词汇特殊处理。
    ## 有的一些特殊词我们希望把它当成另外一个词来处理。比如,c++ => cplusplus来处理。
    # exceptions        = /home/yejianfeng/instance/coreseek/var/data/exceptions.txt

    ## 最小索引词长度,小于这个长度的词不会被索引。
    min_word_len        = 1

    ## 字符集编码类型,可以为sbcs,utf-8。对于Coreseek,还可以有zh_cn.utf-8,zh_ch.gbk,zh_ch.big5
    charset_type        = sbcs

    ## 字符表和大小写转换规则。对于Coreseek,这个字段无效。
    # 'sbcs' default value is
    # charset_table     = 0..9, A..Z->a..z, _, a..z, U+A8->U+B8, U+B8, U+C0..U+DF->U+E0..U+FF, U+E0..U+FF
    #
    # 'utf-8' default value is
    # charset_table     = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F

    ## 忽略字符表。在忽略字符表中的前后词会被连起来当做一个单独关键词处理。
    # ignore_chars      = U+00AD

    ## 是否启用通配符,默认为0,不启用
    # enable_star       = 1

    ## min_prefix_len,min_infix_len,prefix_fields,infix_fields都是在enable_star开启的时候才有效果。
    ## 最小前缀索引长度
    ## 为什么要有这个配置项呢?
    ## 首先这个是当启用通配符配置启用的前提下说的,前缀索引使得一个关键词产生了多个索引项,导致索引文件体积和搜索时间增加巨大。
    ## 那么我们就有必要限制下前缀索引的前缀长度,比如example,当前缀索引长度设置为5的时候,它只会分解为exampl,example了。
    # min_prefix_len        = 0
    ## 最小索引中缀长度。理解同上。
    # min_infix_len     = 0

    ## 前缀索引和中缀索引字段列表。并不是所有的字段都需要进行前缀和中缀索引。
    # prefix_fields     = filename
    # infix_fields      = url, domain

    ## 词汇展开
    ## 是否尽可能展开关键字的精确格式或者型号形式
    # expand_keywords       = 1

    ## N-Gram索引的分词技术
    ## N-Gram是指不按照词典,而是按照字长来分词,这个主要是针对非英文体系的一些语言来做的(中文、韩文、日文)
    ## 对coreseek来说,这两个配置项可以忽略。
    # ngram_len     = 1
    # ngram_chars       = U+3000..U+2FA1F

    ## 词组边界符列表和步长
    ## 哪些字符被看做分隔不同词组的边界。
    # phrase_boundary       = ., ?, !, U+2026 # horizontal ellipsis
    # phrase_boundary_step  = 100

    ## 混合字符列表
    # blend_chars       = +, &, U+23
    # blend_mode        = trim_tail, skip_pure

    ## html标记清理,是否从输出全文数据中去除HTML标记。
    html_strip      = 0

    ## HTML标记属性索引设置。
    # html_index_attrs  = img=alt,title; a=title;

    ## 需要清理的html元素
    # html_remove_elements  = style, script

    ## searchd是预先打开全部索引还是每次查询再打开索引。
    # preopen           = 1

    ## 字典文件是保持在磁盘上还是将他预先缓冲在内存中。
    # ondisk_dict       = 1

    ## 由于在索引建立的时候,需要建立临时文件和和副本,还有旧的索引
    ## 这个时候磁盘使用量会暴增,于是有个方法是临时文件重复利用
    ## 这个配置会极大减少建立索引时候的磁盘压力,代价是索引建立速度变慢。
    # inplace_enable        = 1
    # inplace_hit_gap       = 0 # preallocated hitlist gap size
    # inplace_docinfo_gap   = 0 # preallocated docinfo gap size
    # inplace_reloc_factor  = 0.1 # relocation buffer size within arena
    # inplace_write_factor  = 0.1 # write buffer size within arena

    ## 在经过过短的位置后增加位置值
    # overshort_step        = 1

    ## 在经过 停用词 处后增加位置值
    # stopword_step     = 1

    ## 位置忽略词汇列表
    # hitless_words     = all
    # hitless_words     = hitless.txt

    ## 是否检测并索引句子和段落边界
    # index_sp          = 1

    ## 字段内需要索引的HTML/XML区域的标签列表
    # index_zones       = title, h*, th
}

index test1stemmed : test1
{
    path            = /home/yejianfeng/instance/coreseek/var/data/test1stemmed
    morphology      = stem_en
}

index dist1
{
    type            = distributed

    local           = test1
    local           = test1stemmed

    ## 分布式索引(distributed index)中的远程代理和索引声明
    agent           = localhost:9313:remote1
    agent           = localhost:9314:remote2,remote3
    # agent         = /var/run/searchd.sock:remote4

    ## 分布式索引( distributed index)中声明远程黑洞代理
    # agent_blackhole       = testbox:9312:testindex1,testindex2

    ## 远程代理的连接超时时间
    agent_connect_timeout   = 1000

    ## 远程查询超时时间
    agent_query_timeout = 3000
}

index rt
{
    type            = rt

    path            = /home/yejianfeng/instance/coreseek/var/data/rt

    ## RT索引内存限制
    # rt_mem_limit      = 512M

    ## 全文字段定义
    rt_field        = title
    rt_field        = content

    ## 无符号整数属性定义
    rt_attr_uint        = gid

    ## 各种属性定义
    # rt_attr_bigint        = guid
    # rt_attr_float     = gpa
    # rt_attr_timestamp = ts_added
    # rt_attr_string        = author
}

indexer
{
    ## 建立索引的时候,索引内存限制
    mem_limit       = 32M

    ## 每秒最大I/O操作次数,用于限制I/O操作
    # max_iops      = 40

    ## 最大允许的I/O操作大小,以字节为单位,用于I/O节流
    # max_iosize        = 1048576

    ## 对于XMLLpipe2数据源允许的最大的字段大小,以字节为单位
    # max_xmlpipe2_field    = 4M

    ## 写缓冲区的大小,单位是字节
    # write_buffer      = 1M

    ## 文件字段可用的最大缓冲区大小,字节为单位
    # max_file_field_buffer = 32M
}

## 搜索服务配置
searchd
{
    # listen            = 127.0.0.1
    # listen            = 192.168.0.1:9312
    # listen            = 9312
    # listen            = /var/run/searchd.sock

    ## 监听端口
    listen          = 9312
    listen          = 9306:mysql41

    ## 监听日志
    log         = /home/yejianfeng/instance/coreseek/var/log/searchd.log

    ## 查询日志
    query_log       = /home/yejianfeng/instance/coreseek/var/log/query.log

    ## 客户端读超时时间 
    read_timeout        = 5

    ## 客户端持久连接超时时间,即客户端读一次以后,持久连接,然后再读一次。中间这个持久连接的时间。
    client_timeout      = 300

    ## 并行执行搜索的数目
    max_children        = 30

    ## 进程id文件
    pid_file        = /home/yejianfeng/instance/coreseek/var/log/searchd.pid

    ## 守护进程在内存中为每个索引所保持并返回给客户端的匹配数目的最大值
    max_matches     = 1000

    ## 无缝轮转。防止 searchd 轮换在需要预取大量数据的索引时停止响应
    ## 当进行索引轮换的时候,可能需要消耗大量的时间在轮换索引上。
    ## 但是启动了无缝轮转,就以消耗内存为代价减少轮转的时间
    seamless_rotate     = 1

    ## 索引预开启,是否强制重新打开所有索引文件
    preopen_indexes     = 1

    ## 索引轮换成功之后,是否删除以.old为扩展名的索引拷贝
    unlink_old      = 1

    ## 属性刷新周期
    ## 就是使用UpdateAttributes()更新的文档属性每隔多少时间写回到磁盘中。
    # attr_flush_period = 900

    ## 索引字典存储方式
    # ondisk_dict_default   = 1

    ## 用于多值属性MVA更新的存储空间的内存共享池大小
    mva_updates_pool    = 1M

    ## 网络通讯时允许的最大的包的大小
    max_packet_size     = 8M

    ## 崩溃日志文件
    # crash_log_path        = /home/yejianfeng/instance/coreseek/var/log/crash

    ## 每次查询允许设置的过滤器的最大个数
    max_filters     = 256

    ## 单个过滤器允许的值的最大个数
    max_filter_values   = 4096

    ## TCP监听待处理队列长度
    # listen_backlog        = 5

    ## 每个关键字的读缓冲区的大小
    # read_buffer       = 256K

    ## 无匹配时读操作的大小
    # read_unhinted     = 32K

    ## 每次批量查询的查询数限制
    max_batch_queries   = 32

    ## 每个查询的公共子树文档缓存大小
    # subtree_docs_cache    = 4M

    ## 每个查询的公共子树命中缓存大小
    # subtree_hits_cache    = 8M

    ## 多处理模式(MPM)。 可选项;可用值为none、fork、prefork,以及threads。 默认在Unix类系统为form,Windows系统为threads。
    workers         = threads # for RT to work

    ## 并发查询线程数
    # dist_threads      = 4

    ## 二进制日志路径
    # binlog_path       = # disable logging
    # binlog_path       = /home/yejianfeng/instance/coreseek/var/data # binlog.001 etc will be created there

    ## 二进制日志刷新
    # binlog_flush      = 2

    ## 二进制日志大小限制
    # binlog_max_log_size   = 256M

    ## 线程堆栈
    # thread_stack          = 128K

    ## 关键字展开限制
    # expansion_limit       = 1000

    ## RT索引刷新周期 
    # rt_flush_period       = 900

    ## 查询日志格式
    ## 可选项,可用值为plain、sphinxql,默认为plain。 
    # query_log_format      = sphinxql

    ## MySQL版本设置
    # mysql_version_string  = 5.0.37

    ## 插件目录
    # plugin_dir            = /usr/local/sphinx/lib

    ## 服务端默认字符集
    # collation_server      = utf8_general_ci
    ## 服务端libc字符集
    # collation_libc_locale = ru_RU.UTF-8

    ## 线程服务看守
    # watchdog              = 1
    ## 兼容模式
    # compat_sphinxql_magics    = 1
}

这份配置文件属于sphinx3.x,coreseek4.1的sphinx版本是2.x。有一些选项不兼容,但是coreseek4.1也是支持索引和数据源的继承的,分布式索引也是支持的。这是一份中文的coreseek手册:CoreSeekSphinx手册,这份手册是基于coreseek4.1的。可以两份配置文件参考着看。

2.api

coreseek提供了Java、Php、Ruby和Python的API,API的命名基本是一致的。注意coreseek提供的python接口还是python2的,而最新版的sphinx3.x已经更新为python3。

这里以php的接口为例:

$cl = new SphinxClient ();
$q = "";
$sql = "";
$mode = SPH_MATCH_ALL; #匹配模式,不同的类型pdf手册里有介绍
$host = "localhost";
$port = 9312;
$index = "*";
$groupby = "";
$groupsort = "@group desc";
$filter = "group_id";
$filtervals = array();
$distinct = "";
$sortby = "";
$sortexpr = "";
$limit = 20;
$ranker = SPH_RANK_PROXIMITY_BM25; #评分模式,见pdf手册
$select = "";

$cl->SetServer ( $host, $port );
$cl->SetConnectTimeout ( 1 );
$cl->SetArrayResult ( true );
$cl->SetRankingMode ( $ranker );
$res = $cl->Query ( $q, $index );

if ( $res===false )
{
    print "Query failed: " . $cl->GetLastError() . ".\n";

} else
{
    if ( $cl->GetLastWarning() )
        print "WARNING: " . $cl->GetLastWarning() . "\n\n";

    print "Query '$q' retrieved $res[total] of $res[total_found] matches in $res[time] sec.\n";
    print "Query stats:\n";
    if ( is_array($res["words"]) )
        foreach ( $res["words"] as $word => $info )
            print "    '$word' found $info[hits] times in $info[docs] documents\n";
    print "\n";

    if ( is_array($res["matches"]) )
    {
        $n = 1;
        print "Matches:\n";
        foreach ( $res["matches"] as $docinfo )
        {
            print "$n. doc_id=$docinfo[id], weight=$docinfo[weight]";
            foreach ( $res["attrs"] as $attrname => $attrtype )
            {
                $value = $docinfo["attrs"][$attrname];
                if ( $attrtype==SPH_ATTR_MULTI || $attrtype==SPH_ATTR_MULTI64 )
                {
                    $value = "(" . join ( ",", $value ) .")";
                } else
                {
                    if ( $attrtype==SPH_ATTR_TIMESTAMP )
                        $value = date ( "Y-m-d H:i:s", $value );
                }
                print ", $attrname=$value";
            }
            print "\n";
            $n++;
        }
    }
}

//¥res变量中存储了检索的结果:总共检索$res[total]个条目,匹配$res[total_found]个,耗时$res[time]秒;分组检索的结果在array $res["words"]中。

具体的用法要查看test.php和sphinxapi.php,里面的英文注释很详尽。

3.实例

1.普通索引

这是我写的一个普通索引的实例,应该算是涵盖所有基本功能了:

首先配置数据库:

表documents:documents

主键是id,注意字符集,其中title和content是全文索引的字段,这些在配置文件中设置。

表label:lable

这个表用来进行MVA的索引,一个id对应多个tag,所以主键是(id,tag)。我插入的值是这样的:

id tag
1 1
1 2
1 3

意思就是说id=1的文档有1,2,3,3个tag。这个tag就是用来筛选用的,在索引中实现类似于MySQL的where in这样的功能。原始的索引是不支持的。我们可以通过MVA,来指定只从拥有某些tag的文档中进行匹配。

documents表中填充要索引的条目就好。注意,id必须是从1开始自增的。

以下是配置文件:

数据源:

source mysql
{
    type                    = mysql

    sql_host                = 192.168.1.100
    sql_user                = root
    sql_pass                = sa
    sql_db                    = test
    sql_port                = 3306
    sql_query_pre            = SET NAMES utf8

    sql_query                = SELECT id, group_id,group_id2, UNIX_TIMESTAMP(date_added) AS date_added, title, content FROM documents

    sql_attr_uint            = group_id 
    sql_attr_timestamp        = date_added 
    sql_attr_multi = uint tag from query;\
        SELECT id,tag FROM label

    sql_query_info_pre      = SET NAMES utf8                        
    sql_query_info            = SELECT * FROM documents WHERE id=$id 
}

要注意,并不是只有文本类型能索引。在这里我从数据库里取出了group_id2这个字段,这个字段是int类型的,也参加索引。之前在介绍配置文件的时候说过,没有被列出来(sql_attr_xxx)的属性,都会参加索引。被列出来的属性,会被存储在sphinx的索引表中,可以通过查询的返回值输出。

索引及守护进程:

index mysql
{
    source            = mysql            
    path            = /usr/local/coreseek/var/data/mysql
    docinfo            = extern
    mlock            = 0
    morphology        = none
    min_word_len        = 1
    html_strip                = 0
    charset_dictpath = /usr/local/mmseg/etc/ 
    charset_type        = zh_cn.utf-8
}

indexer
{
    mem_limit            = 128M
}

searchd
{
    workers = threads
    listen                  =   9312
    listen = 0.0.0.0:9011:mysql41
    read_timeout        = 5
    max_children        = 30
    max_matches            = 1000
    seamless_rotate        = 0
    preopen_indexes        = 0
    unlink_old            = 1
    pid_file = /usr/local/coreseek/var/log/searchd_mysql.pid 
    log = /usr/local/coreseek/var/log/searchd_mysql.log       
    query_log = /usr/local/coreseek/var/log/query_mysql.log
    binlog_path =  /usr/local/coreseek/var/log/rtindex              
    binlog_flush = 2
    binlog_max_log_size = 16M
}

配置完毕后启动守护进程searchd。

这里我用php的接口进行查询,这是我的php程序:

<?php
require("sphinxapi.php");
$cl = new SphinxClient();
$q = "父亲的背影";
$index = "mysql";
$mode = SPH_MATCH_ALL;
$cl->SetServer("localhost",9312);
$cl->SetArrayResult(true);
$cl->SetWeights(array(100,1));
$cl->SetMatchMode($mode);
$cl->SetFilter('tag',array(1));
$cl->SetFilter('tag',array(2));
$cl->SetFilter('group_id',array(2));
//SetFilter字段是用来设置过滤的,这里我选择了,从tag有1,2两项,以及group_id=2的条目中匹配。这个字段必须是之前设置过sql_attr_xxx的字段,比如如果我在这里去筛选之前没有设置过的group_id2字段,查询会报错。
$res = $cl->Query($q,$index);
if($res)
{
        print "Query Success\n";
        print "$res[total_found]\n";
        if(is_array($res["matches"]))
        {
                foreach($res["words"] as $word  => $info)
                        print "$word,$info[hits]\n";
                foreach($res["matches"] as $docinfo)
                {
                        print "doc_id=$docinfo[id],weight=$docinfo[weight],";
                        foreach($res["attrs"] as $attrname => $attrtype)
                        {
                                $value = $docinfo["attrs"][$attrname];
                                if($attrtype==SPH_ATTR_TIMESTAMP)
                                        $value = date("Y-m-d H:i:s",$value);
                                if ( $attrtype==SPH_ATTR_MULTI || $attrtype==SPH_ATTR_MULTI64 )
                                {
                                        $value = "(" . join ( ",", $value ) .")";
                                }
                                print "$attrname=$value\n";
                        }
                }
        }
}
?>

输出结果:

Query Success
1
父亲,11
的,29
背影,5
doc_id=1,weight=3,group_id=2
date_added=2018-11-27 19:55:57
tag=(1,2,3)

我们之前设定的字段都返回了。

现在修改关键字为3试试,即在我的表中,3是group_id2的值。输出:

Query Success
1
3,1
doc_id=1,weight=100,group_id=2
date_added=2018-11-27 19:55:57
tag=(1,2,3)

3只在group_id2中出现了一次,符合预期。

特别地,如果需要批量查询,可以使用AddQuery()接口,它的参数和Query()接口一样,然后调用RunQueries()接口执行。返回的res也是array。如果你的查询有关联,批量查询的性能会更好,因为其内部有优化措施。所以在所有查询都无关的情况下,应该采取单次查询。具体用法见中文手册。

2.MySQL接口

在介绍实时索引之前,必须先介绍MySQL的接口。

sphinx提供了一个MySQL接口,可以用标准的mysqlclient登陆(mariadb客户端不行)。我测试过,也支持MySQL的C API去连接、查询和获取返回。但是它的语法和MySQL不完全一样,官网把它的语言叫SphinxQL。

coreseek4.1的内核是sphinx2.0.2,这是sphinx官方文档的地址:

Sphinx 2.0.2-beta reference manual

通过标准MysqlClient连接,成功后提示如下:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 2.0.2-dev (r2922)

这个2.0.2就是sphinx的版本信息。

可以通过SQL输出索引mysql的信息:

mysql> select * from mysql;
+------+--------+----------+------------+
| id   | weight | group_id | date_added |
+------+--------+----------+------------+
|    1 |      1 |        2 | 1270131607 |
|    2 |      1 |        3 | 1270135548 |
|    3 |      1 |        3 | 1270094460 |
+------+--------+----------+------------+
3 rows in set (0.36 sec)

这个表和例1用的数据不同,但是结构相同。这里我插入了3条中文数据。

可以通过match来简单匹配条目:

mysql> select * from mysql where match('抢先版');
+------+--------+----------+------------+
| id   | weight | group_id | date_added |
+------+--------+----------+------------+
|    3 |   1680 |        3 | 1270094460 |
+------+--------+----------+------------+
1 row in set (0.06 sec)

目前已知一个bug,通过match进行匹配查询,大概率会导致searchd进程崩溃,原因未查明。但你仍然可以通过php等API实现查询,这些我测过没有bug。所以我建议不要用它提供的where match这个功能,实在太危险。

如果设置了MVA,还可以这样查询:

mysql> select * from mysql where tag=1;
+------+--------+----------+------------+-------+
| id   | weight | group_id | date_added | tag   |
+------+--------+----------+------------+-------+
|    1 |      1 |        2 | 1543319757 | 1,2,3 |
+------+--------+----------+------------+-------+
1 row in set (0.00 sec)

mysql> select * from mysql where tag=2 and group_id=2;
+------+--------+----------+------------+-------+
| id   | weight | group_id | date_added | tag   |
+------+--------+----------+------------+-------+
|    1 |      1 |        2 | 1543319757 | 1,2,3 |
+------+--------+----------+------------+-------+
1 row in set (0.00 sec)

mysql> select * from mysql where tag=4;
Empty set (0.00 sec)

可以通过tag筛选条目。不过这个等号的匹配只支持数值类型,字符串是没办法的。而字符串匹配的match现在又bug,不建议使用。

普通索引是不支持修改的,但是实时索引支持。如果尝试用sphinx提供的insert、replace、delete修改普通索引,会报错,提示not support。具体的语法查阅中文手册。

3.实时索引

实时索引和普通索引的区别还是很大的。

实时索引不需要配置数据源,只需要把类型设置为rt。以下为配置:

index rt
{
    type                    = rt
    path            = /usr/local/coreseek/var/data/rtindex
    docinfo            = extern
    mlock            = 0
    morphology        = none
    min_word_len        = 1
    html_strip                = 0

    charset_dictpath = /usr/local/mmseg/etc/ 
    charset_type        = zh_cn.utf-8

    rt_field                  = title      
    rt_field                  = content        

    rt_attr_uint            = groupid
    rt_attr_bigint         = biguid
    rt_attr_float           = score
    rt_attr_timestamp  = date_added
    rt_attr_string          = author 
    rt_attr_string          = title
    rt_attr_string          = content  

    rt_mem_limit = 512M
}

其余的配置和普通索引相同。

索引的字段要被设为rt_field,这里是title和content,其余attr字段都存储在表中。和普通索引不同的是,在实时索引中,设定了全文索引的字段,也可以存储在表中。但是在普通索引中,一个字段被存储,就不会参与索引(因为没有这个需求,可以通过id查MySQL获取条目信息,而且开销过大)。

对实时索引的操作,需要用到sphinx提供的MySQL接口。我们需要通过MySQL客户端连接searchd提供的端口:listen = 0.0.0.0:9011:mysql41,我这里设置的是9011。

现在通过mysql访问这个索引:

mysql> select * from rt;
Empty set (0.00 sec)

是空的。再通过python api查询下:

navazil@root:~/api$ python2 test.py -i rt
Query '' retrieved 0 of 0 matches in 0.000 sec
Query stats:

Matches:

结果是正确的。现在尝试往表里添加元素,注意语法:

mysql> insert into rt(id,groupid,biguid,score,author,title,content) values(1,1,9999,1.23456,'zkz','这是一次中文实时索引设置','你也在网上冲浪啊,你是GG还是MM?');
Query OK, 1 row affected (0.00 sec)
mysql> select * from rt;
+------+--------+---------+--------+----------+------------+--------+--------------------------------------+------------------------------------------------+
| id   | weight | groupid | biguid | score    | date_added | author | title                                | content                                        |
+------+--------+---------+--------+----------+------------+--------+--------------------------------------+------------------------------------------------+
|    1 |      1 |       1 |   9999 | 1.234560 |          0 | zkz    | 这是一次中文实时索引设置             | 你也在网上冲浪啊,你是GG还是MM?               |
+------+--------+---------+--------+----------+------------+--------+--------------------------------------+------------------------------------------------+
1 row in set (0.00 sec)

现在有结果了。用python API查询下:

navazil@root:~/api$ python2 test.py -i rt 上网冲浪
Query '上网冲浪 ' retrieved 0 of 0 matches in 0.000 sec
Query stats:
        '上网' found 0 times in 0 documents
        '冲浪' found 1 times in 1 documents

Matches:
navazil@root:~/api$ python2 test.py -i rt 网上冲浪
Query '网上冲浪 ' retrieved 1 of 1 matches in 0.000 sec
Query stats:
        '网上' found 1 times in 1 documents
        '冲浪' found 1 times in 1 documents

Matches:
1. doc_id=1, weight=2, groupid=1, biguid=9999, score=1.23456001282, date_added=1970-01-01 08:00:00, author=zkz, title=这是一次中文实时索引设置, content=你也在网上冲浪啊,你是GG还是MM?
navazil@root:~/api$ python2 test.py -i rt 实时索引
Query '实时索引 ' retrieved 1 of 1 matches in 0.000 sec
Query stats:
        '实时' found 1 times in 1 documents
        '索引' found 1 times in 1 documents

Matches:
1. doc_id=1, weight=200, groupid=1, biguid=9999, score=1.23456001282, date_added=1970-01-01 08:00:00, author=zkz, title=这是一次中文实时索引设置, content=你也在网上冲浪啊,你是GG还是MM?

现在修改一下我们刚才写的php程序,检测一下我们设置的过滤器是否正常工作。

$q = "网上冲浪";
#$cl->SetFilter('tag',array(1));
#$cl->SetFilter('tag',array(2));
$cl->SetFilter('groupid',array(1));
//tag字段没有了,这里指设置groupid等于我们刚才插入的1

输出:

Query Success
1
网上,1
冲浪,1
doc_id=1,weight=2,groupid=1
biguid=9999
score=1.2345600128174
date_added=1970-01-01 08:00:00
author=zkz
title=这是一次中文实时索引设置
content=你也在网上冲浪啊,你是GG还是MM?

现在把上面设置的过滤id改为2:

Query Success
0

结果正确。实时索引在内存中做修改,然后定期刷写文件。刷写的选项,配置文件里都可选,具体要参考手册。

如果我们需要修改一项内容,sphinxql提供了replace into的语法。现在我们尝试为之前的条目加一条时间戳:

mysql> replace into rt(id,groupid,biguid,score,date_added,author,title,content) values(1,1,9999,1.23456,153456,'zkz','这是一次中文实时索引设置','你也在网上冲浪啊,你是GG还是MM?');
Query OK, 1 row affected (0.00 sec)
mysql> select * from rt;
+------+--------+---------+--------+----------+------------+--------+--------------------------------------+------------------------------------------------+
| id   | weight | groupid | biguid | score    | date_added | author | title                                | content                                        |
+------+--------+---------+--------+----------+------------+--------+--------------------------------------+------------------------------------------------+
|    1 |      1 |       1 |   9999 | 1.234560 |     153456 | zkz    | 这是一次中文实时索引设置             | 你也在网上冲浪啊,你是GG还是MM?               |
+------+--------+---------+--------+----------+------------+--------+--------------------------------------+------------------------------------------------+
1 row in set (0.00 sec)

这样时间项就有了。这里要特别注意,replace into和mysql的功能是一样的,它会替代已有的条目,也就是说,你没有在values中设置的值,会被重置为空(default)!所以假如你只需要更新某一列,你需要填充所有的其他列和原来一样,否则你就无法正确更新条目。用php查询下试试:

Query Success
1
网上,1
冲浪,1
doc_id=1,weight=2,groupid=1
biguid=9999
score=1.2345600128174
date_added=1970-01-03 02:37:36
author=zkz
title=这是一次中文实时索引设置
content=你也在网上冲浪啊,你是GG还是MM?

可见时间戳变了。

假如你对已有的id进行insert into,那么也会报错:

mysql> insert into rt(id,groupid,biguid,score,date_added,author,title,content) values(1,1,9999,1.23456,153456,'zkz','这是一次中文实时索引设置','你也在网上冲浪啊,你是GG还是MM?');
ERROR 1064 (42000): duplicate id '1'

如果需要删除一个条目,执行delete即可:

mysql> delete from rt where id=1;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from rt;
Empty set (0.00 sec)

delete的where只能用id去匹配,不支持其他字段。

传参规范

正确选择参数:分清输入参数、输出参数,分清值参数和引用参数。正确地传递参数。
正确选择参数是通过值、通过引用还是通过指针传递,是一种能够最大程度提高安全性和效率的好习惯。
选择如何传递参数时,应该遵循一下准则。对于只输入参数:
1.始终用const限制所有指向只输入参数的指针和应用。
2.优先通过值来取得原始类型(如char、float)和复制开销比较低的值对象(如point、complex<float>)的输入。
3.优先按const的引用取得其他用户定义类型的输入;
4.如果函数需要其参数的副本,则可以通过值传递代替
对于输出参数或者输入/输出参数:
1.如果参数是可选的(这样调用者可以传递null表示“不适用的”或“无需关心的”值),或者函数需要保存这个指针的副本或者操控参数的所有权,那么应该优先通过(智能)指针传递;
2.如果参数是必须的,而且函数无需保存指向参数的指针,或者无需操控其所有权,那么应该优先通过引用传递。这表明参数是必须的,而且调用者必须提供有效对象。