嘗試WebMagic+Dubbo搭建爬蟲Cluster(更新完畢)

目錄

  • 現在還沒解決的問題
  • 已經解決的問題
  • 大致思路的演化
  • 2017年11月11日開發記錄
  • 2017年11月16日開發記錄
  • 2017年11月17日開發記錄
  • 2017年11月18日開發記錄
  • 2017年11月19日開發記錄
  • 2017年11月20日開發記錄
  • 2017年11月21日開發記錄
  • 2017年11月22日開發記錄
  • 2017年11月24日開發記錄
  • 面試該項目時得到的一些改進的建議

現在還沒解決的問題

以下是還沒解決的問題,其中有技術問題,還有優化問題。有思路的朋友可以給些建議。

  • 關於“先在子機累積song,累積到1000條,然後拼接sql批量插入。並且在spider.run()結束後,還準備了一個flush方法,把緩衝區中剩餘的song但是不滿1000條的數據批量插入數據庫。”的問題二:由以前的經驗得知,這個程序運行的時間越長,很有可能出現程序進入停滯狀態(可能是死鎖),也不繼續往下爬取,也不停止,導致放在spider.run()後的代碼執行不了。

  • slave子機在運行到main()方法的結束”}”後(經過調試得知的)還不退出程序,debug的時候到了”}”後,光標就消失了,再按F8就不管用了,原因還不得知。

已經解決的問題

  1. HTTPClient包在webMagic和dubbo中都有,發生衝突

    • 解決方案:用maven的去掉dubbo的HTTPClient和httpcore就好了
  2. task接口沒有序列化,導致dubbo不能傳輸

    • 解決方案:一開始的解決方案是光傳輸Request,不傳輸task。後來是自定義一個SerializableTask類實現task接口,使得dubbo傳輸的是它,在提供者和消費者各自進行裝配和拆卸(詳情請見2017年11月16日和17日的開發記錄)。
  3. 關於“先在子機累積song,累積到1000條,然後拼接sql批量插入。並且在spider.run()結束後,還準備了一個flush方法,把緩衝區中剩餘的song但是不滿1000條的數據批量插入數據庫。”的問題一:如果這一千條批量插入的時候,有一條出現錯誤(比如某個字段超了),由於這是一個事務,那麼MySQL將會回滾,一條都插不進去,導致了程序進入停滯狀態(一條新的song都插入不進去)。

    • 解決方案:MySQL數據庫方面,設置大文本字段爲text;代碼方面(爲了提高性能,用SpringJDBC),先寫SQL = insert into song(name,lyric,singer…除了id所有的字段),然後append()1000條記錄,每條記錄中如果有空值,則用’_’或者0代替。這樣就實現了insertSelective的效果。但是這樣還是解決不了根本問題,還是可能插入失敗回滾並報錯,這樣只能減少錯誤出現。
  4. 插入的數據有重複的。

    • 原因:經過分析後是併發問題,因爲我的邏輯是批量插入有一個bufferList,當bufferList沒滿500時,往bufferList裏插入,滿了500,就往數據庫批量導入。這裏強調幾點,bufferList是多個線程共同維護的,換句話說,多個線程爬取到的song數據都往bufferList裏放。而錯誤的原因是,當bufferList滿了500時,當其中一個線程正在批量導入的時候,另一個線程也正好判斷了bufferList.size() > 500,於是也進行了插入,這導致了同一個bufferList插入了多遍。
    • 解決方案:加synchronized代碼塊。
  5. 我的代碼:

        if (bufferList.size() < 500) {
                bufferList.add(song);
            } else {
                synchronized (this) {
                    try {
    //                    爲了性能,用SpringJDBC
                        Date date = new Date();
                        batchInsertSelective(bufferList);
                        Date date1 = new Date();
                        System.out.println("[INFO] 線程\t" + Thread.currentThread().getId() + "\t本次插入所花時間:\t" + (date1.getTime() - date.getTime()));
                        bufferList.clear();
                        objectCount += bufferList.size();
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(e.getMessage());
                    }
                }
    
            }
    • 打印的時候是這樣的:
      [INFO] 線程   57  本次插入所花時間:   1106
      [INFO] 線程   89  本次插入所花時間:   0
      [INFO] 線程   36  本次插入所花時間:   0
      [INFO] 線程   45  本次插入所花時間:   0
      [INFO] 線程   76  本次插入所花時間:   0
      [INFO] 線程   42  本次插入所花時間:   0
      [INFO] 線程   58  本次插入所花時間:   0
      [INFO] 線程   86  本次插入所花時間:   0
      [INFO] 線程   67  本次插入所花時間:   0
      [INFO] 線程   81  本次插入所花時間:   0
      [INFO] 線程   64  本次插入所花時間:   0

    不過沒影響數據庫,但這是爲啥?

    • 原因:這是因爲當一個線程獲得鎖進入synchronized代碼塊後,隨後有多個線程到了else,等待釋放鎖,噹噹前線程釋放了synchronized鎖後,等待的線程挨個拿到鎖後又把這個代碼塊執行了一遍,但因爲bufferList是空的所以執行的很快。這是一個缺陷。

