每日学习笔记(25)

 一, 在上一篇中第4点里,我遇到了一个solr集群中各个节点在进行master-slave身份切换时困扰的问题,详情见上一篇。今天顺利地解决了这个问题,下面记录下完整的解决方案。

     0) 值得特别注意的一点,在创建Solr核的时候,不要指定dataDir,而只提供必需的instanceDir即可,即创建命令如下:   
 
  
  
  
  
  1. http://localhost:8983/solr/admin/cores?action=CREATE&name=coreX&instanceDir=path_to_instance_directory 

 

这是因为后面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之间的索引复制过程

 

 

  
  
  
  
  1. /** 
  2.      * 实际的重新加载solr核动作 
  3.      * @param coreName 
  4.      */ 
  5.     private void doReloadSolrCore(String coreName) { 
  6.         boolean isLoadOk = false
  7.         HttpClient client = new HttpClient(); 
  8.  
  9.         String createCMD = "http://" + Constants.LOCAL_ADDRESS + ":" + jbossPort + "/admin/cores?action=RELOAD&core=" + coreName; 
  10.         HttpMethod method = new GetMethod(createCMD); 
  11.         method.getParams().setContentCharset("GBK"); 
  12.         method.getParams().setHttpElementCharset("GBK"); 
  13.         method.getParams().setCredentialCharset("GBK"); 
  14.         logger.error("重新加载核命令: " + createCMD); 
  15.         // execute the method. 
  16.         try { 
  17.             client.executeMethod(method); 
  18.         } catch (Exception e) { 
  19.             logger.error("重新加载solr核失败, 核名称: " + coreName, e); 
  20.             throw new RuntimeException("重新加载solr核失败, 核名称: " + coreName, e); 
  21.         } 
  22.  
  23.     } 
  24.      
  25.     /** 
  26.      * 启动一个线程重新加载solr核 
  27.      * @param groupName 
  28.      * @param coreName 
  29.      */ 
  30.     private void reloadSolrCore(final String coreName) { 
  31.         //http://localhost:8983/solr/admin/cores?action=RELOAD&core=core0 
  32.         new Thread() { 
  33.             @Override 
  34.             public void run() { 
  35.                 try { 
  36.                     doReloadSolrCore(coreName); 
  37.                 } catch (Exception e) { 
  38.                     logger.error("重新加载solr核失败, 核名称: " + coreName, e); 
  39.                     throw new RuntimeException("重新加载solr核失败, 核名称: " + coreName, e); 
  40.                 } 
  41.             } 
  42.         }.start(); 
  43.     } 
  44.      
  45.     /** 
  46.      * 本机从slave成为master,修改对应的配置文件 
  47.      * @param solrConfigPath solr配置文件路径 
  48.      */ 
  49.     private void changeConfigAsMaster(String solrConfigPath) { 
  50.         SAXReader reader = new SAXReader();   
  51.         File file = new File(solrConfigPath); 
  52.         try { 
  53.             Document doc = reader.read(file); 
  54.             //首先移除以前slave的配置片段 
  55.             Node node = doc.selectSingleNode("/config/requestHandler[@name=\"/replication\"]"); 
  56.             node.detach(); 
  57.             //然后加入master的配置片段 
  58.             Element rootElement = doc.getRootElement();//拿到<config/>元素 
  59.             Element oneLevelElement = rootElement.addElement("requestHandler").addAttribute("name""/replication").addAttribute("class""solr.ReplicationHandler"); 
  60.             Element twoLevelElement = oneLevelElement.addElement("lst").addAttribute("name""master"); 
  61.             twoLevelElement.addElement("str").addAttribute("name""replicateAfter").addText("startup"); 
  62.             twoLevelElement.addElement("str").addAttribute("name""replicateAfter").addText("commit"); 
  63.             twoLevelElement.addElement("str").addAttribute("name""commitReserveDuration").addText("00:00:10"); 
  64.             XMLWriter writer = new XMLWriter(new FileWriter(solrConfigPath)); 
  65.             writer.write(doc); 
  66.             writer.close(); 
  67.         } catch (DocumentException e) { 
  68.             logger.error("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath); 
  69.             throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e); 
  70.         }catch (IOException e) { 
  71.             logger.error("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath); 
  72.             throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e); 
  73.         } 
  74.     } 
  75.     /** 
  76.      * 修改slave配置片段,指向新的mater节点,如果我原先是master,那么先去掉master的配置,因为我身份将转为slave,若原先是slave,只需要简单修改masterUrl即可 
  77.      * @param solrConfigPath solr配置文件路径 
  78.      */ 
  79.     private void changeConfigForSlave(String solrConfigPath, String coreName, String newMasterHost) { 
  80.         SAXReader reader = new SAXReader();   
  81.         File file = new File(solrConfigPath); 
  82.         String masterUrl = ""
  83.         if (StringUtils.isNotBlank(newMasterHost)) { 
  84.             masterUrl = "http://" + newMasterHost + ":" + jbossPort + "/" + coreName + "/replication"
  85.             try { 
  86.                 Document doc = reader.read(file); 
  87.                  
  88.                 Node node = doc.selectSingleNode("/config/requestHandler[@name=\"/replication\"]"); 
  89.                 if (null != node) {//如果我原先是master,那么先去掉master的配置, 
  90.                     node.detach(); 
  91.                 } 
  92.                 Element rootElement = doc.getRootElement();//拿到<config/>元素 
  93.                 Element oneLevelElement = rootElement.addElement("requestHandler").addAttribute("name""/replication").addAttribute("class""solr.ReplicationHandler"); 
  94.                 Element twoLevelElement = oneLevelElement.addElement("lst").addAttribute("name""slave"); 
  95.                 twoLevelElement.addElement("str").addAttribute("name""masterUrl").addText(masterUrl); 
  96.                 twoLevelElement.addElement("str").addAttribute("name""pollInterval").addText("00:02:00"); 
  97.                 twoLevelElement.addElement("str").addAttribute("name""compression").addText("internal"); 
  98.                 twoLevelElement.addElement("str").addAttribute("name""httpConnTimeout").addText("5000"); 
  99.                 twoLevelElement.addElement("str").addAttribute("name""httpReadTimeout").addText("10000"); 
  100.                 XMLWriter writer = new XMLWriter(new FileWriter(solrConfigPath)); 
  101.                 writer.write(doc); 
  102.                 writer.close(); 
  103.             } catch (DocumentException e) { 
  104.                 logger.error("写入slave节点配置片段失败, 配置文件路径: " + solrConfigPath); 
  105.                 throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e); 
  106.             }catch (IOException e) { 
  107.                 logger.error("写入slave节点配置片段失败, 配置文件路径: " + solrConfigPath); 
  108.                 throw new RuntimeException("写入master节点配置片段失败, 配置文件路径: " + solrConfigPath, e); 
  109.             } 
  110.         } 
  111.     } 
  112.      
  113.     /** 
  114.      * 本机新身份是master,是从slave转来的,调整指定核的配置文件,重新reload 
  115.      * @param coreName 
  116.      */ 
  117.     private void adjustSolrCoreAsMaster(String coreName) { 
  118.         if (this.mapCoreConfigInfo.containsKey(coreName)) { 
  119.             String configFilePath = this.mapCoreConfigInfo.get(coreName);//目标配置文件路径 
  120.             if (StringUtils.isNotBlank(configFilePath)) { 
  121.                 //修改配置文件replication判断为master的片段 
  122.                 this.changeConfigAsMaster(configFilePath); 
  123.                 this.reloadSolrCore(coreName); 
  124.             } 
  125.         } 
  126.     } 
  127.      
  128.     /** 
  129.      * 本机新的身份是slave,可能是master转来的,或者原先还是slave,无论哪种,都需要调整指定核的配置文件,指向新的master节点, 重新reload 
  130.      * @param coreName solr核名称 
  131.      */ 
  132.     private void adjustSolrCoreAsSlave(String coreName, String newMasterHost) { 
  133.         if (this.mapCoreConfigInfo.containsKey(coreName)) { 
  134.             String configFilePath = this.mapCoreConfigInfo.get(coreName);//目标配置文件路径 
  135.             if (StringUtils.isNotBlank(configFilePath)) { 
  136.                 //修改配置文件replication, 指向新的master地址 
  137.                 this.changeConfigForSlave(configFilePath, coreName, newMasterHost); 
  138.                 this.reloadSolrCore(coreName); 
  139.             } 
  140.         } 
  141.     } 
  142.      
  143.     /** 
  144.      * 为了m/s同步,master节点强制性commit一下 
  145.      */ 
  146.     private void commitMasterIndex(String masterHost, String port, String coreName) { 
  147.         String targerUrl = "http://" + masterHost + ":" + port + "/" + coreName; 
  148.         logger.error("master节点强制性commit一下,url: " + targerUrl); 
  149.         CommonsHttpSolrServer httpSolrServer = null
  150.         try { 
  151.             httpSolrServer = new CommonsHttpSolrServer(targerUrl); 
  152.         } catch (MalformedURLException e1) { 
  153.             throw new RuntimeException("连接solr服务器失败, targetURL: " + targerUrl, e1); 
  154.         } 
  155.         httpSolrServer.setSoTimeout(Constants.soTimeOut); 
  156.         httpSolrServer.setConnectionTimeout(Constants.connectionTimeOut); 
  157.         httpSolrServer.setDefaultMaxConnectionsPerHost(Constants.maxConnectionsPerHost); 
  158.         httpSolrServer.setMaxTotalConnections(Constants.maxTotalConnections); 
  159.         httpSolrServer.setFollowRedirects(false); 
  160.         httpSolrServer.setAllowCompression(true); 
  161.         httpSolrServer.setMaxRetries(Constants.maxRetries); 
  162.  
  163.         try { 
  164.             httpSolrServer.commit(); 
  165.         } catch (Exception e) { 
  166.         } 
  167.     } 
  168.      
  169.     /** 
  170.      * 指定核下指定组的节点发生变化 
  171.      * @param coreName solr核名称 
  172.      * @param groupName 机器分组名称 
  173.      * @param newNodeNameList 最新的组内各个节点 
  174.      */ 
  175.     private void masterSlaveNodeChanged(String coreName, String groupName, List<String> newNodeHostList) { 
  176.         logger.error("===========masterSlaveNodeChanged============, solr核名称: " + coreName + ", 组节点名称: " + groupName + ", node节点IP列表: " + newNodeHostList); 
  177.         logger.error("此时全局数据信息: " + mapGroupNodeStatus); 
  178.  
  179.         String newMasterHost = getNewestMasterHost(newNodeHostList);//当前最新的master节点IP 
  180.         String oldMasterHost = this.getOldMasterHost(coreName, groupName);//老的master节点地址 
  181.         if (StringUtils.isBlank(oldMasterHost)) { 
  182.             //若本地缓存中记录的master节点没有,则说明是第一次取master节点, 
  183.             if (StringUtils.isNotBlank(newMasterHost)) { 
  184.                 logger.error("maser节点地址: " + newMasterHost); 
  185.                 this.setMasterNode(coreName, groupName, newMasterHost); 
  186.             } 
  187.             if (newMasterHost.equalsIgnoreCase(Constants.LOCAL_ADDRESS) && MineNotifyProducer.isAuctionEntityGroup(coreName)) { 
  188.                 this.commitMasterIndex(Constants.LOCAL_ADDRESS, jbossPort, coreName);//若这一次是本机重新夺回master节点,强制性commit一下,避免出现replication过程没有启动 
  189.                 logger.error("本机是master节点,可以启动相应的消费者线程"); 
  190.             } 
  191.         } else if (!oldMasterHost.equalsIgnoreCase(newMasterHost)) { 
  192.             //若本地缓存有记录,却与最新的master节点不一致,则说明原来的master挂了,取最新的master节点 
  193.             if (StringUtils.isNotBlank(newMasterHost)) { 
  194.                 this.setMasterNode(coreName, groupName, newMasterHost); 
  195.                 if (oldMasterHost.equalsIgnoreCase(Constants.LOCAL_ADDRESS)) { 
  196.                     //本机是老的master,集群却又有了新的master,说明是本机提前窃取了master身份,现在需要归还出master身份,自己重新成为slave 
  197.                     //结束掉相应的处理线程 
  198.                     logger.error("本机是老的master,集群却又有了新的master: " + newMasterHost + ", 说明是发布启动阶段, 本机提前窃取了master身份,结束掉相应的处理线程"); 
  199.  
  200.                     this.adjustSolrCoreAsSlave(coreName, newMasterHost);//身份成为slave, 写入slave的配置,指向当前最新的master地址,并reload对应的solr核 
  201.                 } else { 
  202.                     if (newMasterHost.equalsIgnoreCase(Constants.LOCAL_ADDRESS)) { 
  203.                         //若本机是新的mater节点,则需要转换身份,从slave转为maser,修改replciation配置,重载solr核, 
  204.                         this.adjustSolrCoreAsMaster(coreName); 
  205.                     } else { 
  206.                         //本机原本就是slave节点,需要修改replication配置,指向新的master,重载solr核 
  207.                         this.adjustSolrCoreAsSlave(coreName, newMasterHost); 
  208.                     } 
  209.                 } 
  210.             } 
  211.         } 
  212.          
  213.         //首先从节点列表中去掉master节点 
  214.         newNodeHostList.remove(newMasterHost); 
  215.         //设置新的slave列表 
  216.         if (newNodeHostList.size() > 0) { 
  217.             this.setSlaveNodeList(coreName, groupName, newNodeHostList); 
  218.         } else {//slave节点全都挂了,只剩下master节点了, 
  219.             this.clearSlaveNodeList(coreName, groupName); 
  220.         } 

二, HSF服务的测试步骤:

1) 首先在pom文件中加入依赖项 

 

  
  
  
  
  1. <dependency> 

2) 启动HSF

 

  
  
  
  
  1. static { 
  2.         HSFEasyStarter.startFromPath("/hsf/release"); 
  3.     } 

3)  在Junit测试脚本中加入下述语句,每次都等待HSF服务准备完毕,才在test**方法中去使用服务。

 

  
  
  
  
  1. @Before 
  2.    public void preDate() throws SolrServerException, IOException{         
  3.        ServiceUtil.waitServiceReady(queryService); 
  4.    } 

 

你可能感兴趣的:(java,职场,随笔,java语言,休闲)