Objekte cachen

LL0rd

Erfahrenes Mitglied
Hallo Leute,

ich bin gerade dabei ein Portal zu entwickeln, welches auf einem Tomcat als Servlet laufen wird. Aber nun stehe ich vor einem kleinen Problem. Das Abrufen eines vollständigen Benutzerprofils aus der Datenbank gestaltet sich als etwas langsam aus diesem Grund habe ich mir gedacht, ich könnte auf dem Webserver einfach einen Cache einführen, in dem die Daten gespeichert werden.

Da jedes Benutzerprofil eine eindeutige ID hat, sowie eine Instanz der Benutzerklasse bekommt, habe ich mir gedacht, ich könnte doch einfach eine HashMap <id,benutzerprofil> anlegen und vor dem Abrufen der Daten aus der Datenbank einfach nach der ID in der HashMap suchen. Ich bin mir sicher, dass es funktionieren wird. Aber:

Der Server hat ja nur einen begrenzten Speicher und es gibt Profile, die ich nicht mehr im Speicher brauche. Hat von euch jemand eine Idee, wie ich soetwas wie eine Ablaufzeit definieren kann, nachdem die Daten aus dem Cache entfernt werden?
 
Hi,

Tomcat ist bei mir schon ne Weile her, aber wenn ich mich richtig erinnere gabs dort doch Sessions, Du könntest das Profil in einer Session ablegen ( aber unschön)! Du könntest Dir auch per HashMap so eine Art Sessions selber bauen mit TimeStamp die die Gültigkeit der Sessions beschränken, nach ablauf der Zeit löscht Du die verfallenen Sessions einfach wieder aus der Map....

hmf
 
Das Problem mit dem Speichern in einer Session ist, dass dann der Cache nur für einen Client gültig ist. Ich möchte es aber für die ganze Site haben (andere User können auch fremde Profile aufrufen). Deshalb fand ich meinen Vorschlag mit der HashMap auch relativ gut, nur die Frage ist jetzt, wie ich die HashMap effektiv einsetze, um das Ablaufdatum zu speichern. Ich frage deshalb mal ganz allgemein:

Wo würdest du das Datum speichern, sodass man ganz einfach dannach suchen kann?
 
Hallo,

