从扬大数院毕业后,与数学渐行渐远,许多时候,考虑问题都角度渐渐变得工程,也就是直观,而不是从抽象的角度来解决。组里有一个实习生,带来了几道笔试题,思考后,解决了,从解决问题的角度,明显看出,越来越工程化了。

第一题说的是用6种颜色去涂一个立方体,问有几种涂法?

一个明显的解答是6的阶乘,也就是720次,可是其中有很多种涂色,经过旋转后是一样的,所以这是错的。看到这题,立刻想到了魔方,之后想到了骰子,考虑到骰子更加直观,就用骰子。思考之后,其实挺简单的。把1这面朝上,那么1的对面,也就是底面有5种可能的情况。之后再看侧面的情况,对于侧面,固定一面之后,它的对面还有3中可能的情况,之后剩下两个侧面,有2种情况。所以一共有5 x 3 x 2 = 30种情况。这题一个难点是最开始的1这面的选择,以及侧面时,固定一面的选择。对于1这面的选择,是不能算概率的,因为无论怎么排,总是可以把1这面朝上。而对于侧面时固定一面,这固定一面也是不能算概率的,因为无论怎么排,都可以固定一面。

第二题说的是,对于座位编号从1到5的5个人,将他们的座位打乱,每个人都不在自己座位上的情况有几种?

经过上一题的训练后,抽象一下题目 ,对于座位编号从1到n的n个人,将他们的座位打乱,每个人都不在自己座位上的情况有几种
所以对于这题,相当于n为5的情况。依然用上面的类似方法。对于编号1的人,他一共有4种情况不在自己的座位上,假设他占了编号为5的座位。那么对于编号为5的这个人,他有两种情况可以选择,第一种,他占了座位1,则此时还剩三个人,这相当于n为3的情况,计算得到一种有2种可能;第二种情况是5不在座位1上,那么剩下的情况就相当于n=4的情形,计算得到有9中可能。于是最终结果等于4 x (2 + 9) = 44。而从这里也可以得到一个递推公式。设t(n)为人数为n时的可能情形。则t(n) = (n - 1) x (t(n - 1) + t(n - 2)),于是得到一个序列为0 1 2 9 44 265 ….

第三题说的是,一共有27个人想喝饮料,三个空瓶子可以换一瓶饮料,那么一共需要买多少瓶饮料才能保证每个人都能喝到一瓶饮料?

对于这题,立刻想到经典的借瓶子策略,对于这里,即只需要2个空瓶就可以喝一瓶饮料,这是因为当有2个空瓶时,可以向老板借一个空瓶,凑齐三个空瓶换来一瓶饮料,喝完之后,把空瓶换给老板。我想这里也是可以用到这个策略,于是写了一个序列1 2 6 18,等于27。所以最终的答案是18瓶。

联系作者

从QueryComponent可以知道,一个分布式solr请求从发起请求到对响应的结果进行处理会经历许多的stage.

对于分布式普通请求,从private int regularDistributedProcess(ResponseBuilder rb)的实现中可以看到,会经历ResponseBuilder.STAGE_PARSE_QUERY,ResponseBuilder.STAGE_EXECUTE_QUERY,ResponseBuilder.STAGE_GET_FIELDS,ResponseBuilder.STAGE_DONE等stage。从private void handleRegularResponses(ResponseBuilder rb, ShardRequest sreq),分布式普通请求有ShardRequest.PURPOSE_GET_TOP_IDS,ShardRequest.PURPOSE_GET_FIELDS两次响应

对于分布式group请求,从private int groupedDistributedProcess(ResponseBuilder rb)的实现中可以看到,则会经历ResponseBuilder.STAGE_PARSE_QUERY,ResponseBuilder.STAGE_TOP_GROUPS,ResponseBuilder.STAGE_EXECUTE_QUERY,ResponseBuilder.STAGE_GET_FIELDS,ResponseBuilder.STAGE_DONE等stage。从private void handleGroupedResponses(ResponseBuilder rb, ShardRequest sreq)可以看到,分布式group请求有ShardRequest.PURPOSE_GET_TOP_GROUPS,ShardRequest.PURPOSE_GET_TOP_IDS,ShardRequest.PURPOSE_GET_FIELDS三次响应.

