欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

目錄

柚子快報(bào)邀請(qǐng)碼778899分享:面試項(xiàng)目準(zhǔn)備:黑馬點(diǎn)評(píng)項(xiàng)目總結(jié)

柚子快報(bào)邀請(qǐng)碼778899分享:面試項(xiàng)目準(zhǔn)備:黑馬點(diǎn)評(píng)項(xiàng)目總結(jié)

http://yzkb.51969.com/

面試項(xiàng)目準(zhǔn)備:黑馬點(diǎn)評(píng)項(xiàng)目總結(jié)

1. 項(xiàng)目介紹1.1 項(xiàng)目使用的技術(shù)棧1.2 項(xiàng)目架構(gòu)Nginx作用:

2. 各個(gè)功能模塊2.1 登錄模塊短信登錄功能(基于session)基于redis的短信登錄

2.2 用戶(hù)查詢(xún)緩存模塊2.3 優(yōu)惠券秒殺功能2.4 好友關(guān)注功能

3. 總結(jié)

1. 項(xiàng)目介紹

??黑馬點(diǎn)評(píng)項(xiàng)目是一個(gè)前后端分離項(xiàng)目,類(lèi)似于大眾點(diǎn)評(píng),實(shí)現(xiàn)了發(fā)布查看商家,達(dá)人探店,點(diǎn)贊,關(guān)注等功能,業(yè)務(wù)可以幫助商家引流,增加曝光度,也可以為用戶(hù)提供查看提供附近消費(fèi)場(chǎng)所,主要。用來(lái)配合學(xué)習(xí)Redis的知識(shí)。 ??基于 Redis + Springboot的點(diǎn)評(píng)APP ,實(shí)現(xiàn)了短信驗(yàn)證碼登錄、查找店鋪、秒殺優(yōu)惠券、發(fā)表點(diǎn)評(píng)、關(guān)注推送的完 整業(yè)務(wù)流程。

1.1 項(xiàng)目使用的技術(shù)棧

??SpringBoot+Mysql+Lombok+MyBatis-Plus+Hutool+Redis

1.2 項(xiàng)目架構(gòu)

后端部署在Tomcat上,前端部署在Nginx。

Nginx作用:

1. 反向代理Tomcat服務(wù)器,解決多臺(tái)服務(wù)器,session不共享問(wèn)題,隱藏真實(shí)服務(wù)地址。 2. 負(fù)載均衡降低服務(wù)器壓力。三種負(fù)載均衡方式:輪詢(xún)法(默認(rèn)方法)、weight權(quán)重模式(加權(quán)輪詢(xún))、ip_hash Nginx的靜態(tài)處理能力很強(qiáng),但是動(dòng)態(tài)處理能力不足,因此,在企業(yè)中常用動(dòng)靜分離技術(shù)。

2. 各個(gè)功能模塊

2.1 登錄模塊

短信登錄功能(基于session)

發(fā)送驗(yàn)證碼 校驗(yàn)手機(jī)號(hào)、判斷格式是否正確、正確生成驗(yàn)證碼、發(fā)送驗(yàn)證碼。校驗(yàn)手機(jī)號(hào)和驗(yàn)證碼 校驗(yàn)手機(jī)號(hào)、校驗(yàn)驗(yàn)證碼、查找用戶(hù)、如果沒(méi)有創(chuàng)建用戶(hù),保存用戶(hù)到session。

以上完成的兩步把用戶(hù)信息保存到session中了。然而有許多頁(yè)面都需要用戶(hù)信息和校驗(yàn)登錄狀態(tài)。

校驗(yàn)登錄狀態(tài) ?訪問(wèn)不通的后端控制器,要獲取數(shù)據(jù)之前需要校驗(yàn)登錄狀態(tài),用攔截器實(shí)現(xiàn)最好,減少代碼冗余。 ?攔截器實(shí)現(xiàn),訪問(wèn)之前從session中獲取用戶(hù),如果用戶(hù)存在放行,并且把用戶(hù)保存到ThreadLocal中去,不同的線程互不干擾。訪問(wèn)之后,把ThreadLocal保存的信息刪除。 ?配置攔截器生效,選擇要攔截的請(qǐng)求或是排除不攔截的。

