? 在登陸服務(wù)中,如果將數(shù)據(jù)全部存儲(chǔ)到tomcat中,當(dāng)存在多個(gè)tomcat的時(shí)候,數(shù)據(jù)是無(wú)法同步的,這就導(dǎo)致了數(shù)據(jù)的共享問(wèn)題:
為岢嵐等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及岢嵐網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、岢嵐網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!1、每臺(tái)服務(wù)器中都有完整的一份session數(shù)據(jù),服務(wù)器壓力過(guò)大。
????????2、session拷貝數(shù)據(jù)時(shí),可能會(huì)出現(xiàn)延遲
解決辦法就是采用redis, redis完美的符合了以下的要求:
? 1.同步性
? 2.基于內(nèi)存,高效率
? 3.滿足key-value結(jié)構(gòu)
? 同時(shí)注意,redis的string與hashset的區(qū)別
優(yōu)化后的流程如下圖
首先我們很容易想到,將用戶的登錄手機(jī)號(hào)作為key,而后對(duì)于存儲(chǔ)到redis中的用戶的憑證,我們選擇生成隨機(jī)的token,使用token是為了不暴露用戶的手機(jī)號(hào)(隱私處理).同時(shí)我們還要注意,session是能在tomcat中自動(dòng)同步的,但是token是做不到的,我們需要手動(dòng)同步token.而token是怎么做到幫助我們識(shí)別用戶的登錄狀態(tài)的呢,答案就是在每次的服務(wù)器回傳的過(guò)程中,都會(huì)在頭部添加一個(gè)token字段.
2.商品緩存緩存穿透:? 指的是客戶端在請(qǐng)求數(shù)據(jù)的過(guò)程中,緩存和數(shù)據(jù)庫(kù)中都沒(méi)有要請(qǐng)求的數(shù)據(jù),這樣緩存永遠(yuǎn)都不會(huì)生效,請(qǐng)求都會(huì)打到數(shù)據(jù)庫(kù).這樣就導(dǎo)致數(shù)據(jù)庫(kù)在頻繁的查找數(shù)據(jù).服務(wù)器性能大幅度下降.
解決辦法:? 1.緩存空對(duì)象: 對(duì)于同一個(gè)請(qǐng)求的資源,比如請(qǐng)求不存在的id= -1的資源,在緩存中添加id=-1的鍵,將其值設(shè)置為null,這樣避免了同樣的id導(dǎo)致的反復(fù)查找,下次查找id=-1的資源可以直接從緩存中查出來(lái).
? 優(yōu)點(diǎn): 實(shí)現(xiàn)簡(jiǎn)單,維護(hù)方便
? 缺點(diǎn): 一旦請(qǐng)求的資源變換,還是需要繼續(xù)查找.
? 額外的內(nèi)存消耗
? 可能造成短期的不一致(第一次請(qǐng)求的時(shí)候數(shù)據(jù)沒(méi)有,緩存中設(shè)置為null,但是第二次請(qǐng)求時(shí)數(shù)據(jù)庫(kù)中已經(jīng)有了上次請(qǐng)求的id對(duì)應(yīng)的數(shù)據(jù),仍然會(huì)返回null)
? 2.添加布隆過(guò)濾器:它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都遠(yuǎn)遠(yuǎn)超過(guò)一般的算法,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難。
? 優(yōu)點(diǎn): 內(nèi)存占用少,沒(méi)有多余的key
? 缺點(diǎn): 實(shí)現(xiàn)很復(fù)雜,并且存在誤判的可能
3.redis加鎖的辦法: 在后面指定了鎖的有效時(shí)間,是用鎖的超時(shí)時(shí)間進(jìn)行兜底的.private boolean tryLock(String key){
Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 2, TimeUnit.MINUTES);
//如果直接返回的話會(huì)自動(dòng)拆箱,可能導(dǎo)致出現(xiàn)空值
return BooleanUtil.isTrue(ifAbsent);
}
如果是redis中設(shè)置鎖的語(yǔ)句就如下所示:
//給線程1設(shè)置鎖
setnx lock thread1
//設(shè)置鎖的時(shí)長(zhǎng)
expire lock 5
//合并起來(lái)
//(nx指的是不存在的時(shí)候才可以設(shè)置)
set lock thread1 ex 5 nx
//刪除鎖
del lock
4.redis將兩個(gè)操作轉(zhuǎn)換為原子操作(并行的情況下)? 使用lua腳本
lua腳本數(shù)組是從下標(biāo)為1開(kāi)始的
java代碼的改造:
改造之前:
@Override
public void deleteLock() {
//判斷redis中的鎖和當(dāng)前的鎖是否是同一把
//當(dāng)前的鎖
String threadNameUUID = ID_NAME + Thread.currentThread().getId();
//redis數(shù)據(jù)庫(kù)里面的數(shù)據(jù)是否一致
String redisLock = stringRedisTemplate.opsForValue().get(KEY_NAME + name);
if(threadNameUUID.equals(redisLock)){
stringRedisTemplate.delete(KEY_NAME + name);
}
}
改造后:
unlock.lua在resources目錄下
//初始化lua腳本
private static final DefaultRedisScriptUNLOCK_LUA;
//在static]塊中初始化
static{
UNLOCK_LUA = new DefaultRedisScript<>();
UNLOCK_LUA.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_LUA.setResultType(Long.class);
}
unlock.lua
--比較線程標(biāo)識(shí)與redis鎖中的標(biāo)識(shí)是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
--釋放鎖
return redis.call('del', KEYS[1])
end
return 0
調(diào)用代碼:
@Override
public void deleteLock() {
//調(diào)用lua腳本
stringRedisTemplate.execute(
UNLOCK_LUA,
Collections.singletonList(KEY_NAME + name),
ID_NAME + Thread.currentThread().getId());
}
lua傳入空參,比如java調(diào)用lua腳本的時(shí)候key是空的這時(shí)不能傳空對(duì)象,要傳Collections.emptyList()下面舉例:
Long execute = stringRedisTemplate.execute(SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(), id.toString());
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