对于不同的请求和响应,有相应的类或者方法来实现。当然也可以自己实现相应的类或者方法来处理。即便是QueryComponent也可以自己定义,只需要实现相应的接口即可。

对于private int regularDistributedProcess(ResponseBuilder rb),一个可能的实现是:

1
2
3
4
5
6
7
8
9
10
private int regularDistributedProcess(ResponseBuilder rb) {
ComponentDistributedStage cdStage = stages.getCDStage(rb.stage);
int nextState = ResponseBuilder.STAGE_DONE;
if (cdStage != null) {
cdStage.distributedProcess(rb, this);
if (stages.containsNextState(rb.stage))
nextState = stages.getNextState(rb.stage);
}
return nextState;
}

这里stages是一个Map,保存相应stage的实现类,父类型为ComponentDistributedStage。具体实现一个stage时,实现相应的接口即可。

联系作者

感觉上,这段代码不贴上来,仿佛欠别人钱似的。趁现在还有些精力,以后很长一段时间都不会接触Sphinx了,赶紧把这件事给做了。
具体为什么这样改,可以看前面的文章。以下修改是基于sphinx-for-chinese-2.2.1-dev-r4311版本,之需要修改sphinx.cpp即可。

在2296行后面添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct CSphWord
{
BYTE m_sAccum[3 * SPH_MAX_WORD_LEN + 3];
int length;
const BYTE *m_pTokenStart;
const BYTE *m_pTokenEnd;
};
class ISphWords
{
public:
int Length () const
{

return m_dData.GetLength();
}

const CSphWord * First () const
{

return m_dData.Begin();
}

const CSphWord * Last () const
{

return &m_dData.Last();
}
void Clean() {
m_dData.Reset();
}

void AddWord ( BYTE * word, int length, const BYTE *start, const BYTE *end)
{

CSphWord & tWord = m_dData.Add();
memcpy(tWord.m_sAccum, word, length);
tWord.length = length;
tWord.m_pTokenStart = start;
tWord.m_pTokenEnd = end;
}

public:
CSphVector<CSphWord> m_dData;
};

在2296行,virtual int GetMaxCodepointLength () const { return m_tLC.GetMaxCodepointLength(); }后面添加如下方法成员:

1
cvirtual BYTE *          ProcessParsedWord();

在2303行,Darts::DoubleArray::result_pair_type m_pResultPair[256];后面添加如下数据成员:

1
2
3
4
5
6
7
8
9
10
11
/*****add by luodongshan for indexer*****/
int totalParsedWordsNum; //总共需要处理的词
int processedParsedWordsNum; //已经处理的词
int isIndexer; //是否开启细粒度分词
bool needMoreParser; //需要更细粒度分词
const char * m_pTempCur;
char m_BestWord[3 * SPH_MAX_WORD_LEN + 3];
int m_iBestWordLength;
ISphWords m_Words;
CSphWord *current;
bool isParserEnd;

在6448行,m_bHasBlend = false;后面添加如下初始化代码:

1
2
3
4
5
6
7
8
9
char *penv = getenv("IS_INDEX");
if (penv != NULL) {
isIndexer = 1;
} else {
isIndexer = 0;
}
needMoreParser = false;
current = NULL;
isParserEnd = false;

在6743后面添加新增方法成员ProcessParsedWord的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template < bool IS_QUERY >
BYTE * CSphTokenizer_UTF8Chinese<IS_QUERY>::ProcessParsedWord() {
for (; current != NULL && current <= m_Words.Last(); ) {
memcpy(m_sAccum, current->m_sAccum, current->length);
m_pTokenStart = current->m_pTokenStart;
m_pTokenEnd = current->m_pTokenEnd;
current++;
return m_sAccum;
}
isParserEnd = false;
m_Words.Clean();
current = NULL;
return NULL;
}

在6785行, bool bGotSoft = false; // hey Beavis he said soft huh huhhuh后面增加如下代码:

1
2
3
if (isIndexer && isParserEnd) { //使用MMSEG分词结束,处理细粒度分词得到的词
return ProcessParsedWord();
}

