文章目錄
  1. 1. 一、再次系统认识Solr
    1. 1.1. 1.1 Solr组件架构
    2. 1.2. 1.2 Solr目录结构
      1. 1.2.1. 1.2.1 Solr程序包的目录结构
      2. 1.2.2. 1.2.2 SolrCore文件主目录结构
    3. 1.3. 1.3 重新理解Solr各个概念
      1. 1.3.1. 1.3.1 Solr基础——Core
      2. 1.3.2. 1.3.2 Core与Replica(副本|拷贝)
      3. 1.3.3. 1.3.3 Collection与Shard
    4. 1.4. 1.4 Solr动作过程
      1. 1.4.1. 1.4.1 索引创建过程
      2. 1.4.2. 1.4.2 数据搜索过程
      3. 1.4.3. 1.4.3 Shard 分片过程
  2. 2. 二、Solr入口SolrDispatchFilter解析
    1. 2.1. 2.1 关于web.xml
    2. 2.2. 2.2 配置文件读取与核心初始化
      1. 2.2.1. 2.2.1 SolrDiaspatchFilter初始化
      2. 2.2.2. 2.2.2 CoreContainer
      3. 2.2.3. 2.2.3 CoreContainer.load()方法
      4. 2.2.4. 2.2.4 CoreContainer.create()方法
      5. 2.2.5. 2.2.5 CoreContainer.createFromLocal()本地创建方法
      6. 2.2.6. 2.2.6 CoreContainer.registerCore()方法
    3. 2.3. 2.3 SolrResourceLoader类分析
      1. 2.3.1. 2.3.1 三种获取资源路径的方式
      2. 2.3.2. 2.3.2 SolrResourceLoader构造方法
    4. 2.4. 2.4 ConfigSolr类分析
    5. 2.5. 2.5 SolrDisPatchFilter.doFilter()方法

摘要:为了使用Solr进行开发与调参,需要我们对其内部原理进行了解学习.第一部分再次从Solr的组件关系,solr目录结构,Solr各个概念重新认识Solr整体;第二部分从Solr的入口类分析,需要从源码加载配置文件、初始化各个core、初始化各个core中的requesthandler这个过程开始。

一、再次系统认识Solr


1.1 Solr组件架构


 Solr的主要组件关系图:





注:该图取自Solr中国
 通过图片能清晰的看出,整个Solr服务大致可以归纳成一下几个大模块构成:

  • 1、核心分布式索引
  • 2、更新程序
  • 3、检索组件
  • 4、文本处理
  • 5、分片策略与同步机制
  • 6、分布式统一配置
  • 7、对外接口
  • …….

 每一个组件各司其职,将底层lucene进行封装,提供对外的开发接口与更好的分布式支持。

1.2 Solr目录结构


1.2.1 Solr程序包的目录结构


 整个Solr程序的Core结构如图所示:





 下面对其包的主要结构进行解释:

包名称 作用解释
cloud-secript ZooKeeper的shell脚本
contrib 存放爱好者贡献的代码
dist 存放Solr 构建完成的 JAR 文件、WAR 文件和 Solr 依赖的 JAR 文件
example 是一个安装好的Jetty 中间件,其中包括一些样本数据和 Solr 的配置信息
etc Jetty 的配置文件
multicore 当安装Slor multicore 时,用来放置多个 Solr 主目录
example-DIH 可以作为solr的主目录,里面包含多个索引库,以及hsqldb的数据,里面有连接数据库的配置示例,以及邮件、rss的配置示例
solr 默认安装时一个Solr 的主目录
solr-webapp 存放执行solr服务后解压war包生成的可执行文件包含lib、WEB-INF等文件
webapps Solr 的 WAR 文件部署在这里

1.2.2 SolrCore文件主目录结构


 一个运行的 Solr 服务其主目录包含了 Solr 的配置文件和数据(Lucene 的索引文件) Solr 的主目录展开后为如下结构:





 每一个Core下有conf文件夹和data文件夹,各文件夹列表:

包名称 作用解释
bin ZooKeeper的shell脚本
conf 放置配置文件
conf/schema.xml 建立索引的 schema 包含了字段类型定义和其相关的分析器
conf/solrconfig.xml Solr 主要的配置文件
conf/xslt 包含了很多xslt 文件,这些文件能将 Solr 的 XML 的查询结果转换为特定的格式,比如:Atom/RSS
data 放置 Lucene 产生的索引数据和日志数据

1.3 重新理解Solr各个概念


 在根据教程搭建Solr服务之后,需要重新回过头来理解一下Solr中的各个名称的概念,主要重新认知Collection、Core、Replication、Shard、Zookeeper等名词在Solr中的作用。

1.3.1 Solr基础——Core


 Solr Core的提出是为了增加管理灵活性和共用资源。在SolrCloud中有个不同点是它使用的配置是在Zookeeper中的。传统的Solr core的配置文件是在磁盘上的配置目录中。我们从Solr提供Cloud关系图来理解Core:





 上图中,在SolrCLoud分布模式下,各个10.1.198.XX:8XXX即代表着不同的Core服务,一个Solr中包含一个或者多个Solr Core,每个Solr Core可以独立提供索引和查询功能,每个Solr Core对应一个索引或者Collection的Shard。Core是Solr对索引单元管理的最小单位。

 从下面这张SolrCloud图可以比较直观地看清Core、Shard、Collection之间的关系。





 但在Solr独立模式下,Core和Collection概念是可以等同,并没有将一个Collection(或Core)进行分片(Shard split)。一个Core即一个Collection。比如我们只在创建了一个Core,则所有数据的索引都保存在这个Core之中,没有将其分片管理。

1.3.2 Core与Replica(副本|拷贝)


 Replica是Shard的一个拷贝。每个Replica存在于Solr的一个Core中。简单点理解,一个Shard下的每一个Core都保存这个Shard的索引数据,通过Solr内部的选举进行推选出这一个Shard下的哪一个Core作为Leader,哪一个作为Replica(拷贝)。

