Search-Engine/Lucene 2019. 1. 29. 23:04

Lucene - 분석기(Analyzer)로 분석한 토큰(Token)결과 출력




루씬에서 색인을 하기위해서는 선행과정이 있다. 물론 문서안에 정의된 여러개의 필드에 적용한 속성에 따라 다르긴 하지만 ANALYZE속성을 적용한 필드인 경우에는 색인하기 이전에 텍스트를 토큰으로 추출하고 그 토큰에 여러가지 메타정보(start,end 정수/위치증가값 등등의 데이터)를 섞은 텀으로 만든 후에 색인에 들어간다. 여기에서 보여줄 예제는 색인을 위한 텍스트에 분석기의 분석과정을 적용 후에 어떻게 토큰이 분리되는지 확인하는 간단한 예제이다.


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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.lucene.study;
 
 
 
import org.apache.lucene.analysis.core.SimpleAnalyzer;
 
import org.apache.lucene.analysis.core.StopAnalyzer;
 
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 
import org.apache.lucene.analysis.tokenattributes.CharTermAttributeImpl;
 
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 
import org.apache.lucene.util.AttributeImpl;
 
 
 
import java.io.IOException;
 
import java.io.StringReader;
 
 
 
 
 
import org.apache.lucene.analysis.*;
 
 
 
public class AnalyzerTest {
 
    
 
    
 
    private static final String[] examples = {"The quick brown fox jumped over the lazy dog","XY&Z Corporation - zyz@example.com","안녕하세요? 피자 주문하려고 하는데요."};
 
    
 
    private static final Analyzer[] analyzers = new Analyzer[] {new WhitespaceAnalyzer(),new SimpleAnalyzer(),new StopAnalyzer(),new StandardAnalyzer()};
 
    
 
    
 
    public static void analyze(String text) throws IOException {
 
        System.out.println("Analyzing \"" +text+ "\"");
 
        System.out.println("\n");
 
        for(Analyzer analyzer : analyzers) {
 
            
 
            String name = analyzer.getClass().getSimpleName();
 
            System.out.println("  "+name+"  ");
 
            System.out.print("    ");
 
            AnalyzerUtils.displayTokens(analyzer,text);
 
            
 
            System.out.println("\n");
 
        }
 
    }
 
    public static void main(String[] args) throws IOException {
 
        // TODO Auto-generated method stub
 
        String[] strings = examples;
 
        
 
        for(String text:strings) {
 
            analyze(text);
 
        }
 
    }
 
 
 
}
 
 
 
class AnalyzerUtils{
 
    public static void displayTokens(Analyzer analyzer,String text) throws IOException {
 
        displayTokens(analyzer.tokenStream("content"new StringReader(text)));
 
    }
 
    
 
    public static void displayTokens(TokenStream stream) throws IOException {
 
        
 
        //텀 속성확인
 
        CharTermAttribute cattr = stream.addAttribute(CharTermAttribute.class);
 
        
 
        //위치증가값 속성 확인
 
        PositionIncrementAttribute postAtrr = stream.addAttribute(PositionIncrementAttribute.class);
 
        //오프셋위치확인
 
        OffsetAttribute offsetAttr = stream.addAttribute(OffsetAttribute.class);
 
        //텀타입 속성 확인
 
        TypeAttribute typeAttr = stream.addAttribute(TypeAttribute.class);
 
        
 
        
 
        stream.reset();
 
        
 
        int position = 0;
 
        
 
        while (stream.incrementToken()) {
 
            int increment = postAtrr.getPositionIncrement();
 
            
 
            position = position + increment;
 
            System.out.println();
 
            System.out.print(position + ": ");
 
            System.out.print("[ "+cattr.toString()+" : " + offsetAttr.startOffset()+"->"+offsetAttr.endOffset()+" : "+typeAttr.type()+" ]");
 
        }
 
 
 
        stream.end();
 
        stream.close();
 
        
 
    }
 
}
cs



posted by 여성게
:
Search-Engine/Lucene 2019. 1. 3. 21:07

