blogjava-凯发k8网页登录

blogjava-凯发k8网页登录http://www.blogjava.net/wangxinsh55/category/52093.htmlzh-cnmon, 09 jul 2012 08:40:38 gmtmon, 09 jul 2012 08:40:38 gmt60searching http://www.blogjava.net/wangxinsh55/archive/2012/07/09/382584.htmlsimonesimonemon, 09 jul 2012 02:29:00 gmthttp://www.blogjava.net/wangxinsh55/archive/2012/07/09/382584.htmlhttp://www.blogjava.net/wangxinsh55/comments/382584.htmlhttp://www.blogjava.net/wangxinsh55/archive/2012/07/09/382584.html#feedback0http://www.blogjava.net/wangxinsh55/comments/commentrss/382584.htmlhttp://www.blogjava.net/wangxinsh55/services/trackbacks/382584.htmlhttp://beijingit.blog.163.com/blog/static/29639092201212145312231/
solr's xml response format
status:始终为0。除非查询发生错误,将返回错误码
qtime: 查询耗时。由于有内部缓存,所以两个相同的查询的qtime是相同的,但是查询速度更快
numfound: 总共查询到的结果数
start:查询返回结果集的起始数
maxscore:查询结果中所有数据的最高得分,如果查询请求的fl请求参数没有指定score,那么返回结果中doc中是不会有得分字段的
返回的结果中的每一个doc对应的就是一条查询出来的数据,一一对应于索引中的document,数据类型使用solr中的基本数据类型呈现,如果是多值字段,则以排序的多个字段呈现,每个字段同样是简单类型。
result节点之后可能会有facet highlight等

parsing the url
url中的文本必须是utf-8编码,

request handlers
在solrconfig.xml中配置,允许配置两件事情1.配置默认的参数和一些不会改变的常量。2。注册solr查询组件,例如faceting highlighting等
建议在实际应用中针对每一种类型的查询配置单独的request handler,这样做的好处是可以通过配置修改参数,并且对于查询统计提供更好的粒度
qt="handler name";



none
20


a_type:group


false


1。defaults: 默认参数设置,请求时参数会覆盖此值
2。appends:可以设置多次的参数。像fq,除了请求参数中指定的同时此值也会被设置。
3.invariants:此值是常量值,并且不会被覆盖(此值用于安全目的).
4.first-components, components, last-components:注册当前handler可能会用到的handler。默认情况下已经注册了一些组件,例如faceting highlighting等。设置first-components和last-components会分别前置或追加到这个列表中,设置 components会完全覆盖默认的列表设置。

query parameters
对于boolean类型的参数,真值可以是true,on,yes,假值可以为false,off,no.

search criteria related parameters
q: query的简写,指查询字符串,查询语法使用deftype的设定
deftype:默认是lucene,大多数情况下将会使用dismax 或edismax
dismax和edismax支持许多增强的功能,更少的语法限制可以防止用户得到不希望的结果或者是不小心使用lucene语法时提示出错。
fq:用户查询时过滤条件的范围,类似于sql中的where,此参数不会影响得分(scoring),此参数可以重复。
qt:查询类型。就是前边提到的request handler,一种方法就是用/aaa来命名,然后就可以在url中用aaa?....来访问
result pagination related parameters
start:默认是0,返回结果集中的起始位置,如果此值大于结果集的总数,那么将不会返回任何document,但是solr也不认为这是一个错误
rows:默认是10,
output related parameters
fl:返回的字段列表,值用,/ 或空格分格,使用*符号返回所有字段但是不包括score,要想返回此字段必须添加
sort:排序字段,例如r_name asc,score desc,默认是score desc,也可以用function进行排序
wt:返回的格式,在solrconfig.xml中定义,目前支持的有xml(默认的),json,phthon,php,phps,ruby,javabin,csv,xslt,velocity.
version:不是很有用。
diagnostic related parameters
用于开发调试,
indent:boolean值,用于使输出更容易阅读。
debugquery: 如果为true,那么接下来的查询结果会有的调试信息,其中包括转换的查询字符串,score的计算,以及处理faceting所耗时 间,explainother:如果想知道为什么某个document没有被匹配,或者得分不高,那么可以设置此值来进行查询,例如 id:"release:12345",这样debugquery的输出肯定会包含第一个匹配此查询的结果。
echohandler:如果为true,将输出与solr request handler匹配的java 类名。
echoparams: 控制是否在response header中包含查询参数,可以用来调试url编码过的查询字符串,none禁用,默认的request handler设置此属性为explicit,可以使用all来包含所有request handler中的配置参数,除了url中的。
timeallowed:用来指定查询的最长时间,以毫秒为单位。

