熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java開源技術 >> 正文

使用分布式緩存來群集Spring遠程服務

2022-06-13   來源: Java開源技術 

  當企業級計算進入新的SOA世界時在尋找描述/發布/和發現服務的方面中開始變得越來越重要基於網絡服務的方案不提供自動服務發現而且通常都太繁雜了現在新的輕量級的開發框架提供了新的輕量級的服務發布方案

  在過去幾年中Spring框架已經成為開發簡單靈活而且容易配置的JEE應用的事實標准Spring的核心是IoC法則根據IoC應用必須以一個簡單JavaBean的集合來開發然後用一個輕量級的IoC容器來綁定他們並設置相關的依賴關系
  在Spring中容器通過一系列bean定義也配置典型的是用XML文件方式
<bean id=MyServiceBean class=mypackageMyServiceImpl>
   <property name=otherService ref=OtherServiceBean/>
</bean>
  當客戶端代碼需要請求時MyService你只要如下編碼
MyServiceInterface service = (MyServiceInterface)contextgetBean(MyServiceBean);
servicedoSomething();
  除了IoC之外Spring提供了幾百種其他服務代碼約定而且通過回調標准API來簡化開發典型的服務端應用無論應用使用重量級的JEE API如EJB/JMS/JMX或者使用流行的MVC框架來構建網絡接口Spring都提供了簡化的效果
  隨著Spring框架的成熟越來越多的人使用他作為大型企業級項目的基礎Spring已經通過了伸縮性開發的測試而且可以作為組件粘合劑來聯結復雜的分布式系統

  任何企業級應用都由各種組件組成如聯結以前的系統和ERP系統第三方系統網面/表示層/持久導等等通常一個電子商務站點都是由簡單的網頁應用逐漸深化成包含上百個子應用和子系統的大項目而且要面對其中的復雜性會阻礙以後的發展通常的解決方案是將集成電路般的應用分解成一些粗紋理的服務並將其發布到網絡中

  不管應用是被設計成作為分散服務的集成點或者已經集成為一體管理所有分布式組件和其配置的任務通常都是耗時和代價高的但如果你使用了Spring作為應用組件的開發平台那麼你就可以使用Spring的遠程服務通過一系列的協議來將組件暴露給遠程的客戶端通過Spring可以使你的分布式應用就如修改一些配置文件那麼簡單

  在Spring中最簡單的javatojava的遠程通訊方案是使用HTTP遠程服務例如在webxml中注冊了Spring的分發服務件後下面的上下文片斷就可以將MyService作為公共接口使用了
<bean name=/MyRemoteService class=orgspringframeworkremoting>
  <property name=service ref=MyServiceBean/>
  <property name=serviceInterface value=mypackageMyServiceInterface/>
</bean>
  如你所見實際的服務被注入到bean的定義中因此可以被遠程調用
  在客戶端上下文定義如下
<bean id=MyServiceBean
      class=orgspringframeworkremoting>
      <property name=serviceUrl
         value=//somehost:/webappcontext/somemappingforspringservlet/MyRemoteService />
      <property name=serviceInterface
         value=mypackageMyServiceInterface />
   </bean>
  通過Spring的魔法客戶端代碼不需要改變而遠程方法的激活就像以前的本地調用一樣
除了HTTP遠程服務外Spring還支持其他的遠程協議如基於HTTP的解決方案(Web services Hessian and Burlap)和重量級的如RMI