Lucene library를 이용한 간단한 색인/검색(루씬 라이브러리이용)



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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.lucene.study;
 
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
 
//yeoseong_yoon,2019/1/2->txt파일을 색인하는 유틸클래스
public class Indexer {
    
    private IndexWriter writer;
    
    //루씬의 IndexWriter 생성
    public Indexer(String indexDir) throws IOException {
        Directory dir = FSDirectory.open(new File(indexDir).toPath());
        
        writer = new IndexWriter(dir,new IndexWriterConfig(new StandardAnalyzer()));
    }
    
    public void close() throws IOException {
        writer.close();
    }
    
    public int index(String dataDir,FileFilter filter) throws IOException {
        
        File[] files = new File(dataDir).listFiles();
        
        for(File f : files) {
            if(!f.isDirectory() &&
                    !f.isHidden() &&
                    f.exists() &&
                    f.canRead() &&
                    (filter == null || filter.accept(f))) {
                
                indexFile(f);
            }
        }
        
        //색인된 문서건수 리턴
        return writer.numDocs();
    }
    
    private void indexFile(File file) throws IOException {
        System.out.println("Indexing "+file.getCanonicalPath());
        Document doc = getDocument(file);
        writer.addDocument(doc);
    }
    
    @SuppressWarnings("deprecation")
    protected Document getDocument(File file) throws IOException {
        Document doc = new Document();
        //파일의 내용추가
        doc.add(new Field("content"new FileReader(file)));
        //파일 이름추가
        doc.add(new Field("filename",file.getName(),Field.Store.YES,Field.Index.NOT_ANALYZED));
        //파일 전체경로 추가
        doc.add(new Field("fullpath",file.getCanonicalPath(),Field.Store.YES,Field.Index.NOT_ANALYZED));
        
        return doc;
    }
    
    //FileFilter를 이용하여 해당 확장자만 걸러낸다.
    private static class TextFilesFilter implements FileFilter{
        public boolean accept(File path) {
            return path.getName().toLowerCase().endsWith(".txt");
        }
    }
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        if(args.length!=2) {
            throw new IllegalArgumentException("please input index dir & data dir");
        }
        
        String indexDir = args[0];
        String dataDir = args[1];
        
        long start = System.currentTimeMillis();
        Indexer indexer = new Indexer(indexDir);
        int numIndexed = 0;
        
        try {
            numIndexed = indexer.index(dataDir, new TextFilesFilter());
        }catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        }finally {
            indexer.close();
        }
        
        long end = System.currentTimeMillis();
        
        System.out.println("Indexing " + numIndexed + " files took " + (end-start) + " milliseconds");
    }
 
}
 
cs


=>특정 디렉토리에 들어있는 txt파일을 특정 디렉토리 위치에 색인하는 코드이다.(실행시 arguments로 2개의 인자를 전달해야한다.)




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
53
54
55
56
57
58
59
60
61
62
63
64
package com.lucene.study;
 
import java.io.File;
import java.io.IOException;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
 
public class Searcher {
    
    public static void search(String indexDir, String query) throws IOException, ParseException {
        
        Directory dir = FSDirectory.open(new File(indexDir).toPath());
        
        //색인을 연다.
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(dir));
        
        //질의를 분석한다.
        QueryParser parser = new QueryParser("content",
                                            new StandardAnalyzer());
        
        Query q = parser.parse(query);
        
        long start = System.currentTimeMillis();
        
        TopDocs hits = indexSearcher.search(q, 10);
        
        long end = System.currentTimeMillis();
        
        System.out.println("Found "+ hits.totalHits +" document(s) (in "+(end-start) 
                +" millisecond) that matched query '" + q + "' :");
        
        for(ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = indexSearcher.doc(scoreDoc.doc);
            System.out.println(String.format("content - %s\nfullpath = %s"
                                            ,doc.get("content"
                                            ,doc.get("fullpath")));
        }
        
    }
 
    public static void main(String[] args) throws IOException, ParseException {
        // TODO Auto-generated method stub
        if(args.length !=2) {
            throw new IllegalArgumentException("please input index dir & search query String");
        }
        
        String indexDir = args[0];
        String query = args[1];
        search(indexDir, query);
    }
 
}
 
