如何建立一个高可用的缓存

如何建立一个高可用的缓存

最近在写一个项目,有一个列表用来显示信息,原先每页只显示5条10条,后来改了,需要显示50条,现在要显示100条,后面可能需要显示更多记录。

那么问题来了,众所周知,数据库查询5条,50条,100条所用的时间是不一样的,如何高效的返回一个动辄上百条的数据,这是一个问题。

我解决这个问题的大概思路是利用redis建立一个缓存,从缓存中取数据,这样可以解决上面出现的问题,后面又顺便把跟新缓存的操作也写了。

废话不多说,上代码:

DbManager.java

package com.iluwatar.caching;

import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

import org.bson.Document;

import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
/**
 * Auth Ilkka Seppälä
 * Translate by 林文耀
 * 此类模拟与数据库的通信,提供了查询、插入和更新数据的方法。
 * 开发人员/测试人员可以选择应用程序是否应使用MongoDB作为
*  其底层数据存储或适应map来作为虚拟数据库
 */
public final class DbManager {

  private static MongoClient mongoClient;
  private static MongoDatabase db;
  private static boolean useMongoDB;

  private static Map<String, UserAccount> virtualDB;

  private DbManager() {
  }

  /**
   * 创建 DB
   */
  public static void createVirtualDb() {
    useMongoDB = false;
    virtualDB = new HashMap<>();
  }

  /**
   * 连接 DB
   */
  public static void connect() throws ParseException {
    useMongoDB = true;
    mongoClient = new MongoClient();
    db = mongoClient.getDatabase("test");
  }

  /**
   * 从 DB中获取用户ID
   */
  public static UserAccount readFromDb(String userId) {
    if (!useMongoDB) {
      if (virtualDB.containsKey(userId)) {
        return virtualDB.get(userId);
      }
      return null;
    }
    if (db == null) {
      try {
        connect();
      } catch (ParseException e) {
        e.printStackTrace();
      }
    }
    FindIterable<Document> iterable =
        db.getCollection("user_accounts").find(new Document("userID", userId));
    if (iterable == null) {
      return null;
    }
    Document doc = iterable.first();
    return new UserAccount(userId, doc.getString("userName"), doc.getString("additionalInfo"));
  }

  /**
   * 将用户写入DB
   */
  public static void writeToDb(UserAccount userAccount) {
    if (!useMongoDB) {
      virtualDB.put(userAccount.getUserId(), userAccount);
      return;
    }
    if (db == null) {
      try {
        connect();
      } catch (ParseException e) {
        e.printStackTrace();
      }
    }
    db.getCollection("user_accounts").insertOne(
        new Document("userID", userAccount.getUserId()).append("userName",
            userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo()));
  }

  /**
   * 跟新用户信息
   */
  public static void updateDb(UserAccount userAccount) {
    if (!useMongoDB) {
      virtualDB.put(userAccount.getUserId(), userAccount);
      return;
    }
    if (db == null) {
      try {
        connect();
      } catch (ParseException e) {
        e.printStackTrace();
      }
    }
    db.getCollection("user_accounts").updateOne(
        new Document("userID", userAccount.getUserId()),
        new Document("$set", new Document("userName", userAccount.getUserName()).append(
            "additionalInfo", userAccount.getAdditionalInfo())));
  }

  /**
   * 新增或修改用户信息(存在则修改,不存在则跟新)
   */
  public static void upsertDb(UserAccount userAccount) {
    if (!useMongoDB) {
      virtualDB.put(userAccount.getUserId(), userAccount);
      return;
    }
    if (db == null) {
      try {
        connect();
      } catch (ParseException e) {
        e.printStackTrace();
      }
    }
    db.getCollection("user_accounts").updateOne(
        new Document("userID", userAccount.getUserId()),
        new Document("$set", new Document("userID", userAccount.getUserId()).append("userName",
            userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())),
        new UpdateOptions().upsert(true));
  }
}

UserAccount.java

package com.iluwatar.caching;

/**
 * 用户实体类。
 */
public class UserAccount {
  private String userId;
  private String userName;
  private String additionalInfo;