Leader:赢得选举的Shard replicas。每个Shard有多个Replicas,这几个Replicas需要选举来确定一个Leader。

 而且选举可以发生在任何时间,但是通常他们仅在某个Solr实例发生故障时才会触发。当进行索引documents时,SolrCloud会传递它们到此Shard对应的Leader,Leader再分发它们到全部Shard的Replicas。我们以下图举例说明Replica与Leader的关系:




 如果Solr启动的时候,参数numShards=2,那么前两个启动的node就是Leader,分别服务于这两个Shard,第三个和之后启动的node就是Replica,他们会依次按照那两个Leader加入cluster的顺序分配给那两个Leader(可以简单理解成直接分配剩余副本给Leader)。如图,Solr分配给Shard1与Shard2,各三个Core,每个Core都有可能是Leader或者Replica。

 也可以在启动Node的时候使用shardId参数直接把这个node分配给某个shard。当solr重启的时候,所有的node还是归属到重启之前所归属的shard,同个shard中先启动的node就是leader(first come first served)。当一个leader无法工作的时候,这个shard中的某一个replica会被自动推选为leader。

 在Solr Admin UI界面中,Cloud菜单里面可以看到哪个是Leader(实心圆圈者)。

1.3.3 Collection与Shard


 在Solr中,Collection和Shard都是逻辑意义上对索引数据的抽象,并不同于Core。可以把每个Collection理解为一个表,Shard是因为这个表过于大,而根据显示需要进行的分表。我们可以看一看这两者的概念。

Collection:在SolrCloud集群中逻辑意义上的完整的索引。它常常被划分为一个或多个Shard,它们使用相同的配置,包括Solr配置、索引字段配置、ZooKeeper配置等。如果Shard数超过一个,它就是分布式索引,SolrCloud让你通过Collection名称引用它,而不需要关心分布式检索时需要使用的和Shard相关参数。


Shard: Collection的逻辑分片。每个Shard被化成一个或者多个replicas,通过选举确定哪个是Leader。



 我们还是要来看这张图,可以这样理解:Collection类似一张数据库的表table,Shard类似数据库的分表table_201608(类比按时间进行拆分),Core类似不同存贮介质的备份。构成了整个SolrCloud。




1.4 Solr动作过程


1.4.1 索引创建过程


Solr/Lucene采用的是一种反向索引,所谓反向索引:就是从关键字到文档的映射过程,保存这种映射这种信息的索引称为反向索引

 在此我们是从架构上了解Solr服务创建索引的过程,不涉及Solr内部实现细节,索引创建的源码分析会放在日后进行编写。






 分布式索引的过程如下:

  • 1.用户可以把创建文档索引的请求提交给任一个Replica或Leader
  • 2.如果它不是Leader,它会把请求转交给和自己同Shard的Leader
  • 3.Leader把文档路由给本Shard的每个Replica,各自做索引
  • 4.如果文档基于路由规则并不属于本Shard,Leader会把它转交给该文档对应Shard的Leader
  • 5.对应的Leader会把文档路由给本Shard的每个Replica,各自做索引

 如果理解了1.3Solr的各个概念之后,这部分内容就很容易理解了。

1.4.2 数据搜索过程


 在后面入口分析会写到,这边提前提到,Solr查询是通过Http发送命令,Solr Servlet接受并进行处理。所以Solr的查询流程从SolrDispatchsFilter的dofilter方法开始。dofilter包含了对Http的各个请求的操作。





 分布式搜索过程如下:

  • 1.用户的一个查询请求,可以发送到含有该Collection的任意机器,Solr的内部处理逻辑会转到一个Replica
  • 2.此Replica会基于查询索引的方式,启动分布式查询,基于索引的Shard个数,把查询转换为多个子查询,并把每个子查询定位到对应Shard的任意一个Replica
  • 3.每个子查询返回查询结果
  • 4.最初的Replica合并每个子查询的查询结果,并把最终结果返回给用户

1.4.3 Shard 分片过程


 在一个Shard中的索引文档达到阀值时,或者接收到用户的shard-split的API命令,就可以启动shard分裂过程。此时,旧的shard仍然提供服务,旧shard上的现有索引文档,会再次提取并按照路由规则,转到新的shard上再次进行索引。





 如下步骤进行:

  • 1.用户可以把创建文档索引的请求提交给任一个Replica或Leader
  • 2.如果它不是Leader,它会把请求转交给和自己同Shard的Leader
  • 3.Leader将索引文档路由给本Shard的每个Replica,各自做索引
  • 4,6.在一个Shard中的索引文档达到阀值时,或者接收到用户的shard-split的API命令,就可以启动shard分裂过程。此时旧的shard的Leader会将索引文档路由给新的shard的Leader
  • 5,7.新的shard的Leader会将索引文档路由给本Shard的每个Replica,各自做索引.

 在旧的索引文档重新索引完成后,系统会把分发文档的路由切换到对应的新的Leader上,旧Shard关闭

二、Solr入口SolrDispatchFilter解析


2.1 关于web.xml

 与其他web框架类似的是,其启动的交由容器来控制,都是从filter或者servlet开始。我们先来看Solr的web.xml文件。

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
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5"
metadata-complete="true"
>



<!-- Uncomment if you are trying to use a Resin version before 3.0.19.
Their XML implementation isn't entirely compatible with Xerces.
Below are the implementations to use with Sun's JVM.
<system-property javax.xml.xpath.XPathFactory=
"com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl"/>
<system-property javax.xml.parsers.DocumentBuilderFactory=
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"/>
<system-property javax.xml.parsers.SAXParserFactory=
"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl"/>
-->


<!-- People who want to hardcode their "Solr Home" directly into the
WAR File can set the JNDI property here...
-->

<!--
<env-entry>
<env-entry-name>solr/home</env-entry-name>
<env-entry-value>/put/your/solr/home/here</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
-->