基于redis的短信登錄

?session共享問(wèn)題:多臺(tái)Tomcat并不共享session存儲(chǔ)空間,當(dāng)請(qǐng)求切換到不同服務(wù)器會(huì)導(dǎo)致數(shù)據(jù)丟失問(wèn)題。(可以用Tomcat間的數(shù)據(jù)同步解決,但還是會(huì)出現(xiàn)數(shù)據(jù)不一致和占用內(nèi)存問(wèn)題) ?session代替方案應(yīng)該滿足:

數(shù)據(jù)共享內(nèi)存存儲(chǔ)key,value結(jié)構(gòu) 使用redis代替session是完全可以的

問(wèn)題:是訪問(wèn)不攔截的頁(yè)面,token不會(huì)刷新。而session是訪問(wèn)哪個(gè)頁(yè)面都會(huì)刷新。 優(yōu)化:再加一個(gè)攔截器,攔截所有請(qǐng)求并且有token的話就刷新,第二個(gè)則判斷用戶(hù)是否在ThreadLocal中存在。這樣就不會(huì)出現(xiàn)不刷新的現(xiàn)象。

2.2 用戶(hù)查詢(xún)緩存模塊

?什么是緩存? 數(shù)據(jù)交換的緩沖區(qū)(cache),是貯存數(shù)據(jù)的臨時(shí)地方,一般讀寫(xiě)性能高。 ?緩存的作用: 1. 降低后端負(fù)載。 2. 提高讀寫(xiě)效率,降低響應(yīng)時(shí)間。 ?緩存的成本: 1. 數(shù)據(jù)一致性成本 2. 代碼維護(hù)成本 3. 運(yùn)維成本

添加Redis緩存 根據(jù)id查詢(xún)店鋪緩存的流程 主動(dòng)更新策略

?先刪除緩存,在操作數(shù)據(jù)庫(kù)

在線程1 刪除緩存后,線程2查詢(xún)緩存未命中,然后去查詢(xún)數(shù)據(jù)庫(kù),最后把查詢(xún)結(jié)果寫(xiě)入緩存。但此時(shí),線程1更新數(shù)據(jù)庫(kù)的操作還沒(méi)有完成,線程2查到的是舊的值,寫(xiě)入了緩存。當(dāng)線程1更新完數(shù)據(jù)庫(kù)之后,就會(huì)造成數(shù)據(jù)庫(kù)和緩存數(shù)據(jù)不一致問(wèn)題。

?先操作數(shù)據(jù)庫(kù),再刪除緩存

要想并發(fā)問(wèn)題發(fā)生,首先要線程1查詢(xún)緩存,剛好緩存失效,然后去查詢(xún)數(shù)據(jù)庫(kù)。此時(shí)線程2要去更新數(shù)據(jù)庫(kù),然后去刪除緩存。如果線程2在線程1的寫(xiě)入緩存之前更新完數(shù)據(jù)庫(kù)和刪除完緩存,南無(wú)就會(huì)造成數(shù)據(jù)不一致問(wèn)題。但畢竟緩存的操作速度快和線程1查詢(xún)時(shí)緩存剛好失效并且線程2要去更新數(shù)據(jù)庫(kù)。這些事情發(fā)生的概率極小。

所以選擇先操作數(shù)據(jù)庫(kù),再刪除緩存。(給數(shù)據(jù)庫(kù)操作加鎖的話,應(yīng)該可以解決并發(fā)問(wèn)題)

緩存穿透

是指用戶(hù)要查詢(xún)的數(shù)據(jù)在緩存和數(shù)據(jù)庫(kù)中都沒(méi)有,這樣緩存永遠(yuǎn)不會(huì)生效,所有的請(qǐng)求都會(huì)到達(dá)數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)巨大的壓力。

常見(jiàn)的解決方案有兩種

緩存空對(duì)象

優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,維護(hù)方便。缺點(diǎn):內(nèi)存的消耗、短期的數(shù)據(jù)不一致。 布隆過(guò)濾器