大致思路的演化

  • 從官方的原始架構談起…這是webMagic官方的架構圖。整個爬蟲與外界的Internet交互數據。
  • 整個爬蟲分爲四部分:
    • Scheduler:管理URL,主要就是推入URL,去重,讀出URL
    • Downloader:從scheduler讀取URL,然後基於HTTPClient下載頁面,封裝成Page後傳給PageProcessor處理
    • PageProcessor:處理Downloader下載封裝好的Page對象,抽取Items交給PipeLine處理結果數據;抽取有效URL封裝成Request添加到Scheduler的URL管理隊列中。
    • Pipeline:結果集處理,存數據庫,存文件……
  • 因爲webMagic想做爬蟲集羣的話主要是在scheduler模塊上做文章,所以經過上面的分析可以得出與scheduler有關係的兩個模塊是Downloader(讀取URL),PageProcessor(寫入URL)。
  • 經過在網絡上查詢資料後知道,爬蟲集羣的大致思路是:有三類機器,主機器,從機器,數據庫機器。
  • 主機:維護URL隊列,從機請求主機,主機poll一條Request(URL)返回給從機。
  • 從機:有若干臺機器,每臺機器擔負下載,處理頁面,利用pipeLine給數據庫機器填入數據,相當於單個爬蟲。
  • 數據庫機器:存數據。
  • 從上面的分析,思路相信已經很明瞭了。所以,我最初的思路是主機相當於scheduler模塊,從機上有Downloader,PageProcessor,PipeLine,數據庫機器上有MySQL。
    架構圖 v1.0如下:
  • 那麼爬蟲從機和主機的scheduler之間的Request(URL)怎麼傳遞呢?請教別人後瞭解到,阿里的dubbo可以滿足這方面的需求。
  • 研究了一下dubbo的入門用法後,發現直接在 爬蟲從機.downloader和 主機.scheduler,爬蟲從機.pageProcessor 和 主機.scheduler之間傳遞參數有些麻煩。所以在v1.0的基礎上改了一下就有了下面的架構圖 v2.0:

  • 修改的地方是在爬蟲從機自定義scheduler,然後scheduler中用到的一切方法(其實也就是poll和push)全部用dubbo遠程調用主機中的scheduler中的方法(相當於打電話中的“轉接”功能)。
  • 舉例說明(只是爲了說明思路,並不是實際代碼):
    • 主機——服務提供者(Provider)
    • 二者的公共接口:
    public interface HostSchedulerService {

        void pushWhenNoDuplicate(Request request, Task task);//push進一個Request

        Request poll(Task task);//讀取一個Request,需要同步

    }
  • 服務的具體實現類:
    public class HostSchedulerServiceTest extends DuplicateRemovedScheduler
        implements HostSchedulerService {

    private BlockingQueue<Request> queue = new LinkedBlockingQueue();

    @Override
    public void pushWhenNoDuplicate(Request request, Task task) {
        System.out.println("[INFO] pushWhenNoDuplicate " + request.getUrl());
        this.queue.add(request);
    }

    @Override
    public synchronized Request poll(Task task) {
        Request request = (Request)this.queue.poll();
        System.out.println("[INFO] poll " + request.getUrl());
        return request;
    }
    }
    //這是改的webMagic默認的scheduler——QueueScheduler
  • 在spring-dubbo.xml中暴露服務:

    <!-- 具體的實現bean -->
    <bean id="hostSchedulerService" class="cn.spiderCluster.service.impl.HostSchedulerServiceTest" />
    <!-- 聲明需要暴露的服務接口 -->
    <dubbo:service interface="cn.spiderCluster.service.HostSchedulerService" ref="hostSchedulerService" />
  • 然後啓動Provider服務…
  • 從機——服務消費者(Consumer)
  • 從參數來看,主從機消費的對象是Request,Task,因爲涉及到dubbo傳遞的類必須是序列化的,所以現在也是因爲這個問題而經常報錯。 ps:加色部分可以先跳過,看完從機介紹後再回來看,要不然你會覺得我在自言自語的很傻{所以我考慮着是不是光傳String類型的URL就可以了呢(不過Request還有Extras等屬性),畢竟本質上還是URL的傳遞,在這個基礎上又有一個思路就是,主機上維護的服務是一個URL隊列的管理程序,對遠程機器通過dubbo提供poll和push功能,消費的對象是URL+Extras+其他重要參數(比如URL的優先級)組成的可序列化對象。並且我還期望爬蟲從機可以實現“熱插拔”。腦洞超多超大。} 從機中除了Downloader需要webMagic原生的外,其餘三個模塊都要自己寫。PageProcessor,PipeLine沒啥說的,主要說一下Scheduler。
  • Spring-dubbo.xml中定義遠程服務:

    <!-- 生成遠程服務代理,可以像使用本地bean一樣使用demoService -->
    <dubbo:reference id="hostSchedulerService"
                     interface="cn.spiderCluster.service.HostSchedulerService" />
  • 公共接口見上面。

    @Component
    public class SlaveScheduler extends DuplicateRemovedScheduler implements  DuplicateRemover {
    
        @Autowired
    
        HostSchedulerService hostSchedulerService;//注入服務實現類
    
        public SlaveScheduler() {
        }
    
        @Override
        public Request poll(Task task) {
            return hostSchedulerService.poll(task);
        }
    
        @Override
        public void pushWhenNoDuplicate(Request request, Task task) {
            hostSchedulerService.pushWhenNoDuplicate(request,task);
            return;
        }
    }
    • 相當於把Request,Task通過dubbo“轉接”給了主機的scheduler,將遠程服務的處理結果返回給Downloader和PageProcessor。