在6791行, int iNum;后面增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/***add by dengsl 2014/06/24****/
if(isIndexer && needMoreParser) { //对最优匹配进行细粒度分词
while (m_pTempCur < m_BestWord + m_iBestWordLength) {
if(processedParsedWordsNum == totalParsedWordsNum) { //此位置的前缀词已处理完,跳到下一位置
size_t minWordLength = m_pResultPair[0].length;
for(int i = 1; i < totalParsedWordsNum; i++) {
if(m_pResultPair[i].length < minWordLength) {
minWordLength = m_pResultPair[i].length;
}
}
m_pTempCur += minWordLength;
m_pText=(Darts::DoubleArray::key_type *)(m_pCur + (m_pTempCur - m_BestWord));
iNum = m_tDa.commonPrefixSearch(m_pText, m_pResultPair, 256, m_pBufferMax-(m_pCur+(m_pTempCur-m_BestWord)));
totalParsedWordsNum = iNum;
processedParsedWordsNum = 0;
} else {
iWordLength = m_pResultPair[processedParsedWordsNum].length;
processedParsedWordsNum++;
if (m_pTempCur == m_BestWord && iWordLength == m_iBestWordLength) {
continue;
}
memcpy(m_sAccum, m_pText, iWordLength);
m_sAccum[iWordLength] = '\0';
if( 3 * SPH_MAX_WORD_LEN + 3 >= iWordLength + 2) {
m_sAccum[iWordLength + 1] = '\0';
if(m_pTokenEnd == m_pBufferMax) { //是结尾,保存结尾符标志
m_sAccum[iWordLength + 1] = 1;
}
}
m_Words.AddWord(m_sAccum, iWordLength + 2, m_pCur + (m_pTempCur - m_BestWord), m_pCur + (m_pTempCur - m_BestWord) + iWordLength);
}
}
m_pCur += m_iBestWordLength;
needMoreParser = false;
iWordLength = 0;
current = const_cast< CSphWord * > ( m_Words.First() );
}
/***add end by dengsl 2014/06/24****/

在6832行,iNum = m_tDa.commonPrefixSearch(m_pText, m_pResultPair, 256, m_pBufferMax-m_pCur);后面增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/***add by dengsl 2014/06/24****/
if(isIndexer && iNum > 1) {
m_iBestWordLength=getBestWordLength(m_pText, m_pBufferMax-m_pCur);
memcpy(m_sAccum, m_pText, m_iBestWordLength);
m_sAccum[m_iBestWordLength]='\0';
m_pTokenStart = m_pCur;
m_pTokenEnd = m_pCur + m_iBestWordLength;

totalParsedWordsNum = iNum;
needMoreParser = true;
processedParsedWordsNum = 0;
memcpy(m_BestWord, m_pText, m_iBestWordLength);
m_BestWord[m_iBestWordLength]='\0';
m_pTempCur = m_BestWord;
if( 3 * SPH_MAX_WORD_LEN + 3 >= m_iBestWordLength + 2) {
m_sAccum[m_iBestWordLength + 1] = '\0';
if(m_pTokenEnd == m_pBufferMax) { //是结尾,保存结尾符标志
m_sAccum[m_iBestWordLength + 1] = 1;
}
}
return m_sAccum;
}
/***add by dengsl 2014/06/24****/

在6903行,将

1
return NULL;

修改为

1
2
3
/* dengsl */
isParserEnd = true;
return ProcessParsedWord();

在6914行,将

1
2
if_const ( IS_BLEND && !BlendAdjust ( pCur ) )
return NULL;

修改成:

1
2
3
4
5
/* dengsl */
if_const ( IS_BLEND && !BlendAdjust ( pCur ) ) {
isParserEnd = true;
return ProcessParsedWord();
}

在27210行,m_tHits.AddHit ( uDocid, iWord, m_tState.m_iHitPos );后面增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
///add by luodongshan 20140626
if(sWord != NULL) {
int sWord_len = strlen((char*)sWord);
if(sWord_len + 2 <= 3 * SPH_MAX_WORD_LEN + 3 && sWord[sWord_len + 1] == 1 &&
getenv("IS_INDEX") != NULL && !bSkipEndMarker ) {
CSphWordHit * pHit = const_cast < CSphWordHit * > ( m_tHits.Last() );
HITMAN::SetEndMarker ( &pHit->m_iWordPos );

}
}
///add by luodongshan 20140626 end

