Redis 緩存進階篇,緩存真實數據和緩存文件指針最佳實現?如何選擇?

目錄

一. 場景再現、具體分析

二. 常見實現方案及方案分析

2.1 數據庫字段最大存儲理論分析

2.2 最佳實踐方式分析

三. 接口選擇、接口分析

四. 數據庫設計

4.1 接口緩存表設計

4.1.1 建表SQL

4.1.2 查詢接口設計

4.2 調用日志記錄表設計

4.2.1 建表SQL

4.2.2 查詢SQL

五. 項目代碼落地實現

5.1 通用代碼編寫

5.1.1 創建接口請求參數實體類

5.1.2 創建接口響應通用返回結果類

5.1.3 創建接口緩存表實體類

5.1.4 創建接口緩存表Mapper接口

5.1.5 創建接口活動日志表實體類

5.1.6 創建接口活動記錄表Mapper接口

5.1.7 創建 Controller?層控制器類

5.1.8 Service 層方法公共變量和方法創建

5.2 Redis 緩存真實數據業務代碼實現

5.3 Redis 緩存文件指針,通過指針再讀取文件內容業務實現

5.4 二者混合使用

六. 簡要總結


一. 場景再現、具體分析

這里我們來設想以下場景。

假設你的個人項目或公司分給你的項目需求需要查詢公司信息,所以對接了啟信寶、企查查等第三方公司信息查詢接口;此外,為了節省成本,當我們首次查詢一個新公司的時候,如果調用企查查成功,則將企查查返回的數據保存到我們本地的數據庫中,同時將數據庫數據讀到緩存中提高查詢效率,后續我們再次查詢此公司,則不會發起實際調用,而是從緩存獲取數據返回。

對于緩存公司數據的方式,通常我們有以下兩種方案。

方案一:將企查查接口返回的數據報文直接存儲到數據庫,可以使用 VARCHAR(MAX),TEXT,MEDIUMTEXT等字段存儲,根據接口返回數據的大小選擇;然后將接口原文直接緩存到 Redis 中;

方案二:將企查查接口返回的數據報文存儲到文件中(文件可以存儲在服務器上,也可以存儲到 minio,阿里云OSS等云服務器上),然后將文件的指針(文件的地址)存儲到數據庫中,使用 VARCHAR 即可,同時為了提高查詢效率,也將文件的指針緩存到 Redis 中;

再比如,以我們當前的 CSDN 網站為例,用戶編寫的文章,要進行暫存或發布,文章應該如何存儲?是直接以文本格式存儲到數據庫中?還是存儲到文本文件,然后將文件指針存儲到數據中?

這種場景其實并不罕見,那么接下來,我們就來探討一下,這兩種文件存儲方案的優缺點吧!

二. 常見實現方案及方案分析

2.1 數據庫字段最大存儲理論分析

如下表格所示,是MySQL中比較常見的幾個存儲文本的字段類型,目前比較常用的字符集有 utf8mb4 和?utf8mb3,更推薦使用?utf8mb4。

字段類型最大字節數計算基礎

理論最大漢字數

utf8mb3,3字節)

理論最大漢字數

utf8mb4,4字節)

適用場景使用頻率
VARCHAR65,53365,535 - 221,844 字16,383 字短文本極高
TEXT65,535固定限制21,845 字16,383 字普通長文本一般
JSON1,073,741,823LONGTEXT 級357,913,941 字268,435,455 字JSON 數據較低
MEDIUMTEXT16,777,21516MB5,592,405 字4,194,303 字大文本較低

重點來啦!!!小編這里盡可能簡單的說一下,在數據庫的底層,所有的數據都是存儲在數據頁中的,一張數據頁的大小就是16KB,即16384字節,并且數據庫的 InnoDB 存儲引擎大數據存儲機制,當一條數據的某個字段,以VARCHAR為例,大于數據頁的一半8K(8192字節)時,則不會將字段值真實存儲在當前數據頁,而是存儲到"溢出頁",然后數據庫底層會把"溢出頁"的指針值存儲到字段值中,當我們讀取數據的時候,數據庫底層讀取到大字段的值之后,會根據指針值進行IO操作,將"溢出頁"的值讀出來然后進行返回;而TEXT、JSON、MEDIUMTEXT其它三者更不必多說,都是將數據存儲在單獨的數據頁,然后記錄中的字段值則是存儲數據頁的指針地址,具體見下表格。

行格式溢出閾值VARCHAR(10KB)存儲TEXT 存儲JSON 存儲MEDIUMTEXT 存儲版本建議
Redundant完整存行內完整存行內完整存行內完整存行內已淘汰
Compact768 字節768B 前綴 + 溢出鏈768B 前綴 + 溢出鏈768B 前綴 + 溢出鏈768B 前綴 + 溢出鏈兼容舊系統
Dynamic8 KB20B 指針 + 完整溢出鏈20B 指針 + 完整溢出鏈20B 指針 + 完整溢出鏈20B 指針 + 完整溢出鏈MySQL 5.7+默認

不難發現,在 MySQL5.7 版本之后,行格式已經做了進一步優化,采用了?Dynamic 動態行格式,可以簡單理解為,當我們存儲的一條數據某個字段大小小于 8K 時,MySQL選擇直接存儲完整數據到行內;但是當字段大小大于 8K時,則會將真實數據存儲到其他數據頁,字段則存儲指針值。當然了,數據庫底層的設計極為精妙,也有可能某個數據頁第一條要存儲的數據就是大數據,此時有16KB的空間,空間足夠,一般是全量存儲;但如果一個數據頁已經存儲了100條數據,剩下6KB的大小,但要存儲一行8KB的大小的記錄,此時就有可能觸發溢出存儲機制,將大數據存儲在"溢出頁"。

所以我們本篇文章就以 MySQL5.7+ 之后的版本為例,可以得出以下結論,一定牢記!!!

當使用 VARCHAR、TEXT、JSON、MEDIUMTEXT 字段存儲數據時,若字段大小大于8K,則數據庫行內字段值只存儲文件指針,真實數據存儲在數據頁;若字段大小小于8K,都會把真實數據存儲在行內;唯一的區別就是它們的最大字節數據不同會導致數據頁的數量不同僅此而已。數據頁數量越多,也就意味著數據庫要進行更多次的隨機 IO 去讀取數據頁,可能會影響數據庫的性能。

2.2 最佳實踐方式分析

通過上面的一頓分析,我們對于這四種存儲字段已經比較清楚了,那么現在,我們就從實用的角度來考慮到底選擇哪種字段?

小編更推薦使用 VARCHAR 類型!