2017年11月11日

  • 出現的一個問題:maven HTTPClient jar包衝突
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/config/Lookup
    at org.apache.http.impl.client.HttpClients.createDefault(HttpClients.java:58)
    at cn.spiderCluster.slave.utils.PageUtil.<clinit>(PageUtil.java:24)
    at cn.spiderCluster.slave.pageProcessor.KwPageProcessor.<init>(KwPageProcessor.java:34)
    at cn.spiderCluster.slave.Main.main(Main.java:29)
Caused by: java.lang.ClassNotFoundException: org.apache.http.config.Lookup
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 4 more
  • 解決方案:去除dubbo中HTTPClient包和httpcore包即可。另外,說一個跟本問題無關的建議——把dubbo中的Spring也去掉(版本太老)。
    <dependency>
      <groupId>us.codecraft</groupId>
      <artifactId>webmagic-core</artifactId>
      <version>${webMagic.version}</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dubbo</artifactId>
      <version>${dubbox.version}</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpclient</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpcore</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

這樣的話,HTTPClient就用webMagic依賴過來的包就可以了。

2017年11月16日

  • 由於Request和task都需要發送,然而Request實現了Serializable接口,task沒有繼承(task是個接口),所以發生錯誤的原因一般就是task沒有實現接口引起的,閱讀QueueScheduler(Spider默認的Scheduler)源碼後,發現內部使用了BlockingQueue實現的,沒有使用到傳過來的參數——task,所以只需要在host和slave之間傳遞Request即可,成功。
  • QueueScheduler.java
