每日学习笔记(25)
一, 在上一篇中第4点里,我遇到了一个solr集群中各个节点在进行master-slave身份切换时困扰的问题,详情见上一篇。今天顺利地解决了这个问题,下面记录下完整的解决方案。
0) 值得特别注意的一点,在创建Solr核的时候,不要指定dataDir,而只提供必需的instanceDir即可,即创建命令如下:
这是因为后面reload相应的solr核时,系统讲index目录变成index.20120307213400这样带后缀的索引目录,若创建solr核时指定死了dataDir,那么会发现新建立的mater/slava之间无法正常进行索引复制,根本原因在于slave节点会一直认为mater的索引目录在index下面,而实际是没有的,所以不去指定,让系统来处理,反而可以顺利进行replication.
1) 首先,为每个solr节点依次分配优先顺序,在zookeeper集群中同一个ZNode下注册为一个EPHEMERAL节点,节点名称就是每个Solr节点的机器名。
2) 这样在Zookeeper回调的Watcher里就可以拿到当前最新已经注册的solr节点列表,为了简单,我们采用“序号最小的节点为整个集群的master节点”这个master选举策略。
3) 这里有一种特殊情况需要考虑,若序号不是最小的Solr节点抢先注册,那么它会在真正的master节点注册之前窃取到master身份,并且导致在此之前集群里其他solr节点也误认为它是真正的master节点。等到真正最小序号的master节点在zookeeper中注册后,就需要前面窃取master身份的节点将master身份交还给真正序号最小的节点,自己重新变为slave节点,相应的replication配置片段也要从master的配置变为并且其他slave节点需要重新指向新的master节点。
4) 这之后,还有一种情况需要考虑,若集群的master节点突然挂了,那么此时序号紧挨在它之后的slave节点就自动被选举为master节点,它的replication配置片段要从slave片段变为master配置,并且重新reload相应的solr核,而其他slave节点就需要将replication的maserUrl指向这个新选举出来的master节点,并且重新reload相应的solr核。
5) 再之后,若原先挂掉的master节点经过抢修后,又重新回到集群中来,那么由于它的序号会是最小的,就会从上次被选举出的master节点手中夺回master的位置,这就又回到了步骤2)的过程了。这里特别要注意的一点,重新拿回master节点的位置,需要强制性提交一个commit请求,以启动master/slave之间的索引复制过程
整个解决方案具体的实现代码如下:
* 实际的重新加载solr核动作
* @param coreName
*/
private void doReloadSolrCore(String coreName) {
boolean isLoadOk = false;
HttpClient client = new HttpClient();
String createCMD = "http://" + Constants.LOCAL_ADDRESS + ":" + jbossPort + "/admin/cores?action=RELOAD&core=" + coreName;
HttpMethod method = new GetMethod(createCMD);
method.getParams().setContentCharset("GBK");
method.getParams().setHttpElementCharset("GBK");
method.getParams().setCredentialCharset("GBK");
logger.error("重新加载核命令: " + createCMD);
// execute the method.
try {
client.executeMethod(method);
} catch (Exception e) {
logger.error("重新加载solr核失败, 核名称: " + coreName, e);
throw new RuntimeException("重新加载solr核失败, 核名称: " + coreName, e);
}
}
/**
* 启动一个线程重新加载solr核
* @param groupName
* @param coreName
*/
private void reloadSolrCore(final String coreName) {
//http://localhost:8983/solr/admin/cores?action=RELOAD&core=core0
new Thread() {
@Override
public void run() {
try {
doReloadSolrCore(coreName);
} catch (Exception e) {
logger.error("重新加载solr核失败, 核名称: " + coreName, e);
throw new RuntimeException("重新加载solr核失败, 核名称: " + coreName, e);
}
}
}.start();
}
/**
* 本机从slave成为master,修改对应的配置文件
* @param solrConfigPath solr配置文件路径
*/
private void changeConfigAsMaster(String solrConfigPath) {
SAXReader reader = new SAXReader();
File file = new File(solrConfigPath);
try {
Document doc = reader.read(file);
//首先移除以前slave的配置片段
Node node = doc.selectSingleNode("/config/requestHandler[@name=\"/replication\"]");
node.detach();
//然后加入master的配置片段
Element rootElement = doc.getRootElement();//拿到<config/>元素
Element oneLevelElement = rootElement.addElement("requestHandler").addAttribute("name", "/replication").addAttribute("class", "solr.ReplicationHandler");
Element twoLevelElement = oneLevelElement.addElement("lst").addAttribute("name", "master");
twoLevelElement.addElement("str").addAttribute("name", "replicateAfter").addText("startup");
twoLevelElement.addElement("str").addAttribute("name", "replicateAfter").addText("commit");
twoLevelElement.addElement("str").addAttribute("name", "commitReserveDuration").addText("00:00:10");
XMLWriter writer = new XMLWriter(new FileWriter(solrConfigPath));
writer.write(doc);
writer.close();
} catch (DocumentException e) {
logger.error("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath);
throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e);
}catch (IOException e) {
logger.error("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath);
throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e);
}
}
/**
* 修改slave配置片段,指向新的mater节点,如果我原先是master,那么先去掉master的配置,因为我身份将转为slave,若原先是slave,只需要简单修改masterUrl即可
* @param solrConfigPath solr配置文件路径
*/
private void changeConfigForSlave(String solrConfigPath, String coreName, String newMasterHost) {
SAXReader reader = new SAXReader();
File file = new File(solrConfigPath);
String masterUrl = "";
if (StringUtils.isNotBlank(newMasterHost)) {
masterUrl = "http://" + newMasterHost + ":" + jbossPort + "/" + coreName + "/replication";
try {
Document doc = reader.read(file);
Node node = doc.selectSingleNode("/config/requestHandler[@name=\"/replication\"]");
if (null != node) {//如果我原先是master,那么先去掉master的配置,
node.detach();
}
Element rootElement = doc.getRootElement();//拿到<config/>元素
Element oneLevelElement = rootElement.addElement("requestHandler").addAttribute("name", "/replication").addAttribute("class", "solr.ReplicationHandler");
Element twoLevelElement = oneLevelElement.addElement("lst").addAttribute("name", "slave");
twoLevelElement.addElement("str").addAttribute("name", "masterUrl").addText(masterUrl);
twoLevelElement.addElement("str").addAttribute("name", "pollInterval").addText("00:02:00");
twoLevelElement.addElement("str").addAttribute("name", "compression").addText("internal");
twoLevelElement.addElement("str").addAttribute("name", "httpConnTimeout").addText("5000");
twoLevelElement.addElement("str").addAttribute("name", "httpReadTimeout").addText("10000");
XMLWriter writer = new XMLWriter(new FileWriter(solrConfigPath));
writer.write(doc);
writer.close();
} catch (DocumentException e) {
logger.error("写入slave节点配置片段失败, 配置文件路径: " + solrConfigPath);
throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e);
}catch (IOException e) {
logger.error("写入slave节点配置片段失败, 配置文件路径: " + solrConfigPath);
throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e);
}
}
}
/**
* 本机新身份是master,是从slave转来的,调整指定核的配置文件,重新reload
* @param coreName
*/
private void adjustSolrCoreAsMaster(String coreName) {
if (this.mapCoreConfigInfo.containsKey(coreName)) {
String configFilePath = this.mapCoreConfigInfo.get(coreName);//目标配置文件路径
if (StringUtils.isNotBlank(configFilePath)) {
//修改配置文件replication判断为master的片段
this.changeConfigAsMaster(configFilePath);
this.reloadSolrCore(coreName);
}
}
}
/**
* 本机新的身份是slave,可能是master转来的,或者原先还是slave,无论哪种,都需要调整指定核的配置文件,指向新的master节点, 重新reload
* @param coreName solr核名称
*/
private void adjustSolrCoreAsSlave(String coreName, String newMasterHost) {
if (this.mapCoreConfigInfo.containsKey(coreName)) {
String configFilePath = this.mapCoreConfigInfo.get(coreName);//目标配置文件路径
if (StringUtils.isNotBlank(configFilePath)) {
//修改配置文件replication, 指向新的master地址
this.changeConfigForSlave(configFilePath, coreName, newMasterHost);
this.reloadSolrCore(coreName);
}
}
}
/**
* 为了m/s同步,master节点强制性commit一下
*/
private void commitMasterIndex(String masterHost, String port, String coreName) {
String targerUrl = "http://" + masterHost + ":" + port + "/" + coreName;
logger.error("master节点强制性commit一下,url: " + targerUrl);
CommonsHttpSolrServer httpSolrServer = null;
try {
httpSolrServer = new CommonsHttpSolrServer(targerUrl);
} catch (MalformedURLException e1) {
throw new RuntimeException("连接solr服务器失败, targetURL: " + targerUrl, e1);
}
httpSolrServer.setSoTimeout(Constants.soTimeOut);
httpSolrServer.setConnectionTimeout(Constants.connectionTimeOut);
httpSolrServer.setDefaultMaxConnectionsPerHost(Constants.maxConnectionsPerHost);
httpSolrServer.setMaxTotalConnections(Constants.maxTotalConnections);
httpSolrServer.setFollowRedirects(false);
httpSolrServer.setAllowCompression(true);
httpSolrServer.setMaxRetries(Constants.maxRetries);
try {
httpSolrServer.commit();
} catch (Exception e) {
}
}
/**
* 指定核下指定组的节点发生变化
* @param coreName solr核名称
* @param groupName 机器分组名称
* @param newNodeNameList 最新的组内各个节点
*/
private void masterSlaveNodeChanged(String coreName, String groupName, List<String> newNodeHostList) {
logger.error("===========masterSlaveNodeChanged============, solr核名称: " + coreName + ", 组节点名称: " + groupName + ", node节点IP列表: " + newNodeHostList);
logger.error("此时全局数据信息: " + mapGroupNodeStatus);
String newMasterHost = getNewestMasterHost(newNodeHostList);//当前最新的master节点IP
String oldMasterHost = this.getOldMasterHost(coreName, groupName);//老的master节点地址
if (StringUtils.isBlank(oldMasterHost)) {
//若本地缓存中记录的master节点没有,则说明是第一次取master节点,
if (StringUtils.isNotBlank(newMasterHost)) {
logger.error("maser节点地址: " + newMasterHost);
this.setMasterNode(coreName, groupName, newMasterHost);
}
if (newMasterHost.equalsIgnoreCase(Constants.LOCAL_ADDRESS) && MineNotifyProducer.isAuctionEntityGroup(coreName)) {
this.commitMasterIndex(Constants.LOCAL_ADDRESS, jbossPort, coreName);//若这一次是本机重新夺回master节点,强制性commit一下,避免出现replication过程没有启动
logger.error("本机是master节点,可以启动相应的消费者线程");
}
} else if (!oldMasterHost.equalsIgnoreCase(newMasterHost)) {
//若本地缓存有记录,却与最新的master节点不一致,则说明原来的master挂了,取最新的master节点
if (StringUtils.isNotBlank(newMasterHost)) {
this.setMasterNode(coreName, groupName, newMasterHost);
if (oldMasterHost.equalsIgnoreCase(Constants.LOCAL_ADDRESS)) {
//本机是老的master,集群却又有了新的master,说明是本机提前窃取了master身份,现在需要归还出master身份,自己重新成为slave
//结束掉相应的处理线程
logger.error("本机是老的master,集群却又有了新的master: " + newMasterHost + ", 说明是发布启动阶段, 本机提前窃取了master身份,结束掉相应的处理线程");
this.adjustSolrCoreAsSlave(coreName, newMasterHost);//身份成为slave, 写入slave的配置,指向当前最新的master地址,并reload对应的solr核
} else {
if (newMasterHost.equalsIgnoreCase(Constants.LOCAL_ADDRESS)) {
//若本机是新的mater节点,则需要转换身份,从slave转为maser,修改replciation配置,重载solr核,
this.adjustSolrCoreAsMaster(coreName);
} else {
//本机原本就是slave节点,需要修改replication配置,指向新的master,重载solr核
this.adjustSolrCoreAsSlave(coreName, newMasterHost);
}
}
}
}
//首先从节点列表中去掉master节点
newNodeHostList.remove(newMasterHost);
//设置新的slave列表
if (newNodeHostList.size() > 0) {
this.setSlaveNodeList(coreName, groupName, newNodeHostList);
} else {//slave节点全都挂了,只剩下master节点了,
this.clearSlaveNodeList(coreName, groupName);
}
}
二, HSF服务的测试步骤:
1) 首先在pom文件中加入依赖项
<groupId>com.taobao.hsf</groupId>
<artifactId>hsfunit</artifactId>
<version>1.0.2-SNAPSHOT</version>
<scope>test</scope>
</dependency>
HSFEasyStarter.startFromPath("/hsf/release");
}
public void preDate() throws SolrServerException, IOException{
ServiceUtil.waitServiceReady(queryService);
}
作者:洞庭散人
出处:http://phinecos.cnblogs.com/
posted on 2012-03-07 22:12 Phinecos(洞庭散人) 阅读(1804) 评论(0) 编辑 收藏 举报