用Java寫一個分散式緩存——緩存管理

来源:https://www.cnblogs.com/weloe/archive/2023/01/27/17068891.html
-Advertisement-
Play Games

之前也用過一些緩存中間件,框架,也想著自己是不是也能用Java寫一個出來,於是就有了這個想法,打算在寫的過程中同步進行總結 源碼:https://github.com/weloe/Java-Distributed-Cache ...


前言

之前也用過一些緩存中間件,框架,也想著自己是不是也能用Java寫一個出來,於是就有了這個想法,打算在寫的過程中同步進行總結

源碼:weloe/Java-Distributed-Cache (github.com)

本篇代碼:

Java-Distributed-Cache/src/main/java/com/weloe/cache/cachemanager at master · weloe/Java-Distributed-Cache (github.com)

Java-Distributed-Cache/src/test/java/com/weloe/cache/cachemanager at master · weloe/Java-Distributed-Cache (github.com)

上篇:
https://www.cnblogs.com/weloe/p/17050512.html

思路

既然是分散式緩存,那麼就一定會有緩存管理方面的問題,既然是要儲存的數據,那麼就不能讓它無限制的存儲,就需要設置臨界值,這個也是需要緩存淘汰的原因。

而為了對緩存方便管理,比如,我們需要緩存的有多個功能,我們為了方便區分,可能就需要在key前加上功能首碼,這樣不僅變得麻煩,同時由於key變大,也會增加記憶體的壓力。

所以我們就需要把緩存分組進行管理,並提供一些方便的對外介面

實現

CacheObj

Java-Distributed-Cache/CacheObj.java at master · weloe/Java-Distributed-Cache (github.com)

在前篇緩存淘汰中,我們確定了我們真正存儲數據的是一個k,v結構,因此,我們需要抽象出這裡的k,v,k選擇String,而v則抽象出一個CacheObj。

需要註意的是,這裡的endTime是該緩存到期的時間,一般而言,我們都有為目標緩存設定緩存時間的需求,這也是緩存淘汰策略中的一種。實際存儲為byte[]則是為了通用性。

public class CacheObj {

    private LocalDateTime endTime;

    private Class clazz;

    private int byteSize;

    // 存儲的實際數據
    private byte[] data;

    public CacheObj() {
    }

    public CacheObj(LocalDateTime endTime,Class clazz ,int byteSize, byte[] data) {
        this.endTime = endTime;
        this.clazz = clazz;
        this.byteSize = byteSize;
        this.data = data;
    }

    public int getByteSize() {
        return byteSize;
    }

    public byte[] getData() {
        return data;
    }


    public void setEndTime(LocalDateTime endTime) {
        this.endTime = endTime;
    }

    public LocalDateTime getEndTime() {
        return endTime;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }
}

Cache

Java-Distributed-Cache/Cache.java at master · weloe/Java-Distributed-Cache (github.com)

有組管理,也就需要單一的緩存管理

public class Cache {
    // 最大位元組
    private int maxByteSize;

    // 目前使用位元組
    private int normalByteSize;

    // 緩存策略
    private CacheStrategy<String, CacheObj> cacheStrategy;

    Lock readLock;

    Lock writeLock;

    public Cache(int maxByteSize, CacheStrategy<String, CacheObj> cacheStrategy) {
        this.maxByteSize = maxByteSize;
        this.normalByteSize = 0;
        this.cacheStrategy = cacheStrategy;
        readLock = new ReentrantReadWriteLock().readLock();
        writeLock = new ReentrantReadWriteLock().writeLock();
    }

    public CacheObj add(String key, CacheObj cacheObj) {
        writeLock.lock();

        normalByteSize += cacheObj.getByteSize();

        // 緩存上限
        while (normalByteSize > maxByteSize) {
            // 淘汰緩存
            CacheObj outCache = cacheStrategy.outCache();
            normalByteSize -= outCache.getByteSize();
        }

        // 加入緩存
        CacheObj v = cacheStrategy.put(key, cacheObj);

        writeLock.unlock();

        return v;
    }

    public CacheObj get(String key) {
        readLock.lock();

        CacheObj v = cacheStrategy.get(key);
        // 判斷是否過期
        if (v != null && v.getEndTime() != null && LocalDateTime.now().isAfter(v.getEndTime())) {
            CacheObj obj = cacheStrategy.outCache(key);
            return null;
        }

        readLock.unlock();
        return v;
    }

    public CacheObj remove(String key){
        return cacheStrategy.outCache(key);
    }

    public void clear(){
        cacheStrategy.clear();
    }

    public void setMaxByteSize(int maxByteSize) {
        this.maxByteSize = maxByteSize;
    }

    public int getMaxByteSize() {
        return maxByteSize;
    }

    public int getNormalByteSize() {
        return normalByteSize;
    }

}

Group

Java-Distributed-Cache/Group.java at master · weloe/Java-Distributed-Cache (github.com)

既然需要組管理,那麼就需要抽象出一個Group類型,這裡的getter是需要後期自定義的回調函數。

public class Group {

    private String name;

    private Cache cache;

    private Getter getter;