@ThreadSafe
public class QueueScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler {
    private BlockingQueue<Request> queue = new LinkedBlockingQueue();

    public QueueScheduler() {
    }

    public void pushWhenNoDuplicate(Request request, Task task) {
        this.queue.add(request);
    }

    public synchronized Request poll(Task task) {
        return (Request)this.queue.poll();
    }

    public int getLeftRequestsCount(Task task) {
        return this.queue.size();
    }

    public int getTotalRequestsCount(Task task) {
        return this.getDuplicateRemover().getTotalRequestsCount(task);
    }
}
  • Host的服務實現類:
/**
 * Created by Ww on 2017/11/11.
 */
public class HostSchedulerService2Memory implements HostSchedulerService {
    private BlockingQueue<Request> queue = new LinkedBlockingQueue();

    @Override
    public void pushWhenNoDuplicate(Request request) {
        this.queue.add(request);
    }
    @Override
    public Request poll() {
        Request request = (Request)this.queue.poll();
        return (Request)this.queue.poll();
    }
}
  • Slave的自定義Scheduler():
@Override
public Request poll(Task task) {
    return hostSchedulerService.poll();
}

@Override
public void pushWhenNoDuplicate(Request request, Task task) {
    hostSchedulerService.pushWhenNoDuplicate(request);
    return;
}
  • 關於LikedBlockingQueue:
    • LinkedBlockingQueue是一個單向鏈表實現的阻塞隊列,先進先出的順序。支持多線程併發操作。
    • 相比於數組實現的ArrayBlockingQueue的有界,LinkedBlockingQueue可認爲是無界隊列。多用於任務隊列。
    • 定義:LinkedBlockingQueue繼承AbstractQueue,實現了BlockingQueue,Serializable接口。內部使用單向鏈表存儲數據。
    • 鏈接:原文鏈接

2017年11月17日

  • 昨天把搭了一臺centos 6.5的機器準備做爬蟲子機用,安裝的時候各種問題(增霸卡,BIOS設置等等等等等),今天又把網絡給調通了。哎,實驗室電腦資源有限且質量一般。
  • 今天主要是整理了一下slave和host,現在總結一下出現的問題和解決方案。
  • 先說Host:

    • “解決task沒有繼承Serializable接口”,以下所有都是在這個思路的基礎上開始的。
    • 因爲我的自定義Scheduler是在QueueScheduler的基礎上改的(還有一個是改的FileCacheScheduler),所以研究了QueueScheduler後發現,task用到的就光是getUUID(),所以我就自定義了一個SerializableTask繼承Task接口。
    • SerializableTask.java

      public class SerializableTask implements Task, Serializable {
          private String uuid;
      
          public void setUuid(String uuid) {
              this.uuid = uuid;
          }
          @Override
          public String getUUID() {
              return null;
          }
      
          @Override
          public Site getSite() {
              return null;
          }
      }
    • 所以,dubbo傳遞Task接口時傳遞的是SerializableTask類。

    • 因爲需求中需要一開始先加入許多(大約有5000)條起始URL,所以根據需求定製了一個KwScheduler如下:

      public class KwScheduler extends HostSchedulerService_Queue implements KwSchedulerService {
      
          private static AtomicBoolean isAddAllPage = new AtomicBoolean(false);
      
      
          @Override
          public void pushAllPage(List<String> requestList,Task task) {
              for (String r:
                      requestList) {
                  pushWhenNoDuplicate(new Request(r),task);
              }
              isAddAllPage.set(true);
          }
      
          @Override
          public boolean isAddAllPage() {
              return isAddAllPage.get();
          }
      }
    • 從子機上傳來List urlList,在這邊處理並填入queue,並設置爲isAddAllPage爲true,使得其他子機不用重複這個昂貴的操作。另一個增加的方法isAddAllPage()返回是否完成了批量導入初始化URL。

  • 再說slave:

    • Slave中自定義的scheduler中,當Spider傳參Task task的時候,通過
    private SerializableTask setTaskUUID(Task task) {
        SerializableTask task1 = new SerializableTask();
        task1.setUuid(task.getUUID());
        return task1;
    }

    new一個新的SerializableTask,只把UUID填進去,因爲主機的KwScheduler沒有用到site。

    • 關於“先在子機累積song,累積到1000條,然後拼接sql(用mybatis的foreach)批量插入。並且在spider.run()結束後,還準備了一個flush方法,把緩衝區中剩餘的song但是不滿1000條的數據批量插入數據庫。”思路的問題:
    • 這是爲了提升數據庫性能的一個優化邏輯,這樣確實可以提升性能。但是,有幾個問題還沒解決了。
      • 如果這一千條批量插入的時候,有一條出現錯誤(比如某個字段超了),由於這是一個事務,那麼MySQL將會回滾,一條都插不進去,導致了程序進入停滯狀態(一條新的song都插入不進去)。
      • 由以前的經驗得知,這個程序運行的時間越長,很有可能出現程序進入停滯狀態(可能是死鎖),也不繼續往下爬取,也不停止,導致放在spider.run()後的代碼執行不了。
  • Main中的修改就是每臺爬蟲子機在main()中,一開始先判斷service.isAddAllPage(),如果沒有那就裝好urlList後用service.pushAllPage(urlList,task)。