将过上面的修改,重新编译源码,之后设置环境变量IS_INDEX,即运行export IS_INDEX=1,就可以支持细粒度的划分。

一个需要注意的地方是,对于searchd,也变成细粒度分词了,这并不是我们想要的,所以对于searchd,需要使用未修改代码的searchd.因为我们想建索引时细粒度,搜索时粗粒度。

之所以要这样,是因为如果不这样处理,很多结果会搜出来了。如有文章内容分别为中大酒店,中大假日酒店。如果搜索时也是细粒度,则有中大,酒店,中,大,大酒店,酒,店等查询词,而大酒店只在中大酒店中存在,所以只会搜出中大酒店,这并不是我们想要的。

联系作者

离职已经三个多月了,关于Sphinx的知识都快忘的差不多了,所以得赶紧记下来,以备不时之需。

离职前,在Sphinx-for-Chinese讨论组里异常活跃,很热心帮助群里的人解决问题。其中有个问题就是属性更新时无法设置为负值。于是看看Sphinx的更新属性流程。从searchd.cpp的main函数开始,到ServiceMain,TickPreforked,HandleClient,HandleClientSphinx,HandleCommandUpdate在这里看到

1
2
3
4
5
6
7
8
9
10
11
12
13
ARRAY_FOREACH ( i, tUpd.m_dAttrs )
{
tUpd.m_dAttrs[i] = tReq.GetString().ToLower().Leak();
tUpd.m_dTypes[i] = SPH_ATTR_INTEGER;
if ( iVer>=0x102 )
{
if ( tReq.GetDword() )
{
tUpd.m_dTypes[i] = SPH_ATTR_UINT32SET;
bMvaUpdate = true;
}
}
}

也就是说,这里默认是SPH_ATTR_INTEGER,而在Sphinx里,这个是无符号整型。因为在后面的一个判断语句里,有如下句子

} else
{     
    tUpd.m_dPool.Add ( tReq.GetDword() );
}

查看GetDword(),就可以知道返回的是无符号整型。

之后跳转到DoCommandUpdate,UpdateAttributes
在其中发现这样一句话:
// this is a hack
// Query parser tries to detect an attribute type. And this is wrong because, we should
// take attribute type from schema. Probably we’ll rewrite updates in future but
// for now this fix just works.
// Fixes cases like UPDATE float_attr=1 WHERE id=1;
也就是说,Sphinx更新属性时,没有去读取配置文件。而只是根据上面代码中的设定去读取更新信息,所以没有办法读取负数。一个主要的原因是,Sphinx没有32位整型数据的概念,只有32位无符号整型的概念。

因为这样,你也许会尝试将要更为负值的字段设置成64位整型,因为这个是有正负的,可是尝试之后还是不行。这是因为在代码里,没有根据配置文件去读数据,所以它还是按照上面的设定去读数据,这样还是无符号的。所以对于这个问题,还有待Sphinx的开发人员去解决.

太久没用vim看代码了,连ctags的跳转是ctrl + ] 和ctrl + o都快忘记了。

联系作者

最近因为需要在分布式group查询时自定义自己的排序,因为在许多应用中都需要定义针对应用的排序规则。例如在用户名时,需要针对name,添加最匹配原则最左侧优先,最短优先等排序规则。而要使用这些规则, 一个前提条件是,先要拿到这个字段的值。可是在Solr提供的api中,无法定义这样精细的规则,所以必须修改代码才能支持.

在此之前,要了解分布式group查询的过程.当进行分布式group查询时,从QueryComponent中,可以知道,leader会向shard发送三次请求,分别对应三个阶段 ResponseBuilder.STAGE_TOP_GROUPS,ResponseBuilder.STAGE_EXECUTE_QUERY,ResponseBuilder.STAGE_GET_FIELDS三个阶段。