query parsers and local-params
solr中默认的query parser是lucene,
query syntax (the lucene query parser)
solr完整的查询语法是实现的lucene query parser.
lucene不支持查询所有文档,solr使用*:*可以查询所有文档
mandatory(强制性)prohibited(禁止)和优化
mandatory: aaa:只匹配包含aaa的 
prohibited: -aaa:匹配所有的,但是除了包含aaa的
optional: aaa 可选的
如果查询语句至少有一个mandatory,那么optional就是可选的,但是他有一个很有用的功能就是文档得分,会匹配更多的(也就是说文档中可以包含optional也可以没有)
如果查询语句没有mandatory,那么至少要匹配一个optional(也就是说文档中必须要包含optional)。
optional也可以指定一个确定的数或百分比来进行匹配或不匹配,这样的话就必须用dismax的min-should-match功能。
solr4将不再使用这种方法
boolean operators
and or not
如果没有明确标记为prohibited,那么and or && 两边的操作会被认为是mandatory,例如aaa and bbb 等同于 aaa bbb
同样的,or操作会被认为是optional的 not等同于prohibited
sub-queries
(aaa and bbb) or (cccc || ddd )
limitations of prohibited clauses is sub-queries
lucene 不支持纯粹的否定查询,例如-smashing -pumpkins。solr对lucene进行增强来支持这种查询,但是仅仅只能在顶层查询,看下面这个例子 smashing (-pumpkins),这个查询是在问:那个document包含smashing或者不包含pumpkins吗?无论怎样,这样写都不会有正确结果 的。正确的写法应该是让子表达式只包含否定语句,并且添加查询所有的查询“*:*"就像这样smashing (-pumpkins *:*),这个限制只能应用在edismax查询中。
field qualifier
member_name:aaa  
member_name:aaa member_name:bbb 也可以用简写形式 member_name:( aaa bbb),圆括号代表的是子查询,这个查询的目标字段都是同一个。
wildcard queries
要注意的几点
1. 不要对包含通配符的查询字符串应用文本分析,甚至是小写,假如你想查找sma开头的单词,并且索引中的字段类型包含小写,那么就应该使用sma*而不能用 sma*。这是solr-219中的一个缺点。此外,假如你要使用的字段是应用了文本分析的,那么smashing*是不会匹配到smashing的,因 为文本分析会把smash转换为smash,因此,不要应用文本分析。
2.通配符查询是很慢的,使用reversedwildcardfilterfactory可以提高很多,最坏的情况是在单词两端应用*号。
3.起始通配符应用*号的话结果会返回错误,除非使用reversedwildcardfilterfactory。
例子:sma* ,sma*ing, sma??*(至少后边跟两个或者更多)
每一个匹配项都会得到相同的score,不管他使用那种查询模式。lucene以牺牲性能来支持多种score,不过得进行一些设置让solr来完成
有一个问题只是一个*号会怎么样呢?结果取决于schema文件中是否有任何一个字段类型,即使索引链中没有应用一个reversedwildcardfilterfactory,*号都会应用所有field,如果不是这样,那么会得到一个”不支持前导通配符“的错误。
fuzzy queries
例子: smashing~
波浪号用于模糊查询,可以通过修改相似度(0-1,默认0.5)来进行查询如:smashing~0.7
就像使用通配符查询一样,如果要使用模糊查询就应该将查询字符串转为小写。
range queries
例如:a_type:2 and a_begin_date:[1990-01-01t00:00:00.000z to 1999-02-02t22:59:59.999z] 
"[]"包含两端的值,”{}“不包含两端的值,solr3中,要么都包含,要么都不包含。solr4两个都允许。
也可以使用a_duration:[3000 to *] 此处"*"号的方式lucene是不支持的
范围查询也可用于文本字段(不常用),此时所应用的索引字段只能有一个term 例如:somefield:([b to c] -c)
date math
solr 扩展了lucene原始的查询转换器,使date应用范围查询时再加方便,就像数字计算一样。另外还可以通过”now“(精确到毫秒级)来获取当前时间, 其语法还提供了补充,如减法,四舍五入根据不同的粒度,如年,秒等。操作可以链接到一起,并且从左向右执行,不允许有空格。例
r_event_date:[* to now-2year]
因为now是精确到毫秒级的,所以如果只是想到天的话可以用”/"向下舍入(此符号只会向下舍入)
r_event_date:[* to now/day-2year]
可选的单位有year,month,day,date(与day相同),hour,minute,second,milisecond,milit(与milisecond相同)
datemath 不只是用于查询,同样用于索引,用于索引时应该使用正确的精度,否则精确到毫秒级的话不仅占用更多的磁盘空间,而且也会降低查询速度,常用的索引日期字段 是这样的
score boosting
通过添加乘数可以修改查询字符串中的条文贡献给最终得分的程度,这叫做增强,0到1之间的值减少得分,大于1的值增加得分,
例:a_member_name:billy^2 or smashing 或者 billy bob corgan^0.7

