AOP與Redis緩存實現

1. AOP實現緩存業務

1.1 業務需求

1). 自定義註解 @CacheFind(key=「xxx」,second=-1)
2). 使用自定義註解 標識業務方法 將方法的返回值保存到緩存中.
3). 利用AOP 攔截註解 利用環繞通知方法實現業務java

1.2 自定義註解@CacheFind

image.png

1.3 註解標識

image.png

1.4 編輯AOP

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Method;
import java.util.Arrays;

/*@Service
@Controller
@Repository*/
@Component  //組件 將類交給spring容器管理
@Aspect     //表示我是一個切面
public class RedisAOP {

    @Autowired
    private Jedis jedis;

    /*
    * 實現AOP業務調用
    * 1.攔截指定的註解
    * 2.利用環繞通知實現
    * 實現步驟:
    *       1.獲取KEY  必須先獲取註解 從註解中獲取key?
    *       2.校驗redis中是否有值
    *     *
    * 3.知識點補充:
    *   指定參數名稱進行傳值,運行期綁定參數類型完成註解的攔截
    *   joinPoint必須位於參數的第一位.
    */
    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
        Object result = null;
        //key=業務名稱::參數
        String key = cacheFind.key();
        String args = Arrays.toString(joinPoint.getArgs());
        key = key + "::" + args;

        //2.校驗是否有值
        if(jedis.exists(key)){
            String json = jedis.get(key);
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Class returnType = methodSignature.getReturnType();

            result = ObjectMapperUtil.toObj(json,returnType);
            System.out.println("AOP查詢redis緩存");

        }else{
            //redis中沒有數據,因此須要查詢數據庫,將數據保存到緩存中
            try {
                result = joinPoint.proceed();
                String json = ObjectMapperUtil.toJSON(result);
                //是否設定超時時間
                if(cacheFind.seconds()>0){
                    jedis.setex(key, cacheFind.seconds(), json);
                }else{
                    jedis.set(key,json);
                }
                System.out.println("AOP查詢數據庫");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }

        return result;
    }


    /**
     *   //1.獲取key 註解  方法對象  類 方法名稱  參數
     *         Class targetClass = joinPoint.getTarget().getClass();
     *         //2.獲取方法對象
     *         String methodName = joinPoint.getSignature().getName();
     *         Object[] args = joinPoint.getArgs();
     *         Class[] classArgs = new Class[args.length];
     *         for(int i=0;i<args.length;i++){
     *             classArgs[i] = args[i].getClass();
     *         }
     *         try {
     *             //反射實例化對象
     *             Method method = targetClass.getMethod(methodName,classArgs);
     *             CacheFind cacheFind = method.getAnnotation(CacheFind.class);
     *             String key = cacheFind.key();
     *             System.out.println(key);
     *         } catch (NoSuchMethodException e) {
     *             e.printStackTrace();
     *         }
     */


    //公式 aop = 切入點表達式   +   通知方法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")
    //@Pointcut("execution(* com.jt.service.*.*(..))")   //.* 當前包的一級子目錄
   /* @Pointcut("execution(* com.jt.service..*.*(..))")  //..* 當前包的全部的子目錄
    public void pointCut(){

    }*/

    //如何獲取目標對象的相關參數?
    //ProceedingJoinPoint is only supported for around advice
   /* @Before("pointCut()")
    public void before(JoinPoint joinPoint){    //鏈接點
        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("目標對象:"+target);
        System.out.println("方法參數:"+Arrays.toString(args));
        System.out.println("類名稱:"+className);
        System.out.println("方法名稱:"+methodName);
    }*/
}

2. 關於Redis常規屬性

2.1 Redis中持久化策略-RDB (Redis DataBase)

2.1.1 需求說明

說明: Redis中將數據都保存到了內存中,可是內存的特色斷電及擦除. 爲了保證redis中的緩存數據不丟失,則須要將內存數據按期進行持久化操做.
持久化: 將內存數據,寫到磁盤中.node

2.1.2 RDB模式

特色:
1.RDB模式是Redis默認的持久化規則.
2.RDB模式記錄的是Redis內存數據快照(只保留最新數據)
3.RDB模式按期持久化(時間可調) 可能會致使數據丟失.
4.RDB模式備份效率是最高的.
5.RDB模式備份阻塞式的 在備份時不容許其餘用戶操做. 保證數據安全性.
命令:
1.主動備份 save 會阻塞用戶操做
2.後臺備份 bgsave 異步的方式進行持久化操做 不會阻塞.面試