第一个阶段也可称为firstPhase,主要是得到字段的分组信息,也就是得到字段有哪些分组,请求的构造在 SearchGroupsRequestFactory中.在shard中,对这次请求作出响应是在QueryComponent中的process函数 内,if (params.getBool(GroupParams.GROUP_DISTRIBUTED_FIRST, false)) 中完成的,查询得到的结果由SearchGroupsResultTransformer的transform进行转换。对于shard返回的结 果,leader在SearchGroupShardResponseProcessor中进行处理.

第二个阶段也可称为secondPhase,这个阶段主要是得到每个分组内的文档id,在这个阶段,leader会将上一阶段得到的分组 信息发给shard,请求的构造在TopGroupsShardRequestFactory中.在shard中,对这次请求作出响应是在 QueryComponent中的process函数内,else if (params.getBool(GroupParams.GROUP_DISTRIBUTED_SECOND, false))中完成,查询得到的结果由TopGroupsResultTransformer的transform函数进行转换。对于shard返回的 结果,leader在TopGroupsShardResponseProcessor中进行处理

第三个阶段主要是得到文档的字段信息,在这个阶段,leader会将最终结果中的文档id发送给shard,请求的构造在 StoredFieldsShardRequestFactory中.在shard中,对这次请求作出响应是在QueryComponent中的 process函数内,String ids = params.get(ShardParams.IDS);语句后的if (ids != null)中。对于shard返回的结果,leader是在StoredFieldsShardResponseProcessor中.

分布式group查询的过程差不多就这样,以后再介绍如何定义自己的排序。

联系作者

试想这样一种情形,一个publish_time,原先是只索引不保存,运行了很长一段时间后,发现需要返回这个字段,于是改成既索引又保存。这样新进来的数据就可以返回这个字段的值,可是原先保存的数据将无法返回这个字段的值,因为没有保存。那如何解决这个问题?

一个解决的办法是将数据重新跑一遍,重建索引,这样所有的数据都可以返回publish_time这个字段。想想还有没有其它办法,看到建了索引,这样还是有办法可以拿到数据,一个解决的办法是读fiedcache. 索引加载时,会在fieldcache里记录字段信息,这样可以提高字段查询的速度。事实上,在上述例子中,进行publish_time字段查询时,就可以拿到所有数据的publish_time字段信息,而这些信息就是来自于fieldcache.

于是找到了一个切入点,在进行段合并时,将之前没有保存的字段信息从fieldcache中读出,写到新的段中,这样新生成的段中,所有数据都会有publish_time字段信息。

于是问题的关键就变成了如何处理在段合并时,读取fieldcache信息,并且增加到新段中.从《Lucene原理与代码分析》中可以知道,段合并主要是在SegmentMerger中完成,具体是在copyFieldsWithDeletions和copyFieldsNoDeletions中。看名字就可以知道这两个函数是对应的,所以只要讨论其中一个就行了。在合并时主要分两种情况,一种是合并的段所有字段的顺序和个数都是一样的,这样只要将段数据复制到新段中即可,另一种则需要像添加一篇新文档一样将段中的文档一篇篇添加,具体体现就在fieldsWriter.addDocument(doc);这句。

对于后一情况,需要从fieldcache中读取之前没有保存的字段,如FieldCache.DEFAULT.getInts(reader, fieldName),这里的reader是一个SegmentReader实例, fieldName则是字段名,这样就可以得到字段的值,。而文档已经由Document doc = reader.document(docCount, fieldSelectorMerge)读出,之后构建一个Field将它添加到文档中,并将文档添加到段中即可.

需要注意的是,reader一定要加载索引,否则会报,terms index was not loaded when this reader was created错误.

还有就是,对于int,double等数值型数据,需要调用Field(String name, byte[] value)方法,也就是先将int,double等转化成byte[]数组,之后再构建。具体转化方法参见int,double等转化成byte数组.

联系作者