<!-- Any path (name) registered in solrconfig.xml will be sent to that filter -->
<!-- 在web.xml中配置的该Filter是所有Solr请求的入口,从它实现的接口可以得知,它做的工作即初始化Solr核心容器CoreContainer,拦截所有http请求,对请求进行处理 -->
<filter>
<filter-name>SolrRequestFilter</filter-name>
<filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class>
<!-- If you are wiring Solr into a larger web application which controls
the web context root, you will probably want to mount Solr under
a path prefix (app.war with /app/solr mounted into it, for example).
You will need to put this prefix in front of the SolrDispatchFilter
url-pattern mapping too (/solr/*), and also on any paths for
legacy Solr servlet mappings you may be using.
For the Admin UI to work properly in a path-prefixed configuration,
the admin folder containing the resources needs to be under the app context root
named to match the path-prefix. For example:

.war
xxx
js
main.js
-->

<!--
<init-param>
<param-name>path-prefix</param-name>
<param-value>/xxx</param-value>
</init-param>
-->

</filter>

<filter-mapping>
<!--
NOTE: When using multicore, /admin JSP URLs with a core specified
such as /solr/coreName/admin/stats.jsp get forwarded by a
RequestDispatcher to /solr/admin/stats.jsp with the specified core
put into request scope keyed as "org.apache.solr.SolrCore".

It is unnecessary, and potentially problematic, to have the SolrDispatchFilter
configured to also filter on forwards. Do not configure
this dispatcher as <dispatcher>FORWARD</dispatcher>.
-->

<filter-name>SolrRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

 在web.xml中配置的该Filter是所有Solr请求的入口,从它实现的Filter接口可以得知,它做的工作即初始化Solr核心容器CoreContainer,拦截所有http请求,对请求进行处理。

2.2 配置文件读取与核心初始化


2.2.1 SolrDiaspatchFilter初始化


 Filter接口想必大家都不陌生,属于Java Servlet包下的接口,它使Web容器可以改变对Solr发起的request和修改response。接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Filter {

//进行初始化
public void init(FilterConfig filterConfig) throws ServletException;

//拦截所有的http请求
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)

throws IOException, ServletException;


//进行注销的动作
public void destroy();
}

当请求到来时,SolrDiaspatchFilter调用init()方法,对Solr的core进行初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void init(FilterConfig config) throws ServletException
{

log.info("SolrDispatchFilter.init()");

try {
// web.xml configuration
this.pathPrefix = config.getInitParameter( "path-prefix" );

this.cores = createCoreContainer();//初始化Solr的CoreContainer容器
log.info("user.dir=" + System.getProperty("user.dir"));
}
catch( Throwable t ) {
// catch this so our filter still works
log.error( "Could not start Solr. Check solr/home property and the logs");
SolrCore.log( t );
}

log.info("SolrDispatchFilter.init() done");
}

 我们来看一下SorlDispatchFilter类中的createContainer方法:

1
2
3
4
5
6
7
8
9
10
/**
* Override this to change CoreContainer initialization
* @return a CoreContainer to hold this server's cores
* //CoreContainer中SolrResourceLoader 是用来加载solr home中的配置文件文件的
*/

protected CoreContainer createCoreContainer() {
CoreContainer cores = new CoreContainer();
cores.load();
return cores;
}

2.2.2 CoreContainer


 createCoreContainer这个方法是Solr初始化和启动的关键。我们顺便简单分析一下这个方法中用到的几个类和方法:

  • SolrResourceLoader 类如其名,是solr资源加载器。

  • ConfigSolr 是通过SolrResourceLoader来读取solr配置文件的中信息的。

 首先,我们可以跟踪代码进入CoreContainer的构造方法中,这个构造方法整个流程很有意思:

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
/**
* Create a new CoreContainer using system properties to detect the solr home
* directory. The container's cores are not loaded.
* @see #load()
*/

public CoreContainer() {
this(new SolrResourceLoader(SolrResourceLoader.locateSolrHome()));
}

/**
* Create a new CoreContainer using the given SolrResourceLoader. The container's
* cores are not loaded.
* @param loader the SolrResourceLoader
* @see #load()
*/

public CoreContainer(SolrResourceLoader loader) {
this(loader, ConfigSolr.fromSolrHome(loader, loader.getInstanceDir()));//ConfigSolr在需要上一个无参构造方法的类加载器获取的SolrHome路径,以此来加载solr.xml配置信息
}

/**
* Create a new CoreContainer using the given SolrResourceLoader and
* configuration. The container's cores are not loaded.
* @param loader the SolrResourceLoader
* @param config a ConfigSolr representation of this container's configuration
* @see #load()
*/

//import static com.google.common.base.Preconditions.checkNotNull;该类提供判空操作,有兴趣再阅读该工具方法的使用
public CoreContainer(SolrResourceLoader loader, ConfigSolr config) {
this.loader = checkNotNull(loader);
this.solrHome = loader.getInstanceDir();
this.cfg = checkNotNull(config);
}

 整个构造流程是:

  • 1、首先创建无参构造函数CoreContainer(),目的是通过SolrResourceLoader类加载系统配置信息去探测solr home(见注释)
  • 2、进入有一个参数的构造方法CoreContainer(SolrResourceLoader loader),该步骤获取无参构造方法生成的Solr类加载器—-SolrResourceLoader,该加载器已经在创建过程中获取了SolrHome中的各资源文件夹的路径。
  • 3、之后该单参构造函数并创建一个ConfigSolr类,该类保存了solr.xml文件的文件名和solr.xml中配置的各个参数的名称,用以读取用户配置solr.xml信息。ConfigSolr需要持有SolrResourceLoader进行Xml的Dom读取。
  • 4、读取完成之后,再次进入双参构造函数,改构造函数仅实现null判断与变量赋值。

 从整个流程上看,能清晰地看出整个Solr初始化核心容器并读取配置参数的整个流程,其中隐去了ConfigSolr读取xml的细节,之后会在补充。

2.2.3 CoreContainer.load()方法


 通过load()方法进行读取配置文件。并加载SolrCore进入CoreContainer,开启执行线程和关闭线程。load()方法较长,涉及到的类较多,首先进行初步分析,load()方法代码如下,已经在每个步骤加上自己对load源码的分析:

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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/**
* Load the cores defined for this CoreContainer
*/

public void load() {

log.info("Loading cores into CoreContainer [instanceDir={}]", loader.getInstanceDir());

ThreadPoolExecutor coreLoadExecutor = null;
//加载solr共享jar包库
// add the sharedLib to the shared resource loader before initializing cfg based plugins
libDir = cfg.get(ConfigSolr.CfgProp.SOLR_SHAREDLIB , null);
if (libDir != null) {
File f = FileUtils.resolvePath(new File(solrHome), libDir);
log.info("loading shared library: " + f.getAbsolutePath());
loader.addToClassLoader(libDir, null, false);
loader.reloadLuceneSPI();
}

//加载分片相关处理Handler
shardHandlerFactory = ShardHandlerFactory.newInstance(cfg.getShardHandlerFactoryPluginInfo(), loader);

//该方法懒分配暂时Core,并为暂时Core分配Cache,这部分还未深入,只从方法注释上解释
//注意区分SolrCore类和SolrCores类,两者的区别和作用
//SolrCore类似Core有实体类概念,提供Core的属性和方法
//SolrCores类似统一保存管理根据配置文件生成Core的集合,提供上层(CoreContainer)间接管理Core的方法
//这方面可以在之后深入理解其实现目的与实现思路,对提高大型项目的编码能力有很大的借鉴作用
solrCores.allocateLazyCores(cfg, loader);

logging = JulWatcher.newRegisteredLogWatcher(cfg, loader);

if (cfg instanceof ConfigSolrXmlOld) { //TODO: Remove for 5.0
String dcoreName = cfg.get(ConfigSolr.CfgProp.SOLR_CORES_DEFAULT_CORE_NAME, null);
if (dcoreName != null && !dcoreName.isEmpty()) {
defaultCoreName = dcoreName;
}
persistent = cfg.getBool(ConfigSolr.CfgProp.SOLR_PERSISTENT, false);
adminPath = cfg.get(ConfigSolr.CfgProp.SOLR_ADMINPATH, "/admin/cores");
} else {
adminPath = "/admin/cores";
defaultCoreName = DEFAULT_DEFAULT_CORE_NAME;
}

//从配置的XML文件钟读取各类配置信息
zkHost = cfg.get(ConfigSolr.CfgProp.SOLR_ZKHOST, null);
coreLoadThreads = cfg.getInt(ConfigSolr.CfgProp.SOLR_CORELOADTHREADS, CORE_LOAD_THREADS);


shareSchema = cfg.getBool(ConfigSolr.CfgProp.SOLR_SHARESCHEMA, DEFAULT_SHARE_SCHEMA);
zkClientTimeout = cfg.getInt(ConfigSolr.CfgProp.SOLR_ZKCLIENTTIMEOUT, DEFAULT_ZK_CLIENT_TIMEOUT);

distribUpdateConnTimeout = cfg.getInt(ConfigSolr.CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, 0);
distribUpdateSoTimeout = cfg.getInt(ConfigSolr.CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, 0);

// Note: initZooKeeper will apply hardcoded default if cloud mode
String hostPort = cfg.get(ConfigSolr.CfgProp.SOLR_HOSTPORT, null);
// Note: initZooKeeper will apply hardcoded default if cloud mode
String hostContext = cfg.get(ConfigSolr.CfgProp.SOLR_HOSTCONTEXT, null);

String host = cfg.get(ConfigSolr.CfgProp.SOLR_HOST, null);

String leaderVoteWait = cfg.get(ConfigSolr.CfgProp.SOLR_LEADERVOTEWAIT, LEADER_VOTE_WAIT);

adminHandler = cfg.get(ConfigSolr.CfgProp.SOLR_ADMINHANDLER, null);
managementPath = cfg.get(ConfigSolr.CfgProp.SOLR_MANAGEMENTPATH, null);

transientCacheSize = cfg.getInt(ConfigSolr.CfgProp.SOLR_TRANSIENTCACHESIZE, Integer.MAX_VALUE);

boolean genericCoreNodeNames = cfg.getBool(ConfigSolr.CfgProp.SOLR_GENERICCORENODENAMES, false);

if (shareSchema) {
indexSchemaCache = new ConcurrentHashMap<String,IndexSchema>();
}

zkClientTimeout = Integer.parseInt(System.getProperty("zkClientTimeout",
Integer.toString(zkClientTimeout)));

//初始化ZooKeeper
//由ZooKeeper构造函数可知,当配置文件没有配置ZooKeeper主机地址时,会返回null对象
zkSys.initZooKeeper(this, solrHome, zkHost, zkClientTimeout, hostPort, hostContext, host, leaderVoteWait, genericCoreNodeNames, distribUpdateConnTimeout, distribUpdateSoTimeout);

if (isZooKeeperAware() && coreLoadThreads <= 1) {
throw new SolrException(ErrorCode.SERVER_ERROR,
"SolrCloud requires a value of at least 2 in solr.xml for coreLoadThreads");
}

if (adminPath != null) {
if (adminHandler == null) {
coreAdminHandler = new CoreAdminHandler(this);
} else {
coreAdminHandler = this.createMultiCoreHandler(adminHandler);
}
}

collectionsHandler = new CollectionsHandler(this);
containerProperties = cfg.getSolrProperties("solr");

// setup executor to load cores in parallel
//多线程初始化core,根据xml文件配合的开启数SOLR_CORELOADTHREADS,启动对core的多线程初始化,在Solr启动速度优化参数调优有帮助
coreLoadExecutor = new ThreadPoolExecutor(coreLoadThreads, coreLoadThreads, 1,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
new DefaultSolrThreadFactory("coreLoadExecutor"));
try {
CompletionService<SolrCore> completionService = new ExecutorCompletionService<SolrCore>(
coreLoadExecutor);

//声明Set集合用以获取Callable接口执行向CoreContainer添加SolrCore的执行结果
Set<Future<SolrCore>> pending = new HashSet<Future<SolrCore>>();

List<String> allCores = cfg.getAllCoreNames();

for (String oneCoreName : allCores) {

try {
String rawName = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_NAME, null);

if (null == rawName) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Each core in solr.xml must have a 'name'");
}
final String name = rawName;
final CoreDescriptor p = new CoreDescriptor(this, name,
cfg.getProperty(oneCoreName, CoreDescriptor.CORE_INSTDIR, null));//CoreDescriptor是用来设置SolrCore的一些描述信息,具体细节未看

// deal with optional settings
String opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_CONFIG, null);

if (opt != null) {
p.setConfigName(opt);
}
opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_SCHEMA, null);
if (opt != null) {
p.setSchemaName(opt);
}

//当获取了ZooKeeper对象之后设置ZooKeeper参数
if (zkSys.getZkController() != null) {
opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_SHARD, null);
if (opt != null && opt.length() > 0) {
p.getCloudDescriptor().setShardId(opt);
}
opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_COLLECTION, null);
if (opt != null) {
p.getCloudDescriptor().setCollectionName(opt);
}
opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_ROLES, null);
if (opt != null) {
p.getCloudDescriptor().setRoles(opt);
}

opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_NODE_NAME, null);
if (opt != null && opt.length() > 0) {
p.getCloudDescriptor().setCoreNodeName(opt);
}
}
opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_PROPERTIES, null);
if (opt != null) {
p.setPropertiesName(opt);
}
opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_DATADIR, null);
if (opt != null) {
p.setDataDir(opt);
}

p.setCoreProperties(cfg.readCoreProperties(oneCoreName));

opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_LOADONSTARTUP, null);
if (opt != null) {
p.setLoadOnStartup(("true".equalsIgnoreCase(opt) || "on"
.equalsIgnoreCase(opt)) ? true : false);
}

opt = cfg.getProperty(oneCoreName, CoreDescriptor.CORE_TRANSIENT, null);
if (opt != null) {
p.setTransient(("true".equalsIgnoreCase(opt) || "on"
.equalsIgnoreCase(opt)) ? true : false);
}

if (p.isTransient() || ! p.isLoadOnStartup()) {
// Store it away for later use. includes non-transient but not
// loaded at startup cores.
solrCores.putDynamicDescriptor(rawName, p);
}

if (p.isLoadOnStartup()) { // The normal case
//创建Callable接口,初始化Call方法,向Core容器CoreContainer注册SolrCore
Callable<SolrCore> task = new Callable<SolrCore>() {
@Override
public SolrCore call() {
SolrCore c = null;
try {
if (zkSys.getZkController() != null) {//
preRegisterInZk(p);//预先向ZooKeeper注册CoreDescriptor即Core描述
}
//根据该核的描述创建一个SolrCore,见该CoreContainer类中的同名私有方法
c = create(p);
//向Core容器注册该新建的核,如果是使用ZooKeeper也是通过该方法向ZooKeeper注册,
//见该CoreContainer类中的同名私有方法
registerCore(p.isTransient(), name, c, false);
} catch (Throwable t) {
if (isZooKeeperAware()) {
try {
zkSys.zkController.unregister(name, p);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
SolrException.log(log, null, e);
} catch (KeeperException e) {
SolrException.log(log, null, e);
}
}
SolrException.log(log, null, t);
if (c != null) {
c.close();
}
}
return c;
}
};
pending.add(completionService.submit(task));//多线程执行,并将执行结果放入结果集合

}
} catch (Throwable ex) {
SolrException.log(log, null, ex);
}
}

