1. 程式人生 > >關於Redis入門及透明(透明即使用AOP實現低耦合)快取層實現

關於Redis入門及透明(透明即使用AOP實現低耦合)快取層實現

emm...博主在前些天學習了一下Redis,以及在網上查詢了部分資料、將看到的大牛們寫的相關文章整合了一下。。(如有侵權多見諒...)分享一些經驗,可能寫的或者瞭解的不是很透徹,哈哈...大牛們請無視

那麼在講解之前我們先來簡單的瞭解一下Redis

一、什麼是Redis

  • Redis是一個快取 ( 快取的優勢在於它的速度遠遠高於硬碟執行速度) 伺服器   ——高效能系統必備技術之一
  • Redis是一個開源的、使用ANSI C語言編寫的、支援網路互動的、遵循BSD協議、可基於記憶體也可持久化的Key-Value資料庫

二、Redis的優點:

  • 因為是純記憶體操作,Redis的效能非常出色,Redis的速度每秒可執行大約 110000 
    次的設定(SET)操作,每秒大約可執行 81000 次的讀取/獲取(GET)操作。
  • Redis可以支援五種資料型別:string(字串),hash(雜湊),list(列表),set(集合)及zset(sorted set:有序集合)     由於開發人員常用的大多數資料型別,例如列表,集合,排序集和雜湊等等。這使得Redis很容易被用來解決各種問題,因為我們知道哪些問題可以更好使用地哪些資料型別來處理解決。
  • 操作具有原子性所有Redis操作都是原子操作,這確保如果兩個客戶端併發訪問,Redis伺服器能接收更新的值,避免接收異常資料。
  • 支援資料持久化,支援AOF和RDB兩種持久化方式
  • Redis是一個多實用工具,可用於多種用例,如:快取,訊息佇列(Redis本地支援釋出/訂閱),應用程式中的任何短期資料,例如,web應用程式中的會話,網頁命中計數等
  • 此外單個value的最大限制是1GB,不像 memcached只能儲存1MB的資料,因此Redis可以用來實現很多有用的功能,比方說用他的List來做FIFO雙向連結串列,實現一個輕量級的高性 能訊息佇列服務,用他的Set可以做高效能的tag系統等等。另外Redis也可以對存入的Key-Value設定expire時間,因此也可以被當作一 個功能加強版的memcached來用。

缺點:

  • 資料庫容量受到實體記憶體的限制,不能用作海量資料的高效能讀寫,因此Redis適合的場景主要侷限在較小資料量的高效能操作和運算上。
  • Redis比較難線上擴容,在叢集的容量上限時,線上擴容會變得很複雜。

 三、Redis與其他鍵值儲存系統相比:

  • Redis是鍵值資料庫系統的不同進化路線,它的值可以包含更復雜的資料型別,可在這些資料型別上定義原子操作。
  • Redis是一個記憶體資料庫,但在磁碟資料庫上是持久的,因此它代表了一個不同的權衡,在這種情況下,在不能大於儲存器(記憶體)的資料集的限制下實現非
  • 常高的寫和讀速度。
  • 記憶體資料庫的另一個優點是,它與磁碟上的相同資料結構相比,複雜資料結構在記憶體中儲存表示更容易操作。 因此,Redis可以做很少的內部複雜性。

四、Redis執行流程 

  • 業務層所有指定的查詢方法,都要先去快取去查詢,如果有,就直接返回快取的資料(物件);如果沒有,就去資料庫查詢,並且把資料庫中查到的資料,放入快取中;
  • 刪除、修改:判斷Redis快取中key是否存在,如果有,則刪除快取中的資料(物件);如果沒有,則直接執行。這裡修改語句的話博主認為不需要在快取中去修改,而選擇直接刪除的原因是因為,在使用者修改完之後可能並不需要立即使用,並且多了許多程式碼所產生的收益微乎其微(還可以偷懶..)。
  • 增:不必要提前查出來放到快取中或者進行key的非空驗證,所以在下面的程式碼中博主沒有做增加的環繞增強

 

5、Redis的運用場景

  • 比如說資料高併發的讀寫
  • 需要進行大量資料的讀寫
  • 對擴充套件性要求高的資料
     