  /**
   * 构造器
   */
  public UserAccount(String userId, String userName, String additionalInfo) {
    this.userId = userId;
    this.userName = userName;
    this.additionalInfo = additionalInfo;
  }

  public String getUserId() {
    return userId;
  }

  public void setUserId(String userId) {
    this.userId = userId;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getAdditionalInfo() {
    return additionalInfo;
  }

  public void setAdditionalInfo(String additionalInfo) {
    this.additionalInfo = additionalInfo;
  }

  @Override
  public String toString() {
    return userId + ", " + userName + ", " + additionalInfo;
  }
}

CachingPolicy.java

package com.iluwatar.caching;

/**
 * 包含了4中缓存策略的枚举类。
 */
public enum CachingPolicy {
  THROUGH("through"), AROUND("around"), BEHIND("behind"), ASIDE("aside");

  private String policy;

  private CachingPolicy(String policy) {
    this.policy = policy;
  }

  public String getPolicy() {
    return policy;
  }
}

下面重头戏来了。
LruCache.java

package com.iluwatar.caching;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  数据缓存结构的实现。
 */
public class LruCache {

  private static final Logger LOGGER = LoggerFactory.getLogger(LruCache.class);

  class Node {
    String userId;
    UserAccount userAccount;
    Node previous;
    Node next;

    public Node(String userId, UserAccount userAccount) {
      this.userId = userId;
      this.userAccount = userAccount;
    }
  }

  int capacity;
  Map<String, Node> cache = new HashMap<>();
  Node head;
  Node end;

  public LruCache(int capacity) {
    this.capacity = capacity;
  }

  public UserAccount get(String userId) {
    if (cache.containsKey(userId)) {
      Node node = cache.get(userId);
      remove(node);
      setHead(node);
      return node.userAccount;
    }
    return null;
  }

  public void remove(Node node) {
    if (node.previous != null) {
      node.previous.next = node.next;
    } else {
      head = node.next;
    }
    if (node.next != null) {
      node.next.previous = node.previous;
    } else {
      end = node.previous;
    }
  }

  public void setHead(Node node) {
    node.next = head;
    node.previous = null;
    if (head != null) {
      head.previous = node;
    }
    head = node;
    if (end == null) {
      end = head;
    }
  }

  public void set(String userId, UserAccount userAccount) {
    if (cache.containsKey(userId)) {
      Node old = cache.get(userId);
      old.userAccount = userAccount;
      remove(old);
      setHead(old);
    } else {
      Node newNode = new Node(userId, userAccount);
      if (cache.size() >= capacity) {
        LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
        cache.remove(end.userId); // remove LRU data from cache.
        remove(end);
        setHead(newNode);
      } else {
        setHead(newNode);
      }
      cache.put(userId, newNode);
    }
  }

  public boolean contains(String userId) {
    return cache.containsKey(userId);
  }

  public void invalidate(String userId) {
    Node toBeRemoved = cache.remove(userId);
    if (toBeRemoved != null) {
      LOGGER.info("# {} has been updated! Removing older version from cache...", userId);
      remove(toBeRemoved);
    }
  }

  public boolean isFull() {
    return cache.size() >= capacity;
  }

  public UserAccount getLruData() {
    return end.userAccount;
  }

  public void clear() {
    head = null;
    end = null;
    cache.clear();
  }

  public List<UserAccount> getCacheDataInListForm() {
    List<UserAccount> listOfCacheData = new ArrayList<>();
    Node temp = head;
    while (temp != null) {
      listOfCacheData.add(temp.userAccount);
      temp = temp.next;
    }
    return listOfCacheData;
  }

  public void setCapacity(int newCapacity) {
    if (capacity > newCapacity) {
      clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll
               // just clear the cache.
    } else {
      this.capacity = newCapacity;
    }
  }
}

LruCache类是用来存储缓存数据的类,有趣的是类似于HashMap的TreeNode一样,他不但维护了一个table表(Node数组)。并且table的所有node自身又组成了一个双端队列,这是怎么做的的呢?我们先来看一下它的内部类Node。

class Node {
    String userId;
    UserAccount userAccount;
    Node previous;
    Node next;