existence (and non-existence) queries
如果要查询某个字段下的所有文档可以这样a_name:[* to * ]
也可以查询某个字段下没有值的所有文档,-a_name:[* to *]
escaping special characters
- && || ! () {} [] ^ '' ~ * ? : \ 要想使用这些字符的原意可以用"\" 例id:artist\:aaa 通过双引号也可以实现同样效果id:"artist:aaa"
如果使用solrj与solr交互,可以使用clientutils.escapequerychars()进行转义
the dismax query parser
dismax 是lucene的disjunctionmaxquery之后的名字,这个query parser对于scoring 有直接关系.
edismax中的e是extended的意思,这个parser是在solr3.1中增加的,他由dismax的演化而来。
searching multiple fields
a_name a_alias^0.7 a_member_name^0.4如果想根据scoring排序,可以将scoring设大一点,这样就会排在最上边
关 于schema中定义的stop words的效果,如果qf指定的字段中其中一些字段使用stop words而别一些没有使用,那么查询stop words将不会返回任何结果。edismax把查询字符串中的stop words认为都是可选的(也就是可有可无),除非查询字符串中全部使用stop words,使用dismax 你可以确保查询字段中的查询分板链过滤出相同的stop words值。
limited query syntax
edismax首先会把用户的查询使用lucene支持的所有语法进行转换,进行两次调整,如果转换失败,它接下来会跳到原始的dismax的语法规则。
or和and还有布尔运算可以用小写形式,并且支持纯否定子查询。
如果使用dismax,对于terms,phrase它都会限制可以使用的语法,并且使用 和-(而不是and,or,&&,||)来对条文应用强制应用和禁止应用。
还有就是是否需要对相关查询的有效性进行转义。为了不触发错误,除非使用edismax否则就必须编码来处理某些突出的问题。
min-should-match
lucene query parser有两种操作,一是默认操作是or,也就是说只要有一个条文匹配就可以,另一个就是使用and来使所有条文匹配。它不能使用 或-进行明确指 定。这是两种极端的情况。dismax有一个方法叫min-should-mathc,它用来指定多少条文必须匹配,或者有多少个是禁止的。这个值可以设 置为百分比或确定的数值。在配置文件中可以通过查询参数“mm"来设置。语法也比较简单。
basic rules
mm参数的四个基本语法如下:
1:3  必须匹配3个条文,其余的是可选的。
2:-2 2个条文是可选的,其余的是必须的。
3:66% 66%的条文是必须的,其余的是可选的。
4:-25% 25%的条文是可选的,其余的是必须的。
注 意:-是对必须的除以可选的的逆向取值,它在这儿的定义其实没有否定多少的意思。虽然75%和-25%看起来一样,但是在rounding的时候就不一样 了,例如有五个查询条文,第一个条文需要三次,第二个条文需要四次,这也就说明如果要进行rounding计算,那么就需要反转标志再用100减去此值。
另外两点:1.如果mm的值是一个确定的值n,但是查询的条文数小于此值,那么n会减少到查询结果中的条文总数。例如mm值为-5但是查询中的条文一共只有2个,所以结果就是所有条文都是可选的。
2.记住一点,不管是lucene还是solr,在所有查询中,必须有一个条文是匹配的,即使所有条文都是可选的,
multiple rules
是 以空格分隔的一个串,例如number2<75% 9<-3 这个规则的意思是说,如果条文总数大于9,那么其中3个是可选的,其它的都是必须的。如果条文总数大于2,那么75%是必须的。其它情况(也就是条文总数只有1个或2个)那么所有条文都是必须的(这是默认)。
what to choose
min-should-match的简单配置是使所有terms都是可选的。它的效果相当于默认的lucene query parser 的or操作,默认是0%。反过来的另一种极端就是所有terms都是必须的,这个就相当于and操作,就像100%。
注 意:你可能想让所有terms都匹配,实事上这也是默认设置。但是如果有一个term没有找到,那么不会返回任何结果。当你设置一些terms是可选的时 候,匹配的结果会以score排序,包含最多terms的结果会排在顶部。还有一方法就是在没有返回结果的时候进行二次查询,但是solr是不支持的,不 过可以通过客户端很容易的实现。

a default search
dismax 查询支持一个默认查询。也就是用户没有指定q的时候。参数是q.alt。例如*:*。这个参数通常设置在solrconfig.xml文件的request handler中来查询所有文档。在faceting中会使用到这个设置。如果不想返回任何结果可以设置为-*:*;

filtering
filter查询不会影响scoring,不像用户查询。要添加一个filter,只要使用fq参数。使用filter可以改进执行效率,因为每一个filter查询都会缓存在solr的filter cache中并且执行速度相当快。
solr 3.4通过本地参数cache来控制是否启用缓存

sorting
使用sort参数,默认是score desc。例:sort=a_type desc,score desc
进行排序字段的数值类型必须是single valued,indexed,并且是not-tokenized。
另外solr还支持function query ,它通常用来计算空间地理位置的距离,或现在与字段值的时间差等。例sort=sub(begin_time,end_time) desc


