摘要:同樣,還有四實現(xiàn)原理結(jié)合以及實現(xiàn)首先,定義名稱前綴所需要包含的鍵值過期時間定義切面類,用于接受的響應(yīng)注冊到容器,必須加入這個注解該注解標示該類為切面類,切面是由通知和切點組成的。
一 什么是Cache 1 Cache
Cache通常意義上是指高速緩存,它與數(shù)據(jù)庫最大的區(qū)別是“更快”,可能會快上100倍,而且Cache是全部運行在內(nèi)存中,而數(shù)據(jù)庫中的數(shù)據(jù)一般都是存在硬盤中,而IO一直都是網(wǎng)站等大規(guī)模系統(tǒng)的瓶頸,如果不使用Cache,完全用數(shù)據(jù)庫,當(dāng)訪問量過大時將導(dǎo)致數(shù)據(jù)丟失,更嚴重時會導(dǎo)致系統(tǒng)崩潰,特別是遇到惡意攻擊的情況,所以緩存構(gòu)成了網(wǎng)絡(luò)的第一道防線。
當(dāng)用戶請求網(wǎng)絡(luò)資源時,會先訪問緩存中的數(shù)據(jù),如果緩存中沒有,再去訪問數(shù)據(jù)庫,請求返回給用戶的同時,更新到緩存中。而由于網(wǎng)絡(luò)請求的定律,80%的請求會集中在20%的數(shù)據(jù)上,所以緩存會極大的提高服務(wù)的響應(yīng)能力。
2 應(yīng)用場景針對數(shù)據(jù)庫的增、刪、查、改,數(shù)據(jù)庫緩存技術(shù)應(yīng)用場景絕大部分針對的是“查”的場景。比如,一篇經(jīng)常訪問的帖子/文章/新聞、熱門商品的描述信息、好友評論/留言等。因為在常見的應(yīng)用中,數(shù)據(jù)庫層次的壓力有80%的是查詢,20%的才是數(shù)據(jù)的變更操作。所以絕大部分的應(yīng)用場景的還是“查”緩存。當(dāng)然,“增、刪、改”的場景也是有的。比如,一篇文章訪問的次數(shù),不可能每訪問一次,我們就去數(shù)據(jù)庫里面加一次吧?這種時候,我們一般“增”場景的緩存就必不可少。否則,一篇文章被訪問了十萬次,代碼層次不會還去做十萬次的數(shù)據(jù)庫操作吧。
3 應(yīng)用舉例讀操作流程
有了數(shù)據(jù)庫和緩存兩個地方存放數(shù)據(jù)之后(uid->money),每當(dāng)需要讀取相關(guān)數(shù)據(jù)時(money),操作流程一般是這樣的:
(1)讀取緩存中是否有相關(guān)數(shù)據(jù),uid->money
(2)如果緩存中有相關(guān)數(shù)據(jù)money,則返回【這就是所謂的數(shù)據(jù)命中“hit”】
(3)如果緩存中沒有相關(guān)數(shù)據(jù)money,則從數(shù)據(jù)庫讀取相關(guān)數(shù)據(jù)money【這就是所謂的數(shù)據(jù)未命中“miss”】,放入緩存中uid->money,再返回
二 使用spring中的annotation極大的方便了緩存操作,加上annotation就能夠自動實現(xiàn)redis的讀取、更新等策略。
比如
@Cacheable(value="users") public User findByUsername(String username)
此時,會首先在redis中查 users:username 中構(gòu)成的鍵值,比如username:張三,那么會到redis中查key="users:張三",如果redis中沒有,會到數(shù)據(jù)庫中去查,查好后返回前端,同時將數(shù)據(jù)更新到redis。
這是默認key的情況,當(dāng)然,也可以手動加上key,比如
@Cacheable(value="users",key="#username") public User findByUsername(String username,String gender)
此時,會按照key表達式的值,去參數(shù)里面去取,這里同樣,key="users:張三"
此外,還有
@Cacheput(value="users",key="#username") public int insertUser(User user)
會首先查數(shù)據(jù),然后更新數(shù)據(jù)到redis中,key="users:user.username"
三 改進項目中存在這樣的問題,有的對象有30+字段,但需要緩存的只有3個,查如果全部都存到redis中,無疑將加大redis的負擔(dān),能否指定字段呢?其實可以專門定義一個視圖對象,里面只存放需要的字段,用來返回,但一來加大了工作量,導(dǎo)致代碼膨脹,二來put還是沒法操作,所以我們寫了各自定義注解,用來指定redis中的存儲字段。
使用方式如下
@RedisCacheAble(value="users",names={"name","gender","age"}) public User findByUsername(String username)
如此一來就能只保存User對象中的name,gender,age屬性,其它屬性為null,減少了redis中對象的存儲大小。
同樣,還有cacheput
@RedisCachePut(value="users",key="#username",names={"name","gender","age"}) public int insertUser(User user)四 實現(xiàn)原理
結(jié)合annotation以及aop實現(xiàn)
首先,定義annotataion
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface RedisCacheAble { String value() default ""; //key名稱、前綴 String[] names() default {}; //所需要包含的鍵值 long timeout() default 30; //過期時間 }
定義切面類,用于接受annotation的響應(yīng)
@Component // 注冊到Spring容器,必須加入這個注解 @Aspect // 該注解標示該類為切面類,切面是由通知和切點組成的。 public class ApiAspect { @Pointcut("@annotation(cn.com.spdbccc.hotelbank.rediscache.RedisCacheAble)")// 定義注解類型的切點,只要方法上有該注解,都會匹配 public void annotationAble(){ } @Around("annotationAble()&& @annotation(rd)") //定義注解的具體實現(xiàn),以及能夠接受注解對象,定義 @annotation(rd)就可以直接取到annotation的實例了 public Object redisCacheAble(ProceedingJoinPoint joinPoint, RedisCacheAble rd) throws Throwable { String preKey = rd.value(); String arg0 = joinPoint.getArgs()[0].toString(); //TODO arg0判斷 String key = preKey + ":" +arg0; //如果redis中已經(jīng)有值,直接返回 Object rtObject = redisTemplate.opsForValue().get(key); if (rtObject != null) { return rtObject; } // 執(zhí)行函數(shù),如果返回值為空,返回 Object sourceObject = joinPoint.proceed(); if (sourceObject == null) { return null; } // 根據(jù)values獲取object里的值,并生成用于redis存儲的對象 Class cl = sourceObject.getClass(); // 插入數(shù)據(jù)庫成功 // 如果values沒有值,那么redis對應(yīng)的value為輸入對象;否則根據(jù)輸入?yún)?shù)重新生成對象 if (rd.names() == null) { // 存入目標對象 redisTemplate.opsForValue().set(key, sourceObject,rd.timeout(),TimeUnit.MINUTES); } else { // 將目標對象特定字段存入redis Object targetObject = cl.newInstance(); for (String name : rd.names()) { try { // 生成值到新的對象中 String upChar = name.substring(0, 1).toUpperCase(); String getterStr = "get" + upChar + name.substring(1); Method getMethod = cl.getMethod(getterStr, new Class[] {}); Object objValue = getMethod.invoke(sourceObject, new Object[] {}); String setterStr = "set" + upChar + name.substring(1); Method setMethod = cl.getMethod(setterStr, String.class); setMethod.invoke(targetObject, objValue); } catch (Exception e) { logger.error(e.getMessage(), e); } } // 存入目標對象,key=類名:keyvalue redisTemplate.opsForValue().set(key, targetObject,rd.timeout(),TimeUnit.MINUTES); } return sourceObject; }五 項目規(guī)范化
我們使用了參數(shù):
@RedisCachePut(value="users",key="#username",names={"name","gender","age"})
但在實際的使用中要求用常量來表示key前綴,比如
public final static String PRE_USER="users"
字符串固然是沒有問題,數(shù)組貌似是沒有辦法用常量來定義的,PRE_USERS={"user1","user2"},此時會報編譯錯誤,解決方式就是直接使用String類型了,而后在具體的切面處理函數(shù)中再轉(zhuǎn)成字符串。
六 分布式中使用在分布式系統(tǒng)中redis中對象序列化、反序列化無法跨服務(wù),即使對于同一個類名,在不同的服務(wù)中,是無法反序列化出來的,必須存儲為純String類型,所以新加了個轉(zhuǎn)換器
public class StringJackson2JsonSerializerextends Jackson2JsonRedisSerializer { private ObjectMapper objectMapper = new ObjectMapper(); public StringJackson2JsonSerializer(Class type) { super(type); // TODO Auto-generated constructor stub } public byte[] serialize(Object t) throws SerializationException { if (t == null) { return new byte[0]; } try { //將對象轉(zhuǎn)為Json String然后再序列化,方便跨服務(wù) return this.objectMapper.writeValueAsBytes(JacksonUtil.objToJson(t)); } catch (Exception ex) { throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex); } }
這個轉(zhuǎn)換器能夠?qū)⑺械膋ey-value的value存為string類型,這樣就解決的跨服務(wù)對象傳輸?shù)膯栴}
七 優(yōu)化 1 縮減存儲字段在redis中直接存儲為String就可以了,所以只要把字段挑出來,存儲為HashMap就可以了,所以將代碼優(yōu)化下
Map jsonMap = new HashMap2 重定義RedisTemplate(); ...... jsonRedisTemplate.opsForValue().set(key, jsonMap);
分布式存儲需考慮存入json字符串,而原生則不能,而有些情況必須使用底層的RedisTemplate,所以必須定義一個xxTemplate來專職處理該情況,包括hash,set等。
//用來專門處理需要以json字符串存入redis中的redistemplate
@Bean public RedisTemplatejsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate (); redisTemplate.setConnectionFactory(redisConnectionFactory); //序列化、反序列化,使用原始的json string存儲到redis,方便跨服務(wù) StringJackson2JsonSerializer
我們專門定義了一個StringJackson2JsonSerializer來處理redis的序列化,在序列化前將對象轉(zhuǎn)為string
public class StringJackson2JsonSerializerextends Jackson2JsonRedisSerializer { private ObjectMapper objectMapper = new ObjectMapper(); public StringJackson2JsonSerializer(Class type) { super(type); // TODO Auto-generated constructor stub } public byte[] serialize(Object t) throws SerializationException { if (t == null) { return new byte[0]; } try { //將對象轉(zhuǎn)為Json String然后再序列化,方便跨服務(wù) return this.objectMapper.writeValueAsBytes(JacksonUtil.objToJson(t)); } catch (Exception ex) { throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex); } }
}
3 關(guān)鍵操作異步入庫領(lǐng)導(dǎo)又要求將redis的關(guān)鍵操作,比如說存的操作存到數(shù)據(jù)庫,因此要將一些操作記錄到數(shù)據(jù)庫,此時顯然不能直接存數(shù)據(jù)庫,造成額外的開銷,所以需要使用消息隊列
八 Git地址歡迎使用、拍磚:https://github.com/vvsuperman...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/67829.html
摘要:至此,已完成整合獨立模塊做緩存詳情請看地址相關(guān)文章系列整合獨立模塊 項目github地址:https://github.com/5-Ason/aso...具體可看 ./db/db-redis 和 ./db/db-cache 兩個模塊 // TODO 在整合redis之前需要先本地配置好redis環(huán)境,遲點有時間補一下linux下下載安裝配置redis 本文主要實現(xiàn)的是對數(shù)據(jù)操作進行獨立...
摘要:至此,已完成整合獨立模塊做緩存詳情請看地址相關(guān)文章系列整合獨立模塊 項目github地址:https://github.com/5-Ason/aso...具體可看 ./db/db-redis 和 ./db/db-cache 兩個模塊 // TODO 在整合redis之前需要先本地配置好redis環(huán)境,遲點有時間補一下linux下下載安裝配置redis 本文主要實現(xiàn)的是對數(shù)據(jù)操作進行獨立...
摘要:本文以一個簡單的接口根據(jù)用戶工號獲取用戶信息為例,介紹的使用。創(chuàng)建工程打開生成一個標準工程因為需要支持需要輸入,提供對的支持。創(chuàng)建項目選擇支持將壓縮包中目錄覆蓋項目目錄將項目替換為壓縮包中的文件。 背景 想想,微服務(wù)這概念在當(dāng)初剛從業(yè)時就聽過,那時也只是停留在概念上,缺少技術(shù)支撐,或者說沒有公認完美的技術(shù)支撐。docker的出現(xiàn),給微服務(wù)提供了平臺支持,spring cloud的出現(xiàn)給...
閱讀 2946·2021-11-22 15:25
閱讀 2240·2021-11-18 10:07
閱讀 1045·2019-08-29 15:29
閱讀 472·2019-08-29 13:25
閱讀 1504·2019-08-29 12:58
閱讀 3201·2019-08-29 12:55
閱讀 2911·2019-08-29 12:28
閱讀 500·2019-08-29 12:16