设为首页 收藏本站
查看: 1179|回复: 0

[经验分享] [Erlang 0104] 当Erlang遇到Solr

[复制链接]

尚未签到

发表于 2015-7-16 11:46:19 | 显示全部楼层 |阅读模式
Joe Armstrong的访谈中有一段关于"打开黑盒子"的阐述,给我留下很深的印象:Joe Armstrong在做XWindows开发时没有使用对应的类库,而是在了解XWindows底层实现后选择了直接和套接字通信,"把这20条消息映射到Erlang术语上,变个小魔术,然后可以向窗口直接发送消息,它们就开始执行动作了". [访谈全文] 回到今天的任务:Erlang使用Solr服务?当问题落实到数据通信协议的时候,就豁然开朗了,转换为我们熟悉的技术方案组合.先看下Solr的简介:


  Solr





   Solr (pronounced "solar") is an open source enterprise search platform from the Apache Lucene project. Its major features include full-text search, hit highlighting, faceted search, dynamic clustering, database integration, and rich document (e.g., Word, PDF) handling. Providing distributed search and index replication, Solr is highly scalable. Solr is the most popular enterprise search engine. Solr 4 adds NoSQL features.    Solr is written in Java and runs as a standalone full-text search server within a servlet container such as Apache Tomcat or Jetty. Solr uses the Lucene Java search library at its core for full-text indexing and search, and has REST-like HTTP/XML and JSON APIs that make it usable from most popular programming languages. Solr's powerful external configuration allows it to be tailored to many types of application without Java coding, and it has a plugin architecture to support more advanced customization.




2014-2-17 15:03:21 补充一点Solr的东西:




  • Solr 可以做到 NEAR-Real-time Search ,  这个应该归功于softcommit
  • Solr 4 之前的版本只有提交到Lucene索引才能检索到,现在可以做到Real Time Get;换句话话说一个刚刚提交的数据,还没有Commit,现在是可以做修改的;能够做到这点是因为Transcation log,数据提交到Solr会写入transaction log.这个设计解耦了update durability和update visibility
  • 目前Solr 4.x还是需要以文档为单位进行数据更新,重新做索引;换句话说新增字段的时候每条数据都要重新处理,重新做索引
  • Solr在任意时刻只有一个Active Searcher,Active Searcher有一份只读的Lucene索引快照.换句话说,当有新内容提交到Solr之后,这条数据对当前的Searcher是不可见的;新条目从不可见到可见:关闭当前的Searcher然后打开一个新的Searcher,新的Searcher会加载最新的索引的快照;这就是我们Commit数据到Solr的本质,关闭Old Searcher的时候会等把当前in-progress请求全部处理完;Old Searcher中缓存的数据也会被清理掉,这样是有道理的,有的数据可能已经删除了,可能有新数据了;New Searcher需要做一些预计算,所以Solr会有一个Warming Searcher的设计:等待New Searcher预热完毕再关闭之前的Searcher,这样搜索性能不会有明显的下降,对于Solr重新启动的情况,要看useColdSearcher是否开启,如果是false那就要阻塞到Searcher完成预热;Warming Searcher有两种方式:1.从old cache复制 2.执行cache warming queries,这个语句是开发者根据自己的业务定制的,不能用默认的;因为开启之后会有性能损失,默认是注释掉的,由开发者根据实际情况执行定制;一旦达到maxWarmingSearchers的数量上限,Commit就会失败,预热太多个Searcher会消耗CPU和Memeory;
  • filtercache相当于缓存了若干数据分组结果,但是如果数据分组数量有限,命中率太低,那效果可以想象非常差;这个策略如果使用不当,就会浪费内存,比如:过滤条件过于复杂极小概率命中;solrconfig.xml中有对应的配置节,可以配置autowarmcount,自动预热是从old searcher中提取一些语句在new searcher中执行;autowarm会影响new searcher的启动速度,如果commit比较快,要求不停的创建新的searcher,就悲剧了;所以autowarmcount设置的值一般比较小;
  • queryResultWindowsSize 可以预取分页数据.queryResultMaxDocsCached 每条检索语句最多返回多少条文档;enableLazyFieldLoading可以优化取文档部分字段的情况,延迟加载的字段在用到的时候再请求;
  • segement merging是怎么回事?一份Luncene索引由若干个Segement构成,每一个查询都要在所有的Segement里面拿结果,Segment太多会影响性能;所以需要用合并机制








  搭建全文搜索服务Solr的确是一个不错的选择,分分钟就可以搭建起来Solr的环境,配置好IK什么的,那Erlang应用如何使用Solr服务呢?从上面维基百科的介绍中,我们可以捕捉到一些信息:REST-full API,XML,JSON,HTTP.看到这里已经全是我们熟悉的技术方案了,我们深入进去看下:


esolr




     2008年ppolv (Pablo Polvorin)在trapexit.org提交了一个Solr的功能模块,[地址: http://forum.trapexit.org/viewtopic.php?t=13059 ],完成了操作Solr的基本功能:


     |> Add/Update documents esolr:add/2
     |> Delete documents esolr:delete/2
     |> Search esolr:search/3



先看看怎么使用这些上面的接口:






%% 测试代码
-module(t).
-compile(export_all).

start()->
SearchUrl="http://192.168.0.160:8080/solr/hear_me/select",
UpdateUrl="http://192.168.0.160:8080/solr/hear_me/update",
MltUrl="http://192.168.0.160:8080/solr/hear_me/mlt",
{ok,Pid}=esolr:start([{select_url, SearchUrl}, {update_url, UpdateUrl}, {morelikethis_url, MltUrl}]),
Pid.
search(SolrPid)->
esolr:search("10",[{fields,"*,*"}],SolrPid).

add(SolrPid) ->
esolr:add([{doc,[{id,"ai234"}, {text,}]}],SolrPid),
esolr:add([{doc,[{id,"a3456"}, {text,}]}],SolrPid),
esolr:commit(SolrPid).
  测试结果如下:



Eshell V5.9  (abort with ^G)
1> P=t:start().

2> t:add(P).
ok
3> esolr:search("searching",[{fields,"*,*"}],P).
{ok,[{"numFound",2},{"start",0}],
[{doc,[{"id",},
{"_version_",1440978100186775552}]},
{doc,[{"id",},
{"_version_",1440978100212989952}]}],
[]}
4> t:search(P).
{ok,[{"numFound",9},{"start",0}],
[{doc,[{"c_type",1},
{"c_tags",
[,
,
,
,
,
,
,
,
]},
{"c_pub_date",},
{"id",},
{"_version_",1440342611812417536}]},
{doc,[{"c_type",1},
{"c_tags",
[,
,
,
,
,
,
,
..... ...
  
  代码实现 


  翻开代码,下面这个方法包含了大部分技术要点:




make_post_request(Request,PendingInfo,
State=#esolr{update_url=URL,pending=P,auto_commit=AC,dirty=Dirty},
Timeout) ->
{ok,RequestId} = httpc:request(post,{URL,[{"connection", "close"}],"text/xml",Request},[{timeout,Timeout}],[{sync,false}]),
Pendings = gb_trees:insert(RequestId,PendingInfo,P),
if
(AC == always) and Dirty ->
CommitRequest = encode_commit(),
{ok,C_RequestId} = httpc:request(post,{URL,[{"connection", "close"}],"text/xml",CommitRequest},
[{timeout,State#esolr.commit_timeout}],[{sync,false}]),
Pendings2 = gb_trees:insert(C_RequestId,{auto,auto_commit},Pendings),
error_logger:info_report([{auto_commit,send}]),
{noreply,State#esolr{pending=Pendings2,dirty=false}};
         
true -> {noreply,State#esolr{pending=Pendings}}
end.



首先在init阶段开启了inets:start(),make_post_request发起HTTP请求靠的是httpc,每一次请求之后都会把RequestId和请求发起者({From,_}里面的From)对应关系存储到gb_tree.在后面的handle_info代码段,可以看到对HTTPResponse的消息的接收.






% @hidden
handle_info({http,{RequestId,HttpResponse}},State = #esolr{pending=P}) ->
case gb_trees:lookup(RequestId,P) of
{value,{Client,RequestOp}} -> handle_http_response(HttpResponse,RequestOp,Client),
{noreply,State#esolr{pending=gb_trees:delete(RequestId,P)}};
          none -> {noreply,State}
%% the requestid isn't here, probably the request was deleted after a timeout
end;
parse_search_response(Response,Client) ->
{value,{"response",{obj,SearchRespFields}},RestResponse} = lists:keytake("response",1, Response),
{value,{"docs",Docs},RespFields} =  lists:keytake("docs",1,SearchRespFields),
gen_server:reply(Client,{ok,RespFields,[{doc,DocFields} || {obj,DocFields} xmerl:export_simple([{commit,[]}],xmerl_xml).
["",[[""]]]
2>
   HTTPResponse解析还会用到xmerl_scan,xmerl_xpath





handle_http_response({{_HttpV,200,_Reason},_Headers,Data},Op,Client) ->
{Response,[]} = xmerl_scan:string(binary_to_list(Data)),
[Header] = xmerl_xpath:string("/response/lst[@name='responseHeader']",Response),
case parse_xml_response_header(Header) of
{ok,QTime} ->  parse_xml_response(Op,Response,QTime,Client);
          {error,Error} ->  response_error(Op,Client,Error)
end;
   除了XML之外,还要解析JSON,这里使用的是RFC4627.







扩展




这个简单的功能模块,呃,是不是太简陋了?而且你会发现代码太老了?这段代码后续被修改应用在了Zotontic项目实现搜索功能,之前盘点Erlang Web Server和Web Framework的时候提到过这个CMS系统 [地址:https://github.com/arjan/mod_search_solr] 这个项目里面把原有代码做了重构,并增加了很多实用的接口比如翻页 "MoreLikeThis"功能封装.可以在Github上获取代码试一下,Zotontic的代码略显庞大,只取必需的模块编译即可;





ok,今天就到这里.





最后小图一张 Miss Nine



DSC0000.jpg

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-87274-1-1.html 上篇帖子: Apache Solr 扩展 下篇帖子: EasyNet.Solr 3.5.1发布及使用中的一些原则
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表