Spring Boot實(shí)現(xiàn)數(shù)據(jù)訪問計(jì)數(shù)器方案詳解
1、數(shù)據(jù)訪問計(jì)數(shù)器
在Spring Boot項(xiàng)目中,有時(shí)需要數(shù)據(jù)訪問計(jì)數(shù)器。大致有下列三種情形:
1)純計(jì)數(shù):如登錄的密碼錯(cuò)誤計(jì)數(shù),超過門限N次,則表示計(jì)數(shù)器滿,此時(shí)可進(jìn)行下一步處理,如鎖定該賬戶。
2)時(shí)間滑動(dòng)窗口:設(shè)窗口寬度為T,如果窗口中尾幀時(shí)間與首幀時(shí)間差大于T,則表示計(jì)數(shù)器滿。
例如使用redis緩存時(shí),使用key查詢r(jià)edis中數(shù)據(jù),如果有此key數(shù)據(jù),則返回對(duì)象數(shù)據(jù);如無此key數(shù)據(jù),則查詢數(shù)據(jù)庫(kù),但如果一直都無此key數(shù)據(jù),從而反復(fù)查詢數(shù)據(jù)庫(kù),顯然有問題。此時(shí),可使用時(shí)間滑動(dòng)窗口,對(duì)于查詢的失敗的key,距離首幀T時(shí)間(如1分鐘)內(nèi),不再查詢數(shù)據(jù)庫(kù),而是直接返回?zé)o此數(shù)據(jù),直到新查詢的時(shí)間超過T,更新滑窗首幀為新時(shí)間,并執(zhí)行一次查詢數(shù)據(jù)庫(kù)操作。
3)時(shí)間滑動(dòng)窗口+計(jì)數(shù):這往往在需要進(jìn)行限流處理的場(chǎng)景使用。如T時(shí)間(如1分鐘)內(nèi),相同key的訪問次數(shù)超過超過門限N,則表示計(jì)數(shù)器滿,此時(shí)進(jìn)行限流處理。
2、代碼實(shí)現(xiàn)
2.1、方案說明
1)使用字典來管理不同的key,因?yàn)椴煌膋ey需要單獨(dú)計(jì)數(shù)。
2)上述三種情況,使用類型屬性區(qū)分,并在構(gòu)造函數(shù)中進(jìn)行設(shè)置。
3)滑動(dòng)窗口使用雙向隊(duì)列Deque來實(shí)現(xiàn)。
4)考慮到訪問并發(fā)性,讀取或更新時(shí),加鎖保護(hù)。
2.2、代碼
package com.abc.example.service;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
/**
* @className : DacService
* @description : 數(shù)據(jù)訪問計(jì)數(shù)服務(wù)類
* @summary :
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/03 1.0.0 sheng.zheng 初版
*
*/
public class DacService {
// 計(jì)數(shù)器類型:1-數(shù)量;2-時(shí)間窗口;3-時(shí)間窗口+數(shù)量
private int counterType;
// 計(jì)數(shù)器數(shù)量門限
private int counterThreshold = 5;
// 時(shí)間窗口長(zhǎng)度,單位毫秒
private int windowSize = 60000;
// 對(duì)象key的訪問計(jì)數(shù)器
private Map<String,Integer> itemMap;
// 對(duì)象key的訪問滑動(dòng)窗口
private Map<String,Deque<Long>> itemSlideWindowMap;
/**
* 構(gòu)造函數(shù)
* @param counterType : 計(jì)數(shù)器類型,值為1,2,3之一
* @param counterThreshold : 計(jì)數(shù)器數(shù)量門限,如果類型為1或3,需要此值
* @param windowSize : 窗口時(shí)間長(zhǎng)度,如果為類型為2,3,需要此值
*/
public DacService(int counterType, int counterThreshold, int windowSize) {
this.counterType = counterType;
this.counterThreshold = counterThreshold;
this.windowSize = windowSize;
if (counterType == 1) {
// 如果與計(jì)數(shù)器有關(guān)
itemMap = new HashMap<String,Integer>();
}else if (counterType == 2 || counterType == 3) {
// 如果與滑動(dòng)窗口有關(guān)
itemSlideWindowMap = new HashMap<String,Deque<Long>>();
}
}
/**
*
* @methodName : isItemKeyFull
* @description : 對(duì)象key的計(jì)數(shù)是否將滿
* @param itemKey : 對(duì)象key
* @param timeMillis : 時(shí)間戳,毫秒數(shù),如為滑窗類計(jì)數(shù)器,使用此參數(shù)值
* @return : 滿返回true,否則返回false
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/03 1.0.0 sheng.zheng 初版
* 2021/08/08 1.0.1 sheng.zheng 支持多種類型計(jì)數(shù)器
*
*/
public boolean isItemKeyFull(String itemKey,Long timeMillis) {
boolean bRet = false;
if (this.counterType == 1) {
// 如果為計(jì)數(shù)器類型
if (itemMap.containsKey(itemKey)) {
synchronized(itemMap) {
Integer value = itemMap.get(itemKey);
// 如果計(jì)數(shù)器將超越門限
if (value >= this.counterThreshold - 1) {
bRet = true;
}
}
}else {
// 新的對(duì)象key,視業(yè)務(wù)需要,取值true或false
bRet = true;
}
}else if(this.counterType == 2){
// 如果為滑窗類型
if (itemSlideWindowMap.containsKey(itemKey)) {
Deque<Long> itemQueue = itemSlideWindowMap.get(itemKey);
synchronized(itemQueue) {
if (itemQueue.size() > 0) {
Long head = itemQueue.getFirst();
if (timeMillis - head >= this.windowSize) {
// 如果窗口將滿
bRet = true;
}
}
}
}else {
// 新的對(duì)象key,視業(yè)務(wù)需要,取值true或false
bRet = true;
}
}else if(this.counterType == 3){
// 如果為滑窗+數(shù)量類型
if (itemSlideWindowMap.containsKey(itemKey)) {
Deque<Long> itemQueue = itemSlideWindowMap.get(itemKey);
synchronized(itemQueue) {
Long head = 0L;
// 循環(huán)處理頭部數(shù)據(jù),確保新數(shù)據(jù)幀加入后,維持窗口寬度
while(true) {
// 取得頭部數(shù)據(jù)
head = itemQueue.peekFirst();
if (head == null || timeMillis - head <= this.windowSize) {
break;
}
// 移除頭部
itemQueue.remove();
}
if (itemQueue.size() >= this.counterThreshold -1) {
// 如果窗口數(shù)量將滿
bRet = true;
}
}
}else {
// 新的對(duì)象key,視業(yè)務(wù)需要,取值true或false
bRet = true;
}
}
return bRet;
}
/**
*
* @methodName : resetItemKey
* @description : 復(fù)位對(duì)象key的計(jì)數(shù)
* @param itemKey : 對(duì)象key
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/03 1.0.0 sheng.zheng 初版
* 2021/08/08 1.0.1 sheng.zheng 支持多種類型計(jì)數(shù)器
*
*/
public void resetItemKey(String itemKey) {
if (this.counterType == 1) {
// 如果為計(jì)數(shù)器類型
if (itemMap.containsKey(itemKey)) {
// 更新值,加鎖保護(hù)
synchronized(itemMap) {
itemMap.put(itemKey, 0);
}
}
}else if(this.counterType == 2){
// 如果為滑窗類型
// 清空
if (itemSlideWindowMap.containsKey(itemKey)) {
Deque<Long> itemQueue = itemSlideWindowMap.get(itemKey);
if (itemQueue.size() > 0) {
// 加鎖保護(hù)
synchronized(itemQueue) {
// 清空
itemQueue.clear();
}
}
}
}else if(this.counterType == 3){
// 如果為滑窗+數(shù)量類型
if (itemSlideWindowMap.containsKey(itemKey)) {
Deque<Long> itemQueue = itemSlideWindowMap.get(itemKey);
synchronized(itemQueue) {
// 清空
itemQueue.clear();
}
}
}
}
/**
*
* @methodName : putItemkey
* @description : 更新對(duì)象key的計(jì)數(shù)
* @param itemKey : 對(duì)象key
* @param timeMillis : 時(shí)間戳,毫秒數(shù),如為滑窗類計(jì)數(shù)器,使用此參數(shù)值
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/03 1.0.0 sheng.zheng 初版
* 2021/08/08 1.0.1 sheng.zheng 支持多種類型計(jì)數(shù)器
*
*/
public void putItemkey(String itemKey,Long timeMillis) {
if (this.counterType == 1) {
// 如果為計(jì)數(shù)器類型
if (itemMap.containsKey(itemKey)) {
// 更新值,加鎖保護(hù)
synchronized(itemMap) {
Integer value = itemMap.get(itemKey);
// 計(jì)數(shù)器+1
value ++;
itemMap.put(itemKey, value);
}
}else {
// 新key值,加鎖保護(hù)
synchronized(itemMap) {
itemMap.put(itemKey, 1);
}
}
}else if(this.counterType == 2){
// 如果為滑窗類型
if (itemSlideWindowMap.containsKey(itemKey)) {
Deque<Long> itemQueue = itemSlideWindowMap.get(itemKey);
// 加鎖保護(hù)
synchronized(itemQueue) {
// 加入
itemQueue.add(timeMillis);
}
}else {
// 新key值,加鎖保護(hù)
Deque<Long> itemQueue = new ArrayDeque<Long>();
synchronized(itemSlideWindowMap) {
// 加入映射表
itemSlideWindowMap.put(itemKey, itemQueue);
itemQueue.add(timeMillis);
}
}
}else if(this.counterType == 3){
// 如果為滑窗+數(shù)量類型
if (itemSlideWindowMap.containsKey(itemKey)) {
Deque<Long> itemQueue = itemSlideWindowMap.get(itemKey);
// 加鎖保護(hù)
synchronized(itemQueue) {
Long head = 0L;
// 循環(huán)處理頭部數(shù)據(jù)
while(true) {
// 取得頭部數(shù)據(jù)
head = itemQueue.peekFirst();
if (head == null || timeMillis - head <= this.windowSize) {
break;
}
// 移除頭部
itemQueue.remove();
}
// 加入新數(shù)據(jù)
itemQueue.add(timeMillis);
}
}else {
// 新key值,加鎖保護(hù)
Deque<Long> itemQueue = new ArrayDeque<Long>();
synchronized(itemSlideWindowMap) {
// 加入映射表
itemSlideWindowMap.put(itemKey, itemQueue);
itemQueue.add(timeMillis);
}
}
}
}
/**
*
* @methodName : clear
* @description : 清空字典
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/03 1.0.0 sheng.zheng 初版
* 2021/08/08 1.0.1 sheng.zheng 支持多種類型計(jì)數(shù)器
*
*/
public void clear() {
if (this.counterType == 1) {
// 如果為計(jì)數(shù)器類型
synchronized(this) {
itemMap.clear();
}
}else if(this.counterType == 2){
// 如果為滑窗類型
synchronized(this) {
itemSlideWindowMap.clear();
}
}else if(this.counterType == 3){
// 如果為滑窗+數(shù)量類型
synchronized(this) {
itemSlideWindowMap.clear();
}
}
}
}
2.3、調(diào)用
要調(diào)用計(jì)數(shù)器,只需在應(yīng)用類中添加DacService對(duì)象,如:
public class DataCommonService {
// 數(shù)據(jù)訪問計(jì)數(shù)服務(wù)類,時(shí)間滑動(dòng)窗口,窗口寬度60秒
protected DacService dacService = new DacService(2,0,60000);
/**
*
* @methodName : procNoClassData
* @description : 對(duì)象組key對(duì)應(yīng)的數(shù)據(jù)不存在時(shí)的處理
* @param classKey : 對(duì)象組key
* @return : 數(shù)據(jù)加載成功,返回true,否則為false
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/08 1.0.0 sheng.zheng 初版
*
*/
protected boolean procNoClassData(Object classKey) {
boolean bRet = false;
String key = getCombineKey(null,classKey);
Long currentTime = System.currentTimeMillis();
// 判斷計(jì)數(shù)器是否將滿
if (dacService.isItemKeyFull(key,currentTime)) {
// 如果計(jì)數(shù)將滿
// 復(fù)位
dacService.resetItemKey(key);
// 從數(shù)據(jù)庫(kù)加載分組數(shù)據(jù)項(xiàng)
bRet = loadGroupItems(classKey);
}
dacService.putItemkey(key,currentTime);
return bRet;
}
/**
*
* @methodName : procNoItemData
* @description : 對(duì)象key對(duì)應(yīng)的數(shù)據(jù)不存在時(shí)的處理
* @param itemKey : 對(duì)象key
* @param classKey : 對(duì)象組key
* @return : 數(shù)據(jù)加載成功,返回true,否則為false
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/08 1.0.0 sheng.zheng 初版
*
*/
protected boolean procNoItemData(Object itemKey, Object classKey) {
// 如果itemKey不存在
boolean bRet = false;
String key = getCombineKey(itemKey,classKey);
Long currentTime = System.currentTimeMillis();
if (dacService.isItemKeyFull(key,currentTime)) {
// 如果計(jì)數(shù)將滿
// 復(fù)位
dacService.resetItemKey(key);
// 從數(shù)據(jù)庫(kù)加載數(shù)據(jù)項(xiàng)
bRet = loadItem(itemKey, classKey);
}
dacService.putItemkey(key,currentTime);
return bRet;
}
/**
*
* @methodName : getCombineKey
* @description : 獲取組合key值
* @param itemKey : 對(duì)象key
* @param classKey : 對(duì)象組key
* @return : 組合key
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/08/08 1.0.0 sheng.zheng 初版
*
*/
protected String getCombineKey(Object itemKey, Object classKey) {
String sItemKey = (itemKey == null ? "" : itemKey.toString());
String sClassKey = (classKey == null ? "" : classKey.toString());
String key = "";
if (!sClassKey.isEmpty()) {
key = sClassKey;
}
if (!sItemKey.isEmpty()) {
if (!key.isEmpty()) {
key += "-" + sItemKey;
}else {
key = sItemKey;
}
}
return key;
}
}
procNoClassData方法:分組數(shù)據(jù)不存在時(shí)的處理。procNoItemData方法:?jiǎn)蝹€(gè)數(shù)據(jù)項(xiàng)不存在時(shí)的處理。
主從關(guān)系在數(shù)據(jù)庫(kù)中,較為常見,因此針對(duì)分組數(shù)據(jù)和單個(gè)對(duì)象key分別編寫了方法;如果key的個(gè)數(shù)超過2個(gè),可以類似處理。
作者:阿拉伯1999 出處:http://www.cnblogs.com/alabo1999/ 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利. 養(yǎng)成良好習(xí)慣,好文章隨手頂一下。
到此這篇關(guān)于Spring Boot實(shí)現(xiàn)數(shù)據(jù)訪問計(jì)數(shù)器方案詳解的文章就介紹到這了,更多相關(guān)Spring Boot數(shù)據(jù)訪問計(jì)數(shù)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JSP 獲取spring容器中bean的兩種方法總結(jié)
這篇文章主要介紹了JSP 獲取spring容器中bean的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-05-05
J2EE Servlet基礎(chǔ)在瀏覽器上運(yùn)行HelloServlet的方法
這篇文章主要介紹了J2EE Servlet基礎(chǔ)在瀏覽器上運(yùn)行HelloServlet的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
Spring Web零xml配置原理以及父子容器關(guān)系詳解
這篇文章主要介紹了Spring Web零xml配置原理以及父子容器關(guān)系詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Maven根據(jù)不同環(huán)境打包不同配置文件的方法
這篇文章主要介紹了Maven根據(jù)不同環(huán)境打包不同配置文件的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
詳解MyBatis的Dao層實(shí)現(xiàn)和配置文件深入
這篇文章主要為大家詳細(xì)介紹了MyBatis的Dao層實(shí)現(xiàn)和配置文件深入,文中的示例代碼講解詳細(xì),感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)一下2022-07-07
Java數(shù)據(jù)結(jié)構(gòu)與算法之稀疏數(shù)組與隊(duì)列深入理解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)與算法之稀疏數(shù)組與隊(duì)列深入理解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
SpringSecurity實(shí)現(xiàn)登陸認(rèn)證并返回token方式
這篇文章主要介紹了SpringSecurity實(shí)現(xiàn)登陸認(rèn)證并返回token方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Javas使用Redlock實(shí)現(xiàn)分布式鎖過程解析
這篇文章主要介紹了Javas使用Redlock實(shí)現(xiàn)分布式鎖過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Java實(shí)現(xiàn)Socket服務(wù)端與客戶端雙向通信功能
大家好,由于工作上業(yè)務(wù)的需要,在java項(xiàng)目中引入了socket通信,特此記錄一下,用以備份,本文章中的socket通信實(shí)現(xiàn)了,服務(wù)端與客戶端的雙向通訊,以及二者之間的心跳通信,服務(wù)端重啟之后,客戶端的自動(dòng)重連功能,需要的朋友可以參考下2025-04-04

