国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

目錄
一、Redis實作分散式鎖定原理
為什麼需要分散式鎖定
分散式鎖定如何實現(xiàn)
如何避免死鎖
鎖被別人給釋放了
如何確定鎖的過期時間
Redis的部署方式對鎖的影響
二、代碼實現(xiàn)Redis分布式鎖
1.SpringBoot整合redis用到最多的當然屬于我們的老朋友RedisTemplate,pom依賴如下:
2.Redis配置類:
3.Service層面
4.業(yè)務(wù)調(diào)用實現(xiàn)分布式鎖示例:
首頁 資料庫 Redis 怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

Jun 03, 2023 am 08:16 AM
redis springboot

一、Redis實作分散式鎖定原理

為什麼需要分散式鎖定

在聊天分散式鎖定之前,有必要先解釋一下,為什麼需要分散式鎖定。

與分散式鎖相對就的是單機鎖,我們在寫多執(zhí)行緒程式時,避免同時操作一個共享變數(shù)產(chǎn)生資料問題,通常會使用一把鎖來互斥以保證共享變數(shù)的正確性,其使用範圍在同一個進程中。如果換做是多個進程,需要同時操作一個共享資源,如何互斥?現(xiàn)在的業(yè)務(wù)應(yīng)用通常是微服務(wù)架構(gòu),這也意味著一個應(yīng)用會部署多個進程,多個進程如果需要修改MySQL中的同一行記錄,為了避免操作亂序?qū)е麦v數(shù)據(jù),此時就需要引入分佈式鎖了。

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

想要實作分散式鎖,必須藉助一個外部系統(tǒng),所有行程都去這個系統(tǒng)上申請加鎖。這個外部系統(tǒng)必須具有互斥能力,也就是說,如果兩個請求同時到達,系統(tǒng)只會成功地為一個進程加鎖,而另一個進程會失敗。這個外部系統(tǒng)可以是資料庫,也可以是Redis或Zookeeper,但為了追求效能,我們通常會選擇使用Redis或Zookeeper來做。

Redis可以作為一個共享儲存系統(tǒng),多個客戶端可以共享訪問,因此可以被用來保存分散式鎖定。而且 Redis 的讀寫效能高,可以應(yīng)付高並發(fā)的鎖定操作場景。這篇文章的重點在於介紹如何使用Redis實現(xiàn)分散式鎖定,並探討在實作過程中可能會遇到的問題。

分散式鎖定如何實現(xiàn)

作為分散式鎖定實作過程中的共用儲存系統(tǒng),Redis可以使用鍵值對來保存鎖定變量,在接收和處理不同客戶端發(fā)送的加鎖和釋放鎖的操作請求。那麼,鍵值對的鍵和值具體是怎麼定的呢?我們要賦予鎖變數(shù)一個變數(shù)名,把這個變數(shù)名當作鍵值對的鍵,而鎖變數(shù)的值,則是鍵值對的值,這樣一來,Redis就能保存鎖變數(shù)了,客戶端也就可以透過Redis的命令操作來實現(xiàn)鎖定操作。

想要實作分散式鎖定,必須要求Redis有互斥的能力??梢允褂肧ETNX指令,其意義是SET IF NOT EXIST,也就是如果key不存在,才會設(shè)定它的值,否則什麼都不做。實作一種分散式鎖的方法是,兩個客戶端進程互斥地執(zhí)行該命令。

以下展示了Redis使用key/value對保存鎖定變量,以及兩個客戶端同時請求加鎖的操作過程。

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

加上鎖定作業(yè)完成後,加上鎖定成功的客戶端,就可以去操作共享資源,例如,修改MySQL的某一行資料。操作完成後,也要及時釋放鎖,給後來者讓出操作共享資源的機會。如何釋放鎖呢?直接使用DEL指令刪除這個key即可。這個邏輯非常簡單,整體的流程寫成偽程式碼就是下面這樣。

// 加鎖
SETNX lock_key 1
// 業(yè)務(wù)邏輯
DO THINGS
// 釋放鎖
DEL lock_key

但是,以上實作有一個很大的問題,當客戶端1拿到鎖後,如果發(fā)生下面的場景,就會造成死鎖。

程式處理業(yè)務(wù)邏輯異常,沒及時釋放鎖定進程掛了,沒機會釋放鎖定