    public Node(String userId, UserAccount userAccount) {
      this.userId = userId;
      this.userAccount = userAccount;
    }
  }

很显然,这个Node包含了上一个Node的引用和下一个Node的引用,这样可以根据第一个Node顺序找到余下的所有Node。

接下来看LruCache类的属性:

int capacity;											//缓存的最大值
Map<String, Node> cache = new HashMap<>();		//缓存容器
Node head;											//第一个节点(头节点)的引用
Node end;											//最后一个节点的引用

remove方法:

  • 如果此节点不是头节点,将此节点的上个节点的next引用指向此节点的next
  • 如果是头节点,将此节点的下个节点设为头节点
  • 如果不是尾节点,将此节点的下个节点的previous引用指向此节点的previous
  • 如果是尾节点,将尾节点指向此节点的下一个节点
public void remove(Node node) {
	if (node.previous != null) {
	  node.previous.next = node.next;
	} else {
	  head = node.next;
	}
	if (node.next != null) {
	  node.next.previous = node.previous;
	} else {
	  end = node.previous;
	}
}

setHead方法:

  • 将此节点的下个节点设为头节点
  • 将此节点的上一个节点设为null
  • 如果头节点不是空的,将头节点的上个节点设为此节点
  • 将头节点的指向此节点(注:head只是一个头节点的引用,如果一个节点被设置为头节点,则head需要指向此节点)
  • 如果尾节点是空,将尾节点设置为头节点
public void setHead(Node node) {
	node.next = head;
	node.previous = null;
	if (head != null) {
	  head.previous = node;
	}
	head = node;
	if (end == null) {
	  end = head;
	}
}

get方法:

  • 如果缓存中命中了key(userId)则取出这个node
  • 移除node节点的前后引用
  • 设置此节点为头节点(这样下次取数据时此节点会优先匹配,命中率越多的节点,优先级更高)
public UserAccount get(String userId) {
    if (cache.containsKey(userId)) {
      Node node = cache.get(userId);
      remove(node);
      setHead(node);
      return node.userAccount;
    }
    return null;
}

set方法:

  • 判断缓存中是否包含了key(userId),如果有,则直接跟新并且位置重置为head
  • 如果没有并且队列满了,移除尾部的队列并且新增一条缓存记录,重置为head,并且往容器中加入此节点
public void set(String userId, UserAccount userAccount) {
    if (cache.containsKey(userId)) {
      Node old = cache.get(userId);
      old.userAccount = userAccount;
      remove(old);
      setHead(old);
    } else {
      Node newNode = new Node(userId, userAccount);
      if (cache.size() >= capacity) {
        LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
        cache.remove(end.userId); // remove LRU data from cache.
        remove(end);
        setHead(newNode);
      } else {
        setHead(newNode);
      }
      cache.put(userId, newNode);
    }
}

返回尾节点的用户信息

public UserAccount getLruData() {
    return end.userAccount;
}

CacheStore.java

/**
 * The MIT License
 * Copyright (c) 2014-2016 Ilkka Seppälä
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.iluwatar.caching;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 缓存策略类
 */
public class CacheStore {

  private static final Logger LOGGER = LoggerFactory.getLogger(CacheStore.class);

  static LruCache cache;

  private CacheStore() {
  }

  public static void initCapacity(int capacity) {
    if (cache == null) {
      cache = new LruCache(capacity);
    } else {
      cache.setCapacity(capacity);
    }
  }

  public static UserAccount readThrough(String userId) {
    if (cache.contains(userId)) {
      LOGGER.info("# Cache Hit!");
      return cache.get(userId);
    }
    LOGGER.info("# Cache Miss!");
    UserAccount userAccount = DbManager.readFromDb(userId);
    cache.set(userId, userAccount);
    return userAccount;
  }

  public static void writeThrough(UserAccount userAccount) {
    if (cache.contains(userAccount.getUserId())) {
      DbManager.updateDb(userAccount);
    } else {
      DbManager.writeToDb(userAccount);
    }
    cache.set(userAccount.getUserId(), userAccount);
  }