配置和部署基於URL的遠程服務

  通過基於HTTP遠程服務來部署應用服務有幾個明顯的優點其中一個是相對於RMI或EJB方案你不需要擔心更多的配置問題任何嘗試過使用JNDI配置(來自不同廠家的JEE容器或者同一廠家容器的不同版本的負載均衡及群集)的人都這樣認為

  URL是無格式的文本串而這是最方便的但同時通過URL定義服務使得定義有些脆弱在前面章節列舉的URL的不同部分都會按照自己的方式進行變化網絡拓樸變化負載均衡服務器代替普通服務器應用被布署到不同機器的不同容器中網絡防火牆間的商品被打開或關閉等等

  此外這些不穩定的URL必須被存儲在每一個可能訪問服務的客戶端的Spring上下文文件中當變化發生時所有的客戶端必須更新還有從開發階段到產品階段的服務進程指向服務的URL必須反映服務所在的環境
  最後我們到達了問題的關鍵Spring的暴露各部分受管理的bean作為遠程訪問服務的能力是非常棒的甚至在我們需要定義一個服務為服務名時對客戶端隱藏所有有關服務定位的問題

自動發現和容錯的緩存服務

  這個問題最簡單解決方法是使用某些命名服務來動態實時的轉換服務名與服務位置實際上我只需要構建一次這樣的系統通過使用 JmDNS類庫注冊Spring遠程服務在Zeroconf命名空間中

  基於DNS方案的問題在於更新服務定義是不可能做到實時或事務的一個失敗的服務器在各類超時前還是出現在服務列表中而我們需要的是快速發布並更新URL列表來實現服務並在整個網絡中同步的表現所有變化

  滿足這些需求的系統才是可用的這包含各種分布式緩存的實現對Java開發人員來說最簡單的想像緩存的方式是認為緩存是一個javautilMap接口的實現你可以通過鍵值來放入一引起對象然後你可以用同一鍵值取得這個對象一個分布式緩存系統需要確保相同的鍵/值映射會存在於每一個參與這個緩存的服務器中的相同Map中並且步伐一致的更新緩存

  一個好的分布式緩存可以解決我們的問題我們在實現了服務的網絡中關聯一個服務名和一個或多個URL然後我們在分布式緩存中存儲name=(URL列表)關聯並隨著網絡狀態的變化(服務器的加入/移除/當機等)而相應更新客戶端訪問參與分布式緩存的服務就像訪問私有的服務一樣

  作為附加的獎勵我們會在這裡介紹一個簡單的負載均衡/容錯的解決方案如果客戶端知道一個服務與幾個服務URL關聯他可以隨機地使用其中的一個並且通過為這些URL服務的幾個服務來提供自然的但也有效的負載均衡而且在一個遠程調用失敗時客戶端簡單地標識那個URL不可用並且使用下一個因為服務URL列表存儲在分布式緩存中服務器A不可用的情況也會立刻通知給別的客戶端

  分布式緩存在常規的JEE應用中非常有用是群集服務的基礎例如如果你有一個分布式的群集應用分布式緩存可以在你的群集成員中提供會話復制雖然這種方式提供了高可用性但也存在嚴重的瓶頸會話數據變化的很快更新所有群集成員和容錯的代價非常高帶有會話復制的群集應用效率通常比基於負載均衡的非會話復制的方案低很多

  在我們的案例中使用分布式緩存是因為緩存的數據很少相對於通常有上千會話對象的分布式系統來說我們只有少量的服務列表和對應其實現的URL此外我們的列表更新並不頻繁使用這樣一個小列表的分布式緩存可以服務於大量的服務器和客戶端
  在本文的剩余部分我們來看一下服務描述緩存算法的實際實現

使用Spring和Jboss緩存來實現服務描述緩存

  Jboss應用服務器可能是今天最成功的開源JEE項目了不管是愛是恨Jboss應用服務器在布署服務器排行榜上占據應得的位置而且他的模塊天性使得布署更加友好

  JBoss發布包包含了很服務其中一個是JBoss緩存他實現的緩存提供了無論本地或遠程的Java對象的高性能緩存JBoss緩存有許多配置選項和特性我希望你更深入的研究使得他更好的適合你的下一個項目

  對我們最有吸引的特性如下