以上情況會導(dǎo)致已經(jīng)取得鎖定的用戶端一直佔用鎖,其他用戶端永遠無法取得到鎖。

如何避免死鎖

為了解決以上死鎖問題,最容易想到的方案是在申請鎖定時,在Redis中實作時,給鎖定設(shè)定一個過期時間,假設(shè)操作共享資源的時間不會超過10s,那麼加鎖時,給這個key設(shè)定10s過期即可。

但以上操作還是有問題,加鎖、設(shè)定過期時間是2條指令,有可能只執(zhí)行了第一條,第二條卻執(zhí)行失敗,例如:

1.SETNX執(zhí)行成功,執(zhí)行EXPIRE時由於網(wǎng)路問題,執(zhí)行失敗
2.SETNX執(zhí)行成功,Redis異常宕機,EXPIRE沒有機會執(zhí)行
3.SETNX執(zhí)行成功,客戶端異常崩潰,EXPIRE沒有機會執(zhí)行

總之這兩條指令如果不能保證是原子操作,就有潛在的風(fēng)險導(dǎo)致過期時間設(shè)定失敗,依舊有可能發(fā)生死鎖問題。幸好在Redis 2.6.12之後,Redis擴展了SET指令的參數(shù),可以在SET的同時指定EXPIRE時間,這條操作是原子的,例如以下指令是設(shè)定鎖的過期時間為10秒。

SET lock_key 1 EX 10 NX

#至此,解決了死鎖問題,但還是有其他問題。想像下面這個這樣一個場景:

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

  1. 客戶端1加鎖定成功,開始操作共享資源

  2. 客戶端1操作共享資源耗時太久,超過了鎖的過期時間,鎖失效(鎖被自動釋放)

  3. 客戶端2加鎖成功,開始操作共享資源

  4. 客戶端1操作共享資源完成,在finally塊中手動釋放鎖,但此時它釋放的是客戶端2的鎖。

這里存在兩個嚴重的問題:

  • 鎖過期

  • 釋放了別人的鎖

第1個問題是評估操作共享資源的時間不準確導(dǎo)致的,如果只是一味增大過期時間,只能緩解問題降低出現(xiàn)問題的概率,依舊無法徹底解決問題。原因在于客戶端在拿到鎖之后,在操作共享資源時,遇到的場景是很復(fù)雜的,既然是預(yù)估的時間,也只能是大致的計算,不可能覆蓋所有導(dǎo)致耗時變長的場景。

第二個問題在于解鎖操作是不夠嚴謹?shù)?,因為它是一種不加區(qū)分地釋放鎖的操作,沒有對鎖的所有權(quán)進行檢查。如何解決呢?

鎖被別人給釋放了

解決辦法是,客戶端在加鎖時,設(shè)置一個只有自己知道的唯一標識進去,例如可以是自己的線程ID,如果是redis實現(xiàn),就是SET key unique_value EX 10 NX。之后在釋放鎖時,要先判斷這把鎖是否歸自己持有,只有是自己的才能釋放它。

//釋放鎖 比較unique_value是否相等,避免誤釋放
if redis.get("key") == unique_value then
    return redis.del("key")

這里釋放鎖使用的是GET + DEL兩條命令,這時又會遇到原子性問題了。

  1. 客戶端1執(zhí)行GET,判斷鎖是自己的

  2. 客戶端2執(zhí)行了SET命令,強制獲取到鎖(雖然發(fā)生概念很低,但要嚴謹考慮鎖的安全性)

  3. 客戶端1執(zhí)行DEL,卻釋放了客戶端2的鎖

由此可見,以上GET + DEL兩個命令還是必須原子的執(zhí)行才行。怎樣原子執(zhí)行兩條命令呢?答案是Lua腳本,可以把以上邏輯寫成Lua腳本,讓Redis執(zhí)行。因為Redis處理每個請求是單線程執(zhí)行的,在執(zhí)行一個Lua腳本時其它請求必須等待,直到這個Lua腳本處理完成,這樣一來GET+DEL之間就不會有其他命令執(zhí)行了。

以下是使用Lua腳本(unlock.script)實現(xiàn)的釋放鎖操作的偽代碼,其中,KEYS[1]表示lock_key,ARGV[1]是當前客戶端的唯一標識,這兩個值都是我們在執(zhí)行 Lua腳本時作為參數(shù)傳入的。