一些實現細節大家可以看圖來理解

下面來看一下相關的實現程式碼。

實體類以及Dao層、Service層操作省略

Bean配置 

Spring-Beans.xml內程式碼

<?xml version="1.0" encoding="UTF-8"?>

       <aop:aspectj-autoproxy/> //宣告自動為spring容器中那些配置@aspectJ切面的bean建立代理,織入切面

       <context:annotation-config/>// 是用於啟用那些已經在spring容器裡註冊過的bean(無論是通過xml的方式還是通過package sanning的方式)上面的註解。

       <context:component-scan base-package="com.landun.lichenming"/>//  在xml配置了這個標籤後,spring可以自動去掃描base-pack下面或者子包下面的java檔案,如果掃描到有@Component @[email protected]等這些註解的類,則把這些類註冊為bean

<!-- 配置Jedis -->

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">

        <property name="maxActive" value="20"/>//連線池最大的連線數

        <property name="maxIdle" value="10"/> //連線池中最多可空閒maxIdle個連線 ,這裡取值為10,表示即使沒有資料庫連線時依然可以保持10空閒的連線,而不被清除,隨時處於待命狀態。設 0 為沒有限制。minIdle 表示 連線池中最少空閒maxIdle個連線

        <property name="maxWait" value="1000"/>// maxWait 連線池中連線用完時,新的請求等待時間,毫秒,取值-1,表示無限等待,直到超時為止,也可取值9000,表示9秒後超時。超過時間會出錯誤資訊一般把maxActive設定成可能的併發量就行了

        <property name="testOnBorrow" value="true"/>// 從連線池獲取一個連線時,驗證有效性

    </bean>

    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">

        <constructor-arg index="0" ref="jedisPoolConfig" />

        <constructor-arg index="1" value="127.0.0.1" />

        <constructor-arg index="2" value="6379" />

        <!-- timeout -->

        <constructor-arg index="3" value="2000" />

        <!-- password -->

        <constructor-arg index="4" value="62416038626259878353394562797" />

    </bean>

<!--匯入spring-mybatis.xml-->

<import resource="spring-mybatis.xml"/>

</bean>

配置mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

<configuration>

       <settings>

             <setting name="lazyLoadingEnabled" value="false"/>//延遲載入的開關,即懶載入

為什麼使用懶載入:因為記憶體容量有限 ,為了減少併發量,減少系統資源的消耗,

       </settings>

       <typeAliases>

             <package name="com.landun.lichenming.entity"/>//對映實體類

       </typeAliases>

       <mappers>

             <mapper resource="com/landun/lichenming/dao/UserMapper.xml"/>//對映dao內的對映檔案

       </mappers>

</configuration>

配置RedisCache(進行jedis的查詢,刪除等操作)

package com.landun.lichenming.cache;

import javax.annotation.Resource;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

@Component

public class RedisCache {

    @Resource

    private JedisPool jedisPool;

    public Object getFromCache(String key) {

        Jedis jedis = jedisPool.getResource();

        byte[] result = jedis.get(key.getBytes());

        // 如果查詢沒有為空

        if (null == result) {

            return null;

        }

        // 查詢到了,反序列化

        return SerializeUtil.unSerialize(result);

    }

    // 將資料庫中查詢到的資料放入redis

    public void save(String redisKey, Object obj) {

        // 序列化

        byte[] bytes = SerializeUtil.serialize(obj);

        // 存入redis

        Jedis conn = jedisPool.getResource();

        String result = conn.set(redisKey.getBytes(), bytes);

        if ("OK".equals(result)) {

            System.out.println("[RedisCache]:資料成功儲存到redis cache...");

        }

    }

    // 將資料庫查詢到的key刪除

    public void del(String key) {

        Jedis jedis = jedisPool.getResource();

        if (jedis.exists(key.getBytes())) {

            jedis.del(key.getBytes());

            if (jedis.exists(key.getBytes())) {

                System.out.println(" [RedisCache] :資料刪除失敗!!!請聯絡程式設計師");

            } else {

                System.out.println(" [RedisCache] :資料刪除成功");

            }

        } else {

            System.out.println(" [RedisCache] :記憶體資料不存在");

        }

    }

}