//开启循环,执行线程池,等待线程执行结束
while (pending != null && pending.size() > 0) {
try {

Future<SolrCore> future = completionService.take();
if (future == null) return;
pending.remove(future);

try {
SolrCore c = future.get();//如果子线程未完成,则主线程回阻塞,等待子线程
// track original names
if (c != null) {
solrCores.putCoreToOrigName(c, c.getName());
}
} catch (ExecutionException e) {
SolrException.log(SolrCore.log, "Error loading core", e);
}

} catch (InterruptedException e) {
throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
"interrupted while loading core", e);
}
}

// Start the background thread
//设置后台关闭线程,见该CoreContainer类中的同名内部类CloserThread
backgroundCloser = new CloserThread(this, solrCores, cfg);
backgroundCloser.start();

} finally {
if (coreLoadExecutor != null) {
ExecutorUtil.shutdownNowAndAwaitTermination(coreLoadExecutor);
}
}
}

2.2.4 CoreContainer.create()方法


 通过2.2.3代码分析中可知,在需要根据Core描述创建的SolrCore,使用的是当前类中的protected方法:create(),其代码如下:

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
/**
* Creates a new core based on a descriptor but does not register it.
* 由注释可知其作用
* @param dcore a core descriptor
* @return the newly created core //创建一个最新的Core
*/

public SolrCore create(CoreDescriptor dcore) {

if (isShutDown) {
throw new SolrException(ErrorCode.SERVICE_UNAVAILABLE, "Solr has shutdown.");
}

final String name = dcore.getName();

try {
// Make the instanceDir relative to the cores instanceDir if not absolute
File idir = new File(dcore.getInstanceDir());
String instanceDir = idir.getPath();
log.info("Creating SolrCore '{}' using instanceDir: {}",
dcore.getName(), instanceDir);

// Initialize the solr config
SolrCore created = null;

//如果有配置ZooKeeper则会创建一个zk实例,将会从ZK中创建Core,后续将深入探究Solr的ZkContainer类的作用
if (zkSys.getZkController() != null) {
created = zkSys.createFromZk(instanceDir, dcore, loader);
} else {
created = createFromLocal(instanceDir, dcore);//传统方式创建Core,我们重点来看一下这个代码
}

solrCores.addCreated(created); // For persisting newly-created cores.持久化新创建的Core,将SolrCore放入SolrCores,主意两个类名称相差不大,但作用不同
return created;

// :TODO: Java7...
// http://docs.oracle.com/javase/7/docs/technotes/guides/language/catch-multiple.html
} catch (Exception ex) {
throw recordAndThrow(name, "Unable to create core: " + name, ex);
}
}

 该方法会根据判别ZKContainer是否被创建来决定Core的创建方式。并将生成的Core加入到SolrCores类中的Map集合中进行持久化。大致流程图如下所示:

2.2.5 CoreContainer.createFromLocal()本地创建方法


 我们重点关注一下createFromLocal()这个方法,是如何创建Core实例的。createFromLocal()代码如下:

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
// Helper method to separate out creating a core from local configuration files. See create()
// 这个注释很好地说明了,该创建方法是通过读取本地配置文件进行创建Core实例,而不是交由ZK去管理
private SolrCore createFromLocal(String instanceDir, CoreDescriptor dcore) {
SolrResourceLoader solrLoader = null;//很熟悉的SolrResourceLoader资源加载器

SolrConfig config = null;
solrLoader = new SolrResourceLoader(instanceDir, loader.getClassLoader(), ConfigSolrXml.getCoreProperties(instanceDir, dcore));//通过Core描述获取该Core内的配置文件
try {
config = new SolrConfig(solrLoader, dcore.getConfigName(), null);
} catch (Exception e) {
log.error("Failed to load file {}", new File(instanceDir, dcore.getConfigName()).getAbsolutePath());
throw new SolrException(ErrorCode.SERVER_ERROR, "Could not load config for " + dcore.getConfigName(), e);
}

//以下读取给Solr配置的schema.xml索引文件
IndexSchema schema = null;
if (indexSchemaCache != null) {
final String resourceNameToBeUsed = IndexSchemaFactory.getResourceNameToBeUsed(dcore.getSchemaName(), config);
File schemaFile = new File(resourceNameToBeUsed);
if (!schemaFile.isAbsolute()) {
schemaFile = new File(solrLoader.getConfigDir(), schemaFile.getPath());
}
if (schemaFile.exists()) {
String key = schemaFile.getAbsolutePath()
+ ":"
+ new SimpleDateFormat("yyyyMMddHHmmss", Locale.ROOT).format(new Date(
schemaFile.lastModified()));
schema = indexSchemaCache.get(key);
if (schema == null) {
log.info("creating new schema object for core: " + dcore.getProperty(CoreDescriptor.CORE_NAME));
schema = IndexSchemaFactory.buildIndexSchema(dcore.getSchemaName(), config);
indexSchemaCache.put(key, schema);
} else {
log.info("re-using schema object for core: " + dcore.getProperty(CoreDescriptor.CORE_NAME));
}
}
}

if (schema == null) {
schema = IndexSchemaFactory.buildIndexSchema(dcore.getSchemaName(), config);
}
//由本地配置构造SolrCore
SolrCore core = new SolrCore(dcore.getName(), null, config, schema, dcore);

if (core.getUpdateHandler().getUpdateLog() != null) {
// always kick off recovery if we are in standalone mode.
// Core的一些更新和恢复的处理,未深入
core.getUpdateHandler().getUpdateLog().recoverFromLog();
}
return core;
}

通过此方法读取了本地配置文件,调用了SolrCore的构造方法根据配置文件真正地生成了SolrCore实例。

2.2.6 CoreContainer.registerCore()方法


 由2.2.3代码分析中可知,在需要将创建的SolrCore加载(注册)进入CoreContainer中,用到了registCore()方法:

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
protected SolrCore registerCore(boolean isTransientCore, String name, SolrCore core, boolean returnPrevNotClosed) {
if( core == null ) {
throw new RuntimeException( "Can not register a null core." );
}
if( name == null ||
name.indexOf( '/' ) >= 0 ||
name.indexOf( '\\' ) >= 0 ){
throw new RuntimeException( "Invalid core name: "+name );
}

SolrCore old = null;

if (isShutDown) {
core.close();
throw new IllegalStateException("This CoreContainer has been shutdown");
}
if (isTransientCore) {
old = solrCores.putTransientCore(cfg, name, core, loader);
} else {
old = solrCores.putCore(name, core);
}
/*
* set both the name of the descriptor and the name of the
* core, since the descriptors name is used for persisting.
*/


core.setName(name);
core.getCoreDescriptor().putProperty(CoreDescriptor.CORE_NAME, name);

//此步骤意义见代码块后注
synchronized (coreInitFailures) {
coreInitFailures.remove(name);
}

if( old == null || old == core) {
log.info( "registering core: "+name );
zkSys.registerInZk(core);//向ZK注册Core
return null;
}
else {//这部分作用未知
log.info( "replacing core: "+name );
if (!returnPrevNotClosed) {
old.close();
}
zkSys.registerInZk(core);
return old;
}
}

 注:上述代码中的同步代码块意义猜想; 这部分同步的代码coreInitFailures是一个Map集合,保存这create方法里生成core过程中出现的异常,create方法在出现异常后将异常信息放入改Map中:代码如下:

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
public SolrCore create(CoreDescriptor dcore) {

............

final String name = dcore.getName();

try {

............

} catch (Exception ex) {
throw recordAndThrow(name, "Unable to create core: " + name, ex);//该方法保存出现的异常信息
}
}

// Just to tidy up the code where it did this in-line.
//这个方法将创建Core过程中出现的异常记录到coreInitFailures中
private SolrException recordAndThrow(String name, String msg, Exception ex) {
synchronized (coreInitFailures) {
coreInitFailures.remove(name);
coreInitFailures.put(name, ex);
}
log.error(msg, ex);
return new SolrException(ErrorCode.SERVER_ERROR, msg, ex);
}

 在注册方法register中,因为已经完成Core创建,所以将出现的异常移出Map。猜想是在多线程中创建Core可能回出错,子线程不断执行该方法,直到完成Core的创建。可能涉及到ZK对Core的恢复等问题,需要后续深入了解其作用。

 至此整个Solr初始化过程完成。涉及两大部分的内容:

  • 1、配置信息的读取:获取Solr各资源文件路径、加载solr.xml配置文件
  • 2、根据配置信息生成对应的Core和ZkContainer,将Core加入SolrCores中统一管理

2.3 SolrResourceLoader类分析


2.3.1 三种获取资源路径的方式


在创建Solr的资源加载器实例时,会先调用在SolrResourceLoader中的静态方法locateSolrHome()方法,该方法作用主要是确定本地Java运行环境钟solrhome的路径。以下引用Solr中源码:

/**

  • Determines the solrhome from the environment.
  • Tries JNDI (java:comp/env/solr/home) then system property (solr.solr.home);
  • if both fail, defaults to solr/
  • @return the instance directory name
    */

由三种方式获取Solr的资源路径。分别是:JNDI、System property、Default。获取方式,重点是在JNDI与System property。

  • JNDI:JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一。J2EE 1.3 开始,资源的管理由应用服务器单独来管理和配置,这与 J2EE 1.2 不同,在 J2EE 1.2 中我们直接在应用程序中配置我们要用的资源。J2EE 1.3 中我们配置一个数据源在服务器上,我们在应用程序中只需要说明我们配置的资源的引用就行了。见主要代码:

    1
    2
    3
    Context c = new InitialContext();
    home = (String)c.lookup("java:comp/env/"+project+"/home");
    log.info("Using JNDI solr.home: "+home );
  • system property:见名知意,从系统资源中获取:

    1
    2
    String prop = project + ".solr.home";
    home = System.getProperty(prop);