//Lua腳本語言,釋放鎖 比較unique_value是否相等,避免誤釋放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

最后我們執(zhí)行以下命令,即可

redis-cli  --eval  unlock.script lock_key , unique_value

這樣一路優(yōu)先下來,整個加鎖、解鎖流程就更嚴謹了,先小結(jié)一下,基于Redis實現(xiàn)的分布式鎖,一個嚴謹?shù)牧鞒倘缦拢?/p>

  1. 加鎖時要設(shè)置過期時間SET lock_key unique_value EX expire_time NX

  2. 操作共享資源

  3. 釋放鎖:Lua腳本,先GET判斷鎖是否歸屬自己,再DEL釋放鎖

有了這個嚴謹?shù)逆i模型,我們還需要重新思考之前的那個問題,鎖的過期時間不好評估怎么辦。

如何確定鎖的過期時間

前面提到過,過期時間如果評估得不好,這個鎖就會有提前過期的風(fēng)險,一種妥協(xié)的解決方案是,盡量冗余過期時間,降低鎖提前過期的概率,但這個方案并不能完美解決問題。是否可以設(shè)置這樣的方案,加鎖時,先設(shè)置一個預(yù)估的過期時間,然后開啟一個守護線程,定時去檢測這個鎖的失效時間,如果鎖快要過期了,操作共享資源還未完成,那么就自動對鎖進行續(xù)期,重新設(shè)置過期時間

Redisson是一個已封裝好這些工作的庫,可以說是一種非常優(yōu)秀的解決方案。Redisson是一個Java語言實現(xiàn)的Redis SDK客戶端,在使用分布式鎖時,它就采用了自動續(xù)期的方案來避免鎖過期,這個守護線程我們一般叫它看門狗線程。這個SDK提供的API非常友好,它可以像操作本地鎖一樣操作分布式鎖。客戶端一旦加鎖成功,就會啟動一個watch dog看門狗線程,它是一個后臺線程,會每隔一段時間(這段時間的長度與設(shè)置的鎖的過期時間有關(guān))檢查一下,如果檢查時客戶端還持有鎖key(也就是說還在操作共享資源),那么就會延長鎖key的生存時間。

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

那如果客戶端在加鎖成功后就宕機了呢?宕機了那么看門狗任務(wù)就不存在了,也就無法為鎖續(xù)期了,鎖到期自動失效。

Redis的部署方式對鎖的影響

上面討論的情況,都是鎖在單個Redis 實例中可能產(chǎn)生的問題,并沒有涉及到Redis的部署架構(gòu)細節(jié)。

Redis發(fā)展到現(xiàn)在,幾種常見的部署架構(gòu)有:

  • 單機模式;

  • 主從模式;

  • 哨兵(sentinel)模式;

  • 叢集模式;

我們使用Redis時,一般會採用主從叢集哨兵的模式部署,哨兵的功能就是監(jiān)測redis節(jié)點的運行狀態(tài)。普通的主從模式,當master崩潰時,需要手動切換讓slave成為master,使用主從哨兵結(jié)合的好處在於,當master異常宕機時,哨兵可以實現(xiàn)故障自動切換,把slave提升為新的master,繼續(xù)提供服務(wù),以此保證可用性。那麼當主從發(fā)生切換時,分散式鎖依舊安全嗎?

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

想像這樣的場景:

  1. #客戶端1在master上執(zhí)行SET指令,加鎖成功

  2. #此時,master異常宕機,SET指令還未同步到slave上(主從複製是異步的)

  3. 哨兵將slave提升為新的master,但這個鎖在新的master上丟失了,導(dǎo)致客戶端2來加鎖成功了,兩個客戶端共同操作共享資源

可見,當引入Redis副本後,分散式鎖還是可能受到影響。即使Redis透過sentinel保證高可用,如果這個master節(jié)點因為某些原因發(fā)生了主從切換,那麼就會出現(xiàn)鎖丟失的情況。

叢集模式Redlock實作高可靠的分散式鎖定

為了避免Redis實例故障而導(dǎo)致的鎖定無法運作的問題,Redis的開發(fā)者Antirez提出了分佈式鎖演算法Redlock。 Redlock演算法的基本思路,是讓客戶端和多個獨立的Redis實例依序請求加鎖,如果客戶端能夠和半數(shù)以上的實例成功地完成加鎖操作,那麼我們就認為,客戶端成功地獲得分散式鎖定了,否則加鎖失敗。這樣一來,即使有單一Redis實例發(fā)生故障,因為鎖定變數(shù)在其它實例上也有保存,所以,客戶端仍然可以正常地進行鎖定操作,鎖定變數(shù)並不會遺失。

