redis緩存實踐

Redis 是一個由Salvatore Sanfilippo寫的key-value存儲系統(tǒng)蒋譬。Redis是一個開源的使用ANSI C語言編寫、遵守BSD協(xié)議鬼譬、支持網(wǎng)絡(luò)刃宵、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫韧献,并提供多種語言的API末患。它通常被稱為數(shù)據(jù)結(jié)構(gòu)服務(wù)器,因為值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等類型锤窑。

我們將redis作為mysql的緩存服務(wù)器璧针,每次請求數(shù)據(jù)都會去redis里查一遍,若沒有則請求mysql走真正的查詢再把數(shù)據(jù)存到redis渊啰。當(dāng)有新增探橱,修改,刪除操作的時候更新緩存中的數(shù)據(jù)以防止臟讀的發(fā)生绘证。

引入redis的依賴

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

配置文件

#redis
spring.redis.database=0
spring.redis.host=172.16.10.11
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1

redis緩存配置類

@Configuration
@EnableCaching  //開啟緩存
public class RedisCacheConfig {

    /**
     * 自定義key的生成規(guī)則,保證key的唯一性
     * @return
     */
    @Bean  
    public KeyGenerator wiselyKeyGenerator(){  
        return new KeyGenerator() {  
            @Override  
            public Object generate(Object target, Method method, Object... params) {  
                StringBuilder sb = new StringBuilder();  
                sb.append(target.getClass().getName());  
                sb.append(method.getName());  
                for (Object obj : params) {  
                    sb.append(obj.toString());  
                }  
                return sb.toString();  
            }  
        };  
  
    }  
    
    @Bean
    public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        //設(shè)置key的超時時間
        cacheManager.setDefaultExpiration(60*30);
        return cacheManager;
    }

    @Bean  
    public RedisTemplate<String, String> redisTemplate(  
            RedisConnectionFactory factory) {  
        StringRedisTemplate template = new StringRedisTemplate(factory);  
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);  
        ObjectMapper om = new ObjectMapper();  
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);  
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  
        jackson2JsonRedisSerializer.setObjectMapper(om);  
        template.setValueSerializer(jackson2JsonRedisSerializer);  
        template.afterPropertiesSet();  
        return template;  
    }  
}

封裝一個redis的操作類

@Component  
public class RedisClient {  
  
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 獲取key對應(yīng)list的長度
     * @param key
     * @return
     */
    public long getListSize(String key) {  
        Long size = stringRedisTemplate.opsForList().size(key);
        return size;
    }  
    
    /**
     * 根據(jù)key獲取list
     * @param key
     * @param i
     * @return
     */
    public String getListData(String key,int i) {  
        String json = stringRedisTemplate.opsForList().index(key,i);
        return json;
    }    
   
    /**
     * 逐條加入數(shù)據(jù)到key所在的list
     * @param key
     * @param json
     */
    public void setListData(String key,String json) {  
        stringRedisTemplate.opsForList().rightPush(key, json);
    }    
    
    /**
     * 修改index的數(shù)據(jù)
     * @param key
     * @param index
     * @param json
     */
    public void updateListData(String key, int index, String json) {  
        stringRedisTemplate.opsForList().set(key, index, json);
    }   
   
    /**
     * 獲取key的值
     * @param key
     * @return
     */
    public String getValue(String key){
        return stringRedisTemplate.opsForValue().get(key);
    }
    
    /**
     * 設(shè)置key的值
     * @param key
     * @param json
     */
    public void setValue(String key,String json){
        stringRedisTemplate.opsForValue().set(key, json);
    }
}  

寫一個簡單的controller

@Controller
@RequestMapping("/user")
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    private UserDao userDao;
         
    @RequestMapping(value = {"/{id}"}, method = RequestMethod.GET)
    @Cacheable(value="userCache",key= "'user' + #id" )
    @ResponseBody
        public User user(@PathVariable long id) {
        User u =  userDao.findOne(id);
        log.info("走db查詢");
        return u;
        }
}

關(guān)于Spring緩存的三個注解:
1.@Cacheable
@Cacheable是用來聲明方法是可緩存的隧膏。將結(jié)果存儲到緩存中以便后續(xù)使用相同參數(shù)調(diào)用時不需執(zhí)行實際的方法。直接從緩存中取值嚷那。最簡單的格式需要制定緩存名稱胞枕。
默認key生成:
默認key的生成按照以下規(guī)則:

  • 如果沒有參數(shù),則使用0作為key
  • 如果只有一個參數(shù),使用該參數(shù)作為key
  • 如果又多個參數(shù)魏宽,使用包含所有參數(shù)的hashCode作為key

也可以通過keyGenerator="wiselyKeyGenerator"自定義key的生成策略曲稼。

2.@CachePut
如果緩存需要更新,且不干擾方法的執(zhí)行,可以使用注解@CachePut湖员。@CachePut標(biāo)注的方法在執(zhí)行前不會去檢查緩存中是否存在之前執(zhí)行過的結(jié)果,而是每次都會執(zhí)行該方法瑞驱,并將執(zhí)行結(jié)果以鍵值對的形式存入指定的緩存中娘摔。