最近需要用到这个功能,本来想自己写,怕写错了,上网找了一下,都没找到合适的。看了solr与源码中的TrieField.java,有这一部分的代码,copy到这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ByteUtil {
public static int toInt(byte[] arr) {
return (arr[0] << 24) | ((arr[1] & 0xff) << 16)
| ((arr[2] & 0xff) << 8) | (arr[3] & 0xff);
}

public static long toLong(byte[] arr) {
int high = (arr[0] << 24) | ((arr[1] & 0xff) << 16)
| ((arr[2] & 0xff) << 8) | (arr[3] & 0xff);
int low = (arr[4] << 24) | ((arr[5] & 0xff) << 16)
| ((arr[6] & 0xff) << 8) | (arr[7] & 0xff);
return (((long) high) << 32) | (low & 0x0ffffffffL);
}

public static float toFloat(byte[] arr) {
return Float.intBitsToFloat(toInt(arr));
}

public static double toDouble(byte[] arr) {
return Double.longBitsToDouble(toLong(arr));
}

public static byte[] toArr(int val) {
byte[] arr = new byte[4];
arr[0] = (byte) (val >>> 24);
arr[1] = (byte) (val >>> 16);
arr[2] = (byte) (val >>> 8);
arr[3] = (byte) (val);
return arr;
}

public static byte[] toArr(long val) {
byte[] arr = new byte[8];
arr[0] = (byte) (val >>> 56);
arr[1] = (byte) (val >>> 48);
arr[2] = (byte) (val >>> 40);
arr[3] = (byte) (val >>> 32);
arr[4] = (byte) (val >>> 24);
arr[5] = (byte) (val >>> 16);
arr[6] = (byte) (val >>> 8);
arr[7] = (byte) (val);
return arr;
}

public static byte[] toArr(float val) {
return toArr(Float.floatToRawIntBits(val));
}

public static byte[] toArr(double val) {
return toArr(Double.doubleToRawLongBits(val));
}
}

联系作者

最近在做一个站内搜索功能,有用到一个分页功能.搜索时会传两个参数pageId和pageSize 用来指定页号与每页的条数.solr已经提供了start和rows两个参数,于是将分页参数与之对应起来,在component初始化时写了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
String pidStr = req.getParams().get(PAGE_ID);
if ( pidStr != null ) {
int pageId = req.getParams().getInt(PAGE_ID, 0);
int pageSize = req.getParams().getInt(PAGE_SIZE, 10);
pageId = (pageId > -1) ? pageId : 0;
pageSize = (pageSize > 0 ) ? pageSize : 10;
ModifiableSolrParams params = new ModifiableSolrParams(req.getParams());
params.set(CommonParams.START, pageId * pageSize);
params.set(CommonParams.ROWS, pageSize);
params.set(CommonParams.WT, "json");
params.set(CommonParams.OMIT_HEADER, "true");
req.setParams(params);
}

可是一直得不到正确的结果,在后台输出日志,发现start和rows在设置了上面的值后,start和rows会用来构建新的查询,在新的查询中start会变为0,而rows会变成start与rows的和,可是之后start和rows又会变成原先的值,于是得不到想要的结果。

举个例子,假设要搜第三页,每页10条,则可设置pageId为2,pageSize为10,于是start被设置成20,rows被设置成10,之后start和rows会被用于生成向shard发送的请求,start被设置为0,rows设置为30.问题在于这个请求发出之后start的值又变成了20,rows变成了10,百思不得其解.

跟踪代码到createMainQuery函数,看到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (rb.shards_start > -1) {
// if the client set shards.start set this explicitly
sreq.params.set(CommonParams.START, rb.shards_start);
} else {
sreq.params.set(CommonParams.START, "0");
}
if (rb.shards_rows > -1) {
// if the client set shards.rows set this explicity
sreq.params.set(CommonParams.ROWS, rb.shards_rows);
} else {
sreq.params.set(CommonParams.ROWS, rb.getSortSpec().getOffset()
+ rb.getSortSpec().getCount());
}

打印日志rb.shards_start与rb.shards_rows都为-1,于是start变为0,row变成30,这是正确的,那么关键点就是要找出start和rows何时变成20与10,跟踪程序找不到原因。看后台日志,发现初始化每次都会执行两次,然后想到分布式,才渐渐明白问题的原因.