來具體看下Redlock演算法的執(zhí)行步驟。 Redlock演算法的實作要求Redis採用叢集部署模式,無哨兵節(jié)點,需要有N個獨立的Redis實例(官方建議至少5個實例)。接下來,我們可以分成3步驟來完成加鎖操作。

怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖

第一步是,客戶端取得目前時間。

第二步是,客戶端依序向N個Redis實例執(zhí)行加鎖操作。

這裡的加鎖操作和在單一實例上執(zhí)行的加鎖操作一樣,使用SET指令,帶上NX、EX/PX選項,以及帶上客戶端的唯一識別。當然,如果某個Redis實例發(fā)生故障了,為了確保在這種情況下,Redlock演算法能夠繼續(xù)運行,我們需要為加鎖操作設(shè)定一個逾時時間。如果客戶端在和一個Redis實例請求加鎖時,一直到逾時都沒有成功,那麼此時,客戶端會和下一個Redis實例繼續(xù)請求加鎖。一般需要將加鎖操作的超時時間設(shè)定為鎖的有效時間的一小部分,通常約為幾十毫秒。

第三步是,一旦客戶端完成了和所有Redis實例的加鎖操作,客戶端就要計算整個加鎖過程的總耗時。

客戶端只有在滿足兩個條件時,才能認為是加鎖成功,條件一是客戶端從超過半數(shù)(大於等於N/2 1)的Redis實例上成功獲取到了鎖;條件二是客戶端取得鎖的總耗時沒有超過鎖的有效時間。

為何只有在大多數(shù)實例加鎖成功時才能算操作成功?事實上,多個Redis實例一起使用組成了一個分散式系統(tǒng)。在分散式系統(tǒng)中總是會出現(xiàn)異常節(jié)點,所以在談?wù)摲稚⑹较到y(tǒng)時,需要考慮異常節(jié)點達到多少個,也依舊不影響整個系統(tǒng)的正確運作。這是一個分散式系統(tǒng)的容錯問題,這個問題的結(jié)論是:如果只存在故障節(jié)點,只要大多數(shù)節(jié)點正常,那麼整個系統(tǒng)依舊可以提供正確服務(wù)。

在滿足了這兩個條件後,我們需要重新計算這把鎖的有效時間,計算的結(jié)果是鎖的最初有效時間減去客戶端為取得鎖的總耗時。如果鎖的有效時間已經(jīng)來不及完成共享資料的操作了,我們可以釋放鎖,以免出現(xiàn)還沒完成共享資源操作,鎖就過期了的情況。

當然,如果客戶端在和所有實例執(zhí)行完加鎖操作后,沒能同時滿足這兩個條件,那么,客戶端就要向所有Redis節(jié)點發(fā)起釋放鎖的操作。為什么釋放鎖,要操作所有的節(jié)點呢,不能只操作那些加鎖成功的節(jié)點嗎?因為在某一個Redis節(jié)點加鎖時,可能因為網(wǎng)絡(luò)原因?qū)е录渔i失敗,例如一個客戶端在一個Redis實例上加鎖成功,但在讀取響應(yīng)結(jié)果時由于網(wǎng)絡(luò)問題導(dǎo)致讀取失敗,那這把鎖其實已經(jīng)在Redis上加鎖成功了。所以釋放鎖時,不管之前有沒有加鎖成功,需要釋放所有節(jié)點上的鎖以保證清理節(jié)點上的殘留的鎖。

在Redlock算法中,釋放鎖的操作和在單實例上釋放鎖的操作一樣,只要執(zhí)行釋放鎖的 Lua腳本就可以了。如果N個Redis實例中超過一半的實例正常工作,就能確保分布式鎖正常運作。為了提高分布式鎖的可靠性,您可以在實際業(yè)務(wù)應(yīng)用中使用Redlock算法。

二、代碼實現(xiàn)Redis分布式鎖

1.SpringBoot整合redis用到最多的當然屬于我們的老朋友RedisTemplate,pom依賴如下:

<!-- springboot整合redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.Redis配置類:

package com.example.redisdemo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @description: Redis配置類
 * @author Keson
 * @date 21:20 2022/11/14
 * @Param
 * @return
 * @version 1.0
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        // 設(shè)置序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        RedisSerializer<?> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);// key序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
        redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

3.Service層面

package com.example.redisdemo.service;

import com.example.redisdemo.entity.CustomerBalance;
import java.util.concurrent.Callable;

/**
 * @author Keson
 * @version 1.0
 * @description: TODO
 * @date 2022/11/14 15:12
 */
public interface RedisService {

    <T> T callWithLock(CustomerBalance customerBalance, Callable<T> callable) throws Exception;
}
package com.example.redisdemo.service.impl;

import com.example.redisdemo.entity.CustomerBalance;
import com.example.redisdemo.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * @author Keson
 * @version 1.0
 * @description: TODO Redis實現(xiàn)分布式鎖
 * @date 2022/11/14 15:13
 */
@Service
@Slf4j
public class RedisServiceImpl implements RedisService {

    //設(shè)置默認過期時間
    private final static int DEFAULT_LOCK_EXPIRY_TIME = 20;
    //自定義lock key前綴
    private final static String LOCK_PREFIX = "LOCK:CUSTOMER_BALANCE";

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public <T> T callWithLock(CustomerBalance customerBalance, Callable<T> callable) throws Exception{
        //自定義lock key
        String lockKey = getLockKey(customerBalance.getCustomerNumber(), customerBalance.getSubAccountNumber(), customerBalance.getCurrencyCode());
        //將UUID當做value,確保唯一性
        String lockReference = UUID.randomUUID().toString();

        try {
            if (!lock(lockKey, lockReference, DEFAULT_LOCK_EXPIRY_TIME, TimeUnit.SECONDS)) {
                throw new Exception("lock加鎖失敗");
            }
            return callable.call();
        } finally {
            unlock(lockKey, lockReference);
        }
    }

    //定義lock key
    String getLockKey(String customerNumber, String subAccountNumber, String currencyCode) {
        return String.format("%s:%s:%s:%s", LOCK_PREFIX, customerNumber, subAccountNumber, currencyCode);
    }

    //redis加鎖
    private boolean lock(String key, String value, long timeout, TimeUnit timeUnit) {
        Boolean locked;
        try {
            //SET_IF_ABSENT --> NX: Only set the key if it does not already exist.
            //SET_IF_PRESENT --> XX: Only set the key if it already exist.
            locked = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection ->
                    connection.set(key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8),
                            Expiration.from(timeout, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT));
        } catch (Exception e) {
            log.error("Lock failed for redis key: {}, value: {}", key, value);
            locked = false;
        }
        return locked != null && locked;
    }

    //redis解鎖
    private boolean unlock(String key, String value) {
        try {
            //使用lua腳本保證刪除的原子性,確保解鎖
            String script = "if redis.call(&#39;get&#39;, KEYS[1]) == ARGV[1] " +
                            "then return redis.call(&#39;del&#39;, KEYS[1]) " +
                            "else return 0 end";
            Boolean unlockState = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection ->
                    connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1,
                            key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8)));
            return unlockState == null || !unlockState;
        } catch (Exception e) {
            log.error("unLock failed for redis key: {}, value: {}", key, value);
            return false;
        }
    }
}

4.業(yè)務(wù)調(diào)用實現(xiàn)分布式鎖示例:

    @Override
    public int updateById(CustomerBalance customerBalance) throws Exception {
        return redisService.callWithLock(customerBalance, ()-> customerBalanceMapper.updateById(customerBalance));
    }

以上是怎麼在SpringBoot中使用Redis實現(xiàn)分散式鎖的詳細內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願投稿,版權(quán)歸原作者所有。本站不承擔相應(yīng)的法律責任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

laravel8 的優(yōu)化點 laravel8 的優(yōu)化點 Apr 18, 2025 pm 12:24 PM

Laravel 8 針對性能優(yōu)化提供了以下選項:緩存配置:使用 Redis 緩存驅(qū)動、緩存門面、緩存視圖和頁面片段。數(shù)據(jù)庫優(yōu)化:建立索引、使用查詢範圍、使用 Eloquent 關(guān)係。 JavaScript 和 CSS 優(yōu)化:使用版本控制、合併和縮小資產(chǎn)、使用 CDN。代碼優(yōu)化:使用 Composer 安裝包、使用 Laravel 助手函數(shù)、遵循 PSR 標準。監(jiān)控和分析:使用 Laravel Scout、使用 Telescope、監(jiān)控應(yīng)用程序指標。

