多級緩存
應用場景:我們知道redis的tps讀寫能力在10w/s左右,在大促或者雙11場景,很多商品的訪問高達百萬千萬級別,如果只使用redis緩存,是不能滿足業務需要。
緩存混合存在問題
基於以上場景,我們需要使用多級緩存實現,利用本地緩存與redis緩存來實現:
- 本地緩存 ,使用ehcache來實現,ehcache作爲JVM級別的緩存,不能夠保證分佈式集羣部署一致性,無法實現分佈式場景下緩存共享;
- 本地緩存和分佈式redis緩存如何混合使用;
sboot代碼實現
(1) 配置文件配置, ehcache.xml網上有很多配置,可以根據實際需要配置
#encache 本地緩存
spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache.xml
(2) Springboot開啓Config配置
/
**
-
@author libiao
-
開啓本地緩存EnableCaching掃描spring.cache.ehcache.config
*/
@Configuration
@EnableCaching
public class CacheConfig {
@Resource
private CacheManager cacheManager;/**
- ehcache緩存處理
- @return
- @throws Exception
*/
@Bean(“ehcache”)
public Cache initEhcache()throws Exception{
return cacheManager.getCache(“userCache”);
}
}
(3) 業務代碼
//1、 從本地緩存獲取
Product product = ehcache.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null){
return product;
}
//2、從redis緩存獲取
product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null){
return product;
}
//3、增加商品的zk路徑監控,如下所示;
本地緩存一致性保證
本地緩存使用zookeeper保證,針對當前商品添加zk的path,如果商品信息發生變更通過zk的watch機制進行淘汰本地緩存
String zkMonitorProductPath = Constants.getZkMonitorProductPath(productId);
if (zooKeeper.exists(zkMonitorProductPath,true) == null){
//路徑不存在,則創建路徑,狀態爲true
zooKeeper.create(zkSoldOutProductPath, “true”.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//監聽zk節點某個商品狀態
zooKeeper.exists(zkSoldOutProductPath, true);
@Bean
public ZooKeeper initZookeeper()throws Exception{
ZookeeperWatcher zkWatcher = new ZookeeperWatcher();
ZooKeeper zooKeeper = new ZooKeeper(zookeeperAddress, 30000, zkWatcher);
zkWatcher.setZooKeeper(zooKeeper);
zkWatcher.setCache(cache); //見上cache配置
return zooKeeper;
}
/**zk淘汰本地緩存*/
public class ZookeeperWatcher implements Watcher {
private ZooKeeper zooKeeper;
private Cache cache;
public void setZooKeeper(ZooKeeper zooKeeper, Cache cache){
this.zooKeeper = zooKeeper;
this.cache = cache;
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.None && event.getPath() == null){
log.info("zookeeper connected success!");
//創建zk的商品標記根節點
try{
//App啓動時候創建標記root節點ZK_PRODUCT_MONITOR_FLAG
if (zooKeeper.exists(Constants.ZK_PRODUCT_MONITOR_FLAG, false) == null){
//創建根節點,無數據
zooKeeper.create(Constants.ZK_PRODUCT_SOLD_OUT_FLAG, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}catch (Exception e){
log.error("商品標記失敗", e);
}
}else if (event.getType() == Event.EventType.NodeDataChanged){
//zk目錄節點數據變化通知事件
try{
String path = event.getPath();
if (path.startsWith(Constants.ZK_PRODUCT_MONITOR_FLAG)) {
String monitorFlag = new String(zooKeeper.getData(path, true, new Stat()));
log.info("zookeeper 數據節點修改變動,path:{},value:{}", path, monitorFlag );
if (Constants.ZK_FALSE.equals(monitorFlag )) {
String productId = path.substring(path.lastIndexOf("/") + 1);
cache.evict(Constants.ZK_PRODUCT_MONITOR_FLAG+productId);
log.info("清除商品{}本地內存", productId);
}
}
}catch (Exception e){
log.error("zookeeper數據節點修改回調事件異常", e);
}
}
}
}