優(yōu)點(diǎn):內(nèi)存占用少,沒(méi)有多余的key.缺點(diǎn):實(shí)現(xiàn)復(fù)雜、存在誤差。

查詢(xún)店鋪的緩存穿透解決(使用緩存空對(duì)象方式) 代碼如下:

public Shop queryWithPassThrough(Long id) {

String key = CACHE_SHOP_KEY + id;

//1 從Redis查詢(xún)商鋪信息

String shopJson = stringRedisTemplate.opsForValue().get(key);

//2 判斷是否存在,isNotBlank(null," ", "")

if (StrUtil.isNotBlank(shopJson)) {

//3 存在,直接返回

Shop shop = JSONUtil.toBean(shopJson, Shop.class);

return shop;

}

if (shopJson != null) { //因?yàn)榈扔趎ull是沒(méi)有查到緩存,其他的""、" "是緩存的空對(duì)象直接返回

//返回錯(cuò)誤信息

return null;

}

//4 不存在,根據(jù)id查詢(xún)數(shù)據(jù)庫(kù)

Shop shop = getById(id);

//5 不存在,返回錯(cuò)誤

if (shop == null) {

//將空值寫(xiě)入Redis

stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); //設(shè)置ttl

return null;

}

//6 存在,寫(xiě)入Redis

stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);

//7 返回

return shop;

}

null是指沒(méi)有這個(gè)對(duì)象,空值(空字符串)是有這個(gè)對(duì)象,但是里面的內(nèi)容為空

緩存穿透的解決方案還有哪些?

緩存null值布隆過(guò)濾增強(qiáng)id的復(fù)雜度,避免被猜測(cè)id規(guī)律做好數(shù)據(jù)的基礎(chǔ)格式校驗(yàn)加強(qiáng)用戶(hù)權(quán)限校驗(yàn)做好熱點(diǎn)參數(shù)限流

緩存雪崩 ?緩存雪崩是指在一段時(shí)間大量的key過(guò)期或者Redis宕機(jī),導(dǎo)致大量的請(qǐng)求打到數(shù)據(jù)庫(kù),給數(shù)據(jù)庫(kù)造成巨大的壓力。 解決方案

給不同可以的TTL添加隨機(jī)值利用Redis集群提高服務(wù)的可用性給業(yè)務(wù)添加降級(jí)限流策略給業(yè)務(wù)添加多級(jí)緩存(瀏覽器緩存、Nginx緩存、Tomcat緩存等)

緩存擊穿 ?緩存擊穿問(wèn)題也叫熱點(diǎn)key問(wèn)題,就是一個(gè)被高并發(fā)訪問(wèn)并且緩存業(yè)務(wù)重建復(fù)雜的key突然失效了,無(wú)數(shù)的請(qǐng)求訪問(wèn)會(huì)在瞬間給數(shù)據(jù)庫(kù)造成巨大的沖擊。

?常見(jiàn)的解決方案有兩種

互斥鎖邏輯過(guò)期 在業(yè)務(wù)中經(jīng)常會(huì)遇到一致性和可用性的選擇。 需求:修改根據(jù)id查詢(xún)商鋪的業(yè)務(wù),基于邏輯過(guò)期方式來(lái)解決緩存擊穿問(wèn)題。 代碼如下:

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

//緩存擊穿 邏輯過(guò)期

public Shop queryWithLogicalExpire(Long id) {

String key = CACHE_SHOP_KEY + id;

//1 從Redis查詢(xún)商鋪信息

String shopJson = stringRedisTemplate.opsForValue().get(key);

//2 判斷是否存在

if (StrUtil.isBlank(shopJson)) {

//3 存在,直接返回

return null;

}

// 4.命中,需要先把json反序列化為對(duì)象

RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);

Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);

LocalDateTime expireTime = redisData.getExpireTime();

// 5.判斷是否過(guò)期

if (expireTime.isAfter(LocalDateTime.now())) {

// 5.1未過(guò)期,直接返回店鋪信息

return null;

}

// 5.2已過(guò)期,需要緩存重建

//6.緩存重建

//6.1獲取互斥鎖