cs

=>위에서 색인한 색인 목록에서 검색어에 해당하는 문서를 찾는 코드이다. 이도 동일하게 2개의 아규먼트를 전달해야한다.




2개의 간단한 색인/검색을 예제로 짜보았다. 이 코드를 짜기전에 루씬 라이브러리를 dependency 해야하는 선과정이 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- START - Lucene Dependencies -->
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>5.3.0</version>
</dependency>
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-common</artifactId>
      <version>5.3.0</version>
</dependency>
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-queryparser</artifactId>
      <version>5.3.0</version>
</dependency>
<!-- END - Lucene Dependencies -->
cs


posted by 여성게
:

Solr 7.4.x version의 Tagger Handler를 이용한 NER(Named-Entity Recognition)


NER이란 자연어에서 뜻이 있는 단어를 뽑아내는 것이다. 챗봇처럼 자연어와 관련있는 기술에서 사용되는 기능인데, 예를 들어 "피자 주문할게요" 라는 질문이 있다. 이 문장에서 유추해 볼 수 있는 것은 "피자 주문" 이것을 더 보편화시켜 보면 "메뉴 주문"이라는 사용자질의 "의도"를 알 수 있다. 만약 "햄버거 주문할게요"라는 질의가 있으면 이 또한 "메뉴 주문"이라는 의도라는 것을 유추할 수 있다. 그럼 사용자가 어떠한 메뉴를 주문한다는 것은 알겠는데 그럼 그 메뉴가 무엇인가? 이러한 것이 챗봇에서는 NER이라는 기술로 추출해 낼 수 있는 단어라는 것이다. "피자 주문할게요"라는 질의의 의도는 "메뉴 주문" 개체명(Named-Entity)는 "피자"가 되는 것이다. 이렇게 의미있는 단어를 뽑아내는 것은 아주 중요한 챗봇의 기술이며 앞으로 더욱 발전해야할 기술이기도 하다. 이 NER 기술을 solr 7.4의 Tagger Handler를 이용하여 구현해 볼 것이다.


모든 구현은 Mac OS 환경에서 구현하였습니다.


우선 구현에 앞서 선행되어야 하는 것이, 바로 이전의 글인 Solr와 Zookeeper 연동입니다. 모든 환경은 solr cloud 환경에서 구현하였기에 중복되는 설명은 배제하였습니다.



$SOLR_HOME/server/solr 폴더 밑에 configset이 존재할 것입니다. 그 폴더를 보면 디폴트 환경설정 파일이 있는데, 그 안의 schema.xml과 solrconfig.xml 파일을 만질 것입니다. 이전에 따로 사용중이셨던 설정파일을 이용하셔도 무관합니다.




<schema.xml>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<field name="DOCID" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="NER_VALUE" type="text_general" indexed="true"/>
<field name="NER_KEY" type="string" indexed="true"/>
<field name="NER_TAG" type="tag" stored="false"/>
<copyField source="NER_VALUE" dest="NER_TAG"/>
<uniqueKey>DOCID</uniqueKey>
<fieldtype name="tag" class="solr.TextField" positionIncrementGap="100" postingsFormat="FST50"
        omitTermFreqAndPositions="true" omitNorms="true">
    <analyzer type="index">
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.ConcatenateGraphFilterFactory" preservePositionIncrements="false"/>
    </analyzer>
    <analyzer type="query">
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>  
</fieldtype>
cs



적당한 위치에 선언해줍니다. NER과 관련된 필드들과 필드 타입을 선언한 설정입니다.


<solrconfig.xml>

1
2
3
4
5
<requestHandler name="/tag" class="solr.TaggerRequestHandler">
    <lst name="defaults">
      <str name="field">NER_TAG</str>
    </lst>
</requestHandler>
cs


적당한 위치에 선언해줍니다. 개체명을 추출하기 위한 Tagger Handler를 등록하는 설정입니다.