2017年11月18日

今天出現的問題:

  • 多臺爬蟲子機爬取的時候可能報異常TimeoutException: Waiting server-side response timeout。

    • slave1異常節選:

      TimeoutException: Waiting server-side response timeout. start time: 2017-11-18 10:13:29.968, end time: 2017-11-18 10:13:30.969, client elapsed: 0 ms, server elapsed: 1001 ms, timeout: 1000 ms
    • slave2異常節選:

    Waiting server-side response timeout. start time: 2017-11-18 10:13:29.979, end time: 2017-11-18 10:13:30.980, client elapsed: 0 ms, server elapsed: 1001 ms, timeout: 1000 ms
    • 簡單分析後,我覺得是在2017-11-18 10:13:30.969——2017-11-18 10:13:30.980之間這段時間,dubbo接收到兩個poll請求,先接受slave1的,因爲poll是BlockingQueue.poll,是線程安全的,換句話說,是會阻塞,host處理slave1超過了1000ms,在這期間阻塞,導致也不能處理slave2的請求,導致了兩者都超時。
    • 解決思路:
      • 保證主機資源夠用。
      • 建立slave重試機制。
      • 想辦法slave報異常後可以自動跳過異常(現在是報了這個異常,程序不會繼續運行,但是也不會退出)。
      • 優化dubbo配置,提升重試次數,提升超時時間。
      • 報異常的時候通知程序維護者,讓程序維護者重啓程序。
      • 當發生這種情況時用jvisualvm和mat看一下jvm的狀態。
  • 發現網絡上有mybatis批量插入和jdbc批量插入的對比,結論是jdbc+Spring的事務管理最快,用JdbcTemplate。所以準備在pipeLine里加上JdbcTemplate的批量插入(當然要處理多線程問題了)。