simone 2012-07-09 10:29
]]>
深入剖析solrcloud(四)http://www.blogjava.net/wangxinsh55/archive/2012/07/04/382220.htmlsimonesimonewed, 04 jul 2012 10:42:00 gmthttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382220.htmlhttp://www.blogjava.net/wangxinsh55/comments/382220.htmlhttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382220.html#feedback0http://www.blogjava.net/wangxinsh55/comments/commentrss/382220.htmlhttp://www.blogjava.net/wangxinsh55/services/trackbacks/382220.html阅读全文

simone 2012-07-04 18:42
]]>
phinecos(洞庭散人) 专注于开源技术的研究与应用 深入剖析solrcloud(三) http://www.blogjava.net/wangxinsh55/archive/2012/07/04/382219.htmlsimonesimonewed, 04 jul 2012 10:41:00 gmthttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382219.htmlhttp://www.blogjava.net/wangxinsh55/comments/382219.htmlhttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382219.html#feedback0http://www.blogjava.net/wangxinsh55/comments/commentrss/382219.htmlhttp://www.blogjava.net/wangxinsh55/services/trackbacks/382219.htmlhttp://www.cnblogs.com/phinecos/archive/2012/02/16/2354834.html

中介绍了solrcloud的第一个模块---构建管理solr集群状态信息的zookeeper集群。当我们在solr服务器启动时拥有了这样一个zookeeper集群后,显然我们需要连接到zookeeper集群的方便手段,在这一篇中我将对zookeeper客户端相关的各个封装类进行分析。

solrzkclient类是solr服务器用来与zookeeper集群进行通信的接口类,它包含的主要组件有:

  private connectionmanager connmanager;
  private volatile solrzookeeper keeper;
  private zkcmdexecutor zkcmdexecutor = new zkcmdexecutor();

    其中connectionmanagerwatcher的实现类,主要负责对客户端与zookeeper集群之间连接的状态变化信息进行响应,关于watcher的详细介绍,可以参考

solrzookeeper类是一个包装类,没有实际意义,zkcmdexecutor类是负责在连接失败的情况下,重试某种操作特定次数,具体的操作是zkoperation这个抽象类的具体实现子类,其execute方法中包含了具体操作步骤,这些操作包括新建一个znode节点,读取znode节点数据,创建znode路径,删除znode节点等zookeeper操作。

首先来看它的构造函数,先创建connectionmanager对象来响应两端之间的状态变化信息,然后zkclientconnectionstrategy类是一个连接策略抽象类,它包含连接和重连两种策略,并且采用模板方法模式,具体的实现是通过静态累不类zkupdate来实现的,defaultconnectionstrategy是它的一个实现子类,它覆写了connectreconnect两个连接策略方法。

  public solrzkclient(string zkserveraddress, int zkclienttimeout,
      zkclientconnectionstrategy strat, final onreconnect onreconnect, int clientconnecttimeout) throws interruptedexception,
      timeoutexception, ioexception {
    connmanager = new connectionmanager("zookeeperconnection watcher:"
          zkserveraddress, this, zkserveraddress, zkclienttimeout, strat, onreconnect);
    strat.connect(zkserveraddress, zkclienttimeout, connmanager,
        new zkupdate() {
          @override
          public void update(solrzookeeper zookeeper) {
            solrzookeeper oldkeeper = keeper;
            keeper = zookeeper;
            if (oldkeeper != null) {
              try {
                oldkeeper.close();
              } catch (interruptedexception e) {
                // restore the interrupted status
                thread.currentthread().interrupt();
                log.error("", e);
                throw new zookeeperexception(solrexception.errorcode.server_error,
                    "", e);
              }
            }
          }
        });
    connmanager.waitforconnected(clientconnecttimeout);
    numopens.incrementandget();
  }

值得注意的是,构造函数中生成的zkupdate匿名类对象,它的update方法会被调用,

在这个方法里,会首先将已有的老的solrzookeeperg关闭掉,然后放置上一个新的solrzookeeper。做好这些准备工作以后,就会去连接zookeeper服务器集群,

connmanager.waitforconnected(clientconnecttimeout);//连接zk服务器集群,默认30秒超时时间

其实具体的连接动作是new solrzookeeper(serveraddress, timeout, watcher)引发的,上面那句代码只是在等待指定时间,看是否已经连接上。

如果连接zookeeper服务器集群成功,那么就可以进行zookeeper的常规操作了:

1) 是否已经连接

  public boolean isconnected() {
    return keeper != null && keeper.getstate() == zookeeper.states.connected;
  }

2) 是否存在某个路径的znode

  public stat exists(final string path, final watcher watcher, boolean retryonconnloss) throws keeperexception, interruptedexception {
    if (retryonconnloss) {
      return zkcmdexecutor.retryoperation(new zkoperation() {
        @override
        public stat execute() throws keeperexception, interruptedexception {
          return keeper.exists(path, watcher);
        }
      });
    } else {
      return keeper.exists(path, watcher);
    }
  }