만약 디폴트 설정파일을 이용하지 않고 새로 정의하셨다면 해당 설정 폴더를 SolrConifg/conf/ 밑으로 넣어줍니다.(SolrConfg는 맘대로정의 가능하지만 conf는 맞춰주셔야합니다.)


solr 디렉토리의 적당한 곳에 위치시켜줍니다. 그리고 난 후에 zookeeper에 upconfig를 합니다.(solr collection들이 사용할 설정파일을 zookeeper에 업로드하여 중앙에서 관리해줍니다.)

저는 앞으로 자주 사용할 가능성이 있다 판단이 되어서 쉘스크립트 실행파일을 만들어서 upconfig 했습니다.


<uploadSolrConfig.sh>

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
 
# usage uploadSolrConfig.sh zipConfigPath configName zkServers
 
CONFIG_PATH=$1
CONFIG_NAME=$2
ZK_SERVERS=$3
 
# upload config info
 
$HOME/solr/bin/solr zk upconfig -n $CONFIG_NAME -d $CONFIG_PATH -z $ZK_SERVERS
 
echo $?
cs


~$ ./uploadSolrConfig $SOLR_HOME/SolrConfig TaggerHandlerConf localhost:2181,localhost:2182,localhost:2183 으로 실행시켜줍니다.

이후 solr admin 페이지를 들어가서 cloud>file>confg에 생성한 이름으로 conf file이 생성되었는지 확인합니다. 그리고 Collection탭에 들어가서 생성한 conf file을 이용하여 컬렉션을 생성해줍니다.(collectionName = TaggerHandler)




<indexing file>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<add>
    <doc>
        <field name="DOCID">NER1</field>
        <field name="NER_VALUE">햄버거</field>
        <field name="NER_KEY">메뉴</field>
    </doc>    
    <doc>
        <field name="DOCID">NER2</field>
        <field name="NER_VALUE">피자</field>
        <field name="NER_KEY">메뉴</field>
    </doc>
    <doc>
        <field name="DOCID">NER3</field>
        <field name="NER_VALUE">치킨</field>
        <field name="NER_KEY">메뉴</field>
    </doc>    
    <doc>
        <field name="DOCID">NER</field>
        <field name="NER_VALUE">파스타</field>
        <field name="NER_KEY">메뉴</field>
    </doc>
</add>
cs


그리고 색인할 파일을 만듭니다.


$SOLR_HOME/bin/post -c TaggerHandler $SOLR_HOME/menu.xml 로 색인해줍니다.


이제 모든 작업이 끝이 났고, 적당한 request를 보내서 결과를 확인하면 됩니다.


요청은 POST 방식이고, 요청헤더에 Content-Type text/plain 요청바디에 "햄버거, 피자,피자 주문할게요"라는 질의를 넣고 밑의 요청을 보내면 (url 테스트를 위하여 저는 간편한 postman이라는 툴을 사용하였습니다. curl을 사용해도 무관합니다.)


http://localhost:8983/solr/TaggerHandler/tag?overlaps=NO_SUB&tagsLimit=5000&fl=DOCID,NER_VALUE,NER_KEY&wt=json&intent=on


{ "responseHeader": { "status": 0, "QTime": 35 }, "tagsCount": 3, "tags": [ [ "startOffset", 0, "endOffset", 3, "ids", [ "NER1" ] ], [ "startOffset", 5, "endOffset", 7, "ids", [ "NER2" ] ], [ "startOffset", 8, "endOffset", 10, "ids", [ "NER2" ] ] ], "response": { "numFound": 2, "start": 0, "docs": [ { "DOCID": "NER1", "NER_VALUE": "햄버거", "NER_KEY": "메뉴" }, { "DOCID": "NER2", "NER_VALUE": "피자", "NER_KEY": "메뉴" } ] } }


=>이런 결과가 나옵니다!!!


posted by 여성게
:

Solr&Zookeeper(솔라&주키퍼) cloud 환경 구성



Mac OS 환경에서 작성되었습니다.


solr와 zookeeper를 연동하여 cloud 환경구성하기 입니다.