配置SerializeUtil(對傳進來的資料進行序列化與反序列化操作)

package com.landun.lichenming.cache;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class SerializeUtil {

    //序列化

    public static byte[] serialize(Object obj){

        ObjectOutputStream obi=null;

        ByteArrayOutputStream bai=null;

        try {

            bai=new ByteArrayOutputStream();

            obi=new ObjectOutputStream(bai);

            obi.writeObject(obj);

            byte[] byt=bai.toByteArray();

            return byt;

        } catch (IOException e) {

            e.printStackTrace();

        }

        return null;

    }

    //反序列化

    public static Object unSerialize(byte[] byt){

        ObjectInputStream oii=null;

        ByteArrayInputStream bis=null;

        bis=new ByteArrayInputStream(byt);

        try {

            oii=new ObjectInputStream(bis);

            Object obj=oii.readObject();

            return obj;

        } catch (Exception e) {

            e.printStackTrace();

        }

        return null;

    }

}

定義切面

package com.landun.lichenming.cache;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 該類表示一個切面,表示: “呼叫find*方法根據id查某個物件時,先從快取查詢,如果沒有,再查資料庫”
 * 
 */
@Component
// 使用Aspect註解將RedisCacheAspect定義為切面
@Aspect
public class RedisCacheAspect {

    private static String PROJECT_NAME = "REDIS-";
    private static String MODULE_NAME = "AUTH-";

    private static final Logger log = Logger.getLogger(RedisCacheAspect.class);

    @Resource
    private RedisCache redisCache;

    // 定義切入點
    @Pointcut("execution(* com.landun.lichenming.service..*Service.find*(..))")
    public void getCachePointcut() {
    }

    @Around("getCachePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {

        // 先獲取目標方法引數
        String id = null;
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            id = String.valueOf(args[0]);
        }

        // 得到一個具有唯一性的快取key
        String redisKey = getRedisKey(joinPoint, id);
        log.debug("進入環繞增強前半部分,嘗試從redis查詢,生成的快取key:" + redisKey);
        // 根據key獲取從redis中查詢到的物件
        Object data = redisCache.getFromCache(redisKey);
        // 如果查詢到了
        if (null != data) {
            log.debug("[" + redisKey + "]快取命中:" + data);
            return data;
        }

        // 沒有查到,那麼查詢資料庫
        try {
            data = joinPoint.proceed();// 執行目標方法(service中的find*方法)
        } catch (Throwable e) {
            e.printStackTrace();
        }
        log.debug("沒有從redis中取出資料,改為從資料庫中查詢的資料..." + data);

        // 後置:將資料庫中查詢的資料放到redis中
        redisCache.save(redisKey, data);
        log.debug("資料已儲存到redis" + data);