3) 创建一个znode节点

  public string create(final string path, final byte data[], final list acl, final createmode createmode, boolean retryonconnloss) throws keeperexception, interruptedexception {
    if (retryonconnloss) {
      return zkcmdexecutor.retryoperation(new zkoperation() {
        @override
        public string execute() throws keeperexception, interruptedexception {
          return keeper.create(path, data, acl, createmode);
        }
      });
    } else {
      return keeper.create(path, data, acl, createmode);
    }
  }

4) 获取指定路径下的孩子znode节点

  public list getchildren(final string path, final watcher watcher, boolean retryonconnloss) throws keeperexception, interruptedexception {
    if (retryonconnloss) {
      return zkcmdexecutor.retryoperation(new zkoperation() {
        @override
        public list execute() throws keeperexception, interruptedexception {
          return keeper.getchildren(path, watcher);
        }
      });
    } else {
      return keeper.getchildren(path, watcher);
    }
  }

5) 获取指定znode上附加的数据

  public byte[] getdata(final string path, final watcher watcher, final stat stat, boolean retryonconnloss) throws keeperexception, interruptedexception {
    if (retryonconnloss) {
      return zkcmdexecutor.retryoperation(new zkoperation() {
        @override
        public byte[] execute() throws keeperexception, interruptedexception {
          return keeper.getdata(path, watcher, stat);
        }
      });
    } else {
      return keeper.getdata(path, watcher, stat);
    }
  }

6) 在指定znode上设置数据

  public stat setdata(final string path, final byte data[], final int version, boolean retryonconnloss) throws keeperexception, interruptedexception {
    if (retryonconnloss) {
      return zkcmdexecutor.retryoperation(new zkoperation() {
        @override
        public stat execute() throws keeperexception, interruptedexception {
          return keeper.setdata(path, data, version);
        }
      });
    } else {
      return keeper.setdata(path, data, version);
    }
  }

7) 创建路径

  public void makepath(string path, byte[] data, createmode createmode, watcher watcher, boolean failonexists, boolean retryonconnloss) throws keeperexception, interruptedexception {
    if (log.isinfoenabled()) {
      log.info("makepath: "   path);
    }
    boolean retry = true;
    
    if (path.startswith("/")) {
      path = path.substring(1, path.length());
    }
    string[] paths = path.split("/");
    stringbuilder sbpath = new stringbuilder();
    for (int i = 0; i < paths.length; i ) {
      byte[] bytes = null;
      string pathpiece = paths[i];
      sbpath.append("/"   pathpiece);
      final string currentpath = sbpath.tostring();
      object exists = exists(currentpath, watcher, retryonconnloss);
      if (exists == null || ((i == paths.length -1) && failonexists)) {
        createmode mode = createmode.persistent;
        if (i == paths.length - 1) {
          mode = createmode;
          bytes = data;
          if (!retryonconnloss) retry = false;
        }
        try {
          if (retry) {
            final createmode finalmode = mode;
            final byte[] finalbytes = bytes;
            zkcmdexecutor.retryoperation(new zkoperation() {
              @override
              public object execute() throws keeperexception, interruptedexception {
                keeper.create(currentpath, finalbytes, zoodefs.ids.open_acl_unsafe, finalmode);
                return null;
              }
            });
          } else {
            keeper.create(currentpath, bytes, zoodefs.ids.open_acl_unsafe, mode);
          }
        } catch (nodeexistsexception e) {
          
          if (!failonexists) {
            // todo: version ? for now, don't worry about race
            setdata(currentpath, data, -1, retryonconnloss);
            // set new watch
            exists(currentpath, watcher, retryonconnloss);
            return;
          }
          
          // ignore unless it's the last node in the path
          if (i == paths.length - 1) {
            throw e;
          }
        }
        if(i == paths.length -1) {
          // set new watch
          exists(currentpath, watcher, retryonconnloss);
        }
      } else if (i == paths.length - 1) {
        // todo: version ? for now, don't worry about race
        setdata(currentpath, data, -1, retryonconnloss);
        // set new watch
        exists(currentpath, watcher, retryonconnloss);
      }
    }
  }