우선 진행하기 전에 수정 혹은 생성 되어야할 설정 목록입니다.


1)solr.xml : solr cloud를 이루는 solr instance에 관한 설정파일입니다.

2)zoo.cfg : zookeeper 관련 설정파일입니다.

3)collection config file : solr collection들이 가지게 될 schema.xml,solrConfig.xml 등의 파일이 들어가는 config file입니다. 이 파일은 zookeeper에 upconfig하여 모든 solr                                         instance들이 공유하게 됩니다.

4)zooServer Dir : zookeeper들은 파일로써 data snapshot을 저장합니다. 그러한 snapshot파일과 zookeeper 식별 파일들이 들어가는 디렉토리입니다.




<solr.xml>

solr의 폴더의 위치를 편하게 $SOLR_HOME이라고 표현합니다.


$SOLR_HOME/server/solr

$SOLR_HOME/server/solr2

$SOLR_HOME/server/solr3


이렇게 3개의 폴더를 생성해줍니다.(solr는 원래 존재하는 폴더) 그리고 solr folder 밑에 solr.xml파일을 복사하여 solr2/solr3 folder 밑에 복사해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<solr>
  <solrcloud>
    <str name="host">localhost</str>
    <int name="hostPort">${jetty.port:8983}</int>
    <str name="hostContext">${hostContext:solr}</str>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
    <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
    <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
  </solrcloud>
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
  </shardHandlerFactory>
</solr>
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<solr>
  <solrcloud>
    <str name="host">localhost</str>
    <int name="hostPort">${jetty.port:8984}</int>
    <str name="hostContext">${hostContext:solr}</str>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
    <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
    <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
  </solrcloud>
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
  </shardHandlerFactory>
</solr>
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<solr>
  <solrcloud>
    <str name="host">localhost</str>
    <int name="hostPort">${jetty.port:8985}</int>
    <str name="hostContext">${hostContext:solr}</str>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
    <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
    <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
  </solrcloud>
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
  </shardHandlerFactory>
</solr>
 
cs


총 3개의 solr.xml이 각각의 solr,solr2,solr3 폴더에 위치하게 됩니다. 보시는 것과 같이 service port 만 달라지게 됩니다. 이렇게 solr instance가 3개가 뜨게 되며, 각각 8983,8984,8985 port로 요청을 받게 됩니다. 여기서 인스턴스를 홀수개로 띄우는 이유는 리더선출에 관련되어 있습니다. 반드시 클라우드 환경에서는 홀수개의 인스턴스를 띄워줘야 합니다.


그리고 $SOLR_HOME에 적절한 위치에 zookeeper에 upconfig할 config file folder를 위치시켜줍니다. 본인은 server folder와 같은 위치에 solr-config 라는 folder로 만듬. 그리고 이 solr-config 밑에는 conf라는 폴더가 위치해야하고 conf라는 폴더 밑에 upconfig를 위한 설정파일들이 위치해야함.







<zoo.cfg>

zookeeper가 위치한 폴더경로를 편하게 $ZK_HOME이라고 표현하겠습니다.


1
2
3
4
5
6
7
8
9
#tickTime=2000
initLimit=10
syncLimit=5
dataDir=/Users/yun-yeoseong/zooServer/zookeeper/1
clientPort=2181
 
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
cs


1
2
3
4
5
6
7
8
9
#tickTime=2000
initLimit=10
syncLimit=5
dataDir=/Users/yun-yeoseong/zooServer/zookeeper/2
clientPort=2182
 
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
cs

1
2
3
4
5
6
7
8
9
#tickTime=2000
initLimit=10
syncLimit=5
dataDir=/Users/yun-yeoseong/zooServer/zookeeper/3
clientPort=2183
 
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
cs


