web-dev-qa-db-ja.com

Java Webアプリケーション:キャッシングテクニックの実装方法は?

私は、Java Webアプリケーションから動作する大規模なXML構成ファイルを介して動作するWebアプリケーションを開発しています。これらのファイルは、アプリケーションの特定のセクションにアクセスするまで実際には必要ないため、これらのファイルのいずれかが遅延して読み込まれます。これらのファイルの1つが必要になると、対応するファイルを取得するためにクエリがWebサービスに送信されます。構成ファイルの一部は、他のファイルよりも頻繁に使用される可能性が高いため、セットアップしたい同じファイルを何度もリクエストしないようにするための、ある種のキャッシュ(有効期限は1時間程度)。

Webサービスによって返されるファイルは、すべてのセッションのすべてのユーザーで同じです。私は、JSP、JSF、またはその他の豪華なフレームワークを使用せず、プレーンサーブレットのみを使用します。

私の質問は、Java Webアプリケーション内にそのようなグローバルで静的なキャッシュを実装するためのベストプラクティスとは何ですか?シングルトンクラスは適切ですか、それともJ2EEコンテナが原因で奇妙な動作になるでしょうか? ?JNDIを介して何かを公開する必要がありますか?クラスター環境でキャッシュがねじ込まれないようにするにはどうすればよいですか(クラスターサーバーごとに1つのキャッシュがあっても問題ありません)。

上記の情報を前提として、キャッシングを担当するオブジェクトをServletContext属性として配置するのは正しい実装でしょうか?

注:起動時にそれらのすべてをロードしたくありません。

1)。アプリケーションが起動するたびにWebサービスをオーバーロードする
2)。アプリケーションの実行中にファイルが変更される可能性があるため、とにかくそれらを再クエリする必要があります
3)。私はまだグローバルにアクセス可能なキャッシュが必要なので、私の質問はまだ残っています

更新:キャッシングプロキシ(Squidなど)を使用することは良い考えですが、Webサービスへの各リクエストは、ポストデータでかなり大きなXMLクエリを送信します。 Webサービスへの2つの異なる呼び出しが実際には同等であることを実際に認識しているのは、Webアプリケーションだけです。

ご協力いただきありがとうございます

25
LordOfThePigs

あなたの質問には、いくつかの別々の質問が一緒に含まれています。ゆっくり始めましょう。 ServletContextは、キャッシュへのハンドルを格納できる適切な場所です。ただし、サーバーインスタンスごとにキャッシュを用意することで支払います。問題ないはずです。より広い範囲でキャッシュを登録する場合は、JNDIに登録することを検討してください。

キャッシングの問題。基本的には、webサービスを介してxmlを取得しています。 HTTP経由でこのWebサービスにアクセスする場合は、XMLのキャッシュを処理する単純なHTTPプロキシサーバーをインストールできます。次のステップは、解決されたxmlを何らかのローカルオブジェクトキャッシュにキャッシュすることです。このキャッシュは、サーバーごとに問題なく存在できます。この2番目のケースでは、EHCacheは完全に機能します。この場合、一連の処理は次のようになりますClient - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice)

長所:

  • サーバーインスタンスごとのローカルキャッシュ。要求されたxmlからのオブジェクトのみが含まれます
  • Webアプリケーションと同じハードウェアで実行される1つのhttpプロキシ。
  • Xmlファイルに新しいhttpプロキシを追加せずにwebappをスケーリングする可能性。

短所:

  • 次のレベルのインフラストラクチャ
  • +1障害点(httpプロキシ)
  • より複雑な展開

更新:常にHTTP HEADリクエストをプロキシに送信して、キャッシュが最新であることを確認することを忘れないでください。

13

EhCacheを使用したキャッシュの例を次に示します。このコードは、アドホックキャッシングを実装するためにいくつかのプロジェクトで使用されます。

1)キャッシュをグローバルコンテキストに配置します。 (WEB.XMLにリスナーを追加することを忘れないでください。)

_import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

public class InitializationListener implements ServletContextListener {    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        CacheManager singletonManager = CacheManager.create();
        Cache memoryOnlyCache = new Cache("dbCache", 100, false, true, 86400,86400);
        singletonManager.addCache(memoryOnlyCache);
        cache = singletonManager.getCache("dbCache");       
        ctx.setAttribute("dbCache", cache );           
    }
}
_

2)必要なときにキャッシュインスタンスを取得します。つまり、サーブレットから:

cache = (Cache) this.getContext().getAttribute("dbCache");

3)負荷の高い操作を行う直前にキャッシュをクエリします。

_        Element e = getCache().get(key);
        if (e != null) {
            result = e.getObjectValue(); // get object from cache
        } else {
            // Write code to create the object you need to cache, then store it in the cache.
            Element resultCacheElement = new Element(key, result);
            cache.put(resultCacheElement);

        }
_

4)必要に応じて、キャッシュされたオブジェクトを無効にすることも忘れないでください。

あなたはより多くのサンプルを見つけることができます ここ

37
javito

オプション#1:EHCacheなどのオープンソースのキャッシュライブラリを使用する

立ち寄って使い始めることができるオープンソースの代替案が多数ある場合は、独自のキャッシュを実装しないでください。独自のキャッシュを実装することは、ほとんどの人が理解するよりもはるかに複雑であり、スレッド化について何をしているのか正確にわからない場合は、簡単にホイールの再発明を開始し、いくつかの非常に困難な問題を解決します。

