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

首頁綜合 正文
目錄

柚子快報邀請碼778899分享:面試項目準(zhǔn)備:黑馬點評項目總結(jié)

柚子快報邀請碼778899分享:面試項目準(zhǔn)備:黑馬點評項目總結(jié)

http://yzkb.51969.com/

面試項目準(zhǔn)備:黑馬點評項目總結(jié)

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

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

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

3. 總結(jié)

1. 項目介紹

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

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

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

1.2 項目架構(gòu)

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

Nginx作用:

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

2. 各個功能模塊

2.1 登錄模塊

短信登錄功能(基于session)

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

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

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

基于redis的短信登錄

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

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

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

2.2 用戶查詢緩存模塊

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

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

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

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

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

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

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

緩存穿透

是指用戶要查詢的數(shù)據(jù)在緩存和數(shù)據(jù)庫中都沒有,這樣緩存永遠不會生效,所有的請求都會到達數(shù)據(jù)庫,造成數(shù)據(jù)庫巨大的壓力。

常見的解決方案有兩種

緩存空對象

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

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

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

public Shop queryWithPassThrough(Long id) {

String key = CACHE_SHOP_KEY + id;

//1 從Redis查詢商鋪信息

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) { //因為等于null是沒有查到緩存,其他的""、" "是緩存的空對象直接返回

//返回錯誤信息

return null;

}

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

Shop shop = getById(id);

//5 不存在,返回錯誤

if (shop == null) {

//將空值寫入Redis

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

return null;

}

//6 存在,寫入Redis

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

//7 返回

return shop;

}

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

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

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

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

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

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

?常見的解決方案有兩種

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

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

//緩存擊穿 邏輯過期

public Shop queryWithLogicalExpire(Long id) {

String key = CACHE_SHOP_KEY + id;

//1 從Redis查詢商鋪信息

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

//2 判斷是否存在

if (StrUtil.isBlank(shopJson)) {

//3 存在,直接返回

return null;

}

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

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

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

LocalDateTime expireTime = redisData.getExpireTime();

// 5.判斷是否過期

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

// 5.1未過期,直接返回店鋪信息

return null;

}

// 5.2已過期,需要緩存重建

//6.緩存重建

//6.1獲取互斥鎖

String lockKey = LOCK_SHOP_KEY + id;

boolean isLock = tryLock(lockKey);

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

if (isLock){

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

//6.3成功開啟獨立線程

CACHE_REBUILD_EXECUTOR.submit(() -> {

try {

this.saveShop2Redis(id, 20L);

} finally {

unlock(lockKey);

}

});

}

}

//7 返回

return shop;

}

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

//緩存穿透 緩存空對象

public R queryWithPassThrough(

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

String key = keyPrefix + id;

//1 從Redis查詢商鋪信息

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

//2 判斷是否存在

if (StrUtil.isNotBlank(json)) {

//3 存在,直接返回

return JSONUtil.toBean(json, type);

}

if (json != null) {

//返回錯誤信息

return null;

}

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

R r = dbFallback.apply(id);

//5 不存在,返回錯誤

if (r == null) {

//將空值寫入Redis

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

return null;

}

//6 存在,寫入Redis

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

//7 返回

return r;

}

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

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

String key = keyPrefix + id;

//1 從Redis查詢商鋪信息

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

//2 判斷是否存在

if (StrUtil.isBlank(json)) {

//3 存在,直接返回

return null;

}

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

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

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

LocalDateTime expireTime = redisData.getExpireTime();

// 5.判斷是否過期

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

// 5.1未過期,直接返回店鋪信息

return null;

}

// 5.2已過期,需要緩存重建

//6.緩存重建

//6.1獲取互斥鎖

String lockKey = LOCK_SHOP_KEY + id;

boolean isLock = tryLock(lockKey);

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

if (isLock){

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

//6.3成功開啟獨立線程

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. 生成時間戳

LocalDateTime now = LocalDateTime.now();

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

long timeStamp = nowSecond - BEGIN_TIMESTAMP;

//2. 生成序列號

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

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

//3. 拼接并返回

return timeStamp << COUNT_BITS | count;

}

全局唯一ID生成策略:

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

Redis自增ID策略:

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

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

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

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

一人一單:同一個優(yōu)惠劵,一人只能下一單。 分布式鎖 在集群模式下,普通的鎖還會出現(xiàn)問題,因為不同jvm有不同的鎖監(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的極端情況,誤刪情況 極端情況下依然會出現(xiàn)線程誤刪,釋放業(yè)務(wù)阻塞,以判斷完畢。 獲取鎖標(biāo)識并判斷要和釋放鎖是原子操作 Redisson入門 Redis三種消息隊列

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

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

3. 總結(jié)

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

柚子快報邀請碼778899分享:面試項目準(zhǔn)備:黑馬點評項目總結(jié)

http://yzkb.51969.com/

好文推薦

評論可見,查看隱藏內(nèi)容

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

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

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

發(fā)布評論

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

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