    @FunctionalInterface
    interface Getter {
        byte[] get(String k) throws Exception;
    }

put,get

為了方便管理,Group需要提供get,put法

	public CacheObj get(String key) {
        if ("".equals(key) || key == null) {
            throw new RuntimeException("key不能為空");
        }

        CacheObj cacheObj = cache.get(key);

        if (cacheObj != null) {
            return cacheObj;
        }

        return load(key);
    }

    /**
     * 通過Getter回調獲取數據
     *
     * @param key
     * @return
     */
    private CacheObj load(String key) {
        byte[] bytes = null;
        try {
            bytes = getter.get(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        if (bytes == null) {
            return null;
        }
        CacheObj cacheObj = BytesUtil.bytes2CacheObj(bytes);

        cache.add(key, cacheObj);

        return cacheObj;
    }

    public CacheObj putCacheObj(String key,CacheObj cacheObj){
        CacheObj obj = cache.add(key, cacheObj);
        return obj;
    }

expire

為存儲的數據設定存儲時間的方法

    public CacheObj expire(String key, long num, ChronoUnit timeUnit){
        CacheObj cacheObj;
        try {
            cacheObj = cache.get(key);
            cacheObj.setEndTime(LocalDateTime.now().plus(num, timeUnit));
        } catch (Exception e) {
            return null;
        }
        return cacheObj;
    }

setSize

設置緩存臨界值的方法

    public boolean setMaxSize(int num){
        if(num < cache.getNormalByteSize()){
            return false;
        }
        cache.setMaxByteSize(num);
        return true;
    }

delete,clear

清除組緩存的方法,從這裡也可以看出其方便性,即可以清除單一功能(組)的緩存

    public CacheObj delete(String key){
        CacheObj obj = cache.remove(key);
        return obj;
    }

    public void clear(){
        cache.clear();
    }

GroupManager

Java-Distributed-Cache/GroupManager.java at master · weloe/Java-Distributed-Cache (github.com)

既然有Group,就需要管理Group,也就需要相對應的put,get方法

public class GroupManager {

    private Map<String, Group> groupMap;


    public GroupManager(Map<String, Group> groupMap) {
        this.groupMap = groupMap;
    }


    public Group getGroup(String key) {
        Group group = groupMap.get(key);
        return group;
    }

    public Group put(Group group){
        return groupMap.put(group.getName(),group);
    }


}

測試

CacheTest

Java-Distributed-Cache/CacheTest.java at master · weloe/Java-Distributed-Cache (github.com)

class CacheTest {
    Cache cache;

    @BeforeEach
    void setUp() {

        CacheStrategy<String, CacheObj> lruCache = new LRUCache<>(5);
        lruCache.setCallback((s1, s2)-> System.out.println("緩存淘汰"));
        cache = new Cache(1024*1024,lruCache);
    }

    @Test
    void add() {
        String s = "123";
        CacheObj cacheObj = new CacheObj(LocalDateTime.MAX, String.class, 512*1024, s.getBytes(StandardCharsets.UTF_8));
        cache.add("test", cacheObj);

        for (int i = 0; i < 5; i++) {
            cache.add("test"+i,cacheObj);
            
        }

    }

    @Test
    void get() {
        CacheObj cacheObj = cache.get("123");
        Assertions.assertNull(cacheObj);

        String s = "123";

        cacheObj = new CacheObj(LocalDateTime.MAX,String.class, s.getBytes(StandardCharsets.UTF_8).length, s.getBytes(StandardCharsets.UTF_8));
        cache.add("test", cacheObj);

        CacheObj test = cache.get("test");
        Assertions.assertNotNull(test);

        byte[] data = test.getData();
        String s1 = BytesUtil.bytes2String(data);

        System.out.println(s1);

    }

}

GroupTest

Java-Distributed-Cache/GroupTest.java at master · weloe/Java-Distributed-Cache (github.com)

package com.weloe.cache.cachemanager;

import com.weloe.cache.outstrategy.CacheStrategy;
import com.weloe.cache.outstrategy.LRUCache;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

class GroupTest {
    Group group;

    @BeforeEach
    void setUp() {

        CacheStrategy<String, CacheObj> lruCache = new LRUCache<>(5);
        lruCache.setCallback((s1, s2)-> System.out.println("緩存淘汰"));

        group = new Group("group1", new Cache(1024*1024,lruCache), str -> {
            System.out.println("group1回調");
            return new byte[0];
        });


    }

    @Test
    void get() {
        group.putCacheObj("1",new CacheObj());
        CacheObj cacheObj = group.get("1");
    }

    @Test
    void getName() {
        String name = group.getName();
        System.out.println(name);
    }

    @Test
    void putCacheObj() {
        String x = "132";
        group.putCacheObj("cache1",new CacheObj(null,String.class,x.getBytes(StandardCharsets.UTF_8).length,x.getBytes(StandardCharsets.UTF_8)));
    }

    @Test
    void expire() {
        String x = "132";
        group.putCacheObj("cache1",new CacheObj(null,String.class,x.getBytes(StandardCharsets.UTF_8).length,x.getBytes(StandardCharsets.UTF_8)));
        CacheObj cache1 = group.expire("cache1", 2, ChronoUnit.MINUTES);
        System.out.println(cache1.getEndTime());
        System.out.println(group.get("cache1").getEndTime());
        Assertions.assertSame(cache1.getEndTime(),group.get("cache1").getEndTime());
    }
}

GroupManagerTest

Java-Distributed-Cache/GroupManagerTest.java at master · weloe/Java-Distributed-Cache (github.com)

package com.weloe.cache.cachemanager;

import com.weloe.cache.outstrategy.LRUCache;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.concurrent.locks.ReentrantLock;

class GroupManagerTest {
    GroupManager groupManager;

    @BeforeEach
    void setUp() {
        Group group1 = new Group("group1",
                new Cache(1024*1024, new LRUCache<>(5,(s1, s2)-> System.out.println("group1緩存淘汰"))),
                str -> {System.out.println("group1未獲取緩存的回調");return new byte[0];}
        );
        Group group2 = new Group("group2",
                new Cache(1024*1024, new LRUCache<>(5,(s1, s2)-> System.out.println("group2緩存淘汰"))),
                str -> {System.out.println("group2未獲取緩存的回調");return new byte[0];}
        );

        groupManager = new GroupManager(new HashMap<>(),new ReentrantLock());
        groupManager.put(group1);
        groupManager.put(group2);


    }

    @Test
    void getGroup() {

        System.out.println(groupManager.getGroup(""));
        System.out.println(groupManager.getGroup("group1"));
        System.out.println(groupManager.getGroup("group2").getName());

    }

    @Test
    void put() {
        Group group3 = new Group("group3",
                new Cache(1024*1024, new LRUCache<>(5,(s1, s2)-> System.out.println("group3緩存淘汰"))),
                str -> {System.out.println("group3未獲取緩存的回調");return new byte[0];}
        );
        groupManager.put(group3);
        System.out.println(groupManager.getGroup("group3").getName());
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 閉包和作用域 變數聲明 var 聲明特點 在使用var聲明變數時,變數會被自動添加到最接近的上下文 var存在聲明提升。var聲明會被拿到函數或全局作用域的頂部,位於作用域中所有代碼之前。 可多次重覆聲明。而重覆的var聲明則會被忽略 let 聲明特點 let聲明存在塊級作用域 let聲明(創建過程 ...
  • 如果你在項目中使用了 vuex模塊化,並且在項目中使用actions中函數調用頻率高,推薦瞭解一下這種方式。 比如下麵兩種方式調用 , 第一個是直接傳參設置, 第二個是添加了非同步ajax返回內容 在回調到等下我們要封裝的js中的成功回調里,然後這個成功回調就會反饋給組件 1.創建文件utils/vu ...
  • 一、Lua應用場景 游戲開發 獨立應用腳本 Web 應用腳本 擴展和資料庫插件如:MySQL Proxy 和 MySQL WorkBench 安全系統,如入侵檢測系統 教程採用Aide Lua Pro或AndLua+開發安卓應用。在學習開發安卓應用前,先學習lua的基礎課程。 二、配置手機開發環境 ...
  • 實現Spring底層機制-01 主要實現:初始化IOC容器+依賴註入+BeanPostProcessor機制+AOP 前面我們實際上已經使用代碼簡單實現了: Spring XML 註入 bean (Spring基本介紹02) Spring 註解方式註入 bean (Spring管理Bean-IOC- ...
  • 簡介: 訪問者模式,屬於行為型的設計模式。表示一個作用於某對象結構中的各元素的操作。它是你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。 適用場景: 類中有易於變化的演算法。 希望數據結構與數據分離。 優點: 便於增加新的操作,相當於增加一個訪問者。訪問者模式將有關行為集中到一個訪問者對象 ...
  • 1.簡介 1.1 gRPC的起源 RPC是Remote Procedure Call的簡稱,中文叫遠程過程調用。用於解決分散式系統中服務之間的調用問題。通俗地講,就是開發者能夠像調用本地方法一樣調用遠程的服務。所以,RPC的作用主要體現在這兩個方面: 屏蔽遠程調用跟本地調用的區別,讓我們感覺就是調用 ...
  • 寫在開頭:本文章提供深搜與寬搜的解題思路,無具體題目對應的代碼,如想瞭解,請到個人主頁查找,感謝觀看。 深度優先搜索(DFS): 遞歸,即函數調用自身,以逐步減小問題 的規模。但在一些問題中,並不是所有的 遞歸路徑都是有效的。 如圖所示迷宮,很可能會進入橙色所標識 的“死衚衕”,只能回到之前的路徑, ...
  • 題目描述 牛牛嘗試用鍵盤讀入一個字元,然後在屏幕上顯示用這個字元組成的 3*3 的矩形。 輸入描述 一行讀入一個 char 類型的字元。 輸出描述 輸出這個字元組成的 3*3 矩形。 示例 1 輸入:# 輸出: ### ### ### 解題思路 方案一 採用多條 printf() 按照格式輸出,從而 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...