私はそれがApacheライセンスの下にあるEHCacheをお勧めします。 EHCaceコードサンプル を確認してください。

オプション#2:Squidを使用

問題のさらに簡単な解決策は、Squidを使用することです...データのキャッシュを要求するプロセスと要求を行うシステムの間にSquidを挿入します。 http://www.squid-cache.org /

8
Tim O'Brien

自分の周りを少し見回した後、(質問で説明されている要件と許容可能な制限内で)必要なものを達成する最も簡単な方法は、キャッシュオブジェクトをサーブレットコンテキストに追加し、それを調べることです(または必要に応じて).

私はServletContextListenerから設定ローダーをインスタンス化し、contextInitialized()メソッド内で、ServletContext.setAttribute()を使用してそれをServletContextに格納します。その後、request.getSession()。getServletContext()。getAttribute()を使用して、サーブレット自体から簡単に検索できます。

これは、Springやその他の依存性注入フレームワークを導入せずに行う適切な方法だと思います。

1
LordOfThePigs

Bref、この準備済みのSpring ehcache構成を使用できます

1- ehcache.xml:Ehcacheのグローバル構成を表示します。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="./ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" name="myCacheManager">

    <!-- 
    see ehcache-core-*.jar/ehcache-fallback.xml for description of elements
    Attention: most of those settings will be overwritten by hybris
     -->
    <diskStore path="Java.io.tmpdir"/>

</ehcache>

2- ehcache-spring.xml:EhCacheManagerFactoryBeanおよびEhCacheFactoryBeanを作成します。

    <bean id="myCacheManager"  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
        scope="singleton">
        <property name="configLocation" value="ehcache.xml" />
        <property name="shared" value="true" />

    </bean>

 <bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean" scope="singleton">
        <property name="cacheManager" ref="myCacheManager" />
        <property name="cacheName" value="myCache" />
        <property name="maxElementsInMemory" value="1000" />
        <property name="maxElementsOnDisk" value="1000" />
        <property name="eternal" value="false" />
        <property name="diskPersistent" value="true" />
        <property name="timeToIdle" value="600" />
        <property name="timeToLive" value="1200" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
        <property name="statisticsEnabled" value="true" />
        <property name="sampledStatisticsEnabled" value="true" />
    </bean>

3- "myCache" Beanをビジネスクラスに挿入します。次の例を参照して、オブジェクトを取得してキャッシュに配置してください。

@Resource("myCache")
private net.sf.ehcache.Cache myCache; 

@Resource("myService")
private Service myService; 

public byte[] getFromCache(final String code) 
{ 
// init Cache 
final StringBuilder builder = new StringBuilder(); 
 // key to identify a entry in cache map
final String key = code;
// get form the cache 
final Element element = myCache.get(key); 
if (element != null && element.getValue() != null) 
{ 
       return (byte[]) element.getValue(); 
} 

final byte[] somethingToBeCached = myService.getBy(code); 
// store in the cache
myCache.put(new Element(key, somethingToBeCached)); 

return somethingTobeCached; 

} 
1

キャッシュされたオブジェクトインスタンスをServletContext内に配置することに問題はありませんでした。このオブジェクトのsetAttributesメソッドでは、他の2つのオプション(要求スコープ、セッションスコープ)を忘れないでください。 webcontainersとj2eeサーバーの内部でネイティブにサポートされているものはすべて適切です(つまり、ベンダーに依存せず、Springのような重いj2eeライブラリがない場合)。私の最大の要件は、サーバーが5〜10秒で稼働することです。

すべてのキャッシングソリューションが本当に嫌いです。ローカルマシンで簡単に機能させることができ、プロダクションマシンで機能させるのが難しいためです。 EHCACHE、Infinispanなど。Javaエコシステムと緊密に統合されたクラスター全体のレプリケーション/配布が必要でない限り、REDIS(NOSQLデータベース)またはnodejsを使用できます... HTTPインターフェースを備えたすべてが実行されます。特に

キャッシングは非常に簡単です。ここに、純粋なJavaソリューション(フレームワークなし))を示します。

import Java.util.*;

/*
  ExpirableObject.

  Abstract superclass for objects which will expire. 
  One interesting design choice is the decision to use
  the expected duration of the object, rather than the 
  absolute time at which it will expire. Doing things this 
  way is slightly easier on the client code this way 
  (often, the client code can simply pass in a predefined 
  constant, as is done here with DEFAULT_LIFETIME). 
*/

public abstract class ExpirableObject {
  public static final long FIFTEEN_MINUTES = 15 * 60 * 1000;
  public static final long DEFAULT_LIFETIME = FIFTEEN_MINUTES;

  protected abstract void expire();

  public ExpirableObject() {
    this(DEFAULT_LIFETIME);
  }

  public ExpirableObject(long timeToLive) {
    Expirer expirer = new Expirer(timeToLive);
    new Thread(expirer).start();
  }

  private class Expirer implements Runnable {  
    private long _timeToSleep;
    public Expirer (long timeToSleep){
      _timeToSleep = timeToSleep;
    }

    public void run() {
      long obituaryTime = System.currentTimeMillis() + _timeToSleep; 
      long timeLeft = _timeToSleep;
      while (timeLeft > 0) {
        try {
          timeLeft = obituaryTime - System.currentTimeMillis();  
          if (timeLeft > 0) {
            Thread.sleep(timeLeft);
          } 
        }
        catch (InterruptedException ignored){}      
      }
      expire();
    }
  }
}

さらなる改善については、この link を参照してください。

0
Mitja Gustin