8) 删除指定znode

  public void delete(final string path, final int version, boolean retryonconnloss) throws interruptedexception, keeperexception {
    if (retryonconnloss) {
      zkcmdexecutor.retryoperation(new zkoperation() {
        @override
        public stat execute() throws keeperexception, interruptedexception {
          keeper.delete(path, version);
          return null;
        }
      });
    } else {
      keeper.delete(path, version);
    }
  }

         我们再回过头来看看connectionmanager类是如何响应两端的连接状态信息的变化的,它最重要的方法是process方法,当它被触发回调时,会从watchedevent参数中得到事件的各种状态信息,比如连接成功,会话过期(此时需要进行重连),连接断开等。

  public synchronized void process(watchedevent event) {
    if (log.isinfoenabled()) {
      log.info("watcher "   this   " name:"   name   " got event "   event   " path:"   event.getpath()   " type:"   event.gettype());
    }

    state = event.getstate();
    if (state == keeperstate.syncconnected) {
      connected = true;
      clientconnected.countdown();
    } else if (state == keeperstate.expired) {
      connected = false;
      log.info("attempting to reconnect to recover relationship with zookeeper...");
      //尝试重新连接zk服务器
      try {
        connectionstrategy.reconnect(zkserveraddress, zkclienttimeout, this,
            new zkclientconnectionstrategy.zkupdate() {
              @override
              public void update(solrzookeeper keeper) throws interruptedexception, timeoutexception, ioexception {
                synchronized (connectionstrategy) {
                  waitforconnected(solrzkclient.default_client_connect_timeout);
                  client.updatekeeper(keeper);
                  if (onreconnect != null) {
                    onreconnect.command();
                  }
                  synchronized (connectionmanager.this) {
                    connectionmanager.this.connected = true;
                  }
                }
                
              }
            });
      } catch (exception e) {
        solrexception.log(log, "", e);
      }
      log.info("connected:"   connected);
    } else if (state == keeperstate.disconnected) {
      connected = false;
    } else {
      connected = false;
    }
    notifyall();
  }

 

 



simone 2012-07-04 18:41
]]>
phinecos(洞庭散人) 专注于开源技术的研究与应用 深入剖析solrcloud(一) http://www.blogjava.net/wangxinsh55/archive/2012/07/04/382217.htmlsimonesimonewed, 04 jul 2012 10:40:00 gmthttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382217.htmlhttp://www.blogjava.net/wangxinsh55/comments/382217.htmlhttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382217.html#feedback0http://www.blogjava.net/wangxinsh55/comments/commentrss/382217.htmlhttp://www.blogjava.net/wangxinsh55/services/trackbacks/382217.htmlhttp://www.cnblogs.com/phinecos/archive/2012/02/10/2345634.html

    是基于solrzookeeper的分布式搜索方案,是正在开发中的solr4.0的核心组件之一,它的主要思想是使用zookeeper作为集群的配置信息中心。它有几个特色功能:1)集中式的配置信息 2)自动容错 3)近实时搜索 4)查询时自动负载均衡 

 

基本可以用上面这幅图来概述,这是一个拥有4solr节点的集群,索引分布在两个shard里面,每个shard包含两个solr节点,一个是leader节点,一个是replica节点,此外集群中有一个负责维护集群状态信息的overseer节点,它是一个总控制器。集群的所有状态信息都放在zookeeper集群中统一维护。从图中还可以看到,任何一个节点都可以接收索引更新的请求,然后再将这个请求转发到文档所应该属于的那个shardleader节点,leader节点更新结束完成,最后将版本号和文档转发给同属于一个shardreplicas节点。

下面我们来看一个简单的solrcloud集群的配置过程。

首先去的源码和二进制包,注意solr4.0现在还在开发中,因此这里是nightly build版本。

示例1,简单的包含2shard的集群

这个示例中,我们把一个collection的索引数据分布到两个shard上去,步骤如下:

为了弄2solr服务器,我们拷贝一份example目录

cp -r example example2

然后启动第一个solr服务器,并初始化一个新的solr集群,

cd example
java -dbootstrap_confdir=./solr/conf -dcollection.configname=myconf -dzkrun -dnumshards=2 -jar start.jar

-dzkrun参数是启动一个嵌入式的zookeeper服务器,它会作为solr服务器的一部分,-dbootstrap_confdir参数是上传本地的配置文件上传到zookeeper中去,作为整个集群共用的配置文件,-dnumshards指定了集群的逻辑分组数目。

然后启动第二个solr服务器,并将其引向集群所在位置

cd example2
java -djetty.port=7574 -dzkhost=localhost:9983 -jar start.jar

-dzkhost=localhost:9983就是指明了zookeeper集群所在位置

我们可以打开 或者看看目前集群的状态,

现在,我们可以试试索引一些文档,

cd exampledocs
java -durl=http://localhost:8983/solr/collection1/update -jar post.jar ipod_video.xml
java -durl=http://localhost:8983/solr/collection1/update -jar post.jar monitor.xml
java -durl=http://localhost:8983/solr/collection1/update -jar post.jar mem.xml

最后,来试试分布式搜索吧:

zookeeper维护的集群状态数据是存放在solr/zoo_data目录下的。

现在我们来剖析下这样一个简单的集群构建的基本流程:

先从第一台solr服务器说起:

1)       它首先启动一个嵌入式的zookeeper服务器,作为集群状态信息的管理者,

2 将自己这个节点注册到/node_states/目录下

3 同时将自己注册到/live_nodes/目录下

4)创建/overseer_elect/leader,为后续overseer节点的选举做准备,新建一个overseer

5) 更新/clusterstate.json目录下json格式的集群状态信息

6) 本机从zookeeper中更新集群状态信息,维持与zookeeper上的集群信息一致

7)上传本地配置文件到zookeeper中,供集群中其他solr节点使用

8) 启动本地的solr服务器,