3.@CacheEvict
@CacheEvict要求指定一個或多個緩存,使之都受影響唤反。此外凳寺,還提供了一個額外的參數(shù)allEntries 。表示是否需要清除緩存中的所有元素彤侍。默認為false肠缨,當(dāng)指定了allEntries為true時,將會清除緩存中所有的元素盏阶。

首次訪問http://localhost:8080/user/1時會打日志走了db查詢晒奕,之后的每次訪問都沒有再打印db的日志。通過redis客戶端可以看到我們緩存的數(shù)據(jù)

緩存.png

更新緩存
controller里新建一個更新數(shù)據(jù)的方法,配合@CachePut更新緩存

@Controller
@RequestMapping("/user")
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    private UserDao userDao;
         
    @RequestMapping(value = {"/{id}"}, method = RequestMethod.GET)
    @Cacheable(value="userCache",key= "'user' + #id" )
    @ResponseBody
    public User user(@PathVariable long id) {
        User u =  userDao.findOne(id);
        log.info("走db查詢");
        return u;
        }

    @RequestMapping(value = {"/update/{id}"}, method = RequestMethod.GET)
    @CachePut(value= "userCache",key= "'user' + #id")
    @ResponseBody
    public User update(@PathVariable long id) {
        User user = userDao.findOne(id);
        user.setAge(10086);
        userDao.save(user);
        return user;
    }
}

訪問http://localhost:8080/user/update/1 更新數(shù)據(jù)同時更新了緩存

上述是針對單一一條數(shù)據(jù)的緩存脑慧,實際中也會有緩存列表數(shù)據(jù)的場景魄眉,我們看下列表數(shù)據(jù)要如何緩存和更新
在原來的controller上新增查詢列表的接口

    @RequestMapping(value = {""}, method = RequestMethod.GET)
    @ResponseBody
    public List<User> list() {
        List<User> dataList = new ArrayList<>();
        Long size = redisClient.getListSize("userList");

        if(0==size){
            dataList= userDao.findAll();
            log.info("FindAll  走db查詢");
            if (null != dataList) {
                for (int i = 0; i < dataList.size(); i++) {
                    redisClient.setListData("userList", JSON.toJSONString(dataList.get(i)));
                }
            }
        }else{
            log.info("FindAll  從redis中獲取");
            for (int i = 0; i < size; i++) {
                String json = redisClient.getListData("userList",i);
                User u = JSON.parseObject(json, User.class);  
                dataList.add(u);
            }
        }
        return dataList;
    }


    @RequestMapping(value = {"/update/{id}"}, method = RequestMethod.GET)
    @CachePut(value= "userCache",key= "'user' + #id")
    @ResponseBody
    public User update(@PathVariable long id) {
        User user = userDao.findOne(id);
        user.setAge(10086);
        userDao.save(user);
        Long size = redisClient.getListSize("userList");
        
        for (int j = 0; j < size; j++) {
            User u = JSON.parseObject(redisClient.getListData("userList", j), User.class);  
            if(id==u.getId()){
                redisClient.updateListData("userList", j, JSON.toJSONString(user));
                log.info("已更新緩存");
            }
        }
        
        return user;
    }

對于列表數(shù)據(jù)緩存的更新我還沒有想好一個更好的方式,先實現(xiàn)一種闷袒。

緩存的刪除

    @RequestMapping(value = {"/delete/{id}"})
    @CacheEvict(value = "userCache", allEntries = true)  
    @ResponseBody
    public String delete(@PathVariable long id) {
        userDao.delete(id);
        return "success";
    }

這樣實現(xiàn)了一個簡單的讀寫分離坑律,實際場景中還會更加嚴(yán)謹(jǐn),本文僅提供一個想法囊骤。

代碼gti地址:http://git.oschina.net/gpy1994/redisdemo

福利


雛田
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晃择,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子也物,更是在濱河造成了極大的恐慌宫屠,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焦除,死亡現(xiàn)場離奇詭異激况,居然都是意外死亡,警方通過查閱死者的電腦和手機膘魄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門乌逐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人创葡,你說我怎么就攤上這事浙踢。” “怎么了灿渴?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵洛波,是天一觀的道長。 經(jīng)常有香客問我骚露,道長蹬挤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任棘幸,我火速辦了婚禮焰扳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘误续。我一直安慰自己吨悍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布蹋嵌。 她就那樣靜靜地躺著育瓜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栽烂。 梳的紋絲不亂的頭發(fā)上躏仇,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天恋脚,我揣著相機與錄音,去河邊找鬼钙态。 笑死慧起,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的册倒。 我是一名探鬼主播蚓挤,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼驻子!你這毒婦竟也來了灿意?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤崇呵,失蹤者是張志新(化名)和其女友劉穎缤剧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體域慷,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡荒辕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了犹褒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抵窒。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叠骑,靈堂內(nèi)的尸體忽然破棺而出李皇,到底是詐尸還是另有隱情,我是刑警寧澤宙枷,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布掉房,位于F島的核電站,受9級特大地震影響慰丛,放射性物質(zhì)發(fā)生泄漏卓囚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一诅病、第九天 我趴在偏房一處隱蔽的房頂上張望哪亿。 院中可真熱鬧,春花似錦睬隶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至变勇,卻和暖如春恤左,著一層夾襖步出監(jiān)牢的瞬間贴唇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工飞袋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戳气,地道東北人。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓巧鸭,卻偏偏與公主長得像瓶您,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纲仍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容