  public static void writeAround(UserAccount userAccount) {
    if (cache.contains(userAccount.getUserId())) {
      DbManager.updateDb(userAccount);
      cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older
                                                 // version from cache.
    } else {
      DbManager.writeToDb(userAccount);
    }
  }

  public static UserAccount readThroughWithWriteBackPolicy(String userId) {
    if (cache.contains(userId)) {
      LOGGER.info("# Cache Hit!");
      return cache.get(userId);
    }
    LOGGER.info("# Cache Miss!");
    UserAccount userAccount = DbManager.readFromDb(userId);
    if (cache.isFull()) {
      LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
      UserAccount toBeWrittenToDb = cache.getLruData();
      DbManager.upsertDb(toBeWrittenToDb);
    }
    cache.set(userId, userAccount);
    return userAccount;
  }

  public static void writeBehind(UserAccount userAccount) {
    if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
      LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
      UserAccount toBeWrittenToDb = cache.getLruData();
      DbManager.upsertDb(toBeWrittenToDb);
    }
    cache.set(userAccount.getUserId(), userAccount);
  }

  public static void clearCache() {
    if (cache != null) {
      cache.clear();
    }
  }

  public static void flushCache() {
    LOGGER.info("# flushCache...");
    if (null == cache) {
      return;
    }
    List<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm();
    for (UserAccount userAccount : listOfUserAccounts) {
      DbManager.upsertDb(userAccount);
    }
  }

  public static String print() {
    List<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm();
    StringBuilder sb = new StringBuilder();
    sb.append("\n--CACHE CONTENT--\n");
    for (UserAccount userAccount : listOfUserAccounts) {
      sb.append(userAccount.toString() + "\n");
    }
    sb.append("----\n");
    return sb.toString();
  }

  public static UserAccount get(String userId) {
    return cache.get(userId);
  }

  public static void set(String userId, UserAccount userAccount) {
    cache.set(userId, userAccount);
  }

  /**
 * Delegate to backing cache store
   */
  public static void invalidate(String userId) {
    cache.invalidate(userId);
  }

  static class User{
    public User(Integer a){
      this.a = a;
    }
    public Integer a ;
  }
}

这里需要先了解缓存的4种策略,因为Read-Through和Read-Around代码上没区别,所以放一起讲了。
Read-Through与Read-Around:效率高,但是缓存区满,有可能会造成数据丢失(取决于缓存的写入策略)

  • 如果缓存中命中了,直接返回
  • 否则从数据库中查询,存入缓存后返回
public static UserAccount readThrough(String userId) {
    if (cache.contains(userId)) {
      LOGGER.info("# Cache Hit!");
      return cache.get(userId);
    }
    LOGGER.info("# Cache Miss!");
    UserAccount userAccount = DbManager.readFromDb(userId);
    cache.set(userId, userAccount);
    return userAccount;
}

Read-Behind:带有更新数据库的缓存使用

  • 如果缓存中命中了,直接返回
  • 否则先从数据库中获取数据,如果缓存满了,往数据库中更新一条记录,再返回,如果没满,则设置缓存后返回
public static UserAccount readThroughWithWriteBackPolicy(String userId) {
    if (cache.contains(userId)) {
      LOGGER.info("# Cache Hit!");
      return cache.get(userId);
    }
    LOGGER.info("# Cache Miss!");
    UserAccount userAccount = DbManager.readFromDb(userId);
    if (cache.isFull()) {
      LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
      UserAccount toBeWrittenToDb = cache.getLruData();
      DbManager.upsertDb(toBeWrittenToDb);
    }
    cache.set(userId, userAccount);
    return userAccount;
}

Read-Aside:代码似乎跟Read-Thound差不多

private static UserAccount findAside(String userId) {
    UserAccount userAccount = CacheStore.get(userId);
    if (userAccount != null) {
      return userAccount;
    }

    userAccount = DbManager.readFromDb(userId);
    if (userAccount != null) {
      CacheStore.set(userId, userAccount);
    }

    return userAccount;
}

接下来说写缓存的策略,也是从4个方面入手
Write-Through:尽可能确保数据是最新的

  • 不管缓存中是否有数据,直接写入数据库,然后再跟新缓存