9) solr启动完成后,overseer会得知shard中有第一个节点进来,更新shard状态信息,并将本机所在节点设置为shard1leader节点,并向整个集群发布最新的集群状态信息。

10)本机从zookeeper中再次更新集群状态信息,第一台solr服务器启动完毕。

然后来看第二台solr服务器的启动过程:

1) 本机连接到集群所在的zookeeper

2) 将自己这个节点注册到/node_states/目录下

3)  同时将自己注册到/live_nodes/目录下

4) 本机从zookeeper中更新集群状态信息,维持与zookeeper上的集群信息一致

5) 从集群中保存的配置文件加载solr所需要的配置信息

6) 启动本地solr服务器,

7) solr启动完成后,将本节点注册为集群中的shard,并将本机设置为shard2leader节点,

8) 本机从zookeeper中再次更新集群状态信息,第二台solr服务器启动完毕。

示例2,包含2shard的集群,每个shard中有replica节点

如图所示,集群包含2shard,每个shard中有两个solr节点,一个是leader,一个是replica节点,

cp -r example exampleb
cp -r example2 example2b
cd exampleb
java -djetty.port=8900 -dzkhost=localhost:9983 -jar start.jar
cd example2b
java -djetty.port=7500 -dzkhost=localhost:9983 -jar start.jar

我们可以打开  看看包含4个节点的集群的状态,

 

 

这个集群现在就具备容错性了,你可以试着干掉一个solr服务器,然后再发送查询请求。背后的实质是集群的ov erseer会监测各个shardleader节点,如果leader节点挂了,则会启动自动的容错机制,会从同一个shard中的其他replica节点集中重新选举出一个leader节点,甚至如果overseer节点自己也挂了,同样会自动在其他节点上启用新的overseer节点,这样就确保了集群的高可用性。

示例3 包含2shard的集群,带shard备份和zookeeper集群机制

 

上一个示例中存在的问题是:尽管solr服务器可以容忍挂掉,但集群中只有一个zookeeper服务器来维护集群的状态信息,单点的存在即是不稳定的根源。如果这个zookeeper服务器挂了,那么分布式查询还是可以工作的,因为每个solr服务器都会在内存中维护最近一次由zookeeper维护的集群状态信息,但新的节点无法加入集群,集群的状态变化也不可知了。因此,为了解决这个问题,需要对zookeeper服务器也设置一个集群,让其也具备高可用性和容错性。

有两种方式可选,一种是提供一个外部独立的zookeeper集群,另一种是每个solr服务器都启动一个内嵌的zookeeper服务器,再将这些zookeeper服务器组成一个集群。 我们这里用后一种做示例:

cd example
java -dbootstrap_confdir=./solr/conf -dcollection.configname=myconf -dzkrun -dzkhost=localhost:9983,localhost:8574,localhost:9900 -dnumshards=2 -jar start.jar
cd example2
java -djetty.port=7574 -dzkrun -dzkhost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar
cd exampleb
java -djetty.port=8900 -dzkrun -dzkhost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar
cd example2b
java -djetty.port=7500 -dzkhost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar

我们可以打开  看看包含4个节点的集群的状态,可以发现其实和上一个没有任何区别。

后续的文章将从实现层面对solrcloud这个分布式搜索凯发天生赢家一触即发官网的解决方案进行进一步的深入剖析。



simone 2012-07-04 18:40
]]>
深入剖析solrcloud(二)http://www.blogjava.net/wangxinsh55/archive/2012/07/04/382218.htmlsimonesimonewed, 04 jul 2012 10:40:00 gmthttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382218.htmlhttp://www.blogjava.net/wangxinsh55/comments/382218.htmlhttp://www.blogjava.net/wangxinsh55/archive/2012/07/04/382218.html#feedback0http://www.blogjava.net/wangxinsh55/comments/commentrss/382218.htmlhttp://www.blogjava.net/wangxinsh55/services/trackbacks/382218.htmlhttp://www.cnblogs.com/phinecos/archive/2012/02/15/2353007.html
  介绍了solrcloud的基本概念,从这一篇开始我将深入到其实现代码中进行剖析。

solrcloud最重要的一点就是引入了zookeeper来统一管理各种配置和状态信息。是一个开源分布式的服务,它提供了分布式协作,分布式同步,配置管理等功能. 其实现的功能与google基本一致.zookeeper的官方网站已经写了一篇非常经典的概述性文章,请大家参阅:.

        的示例中是在启动每个solr服务器前,内嵌启动了一个zookeeper服务器,再将这几台zookeeper服务器组成一个集群,确保solr集群信息的高可用性和容错性。

      构建一个可用的zookeeper集群,这就是solrcloud要做的第一件工作。下面来看下solrcloud是如何实现这一功能的:

1) 首先在web.xml中配置了一个filter