        // 將查詢到的資料返回
        return data;

    }

    // 定義切入點
    @Pointcut("execution(* com.landun.lichenming.service..*Service.update*(..))")
    public void updateCachePointcut() {
    }

    @Pointcut("execution(* com.landun.lichenming.service..*Service.delete*(..))")
    public void deleteCachePointcut() {
    }

    @Around("updateCachePointcut() or deleteCachePointcut()")
    public Object UpdateAnddelete(ProceedingJoinPoint joinPoint) {

        // 先獲取目標方法引數
        String id = null;
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            id = String.valueOf(args[0]);
        }

        // 得到一個具有唯一性的快取key
        String redisKey = getRedisKey(joinPoint, id);
        // 根據key獲取從redis中查詢到的物件
        log.debug("update進入環繞增強前半部分,嘗試從redis查詢,生成的快取key:" + redisKey);
        Object data = redisCache.getFromCache(redisKey);
        // 如果查詢到了
        if (null != data) {
            log.debug("update[" + redisKey + "]快取命中:" + data);
            try {
                data = joinPoint.proceed();// 執行目標方法(service中的find*方法)
            } catch (Throwable e) {
                e.printStackTrace();
            }
            // 執行刪除操作
            redisCache.del(redisKey);
            log.debug("資料已從redis刪除");
            return 1;
        } else {
            return 0;
        }
    }
    /**
     * 拼接一個獨特的key
     * @param joinPoint
     * @param id
     * @return
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, String id) {
        return PROJECT_NAME + MODULE_NAME + joinPoint.getTarget().getClass().getName() + "-" + id;
    }
}
 

建立一個測試類進行測試

package com.landun.lichenming;

import static org.junit.Assert.assertNotNull;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.landun.lichenming.entity.User;
import com.landun.lichenming.service.UserService;

public class TestAspectOfCache {

    private UserService userService;
    
    @Before
    public void setUp() throws Exception {
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext();
        ac.setValidating(false);
        ac.setConfigLocation("spring-beans.xml");
        ac.refresh();
        System.out.println("Spring IoC container initialized.");
        userService = (UserService)ac.getBean("userService");
        assertNotNull(userService);
        System.out.println("userService initialized.");
    }

//我們測試一下查詢

    @Test
    public void testFindBtyId() {
        User u = userService.findUser(1);
        assertNotNull(u);
        System.out.println(u);
    }

測試後控制檯輸出

相關推薦

關於Redis入門透明透明使用AOP實現耦合快取實現

emm...博主在前些天學習了一下Redis,以及在網上查詢了部分資料、將看到的大牛們寫的相關文章整合了一下。。(如有侵權多見諒...)分享一些經驗,可能寫的或者瞭解的不是很透徹,哈哈...大牛們請無視 那麼在講解之前我們先來簡單的瞭解一下Redis 一、什麼是Redis

solr搜索之入門原理

solr solr入門 1 solr簡介solr官方文檔:http://wiki.apache.org/solr/DataImportHandler 下載地址:http://www.apache.org/dyn/closer.cgi/lucene/solr/2 solr入門我們使

redis入門學習記錄

1、linux線上下載Redis ,官網地址:https://redis.io/download目前,最新的Redist版本為redis-5.0.0,使用wget下載 進入/usr/local/src目錄,使用如下命令: wget http://download.redis.io/releases/red

redis入門學習記錄 redis入門學習記錄

繼第一節 redis入門學習記錄(一)之後,我們來學習redis的基本使用。 接下來我們看看/usr/local/redis/bin目錄下的幾個檔案作用是什麼?   redis-benchmark:redis效能測試工具   redis-check-aof:檢查aof日誌的工具   redi

MySQL 入門安裝親測適用win10

今天上午學習MySQL資料庫的安裝,也順便理清楚了資料庫的一些背景知識,特記錄如下: I. 背景知識 1. 資料 常見的文字,圖片,視訊,音訊都是資料。 2. 資料庫 儲存和管理海量的資料。 3. 資料庫的分類(database - DB) A. 關係型資料庫

Python celery和Redis入門安裝使用排難帖

shrink 獲取 問題 wine span cti bubuko cover hub 1、redis安裝 下載地址 https://github.com/MicrosoftArchive/redis/releases,選擇Redis-x64-3.2.100.msi5.8

Redis安裝配置基於5.0版本

目前最新版為:5.0.3,官方推薦使用原始碼方式進行安裝,以下是安裝步驟: 1、下載 wget http://download.redis.io/releases/redis-5.0.3.tar.gz 2、解壓 tar -xzvf redis-5.0.3.tar.gz -C

Redis入門安裝

一、什麼是Redis Redis是Remote Dictionary Server(遠端資料服務)的縮寫,由義大利人antirez(Salvatore Sanflippo) 開發的一款 記憶體高速快取資料庫,該軟體使用C語言編寫,它的資料模型為(key-value),它支援

Redis入門】-叢集手動搭建

使用哨兵模式可以有效的增加資料庫容量,同時可以實現自動化,但是,即使使用哨兵模式,redis叢集的每個資料庫仍然儲存著叢集中的所有資料,這樣就會存在木桶效應:資料庫的總容量受限於儲存記憶體最小的redis節點! 而這裡講的叢集,是對資料庫進行水平擴容,每個節點會儲存不同區域

Redis 原理應用3--記憶體淘汰機制、主從同步原理,HA策略哨兵機制分析

非精準的LRU 上面提到的LRU(Least Recently Used)策略,實際上Redis實現的LRU並不是可靠的LRU,也就是名義上我們使用LRU演算法淘汰鍵,但是實際上被淘汰的鍵並不一定是真正的最久沒用的,這裡涉及到一個權衡的問題,如果需要在全部鍵空間內搜尋最優解,則必然會增加系統的開銷,Re

C/C++基本語法,入門提高1

-- 學習C++可分為4個層次:   第一層次,C++基礎:挑選一本入門書籍,如《C++ Primer》、《C++大學教程》、或Stroustrup撰寫的經典《C++程式設計語言》或他一年半前的新作《C++程式設計原理與實踐》,而一般C++課程也止於此,另外《C++ 標準程式

Redis入門在商城案例中的使用

自學那麼多月以來學到的知識點挺多幾乎每天都在接受新東西,接受的多忘的也多,想回頭再去找也不知道從哪裡找了,所以決定執行好幾個月前就決定的事情-寫部落格,用來記錄自己每次所學習到的東西。 由於自己實習的時候,自己做的專案的資料庫就是用的MySql跟Redis。所

jvm入門理解——類載入器子系統

一、類載入子系統的作用 類載入子系統負責從檔案系統或者網路中載入Class檔案,class檔案在檔案開頭有特定的檔案標識; ClassLoader只負責class檔案的載入,至於它是否可以執行,則由Execution Engine決定 載入的類資訊存放於一塊成為方法區的記憶體空間。除了類資訊之外,方法區還會

jvm入門理解——垃圾回收與演算法

一、jvm垃圾回收要做的事情 哪些記憶體需要回收 什麼時候回收 怎麼回收 二、如何判斷物件已經死亡,或者說確定為垃圾 引用計數法: 給物件中新增一個引用計數器,每當有一個地方引用它時,計數器的值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件就是不可能再被使用的。這也就是需要回收的物件

2017面向對象程序設計Java 第1周學習指導要求2017.8.24-2017.8.27

令行 str applet 面向 學習目標 對象 com 變量 課程學習 2017面向對象程序設計(Java) 第1周學習指導及要求(2017.8.24-2017.8.27) 學習目標 了解課程上課方式及老師教學要求,掌握課程學習必要的軟件工具; 簡單了解Java特點

axios配置使用發起請求時帶上token

ima exp The push .post 設置 export host 接口 1.安裝 利用npm安裝 npm install axios --save 2.引入即可使用 import axios from ‘axios‘ 3.目錄 4.各個文件設置: (1

【SU外掛情報局】 Enscape for SketchUp 基礎入門完全解析附Enscape2.3.3安裝包

作者 | 活力網-Andrew 同學們大家好! 活力網SU外掛情報局正式開播啦!!! 我們第一期要講的外掛是——一款重量級的實時渲染外掛 那它是一款什麼樣的外掛吶? 他是一款實時渲染的SU外掛 與它類似的軟體有: Lumion Twinmotion Mars 相比 L T

軟體測試流程規範參考大華為的規範

軟體測試流程及規範(參考大華為的規範) 參考某大佬(窩真不知道是哪位大佬)總結的測試流程並結合在華為做測試學到的規範,整理的我們公司的測試流程,分享是一種美德,so開始你的閱讀吧~ 軟體測試流程及規範 一、目標 制定完整且具體的測試路線和流程,為快速、高效和高質量的軟體測試提供基礎流

SpringBoot全域性異常捕獲處理包括自定義異常捕獲處理

在做專案的時候需要對自定義異常做捕獲和處理,現在將程式碼記錄下來便於以後查閱。 1、全域性異常捕捉處理 @ControllerAdvice( annotations = {RestController.class} ) public class ExceptionHandlerAdv

特殊權限set_uid、set_gidstick_bit使用不多,了解

分享圖片 作用 set_gid tick com oss mar blog 臨時 set_uid存放密碼的文件:/etc/shadow ![](http://i2.51cto.com/images/blog/201812/04/c0dc4eaedca