在一次分布式查询中,solr的leader会接受请求,然后对请求进行解析,之后重新构建请求,将新的请求发给各个Shard,Shard做非分布式查询之后,将结果发给leader,之后leader汇总各个Shard的响应,进行最后的处理(如做offset等),问题是leader和Shard都是同一份代码,而且初始化部分每个Shard接收leader的请求后都要执行,于是start和rows又被重新设置了.在上面的例子中,start和rows在Shard中分别被设置成20和10了,只会Shard做非分布式查询,这样Shard只会返回10条数据给leader,这显然不是想要的.

之后发现,在leader构建的新的请求中,会添加isShard=true参数,于是可以修改代码如下:

1
2
boolean isShard = req.getParams().getBool(ShardParams.IS_SHARD, false);
if ( pidStr != null && !isShard) { //分布式时,只有leader才需要执行这里

之后结果就是正确的。到这里,才有点明白分布式程序,真不好写.

联系作者

工作一年后,才知道有Screen这个东西,太不应该了,也许真是环境影响人.

今天遇到问题,导师过来指导,看我的SecureCRT开着很多个会话,没有用Screen,于是提醒可以用这个.之前在公司的wiki上看到过介绍,本以为就是SecureCRT,原来是一个管理会话工具,有了它之后,就不需要再开很多个会话,然后关闭了.会话间的切换也可以很方便的用快捷键命令,而不是鼠标,因为鼠标极其影响效率.

用了公司一个员工的配置
hardstatus alwayslastline “%{=b}%{b}%-w%{.BW}%10>%n*%t%{-}%+w%< %=%{kG}%C%A, %Y-%m-%d”
screen -t local1 0 bash
screen -t local2 1 bash
screen -t local3 2 bash
screen -t local4 3 bash
screen -t local5 4 bash

select 0

vim ~/.screenrc,复制上面内容.之后就可以使用Screen了.一些常用命令如下:
c-a : Ctrl + a
screen -S name #开一个session
screen -S name -X quit #杀死session
c-a c #创建一个窗口
c-a n #next 窗口
c-a p #previous 窗口
c-a A #为窗口命名
c-a d #detach screen
c-a #跳转到number的窗口
screen -ls #查看窗口
screen -r name #连接一个session
screen -x name #共享session
可以参考http://hunsefee.diandian.com/post/2010-10-28/7319178

联系作者

开始Lucene之路,从官网下了最新的4.9.0,从先从小例子开始.
建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package org.dsl;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import java.io.File;
import java.io.IOException;
public class Index {
public static void main(String[] args) throws IOException {
String INDEX_DIR = "e:\\index";
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_4_9);
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_4_9, analyzer);
IndexWriter writer = null;
iwc.setOpenMode(OpenMode.CREATE);
iwc.setUseCompoundFile(false);
try {
writer = new IndexWriter(FSDirectory.open(new File(INDEX_DIR)), iwc);
Document doc = new Document();
doc.add(new TextField("title", "who are you, you are a man", Field.Store.YES));
doc.add(new TextField("content", "A long way to go there. Please drive a car", Field.Store.NO));
writer.addDocument(doc);
doc = new Document();
doc.add(new TextField("title", "are you sure", Field.Store.YES));
doc.add(new TextField("content", "He is a good man. He is a driver", Field.Store.NO));
writer.addDocument(doc);
writer.commit();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package org.dsl;
import java.io.File;
import java.util.Date;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.FSDirectory;
public class Search {
private Search() {}
public static void main(String[] args) throws Exception {
String index = "e:\\index";
IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(index)));
IndexSearcher searcher = new IndexSearcher(reader);
String queryString = "driver";
Query query = new TermQuery(new Term("content", queryString));
System.out.println("Searching for: " + query.toString());
Date start = new Date();
TopDocs results = searcher.search(query, null, 100);
Date end = new Date();
System.out.println("Time: "+(end.getTime()-start.getTime())+"ms");
ScoreDoc[] hits = results.scoreDocs;
int numTotalHits = results.totalHits;
System.out.println(numTotalHits + " total matching documents");
for (int i = 0; i < hits.length; i++) {
String output = "";
Document doc = searcher.doc(hits[i].doc);
output += "doc="+hits[i].doc+" score="+hits[i].score;
String title = doc.get("title");
if (title != null) {
output += " " + title;
}
System.out.println(output);
}
reader.close();
}
}

联系作者