Java實(shí)現(xiàn)限流接口的示例詳解
一、限流
為什么要進(jìn)行限流?
1.瞬時(shí)流量過(guò)高,服務(wù)被壓垮?
2.惡意用戶高頻光顧,導(dǎo)致服務(wù)器宕機(jī)?
3.消息消費(fèi)過(guò)快,導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大,性能下降甚至崩潰?
……
什么是限流?
限流是對(duì)某一時(shí)間窗口內(nèi)的請(qǐng)求數(shù)進(jìn)行限制,保持系統(tǒng)的可用性和穩(wěn)定性,防止因流量暴增而導(dǎo)致的系統(tǒng)運(yùn)行緩慢或宕機(jī)。
在高并發(fā)系統(tǒng)中,出于系統(tǒng)保護(hù)角度考慮,通常會(huì)對(duì)流量進(jìn)行限流。
在分布式系統(tǒng)中,高并發(fā)場(chǎng)景下,為了防止系統(tǒng)因突然的流量激增而導(dǎo)致的崩潰,同時(shí)保證服務(wù)的高可用性和穩(wěn)定性,限流是最常用的手段。
有哪些限流算法?
常見(jiàn)的四種限流算法,分別是:固定窗口算法、滑動(dòng)窗口算法、漏桶算法、令牌桶算法。
二、限流算法
固定窗口
實(shí)現(xiàn)原理
固定窗口又稱固定窗口(又稱計(jì)數(shù)器算法,F(xiàn)ixed Window)限流算法,是最簡(jiǎn)單的限流算法。
實(shí)現(xiàn)原理:在指定周期內(nèi)累加訪問(wèn)次數(shù),當(dāng)訪問(wèn)次數(shù)達(dá)到設(shè)定的閾值時(shí),觸發(fā)限流策略,當(dāng)進(jìn)入下一個(gè)時(shí)間周期時(shí)進(jìn)行訪問(wèn)次數(shù)的清零。如圖所示,我們要求3秒內(nèi)的請(qǐng)求不要超過(guò)150次:
代碼實(shí)現(xiàn)
public class FixedWindowRateLimiter { Logger logger = LoggerFactory.getLogger(FixedWindowRateLimiter.class); //時(shí)間窗口大小,單位毫秒 long windowSize; //允許通過(guò)的請(qǐng)求數(shù) int maxRequestCount; //當(dāng)前窗口通過(guò)的請(qǐng)求數(shù) AtomicInteger counter = new AtomicInteger(0); //窗口右邊界 long windowBorder; public FixedWindowRateLimiter(long windowSize, int maxRequestCount) { this.windowSize = windowSize; this.maxRequestCount = maxRequestCount; this.windowBorder = System.currentTimeMillis() + windowSize; } public synchronized boolean tryAcquire() { long currentTime = System.currentTimeMillis(); if (windowBorder < currentTime) { logger.info("window reset"); do { windowBorder += windowSize; } while (windowBorder < currentTime); counter = new AtomicInteger(0); } if (counter.intValue() < maxRequestCount) { counter.incrementAndGet(); logger.info("tryAcquire success"); return true; } else { logger.info("tryAcquire fail"); return false; } } }
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,容易理解
缺點(diǎn):
1.限流不夠平滑。例如:限流是每秒3個(gè),在第一毫秒發(fā)送了3個(gè)請(qǐng)求,達(dá)到限流,窗口剩余時(shí)間的請(qǐng)求都將會(huì)被拒絕,體驗(yàn)不好。
2.無(wú)法處理窗口邊界問(wèn)題。因?yàn)槭窃谀硞€(gè)時(shí)間窗口內(nèi)進(jìn)行流量控制,所以可能會(huì)出現(xiàn)窗口邊界效應(yīng),即在時(shí)間窗口的邊界處可能會(huì)有大量的請(qǐng)求被允許通過(guò),從而導(dǎo)致突發(fā)流量。即:如果第2到3秒內(nèi)產(chǎn)生了150次請(qǐng)求,而第3到4秒內(nèi)產(chǎn)生了150次請(qǐng)求,那么其實(shí)在第2秒到第4秒這兩秒內(nèi),就已經(jīng)發(fā)生了300次請(qǐng)求了,遠(yuǎn)遠(yuǎn)大于我們要求的3秒內(nèi)的請(qǐng)求不要超過(guò)150次這個(gè)限制,如下圖所示:
滑動(dòng)窗口
實(shí)現(xiàn)原理
滑動(dòng)窗口為固定窗口的改良版,解決了固定窗口在窗口切換時(shí)會(huì)受到兩倍于閾值數(shù)量的請(qǐng)求。在滑動(dòng)窗口算法中,窗口的起止時(shí)間是動(dòng)態(tài)的,窗口的大小固定。這種算法能夠較好地處理窗口邊界問(wèn)題,但是實(shí)現(xiàn)相對(duì)復(fù)雜,需要記錄每個(gè)請(qǐng)求的時(shí)間戳。
實(shí)現(xiàn)原理:滑動(dòng)窗口在固定窗口的基礎(chǔ)上,將時(shí)間窗口進(jìn)行了更精細(xì)的分片,將一個(gè)窗口分為若干個(gè)等份的小窗口,每次僅滑動(dòng)一小塊的時(shí)間。每個(gè)小窗口對(duì)應(yīng)不同的時(shí)間點(diǎn),擁有獨(dú)立的計(jì)數(shù)器,當(dāng)請(qǐng)求的時(shí)間點(diǎn)大于當(dāng)前窗口的最大時(shí)間點(diǎn)時(shí),則將窗口向前平移一個(gè)小窗口(將第一個(gè)小窗口的數(shù)據(jù)舍棄,第二個(gè)小窗口變成第一個(gè)小窗口,當(dāng)前請(qǐng)求放在最后一個(gè)小窗口),整個(gè)窗口的所有請(qǐng)求數(shù)相加不能大于閾值。其中,Sentinel就是采用滑動(dòng)窗口算法來(lái)實(shí)現(xiàn)限流的。如圖所示:
核心步驟:
1.把3秒鐘劃分為3個(gè)小窗,每個(gè)小窗限制請(qǐng)求不能超過(guò)50秒。
2.比如我們?cè)O(shè)置,3秒內(nèi)不能超過(guò)150個(gè)請(qǐng)求,那么這個(gè)窗口就可以容納3個(gè)小窗,并且隨著時(shí)間推移,往前滑動(dòng)。每次請(qǐng)求過(guò)來(lái)后,都要統(tǒng)計(jì)滑動(dòng)窗口內(nèi)所有小窗的請(qǐng)求總量。
代碼實(shí)現(xiàn)
public class SlidingWindowRateLimiter { Logger logger = LoggerFactory.getLogger(FixedWindowRateLimiter.class); //時(shí)間窗口大小,單位毫秒 long windowSize; //分片窗口數(shù) int shardNum; //允許通過(guò)的請(qǐng)求數(shù) int maxRequestCount; //各個(gè)窗口內(nèi)請(qǐng)求計(jì)數(shù) int[] shardRequestCount; //請(qǐng)求總數(shù) int totalCount; //當(dāng)前窗口下標(biāo) int shardId; //每個(gè)小窗口大小,毫秒 long tinyWindowSize; //窗口右邊界 long windowBorder; public SlidingWindowRateLimiter(long windowSize, int shardNum, int maxRequestCount) { this.windowSize = windowSize; this.shardNum = shardNum; this.maxRequestCount = maxRequestCount; this.shardRequestCount = new int[shardNum]; this.tinyWindowSize = windowSize / shardNum; this.windowBorder = System.currentTimeMillis(); } public synchronized boolean tryAcquire() { long currentTime = System.currentTimeMillis(); if (windowBorder < currentTime) { logger.info("window reset"); do { shardId = (++shardId) % shardNum; totalCount -= shardRequestCount[shardId]; shardRequestCount[shardId] = 0; windowBorder += tinyWindowSize; } while (windowBorder < currentTime); } if (totalCount < maxRequestCount) { logger.info("tryAcquire success:{}", shardId); shardRequestCount[shardId]++; totalCount++; return true; } else { logger.info("tryAcquire fail"); return false; } } }
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):解決了固定窗口算法的窗口邊界問(wèn)題,避免突發(fā)流量壓垮服務(wù)器。
缺點(diǎn):還是存在限流不夠平滑的問(wèn)題。例如:限流是每秒3個(gè),在第一毫秒發(fā)送了3個(gè)請(qǐng)求,達(dá)到限流,剩余窗口時(shí)間的請(qǐng)求都將會(huì)被拒絕,體驗(yàn)不好。
漏桶算法
實(shí)現(xiàn)原理
漏桶限流算法是一種常用的流量整形(Traffic Shaping)和流量控制(Traffic Policing)的算法,它可以有效地控制數(shù)據(jù)的傳輸速率以及防止網(wǎng)絡(luò)擁塞。
主要的作用:
a.控制數(shù)據(jù)注入網(wǎng)絡(luò)的速度。
b.平滑網(wǎng)絡(luò)上的突發(fā)流量
實(shí)現(xiàn)原理:
漏桶是一個(gè)很形象的比喻,外部請(qǐng)求就像是水一樣不斷注入水桶中,而水桶已經(jīng)設(shè)置好了最大出水速率,漏桶會(huì)以這個(gè)速率勻速放行請(qǐng)求,而當(dāng)水超過(guò)桶的最大容量后則被丟棄。不管上面的水流速度有多塊,漏桶水滴的流出速度始終保持不變。消息中間件就采用的漏桶限流的思想。如圖所示:
核心步驟:
a.一個(gè)固定容量的漏桶,按照固定速率出水(處理請(qǐng)求);
b.當(dāng)流入水(請(qǐng)求數(shù)量)的速度過(guò)大會(huì)直接溢出(請(qǐng)求數(shù)量超過(guò)限制則直接拒絕)。
c.桶里的水(請(qǐng)求)不夠則無(wú)法出水(桶內(nèi)沒(méi)有請(qǐng)求則不處理)。
代碼實(shí)現(xiàn)
public class LeakyBucketRateLimiter { Logger logger = LoggerFactory.getLogger(LeakyBucketRateLimiter.class); //桶的容量 int capacity; //桶中現(xiàn)存水量 AtomicInteger water = new AtomicInteger(); //開(kāi)始漏水時(shí)間 long leakTimestamp; //水流出的速率,即每秒允許通過(guò)的請(qǐng)求數(shù) int leakRate; public LeakyBucketRateLimiter(int capacity, int leakRate) { this.capacity = capacity; this.leakRate = leakRate; } public synchronized boolean tryAcquire() { //桶中沒(méi)有水, 重新開(kāi)始計(jì)算 if (water.get() == 0) { logger.info("start leaking"); leakTimestamp = System.currentTimeMillis(); water.incrementAndGet(); return water.get() < capacity; } //先漏水,計(jì)算剩余水量 long currentTime = System.currentTimeMillis(); int leakedWater = (int) ((currentTime - leakTimestamp) / 1000 * leakRate); logger.info("lastTime:{}, currentTime:{}. LeakedWater:{}", leakTimestamp, currentTime, leakedWater); //可能時(shí)間不足,則先不漏水 if (leakedWater != 0) { int leftWater = water.get() - leakedWater; //可能水已漏光。設(shè)為0 water.set(Math.max(0, leftWater)); leakTimestamp = System.currentTimeMillis(); } logger.info("剩余容量:{}", capacity - water.get()); if (water.get() < capacity) { logger.info("tryAcquire sucess"); water.incrementAndGet(); return true; } else { logger.info("tryAcquire fail"); return false; } } }
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1.平滑流量。由于漏桶算法以固定的速率處理請(qǐng)求,可以有效地平滑和整形流量,避免流量的突發(fā)和波動(dòng)(類似于消息隊(duì)列的削峰填谷的作用)。
2.防止過(guò)載。當(dāng)流入的請(qǐng)求超過(guò)桶的容量時(shí),可以直接丟棄請(qǐng)求,防止系統(tǒng)過(guò)載。
缺點(diǎn):
1.無(wú)法處理突發(fā)流量:由于漏桶的出口速度是固定的,無(wú)法處理突發(fā)流量。例如,即使在流量較小的時(shí)候,也無(wú)法以更快的速度處理請(qǐng)求。
2.可能會(huì)丟失數(shù)據(jù):如果入口流量過(guò)大,超過(guò)了桶的容量,那么就需要丟棄部分請(qǐng)求。在一些不能接受丟失請(qǐng)求的場(chǎng)景中,這可能是一個(gè)問(wèn)題。
3.不適合速率變化大的場(chǎng)景:如果速率變化大,或者需要?jiǎng)討B(tài)調(diào)整速率,那么漏桶算法就無(wú)法滿足需求。
4.資源利用率:不管當(dāng)前系統(tǒng)的負(fù)載壓力如何,所有請(qǐng)求都得進(jìn)行排隊(duì),即使此時(shí)服務(wù)器的負(fù)載處于相對(duì)空閑的狀態(tài),這樣會(huì)造成系統(tǒng)資源的浪費(fèi)。
由于漏桶的缺陷比較明顯,所以在實(shí)際業(yè)務(wù)場(chǎng)景中,使用的比較少。
令牌算法
實(shí)現(xiàn)原理
令牌桶算法是基于漏桶算法的一種改進(jìn),主要在于令牌桶算法能夠在限制服務(wù)調(diào)用的平均速率的同時(shí),還能夠允許一定程度內(nèi)的突發(fā)調(diào)用。
實(shí)現(xiàn)原理:
1.系統(tǒng)以固定的速率向桶中添加令牌;
2.當(dāng)有請(qǐng)求到來(lái)時(shí),會(huì)嘗試從桶中移除一個(gè)令牌,如果桶中有足夠的令牌,則請(qǐng)求可以被處理或數(shù)據(jù)包可以被發(fā)送;
3.如果桶中沒(méi)有令牌,那么請(qǐng)求將被拒絕;
4.桶中的令牌數(shù)不能超過(guò)桶的容量,如果新生成的令牌超過(guò)了桶的容量,那么新的令牌會(huì)被丟棄。
5.令牌桶算法的一個(gè)重要特性是,它能夠應(yīng)對(duì)突發(fā)流量。當(dāng)桶中有足夠的令牌時(shí),可以一次性處理多個(gè)請(qǐng)求,這對(duì)于需要處理突發(fā)流量的應(yīng)用場(chǎng)景非常有用。但是又不會(huì)無(wú)限制的增加處理速率導(dǎo)致壓垮服務(wù)器,因?yàn)橥皟?nèi)令牌數(shù)量是有限制的。
如圖所示:
代碼實(shí)現(xiàn)
Guava中的RateLimiter就是基于令牌桶實(shí)現(xiàn)的,可以直接拿來(lái)使用。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1.可以處理突發(fā)流量:令牌桶算法可以處理突發(fā)流量。當(dāng)桶滿時(shí),能夠以最大速度處理請(qǐng)求。這對(duì)于需要處理突發(fā)流量的應(yīng)用場(chǎng)景非常有用。
2.限制平均速率:在長(zhǎng)期運(yùn)行中,數(shù)據(jù)的傳輸率會(huì)被限制在預(yù)定義的平均速率(即生成令牌的速率)。
3.靈活性:與漏桶算法相比,令牌桶算法提供了更大的靈活性。例如,可以動(dòng)態(tài)地調(diào)整生成令牌的速率。
缺點(diǎn):
1.可能導(dǎo)致過(guò)載:如果令牌產(chǎn)生的速度過(guò)快,可能會(huì)導(dǎo)致大量的突發(fā)流量,這可能會(huì)使網(wǎng)絡(luò)或服務(wù)過(guò)載。
2.需要存儲(chǔ)空間:令牌桶需要一定的存儲(chǔ)空間來(lái)保存令牌,可能會(huì)導(dǎo)致內(nèi)存資源的浪費(fèi)。
3.實(shí)現(xiàn)稍復(fù)雜:相比于計(jì)數(shù)器算法,令牌桶算法的實(shí)現(xiàn)稍微復(fù)雜一些。
三、應(yīng)用實(shí)踐
Guava中的RateLimiter就是基于令牌桶實(shí)現(xiàn)的,可以直接拿來(lái)使用。所有整個(gè)實(shí)踐是基于Guava的應(yīng)用。
引入依賴
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>32.1.3-jre</version> </dependency>
API直接使用
固定產(chǎn)生令牌
@Test public void acquireTest() { //每秒固定生成5個(gè)令牌 RateLimiter rateLimiter = RateLimiter.create(5); for (int i = 0; i < 10; i++) { double time = rateLimiter.acquire(); logger.info("等待時(shí)間:{}s", time); } }
結(jié)果:
可以看到,每200ms左右產(chǎn)生一個(gè)令牌并放行請(qǐng)求,也就是1秒放行5個(gè)請(qǐng)求,使用RateLimiter能夠很好的實(shí)現(xiàn)單機(jī)的限流。
同時(shí)產(chǎn)生多個(gè)令牌
那么再回到我們前面提到的突發(fā)流量情況,令牌桶是怎么解決的呢?RateLimiter中引入了一個(gè)預(yù)消費(fèi)的概念。
申請(qǐng)令牌的數(shù)量不同不會(huì)影響這個(gè)申請(qǐng)令牌這個(gè)動(dòng)作本身的響應(yīng)時(shí)間,acquire(1)和acquire(1000)這兩個(gè)請(qǐng)求會(huì)消耗同樣的時(shí)間返回結(jié)果,但是會(huì)影響下一個(gè)請(qǐng)求的響應(yīng)時(shí)間。
如果一個(gè)消耗大量令牌的任務(wù)到達(dá)空閑的RateLimiter,會(huì)被立即批準(zhǔn)執(zhí)行,但是當(dāng)下一個(gè)請(qǐng)求進(jìn)來(lái)時(shí),將會(huì)額外等待一段時(shí)間,用來(lái)支付前一個(gè)請(qǐng)求的時(shí)間成本。
至于為什么要這么做,通過(guò)舉例來(lái)引申一下。當(dāng)一個(gè)系統(tǒng)處于空閑狀態(tài)時(shí),突然來(lái)了1個(gè)需要消耗100個(gè)令牌的任務(wù),那么白白等待100秒是毫無(wú)意義的浪費(fèi)資源行為,那么可以先允許它執(zhí)行,并對(duì)后續(xù)請(qǐng)求進(jìn)行限流時(shí)間上的延長(zhǎng),以此來(lái)達(dá)到一個(gè)應(yīng)對(duì)突發(fā)流量的效果。
@Test public void acquireSmoothly() { RateLimiter rateLimiter = RateLimiter.create(5, 3, TimeUnit.SECONDS); long startTimeStamp = System.currentTimeMillis(); for (int i = 0; i < 15; i++) { double time = rateLimiter.acquire(); logger.info("等待時(shí)間:{}s, 總時(shí)間:{}ms", time, System.currentTimeMillis() - startTimeStamp); } }
結(jié)果:
可以看到,令牌發(fā)放時(shí)間從最開(kāi)始的500ms多逐漸縮短,在3秒后達(dá)到了200ms左右的勻速發(fā)放。
總的來(lái)說(shuō),基于令牌桶實(shí)現(xiàn)的RateLimiter功能還是非常強(qiáng)大的,在限流的基礎(chǔ)上還可以把請(qǐng)求平均分散在各個(gè)時(shí)間段內(nèi),因此在單機(jī)情況下它是使用比較廣泛的限流組件。
AOP切面
第一步:創(chuàng)建注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface Limit { // 資源主鍵 String key() default ""; //最多訪問(wèn)次數(shù),代表請(qǐng)求總數(shù)量 double permitsPerSeconds(); // 時(shí)間:即timeout時(shí)間內(nèi),只允許有permitsPerSeconds個(gè)請(qǐng)求總數(shù)量訪問(wèn),超過(guò)的將被限制不能訪問(wèn) long timeout(); //時(shí)間類型 TimeUnit timeUnit() default TimeUnit.MILLISECONDS; //提示信息 String msg() default "系統(tǒng)繁忙,請(qǐng)稍后重試"; }
第二步:AOP切面實(shí)現(xiàn)
@Aspect @Component public class LimitAspect { Logger logger = LoggerFactory.getLogger(LimitAspect.class); private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap(); ???@Around("@annotation(com.alibaba.xxx.xxx.annotation.Limit)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); //拿limit的注解 Limit limit = method.getAnnotation(Limit.class); if (limit != null) { // key作用:不同的接口,不同的流量控制 String key = limit.key(); RateLimiter rateLimiter; //驗(yàn)證緩存是否有命中key if (!limitMap.containsKey(key)) { //創(chuàng)建令牌桶 rateLimiter = RateLimiter.create(limit.permitsPerSeconds()); limitMap.put(key, rateLimiter); logger.info("新建了令牌桶={},容量={}", key, limit.permitsPerSeconds()); } rateLimiter = limitMap.get(key); //拿令牌 boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeUnit()); //拿不到令牌,直接返回異常信息 if (!acquire) { logger.debug("令牌桶={},獲取令牌失敗", key); throw new RuntimeException(limit.msg()); } } return joinPoint.proceed(); } }
第三步:應(yīng)用
@Limit(key = "query", permitsPerSeconds = 1, timeout = 1, msg = "觸發(fā)接口限流,請(qǐng)重試")
第四步:使用位置詳解
若是放在http的mapping接口上,返回如下
{ "timestamp": "2023-12-07 11:21:47", "status": 500, "error": "Internal Server Error", "path": "/table/query" }
若是放在service服務(wù)的接口上,返回如下
{ "code": -1, "message": "觸發(fā)接口限流,請(qǐng)重試", "data": "fail" }
四、總結(jié)
本文介紹的實(shí)現(xiàn)方式屬于應(yīng)用級(jí)限制,應(yīng)用級(jí)限流方式只是單應(yīng)用內(nèi)的請(qǐng)求限流,不能進(jìn)行全局限流。假設(shè)將應(yīng)用部署到多臺(tái)機(jī)器,我們需要分布式限流和接入層限流來(lái)解決這個(gè)問(wèn)題。
總的來(lái)說(shuō),要保證系統(tǒng)的抗壓能力,限流是一個(gè)必不可少的環(huán)節(jié),雖然可能會(huì)造成某些用戶的請(qǐng)求被丟棄,但相比于突發(fā)流量造成的系統(tǒng)宕機(jī)來(lái)說(shuō),這些損失一般都在可以接受的范圍之內(nèi)。前面也說(shuō)過(guò),限流可以結(jié)合熔斷、降級(jí)一起使用,多管齊下,保證服務(wù)的可用性與健壯性。
以上就是Java實(shí)現(xiàn)限流接口的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java限流接口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- java實(shí)現(xiàn)單機(jī)限流
- Java限流實(shí)現(xiàn)的幾種方法詳解
- 詳解5種Java中常見(jiàn)限流算法
- Java服務(wù)限流算法的6種實(shí)現(xiàn)
- Java中實(shí)現(xiàn)接口限流的方案詳解
- java通過(guò)信號(hào)量實(shí)現(xiàn)限流的示例
- 關(guān)于Java限流功能的簡(jiǎn)單實(shí)現(xiàn)
- 使用Java自定義注解實(shí)現(xiàn)一個(gè)簡(jiǎn)單的令牌桶限流器
- Java中常見(jiàn)的4種限流算法詳解
- Java面試之限流的實(shí)現(xiàn)方式小結(jié)
- Java代碼實(shí)現(xiàn)四種限流算法詳細(xì)介紹
相關(guān)文章
淺談java Iterator.remove()方法的用法(詳解)
下面小編就為大家?guī)?lái)一篇淺談java Iterator.remove()方法的用法(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07SpringBoot+MinIO+KKFileView實(shí)現(xiàn)文件預(yù)覽功能
本文主要介紹了使用SpringBoot、MinIO和KKFileView實(shí)現(xiàn)文件上傳和在線預(yù)覽功能,通過(guò)配置MinIO存儲(chǔ)文件,并使用KKFileView生成預(yù)覽鏈接,感興趣的可以了解一下2024-11-11簡(jiǎn)單了解Java synchronized關(guān)鍵字同步
這篇文章主要介紹了簡(jiǎn)單了解Java synchronized關(guān)鍵字同步,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java的基礎(chǔ)語(yǔ)法學(xué)習(xí)筆記
這里為大家整理了Java的基礎(chǔ)語(yǔ)法學(xué)習(xí)筆記,包括關(guān)鍵詞、運(yùn)算符與基本的流程控制語(yǔ)句寫(xiě)法等,需要的朋友可以參考下2016-05-05spring profile 多環(huán)境配置管理詳解
這篇文章主要介紹了 spring profile 多環(huán)境配置管理詳解的相關(guān)資料,需要的朋友可以參考下2017-01-01解決spring-data-jpa 事物中修改屬性自動(dòng)更新update問(wèn)題
這篇文章主要介紹了解決spring-data-jpa 事物中修改屬性自動(dòng)更新update問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家2021-08-08SpringBoot通過(guò)Filter實(shí)現(xiàn)整個(gè)項(xiàng)目接口的SQL注入攔截詳解
這篇文章主要介紹了SpringBoot通過(guò)Filter實(shí)現(xiàn)整個(gè)項(xiàng)目接口的SQL注入攔截詳解,SQL注入是比較常見(jiàn)的網(wǎng)絡(luò)攻擊方式之一,在客戶端在向服務(wù)器發(fā)送請(qǐng)求的時(shí)候,sql命令通過(guò)表單提交或者url字符串拼接傳遞到后臺(tái)持久層,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令,需要的朋友可以參考下2023-12-12