2.1.3 關於持久化配置

打開redis.conf文件:redis

cd  /usr/local/src/redis/            #進入redis目錄
vim  redis.conf                      #打開redis.conf文件
:set  nu                             #顯示行號
:/save                               #查找save
save 900 1 900秒內,用戶執行了一次更新操做時,那麼就持久化一次
save 300 10 300秒內,用戶執行了10次更新操做. 那麼就持久化一次
save 60 10000 60秒內,用戶執行了10000次的更新操做,則持久化一次
save 1 1 1秒內 1次更新 持久化一次!! 性能特別低

image.png

2.1.4 關於持久化文件名稱設定

默認的條件下,持久化文件名稱 dump.rdb算法

image.png

2.1.5 文件存儲目錄

./ 表明當前文件目錄. 意義使用絕對路徑的寫法

image.png

2.2 Redis中持久化策略-AOF (Append-only file)

2.2.1 AOF特色

1).AOF模式默認的條件下是關閉狀態.須要手動開啓.
2).AOF模式記錄的是用戶的操做過程. 能夠實現實時持久化.保證數據不丟失.
3).AOF模式維護的持久化文件佔用的空間較大.因此持久化效率不高. 而且須要按期的維護持久化文件.
4).AOF模式一旦開啓,則redis以AOF模式爲主 讀取的是AOF文件.spring

2.2.2 AOF配置

1).開啓AOF模式數據庫

將appendonly中的 no 修改成 yesjson

:/appendonly

image.png
image.png

2).持久化策略
always: 用戶更新一次,則持久化一次.
everysec: 每秒持久化一次 效率更高
no: 不主動持久化. 操做系統有關. 幾乎不用.
image.pngvim

2.3 關於Redis面試題

2.3.1 關於flushAll操做

業務場景:
小麗是一個的實習生.你是他的項目主管. 因爲小麗業務不熟,在生產環境中無心執行了flushAll操做. 問如何補救??
場景1: redis中的服務只開啓了默認的持久策略 RDB模式.後端

解決方案:

場景一:
redis中的服務開啓了AOF模式.

1.關閉現有的redis服務器.
2.檢查RDB文件是否被覆蓋,若是文件沒有覆蓋,則重啓redis便可.(但願渺茫)
3.若是flushAll命令,同時執行了save操做,則RDB模式無效.

cd  /usr/local/src/redis/shards/
redis-cli  -p  6379  shutdown
vim  dump.rdb

場景二:
redis中的服務開啓了AOF模式.

解決方案:
1.關閉redis服務器.
2.編輯redis 持久化文件,將flushAll命令刪除,保存退出.
3.重啓redis服務器

cd  /usr/local/src/redis/shards/
redis-cli  -p  6379  shutdown
vim  appendonly.aof

通常條件下: RDB模式和AOF模式都會開啓. 經過save命令執行rdb持久化方式.

2.3.2 單線程Redis爲何快

1).redis運行環境在內存中,純內存操做.
2).單線程操做 避免頻繁的上下文切換. 避免了開關連接的開銷.
3).採用了非阻塞I/O(BIO|NIO) 多路複用的機制(動態感知).
image.png

4). Redis最新版本 6.0版本 6.0之前的版本都是單線程操做方式. 6.0之後支持多線程操做方式. (執行時依舊是單線程操做).

2.4 關於Redis內存優化策略

2.4.1 業務場景

若是頻繁使用redis,不停的向其中保存數據,而且不作刪除操做,則內存必然溢出. 可否優化內存策略.
可否自動的刪除不用的數據,讓redis中保留熱點數據!!!.

2.4.2 LRU算法

LRU是Least Recently Used的縮寫,即最近最少使用,是一種經常使用的頁面置換算法,選擇最近最久未使用的頁面(數據)予以淘汰。該算法賦予每一個頁面一個訪問字段,用來記錄一個頁面自上次被訪問以來所經歷的時間 t,當須淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少使用的頁面予以淘汰。
計算維度: 自上一次以來所經歷的時間T.
image.png

說明:LRU算法是內存優化中最好用的算法.

2.4.3 LFU算法

LFU(least frequently used (LFU) page-replacement algorithm)。即最不常用頁置換算法,要求在頁置換時置換引用計數最小的頁,由於常用的頁應該有一個較大的引用次數。可是有些頁在開始時使用次數不少,但之後就再也不使用,這類頁將會長時間留在內存中,所以能夠將引用計數寄存器定時右移一位,造成指數衰減的平均使用次數。
維度: 引用次數
常識: 計算機左移 擴大倍數
計算機右移 縮小倍數
image.png

