【Redis】筆記|第10節|京東HotKey實現多級緩存架構

緩存架構

京東HotKey架構

代碼結構

代碼詳情

功能點:(如代碼有錯誤,歡迎討論糾正)

  1. 多級緩存,先查HotKey緩存,再查Redis,最后才查數據庫
  2. 熱點數據重建邏輯使用分布式鎖,二次查詢
  3. 更新緩存采用讀寫鎖提升性能
  4. 利用HotKey系統推送熱點Key到應用服務端(HotKey客戶端角色)
  5. 適用讀多寫少的場景
/*** 常量**/
public class Constants {public static final String PRODUCT_CACHE = "product:cache:";public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;public static final String EMPTY_CACHE = "{}";public static final String LOCK_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";public static final String LOCK_PRODUCT_UPDATE_PREFIX = "lock:product:update:";
}
/**初始化京東HotKey**/
@Component
public class HotkeyInitializer {@PostConstructpublic void initHotkey() {ClientStarter.Builder builder = new ClientStarter.Builder();// 注意,setAppName很重要,它和dashboard中相關規則是關聯的。ClientStarter starter = builder.setAppName("redis-multi-cache").setEtcdServer("http://127.0.0.1:2379").setCaffeineSize(10).build();starter.startPipeline();}
}
/*** 控制器**/
@RestController
@RequestMapping("/api/product/")
public class ProductController {@Autowiredprivate ProductService productService;@PostMapping(value = "/create")public Product createProduct(@RequestBody Product product) {return productService.createProduct(product);}@PostMapping(value = "/update")public Product updateProduct(@RequestBody Product product) {return productService.updateProduct(product);}@GetMapping("/get/{productId}")public Product getProduct(@PathVariable Long productId) {return productService.getProduct(productId);}}
/*** DAO層**/
@Repository
public class ProductDao {public Product createProduct(Product product) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("創建商品成功");return product;}public Product updateProduct(Product product) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("修改商品成功");return product;}public Product getProduct(Long productId) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("查詢商品成功");Product product = new Product();product.setId(productId);product.setName("test");return product;}}
/*** 商品實體**/
@Data
public class Product implements Serializable {private Long id; // 商品IDprivate String name; // 商品名稱private String category; // 商品分類(如 "電子產品"、"圖書")private BigDecimal price; // 價格(精確到分,使用BigDecimal避免浮點精度問題)private int stock; // 庫存數量private String description; // 商品描述private LocalDateTime createTime; // 創建時間private LocalDateTime updateTime; // 更新時間
}
/*** 商品服務**/
@Service
public class ProductService {@Autowiredprivate ProductDao productDao;@Autowiredprivate Redisson redisson;//寫鎖public Product createProduct(Product product) {Product productResult = null;RReadWriteLock readWriteLock = redisson.getReadWriteLock(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId());RLock writeLock = readWriteLock.writeLock();writeLock.lock();try {productResult = productDao.createProduct(product);redisson.getBucket(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId()).set(JSON.toJSONString(productResult), Duration.ofSeconds(genProductCacheTimeout()));JdHotKeyStore.smartSet(Constants.PRODUCT_CACHE + productResult.getId(), productResult);} finally {writeLock.unlock();}return productResult;}//寫鎖public Product updateProduct(Product product) {Product productResult = null;RReadWriteLock readWriteLock = redisson.getReadWriteLock(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId());RLock writeLock = readWriteLock.writeLock();writeLock.lock();try {productResult = productDao.updateProduct(product);redisson.getBucket(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId()).set(JSON.toJSONString(productResult), Duration.ofSeconds(genProductCacheTimeout()));JdHotKeyStore.smartSet(Constants.PRODUCT_CACHE + productResult.getId(), productResult);} finally {writeLock.unlock();}return productResult;}public Product getProduct(Long productId) {Product product = null;String productCacheKey = Constants.PRODUCT_CACHE + productId;product = getProductFromCache(productCacheKey);if (product != null) {return product;}//熱點緩存重建,加分布式鎖,第一個請求寫緩存成功后,做二次讀取緩存操作,其余請求都可以走RedisRLock hotCacheLock = redisson.getLock(Constants.LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);hotCacheLock.lock();try {product = getProductFromCache(productCacheKey);if (product != null) {return product;}RReadWriteLock readWriteLock = redisson.getReadWriteLock(Constants.LOCK_PRODUCT_UPDATE_PREFIX + productId);RLock rLock = readWriteLock.readLock();rLock.lock();try {product = productDao.getProduct(productId);if (product != null) {redisson.getBucket(productCacheKey).set(JSON.toJSONString(product),Duration.ofSeconds(genProductCacheTimeout()));JdHotKeyStore.smartSet(productCacheKey, product);} else {redisson.getBucket(productCacheKey).set(Constants.EMPTY_CACHE, Duration.ofSeconds(genEmptyCacheTimeout()));}} finally {rLock.unlock();}} finally {hotCacheLock.unlock();}return product;}//隨機超時時間,防止緩存擊穿(失效)private Integer genProductCacheTimeout() {return Constants.PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;}//空數據的超時時間private Integer genEmptyCacheTimeout() {return 60 + new Random().nextInt(30);}//從緩存獲取數據private Product getProductFromCache(String productCacheKey) {//檢測是不是熱點Key并上報if (JdHotKeyStore.isHotKey(productCacheKey)) {Object productObj = JdHotKeyStore.get(productCacheKey);if (productObj != null) {//如果是熱點Key并且有值就直接返回return (Product) JdHotKeyStore.get(productCacheKey);} else {Product product = getProductFromRedis(productCacheKey);//按文檔如果是熱點Key,第一次返回的Value是空的,要主動設置一次到本地緩存JdHotKeyStore.smartSet(productCacheKey, product);return product;}} else {return getProductFromRedis(productCacheKey);}}private Product getProductFromRedis(String productCacheKey) {Product product = null;RBucket<String> bucket = redisson.getBucket(productCacheKey);String productStr = bucket.get();if (!StringUtils.isEmpty(productStr)) {if (Constants.EMPTY_CACHE.equals(productStr)) {bucket.expire(Duration.ofSeconds(genEmptyCacheTimeout()));return new Product();}product = JSON.parseObject(productStr, Product.class);bucket.expire(Duration.ofSeconds(genProductCacheTimeout())); //讀延期}return product;}}
@SpringBootApplication
public class RedisMultiCacheApplication {public static void main(String[] args) {SpringApplication.run(RedisMultiCacheApplication.class, args);}@Beanpublic Redisson redisson() {// 此為單機模式Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return (Redisson) Redisson.create(config);}}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.12</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example.mutilcache</groupId><artifactId>redis-multi-cache</artifactId><version>0.0.1-SNAPSHOT</version><name>redis-multi-cache</name><description>redis-multi-cache</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.jd.platform.hotkey</groupId><artifactId>hotkey-client</artifactId><version>0.0.4-SNAPSHOT</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.48.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></path></annotationProcessorPaths></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86407.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86407.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86407.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

php apache構建 Web 服務器

虛擬機配置流程winsever2016配置Apache、Mysql、php_windows server 2016配置web服務器-CSDN博客 PHP 和 Apache 通過 ??模塊化協作?? 共同構建 Web 服務器&#xff0c;以下是它們的交互機制和工作流程&#xff1a; ??一、核心組件分工?? 組件角色??Apache??Web …

二分查找排序講解

一、二分查找&#xff08;Binary Search&#xff09; 核心思想&#xff1a; 前提&#xff1a;數組必須是 有序的&#xff08;比如從小到大排列&#xff09;。目標&#xff1a;在數組中快速找到某個數&#xff08;比如找 7&#xff09;。方法&#xff1a;每次排除一半的數&…

【Redis實戰:緩存與消息隊列的應用】

在現代互聯網開發中&#xff0c;Redis 作為一款高性能的內存數據庫&#xff0c;廣泛應用于緩存和消息隊列等場景。本文將深入探討 Redis 在這兩個領域的應用&#xff0c;并通過代碼示例比較兩個流行的框架&#xff08;Redis 和 RabbitMQ&#xff09;的特點與適用場景&#xff0…

[拓撲優化] 1.概述

常見的拓撲優化方法有&#xff1a;均勻化法、變密度法、漸進結構優化法、水平集法、移動可變形組件法等。 常見的數值計算方法有&#xff1a;有限元法、有限差分法、邊界元法、離散元法、無網格法、擴展有限元法、等幾何分析等。 將上述數值計算方法與拓撲優化方法結合&#…

【openssl】升級為3.3.1,避免安全漏洞

本文檔旨在形成 對Linux系統openssl版本進行升級 的搭建標準操作過程&#xff0c;搭建完成后&#xff0c;實現 openssl 達到3.3以上版本&#xff0c;避免安全漏洞 效果。 一、查看當前版本 版本不高于3.1的&#xff0c;均需要升級。 # 服務器上運行以下命令&#xff0c;查看…

基于正點原子阿波羅F429開發板的LWIP應用(6)——SNTP功能和lwiperf測速

說在開頭 正點原子F429開發板主芯片采用的是STM32F429IGT6&#xff0c;網絡PHY芯片采用的是LAN8720A(V1)和YT8512C(V2)&#xff0c;采用的是RMII連接&#xff0c;PHY_ADDR為0&#xff1b;在代碼中將會對不同的芯片做出適配。 CubeMX版本&#xff1a;6.6.1&#xff1b; F4芯片組…

C:\Users\中文名修改為英文名

C:\Users\中文名修改為英文名 背景操作步驟 背景 買了臺新電腦&#xff0c;初始化好不知道啥操作把自己的登錄用戶名改成了中文&#xff0c;有些安裝的軟件看見有中文直接就水土不服了。 操作步驟 以下稱中文用戶名為張三。 正常登錄張三用戶 進入用戶管理頁面修改用戶名&a…

YOLOv12環境配置,手把手教你使用YOLOv12訓練自己的數據集和推理(附YOLOv12網絡結構圖),全文最詳細教程

文章目錄 前言一、YOLOv12代碼下載地址1.YOLOv12模型結構圖 二、YOLO環境配置教程1.創建虛擬環境2.激活虛擬環境3.查詢自己電腦可支持最高cuda版本是多少&#xff08;無顯卡的同學可以跳過這個步驟&#xff09;4.pytorch安裝5.驗證 PyTorch GPU 是否可用&#xff08;沒有顯卡的…

ES6(ES2015)特性全解析

ES6&#xff08;ECMAScript 2015&#xff09;是 JavaScript 語言發展史上的一個重要里程碑&#xff0c;它引入了許多新的語法特性和功能&#xff0c;提升了代碼的可讀性、可維護性和開發效率。 1. 塊級作用域變量&#xff1a;let 和 const ES6 引入了 let 和 const 關鍵字&am…

jvm 垃圾收集算法 詳解

垃圾收集算法 分代收集理論 垃圾收集器的理論基礎&#xff0c;它建立在兩個分代假說之上&#xff1a; 弱分代假說&#xff1a;絕大多數對象都是朝生夕滅的。強分代假說&#xff1a;熬過越多次垃圾收集過程的對象就越難以消亡。 這兩個分代假說共同奠定了多款常用的垃圾收集…

數字孿生+AR/VR的融合創新

目錄 引言&#xff1a;工業元宇宙的興起與技術基石數字孿生&#xff1a;工業元宇宙的數字底座 2.1 數字孿生的概念與關鍵要素 2.2 數字孿生在工業領域的應用 2.3 數字孿生的技術架構 (Mermaid Graph) AR/VR&#xff1a;工業元宇宙的沉浸式體驗層 3.1 AR/VR 的概念與技術原理…

圖解C#教程 第五版 第4章 類型、存儲和變量 筆記

第4章 類型、存儲和變量 筆記 4.1 C# 程序是一組類型聲明 C程序是一組函數和數據類型&#xff0c;C程序是一組函數和類&#xff0c; 而C#程序是一組類型聲明&#xff0c;具有如下特征&#xff1a; C# 程序或 DLL 的源代碼是一組類型聲明類型聲明中必須有一個包含 Main 方法…

SpringBoot整合SSM

1. SSM整合步驟 今天帶大家學習一下基于SpringBoot的SSM整合案例&#xff0c;話不多說&#xff0c;咱們開始&#xff0c;要實現SSM整合&#xff0c;有以下這么幾步 導入依賴創建yml配置文件dao層靜態頁面測試類進行測試 1.1 導入依賴 <?xml version"1.0" enco…

多面體模型-學習筆記2

1&#xff09; 多面體模型被應用于解決程序變換問題&#xff0c;并有效地推動了程 序自動并行化等技術的發展。與傳統的解決程序變換的方法相比&#xff0c;多面體模型 具有許多優勢[5]。多面體模型提供了一種強大的抽象&#xff0c;將每個語句的動態語句執 行實例視作一個多面…

基于django+vue的健身房管理系統-vue

開發語言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.8數據庫&#xff1a;mysql 5.7數據庫工具&#xff1a;Navicat12開發軟件&#xff1a;PyCharm 系統展示 會員信息管理 員工信息管理 會員卡類型管理 健身項目管理 會員卡管理 摘要 健身房管理…

【Linux系統】Linux環境變量:系統配置的隱形指揮官

。# Linux系列 文章目錄 前言一、環境變量的概念二、常見的環境變量三、環境變量特點及其相關指令3.1 環境變量的全局性3.2、環境變量的生命周期 四、環境變量的組織方式五、C語言對環境變量的操作5.1 設置環境變量&#xff1a;setenv5.2 刪除環境變量:unsetenv5.3 遍歷所有環境…

Spring AI中使用ChatMemory實現會話記憶功能

文章目錄 1、需求2、ChatMemory中消息的存儲位置3、實現步驟1、引入依賴2、配置Spring AI3、配置chatmemory4、java層傳遞conversaionId 4、驗證5、完整代碼6、參考文檔 1、需求 我們知道大型語言模型 &#xff08;LLM&#xff09; 是無狀態的&#xff0c;這就意味著他們不會保…

Java 高級泛型實戰:8 個場景化編程技巧

文章目錄 一、通配符高級應用&#xff1a;靈活處理類型關系二、泛型方法與類型推斷三、泛型類的嵌套使用四、受限泛型與邊界條件五、泛型與反射結合六、泛型在函數式接口中的應用七、類型擦除與橋接方法八、自定義泛型注解總結 在Java編程中&#xff0c;泛型不僅是類型安全的保…

[藍橋杯 2024 國 B] 立定跳遠

問題描述 在運動會上&#xff0c;小明從數軸的原點開始向正方向立定跳遠。項目設置了 n 個檢查點 a1,a2,...,an且 ai≥ai?1>0。小明必須先后跳躍到每個檢查點上且只能跳躍到檢查點上。同時&#xff0c;小明可以自行再增加 m 個檢查點讓自己跳得更輕松。在運動會前&#xf…

2025年全國I卷數學壓軸題解答

第19題第3問: b b b 使得存在 t t t, 對于任意的 x x x, 5 cos ? x ? cos ? ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx?cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min ? t max ? x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…