2017年11月19日

  • 實現了SpringJDBC的批量插入,相關併發問題也已經解決。
  • 出現的問題:

    • 插入的數據有重複的。

      • 原因:經過分析後是併發問題,因爲我的邏輯是批量插入有一個bufferList,當bufferList沒滿500時,往bufferList裏插入,滿了500,就往數據庫批量導入。這裏強調幾點,bufferList是多個線程共同維護的,換句話說,多個線程爬取到的song數據都往bufferList裏放。而錯誤的原因是,當bufferList滿了500時,當其中一個線程正在批量導入的時候,另一個線程也正好判斷了bufferList.size() > 500,於是也進行了插入,這導致了同一個bufferList插入了多遍。
      • 解決方案:加synchronized代碼塊。
    • 我的代碼:

          if (bufferList.size() < 500) {
                  bufferList.add(song);
              } else {
                  synchronized (this) {
                      try {
      //                    爲了性能,用SpringJDBC
                          Date date = new Date();
                          batchInsertSelective(bufferList);
                          Date date1 = new Date();
                          System.out.println("[INFO] 線程\t" + Thread.currentThread().getId() + "\t本次插入所花時間:\t" + (date1.getTime() - date.getTime()));
                          bufferList.clear();
                          objectCount += bufferList.size();
                      } catch (Exception e) {
                          e.printStackTrace();
                          logger.error(e.getMessage());
                      }
                  }
      
              }
    • 打印的時候是這樣的:

      [INFO] 線程   57  本次插入所花時間:   1106
      [INFO] 線程   89  本次插入所花時間:   0
      [INFO] 線程   36  本次插入所花時間:   0
      [INFO] 線程   45  本次插入所花時間:   0
      [INFO] 線程   76  本次插入所花時間:   0
      [INFO] 線程   42  本次插入所花時間:   0
      [INFO] 線程   58  本次插入所花時間:   0
      [INFO] 線程   86  本次插入所花時間:   0
      [INFO] 線程   67  本次插入所花時間:   0
      [INFO] 線程   81  本次插入所花時間:   0
      [INFO] 線程   64  本次插入所花時間:   0

    不過沒影響數據庫,但這是爲啥?

  • 原因:這是因爲當一個線程獲得鎖進入synchronized代碼塊後,隨後有多個線程到了else,等待釋放鎖,噹噹前線程釋放了synchronized鎖後,等待的線程挨個拿到鎖後又把這個代碼塊執行了一遍,但因爲bufferList是空的所以執行的很快。這是一個缺陷。

2017年11月20日

  • 實現了SpiderMonitor(WebMagic自帶的基於JMX的監視功能)。
  • 加了日誌。關於日誌很好的一篇文章:點擊我傳送
  • 優化了synchronized代碼塊位置。
  • 增加了Constant類和spider.properties——讓爬蟲可以配置的信息更多(現在有initURL指定初始URL,thread指定線程數,log4j指定日誌文件路徑……)。
  • 還應該解決一下objectCount光存最後插入的數據的size()的問題。

2017年11月21日

  • 關於打包,打包失敗:

    • 先是打的包沒有依賴
    • 裝上依賴後有沒有配置文件
    • 通過360壓縮將配置文件放到classpath下後,又提示“通配符的匹配很全面, 但無法找到元素 ‘dubbo:application’ 的聲明。”的錯誤。
    • 額。明天在研究一下。
  • 關於“運行完main方法還不停止”,請教別人後,得知程序運行完main方法還不停止,可能是還有活躍的線程。

  • 關於“爬蟲開多少線程合適”
    • 爬蟲在處理過程中,會涉及到cpu和IO時間。其中IO等待時,cpu被動放棄執行,其他線程就可以利用這段時間片進行操作。
    • 爬蟲是種IO密集的程序,特別適合多線程。
    • 一篇如何計算tomcat線程池大小?的文章。