2.4.4 隨機算法

隨機刪除數據.

2.4.5 TTL算法

說明:將剩餘存活時間排序,將立刻要被刪除的數據,提早刪除.
image.png

2.4.6 Redis默認的內存優化策略

Redis中採用的策略按期刪除+惰性刪除策略

說明:

  1. 按期刪除策略: redis默認每隔100ms 檢查是否有過時的key, 檢查時隨機的方式進行檢查.(不是檢查全部的數據,由於效率過低.)

問題: 因爲數據衆多,可能抽取時沒有被選中.可能出現 該數據已經到了超時時間,可是redis並無立刻刪除數據.

  1. 惰性刪除策略: 當用戶獲取key的時候,首先檢查數據是否已通過了超時時間. 若是已經超時,則刪除數據.

問題: 因爲數據衆多, 用戶不可能將全部的內存數據都get一遍.必然會出現 須要刪除的數據一直保留在內存中的現象.佔用內存資源.
3.能夠採用上述的內存優化手段,主動的刪除.

內存優化算法說明:

volatile-lru 在設定超時時間的數據 採用LRU算法進行優化
allkeys-lru 在全部的數據採用LRU算法進行優化
volatile-lfu 在設定了超時時間的數據中採用LFU算法優化
allkeys-lfu 在全部的數據中採用LFU算法進行優化
volatile-random 在設定了超時時間的數據 採用隨機算法
allkeys-random 全部數據採用 隨機算法
volatile-ttl 設定超時時間的TTl算法
noeviction 不主動刪除數據,若是內存溢出則報錯返回

image.png

3. Redis分片機制

3.1 業務需求

說明: 單臺redis存儲的數據容量有限的. 若是須要存儲海量的緩存數據,則使用單臺redis確定不能知足要求.爲了知足數據擴容的需求.則能夠採用分片的機制實現.
image.png

3.2 Redis分片機制實現

分片的目的: 爲了給redis內存進行擴容。

3.2.1搭建策略

分別準備3臺redis 6379/6380/6381

3.2.2 準備文件目錄

image.png

3.2.2 複製配置文件

說明: 將redis的配置文件放到shards目錄中.
image.png

修改配置文件端口號,依次修改 6380/6381
image.png

啓動3臺redis:
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf

校驗服務器:
image.png

3.2.3 Redis分片入門案例

package com.jt.test;

import org.junit.jupiter.api.Test;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;

import java.util.ArrayList;
import java.util.List;

public class TestRedisShards {

    @Test
    public void testShards(){
        List<JedisShardInfo> shards = new ArrayList<>();
        shards.add(new JedisShardInfo("192.168.126.129",6379));
        shards.add(new JedisShardInfo("192.168.126.129",6380));
        shards.add(new JedisShardInfo("192.168.126.129",6381));
        ShardedJedis shardedJedis = new ShardedJedis(shards);
        //3臺redis當作1臺使用  內存容量擴大3倍.  79/80/81???
        shardedJedis.set("shards", "redis分片測試");
        System.out.println(shardedJedis.get("shards"));
    }
}

3.3 一致性hash算法

3.3.1 算法介紹

一致性哈希算法在1997年由麻省理工學院提出,是一種特殊的哈希算法,目的是解決分佈式緩存的問題 。在移除或者添加一個服務器時,可以儘量小地改變已存在的服務請求與處理請求服務器之間的映射關係。一致性哈希解決了簡單哈希算法在分佈式哈希表( Distributed Hash Table,DHT) 中存在的動態伸縮等問題。

3.3.2 算法說明

常識:

  1. 若是數據相同,則hash結果必然相同.
  2. 常見hash值 由8位16進制數組成. 共用多少種可能性? 2^32

image.png

3.3.3 平衡性

平衡性是指hash的結果應該平均分配到各個節點,這樣從算法上解決了負載均衡問題。
說明:經過虛擬節點實現數據的平衡
在這裏插入圖片描述

3.3.4 單調性

單調性是指在新增或者刪減節點時,不影響系統正常運行。
原則: 若是節點新增/減小 應該儘量保證原始數據儘量不變.

3.3.5 分散性

分散性是指數據應該分散地存放在分佈式集羣中的各個節點(節點本身能夠有備份),沒必要每一個節點都存儲全部的數據
將數據分散存儲,即便未來服務器宕機,則影響只是一部分,.而不是所有.
諺語: 雞蛋不要放到一個籃子裏.
在這裏插入圖片描述