public static void writeThrough(UserAccount userAccount) {
    if (cache.contains(userAccount.getUserId())) {
      DbManager.updateDb(userAccount);
    } else {
      DbManager.writeToDb(userAccount);
    }
    cache.set(userAccount.getUserId(), userAccount);
}

Write-Around:直接写入数据到数据库,不必写到缓存,缓存的数据应该被立即过期,认为缓存一旦过期就不能使用了

  • 如果缓存中命中,更新,并且删除缓存
  • 否则直接写入数据库
public static void writeAround(UserAccount userAccount) {
    if (cache.contains(userAccount.getUserId())) {
      DbManager.updateDb(userAccount);
      cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older
                                                 // version from cache.
    } else {
      DbManager.writeToDb(userAccount);
    }
}

Write-Behind:类似于牺牲数据时效性,为了改善读写性能,读写操作直接在缓存中进行,统一处理脏数据,这个策略极大的利用到了缓存,适合缓存读写频率高的业务,建议配合Read-Behind使用

  • 如果缓存已满并且缓存中不包含此用户信息,更新缓存链表中的尾节点,并移除
  • 如果缓存未满或者缓存中包含了此用户信息,直接设置或跟新缓存
public static void writeBehind(UserAccount userAccount) {
    if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
      LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
      UserAccount toBeWrittenToDb = cache.getLruData();
      DbManager.upsertDb(toBeWrittenToDb);
    }
    cache.set(userAccount.getUserId(), userAccount);
}

Write-Around:更新完使缓存过期

private static void saveAside(UserAccount userAccount) {
    DbManager.updateDb(userAccount);
    CacheStore.invalidate(userAccount.getUserId());
}

AppManager.java

package com.iluwatar.caching;

import java.text.ParseException;

/**
 *  对缓存策略的进一步封装,可以理解为调用方的管理类
 */
public final class AppManager {

  private static CachingPolicy cachingPolicy;

  private AppManager() {
  }

  public static void initDb(boolean useMongoDb) {
    if (useMongoDb) {
      try {
        DbManager.connect();
      } catch (ParseException e) {
        e.printStackTrace();
      }
    } else {
      DbManager.createVirtualDb();
    }
  }

  public static void initCachingPolicy(CachingPolicy policy) {
    cachingPolicy = policy;
    if (cachingPolicy == CachingPolicy.BEHIND) {
      Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache));//lambda 类似() -> CacheStore.flushCache()
    }
    CacheStore.clearCache();
  }

  public static void initCacheCapacity(int capacity) {
    CacheStore.initCapacity(capacity);
  }

  public static UserAccount find(String userId) {
    if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
      return CacheStore.readThrough(userId);
    } else if (cachingPolicy == CachingPolicy.BEHIND) {
      return CacheStore.readThroughWithWriteBackPolicy(userId);
    } else if (cachingPolicy == CachingPolicy.ASIDE) {
      return findAside(userId);
    }
    return null;
  }

  public static void save(UserAccount userAccount) {
    if (cachingPolicy == CachingPolicy.THROUGH) {
      CacheStore.writeThrough(userAccount);
    } else if (cachingPolicy == CachingPolicy.AROUND) {
      CacheStore.writeAround(userAccount);
    } else if (cachingPolicy == CachingPolicy.BEHIND) {
      CacheStore.writeBehind(userAccount);
    } else if (cachingPolicy == CachingPolicy.ASIDE) {
      saveAside(userAccount);
    }
  }

  public static String printCacheContent() {
    return CacheStore.print();
  }

  private static void saveAside(UserAccount userAccount) {
    DbManager.updateDb(userAccount);
    CacheStore.invalidate(userAccount.getUserId());
  }

  private static UserAccount findAside(String userId) {
    UserAccount userAccount = CacheStore.get(userId);
    if (userAccount != null) {
      return userAccount;
    }

    userAccount = DbManager.readFromDb(userId);
    if (userAccount != null) {
      CacheStore.set(userId, userAccount);
    }

    return userAccount;
  }
}

App.java