提供了高質量的Java對象的事務復制
可以獨立運行或者作為Jboss的一部分
已經是Jboss的一部分
可以使用UDP多播的方式和TCP連接的方式

  JBoss緩存的網絡基礎是JGroups類庫JGroups提供了群體成員間的網絡通訊並且可以工作於UDP或TCP方式
  在本文中我會演示如何使用JBoss緩存來存儲服務的定義和提供動態的自動服務發現

  剛開始我們先引入一個自定義類AutoDiscoveredServiceExporter擴展Spring的標准HttpInvokerServiceExporter類來暴露我們的TestService給遠程調用

<bean name=/TestService class=appserviceAutoDiscoveredServiceExporter>
  <property name=service ref=TestService/>
  <property name=serviceInterface value=appserviceTestServiceInterface/>
</bean>
  這個在沒有什麼可說的我們主要是使用他來標識Spring遠程服務作為我們自己的方式來暴露
接下來是服務端的緩存配置Jboss包含了緩存實現我們可以用Spring內建的JMX代理將緩存引入Spring上下文
<bean id=CustomTreeCacheMBean class=orgspringframeworkjmxaccessMBeanProxyFactoryBean>
    <property name=objectName>
      <value>jbosscache:service=CustomTreeCache</value>
    </property>
    <property name=proxyInterface>
      <value>orgjbosscacheTreeCacheMBean</value>
    </property>
</bean>
  這創建一個CustomTreeCacheMBean在服務端的Spring上下文中通過自動代理的特性這個bean實現了orgjbosscacheTreeCacheMBean接口的方法在這裡布署到Jboss服務器只需要將已經提供的customcacheservicexml放到服務器的布署目錄下

  為了簡化代碼我們引入簡單的CacheServiceInterface接口
   public void put(String path Object key Object value) throws Exception;
   public Object get(String path Object key) throws Exception;
JBoss Cache是一種樹狀結構這也是為什麼我們需要path參數
這個接口的服務端實現如下引用緩存Mbean
<bean id=CacheService class=appserviceJBossCacheServiceImpl>
   <property name=cacheMBean ref=CustomTreeCacheMBean/>
</bean>
  在最後我們需要ServicePublisher來觀察Spring容器的生命周期並且在我們的緩存中發布或移除服務定義
<bean id=ServicePublisher class=appserviceServicePublisher>
   <property name=cache ref=CacheService/>