2.3.2 SolrResourceLoader构造方法


构造方法首先再次对Solrhome路径做校验,随后第一步创建createClassLoader类记载器,该方法作用是,在指定的lib目录下找到所有文件得到一个新的类加载器。主要代码如下两块:

1
2
3
4
5
6
7
8
9
10
11
/**
* Convenience method for getting a new ClassLoader using all files found
* in the specified lib directory.
*/

static URLClassLoader createClassLoader(final File libDir, ClassLoader parent) {
if ( null == parent ) {
parent = Thread.currentThread().getContextClassLoader();
}
return replaceClassLoader(URLClassLoader.newInstance(new URL[0], parent),
libDir, null);
}



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
private static URLClassLoader replaceClassLoader(final URLClassLoader oldLoader,
final File base,
final FileFilter filter)
{

if (null != base && base.canRead() && base.isDirectory()) {
File[] files = base.listFiles(filter);

if (null == files || 0 == files.length) return oldLoader;

URL[] oldElements = oldLoader.getURLs();
URL[] elements = new URL[oldElements.length + files.length];
System.arraycopy(oldElements, 0, elements, 0, oldElements.length);

for (int j = 0; j < files.length; j++) {
try {
URL element = files[j].toURI().normalize().toURL();
log.info("Adding '" + element.toString() + "' to classloader");
elements[oldElements.length + j] = element;
} catch (MalformedURLException e) {
SolrException.log(log, "Can't add element to classloader: " + files[j], e);
}
}
ClassLoader oldParent = oldLoader.getParent();
closeHack(oldLoader); // best effort
return URLClassLoader.newInstance(elements, oldParent);
}
// are we still here?
return oldLoader;
}

在第一次初始化ClassLoader时,直接就通过URLClassLoader.newInstance(new URL[0], parent)生成了一个URLClassLoader实例,下面其余步骤在构造函数中的createClassLoader()方法是永远无法执行到的。

第二步将SolrHome下的lib文件夹下面的文件放入资源加载器。调用第一步中提到的replaceClassLoader重新设置URLClassLoader

最后一步,重新加载Luecene SPI,调用reloadLuceneSPI()方法,略去不表。

2.4 ConfigSolr类分析


2.5 SolrDisPatchFilter.doFilter()方法


 这里面对doFilter方法进行了初步解读,分析已经附在了代码注释上。

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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain, boolean retry) throws IOException, ServletException {
if( abortErrorMessage != null ) {//500错误处理
((HttpServletResponse)response).sendError( 500, abortErrorMessage );
return;
}

if (this.cores == null) {//solr core初始化失败或者已经关闭
((HttpServletResponse)response).sendError( 503, "Server is shutting down or failed to initialize" );
return;
}
CoreContainer cores = this.cores;
SolrCore core = null;
SolrQueryRequest solrReq = null;
Aliases aliases = null;

if( request instanceof HttpServletRequest) {//如果是http请求
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
SolrRequestHandler handler = null;
String corename = "";
String origCorename = null;
try {
// put the core container in request attribute
req.setAttribute("org.apache.solr.CoreContainer", cores);
String path = req.getServletPath();
if( req.getPathInfo() != null ) {
// this lets you handle /update/commit when /update is a servlet
path += req.getPathInfo();
}
if( pathPrefix != null && path.startsWith( pathPrefix ) ) {
path = path.substring( pathPrefix.length() );
}
// check for management path
String alternate = cores.getManagementPath();
if (alternate != null && path.startsWith(alternate)) {
path = path.substring(0, alternate.length());
}
// unused feature ?
int idx = path.indexOf( ':' );
if( idx > 0 ) {
// save the portion after the ':' for a 'handler' path parameter
path = path.substring( 0, idx );
}

// Check for the core admin page
if( path.equals( cores.getAdminPath() ) ) {//solr admin 管理页面请求
handler = cores.getMultiCoreHandler();
solrReq = SolrRequestParsers.DEFAULT.parse(null,path, req);
handleAdminRequest(req, response, handler, solrReq);
return;
}
boolean usingAliases = false;
List<String> collectionsList = null;
// Check for the core admin collections url
if( path.equals( "/admin/collections" ) ) {//管理collections
handler = cores.getCollectionsHandler();
solrReq = SolrRequestParsers.DEFAULT.parse(null,path, req);
handleAdminRequest(req, response, handler, solrReq);
return;
}
// Check for the core admin info url
if( path.startsWith( "/admin/info" ) ) {//查看admin info
handler = cores.getInfoHandler();
solrReq = SolrRequestParsers.DEFAULT.parse(null,path, req);
handleAdminRequest(req, response, handler, solrReq);
return;
}
else {
//otherwise, we should find a core from the path
idx = path.indexOf( "/", 1 );
if( idx > 1 ) {
// try to get the corename as a request parameter first
corename = path.substring( 1, idx );

// look at aliases
if (cores.isZooKeeperAware()) {//solr cloud状态
origCorename = corename;
ZkStateReader reader = cores.getZkController().getZkStateReader();
aliases = reader.getAliases();
if (aliases != null && aliases.collectionAliasSize() > 0) {
usingAliases = true;
String alias = aliases.getCollectionAlias(corename);
if (alias != null) {
collectionsList = StrUtils.splitSmart(alias, ",", true);
corename = collectionsList.get(0);
}
}
}

core = cores.getCore(corename);

if (core != null) {
path = path.substring( idx );
}
}
if (core == null) {
if (!cores.isZooKeeperAware() ) {
core = cores.getCore("");
}
}
}

if (core == null && cores.isZooKeeperAware()) {
// we couldn't find the core - lets make sure a collection was not specified instead
core = getCoreByCollection(cores, corename, path);

if (core != null) {
// we found a core, update the path
path = path.substring( idx );
}

// if we couldn't find it locally, look on other nodes
if (core == null && idx > 0) {
String coreUrl = getRemotCoreUrl(cores, corename, origCorename);
// don't proxy for internal update requests
SolrParams queryParams = SolrRequestParsers.parseQueryString(req.getQueryString());
if (coreUrl != null
&& queryParams
.get(DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM) == null) {
path = path.substring(idx);
remoteQuery(coreUrl + path, req, solrReq, resp);
return;
} else {
if (!retry) {
// we couldn't find a core to work with, try reloading aliases
// TODO: it would be nice if admin ui elements skipped this...
ZkStateReader reader = cores.getZkController()
.getZkStateReader();
reader.updateAliases();
doFilter(request, response, chain, true);
return;
}
}
}

// try the default core
if (core == null) {
core = cores.getCore("");
}
}

// With a valid core...
if( core != null ) {//验证core
final SolrConfig config = core.getSolrConfig();
// get or create/cache the parser for the core
SolrRequestParsers parser = config.getRequestParsers();

// Handle /schema/* and /config/* paths via Restlet
if( path.equals("/schema") || path.startsWith("/schema/")
|| path.equals("/config") || path.startsWith("/config/")) {//solr rest api 入口
solrReq = parser.parse(core, path, req);
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, new SolrQueryResponse()));
if( path.equals(req.getServletPath()) ) {
// avoid endless loop - pass through to Restlet via webapp
chain.doFilter(request, response);
} else {
// forward rewritten URI (without path prefix and core/collection name) to Restlet
req.getRequestDispatcher(path).forward(request, response);
}
return;
}

// Determine the handler from the url path if not set
// (we might already have selected the cores handler)
if( handler == null && path.length() > 1 ) { // don't match "" or "/" as valid path
handler = core.getRequestHandler( path );
// no handler yet but allowed to handle select; let's check
if( handler == null && parser.isHandleSelect() ) {
if( "/select".equals( path ) || "/select/".equals( path ) ) {//solr 各种查询过滤入口
solrReq = parser.parse( core, path, req );
String qt = solrReq.getParams().get( CommonParams.QT );
handler = core.getRequestHandler( qt );
if( handler == null ) {
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "unknown handler: "+qt);
}
if( qt != null && qt.startsWith("/") && (handler instanceof ContentStreamHandlerBase)) {
//For security reasons it's a bad idea to allow a leading '/', ex: /select?qt=/update see SOLR-3161
//There was no restriction from Solr 1.4 thru 3.5 and it's not supported for update handlers.
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Invalid Request Handler ('qt'). Do not use /select to access: "+qt);
}
}
}
}

// With a valid handler and a valid core...
if( handler != null ) {
// if not a /select, create the request
if( solrReq == null ) {
solrReq = parser.parse( core, path, req );
}

if (usingAliases) {
processAliases(solrReq, aliases, collectionsList);
}

final Method reqMethod = Method.getMethod(req.getMethod());
HttpCacheHeaderUtil.setCacheControlHeader(config, resp, reqMethod);
// unless we have been explicitly told not to, do cache validation
// if we fail cache validation, execute the query
if (config.getHttpCachingConfig().isNever304() ||
!HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, reqMethod, resp)) {//solr http 缓存 在header控制失效时间的方式
SolrQueryResponse solrRsp = new SolrQueryResponse();
/* even for HEAD requests, we need to execute the handler to
* ensure we don't get an error (and to make sure the correct
* QueryResponseWriter is selected and we get the correct
* Content-Type)
*/

SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, solrRsp));
this.execute( req, handler, solrReq, solrRsp );
HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod);
// add info to http headers
//TODO: See SOLR-232 and SOLR-267.
/*try {
NamedList solrRspHeader = solrRsp.getResponseHeader();
for (int i=0; i<solrRspHeader.size(); i++) {
((javax.servlet.http.HttpServletResponse) response).addHeader(("Solr-" + solrRspHeader.getName(i)), String.valueOf(solrRspHeader.getVal(i)));
}
} catch (ClassCastException cce) {
log.log(Level.WARNING, "exception adding response header log information", cce);
}*/

QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
writeResponse(solrRsp, response, responseWriter, solrReq, reqMethod);
}
return; // we are done with a valid handler
}
}
log.debug("no handler or core retrieved for " + path + ", follow through...");
}
catch (Throwable ex) {
sendError( core, solrReq, request, (HttpServletResponse)response, ex );
if (ex instanceof Error) {
throw (Error) ex;
}
return;
} finally {
try {
if (solrReq != null) {
log.debug("Closing out SolrRequest: {}", solrReq);
solrReq.close();
}
} finally {
try {
if (core != null) {
core.close();
}
} finally {
SolrRequestInfo.clearRequestInfo();
}
}
}
}

// Otherwise let the webapp handle the request
chain.doFilter(request, response);
}
}

文章目錄
  1. 1. 一、再次系统认识Solr
    1. 1.1. 1.1 Solr组件架构
    2. 1.2. 1.2 Solr目录结构
      1. 1.2.1. 1.2.1 Solr程序包的目录结构
      2. 1.2.2. 1.2.2 SolrCore文件主目录结构
    3. 1.3. 1.3 重新理解Solr各个概念
      1. 1.3.1. 1.3.1 Solr基础——Core
      2. 1.3.2. 1.3.2 Core与Replica(副本|拷贝)
      3. 1.3.3. 1.3.3 Collection与Shard
    4. 1.4. 1.4 Solr动作过程
      1. 1.4.1. 1.4.1 索引创建过程
      2. 1.4.2. 1.4.2 数据搜索过程
      3. 1.4.3. 1.4.3 Shard 分片过程
  2. 2. 二、Solr入口SolrDispatchFilter解析
    1. 2.1. 2.1 关于web.xml
    2. 2.2. 2.2 配置文件读取与核心初始化
      1. 2.2.1. 2.2.1 SolrDiaspatchFilter初始化
      2. 2.2.2. 2.2.2 CoreContainer
      3. 2.2.3. 2.2.3 CoreContainer.load()方法
      4. 2.2.4. 2.2.4 CoreContainer.create()方法
      5. 2.2.5. 2.2.5 CoreContainer.createFromLocal()本地创建方法
      6. 2.2.6. 2.2.6 CoreContainer.registerCore()方法
    3. 2.3. 2.3 SolrResourceLoader类分析
      1. 2.3.1. 2.3.1 三种获取资源路径的方式
      2. 2.3.2. 2.3.2 SolrResourceLoader构造方法
    4. 2.4. 2.4 ConfigSolr类分析
    5. 2.5. 2.5 SolrDisPatchFilter.doFilter()方法