如何利用Redis緩存方案高效實現(xiàn)產(chǎn)品排行榜列表的需求? 如何利用Redis緩存方案高效實現(xiàn)產(chǎn)品排行榜列表的需求? Apr 19, 2025 pm 11:36 PM

Redis緩存方案如何實現(xiàn)產(chǎn)品排行榜列表的需求?在開發(fā)過程中,我們常常需要處理排行榜的需求,例如展示一個?...

Spring Boot中OAuth2Authorization對象Redis緩存失敗怎麼辦? Spring Boot中OAuth2Authorization對象Redis緩存失敗怎麼辦? Apr 19, 2025 pm 08:03 PM

SpringBoot中使用Redis緩存OAuth2Authorization對像在SpringBoot應(yīng)用中,使用SpringSecurityOAuth2AuthorizationServer...

Laravel 最佳擴展包推薦:2024 年必備工具 Laravel 最佳擴展包推薦:2024 年必備工具 Apr 30, 2025 pm 02:18 PM

2024年必備的Laravel擴展包包括:1.LaravelDebugbar,用於監(jiān)控和調(diào)試代碼;2.LaravelTelescope,提供詳細的應(yīng)用監(jiān)控;3.LaravelHorizon,管理Redis隊列任務(wù)。這些擴展包能提升開發(fā)效率和應(yīng)用性能。

Laravel 環(huán)境搭建與基礎(chǔ)配置(Windows/Mac/Linux) Laravel 環(huán)境搭建與基礎(chǔ)配置(Windows/Mac/Linux) Apr 30, 2025 pm 02:27 PM

在不同操作系統(tǒng)上搭建Laravel環(huán)境的步驟如下:1.Windows:使用XAMPP安裝PHP和Composer,配置環(huán)境變量,安裝Laravel。 2.Mac:使用Homebrew安裝PHP和Composer,安裝Laravel。 3.Linux:使用Ubuntu更新系統(tǒng),安裝PHP和Composer,安裝Laravel。每個系統(tǒng)的具體命令和路徑有所不同,但核心步驟一致,確保順利搭建Laravel開發(fā)環(huán)境。

REDIS的角色:探索數(shù)據(jù)存儲和管理功能 REDIS的角色:探索數(shù)據(jù)存儲和管理功能 Apr 22, 2025 am 12:10 AM

Redis在數(shù)據(jù)存儲和管理中扮演著關(guān)鍵角色,通過其多種數(shù)據(jù)結(jié)構(gòu)和持久化機製成為現(xiàn)代應(yīng)用的核心。 1)Redis支持字符串、列表、集合、有序集合和哈希表等數(shù)據(jù)結(jié)構(gòu),適用於緩存和復(fù)雜業(yè)務(wù)邏輯。 2)通過RDB和AOF兩種持久化方式,Redis確保數(shù)據(jù)的可靠存儲和快速恢復(fù)。

centos redis如何配置慢查詢?nèi)照I centos redis如何配置慢查詢?nèi)照I Apr 14, 2025 pm 04:54 PM

在CentOS系統(tǒng)上啟用Redis慢查詢?nèi)照I,提升性能診斷效率。以下步驟將指導(dǎo)您完成配置:第一步:定位並編輯Redis配置文件首先,找到Redis配置文件,通常位於/etc/redis/redis.conf。使用以下命令打開配置文件:sudovi/etc/redis/redis.conf第二步:調(diào)整慢查詢?nèi)照I參數(shù)在配置文件中,找到並修改以下參數(shù):#慢查詢閾值(毫秒)slowlog-log-slower-than10000#慢查詢?nèi)照I最大條目數(shù)slowlog-max-len

在多節(jié)點環(huán)境下,如何確保Spring Boot的@Scheduled定時任務(wù)只在一個節(jié)點上執(zhí)行? 在多節(jié)點環(huán)境下,如何確保Spring Boot的@Scheduled定時任務(wù)只在一個節(jié)點上執(zhí)行? Apr 19, 2025 pm 10:57 PM

SpringBoot定時任務(wù)在多節(jié)點環(huán)境下的優(yōu)化方案在開發(fā)Spring...

See all articles