自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

張開(kāi)濤:京東業(yè)務(wù)數(shù)據(jù)應(yīng)用級(jí)緩存示例

開(kāi)發(fā) 開(kāi)發(fā)工具
我們的業(yè)務(wù)數(shù)據(jù)如商品類(lèi)目、店鋪、商品基本信息都可以進(jìn)行適當(dāng)?shù)谋镜鼐彺妫蕴嵘阅?。?duì)于多實(shí)例的情況時(shí)不僅會(huì)使用本地緩存,還會(huì)使用分布式緩存,因此需要進(jìn)行適當(dāng)?shù)腁PI封裝以簡(jiǎn)化緩存操作。

一、多級(jí)緩存API封裝

我們的業(yè)務(wù)數(shù)據(jù)如商品類(lèi)目、店鋪、商品基本信息都可以進(jìn)行適當(dāng)?shù)谋镜鼐彺?,以提升性能。?duì)于多實(shí)例的情況時(shí)不僅會(huì)使用本地緩存,還會(huì)使用分布式緩存,因此需要進(jìn)行適當(dāng)?shù)腁PI封裝以簡(jiǎn)化緩存操作。

[[189271]]

1. 本地緩存初始化

  1. public class LocalCacheInitService extends BaseService { 
  2.    @Override 
  3.     publicvoid afterPropertiesSet() throws Exception { 
  4.         //商品類(lèi)目緩存 
  5.         Cache<String, Object> categoryCache = 
  6.                CacheBuilder.newBuilder() 
  7.                         .softValues() 
  8.                         .maximumSize(1000000) 
  9.                        .expireAfterWrite(Switches.CATEGORY.getExpiresInSeconds()/ 2, TimeUnit.SECONDS) 
  10.                         .build(); 
  11.        addCache(CacheKeys.CATEGORY_KEY, categoryCache); 
  12.     } 
  13.   
  14.     privatevoid addCache(String key, Cache<??> cache) { 
  15.         localCacheService.addCache(key,cache); 
  16.     } 

本地緩存過(guò)期時(shí)間使用分布式緩存過(guò)期時(shí)間的一半,防止本地緩存數(shù)據(jù)緩存時(shí)間太長(zhǎng)造成多實(shí)例間的數(shù)據(jù)不一致。

另外,將緩存KEY前綴與本地緩存關(guān)聯(lián),從而匹配緩存KEY前綴就可以找到相關(guān)聯(lián)的本地緩存。

2. 寫(xiě)緩存API封裝

先寫(xiě)本地緩存,如果需要寫(xiě)分布式緩存,則通過(guò)異步更新分布式緩存。

  1. public void set(final String key, final Object value, final intremoteCacheExpiresInSeconds) throws RuntimeException { 
  2.     if (value== null) { 
  3.         return; 
  4.     } 
  5.   
  6.     //復(fù)制值對(duì)象 
  7.     //本地緩存是引用,分布式緩存需要序列化 
  8.     //如果不復(fù)制的話,則假設(shè)之后數(shù)據(jù)改了將造成本地緩存與分布式緩存不一致 
  9.     final Object finalValue = copy(value); 
  10.     //如果配置了寫(xiě)本地緩存,則根據(jù)KEY獲得相關(guān)的本地緩存,然后寫(xiě)入 
  11.     if (writeLocalCache) { 
  12.        Cache localCache = getLocalCache(key); 
  13.         if(localCache != null) { 
  14.            localCache.put(key, finalValue); 
  15.         } 
  16.     } 
  17.     //如果配置了不寫(xiě)分布式緩存,則直接返回 
  18.     if (!writeRemoteCache) { 
  19.         return; 
  20.     } 
  21.     //異步更新分布式緩存 
  22.     asyncTaskExecutor.execute(() -> { 
  23.         try { 
  24.             redisCache.set(key,JSONUtils.toJSON(finalValue), remoteCacheExpiresInSeconds); 
  25.         } catch(Exception e) { 
  26.             LOG.error("updateredis cache error, key : {}", key, e); 
  27.         } 
  28.     }); 

此處使用了異步更新,目的是讓用戶(hù)請(qǐng)求盡快返回。而因?yàn)橛斜镜鼐彺?,所以即使分布式緩存更新比較慢又產(chǎn)生了回源,也可以在本地緩存***。

3. 讀緩存API封裝

先讀本地緩存,本地緩存不***的再批量查詢(xún)分布式緩存,在查詢(xún)分布式緩存時(shí)通過(guò)分區(qū)批量查詢(xún)。

  1. private Map innerMget(List<String> keys, List<Class> types) throwsException { 
  2.    Map<String, Object> result = Maps.newHashMap(); 
  3.    List<String> missKeys = Lists.newArrayList(); 
  4.    List<Class> missTypes = Lists.newArrayList(); 
  5.     //如果配置了讀本地緩存,則先讀本地緩存 
  6.     if(readLocalCache) { 
  7.         for(int i = 0; i < keys.size(); i++) { 
  8.            String key = keys.get(i); 
  9.            Class type = types.get(i); 
  10.            Cache localCache = getLocalCache(key); 
  11.             if(localCache != null) { 
  12.                Object value = localCache.getIfPresent(key); 
  13.                result.put(key, value); 
  14.                if (value == null) { 
  15.                    missKeys.add(key); 
  16.                     missTypes.add(type); 
  17.                } 
  18.            } else { 
  19.                missKeys.add(key); 
  20.                missTypes.add(type); 
  21.            } 
  22.         } 
  23.     } 
  24.     //如果配置了不讀分布式緩存,則返回 
  25.     if(!readRemoteCache) { 
  26.         returnresult; 
  27.     } 
  28.     finalMap<String, String> missResult = Maps.newHashMap(); 
  29.   
  30.     //對(duì)KEY分區(qū),不要一次性批量調(diào)用太大 
  31.     final List<List<String>>keysPage = Lists.partition(missKeys, 10); 
  32.    List<Future<Map<String, String>>> pageFutures = Lists.newArrayList(); 
  33.   
  34.     try { 
  35.         //批量獲取分布式緩存數(shù)據(jù) 
  36.         for(final List<String>partitionKeys : keysPage) { 
  37.            pageFutures.add(asyncTaskExecutor.submit(() -> redisCache.mget(partitionKeys))); 
  38.         } 
  39.         for(Future<Map<String,String>> future : pageFutures) { 
  40.            missResult.putAll(future.get(3000, TimeUnit.MILLISECONDS)); 
  41.         } 
  42.     } catch(Exception e) { 
  43.        pageFutures.forEach(future -> future.cancel(true)); 
  44.         throw e; 
  45.     } 
  46.     //合并result和missResult,此處實(shí)現(xiàn)省略 
  47.     return result; 

此處將批量讀緩存進(jìn)行了分區(qū),防止亂用批量獲取API。

二、NULL Cache

首先,定義NULL對(duì)象。

  1. private static final String NULL_STRING =new String(); 

當(dāng)DB沒(méi)有數(shù)據(jù)時(shí),寫(xiě)入NULL對(duì)象到緩存

  1. //查詢(xún)DB 
  2. String value = loadDB(); 
  3. //如果DB沒(méi)有數(shù)據(jù),則將其封裝為NULL_STRING并放入緩存 
  4. if(value == null) { 
  5.     value = NULL_STRING
  6. myCache.put(id, value); 

讀取數(shù)據(jù)時(shí),如果發(fā)現(xiàn)NULL對(duì)象,則返回null,而不是回源到DB

  1. value = suitCache.getIfPresent(id); 
  2. //DB沒(méi)有數(shù)據(jù),返回null 
  3. if(value == NULL_STRING) { 
  4.     return null; 

通過(guò)這種方式可以防止當(dāng)KEY對(duì)應(yīng)的數(shù)據(jù)在DB不存在時(shí)頻繁查詢(xún)DB的情況。

三、強(qiáng)制獲取***數(shù)據(jù)

在實(shí)際應(yīng)用中,我們經(jīng)常需要強(qiáng)制更新數(shù)據(jù),此時(shí)就不能使用緩存數(shù)據(jù)了,可以通過(guò)配置ThreadLocal開(kāi)關(guān)來(lái)決定是否強(qiáng)制刷新緩存(refresh方法要配合CacheLoader一起使用)。

  1. if(ForceUpdater.isForceUpdateMyInfo()) { 
  2.     myCache.refresh(skuId); 
  3. String result = myCache.get(skuId); 
  4. if(result == NULL_STRING) { 
  5.     return null; 

四、失敗統(tǒng)計(jì)

  1. private LoadingCache<String, AtomicInteger> failedCache = 
  2.        CacheBuilder.newBuilder() 
  3.                .softValues() 
  4.                .maximumSize(10000) 
  5.                .build(new CacheLoader<String, AtomicInteger>() { 
  6.                    @Override 
  7.                     public AtomicIntegerload(String skuId) throws Exception { 
  8.                         return new AtomicInteger(0); 
  9.                    } 
  10.                }); 

當(dāng)失敗時(shí),通過(guò)failedCache.getUnchecked(id).incrementAndGet()增加失敗次數(shù);當(dāng)成功時(shí),使用failedCache.invalidate(id)失效緩存。通過(guò)這種方式可以控制失敗重試次數(shù),而且又是內(nèi)存敏感緩存。當(dāng)內(nèi)存不足時(shí),可以清理該緩存騰出一些空間。

五、延遲報(bào)警

  1. private static LoadingCache<String, Integer> alarmCache = 
  2.        CacheBuilder.newBuilder() 
  3.                 .softValues() 
  4.                .maximumSize(10000).expireAfterAccess(1, TimeUnit.HOURS) 
  5.                .build(new CacheLoader<String, Integer>() { 
  6.                    @Override 
  7.                    public Integer load(String key) throws Exception { 
  8.                         return 0; 
  9.                    } 
  10.                }); 
  11.   
  12. //報(bào)警代碼 
  13. Integer count = 0
  14. if(redis != null) { 
  15.     StringcountStr = Objects.firstNonNull(redis.opsForValue().get(key), "0"); 
  16.     count =Integer.valueOf(countStr); 
  17. } else { 
  18.     count = alarmCache.get(key); 
  19. if(count % 5 == 0) { //5次報(bào)一次 
  20.     //報(bào)警 
  21. countcount = count + 1; 
  22. if(redis != null) { 
  23.     redis.opsForValue().set(key,String.valueOf(count), 1, TimeUnit. HOURS); 
  24. } else { 
  25.     alarmCache.put(key,count); 

如果一出問(wèn)題就報(bào)警,則存在報(bào)警量非常多或者假報(bào)警,因此,可以考慮N久報(bào)警了M次,才真正報(bào)警。此時(shí),也可以使用Cache來(lái)統(tǒng)計(jì)。本示例還加入了Redis分布式緩存記錄支持。

六、性能測(cè)試

筆者使用JMH 1.14進(jìn)行基準(zhǔn)性能測(cè)試,比如測(cè)試寫(xiě)。

  1. @Benchmark 
  2. @Warmup(iterations = 10time = 10timeUnit =TimeUnit.SECONDS) 
  3. @Measurement(iterations = 10time = 10timeUnitTimeUnit.SECONDS) 
  4. @BenchmarkMode(Mode.Throughput) 
  5. @OutputTimeUnit(TimeUnit.SECONDS) 
  6. @Fork(1) 
  7. public void test_1_Write() { 
  8.     counterWritercounterWriter= counterWriter + 1; 
  9.     myCache.put("key"+ counterWriter, "value" + counterWriter); 

使用JMH時(shí)首先進(jìn)行JVM預(yù)熱,然后進(jìn)行度量,產(chǎn)生測(cè)試結(jié)果(本文使用吞吐量)。建議讀者按照需求進(jìn)行基準(zhǔn)性能測(cè)試來(lái)選擇適合自己的緩存框架。

【本文是51CTO專(zhuān)欄作者張開(kāi)濤的原創(chuàng)文章,作者微信公眾號(hào):開(kāi)濤的博客( kaitao-1234567)】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來(lái)源: 51CTO專(zhuān)欄
相關(guān)推薦

2017-05-01 17:03:01

Java緩存分布式

2017-05-05 10:13:03

應(yīng)用級(jí)緩存緩存代碼

2017-05-10 11:40:29

緩存Nginx HTTP

2017-05-18 16:07:23

回滾數(shù)據(jù)庫(kù)代碼

2017-04-18 14:49:38

應(yīng)用層API代碼

2017-06-16 15:16:15

2017-06-04 16:24:27

線程線程池中斷

2017-07-02 16:50:21

2012-12-13 17:38:48

2012年度IT博客大IT博客大賽博客

2016-01-04 15:16:01

京東詳情頁(yè)實(shí)踐

2016-06-17 14:19:52

數(shù)據(jù)中心

2010-06-02 17:46:54

MySQL 查詢(xún)緩存

2022-05-12 14:34:14

京東數(shù)據(jù)

2015-07-24 12:38:00

吳靜濤

2010-10-19 08:59:40

PHP緩存技術(shù)

2016-11-10 14:38:44

京東深度學(xué)習(xí)

2024-11-01 10:37:31

2009-08-13 17:50:49

Hibernate 3

2018-01-18 19:11:36

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)