</bean>
這段代碼顯示ServicePublisher在Spring上下文刷新時(如應用補布署時)如何處理
   private void contextRefreshed() throws Exception {
      (context refreshed);

      String[] names = context
            getBeanNamesForType(AutoDiscoveredServiceExporterclass);
      (exporting services: + nameslength);
      for (int i = ; i < nameslength; i++) {
         String serviceUrl = makeUrl(names[i]);
         try {
            Set services = (Set) cacheget(SERVICE_PREFIX + names[i]
                  SERVICE_KEY);
            if (services == null)
               services = new HashSet();
            servicesadd(serviceUrl);
            cacheput(SERVICE_PREFIX + names[i] SERVICE_KEY services);
            (added: + serviceUrl);
         } catch (Exception ex) {
            loggererror(exception adding service: ex);
         }
      }
  如你所見發布器簡單的遍歷通過緩存服務描述導出的服務列表並增加定義到緩存中我們的緩存設計成路徑包含服務名他的URL列表存儲在一個Set對象中將服務名作為路徑的一部分對JBoss Cache實現來說是重要的因為他是基於路徑來創建和釋放事務鎖這種方式下對服務A的更新不會干擾對服務B的更新因為他們被映射到不同的路徑/some/prefix/serviceA/key=(list of URLs) and /some/prefix/serviceB/key=(list of URLs)
移除服務定義的代碼是類似的

  現在我們轉到客戶端我們需要一個緩存實現來與服務端共享
<bean id=LocalCacheService class=appautoLocalJBossCacheServiceImpl>
</bean>
LocalJBossCacheServiceImpl保存著來自與服務端相同的customcacheservicexml配置的JBoss Cache引用
   public LocalJBossCacheServiceImpl() throws Exception {
      super();
      cache = new TreeCache();
      PropertyConfigurator config = new PropertyConfigurator();
      nfigure(cache app/context/customcacheservicexml);
   }
  這個緩存定義文件包含了Jgroups層的配置允許所有緩存成員通過UDP多播來定位彼此
LocalJBossCacheServiceImpl還實現了接口並且為我們的AutoDiscoveredService提供了緩存服務這個bean擴展了標准的HttpInvokerProxyFactoryBean類但配置上有些不同
   <bean id=TestService
      class=appautoAutoDiscoveredService>
      <property name=serviceInterface
         value=appserviceTestServiceInterface />
      <property name=cache ref=LocalCacheService/>
   </bean>
  最初沒有URL存在自動在網絡上尋找在TestService名字上暴露的Spring遠程服務當服務發現時他就獲得了來自分布式緩存的URL列表
   private List getServiceUrls() throws Exception {
      Set services = (Set) cacheget(ServicePublisherSERVICE_PREFIX
            + beanName ServicePublisherSERVICE_KEY);
      if (services == null)
         return null;
      ArrayList results = new ArrayList(services);
      Collectionsshuffle(results);
      (shuffled: + results);
      return results;
   }
  Collectionsshuffle隨機地重排與服務關聯的URL列表因此客戶端的方法調用在他們之間是負載均衡的實際的遠程調用如下
   public Object invoke(MethodInvocation arg) throws Throwable {

      List urls = getServiceUrls();
      if (urls != null)
         for (Iterator allUrls = erator(); allUrlshasNext();) {
            String serviceUrl = null;
            try {
               serviceUrl = (String) allUrlsnext();
               supersetServiceUrl(serviceUrl);
               (going to: + serviceUrl);
               return superinvoke(arg);
            } catch (Throwable problem) {
               if (problem instanceof IOException
                     || problem instanceof RemoteAccessException) {
                  loggerwarn(got error accessing:
                        + supergetServiceUrl() problem);
                  removeFailedService(serviceUrl);
               } else {
                  throw problem;
               }
            }
         }
      throw new IllegalStateException(No services configured for name:
            + beanName);
   }
  如你所見如果遠程調用拋出異常客戶端代碼可以處理這個問題而且可以從列表中取下一個URL因此也就提供了透明的容錯性如果調用因為某些異常失敗了他為重新拋出異常給客戶端處理
  下面的removeFailedService()方法簡單的從列表中移除了失敗的URL並更新分布式緩存使這個信息同步地通知所有其他客戶端
   private void removeFailedService(String url) {
      try {
         (removing failed service: + url);
         Set services = (Set) cacheget(ServicePublisherSERVICE_PREFIX
               + beanName ServicePublisherSERVICE_KEY);
         if (services != null) {
            servicesremove(url);
            cacheput(ServicePublisherSERVICE_PREFIX + beanName ServicePublisherSERVICE_KEY
                  services);
            (removed failed service at: + url);
         }
      } catch (Exception e) {
         loggerwarn(failed to remove failed service: + url e);
      }
   }
  如果你構建並布署一個樣例應用在多個Jboss服務器上而且運行提供的LoopingAutoDiscoveredRemoteServiceTest你可以看到請求是如何在Spring群集中負載均衡的你也可以停止和重啟任何的服務器而調用會動態地路由到其他的服務器上如果你當掉一台服務器你會看到一個異常被輸出到客戶端的控制台上但所有的請求依舊無停頓的傳遞給其他服務器

小結

  在本文中我們了解了如何通過Spring的遠程服務來群集網絡服務此外你可以學到如何通過只使用名字來定義私有的服務及依賴自動發現來綁定服務到相應的URL從而簡化布署一個復雜的多層應用


From:http://tw.wingwit.com/Article/program/Java/ky/201311/28545.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.