前言:
之前一直使用lucene,有很多优点及缺点,最大的缺点就是要维护一个索引的成本很高,需要牵扯到很多方面,其中也包含业务方面;优点呢,不用多说了,速度快,支持查询的模式多,各种条件下的查询都能实现,所以想找一个更加符合现有应用状况的搜索引擎,故想到了coreseek=(sphinx+中文分词+框架)
Sphinx 最大的好处是业务层面不需要你去关心索引的建立、更新等,后台定时去维护主索引和增量索引即可。同时有很多人性化的功能,比如防止建立索引时全表读取造成数据库挂掉,而进行的分批读取。增量索引方面可以读取一段时间内更新的数据,对于一个网站网站来说,更新的量占所有的数据的量比较小。增量索引适合短时间内更新,主索引适合长时间内更新。读取索引的时候同时读取这2个索引。
SphinxEx引擎,这个个人觉得只适合终端查看些数据,做调试相关的,线上使用还是得用他的API,支持N种语言了,今天测试的是JAVA接口,感觉还不错,不过也找到一些小缺点,少float类型的filter啊(用float_range也还可以), 少int类型的属性,不明白string类型的为啥没写出来。
和Lucene比较除了上述的外,好的功能有:支持一对多的搜索,sql_attr_mult 这个很实用,比如 我要找到公交车站叫A的旁边的房子,一个房子对应着好几个公交车站A,B,C,D等等的,很实用,杠杠的!呵呵。居然还有地理坐标的查询功能,正好符合我们的业务,查询 房子周围100米以内的东西之类。
正文:
以下是实际使用的配置参数及功能,有写的不对的还请指正。
1、官方和网上现有的增量索引方式都仅仅为自增的主键增加了多少,而并不包括之前的数据更新,根据这个状况,可以使用mysql里面的一个特殊字段的功能:创建一个字段:
`last_updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
即只要该行有数据变更,这个字段的值即变成最新的时间,同时增加记录表,记录主索引索引的最后时间。
CREATE TABLE `sph_counter` ( `counter_id` int(11) NOT NULL, `max_doc_id` int(11) NOT NULL, PRIMARY KEY (`counter_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
然后呢,就是在sphinx配置文件里写上相关的sql了。
source tasks { type = mysql sql_host = 192.168.0.21 sql_user = root sql_pass = cjkjb110 sql_db = design sql_port = 3306 sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = REPLACE INTO sph_counter SELECT 1, UNIX_TIMESTAMP(MAX(last_updated_time)) FROM tasks sql_query = SELECT id,task_no as taskNo,title,title as title1,reward,status,room,hall,wei,area,total_budget as totalBudget,district,city,province,UNIX_TIMESTAMP(created) as created_time,decoration_style_id,UNIX_TIMESTAMP(last_updated_time) as last_updated_time FROM tasks WHERE last_updated_time<= (SELECT FROM_UNIXTIME(max_doc_id) FROM sph_counter WHERE counter_id=1) sql_attr_float = reward sql_attr_uint = status sql_attr_uint = room sql_attr_bigint = hall sql_attr_bigint = wei sql_attr_float = area sql_attr_float = totalBudget sql_attr_uint = district sql_attr_uint = city sql_attr_uint = province sql_attr_uint = created_time sql_attr_uint = last_updated_time sql_attr_bigint =taskNo sql_attr_string =title sql_attr_multi =uint decoration_style_id from field; sql_query_info_pre = SET NAMES utf8 #命令行查询时,设置正确的字符集 sql_query_info = SELECT * FROM tasks WHERE id=$id #命令行查询时,从数据库读取原始数据信息 } source tasks_delta : tasks { sql_query_pre =SET NAMES utf8 sql_query = SELECT id,task_no as taskNo,title,title as title1,reward,status,room,hall,wei,area,total_budget as totalBudget,district,city,province,UNIX_TIMESTAMP(created) as created_time,decoration_style_id,UNIX_TIMESTAMP(last_updated_time) as last_updated_time FROM tasks WHERE last_updated_time> (SELECT FROM_UNIXTIME(max_doc_id) FROM sph_counter WHERE counter_id=1) }
上面定义了主索引:tasks,增量索引tasks_delta 主索引里sql_query_pre 里插入了最新的last_update_time字段,执行主索引的时候该语句就能执行了,执行增量索引的时候判断的条件即是大于这个主索引的最后更新时间,这样的话就能获取到更新和添加的最新数据了。有一个缺点就是不能找到记录删除数据,我们的解决方法是使用逻辑删除即增加一个is_delete字段来判断是否是删除。
2、搜索1对多的情况,打个比方,数据库中有N个文章的数据,每个文章对应有N个标签,若是要求搜索标签得到M个文章的时候,普通的查询就有困难了,之前的方法也就是把多个标签放在同一个字段中,用逗号隔开之类,使用like查询等,或者使用联合查询,没有太好的解决方案,sphinx里的sql_attr_multi字段就是为了解决这个问题而建。
上述表中的 sql_attr_multi =uint decoration_style_id from field; 该行就使用了这个方法,具体怎样操作呢? decoration_style_id 字段是tasks表中的其中一个varchar列,里面记录了例如12,45,2,89,323之类的数据,其他什么都不需要配置直接使用这一行代码,就能给你自动按uint类型分成多行了,搜索的时候直接set_filter(decoration_style_id,89,false) 就能搜索出带有89这个的记录。功能非常实用,在很多方面都可以用的上,除了上面用的from field 还可以使用from query,from range_query等,这样就可以联合其他的表进行索引查询了。
3、细心的人可能发现我写的sql 中间加了很多别名,好多重复的字段,例如 title,title as title1 ,这样的写法是迎合sphinx的功能特点的,下面可以看到sql_attr_string =title 这句是定义一个属性 attr,title1字段为field,attr 是在搜索结果中可以显示的,但是不能作为分词进行搜索的,field 是可以作为分词进行搜索查询的,两者不能同时起作用,所以复制了一列,作不同的方法用,这是我的办法,不知道有没有更好的办法,有的话麻烦告诉我下谢谢。
4、说下api,使用的是java ,常用的几个语言方法名和参数都类似,贴上几个主要的地方:
SphinxClient cl =null; SearchResult<T> sr = null; SphinxResult res=null; int pagesize=6; int pageindex=1; cl = new SphinxClient(); cl.SetServer ( com.kuaiyoujia.design.commons.constants.PubConstant.SphinxServer, 9312 ); cl.SetConnectTimeout(2000); cl.SetMatchMode(SphinxClient.SPH_MATCH_ALL); if(chk(info.getArea())) { int a=Integer.parseInt(info.getArea()); float area_min=0.00f; float area_max=100000.00f; switch(a){ case 1: area_min=0.00f; area_max=40.00f; break; case 2: area_min=41.00f; area_max=60.00f; break; case 3: area_min=61.00f; area_max=90.00f; break; case 4: area_min=91.00f; area_max=120.00f; break; case 5: area_min=121.00f; area_max=150.00f; break; case 6: area_min=151.00f; area_max=200.00f; break; default: break; } cl.SetFilterFloatRange("area", area_min, area_max, false); }
if(chk(info.getPageSize())) pagesize=Integer.parseInt(info.getPageSize()); if(chk(info.getPageIndex())) pageindex=Integer.parseInt(info.getPageIndex()); cl.SetLimits ( (pageindex-1)*pagesize, pagesize ); cl.SetSortMode(SphinxClient.SPH_SORT_ATTR_DESC, "last_updated_time");//按照字段排序 String q=""; if(info.getKeyWords()!=null) q=info.getKeyWords(); String index="*"; if(chk(info.getTypeName())) { if(info.getTypeName().equals(SearchConstant.TASK)) index="tasks,tasks_delta"; else if(info.getTypeName().equals(SearchConstant.CASE)) index="cases,cases_delta"; } res = cl.Query(q, index);
这是部分代码,写的不好,凑活着看。
5、还有个基本的部署架构可以说下,最简单的单台数据库服务器,都能搞定,可以不用装sphinxEx引擎,量稍微大些可以采用master-slave的数据库结构,master使用innodb引擎,slave使用myisam +sphinxEx引擎,索引读取都直接读slave上即可。指定定时任务,每天凌晨执行主索引,每10分钟执行增量索引即可,合并索引的功能可用可不用。
后语:
本人也是刚接触sphinx,感觉还是很强大的,有新的发现我会继续贴上来的,欢迎拍砖。