本地热点缓存的特点
只有热点数据才能进入本地热点缓存;
本地热点缓存对脏读要非常不敏感,比如商品名称在 MySQL 中的变更,这个变更没有更新到本地缓存中,这是 OK 的,用户下单时的商品名称和用户看到的商品名称不一样,这是 OK 的;
本地热点缓存内存要可控:
当商品信息在 MySQL 中变更时,Redis 中的过期数据只需清除即可;
但对本地热点缓存来说,很少有方法能清除掉 JVM 中的数据的,因为要清除 JVM 中的数据,需要每台应用服务器都清除掉数据才行,可以使用 MQ 做广播消息的发送来实现,但这种操作是得不偿失的;
本地热点缓存的生命周期不会特别长,比 Redis 中 key 的生命周期要短很多,这样才能做到被动失效造成的脏读非常少;
本地热点缓存的设计考量
本地热点缓存的本质其实就是维护一个 HashMap 的数据结构,但维护这个结构是不容易的:
首先要支持并发读写,ConcurrentHashMap 的性能不够理想;
要考虑失效时间和内存容量限制的淘汰机制,比如10分钟自动失效、先进先出、最少访问频次 LRU;
Guava Cache 的特点
Guava Cache 可控制大小和超时时间,本质上也是可支持并发的 HashMap;
Guava Cache 可配置 LRU(最近最少访问的 key 优先被淘汰) 的策略;
Guava Cache 是线程安全的;
基于 Cuava Cache 的本地热点缓存的实现
依赖
com.google.guava
guava
18.0
对本地缓存操作的封装
public interface CacheService {
void setCommonCache(String key, Object value);
Object getFromCommonCache(String key);
}
package com.lixinlei.miaosha.service.impl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.lixinlei.miaosha.service.CacheService;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Service
public class CacheServiceImpl implements CacheService {
private Cache commonCache = null;
@PostConstruct
public void init() {
commonCache = CacheBuilder.newBuilder()
.initialCapacity(10)
.maximumSize(100)
.expireAfterWrite(60, TimeUnit.SECONDS).build();
}
@Override
public void setCommonCache(String key, Object value) {
commonCache.put(key, value);
}
@Override
public Object getFromCommonCache(String key) {
return commonCache.getIfPresent(key);
}
}
Controller 层引入本地缓存逻辑
@RequestMapping(value = "/get", method = {RequestMethod.GET})
@ResponseBody
public CommonReturnType get(@RequestParam(name = "id") Integer id) {
ItemModel itemModel = null;
// 先从本地缓存中找
itemModel = (ItemModel)cacheService.getFromCommonCache("item_" + id);
if (itemModel == null) {
// 再从 Redis 中找
itemModel = (ItemModel)redisTemplate.opsForValue().get("item_" + id);
if (itemModel == null) {
// 最后从 MySQL 中找
itemModel = itemService.getItemById(id);
redisTemplate.opsForValue().set("item_" + id, itemModel);
redisTemplate.expire("item_" + id, 10, TimeUnit.MINUTES);
}
cacheService.setCommonCache("item_" + id, itemModel);
}
ItemVO itemVO = this.convertFromItemModel(itemModel);
return CommonReturnType.create(itemVO);
}
引入本地缓存性能提升
相对于 Redis 缓存,在本地缓存全部命中的情况下,TPS 从 2000 提升到 4000 +(2核8G);