cache - niubods/playframework-notes GitHub Wiki
为了打造一个高性能的系统,有时你需要把数据缓存起来。Play有一个缓存库,并且当用在分布式环境下时会使用 Memcached 。
如果你不配置Memcached,Play会使用一个把数据保存保存在JVM堆内存中的独立缓存。把数据缓存在JVM应用中破坏了Play制定的“无共享”理念:你的应用无法在运行于多服务器时保持行为一致。
缓存的约定很清楚,有必要弄明白这一点:当把你的数据放进缓存的时候,你不要期望数据永远都会在那里。事实上你也不应该这样。缓存速度很快,但是保存的值会失效,而且通常缓存只保存在内存中(没有持久化备份)。
所以使用缓存的最佳方式是当你拿不到期望的数据时要重新放进去一份:
public static void allProducts() {
List<Product> products = Cache.get("products", List.class);
if(products == null) {
products = Product.findAll();
Cache.set("products", products, "30mn");
}
render(products);
}
缓存的API由 play.cache.Cache
类提供。这个类包含了一系列方法用来从缓存中设置、替换和获取数据。参阅Memcached文档来进一步了解每种方法的确切行为。
举个例子:
public static void showProduct(String id) { Product product = Cache.get("product_" + id, Product.class); if(product == null) { product = Product.findById(id); Cache.set("product_" + id, product, "30mn"); } render(product); }
public static void addProduct(String name, int price) { Product product = new Product(name, price); product.save(); showProduct(product.id); }
public static void editProduct(String id, String name, int price) { Product product = Product.findById(id); product.name = name; product.price = price; Cache.set("product_" + id, product, "30mn"); showProduct(id); }
public static void deleteProduct(String id) { Product product = Product.findById(id); product.delete(); Cache.delete("product_" + id); allProducts(); }
有些方法前面带有 safe
前缀——例如 safeDelete
, safeSet
。标准方法都是非阻塞的。也就是说当你进行调用时:
Cache.delete("product_" + id);
delete
方法会立即返回而不会等到缓存的对象真的被删除。所以如果发生了一个错误——比如说一个IO错误——这个对象仍然存在。
当你在继续之前需要确保对象被删除的时候,你可以使用 safeDelete
方法:
Cache.safeDelete("product_" + id);
这个方法会阻塞并会根据对象是不是已经被删除返回一个布尔值。那么确保一个条目从缓存中删除的完整示例是这样的:
if(!Cache.safeDelete("product_" + id)) {
throw new Exception("Oops, the product has not been removed from the cache");
}
...
注意这些都是阻塞式调用, safe
方法会拖慢你的应用。所以只有当确实需要的时候再使用。
还有要注意当指定 expiration == "0s"
(零秒)时实际的失效时间在不同的缓存实现中可能是各种各样的。
如果你是从一个使用由内存实现Session的框架转过来的,你可能对Play只允许在HTTP Session中保存少量字符串数据颇有微词。但样的确是更好的做法,因为session并不是一个让你缓存应用数据的地方!
所以如果你习惯类似下面的做法:
httpServletRequest.getSession().put("userProducts", products);
...
// 然后在随后的请求中
products = (List<Product>)httpServletRequest.getSession().get("userProducts");
在Play中你稍做改变就能达到相同的效果,我们认为这是一种更好的方法:
Cache.set(session.getId(), products);
...
// 然后在随后的请求中
List<Product> products = Cache.get(session.getId(), List.class)
这里我们使用了一个唯一的UUID来确保针对每个用户的信息在缓存中都是唯一的。要记住,和session对象不同,缓存不会绑定到任何具体的用户!
当你想要开启一个真正的Memcached实现时,请配置 application.conf
中的 memcached
项,并且在 memcached.host
中定义后台进程的地址。