String lockKey = LOCK_SHOP_KEY + id;

boolean isLock = tryLock(lockKey);

//6.2判斷獲取鎖是否成功

if (isLock){

if(!expireTime.isAfter(LocalDateTime.now())) {

//6.3成功開(kāi)啟獨(dú)立線程

CACHE_REBUILD_EXECUTOR.submit(() -> {

try {

this.saveShop2Redis(id, 20L);

} finally {

unlock(lockKey);

}

});

}

}

//7 返回

return shop;

}

緩存工具的封裝 ?代碼如下:

//緩存穿透 緩存空對(duì)象

public R queryWithPassThrough(

String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) {

String key = keyPrefix + id;

//1 從Redis查詢(xún)商鋪信息

String json = stringRedisTemplate.opsForValue().get(key);

//2 判斷是否存在

if (StrUtil.isNotBlank(json)) {

//3 存在,直接返回

return JSONUtil.toBean(json, type);

}

if (json != null) {

//返回錯(cuò)誤信息

return null;

}

//4 不存在,根據(jù)id查詢(xún)數(shù)據(jù)庫(kù)

R r = dbFallback.apply(id);

//5 不存在,返回錯(cuò)誤

if (r == null) {

//將空值寫(xiě)入Redis

stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);

return null;

}

//6 存在,寫(xiě)入Redis

this.set(key, r, time, unit);

//7 返回

return r;

}

//緩存擊穿,設(shè)置邏輯過(guò)期時(shí)間

public R queryWithLogicalExpire(String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) {

String key = keyPrefix + id;

//1 從Redis查詢(xún)商鋪信息

String json = stringRedisTemplate.opsForValue().get(key);

//2 判斷是否存在

if (StrUtil.isBlank(json)) {

//3 存在,直接返回

return null;

}

// 4.命中,需要先把json反序列化為對(duì)象

RedisData redisData = JSONUtil.toBean(json, RedisData.class);

R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);

LocalDateTime expireTime = redisData.getExpireTime();

// 5.判斷是否過(guò)期

if (expireTime.isAfter(LocalDateTime.now())) {

// 5.1未過(guò)期,直接返回店鋪信息

return null;

}

// 5.2已過(guò)期,需要緩存重建

//6.緩存重建

//6.1獲取互斥鎖

String lockKey = LOCK_SHOP_KEY + id;

boolean isLock = tryLock(lockKey);

//6.2判斷獲取鎖是否成功

if (isLock){

if(!expireTime.isAfter(LocalDateTime.now())) {

//6.3成功開(kāi)啟獨(dú)立線程

CACHE_REBUILD_EXECUTOR.submit(() -> {

try {

R r1 = dbFallback.apply(id);

this.setWithLogicalExpire(key, r1, time, unit);

} finally {

unlock(lockKey);

}

});

}

}

//7 返回

return r;

}

使用方式

//一行解決緩存穿透,封裝了方法

Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, id2 -> getById(id2), CACHE_SHOP_TTL, TimeUnit.MINUTES);

2.3 優(yōu)惠券秒殺功能

全局唯一ID 代碼如下:

public long nextId(String keyPrefix) {

//1. 生成時(shí)間戳

LocalDateTime now = LocalDateTime.now();

long nowSecond = now.toEpochSecond(ZoneOffset.UTC);

long timeStamp = nowSecond - BEGIN_TIMESTAMP;

//2. 生成序列號(hào)

String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); //方便統(tǒng)計(jì)年月日

long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);//一天下單的量,拼接日期,還有統(tǒng)計(jì)效果

//3. 拼接并返回

return timeStamp << COUNT_BITS | count;

}

全局唯一ID生成策略:

UUIDRedis自增snowflake算法數(shù)據(jù)庫(kù)自增

Redis自增ID策略:

每天一個(gè)key,方便統(tǒng)計(jì)訂單量ID構(gòu)造是時(shí)間戳+計(jì)數(shù)器

實(shí)現(xiàn)優(yōu)惠劵秒殺的下單功能 ?下單時(shí)需要判斷兩點(diǎn):