<filter>
    <filter-name>solrrequestfilterfilter-name>
    <filter-class>org.apache.solr.servlet.solrdispatchfilterfilter-class>
      filter>

   web容器启动时会去加载并初始化solrdispatchfilter这个filter,它的init方法会被调用,这个方法中做的最主要的事情是初始化一个solr核容器。

  corecontainer.initializer init = createinitializer();
    // web.xml configuration
    this.pathprefix = config.getinitparameter( "path-prefix" );
this.cores = init.initialize();

 2) 初始化solr核容器时,首先找到solr的根目录,这个目录下最重要的是solr.xml这个配置文件,这个配置文件用于初始化容器中加载的各个solr核,如果没有提供solr.xml,则会启用默认的配置信息:

  private static final string def_solr_xml ="\n" 
          "\n" 
          "  \n" 
          "    \n" 
          "  \n" 
          "";

  3) 初始化过程的其中一步就是初始化zookeeper服务器,你可以选择单机的zookeeper服务器,也可以构建zookeeper集群,下面以集群为例进行代码分析。

    if (zkrun != null) {
      zkserver = new solrzkserver(zkrun, zookeeperhost, solrhome, hostport);
      zkserver.parseconfig();
      zkserver.start();
      
      // set client from server config if not already set
      if (zookeeperhost == null) {
        zookeeperhost = zkserver.getclientstring();
      }
}

         solrzkserver类就是伴随solr启动的内嵌的zookeeper服务器,首先来看parseconfig方法,它负责解析zoo.cfg文件,读取zookeeper启动时所需要的配置信息,这些配置信息由solrzkserverprops类表示,

首先设置zookeeper存储数据的目录

    if (zkprops == null) {
      zkprops = new solrzkserverprops();
      // set default data dir
      
// todo: use something based on ip port???  support ensemble all from same solr home?
      zkprops.setdatadir(solrhome   '/'   "zoo_data");
      zkprops.zkrun = zkrun;
      zkprops.solrport = solrport;
}

然后读取zoo.cfg配置文件中的信息,为启动zookeeper服务器提供完整的配置信息,

      props = solrzkserverprops.getproperties(solrhome   '/'   "zoo.cfg");
      solrzkserverprops.injectservers(props, zkrun, zkhost);
      zkprops.parseproperties(props);

    下面是一个示例配置文件:

ticktime=2000
datadir=/var/zookeeper/
clientport=2181
initlimit=5
synclimit=2
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888

注意,server.x这些行就指明了zookeeper集群所包含的机器名称,每台zookeeper服务器会使用3个端口来进行工作,其中第一个端口(端口1)用来做运行期间server间的通信,第二个端口(端口2)用来做leader election,另外还有一个端口(端口0)负责接收客户端请求。那么一台机器怎样确定自己是谁呢?这是通过datadir目录下的myid文本文件确定。myid文件只包含一个数字,内容就是所在serveridquorumpeerconfig.myid

1) 准备好集群所需要的配置信息后,就可以启动zookeeper集群了。启动时是生成一个zookeeper服务器线程,根据配置信息来决定是单机还是集群模式,如果是单机模式,则生成zookeeperservermain对象并启动,如果是集群模式,则使用quorumpeermain对象启动。最后将服务器线程设置为daemon模式,就完成了zookeeper服务器的启动工作了。

    public void start() {
        zkthread = new thread() {
            @override
            public void run() {
                try {
                    if (zkprops.getservers().size() > 1) {//zk集群
                        quorumpeermain zkserver = new quorumpeermain();
                        zkserver.runfromconfig(zkprops);
                        if (logger.isinfoenabled()) {
                            logger.info("启动zk服务器集群成功");
                        }
                    } else {//单机zk
                        serverconfig sc = new serverconfig();
                        sc.readfrom(zkprops);
                        zookeeperservermain zkserver = new zookeeperservermain();
                        zkserver.runfromconfig(sc);
                        if (logger.isinfoenabled()) {
                            logger.info("启动单机zk服务器成功");
                        }
                    }
                    logger.info("zookeeper server exited.");
                } catch (throwable e) {
                    logger.error("zookeeper server error", e);
                    throw new solrexception(solrexception.errorcode.server_error, e);                    
                }
            }
        };
        if (zkprops.getservers().size() > 1) {
            logger.info("starting embedded ensemble zookeeper server at port "   zkprops.getclientportaddress().getport());
        } else {
            logger.info("starting embedded standalone zookeeper server at port "   zkprops.getclientportaddress().getport());            
        }
        
        zkthread.setdaemon(true);
        zkthread.start();
        try {
            thread.sleep(500); // pause for zookeeper to start
        } catch (exception e) {
            logger.error("starting zookeeper", e);
        }
    }

为了验证集群是否启动成功,可以使用zookeeper提供的命令行工具进行验证,进入bin目录下,运行:

zkcli.cmd –server zookeeper服务器地址1:端口

    这是连接到集群中1zookeeper服务器,然后创建一个znode,往其中加入一些数据,你再连接到集群中其他的服务器上,查看数据是否一致,即可知道zookeeper集群是否已经构建成功。



simone 2012-07-04 18:40
]]>
网站地图