VARCHAR:可控可變長度,并且當數據小不需要額外存儲數據頁時,它直接存儲完整數據,當數據量大需要數據頁時,則存儲文件地址,并且支持數據檢查和內存臨時表,并且是常見的類型,代碼實現難度低,符合大眾思路;

TEXT:相對來說比較好的一個字段,但還是不如 VARCHAR,不支持數據檢查和內存臨時表,另外一點就是,無論它是否使用到了額外的數據頁,都會有一個20字節的文件指針,比較浪費空間,且此類型很少有人用,所以實際開發我們也不追求新鮮感,以通用型普遍性 VARCAHR 類型為主;

JSON:比較好的一個數據類型,但不建議使用。原因是需要使用 JSONObject 類型來接受,且部分開發人員未必對此字段類型熟悉,甚至可能SQL語法也不太清楚,需要額外進行學習,徒增開發壓力;并且更重要的一點是,如果我們要在程序中獲取JSON中的某個 Key 并進行操作,此時就顯得較為麻煩,不如存儲為字符串或文本類型,在經 JSONObject.parse() 轉化;

MEDIUMTEXT:占用數據庫存儲較大,不易于管理,特別是當用戶大量訪問調用企查查接口時,會增加數據庫存儲壓力,導致實際查詢時,數據庫底層會進行多次IO操作,如此一來還會如直接存儲到數據庫外的單獨文件,在將文件指針存儲到數據庫。

總的來說,我們本篇文章著重考慮的就是是否要將真實數據存表,還是存文件指針,通過 MySQL 的動態優化策略,我們不難發現,當數據量大于 8K 時,即使我們不額外存到文件中,數據庫底層還是會將數據存儲到"數據頁"中,在查詢時需要進行額外的IO操作,既然如此,還不如當數據量小的數后,直接使用 VARCAHR 存儲,當數據量大的時候,使用外部文件存儲,然后使用 VARCHAR 存儲外部文件指針。

所以,我們可以簡單地把8K作為一個臨界點,總結為以下表格,各位小伙伴可根據表格自行選擇最優實現方案 (方案永遠沒有最好的,只有最合適的!)

決策因素數據庫字段緩存完整數據數據庫字段緩存文件指針
適用數據大小建議< 8KB≥ 8KB
用戶查詢頻率建議高、中、低頻率均可低頻率(< 10次/分鐘)
Redis 內存環境專屬 Redis/內存充足,內存充足,隨意使用多個項目或大型項目共享 Redis/內存緊張
實現復雜度對比簡單(直接緩存完整JSON數據)

中等(需文件存儲+指針管理)

如果是分布式系統多臺服務器,可能還需考慮文件共享

數據庫存儲建議對數據存儲無所謂,可接受大量數據直接存庫對數據庫要求較高,不希望大量公司數據占用數據庫存儲
響應時間對比1-5ms50-300ms
程序效率優先級????? (要求越快越好)?? (可接受百毫秒級延遲)

三. 接口選擇、接口分析

這里我們以對接啟信寶第三方接口為例,如下圖所示,可以發現官網對于接口有明確的標注接口ID——1.41 工商照面,查詢公司的基本信息。

從官網可以得出接口的基本信息

接口地址:https://api.qixin.com/APIService/enterprise/getBasicInfo

數據格式:JSON

請求方式:HTTP/HTTPS的GET請求

請求示例:https://api.qixin.com/APIService/enterprise/getBasicInfo?keyword=開平達豐紡織印染服裝有限公司

請求參數:Query,Query 參數內部有一個 keyword 屬性

響應參數:標準的 status 狀態碼參數、message 響應描述、data 數據參數以及一個獨有的數據簽名參數 sign。

綜合上述信息可以得知,要對接這個接口,至少需要創建一個請求參數Query,接口地址靜態變量 private static final url = "",獲取賬號和密鑰。

如果我們要對響應的數據進行操作,最好定義對應的 Java 實體類接收,然后通過 JSONObject.parse 轉化為對應的實體類。

四. 數據庫設計

經過上面一,二的分析,我們已經得出了結論,就是最為關鍵的"接口返回數據" 使用 VARCHAR 字段來存儲,那么我們就開始設計數據庫。

4.1 接口緩存表設計
4.1.1 建表SQL

每個字段的具體用處,小編都在注釋中進行了說明,很好理解。

如果直接存儲真實數據,則需要使用字段 "call_response_context";

如果存儲文件指針,則需要使用字段 "bucket_name" 和 "file_name";

為了方便我都提前定義出來拉!