package com.iluwatar.caching;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {

  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

  public static void main(String[] args) {
    AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
                              // and the App class to avoid Maven compilation errors. Set flag to
                              // true to run the tests with MongoDB (provided that MongoDB is
                              // installed and socket connection is open).
    AppManager.initCacheCapacity(3);
    App app = new App();
    app.useReadAndWriteThroughStrategy();
    app.useReadThroughAndWriteAroundStrategy();
    app.useReadThroughAndWriteBehindStrategy();
    app.useCacheAsideStategy();
  }

  public void useReadAndWriteThroughStrategy() {
    LOGGER.info("# CachingPolicy.THROUGH");
    AppManager.initCachingPolicy(CachingPolicy.THROUGH);

    UserAccount userAccount1 = new UserAccount("001", "John", "He is a boy.");

    AppManager.save(userAccount1);
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("001");
    AppManager.find("001");
  }

  public void useReadThroughAndWriteAroundStrategy() {
    LOGGER.info("# CachingPolicy.AROUND");
    AppManager.initCachingPolicy(CachingPolicy.AROUND);

    UserAccount userAccount2 = new UserAccount("002", "Jane", "She is a girl.");

    AppManager.save(userAccount2);
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("002");
    LOGGER.info(AppManager.printCacheContent());
    userAccount2 = AppManager.find("002");
    userAccount2.setUserName("Jane G.");
    AppManager.save(userAccount2);
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("002");
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("002");
  }

  public void useReadThroughAndWriteBehindStrategy() {
    LOGGER.info("# CachingPolicy.BEHIND");
    AppManager.initCachingPolicy(CachingPolicy.BEHIND);

    UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food.");
    UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
    UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");

    AppManager.save(userAccount3);
    AppManager.save(userAccount4);
    AppManager.save(userAccount5);
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("003");
    LOGGER.info(AppManager.printCacheContent());
    UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
    AppManager.save(userAccount6);
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("004");
    LOGGER.info(AppManager.printCacheContent());
  }

  public void useCacheAsideStategy() {
    LOGGER.info("# CachingPolicy.ASIDE");
    AppManager.initCachingPolicy(CachingPolicy.ASIDE);
    LOGGER.info(AppManager.printCacheContent());

    UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food.");
    UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
    UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
    AppManager.save(userAccount3);
    AppManager.save(userAccount4);
    AppManager.save(userAccount5);

    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("003");
    LOGGER.info(AppManager.printCacheContent());
    AppManager.find("004");
    LOGGER.info(AppManager.printCacheContent());
  }
}

其实缓存策略的定义不止这些,以上只是粗略的使用了一下,如果你对缓存的使用有什么见解,或者对文章有什么意见的话,都欢迎在评论区留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
负载均衡高可用web集群是一种常见的架构模式,旨在提高应用程序的可用性和性能。它通常由多个服务器节点组成,这些节点共同处理来自客户端的请求,并通过负载均衡器分配负载。以下是实现负载均衡高可用web集群的一般步骤: 1. 配置负载均衡器:选择一个适合的负载均衡算法(如轮询、最少连接等),并将负载均衡器配置为监听特定的端口和协议。常见的负载均衡器有Nginx、HAProxy等。 2. 设置服务器节点:建立多个服务器节点来处理客户端请求。这些节点可以是物理服务器或虚拟机,并且最好位于不同的物理位置或云服务提供商中,以增加可用性。 3. 配置健康检查:负载均衡器可以通过定期发送请求来检查服务器节点的健康状况。如果节点无法响应或出现错误,负载均衡器将停止将请求发送给该节点,并将流量转移到其他健康的节点上。 4. 同步会话状态:如果应用程序具有会话状态(例如用户登录信息),则需要确保会话状态在多个服务器节点之间同步。这可以通过使用共享会话存储(如数据库或缓存)来实现。 5. 优化静态内容:将静态内容(如图片、CSS和JavaScript文件)存储在专门的静态文件服务器上,以减轻动态请求对负载均衡器和应用服务器的压力。 6. 监视和扩展:设置监视系统来实时监控整个集群的性能和健康状况。根据负载情况和性能指标,可以动态地扩展集群规模,增加服务器节点数量。 通过以上步骤,您可以实现一个具有负载均衡和高可用性的web集群,提供可靠且高性能的服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

97年的典藏版

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值