$ZK_HOME/conf 밑에 총 3개의 zoo.cfg,zoo2.cfg,zoo3.cfg 파일들을 생성해줍니다. 여기서 dataDir는 zookeeper의 식별자파일 및 data snapshot들이 저장되는 경로입니다. 3개의 zookeeper 인스턴스는 각각의 zoo.cfg를 갖습니다.(zookeeper도 동일한 이유로 홀수개의 인스턴스를 띄움). clientPort는 말그대로 zookeeper가 사용할 포트입니다. 그 밑에 server.1=~는 총개의 zookeeper의 인스턴스를 뜻하며 2개의 포트는 zookeeper들끼리 통신할 포트와 리더선출을 위한 포트로 사용됩니다.



<dataDir>



zoo.cfg에 작성한 dataDir와 동일한 위치에 디렉토리 구조를 잡아줍니다. 그리고 그 디렉토리 밑에 확장자가 없는 myid파일들을 각각 만들어줍니다.(~$vi myid) 그리고 각 내용은 상위 폴더와 같이 1/myid는 1이라는 숫자하나 2/myid는 2라는 숫자하나 3/myid는 3이라는 숫자하나만 넣어줍니다.(version-2 폴더는 필자가 cloud환경을 운영하다 생긴 폴더입니다. zookeeper의 data snapshot 파일등이 들어가있습니다. 별도로 생성할 필요 x)


여기까지 모두 세팅이 완료되었습니다.


zookeeper 실행

bin/zkServer.sh start conf/zoo.cfg

bin/zkServer.sh start conf/zoo2.cfg

bin/zkServer.sh start conf/zoo3.cfg 


zookeeper 종료

bin/zkServer.sh stop conf/zoo.cfg

bin/zkServer.sh stop conf/zoo2.cfg

bin/zkServer.sh stop conf/zoo3.cfg


solr 실행

bin/solr start -c -s server/solr -p 8983 -z localhost:2181,localhost:2182,localhost:2183 -noprompt

bin/solr start -c -s server/solr2 -p 8984 -z localhost:2181,localhost:2182,localhost:2183 -noprompt

bin/solr start -c -s server/solr3 -p 8985 -z localhost:2181,localhost:2182,localhost:2183 -noprompt


solr 종료
bin/solr stop -c -s server/solr -p 8983 -z localhost:2181,localhost:2182,localhost:2183 -noprompt
bin/solr stop -c -s server/solr2 -p 8984 -z localhost:2181,localhost:2182,localhost:2183 -noprompt
bin/solr stop -c -s server/solr3 -p 8985 -z localhost:2181,localhost:2182,localhost:2183 -noprompt

이렇게 하면 zookeeper 3개 solr 3개가 올라가는 cloud환경을 구성할 수 있습니다. 지금은 로컬환경이라 한 서버에 3개를 모두 올렸지만 나중에 서버환경에서는 각각 다른 서버에 올리시면 됩니다. 여기서 중요한 것은 만약 다른 서비스 프로젝트에서 solr와 연동되어 사용될때, solr 요청 ip가 localhost가 아니라 server_ip로 요청(즉, 로컬환경이 아니라 서버배포환경)이 간다면 처음에 solr.xml에 설정된 <str name="host">server_ip</str>로 변경해주셔야합니다. 다른 서비스프로젝트에서는 server_ip로 요청을 보내고 cloud환경의 solr는 localhost로 띄운다면 정상적으로 동작하지 않을 것입니다.

여기까지입니다.

여기서 생략된 부분이 몇가지 있다면 solr의 zk upconfig 명령어를 이용하여 solr-config에 있는 config file을 zookeeper에 업로드하는 명령어와 기타 색인 및 쿼리 작업들은 생략되었습니다. 이 부분들은 대부분 구글링 하면 많은 것이라 생각하기에...생략하였습니다. 하지만 중요한 부분입니다. collection을 생성할때에는 반드시 config파일의 이름을 물고 생성이 되기때문에 해당 config파일이 upconfig되어있어야지만 collection이 생성됩니다.(default config파일도 있지만 사용자들이 커스터마이징한 config file을 사용한다는 전제하에) 혹시라도 이 부분에 대해 확실한 답을 찾지 못하셨거나 궁금하신 점이 있으면 댓글 달아주시면 개인적으로 메일드리겠습니다.

posted by 여성게
:

Elasticsearch local 환경에서 하나의 클러스터에 n개 이상의 노드(인스턴스)생성


데이터 경로는 다른 클러스터의 여러 노드에 의해 공유 될수 있다. 이는 개발 시스템에서 장애 조치 및 다른 구성을 테스트하는데는 유용하다. 하지만 운영환경에서는 하나의 서버당 하나의 노드만 실행하는 것이 좋다. , 하나의 서버에 하나의 노드만 실행시키기 위해서는 node.max_local_storage_nodes:1 로 설정하고, 만약 하나의 머신에서 여러 개의 노드를 실행시키기 위해서는 설정을 1 이상으로 조정해야된다. 만약 한 머신에서 두개 이상의 노드를 운영한다면 샤드들은 elasticsearch에서 자동으로 분배해준다.


elasticsearch.yml의 적당한 위치에 node.max_local_storage_nodes를 생성할 노드수 만큼 설정을 해준다. 그래서 한 데이터를 여러노드가 공유할 수 있게 설정을 해주면 된다. 그리고 여러개의 터미널을 켜서 node 인스턴스들을 생성하면 생성된 샤드가 자동으로 각 노드에 분배가 된다.


culr "localhost:9200/_cat/shards?v" 명령어를 실행시켜서 shard들의 분배상태를 확인할수 있다.


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
index        shard prirep state   docs store ip        node
get-together 1     r      STARTED    1 7.9kb 127.0.0.1 _qpD4qV
get-together 1     p      STARTED    1 7.9kb 127.0.0.1 f6xhNIi
get-together 3     r      STARTED    1 7.9kb 127.0.0.1 _qpD4qV
get-together 3     p      STARTED    1 7.9kb 127.0.0.1 f6xhNIi
get-together 4     r      STARTED    1 7.5kb 127.0.0.1 _qpD4qV
get-together 4     p      STARTED    1 7.5kb 127.0.0.1 f6xhNIi
get-together 2     r      STARTED    2 8.9kb 127.0.0.1 _qpD4qV
get-together 2     p      STARTED    2 8.9kb 127.0.0.1 f6xhNIi
get-together 0     r      STARTED    0  261b 127.0.0.1 _qpD4qV
get-together 0     p      STARTED    0  261b 127.0.0.1 f6xhNIi
myindex      1     r      STARTED    0  261b 127.0.0.1 _qpD4qV
myindex      1     p      STARTED    0  261b 127.0.0.1 f6xhNIi
myindex      3     r      STARTED    0  261b 127.0.0.1 _qpD4qV
myindex      3     p      STARTED    0  261b 127.0.0.1 f6xhNIi
myindex      4     r      STARTED    0  261b 127.0.0.1 _qpD4qV
myindex      4     p      STARTED    0  261b 127.0.0.1 f6xhNIi
myindex      2     r      STARTED    0  261b 127.0.0.1 _qpD4qV
myindex      2     p      STARTED    0  261b 127.0.0.1 f6xhNIi
myindex      0     r      STARTED    0  261b 127.0.0.1 _qpD4qV
myindex      0     p      STARTED    0  261b 127.0.0.1 f6xhNIi
new-index    1     r      STARTED    0  261b 127.0.0.1 _qpD4qV
new-index    1     p      STARTED    0  261b 127.0.0.1 f6xhNIi
new-index    3     r      STARTED    0  261b 127.0.0.1 _qpD4qV
new-index    3     p      STARTED    0  261b 127.0.0.1 f6xhNIi
new-index    4     r      STARTED    0  261b 127.0.0.1 _qpD4qV
new-index    4     p      STARTED    0  261b 127.0.0.1 f6xhNIi
new-index    2     r      STARTED    0  261b 127.0.0.1 _qpD4qV
new-index    2     p      STARTED    0  261b 127.0.0.1 f6xhNIi
new-index    0     r      STARTED    0  261b 127.0.0.1 _qpD4qV
new-index    0     p      STARTED    0  261b 127.0.0.1 f6xhNIi
 
cs

posted by 여성게
: