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