Solr的web.xml配置文件中,配置了一个filter:
<!-- Any path (name) registered in solrconfig.xml will be sent to that filter -->
< filter>
<filter-name >SolrRequestFilter </filter-name >
<filter-class> org.apache.solr.servlet.SolrDispatchFilter</filter-class >
</ filter>
< filter-mapping>
<filter-name >SolrRequestFilter </filter-name >
<url-pattern >/* </url-pattern >
</ filter-mapping>
Solr启动时,通过这个SolrDispatchFilter的init方法来加载配置文件和实例化各个组件。
Solr使用本地文件启动的大概流程为:(暂不考虑SolrCloud的情况)
1. 获取solrhome:先后通过JNDI,system property,default directory三种方式尝试获取
2. 实例化启动过程中使用的类加载器SolrResourceLoader
3. 加载solrhome下的solr.xml文件,封装为ConfigSolr
4. 实例化一个CoreContainer,通过CoreContainer来加载cores
5. (CoreContainer加载cores)先遍历solrhome,寻找含有core.properties的文件夹,视为找到一个core
6. 多线程加载cores
7. 加载每个core时先加载solrconfig.xml封装为SolrConfig
8. 再加载schema.xml封装为IndexSchema
9. 最后实例化SolrCore
下面具体通过代码详细分析启动过程:
先来看下init方法:
@Override
public void init(FilterConfig config) throws ServletException
{
log.info("SolrDispatchFilter.init()");
try {
// web.xml configuration
this.pathPrefix = config.getInitParameter( "path-prefix" );
this.cores = createCoreContainer();
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 );
if (t instanceof Error) {
throw (Error) t;
}
}
log.info("SolrDispatchFilter.init() done");
}
其中主要的一行代码是调用createCoreContainer方法,从这个方法已经可以看出大致的流程:
/**
* Override this to change CoreContainer initialization
* @return a CoreContainer to hold this server's cores
*/
protected CoreContainer createCoreContainer() {
SolrResourceLoader loader = new SolrResourceLoader(SolrResourceLoader.locateSolrHome());
// 加载solrhome/solr.xml作为ConfigSolr
ConfigSolr config = loadConfigSolr(loader);
CoreContainer cores = new CoreContainer(loader, config);
cores.load();
return cores;
}
先看下第一句中的SolrResourceLoader.locateSolrHome(),这个方法看出获取solrhome的流程,先尝试JNDI,在尝试system property,最后使用默认地址:
/**
* Finds the solrhome based on looking up the value in one of three places:
* <ol>
* <li>JNDI: via java:comp/env/solr/home </li>
* <li>The system property solr.solr.home </li>
* <li>Look in the current working directory for a solr/ directory</li>
* </ol>
*
* The return value is normalized. Normalization essentially means it ends in a trailing slash.
* @return A normalized solrhome
* @see #normalizeDir(String)
*/
public static String locateSolrHome() {
String home = null;
// Try JNDI
try {
Context c = new InitialContext();
home = (String)c.lookup("java:comp/env/" +project+"/home");
log.info("Using JNDI solr.home: "+home );
} catch (NoInitialContextException e) {
log.info("JNDI not configured for "+ project+ " (NoInitialContextEx)");
} catch (NamingException e) {
log.info("No /"+project+"/home in JNDI");
} catch( RuntimeException ex ) {
log.warn("Odd RuntimeException while testing for JNDI: " + ex.getMessage());
}
// Now try system property
if( home == null ) {
String prop = project + ".solr.home";
home = System.getProperty(prop);
if( home != null ) {
log.info("using system property "+prop+ ": " + home );
}
}
// if all else fails, try
if( home == null ) {
home = project + '/';
log.info(project + " home defaulted to '" + home + "' (could not find system property or JNDI)" );
}
return normalizeDir( home );
}
之后实例化一个SolrResourceLoader,这个SolrResourceLoader的parents是ContextClassLoader,并且加载了./lib/下的文件:
/**
* <p>
* This loader will delegate to the context classloader when possible,
* otherwise it will attempt to resolve resources using any jar files
* found in the "lib/" directory in the specified instance directory.
* </p>
*
* @param instanceDir - base directory for this resource loader, if null locateSolrHome() will be used.
* @see #locateSolrHome
*/
public SolrResourceLoader( String instanceDir, ClassLoader parent, Properties coreProperties )
{
if( instanceDir == null ) {
this.instanceDir = SolrResourceLoader.locateSolrHome();
log.info("new SolrResourceLoader for deduced Solr Home: '{}'",
this. instanceDir);
} else{
this.instanceDir = normalizeDir(instanceDir );
log.info("new SolrResourceLoader for directory: '{}'",
this. instanceDir);
}
this. classLoader = createClassLoader (null, parent);
addToClassLoader("./lib/", null, true);
reloadLuceneSPI();
this.coreProperties = coreProperties;
}
参数parent此时传的null,所以parent取的ContextClassLoader
/**
* 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);
}
/**
* Adds every file/dir found in the baseDir which passes the specified Filter
* to the ClassLoader used by this ResourceLoader. This method <b>MUST</b>
* only be called prior to using this ResourceLoader to get any resources, otherwise
* it's behavior will be non -deterministic. You also have to {link @reloadLuceneSPI }
* before using this ResourceLoader.
*
* <p>This method will quietly ignore missing or non-directory <code> baseDir</code>
* folder.
*
* @param baseDir base directory whose children (either jars or directories of
* classes) will be in the classpath, will be resolved relative
* the instance dir.
* @param filter The filter files must satisfy, if null all files will be accepted.
* @param quiet Be quiet if baseDir does not point to a directory or if no file is
* left after applying the filter.
*/
void addToClassLoader( final String baseDir, final FileFilter filter, boolean quiet ) {
// 拼绝对路径
File base = FileUtils.resolvePath(new File(getInstanceDir()), baseDir );
if (base != null && base .exists() && base.isDirectory()) {
File[] files = base .listFiles(filter );
if (files == null || files. length == 0) {
if (!quiet) {
log.warn( "No files added to classloader from lib: "
+ baseDir + " (resolved as: " + base.getAbsolutePath() + ").");
}
} else {
this.classLoader = replaceClassLoader(classLoader, base , filter );
}
} else {
if (!quiet) {
log.warn("Can't find (or read) directory to add to classloader: "
+ baseDir + " (resolved as: " + base.getAbsolutePath() + ").");
}
}
}
<span style="font-family:Tahoma;font-size:18px;color:#e30000;">
</span>
接着回到createCoreContainer里的loadConfigSolr,开始加载solr.xml:
private ConfigSolr loadConfigSolr(SolrResourceLoader loader) {
String solrxmlLocation = System.getProperty( "solr.solrxml.location" , "solrhome");
if (solrxmlLocation == null || "solrhome".equalsIgnoreCase(solrxmlLocation))
return ConfigSolr. fromSolrHome(loader, loader.getInstanceDir());
if ("zookeeper".equalsIgnoreCase(solrxmlLocation)) {
String zkHost = System. getProperty("zkHost");
log.info("Trying to read solr.xml from " + zkHost);
if (StringUtils.isEmpty(zkHost))
throw new SolrException( ErrorCode. SERVER_ERROR,
"Could not load solr.xml from zookeeper: zkHost system property not set");
SolrZkClient zkClient = new SolrZkClient(zkHost, 30000);
try {
if (!zkClient. exists( "/solr.xml", true ))
throw new SolrException( ErrorCode. SERVER_ERROR, "Could not load solr.xml from zookeeper: node not found");
byte[] data = zkClient.getData( "/solr.xml", null , null, true );
return ConfigSolr. fromInputStream(loader, new ByteArrayInputStream(data));
} catch (Exception e) {
throw new SolrException( ErrorCode. SERVER_ERROR, "Could not load solr.xml from zookeeper" , e);
} finally {
zkClient.close();
}
}
throw new SolrException( ErrorCode. SERVER_ERROR,
"Bad solr.solrxml.location set: " + solrxmlLocation + " - should be 'solrhome' or 'zookeeper'");
}
在ConfigSolr.fromSolrHome()中加载:
public static ConfigSolr fromSolrHome(SolrResourceLoader loader , String solrHome ) {
// 默认“solr.xml”
return fromFile( loader, new File(solrHome, SOLR_XML_FILE ));
}
public static ConfigSolr fromFile(SolrResourceLoader loader, File configFile) {
log.info("Loading container configuration from {}", configFile.getAbsolutePath());
InputStream inputStream = null;
try {
// solrhome/solr.xml不存在的话,看是不是zk中有,如果没有就用默认的DEF_SOLR_XML
if (!configFile.exists()) {
// 这个if里面的方法是根据zkHost和zkRun判断是否有zk,看注释5.0后要有变化
if (ZkContainer.isZkMode()) {
throw new SolrException(SolrException.ErrorCode .SERVER_ERROR,
"solr.xml does not exist in " + configFile.getAbsolutePath() + " cannot start Solr" );
}
log.info("{} does not exist, using default configuration", configFile.getAbsolutePath());
inputStream = new ByteArrayInputStream(ConfigSolrXmlOld.DEF_SOLR_XML .getBytes(Charsets.UTF_8));
} else {
// 这里的configFile是solrhome/solr.xml
inputStream = new FileInputStream(configFile);
}
return fromInputStream(loader , inputStream);
}
catch (Exception e) {
throw new SolrException(SolrException.ErrorCode .SERVER_ERROR,
"Could not load SOLR configuration", e);
}
finally {
IOUtils.closeQuietly(inputStream);
}
}
public static ConfigSolr fromInputStream(SolrResourceLoader loader , InputStream is) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteStreams.copy(is , baos);
String originalXml = IOUtils.toString( new ByteArrayInputStream(baos.toByteArray()), "UTF-8" );
ByteArrayInputStream dup = new ByteArrayInputStream(baos.toByteArray());
Config config = new Config( loader, null, new InputSource(dup), null, false );
return fromConfig (config, originalXml);
}
catch (Exception e) {
throw new SolrException(SolrException.ErrorCode .SERVER_ERROR, e);
}
}
public static ConfigSolr fromConfig(Config config , String originalXml) {
// 这里兼容了solr.xml的新旧两种格式
boolean oldStyle = ( config.getNode("solr/cores" , false ) != null);
return oldStyle ? new ConfigSolrXmlOld( config, originalXml)
: new ConfigSolrXml( config);
}
看看ConfigSolrXml的构造函数:
public ConfigSolrXml(Config config) {
super(config);
try {
// 校验旧格式的solr.xml,如果发现旧的格式抛SolrException
checkForIllegalConfig();
// 填充所有属性
fillPropMap();
config.substituteProperties();
coresLocator = new CorePropertiesLocator(getCoreRootDirectory());
}
catch (IOException e) {
throw new SolrException(SolrException.ErrorCode .SERVER_ERROR, e);
}
}
fillPropMap()中将所有solr.xml的属性填充到一个HashMap<CfgProp, String>中:propMap,到此就加载完了solr.xml,封装为了ConfigSolr。
(待续)