1、數(shù)據(jù)訪問(wèn)計(jì)數(shù)器
在Spring Boot項(xiàng)目中,有時(shí)需要數(shù)據(jù)訪問(wèn)計(jì)數(shù)器。大致有下列三種情形:
1)純計(jì)數(shù):如登錄的密碼錯(cuò)誤計(jì)數(shù),超過(guò)門限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ù);如無(wú)此key數(shù)據(jù),則查詢數(shù)據(jù)庫(kù),但如果一直都無(wú)此key數(shù)據(jù),從而反復(fù)查詢數(shù)據(jù)庫(kù),顯然有問(wèn)題。此時(shí),可使用時(shí)間滑動(dòng)窗口,對(duì)于查詢的失敗的key,距離首幀T時(shí)間(如1分鐘)內(nèi),不再查詢數(shù)據(jù)庫(kù),而是直接返回?zé)o此數(shù)據(jù),直到新查詢的時(shí)間超過(guò)T,更新滑窗首幀為新時(shí)間,并執(zhí)行一次查詢數(shù)據(jù)庫(kù)操作。
3)時(shí)間滑動(dòng)窗口+計(jì)數(shù):這往往在需要進(jìn)行限流處理的場(chǎng)景使用。如T時(shí)間(如1分鐘)內(nèi),相同key的訪問(wèn)次數(shù)超過(guò)超過(guò)門限N,則表示計(jì)數(shù)器滿,此時(shí)進(jìn)行限流處理。
2、代碼實(shí)現(xiàn)
2.1、方案說(shuō)明
1)使用字典來(lái)管理不同的key,因?yàn)椴煌膋ey需要單獨(dú)計(jì)數(shù)。
2)上述三種情況,使用類型屬性區(qū)分,并在構(gòu)造函數(shù)中進(jìn)行設(shè)置。
3)滑動(dòng)窗口使用雙向隊(duì)列Deque來(lái)實(shí)現(xiàn)。
4)考慮到訪問(wèn)并發(fā)性,讀取或更新時(shí),加鎖保護(hù)。
2.2、代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
|
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ù)訪問(wèn)計(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的訪問(wèn)計(jì)數(shù)器 private Map <String,Integer> itemMap; / / 對(duì)象key的訪問(wèn)滑動(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ì)象,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
public class DataCommonService { / / 數(shù)據(jù)訪問(wèn)計(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ù)中,較為常見(jiàn),因此針對(duì)分組數(shù)據(jù)和單個(gè)對(duì)象key分別編寫了方法;如果key的個(gè)數(shù)超過(guò)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ù)訪問(wèn)計(jì)數(shù)器方案詳解的文章就介紹到這了,更多相關(guān)Spring Boot數(shù)據(jù)訪問(wèn)計(jì)數(shù)器內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://www.cnblogs.com/alabo1999/p/15115695.html