2017年11月22日

  • 關於“程序運行完main後還不停止”,經過研究及上網查詢資料後得知,應該是dubbo的某些線程,這裏涉及到“dubbo的優雅退出”,“Runtime.getRuntime().addShutdownHook(new Thread())”。以下是探索過程記錄:

    • 關於“shutdown hook” 的文章。ShutdownHook - 優雅地停止服務。從其中可以得知shutdown hook當我調用System.exit(0)會被調用,完成平滑退出,具體好處參見文章“二、java進程平滑退出的意義”
    • 關於“dubbo中優雅停機”中根據阿里一位工程師的Dubbo應用與異常記錄提到,Dubbo是通過JDK的ShutdownHook來完成優雅停機的,所以如果用戶使用”kill -9 PID”等強制關閉指令,是不會執行優雅停機的,只有通過”kill PID”或者是“System.exit(0)”或者是“其他方法”時,纔會執行。
    • 關於“Runtime.getRuntime().addShutdownHook(new Thread())在什麼時候纔會被調用”java.lang.Runtime.addShutdownHook(Thread hook) 方法註冊一個新的虛擬機關閉掛鉤。 Java虛擬機的關閉響應於兩種事件:
      • 該程序正常退出,當最後一個非守護線程退出或退出(等同於System.exit)方法被調用時,或關閉鉤子是一個簡單的初始化但尚未啓動的線程。
      • 當虛擬機開始其關閉序列將啓動所有已註冊的關閉鉤子在一些未指定的順序,讓他們同時運行。當所有的鉤子都做完了會然後運行所有未調用的終結,如果最後確定的按出口已啓用。最後,虛擬機將暫停。需要注意的是守護線程將繼續關閉序列期間運行,將作爲非守護線程,如果關機是通過調用exit方法啓動。
  • 在研究上一個問題的時候,看到一個詞“配置分離”,這是那位大神的博客

    什麼叫做配置分離呢?就是使用maven打包時,藉助assemble插件,打一個tar.gz的壓縮包。裏面有三個目錄。bin目錄,用來存放啓動與停止的腳本,lib目錄,用來存放相關依賴的jar包,注意,這裏每個jar包都是單獨的,而不是一個大的jar包。conf目錄,用來存放配置文件,包括dubbo.property,applicatiom.xml等文件。

  • 又研究了大半天,寫了“關於Java main()程序使用maven打Jar包實現配置分離””。

2017年11月24日開發記錄

  • 在命令行中用 java -jar xxx.jar 運行時,出現dubbo不能運行遠程方法(用到的第一個方法就不行)的問題。
    • Fail to decode request due to: RpcInvocation xxxxxxxx…有個dubbo的版本號是2.8.4,但我在pom.xml中早就改成了2.5.3了呀。
  • 解決方案,把pom.xml中dubbo的版本號改成2.8.4就好了。

面試該項目時得到的一些改進的建議

在準備面試和麪試的這段時間(2018.03.05-2018.04.25)內,關於這個項目接收到了來自面試官(七牛)和實驗室同學的一些改進的建議,特做此記錄,採用Q-A的形式。

  • Q:可不可以把中心scheduler和子爬蟲之間的交互改爲異步的,從而實現假如中心scheduler宕機,子爬蟲不會受到影響而宕機的效果。
  • A:這是面試官提的問題,一開始我想到的方案僅僅是針對他後面提出的需求,增加中心scheduler子機,實現讀寫分離,宕機後進行備份的切換。但並沒有實現異步,然後面試官的建議是,在中心scheduler和子爬蟲間加上一個阻塞隊列,實現生產者消費者模式,這就實現了異步,而且可以提高性能。
  • Q:如何設計URL去重操作。
  • A:我回答的是利用HashMap的key不重複的性質,將URL offer進去,然後面試官說這樣就不能知道某個URL是否被消費過。然後我改進,將URL和一個標記位包裝一個pojo作爲key offer進去,然後面試官說這樣的性能太差,因爲每次都需要遍歷HashMap,遍歷的過程中可能會遍歷到標記位爲“已消費”的URL,而這樣的遍歷恰好是沒有意義的。面試官給出的答案,將“未消費”的和“已消費”的分開來放,當從PageProcessor來了一個新的URL的時候,先去“已消費”的集合中遍歷看有沒有,如果沒有的話,將該URL offer進“未消費”的隊列中去,如果有的話,則丟棄,還可以記錄次數,記錄爬取時間,進而還可以實現URL的持久化,實現增量爬取。
  • Q:關於Constant.getConfig()實現“不用重啓就能修改系統配置”
  • A:解釋一下,在這個小系統中我用了一個Constant類去維護系統配置文件(spider.properties),Constant有一個HashMap的類變量做緩存。以前Constant類去加載本地的.properties文件讀取配置的,現在改成從數據庫的一張key-value表中獲取了,管理員去修改數據庫,然後配置一個refresh操作,清空緩存,重新從數據庫中讀取,實現了“不用重啓就能修改系統配置”。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章