CREATE TABLE `interface_cache` (`id`                    BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵自增ID,也可以用UUID,只要保證唯一即可,項目中無實際用處',`consumer_service_name` VARCHAR(50)     DEFAULT NULL COMMENT '調用服務名稱,如果用多個服務,方便以后對各個服務的真實調用次數做統計',`interface_id`          VARCHAR(20)     DEFAULT NULL COMMENT '接口唯一標識(ID),一般情況下官網都會有,這里指上面要對接的工商照面接口ID是1.41',`interface_name`        VARCHAR(100)    DEFAULT NULL COMMENT '第三方接口名稱,比如查詢公司詳情這里就是上面的"工商照面",可加可不加的字段,加上更易于理解接口名稱',`interface_param`       VARCHAR(255)    DEFAULT NULL COMMENT '接口入參,默認為公司名稱/公司社會唯一信用碼等,對應上方1.41接口的參數值keyword值',`bucket_name`           VARCHAR(50)     DEFAULT NULL COMMENT '若文件存儲在本地,則指代文件所在的全路徑名稱;若存儲于minio,則指代桶的名稱(方案二要使用的字段)',`file_name`             VARCHAR(50)     DEFAULT NULL COMMENT '存儲實際公司數據的文件名稱,建議由UUID工具生成(方案二要使用的字段)',`call_response_context` VARCHAR(8192)   DEFAULT NULL COMMENT '接口調用響應報文,因為有些接口不使用data存儲數據而是直接返回,所以我們直接存儲整個響應,對應1.41接口響應的四個參數(方案一使用的字段)',`call_input_time`       DATETIME        COMMENT              '接口調用時間,作為記錄',`create_time`           DATETIME        DEFAULT CURRENT_TIMESTAMP COMMENT '記錄創建時間,其實和接口調用時間一樣,個人感覺可加可不加',`expired_time`          DATETIME        COMMENT              '數據過期時間(可定期清理過期數據),默認3個月有效期,直接在接口調用時間字段值上+3個月有效期',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方接口調用返回數據緩存表';
4.1.2 查詢接口設計

后續我們查詢數據庫時,基本是通過 api_id + api_param + expired_time 來確認唯一一條已存在的公司數據。所以我們可以對這三個主要字段添加索引,這里就不詳細展示了。

Mapper 層接口如下所示,這里也可以直接使用實體傳參,我這里分成了三個,怎么寫都行

InterfaceCache findCacheByCondition(@Param("interfaceId") String interfaceId,@Param("interfaceParam") String interfaceParam,@Param("expiredTime") Date expiredTime
);
  <select id="findCacheByCondition" resultType="com.test.InterfaceCache">select *from interface_cachewhere interface_id = #{interfaceId}and interface_param = #{interfaceParam}and expired_time > #{expiredTime}</select>

插入SQL就直接繼承 mybatisplus 的單行記錄插入即可,就不多贅述了。

4.2 調用日志記錄表設計
4.2.1 建表SQL
CREATE TABLE `interface_active_record` (`id`                    BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵自增ID,也可以用UUID,只要保證唯一即可,項目中無實際作用',`consumer_service_name` VARCHAR(50)     DEFAULT NULL COMMENT '調用服務名稱,方便以后對各個服務的所有調用次數做統計(含緩存、數據庫調用)',`interface_id`          VARCHAR(20)     DEFAULT NULL COMMENT '接口唯一標識(ID),一般情況下官網都會有,這里指上面要對接的工商照面接口ID是1.41',`interface_name`        VARCHAR(100)    DEFAULT NULL COMMENT '第三方接口名稱,比如查詢公司詳情這里就是上面的"工商照面",可加可不加的字段,加上更易于理解接口名稱',`interface_param`       VARCHAR(255)    DEFAULT NULL COMMENT '接口入參,默認為公司名稱/公司社會唯一信用碼等,對應上方1.41接口的參數值keyword值',`is_actually_call`      VARCHAR(10)     DEFAULT NULL COMMENT '是否實際調用啟信查詢(N:否,Y:是),也可以用tinyint類型碼值0否,1是表示,看個人習慣',`call_description`      VARCHAR(255)    DEFAULT NULL COMMENT '接口調用響應描述(查詢緩存返回數據?或查詢數據庫返回數據?或實際調用返回數據?',`call_response_status`  VARCHAR(20)     DEFAULT NULL COMMENT '接口調用響應狀態碼,例如200,201,404等,對應上方1.41接口的相應參數status的值',`call_response_message` VARCHAR(50)     DEFAULT NULL COMMENT '接口調用響應信息,對應上方1.41接口的響應參數message的值',`create_time`           DATETIME        DEFAULT CURRENT_TIMESTAMP COMMENT '當前日志信息創建(插入)時間',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方接口調用日志記錄表';
4.2.2 查詢SQL

方便我們后續查詢日志表進行驗證,這里先把日志表查詢SQL寫出來;

# 1. 查詢最近的接口調用記錄
select * from interface_active_record order by id desc;
# 2. 查詢某個公司所有企查查接口的調用記錄
select * from interface_active_record where interface_param = '?' order by id desc;
# 3. 查詢某個公司特定的企查查接口調用記錄
select * from interface_active_record where interface_id = '?'and interface_param = '?' order by id desc;

五. 項目代碼落地實現

對于到底緩存完整公司數據,還是緩存文件指針,這都是我們請求成功后要做的操作,這兩種方案,其實最本質的區別就在于業務層Service方法的處理邏輯略有區別,其它比如控制器層 Controller 類,控制器接口請求參數 Entity 類,控制器返回通用參數類型 CommonResponseDTO 類都是一樣的。所以我們下面先把通用代碼創建出來,在再分別去寫兩種方案的Service業務層方法即可。我們開始吧!

5.1 通用代碼編寫
5.1.1 創建接口請求參數實體類
/*** 1.41 工商照面接口請求參數* */
@Data
public class BusinessDetailDTO {/*** 企業全名/注冊號/統一社會信用代碼* */private String keyword;
}
5.1.2 創建接口響應通用返回結果類
/*** 通用返回結果* */
@Data
public class CommonResponseDTO {// 響應狀態碼,直接獲取接口的響應狀態碼 status 的值private String status;// 響應消息描述,直接獲取接口的響應消息 message 的值private String message;// 響應數據,直接獲取接口的整個響應報文,即 status,message,data,sign 組成的JSON字符串private String context;
}
5.1.3 創建接口緩存表實體類
/*** 第三方接口調用返回數據緩存表*/
@Data
public class InterfaceCache {/*** 主鍵自增ID,也可以用UUID,只要保證唯一即可,項目中無實際用處*/private Long id;/*** 調用服務名稱,如果用多個服務,方便以后對各個服務的真實調用次數做統計*/private String consumerServiceName;/*** 接口唯一標識(ID),一般情況下官網都會有,這里指上面要對接的工商照面接口ID是1.41*/private String interfaceId;/*** 第三方接口名稱,比如查詢公司詳情這里就是上面的"工商照面",可加可不加的字段,加上更易于理解接口名稱*/private String interfaceName;/*** 接口入參,默認為公司名稱/公司社會唯一信用碼等,對應上方1.41接口的參數值keyword值*/private String interfaceParam;/*** 若文件存儲在本地,則指代文件所在的全路徑名稱;若存儲于minio,則指代桶的名稱(方案二要使用的字段)*/private String bucketName;/*** 存儲實際公司數據的文件名稱,建議由UUID工具生成(方案二要使用的字段)*/private String fileName;/*** 接口調用響應報文,因為有些接口不使用data存儲數據而是直接返回,所以我們直接存儲整個響應,對應1.41接口響應的四個參數(方案一使用的字段)*/private String callResponseContext;/*** 接口調用時間,作為記錄*/private Date callInputTime;/*** 記錄創建時間,其實和接口調用時間一樣,個人感覺可加可不加*/private Date createTime;/*** 數據過期時間(可定期清理過期數據),默認3個月有效期,直接在接口調用時間字段值上+3個月有效期*/private Date expiredTime;
}
5.1.4 創建接口緩存表Mapper接口

Mapper 接口不需要自定義

@Mapper
public interface InterfaceCacheMapper2 extends BaseMapper<InterfaceCache> {InterfaceCache findCacheByCondition(@Param("interfaceId") String interfaceId,@Param("interfaceParam") String interfaceParam,@Param("expiredTime") Date expiredTime);
}
5.1.5 創建接口活動日志表實體類
/*** 第三方接口調用日志記錄表*/
@Data
public class InterfaceActiveRecord {/*** 主鍵自增ID,也可以用UUID,只要保證唯一即可,項目中無實際作用*/private Long id;/*** 調用服務名稱,方便以后對各個服務的所有調用次數做統計(含緩存、數據庫調用)*/private String consumerServiceName;/*** 接口唯一標識(ID),一般情況下官網都會有,這里指上面要對接的工商照面接口ID是1.41*/private String interfaceId;/*** 第三方接口名稱,比如查詢公司詳情這里就是上面的"工商照面",可加可不加的字段,加上更易于理解接口名稱*/private String interfaceName;/*** 接口入參,默認為公司名稱/公司社會唯一信用碼等,對應上方1.41接口的參數值keyword值*/private String interfaceParam;/*** 是否實際調用啟信查詢(N:否,Y:是),也可以用tinyint類型碼值0否,1是表示,看個人習慣*/private String isActuallyCall;/*** 接口調用響應描述(查詢緩存返回數據?或查詢數據庫返回數據?或實際調用返回數據?*/private String callDescription;/*** 接口調用響應狀態碼,例如200,201,404等,對應上方1.41接口的相應參數status的值*/private String callResponseStatus;/*** 接口調用響應信息,對應上方1.41接口的響應參數message的值*/private String callResponseMessage;/*** 當前日志信息創建(插入)時間*/private Date createTime;
}
5.1.6 創建接口活動記錄表Mapper接口
@Mapper
public interface InterfaceActiveRecordMapper extends BaseMapper<InterfaceActiveRecord> {
}
5.1.7 創建 Controller?層控制器類
@RestController
@RequestMapping("/qx")
public class QxController {@Autowiredprivate QxService qxService;/*** @param businessDetailDTO 主要負責企業全名/注冊號/統一社會信用代碼* @return CommonResponseDTO 公共響應結果類對象*/@PostMapping("/getCompanyInfo")public CommonResponseDTO getCompanyInfo(@RequestBody BusinessDetailDTO businessDetailDTO,HttpServletRequest request) {return qxService.getCompanyInfo(businessDetailDTO,request);}
}
5.1.8 Service 層方法公共變量和方法創建
@Slf4j
@Service
public class QxService {// 接口活動跟蹤記錄Mapper@Autowiredprivate InterfaceActiveRecordMapper interfaceActiveRecordMapper;// 接口數據緩存Mapper@Autowiredprivate InterfaceCacheMapper interfaceCacheMapper;// redis緩存@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 發送網絡請求的restTemplate@Autowiredprivate RestTemplate restTemplate;// appkey,secret_key正常來講應該定義來yml文件中,這里懶省事,直接定義在業務類中private static final String appkey = "appkey";private static final String secret_key = "secret_key";// 緩存失效時間,默認7天,正常來講也應該定義在 yml 文件中,這里懶省事,直接定義在業務類中private static final int cacheDays = 7;// 下面這幾個靜態常量類正常來講應該定義在Constants常量類中,這里懶省事,直接定義在業務類中private static final String QX_GET_BUSINESS_DETAIL_URL = "https://api.qixin.com/APIService/enterprise/getBasicInfo";private static final String QX_GET_BUSINESS_DETAIL_CACHE_PREFIX = "QX:getBusinessDetail:";private static final String QX_GET_BUSINESS_DETAIL_INTERFACE_ID = "1.41:";private static final String QX_GET_BUSINESS_DETAIL_INTERFACE_NAME = "企業工商照面";/*** MD5加密方法,待會業務方法中會用到,提前定義出來。* 加密規則 :* appkey + timestamp + secret_key 組成的 32 位md5加密的小寫字符串(實際加密不帶入 ‘+’)* */private String getMD5(String appkey, String timestamp, String secret_key) {byte[] digest = null;try {MessageDigest md = MessageDigest.getInstance("MD5");String str = appkey + timestamp + secret_key;md.update(str.getBytes(StandardCharsets.UTF_8));digest = md.digest();StringBuilder hexString = new StringBuilder();for (byte b : digest) {// 保證兩位十六進制hexString.append(String.format("%02x", b));}return hexString.toString();}catch (Exception e){throw new RuntimeException("MD5加密失敗", e);}}/*** RestTemplate 發送請求方法,返回請求結果,待會業務方法中會用到,提前定義出來。* */private String httpsWithRestTemplate(String appkey, String timestamp, String secret_key,String url){// 創建請求頭HttpHeaders headers = new HttpHeaders();headers.set("Auth-Version", "2.0"); // 官網固定傳入2.0headers.set("appkey", appkey);headers.set("timestamp", timestamp);headers.set("sign", getMD5(appkey, timestamp, secret_key));// 封裝請求頭和空請求體HttpEntity<String> requestEntity = new HttpEntity<>(headers);// 發送 GET 請求ResponseEntity<String> responseEntity = restTemplate.exchange(url,HttpMethod.GET,requestEntity,String.class);// 獲取響應體return responseEntity.getBody();}/*** 生成完整的查詢工商照面接口地址方法* 啟信接口 -"查詢工商照面"* 官網接口ID:1.41* 接口地址:https://api.qixin.com/APIService/enterprise/getBasicInfo* 接口參數:keyword - 待查詢的企業名稱* 請求示例:https://api.qixin.com/APIService/enterprise/getBasicInfo?keyword=開平達豐紡織印染服裝有限公司* */private String getBusinessBasicDetailUrl(String keyword) {StringBuffer url = new StringBuffer();if (StringUtils.isNotBlank(keyword)){url.append(QX_GET_BUSINESS_DETAIL_URL).append("?keyword=").append(keyword);}return url.toString();}
}

5.2 Redis 緩存真實數據業務代碼實現

如下所示,就是小編個人編寫的一段緩存接口真實數據業務層代碼,僅供各位參考。

核心邏輯就三點:

第一:先查詢緩存,緩存命中則直接返回;

第二:緩存未命中,查詢數據庫,數據庫命中,回寫緩存并返回;

第三:緩存、數據庫均未命中,則發送網絡請求查詢數據,判斷響應結果,保存至數據庫并回寫緩存;

/*** 啟信接口查詢 - 1.41 工商照面* */
public CommonResponseDTO getCompanyInfo(BusinessDetailDTO businessDetailDTO,HttpServletRequest request) {// 創建方法返回對象CommonResponseDTO commonResponseDTO = new CommonResponseDTO();// 1. 創建接口活動跟蹤記錄對象并設置值InterfaceActiveRecord interfaceActiveRecord = new InterfaceActiveRecord();interfaceActiveRecord.setConsumerServiceName("XXXService"); // 服務名稱,正常情況下應該從request中獲取,這里寫的比較隨意interfaceActiveRecord.setInterfaceId(QX_GET_BUSINESS_DETAIL_INTERFACE_ID);interfaceActiveRecord.setInterfaceName(QX_GET_BUSINESS_DETAIL_INTERFACE_NAME);interfaceActiveRecord.setCreateTime(new Date());// 2. 獲取企業名稱參數,同時賦值給緩存key和接口活動跟蹤對象String cacheKey = businessDetailDTO.getKeyword();interfaceActiveRecord.setInterfaceParam(cacheKey);// 3. 查詢緩存,判斷緩存值是否為空,不為空直接返回結果String cacheCompanyJson = stringRedisTemplate.opsForValue().get(QX_GET_BUSINESS_DETAIL_CACHE_PREFIX + cacheKey);if (StringUtils.isNotBlank(cacheCompanyJson)) {// 返回結果JSONObject companyJSON = JSON.parseObject(cacheCompanyJson);commonResponseDTO.setStatus(companyJSON.getString("status"));commonResponseDTO.setMessage(companyJSON.getString("message"));commonResponseDTO.setContext(cacheCompanyJson);// 接口活動跟蹤記錄對象設置值interfaceActiveRecord.setIsActuallyCall("N");interfaceActiveRecord.setCallDescription("查詢Redis緩存返回公司數據");interfaceActiveRecord.setCallResponseStatus(companyJSON.getString("status"));interfaceActiveRecord.setCallResponseMessage(companyJSON.getString("message"));interfaceActiveRecordMapper.insertWithParam(interfaceActiveRecord);return commonResponseDTO;}// 4. 緩存為空,則查詢數據庫,判斷數據庫數據是否為空InterfaceCache interfaceCache = null;interfaceCache = interfaceCacheMapper.findCacheByCondition(QX_GET_BUSINESS_DETAIL_INTERFACE_ID,cacheKey,new Date());if(interfaceCache != null) {// 寫入緩存stringRedisTemplate.opsForValue().set(QX_GET_BUSINESS_DETAIL_CACHE_PREFIX + cacheKey,JSON.toJSONString(interfaceCache.getCallResponseContext()), cacheDays, TimeUnit.DAYS);// 返回結果JSONObject companyJSON = JSON.parseObject(interfaceCache.getCallResponseContext());commonResponseDTO.setStatus(companyJSON.getString("status"));commonResponseDTO.setMessage(companyJSON.getString("message"));commonResponseDTO.setContext(JSON.toJSONString(interfaceCache.getCallResponseContext()));// 接口活動跟蹤記錄對象設置值interfaceActiveRecord.setIsActuallyCall("N");interfaceActiveRecord.setCallDescription("查詢數據庫緩存返回公司數據");interfaceActiveRecord.setCallResponseStatus(companyJSON.getString("status"));interfaceActiveRecord.setCallResponseMessage(companyJSON.getString("message"));interfaceActiveRecordMapper.insertWithParam(interfaceActiveRecord);return commonResponseDTO;}// 5. 數據庫數據也為空,說明數據已過期或從未查詢過,則調用啟信官方接口查詢數據String response = null;InterfaceCache interfaceCacheInsert = new InterfaceCache();try {log.info("調用啟信查詢1.41 工商照面接口入參\n{}", cacheKey);// 發送網絡請求獲取響應數據response = httpsWithRestTemplate(appkey,String.valueOf(System.currentTimeMillis()),secret_key,getBusinessBasicDetailUrl(cacheKey));log.info("調用啟信查詢1.41 工商照面接口返回數據\n{}", response);// 不管是否成功,都返回接口響應commonResponseDTO.setStatus(JSON.parseObject(response).getString("status"));commonResponseDTO.setStatus(JSON.parseObject(response).getString("message"));commonResponseDTO.setContext(String.valueOf(JSON.parseObject(response)));// 不管是否成功,接口活動跟蹤記錄對象設置值interfaceActiveRecord.setCallResponseStatus(JSON.parseObject(response).getString("status"));interfaceActiveRecord.setCallResponseMessage(JSON.parseObject(response).getString("message"));interfaceActiveRecord.setIsActuallyCall("Y");interfaceActiveRecord.setCallDescription("調用啟信寶接口獲取數據");// 判斷狀態是否為200,只有200寫入數據庫和緩存。因為可能出現201-余額不足;202-查詢無結果等情況......if (response != null && "200".equals(JSON.parseObject(response).getString("status"))) {// 請求成功,將數據寫入緩存stringRedisTemplate.opsForValue().set(QX_GET_BUSINESS_DETAIL_CACHE_PREFIX + cacheKey, response, cacheDays, TimeUnit.DAYS);// 接口緩存對象賦值interfaceCacheInsert.setConsumerServiceName("XXXService");interfaceCacheInsert.setInterfaceId(QX_GET_BUSINESS_DETAIL_INTERFACE_ID);interfaceCacheInsert.setInterfaceName(QX_GET_BUSINESS_DETAIL_INTERFACE_NAME);interfaceCacheInsert.setInterfaceParam(cacheKey);interfaceCacheInsert.setBucketName(null);interfaceCacheInsert.setCallResponseContext(response);interfaceCacheInsert.setCallInputTime(new Date());interfaceCacheInsert.setCreateTime(new Date());interfaceCacheInsert.setExpiredTime(new Date(new Date().getTime() + 30L * 24 * 60 * 60 * 1000));// 將接口返回數據插入數據庫interfaceCacheMapper.insert(interfaceCacheInsert);}} catch (Exception e) {interfaceActiveRecord.setIsActuallyCall("N");interfaceActiveRecord.setCallDescription("調用啟信寶接口獲取數據失敗");interfaceActiveRecord.setCallResponseMessage(e.getMessage());throw new RuntimeException("企業基本信息數據寫入本地緩存發生錯誤",e);} finally {// 6. 插入接口活動跟蹤記錄,不管調用是否成功,都要進行記錄,放到 finally 塊中interfaceActiveRecordMapper.insert(interfaceActiveRecord);}return commonResponseDTO;
}

5.3 Redis 緩存文件指針,通過指針再讀取文件內容業務實現

因為要使用文件緩存接口響應數據,所以我們先寫一個保存數據的方法。注釋很詳細,不過多解釋啦。

這里我定義了一個文件前綴,就把文件保存到我的本地電腦磁盤上了,正常來講公司的生產項目,通常會存儲在運行項目的 Linux 服務器上,或者 minio 存儲中間件上,或者阿里云OSS云存儲等地方,這里就不整那么復雜啦,主要分享一個思路。同學們了解即可

// 緩存文件前綴,公司數據緩存文件存放在/data/gateway-server/cache目錄下。
private static final String CACHE_FILE_PREFIX = "/data/gateway-server/cache";/***  保存公司數據到文件并返回文件名*  @param response: 第三方接口返回的公司數據,返回 json 字符串*  return: 文件名和文件桶的Map集合···*/
private Map<String,String> saveDataToFile(String response) throws Exception{Map<String,String> map = new HashMap<>();// 緩存文件日期前綴,精確到月份SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");String date = sdf.format(new Date());// 隨機生成緩存文件名String fileName = UUID.randomUUID().toString().concat(".json");// 緩存文件保存的目錄String bucketName = CACHE_FILE_PREFIX.concat("/").concat(date).concat("/");// 若不存在則生成文件File dir = new File(bucketName);if(!dir.exists() && !dir.isDirectory()){dir.mkdirs();}// 緩存數據保存到本地磁盤try(// 創建一個`FileWriter`對象,用于向指定文件寫入字符數據FileWriter fileWriter = new FileWriter(bucketName.concat(fileName));// 創建一個`BufferedWriter`對象,包裝`FileWriter`以提高寫入效率,默認緩存大小為8192字節(8K),與我們上面分析的8K大小節點剛好相同BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)){// 執行實際寫入操作bufferedWriter.write(response);} catch (IOException e){log.error("寫入緩存數據失敗:\n{}", e.getMessage());}map.put("fileName",fileName);map.put("bucketName",bucketName);return map;
}

將數據緩存到本地磁盤文件之后,我們再寫一個從磁盤文件讀取數據的的方法。

/*** 通過文件指針獲取公司數據* @param bucketName 存儲桶名稱,或文件存儲路徑* @param fileName 文件名稱* return: 文件內容,公司數據,返回 json 字符串*/
public String getDataByFile(String bucketName, String fileName)  {File file = new File(bucketName + fileName);StringBuffer sbf = new StringBuffer();BufferedReader reader = null;String response = "";log.info("緩存json讀取的downloadPath:{}", bucketName + fileName);try {// 讀取文件數據reader = new BufferedReader(new FileReader(file));String tempStr;while ((tempStr = reader.readLine()) != null) {sbf.append(tempStr);}reader.close();response = sbf.toString();return response;} catch (Exception e) {log.error("獲取緩存數據失敗:\n{}", e.getMessage());} finally {if (reader != null) {try {reader.close();} catch (IOException e) {log.error("關閉緩存文件失敗:\n{}", e.getMessage());throw new RuntimeException(e);}}}return response;
}

編寫完畢上面的兩個方法,就可以正式來編寫業務邏輯代碼了,如下所示,大致邏輯其實和上面Redis 的思路一樣的,只是在獲取數據操作上加入了文件操作這一層,要求開發者對Java的IO代碼編寫有一定的基礎。

/*** 啟信接口查詢 - 1.41 工商照面* */
public CommonResponseDTO getCompanyInfo(BusinessDetailDTO businessDetailDTO,HttpServletRequest request) {// 1. 創建方法返回對象CommonResponseDTO commonResponseDTO = new CommonResponseDTO();// 1. 創建接口活動跟蹤記錄對象并設置值InterfaceActiveRecord interfaceActiveRecord = new InterfaceActiveRecord();interfaceActiveRecord.setConsumerServiceName("XXXService"); // 服務名稱,正常情況下應該從request中獲取,這里寫的比較隨意interfaceActiveRecord.setInterfaceId(QX_GET_BUSINESS_DETAIL_INTERFACE_ID);interfaceActiveRecord.setInterfaceName(QX_GET_BUSINESS_DETAIL_INTERFACE_NAME);interfaceActiveRecord.setCreateTime(new Date());// 2. 獲取企業名稱參數,同時賦值給緩存key和接口活動跟蹤對象String cacheKey = businessDetailDTO.getKeyword();interfaceActiveRecord.setInterfaceParam(cacheKey);// 3. 查詢緩存,判斷緩存值是否為空,不為空直接返回結果String interfaceCacheJson = stringRedisTemplate.opsForValue().get(QX_GET_BUSINESS_DETAIL_CACHE_PREFIX + cacheKey);InterfaceCache interfaceCache = null;String response = null;if (StringUtils.isNotBlank(interfaceCacheJson)) {// 緩存不為空,則返回緩存數據,轉化為 InterfaceCache 對象interfaceCache = JSON.parseObject(interfaceCacheJson, InterfaceCache.class);// 獲取桶名和文件名稱,從磁盤讀取文件數據response = getDataByFile(interfaceCache.getBucketName(), interfaceCache.getFileName());// 解析 response 為 json 格式數據JSONObject jsonObject = JSON.parseObject(response);commonResponseDTO.setStatus(jsonObject.getString("status"));commonResponseDTO.setMessage(jsonObject.getString("message"));commonResponseDTO.setContext(response);// 接口活動跟蹤記錄對象設置值interfaceActiveRecord.setIsActuallyCall("N");interfaceActiveRecord.setCallDescription("查詢Redis緩存返回公司數據");interfaceActiveRecord.setCallResponseStatus(jsonObject.getString("status"));interfaceActiveRecord.setCallResponseMessage(jsonObject.getString("message"));interfaceActiveRecordMapper.insertWithParam(interfaceActiveRecord);return commonResponseDTO;}// 4. 緩存為空,則查詢數據庫,判斷數據庫數據是否為空interfaceCache = interfaceCacheMapper.findCacheByCondition(QX_GET_BUSINESS_DETAIL_INTERFACE_ID,cacheKey,new Date());if(interfaceCache != null) {// 數據庫中有數據,根據桶名和文件名獲取數據response = getDataByFile(interfaceCache.getBucketName(), interfaceCache.getFileName());// 返回結果JSONObject companyJSON = JSON.parseObject(interfaceCache.getCallResponseContext());commonResponseDTO.setStatus(companyJSON.getString("status"));commonResponseDTO.setMessage(companyJSON.getString("message"));commonResponseDTO.setContext(response);// 寫入緩存stringRedisTemplate.opsForValue().set(QX_GET_BUSINESS_DETAIL_CACHE_PREFIX + cacheKey,JSON.toJSONString(interfaceCache), cacheDays, TimeUnit.DAYS);// 接口活動跟蹤記錄對象設置值interfaceActiveRecord.setIsActuallyCall("N");interfaceActiveRecord.setCallDescription("查詢數據庫緩存返回公司數據");interfaceActiveRecord.setCallResponseStatus(companyJSON.getString("status"));interfaceActiveRecord.setCallResponseMessage(companyJSON.getString("message"));interfaceActiveRecordMapper.insertWithParam(interfaceActiveRecord);return commonResponseDTO;}// 5. 數據庫數據也為空,說明數據已過期或從未查詢過,則調用啟信官方接口查詢數據InterfaceCache interfaceCacheInsert = new InterfaceCache();try {log.info("調用啟信查詢1.41 工商照面接口入參\n{}", cacheKey);// 發送網絡請求獲取響應數據response = httpsWithRestTemplate(appkey,String.valueOf(System.currentTimeMillis()),secret_key,getBusinessBasicDetailUrl(cacheKey));log.info("調用啟信查詢1.41 工商照面接口返回數據\n{}", response);// 不管是否成功,都返回接口響應commonResponseDTO.setStatus(JSON.parseObject(response).getString("status"));commonResponseDTO.setStatus(JSON.parseObject(response).getString("message"));commonResponseDTO.setContext(String.valueOf(JSON.parseObject(response)));// 不管是否成功,接口活動跟蹤記錄對象設置值interfaceActiveRecord.setCallResponseStatus(JSON.parseObject(response).getString("status"));interfaceActiveRecord.setCallResponseMessage(JSON.parseObject(response).getString("message"));interfaceActiveRecord.setIsActuallyCall("Y");interfaceActiveRecord.setCallDescription("調用啟信寶接口獲取數據");// 但判斷狀態是否為200,只有200寫入數據庫和緩存。因為可能出現201-余額不足;202-查詢無結果等情況......if (response != null && "200".equals(JSON.parseObject(response).getString("status"))) {// 保存接口返回數據到本地文件Map<String, String> map = saveDataToFile(response);// 接口緩存對象賦值interfaceCacheInsert.setConsumerServiceName("XXXService");interfaceCacheInsert.setInterfaceId(QX_GET_BUSINESS_DETAIL_INTERFACE_ID);interfaceCacheInsert.setInterfaceName(QX_GET_BUSINESS_DETAIL_INTERFACE_NAME);interfaceCacheInsert.setInterfaceParam(cacheKey);interfaceCacheInsert.setBucketName(map.get("bucketName"));interfaceCacheInsert.setFileName(map.get("fileName"));interfaceCacheInsert.setCallInputTime(new Date());interfaceCacheInsert.setCreateTime(new Date());interfaceCacheInsert.setExpiredTime(new Date(new Date().getTime() + 30L * 24 * 60 * 60 * 1000));// 將接口返回數據插入數據庫interfaceCacheMapper.insert(interfaceCacheInsert);// 插入數據庫之后,interfaceCacheInsert 對象是一條帶有主鍵ID值的完整數據,存入緩存stringRedisTemplate.opsForValue().set(QX_GET_BUSINESS_DETAIL_CACHE_PREFIX + cacheKey, JSON.toJSONString(interfaceCacheInsert), cacheDays, TimeUnit.DAYS);}} catch (Exception e) {interfaceActiveRecord.setIsActuallyCall("N");interfaceActiveRecord.setCallDescription("調用啟信寶接口獲取數據失敗");interfaceActiveRecord.setCallResponseMessage(e.getMessage());throw new RuntimeException("企業基本信息數據寫入本地緩存發生錯誤",e);} finally {// 6. 插入接口活動跟蹤記錄,不管調用是否成功,都要進行記錄,放到 finally 塊中interfaceActiveRecordMapper.insert(interfaceActiveRecord);}return commonResponseDTO;
}

5.4 二者混合使用

這一種方法,也不失為一種解決思路。

比如我們公司一共要對接10個企查查相關接口,有大接口返回大量數據(10~50K),有小接口返回少量數據(1~3K),此時我們就可以混合上面的兩種方法,大接口采用文件指針的解決思路,小接口采用緩存直接存儲的解決思路,可以達到部分接口提高響應效率,同時大接口又不會過度占用 Redis 內存。

代碼就不詳細舉例了,只是將上面兩種方案的代碼都復制使用即可。不過這種方法,做起來復雜度較高就是了,各位開發者同學可以根據司機項目需求的需要,選擇相對應的解決方案!

六. 簡要總結

綜上所述,可以簡單總結為以下三句話。

(1)?對接第三方SDK接口時,如果響應體較小,且希望提高服務器響應效率,則可以將接口響應整個存儲數據庫和 Redis 緩存,實現復雜度低,響應效率高,缺點是大量請求時,可能導致 Redis 內存占用較高;

(2) 如果響應體較大,建議將響應體數據緩存到磁盤文件或指定存儲服務器,將文件指針作為字段值存入數據庫,讀取文件時,先獲取文件指針,然后通過文件指針讀取文件緩存數據,響應給前端,缺點是因為需要進行文件IO操作,所以響應效率不如直接存儲 Redis;

(3) 也可以結合二者,大接口使用文件指針,小接口直接存儲 Redis,但編碼復雜度較高,后續維護可能會略顯復雜;

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

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

相關文章

Redis常用數據結構以及多并發場景下的使用分析:Hash類型

文章目錄前言hash 對比 String簡單存儲對象【秒殺系統】- 商品庫存管理【用戶會話管理】- 分布式Session存儲【信息預熱】- 首頁信息預熱降級策略總結前言 上文我們分析了String類型 在多并發下的應用 本文該輪到 Hash了&#xff0c;期不期待 兄弟們 hhh Redis常用數據結構以…

雙因子認證(2FA)是什么?從零設計一個安全的雙因子登錄接口

前言在信息系統逐漸走向數字化、云端化的今天&#xff0c;賬號密碼登錄已不再是足夠安全的手段。數據泄露、撞庫攻擊、社工手段頻發&#xff0c;僅靠「你知道的密碼」已不足以保證賬戶安全。因此&#xff0c;雙因子認證&#xff08;2FA, Two-Factor Authentication&#xff09;…

stack棧練習

為了你&#xff0c;我變成狼人模樣&#xff1b; 為了你&#xff0c;染上了瘋狂~ 目錄stack棧練習棧括號的分數單調棧模板框架小結下一個更大元素 I&#xff08;單調棧哈希&#xff09;接雨水stack棧練習 棧 一種先進后出的線性數據結構 具體用法可參考往期文章或者維基介紹i…

詳細頁智能解析算法:洞悉海量頁面數據的核心技術

詳細頁智能解析算法&#xff1a;突破網頁數據提取瓶頸的核心技術剖析引言&#xff1a;數字時代的數據采集革命在當今數據驅動的商業環境中&#xff0c;詳細頁數據已成為企業決策的黃金資源。無論是電商商品詳情、金融公告還是新聞資訊&#xff0c;??有效提取結構化信息??直…

ubuntu環境如何安裝matlab2016

一、下載安裝文件&#xff08;里面包含激活包CRACK&#xff09;可從度盤下載&#xff1a;鏈接:https://pan.baidu.com/s/1wxmVMzXiSY4RIT0dyKkjZg?pwd26h6 復制這段內容打開「百度網盤APP 即可獲取」注&#xff1a;這里面包含三個文件&#xff0c;其中ISO包含安裝文件&#x…

Mybits-plus 表關聯查詢,嵌套查詢,子查詢示例演示

在 MyBatis-Plus 中實現表關聯查詢、嵌套查詢和子查詢&#xff0c;通常需要結合 XML 映射文件或 Select 注解編寫自定義 SQL。以下是具體示例演示&#xff1a;示例場景 假設有兩張表&#xff1a; 用戶表 userCREATE TABLE user (id BIGINT PRIMARY KEY,name VARCHAR(50),age IN…

Stable Diffusion Web 環境搭建

默認你的系統Ubuntu、CUDA、Conda等都存在&#xff0c;即具備運行深度學習模型的基礎環境 本人&#xff1a;Ubuntu22.04、CUDA11.8環境搭建 克隆項目并且創建環境 https://github.com/AUTOMATIC1111/stable-diffusion-webui conda create -n sd python3.10運行過程自動安裝依賴…

嵌入式系統中實現串口重定向

在嵌入式系統中實現串口重定向&#xff08;將標準輸出如 printf 函數輸出重定向到串口&#xff09;通常有以下幾種常用方法&#xff0c;下面結合具體代碼示例和適用場景進行說明&#xff1a; 1. 重寫 fputc 函數&#xff08;最常見、最基礎的方法&#xff09; 通過重寫標準庫中…

static補充知識點-代碼

public class Student {private static int age;//靜態的變量private double score;//非靜態的方法public void run(){}public static void go(){}public static void main(String[] args) {new Student().run();Student.go();} } public class Person {//2 &#xff1a; 賦初始…

使用泛型<T>,模塊化,反射思想進行多表數據推送

需求&#xff1a;有13個表&#xff0c;其中一個主表和12細表&#xff0c;主表用來記錄推送狀態&#xff0c;細表記錄12種病例的詳細信息&#xff0c;現在需要把這12張病例表數據進行數據推送&#xff1b;普通方法需要寫12個方法分別去推送數據然后修改狀態&#xff1b;現在可以…

光流 | RAFT光流算法如何改進提升

RAFT(Recurrent All-Pairs Field Transforms)作為ECCV 2020最佳論文,已成為光流估計領域的標桿模型。其通過構建4D相關體金字塔和GRU迭代優化機制,在精度與泛化性上實現了突破。但針對其計算效率、大位移處理、跨場景泛化等問題,研究者提出了多維度改進方案,核心方向可系…

linux/ubuntu日志管理--/dev/log 的本質與作用

文章目錄 **一、基本概念****二、技術細節:UNIX域套接字****三、在不同日志系統中的角色****四、應用程序如何使用 `dev/log`****五、查看和驗證 `/dev/log`****六、總結 `/dev/log` 的核心作用**一、基本概念 /dev/log 是一個 UNIX域套接字(Unix Domain Socket),是Linux系…

EMC整改案例之(1):汽車NFC進入模塊BCI整改

EMC整改案例(1):汽車NFC進入模塊BCI整改 在汽車電子系統中,NFC(Near Field Communication)進入模塊用于實現無鑰匙進入功能,但它在電磁兼容(EMC)測試中常面臨挑戰。本案例聚焦于BCI(Bulk Current Injection)測試整改,該測試模擬大電流注入對設備的影響。以下是基于…

2025年INS SCI2區,靈活交叉變異灰狼算法GWO_C/M+集群任務調度,深度解析+性能實測

目錄1.摘要2.灰狼算法GWO原理3.靈活交叉變異灰狼算法GWO_C/M4.結果展示5.參考文獻6.代碼獲取7.算法輔導應用定制讀者交流1.摘要 隨著云計算的快速發展&#xff0c;受自然現象啟發的任務調度算法逐漸成為研究的熱點。灰狼算法&#xff08;GWO&#xff09;因其強大的收斂性和易于…

Java常用加密算法詳解與實戰代碼 - 附可直接運行的測試示例

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有堅忍不拔之志 &#x1f390; 個人CSND主頁——Micro麥可樂的博客 &#x1f425;《Docker實操教程》專欄以最新的Centos版本為基礎進行Docker實操教程&#xff0c;入門到實戰 &#x1f33a;《RabbitMQ》…

2025開發者工具鏈革命:AI賦能的效率躍遷

目錄引言&#xff1a;效率焦慮下的開發者生存現狀一、智能代碼編輯器&#xff1a;從輔助到主導的進化1.1 GitHub Copilot&#xff1a;全能型AI助手1.2 Cursor Pro&#xff1a;極致編碼體驗1.3 飛算JavaAI&#xff1a;垂直領域顛覆者二、版本控制革命&#xff1a;Git的AI進化論2…

“虛空”的物理、哲學悖論

一、虛空并非“完全真空”&#xff1a;量子場論揭示的“真空不空” 物理真空的本質 現代物理學中的“真空”并非絕對的空無一物&#xff0c;而是量子場的基態&#xff08;能量最低狀態&#xff09;。根據量子場論&#xff1a; 虛粒子漲落&#xff1a;真空中持續發生量子漲落&am…

CSP-S模擬賽二總結(實際難度大于CSP-S)

T1 很簡短&#xff0c;也很好做&#xff0c;第一題直接場切。 我的方法 首先要明確一件事&#xff1a;就是如果選了 ax,ya_{x,y}ax,y?&#xff0c;那么就必然要選 ay,xa_{y,x}ay,x?&#xff0c;所以第一步就在 ax,ya_{x,y}ax,y? 的基礎上加上 ay,xa_{y,x}ay,x?。 然后我…

旋轉屏幕優化

1.問題背景 從google原生算法&#xff0c;可以知道其有2個比較大的缺陷&#xff1a; 1) 通過重力傳感器傳來的x&#xff0c;y&#xff0c;z軸的加速度合成之后只有一個垂直往下的加速度&#xff0c;如果此時用戶在別的方向上有加速度&#xff0c;那么通過反余弦、反正切等計算…

Java---day2

七、IDEA開發工具 &#x1f4e6; 一、下載 IntelliJ IDEA 官網地址&#xff1a; &#x1f517; IntelliJ IDEA – the IDE for Pro Java and Kotlin Development 版本選擇&#xff1a; 版本說明Community Edition (CE)免費開源版本&#xff0c;適合 Java、Kotlin、Android…