秒殺是否開(kāi)始或結(jié)束,如果尚未開(kāi)始或已結(jié)束則無(wú)法下單庫(kù)存是否充足,不足則無(wú)法下單

更新庫(kù)存和查詢(xún)版本是數(shù)據(jù)庫(kù)自帶命令,是原子操作,不會(huì)有線程安全問(wèn)題。 如果字段不是庫(kù)存,需要加版本號(hào),可以通過(guò)分段鎖提高成功率,例如currentHashMap中的分段鎖。

一人一單:同一個(gè)優(yōu)惠劵,一人只能下一單。 分布式鎖 在集群模式下,普通的鎖還會(huì)出現(xiàn)問(wèn)題,因?yàn)椴煌琷vm有不同的鎖監(jiān)視器。

代碼如下:

public class SimpleRedisLock implements ILock{

private String name;

public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {

this.name = name;

this.stringRedisTemplate = stringRedisTemplate;

}

private StringRedisTemplate stringRedisTemplate;

private static final String KEY_PREFIX = "lock:";

private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

private static final DefaultRedisScript UNLOCK_SCRIP;

static {

UNLOCK_SCRIP = new DefaultRedisScript<>();

UNLOCK_SCRIP.setLocation(new ClassPathResource("unlock.lua"));

UNLOCK_SCRIP.setResultType(Long.class);

}

@Override

public boolean tryLock(long timeoutSec) {

String threadId = ID_PREFIX + Thread.currentThread().getId();

Boolean success = stringRedisTemplate.opsForValue()

.setIfAbsent(KEY_PREFIX + name, threadId , timeoutSec, TimeUnit.SECONDS);

return Boolean.TRUE.equals(success);

}

@Override

public void unlock() {

//調(diào)用lua腳本, 判斷和釋放在一行代碼執(zhí)行,滿足原子性。

stringRedisTemplate.execute(

UNLOCK_SCRIP,

Collections.singletonList(KEY_PREFIX + name),

ID_PREFIX + Thread.currentThread().getId());

}

分布式鎖基于Redis的極端情況,誤刪情況 極端情況下依然會(huì)出現(xiàn)線程誤刪,釋放業(yè)務(wù)阻塞,以判斷完畢。 獲取鎖標(biāo)識(shí)并判斷要和釋放鎖是原子操作 Redisson入門(mén) Redis三種消息隊(duì)列

2.4 好友關(guān)注功能

基于Set集合的關(guān)注、取關(guān)、共同關(guān)注、消息推送等功能 實(shí)現(xiàn)分頁(yè)查詢(xún)

3. 總結(jié)

使用 Redis 解決了在集群模式下的 Session共享問(wèn)題,使用攔截器實(shí)現(xiàn)用戶(hù)的登錄校驗(yàn)和權(quán)限刷新 基于Cache Aside模式解決數(shù)據(jù)庫(kù)與緩存的一致性問(wèn)題 使用 Redis 對(duì)高頻訪問(wèn)的信息進(jìn)行緩存 ,降低了數(shù)據(jù)庫(kù)查詢(xún)的壓力 ,解決了緩存穿透、雪崩、擊穿問(wèn)題使用 Redis + Lua腳 本實(shí)現(xiàn)對(duì)用戶(hù)秒殺資格的預(yù)檢 ,同時(shí)用樂(lè)觀鎖解決秒殺產(chǎn)生的超賣(mài)問(wèn)題 使用Redis分布式鎖解決了在集群模式下一人一單的線程安全問(wèn)題 基于stream結(jié)構(gòu)作為消息隊(duì)列,實(shí)現(xiàn)異步秒殺下單 使用Redis的 ZSet 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了點(diǎn)贊排行榜功能,使用Set 集合實(shí)現(xiàn)關(guān)注、共同關(guān)注功能

柚子快報(bào)邀請(qǐng)碼778899分享:面試項(xiàng)目準(zhǔn)備:黑馬點(diǎn)評(píng)項(xiàng)目總結(jié)

http://yzkb.51969.com/

好文推薦

評(píng)論可見(jiàn),查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19548817.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問(wèn)

文章目錄