3.3.6 負載(Load)

負載問題其實是從另外一個角度看待分散性問題。既然不一樣的終端可能將相同的內容映射到不一樣的緩衝區中,那麼對於一個特定的緩衝區而言,也可能被不一樣的用戶映射爲不一樣的內容。與分散性同樣,這種狀況也是應當避免的,所以好的哈希算法應可以儘可能下降緩衝的負荷。

3.4 SpringBoot整合Redis分片機制

3.4.1 編輯 properties 配置文件

# 準備redis節點信息
redis.host=192.168.126.129
redis.port=6379

# 準備3臺redis
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

3.4.2 編輯配置類

package com.jt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import java.util.ArrayList;
import java.util.List;
@Configuration //表示一個配置類 通常會與@bean註解一塊兒使用
@PropertySource("classpath:redis.properties") //導入配置文件
public class RedisConfig {
    @Value("${redis.nodes}")
    private String nodes;
    @Bean
    public ShardedJedis shardedJedis(){
        List<JedisShardInfo> shards = new ArrayList<>();
        String[] nodeArray = nodes.split(",");
        for (String node:nodeArray) { //host:node
            //分割出ip
            String host = node.split(":")[0];
            //分割出端口,並轉爲int類型
            int port = Integer.parseInt(node.split(":")[1]);
            shards.add(new JedisShardInfo(host,port));
        }
        return new ShardedJedis(shards);
    }
}

3.4.3 修改AOP中的配置

@Component //組件 將類交給spring容器管理
@Aspect //表示我是一個切面
public class RedisAOP {

//    @Autowired
//    private Jedis jedis;  //使用單臺中redis
      @Autowired
      private ShardedJedis jedis; //使用分片多臺redis

4. Redis哨兵機制

4.1 Redis分片存在問題

說明:Redis分片機制,雖然能夠實現Redis Redis內存擴容,可是redis 節點並沒有實現高可用.若是節點宕機,則整合redis分片將不可以使用.

4.2 Redis主從結構搭建

規定: 6379主機 /6380/6381 從機

4.2.1 複製文件目錄

若是redis已開啓,先關閉
進入目錄,複製

cd  /usr/local/src/redis/
cp  -r  shards  sentinel

在這裏插入圖片描述

4.2.2 刪除持久化文件

cd  sentinel
rm  -f  appendonly.aof  aump.rdb

在這裏插入圖片描述

4.2.3 啓動3臺Redis服務器

1.redis-server 6379.conf
2.redis-server 6380.conf
3.redis-server 6381.conf

ps  -ef  |  grep  redis

在這裏插入圖片描述

4.2.4 實現redis主從掛載

進入從機

redis-cli  -p  6380

命令1: slaveof host port (slaveof 主機IP 端口)

slaveof  192.168.126.129  6379
info  replication

命令說明: 在從機中執行上述命令 掛載的是主機的地址.
在這裏插入圖片描述

命令2: info replication
在這裏插入圖片描述

掛載第二臺從機

redis-cli  -p  6381
slaveof  192.168.126.129  6379
info  replication
exit
redis-cli  -p  6379
info  replication

主從結構關係:
在這裏插入圖片描述

4.3 Redis哨兵工做原理

4.3.1 工做流程圖

在這裏插入圖片描述
原理說明:
1.哨兵監控主機的運行的狀態. 經過心跳檢測機制(PING-PONG)若是連續3次節點沒有響應,則判定主機宕機,哨兵開始進行選舉.
2.哨兵經過連接主機,獲取主機的相關配置信息(包含主從結構),挑選連接當前主機的從機.根據隨機算法挑選出新的主機. 而且將其餘的節點設置爲新主機的從.

4.3.2 編輯哨兵配置文件

1).複製哨兵的配置文件
在這裏插入圖片描述
2).關閉保護模式
在這裏插入圖片描述
3).開啓後端運行
在這裏插入圖片描述
4).設定哨兵的投票數
在這裏插入圖片描述
5).修改選舉的超時時間
在這裏插入圖片描述
6).修改哨兵的狀態
在這裏插入圖片描述

4.3.3 哨兵測試

哨兵命令: redis-sentinel sentinel.conf
檢查redis服務:
在這裏插入圖片描述redis高可用測試:1.關閉redis主機63792.等待10秒 檢查6380/6381到底誰是主機.3.重啓6379服務器,檢查是否充當了新主機的從

相關文章
相關標籤/搜索