sowas würde ich nicht selber bauen... hierfür sind HTTPSessions da (wobei man aufpassen muss, dass man nicht zu viel Daten in den HTTPSession ablegt das kann sonst schnell zu erheblichen Performanceproblemen führen.

Gruß Tom
 
Hi,

ich verstehe nicht, wie ich so etwas mit HTTPSessions realisieren kann. Denn ich möchte ja keinen Cache für einen User bauen, sondern für die gesamte Seite. Da kann ich ja eigentlich keine Sessions verwenden, oder irre ich mich da? Also nur mal ein Beispiel:

User A ruft das Profil von User Z an, es wird aus der DB geladen.
User B ruft das Profil von User Z an, es befindet sich im Cache und wird direkt ausgegeben.
 
Hallo,

das was du brauchen könntest wäre eine Art LRU (Last Recently Used) / MRO (Most Recently Used) Map für Java:
http://www.google.de/search?hl=de&q=LRU+Map+Java&btnG=Suche&meta=

Bzw. einfach nur ne Map mit TimeoutSupport das kann man sich ziemlich leicht selber bauen. Hier mal auf die schnelle:
Java:
/**
 * 
 */
package de.tutorials;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

/**
 * @author Thomas.Darimont
 */
public class MapWithTimeOutSupport {

  /**
   * @param args
   */
  public static void main(String[] args) throws Exception {
    NotificationAwareConcurrentHashMapWithTimeoutSupport<String, String> map = new NotificationAwareConcurrentHashMapWithTimeoutSupport<String, String>();
    map.getPropertyChangeSupport().addPropertyChangeListener(new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
        System.out.println("Removed: " + evt.getOldValue() + " by thread: " + Thread.currentThread().getName());
      }
    });

    map.put("a", "aa");
    map.put("b", "bb", 5, TimeUnit.SECONDS);
    map.put("c", "cc");

    System.out.println(map);

    TimeUnit.SECONDS.sleep(1);
    System.out.println(map);

    TimeUnit.SECONDS.sleep(4);
    System.out.println(map);

    TimeUnit.SECONDS.sleep(4);
    System.out.println(map);
  }

  static interface NotificatonAwareMapMixinWithTimeoutSupport<TKey, TValue> {
    TValue put(TKey key, TValue value, long timeOut, TimeUnit timeUnit);


    PropertyChangeSupport getPropertyChangeSupport();
  }

  static class NotificationAwareConcurrentHashMapWithTimeoutSupport<TKey, TValue> extends
    ConcurrentHashMap<TKey, TValue> implements NotificatonAwareMapMixinWithTimeoutSupport<TKey, TValue> {

    private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    private final List<KeyWithTimeout<TKey>> keysWithTimeout = new CopyOnWriteArrayList<KeyWithTimeout<TKey>>();

    final static long pollingTimeoutInMilliseconds = 250L;

    {
      Thread thread = new Thread() {
        @Override
        public void run() {
          while (true) {
            try {

              for (KeyWithTimeout<TKey> keyWithTimeout : keysWithTimeout) {
                if (keyWithTimeout.timedOut()) {
                  keysWithTimeout.remove(keyWithTimeout);
                  propertyChangeSupport.firePropertyChange(null, keyWithTimeout.getKey(), null);
                  remove(keyWithTimeout.getKey());
                  keyWithTimeout.reset();
                }
              }

              TimeUnit.MILLISECONDS.sleep(pollingTimeoutInMilliseconds);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        }
      };
      thread.setDaemon(true);
      thread.setName(getClass() + "@" + System.identityHashCode(this) + "_TimeOutChecker");
      thread.start();
    }


    public TValue put(TKey key, TValue value, long timeOut, TimeUnit timeUnit) {
      keysWithTimeout.add(new KeyWithTimeout<TKey>(key, timeOut, timeUnit));
      return super.put(key, value);
    }


    /**
     * @return the propertyChangeSupport
     */
    public PropertyChangeSupport getPropertyChangeSupport() {
      return propertyChangeSupport;
    }

    static class KeyWithTimeout<TKey> {
      private TKey key;
      private long timeout;
      private TimeUnit timeUnit;
      private long creationTimeInMilliseconds;


      /**
       * @param entry
       * @param timeout
       * @param timeUnit
       */
      public KeyWithTimeout(TKey key, long timeout, TimeUnit timeUnit) {
        super();
        this.key = key;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.creationTimeInMilliseconds = System.currentTimeMillis();
      }


      public void reset() {
        this.key = null;
        this.timeUnit = null;
        this.timeout = 0;
        this.creationTimeInMilliseconds = 0;
      }


      /**
       * @return the key
       */
      public TKey getKey() {
        return key;
      }


      /**
       * @return the timeout
       */
      public long getTimeout() {
        return timeout;
      }


      /**
       * @return the timeUnit
       */
      public TimeUnit getTimeUnit() {
        return timeUnit;
      }


      public boolean timedOut() {
        return System.currentTimeMillis() - this.creationTimeInMilliseconds > timeUnit.toMillis(timeout);
      }
    }


    @Override
    public void clear() {
      super.clear();
      this.keysWithTimeout.clear();
    }
  }

}

andere Open Source Cachelösungen findest du hier:
http://java-source.net/open-source/cache-solutions

Gruß Tom
 
Hi, vielen Dank für deine Hilfe, LRU war der entscheidende Begriff. Ich habe da folgendes gefunden:

http://javawiki.sowas.com/doku.php?id=java:lru-cache

Wenn ich es mir so ansehe, dann wird es das einfachste sein, oder?

Ich habe mir jetzt auch http://www.terracotta.org angeschaut und das System sieht richtig nice aus. Aber ich verstehe nicht, wie es dort mit dem cache funktionieren soll. Terracotta klemmt sich doch zwischen die Anwendung und JVM und transferiert alle Objekte rüber. Aber was hat das mit dem Cachen zutun? Genau das gleiche Problem habe ich z.T. auch mit der Beschreibung der anderen Produkte. Ich sehe dort überall die Verwendung von Hibernate, aber meine Anwendung läuft ohne Hibernate, es sollen keine ganzen Objekte in der Datenbank gespeichert werden, sondern die eigentlichen Daten.
 
Ich denk Hibernte wird so oft verwendet, weil es einen expliziten Hook für Caches hat und ORM Tools auch meist Ansatzpunkte für Caching sind. EHCache wird z.B. oft in Verbindung mit Hibernate eingesetzt.

Terracotta ist eher eine Clustering Technologie. Gehört also auch zum Thema Performance Optimierung - daher wohl die Verbindung.

Ansonsten ist vielleicht noch AOP zu erwähnen, da sich damit Caches recht einfach auf bestehende Anwendungen "aufsetzen" lassen und man die Anwendung selbst gar nicht mit dem Cache zu tun hat.

Gruß
Ollie
 
Zurück