最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

非常全面的Java?SpringBoot點(diǎn)贊功能實(shí)現(xiàn)

 更新時(shí)間:2022年01月23日 11:02:28   作者:JoeyHua  
但是這些功能再項(xiàng)目中是高頻出現(xiàn)的,如果直接操作數(shù)據(jù)庫(kù)的話,對(duì)數(shù)據(jù)庫(kù)壓力太大。那遇到這個(gè)問題怎么解決?這篇文章主要給大家介紹了關(guān)于Java?SpringBoot點(diǎn)贊功能實(shí)現(xiàn)?的相關(guān)資料,需要的朋友可以參考下

前言

最近公司在做一個(gè)NFT商城的項(xiàng)目,大致就是一個(gè)只買賣數(shù)字產(chǎn)品的平臺(tái),項(xiàng)目中有個(gè)需求是用戶可以給商品點(diǎn)贊,還需要獲取商品的點(diǎn)贊總數(shù),類似下圖

起初感覺這功能很好實(shí)現(xiàn),無非就是加個(gè)點(diǎn)贊表嘛,后來發(fā)現(xiàn)事情并沒有這么簡(jiǎn)單。

一開始的設(shè)計(jì)是這樣的,一共有三張表:商品表、用戶表、點(diǎn)贊表,用戶點(diǎn)贊的時(shí)候把用戶id和商品id加到點(diǎn)贊表中,并給對(duì)應(yīng)的商品點(diǎn)贊數(shù)+1??雌饋頉]什么問題,邏輯也比較簡(jiǎn)單,但是測(cè)試的時(shí)候缺發(fā)現(xiàn)了奇怪的bug,點(diǎn)贊數(shù)量有時(shí)候會(huì)不正確,結(jié)果會(huì)比預(yù)期的大。

下面貼下關(guān)鍵代碼(項(xiàng)目使用了Mybatis-Plus):

public boolean like(Integer userId, Integer productId) {
        // 查詢是否有記錄,如果有記錄直接返回
        Like like = getOne(new QueryWrapper<Like>().lambda()
                .eq(Like::getUserId, userId)
                .eq(Like::getProductId, productId));
        if(like != null) {
            return true;
        }

        // 保存并商品點(diǎn)贊數(shù)加1
        save(Like.builder()
                .userId(userId)
                .productId(productId)
                .build());
        return productService.update(new UpdateWrapper<Product>().lambda()
                .setSql("like_count = like_count + 1")
                .eq(Product::getId, productId));
}

看上去沒什么問題,但是測(cè)試后數(shù)據(jù)卻不正確,為什么呢?

實(shí)際上這是一個(gè)并發(fā)問題,只要在并發(fā)的情況下就會(huì)出現(xiàn)問題,我們知道Spring Mvc是基于servlet的,servlet在接收到用戶請(qǐng)求后會(huì)從線程池中拿一個(gè)線程分配給它,每個(gè)請(qǐng)求都是一個(gè)單獨(dú)的線程。試想一下,如果A線程在執(zhí)行完查詢操作后,發(fā)現(xiàn)沒有記錄,隨后由于CPU調(diào)度,把控制權(quán)讓了出去,然后B線程執(zhí)行查詢,也發(fā)現(xiàn)沒有記錄,這時(shí)候A和B線程都會(huì)執(zhí)行保存并商品點(diǎn)贊數(shù)加1這個(gè)操作,導(dǎo)致數(shù)據(jù)不正確。

CPU操作順序:A線程查詢 -> B線程查詢 -> A線程保存 -> B線程保存

下面使用JMeter模擬一下并發(fā)的情況,模擬用戶在1秒內(nèi)對(duì)商品執(zhí)行100次點(diǎn)贊請(qǐng)求,結(jié)果應(yīng)該是1,但得到的結(jié)果卻是28(實(shí)際結(jié)果不一定是28,可能是任何數(shù)字)。

解決方案

青銅版

使用synchronized關(guān)鍵字鎖住讀寫操作,操作完成后釋放鎖

public boolean like(Integer userId, Integer productId) {
        String lock = buildLock(userId, productId);
        synchronized (lock) {
            // 查詢是否有記錄,如果有記錄直接返回
            Like like = getOne(new QueryWrapper<Like>().lambda()
                    .eq(Like::getUserId, userId)
                    .eq(Like::getProductId, productId), false);
            if(like != null) {
                return true;
            }

            // 保存并商品點(diǎn)贊數(shù)加1
            save(Like.builder()
                    .userId(userId)
                    .productId(productId)
                    .build());
            return productService.update(new UpdateWrapper<Product>().lambda()
                    .setSql("like_count = like_count + 1")
                    .eq(Product::getId, productId));
        }
}

private String buildLock(Integer userId, Integer productId) {
        StringBuilder sb = new StringBuilder();
        sb.append(userId);
        sb.append("::");
        sb.append(productId);
        String lock = sb.toString().intern();

        return lock;
}

這里要注意一點(diǎn),使用String作為鎖時(shí)一定要調(diào)用intern()方法,intern()會(huì)先從常量池中查找有沒有相同的String,如果有就直接返回,沒有的話會(huì)把當(dāng)前String加入常量池,然后再返回。如果不調(diào)用這個(gè)方法鎖會(huì)失效。

JMeter性能數(shù)據(jù)

優(yōu)點(diǎn):

保證了正確性

缺點(diǎn):

性能太差,并發(fā)低的情況下還可以應(yīng)付,并發(fā)高時(shí)用戶體驗(yàn)極差

白銀版

點(diǎn)贊表user_id和product_id加上聯(lián)合索引,并使用try catch捕獲異常,防止報(bào)錯(cuò)。由于使用了聯(lián)合索引,所以不需要在新增前查詢了,mysql會(huì)幫我們做這件事。

public boolean like(Integer userId, Integer productId) {
        try {
            // 保存并商品點(diǎn)贊數(shù)加1
            save(Like.builder()
                    .userId(userId)
                    .productId(productId)
                    .build());
            return productService.update(new UpdateWrapper<Product>().lambda()
                    .setSql("like_count = like_count + 1")
                    .eq(Product::getId, productId));
        }catch (DuplicateKeyException exception) {

        }

        return true;
}

JMeter性能數(shù)據(jù)

優(yōu)點(diǎn):

性能比上一個(gè)方案好

缺點(diǎn):

中規(guī)中矩,沒什么大的缺點(diǎn)

黃金版

使用Redis緩存點(diǎn)贊數(shù)據(jù)(點(diǎn)贊操作使用lua腳本實(shí)現(xiàn),保證操作的原子性),然后定時(shí)同步到mysql。

注意:Redis需要開啟持久化,最好aof和rdb都開啟,不然重啟數(shù)據(jù)就丟失了

public boolean like(Integer userId, Integer productId) {
        List<String> keys = new ArrayList<>();
        keys.add(buildUserRedisKey(userId));
        keys.add(buildProductRedisKey(productId));

        int value1 = 1;

        redisUtil.execute("lua-script/like.lua", keys, value1);

        return true;
}

private String buildUserRedisKey(Integer userId) {
        return "userId_" + userId;
}

private String buildProductRedisKey(Integer productId) {
        return "productId_" + productId;
}

lua腳本

local userId = KEYS[1]
local productId = KEYS[2]
local flag = ARGV[1] -- 1:點(diǎn)贊 0:取消點(diǎn)贊


if flag == '1' then
  -- 用戶set添加商品并商品點(diǎn)贊數(shù)加1
  if redis.call('SISMEMBER', userId, productId) == 0 then
    redis.call('SADD', userId, productId)
    redis.call('INCR', productId)
  end
else
  -- 用戶set刪除商品并商品點(diǎn)贊數(shù)減1
  redis.call('SREM', userId, productId)
  local oldValue = tonumber(redis.call('GET', productId))
  if oldValue and oldValue > 0 then
    redis.call('DECR', productId)
  end
end

return 1

JMeter性能數(shù)據(jù)

優(yōu)點(diǎn):

  • 性能非常好

缺點(diǎn):

  • 數(shù)據(jù)量多了內(nèi)存占用較高總結(jié)

如果對(duì)性能沒有要求,可以使用白銀版的實(shí)現(xiàn)方式,如果有要求,就使用黃金版的方式,內(nèi)存占用大的問題也可以通過一些手段來解決,比如可以根據(jù)業(yè)務(wù)需求定期刪除一些不常用的緩存數(shù)據(jù),但是相對(duì)應(yīng)的,查詢的時(shí)候就需要在查詢失敗時(shí)再去查數(shù)據(jù)庫(kù)。

源碼

源碼地址:https://github.com/huajiayi/like-demo

源碼里有一些功能沒有實(shí)現(xiàn),比如定時(shí)同步功能,需要根據(jù)業(yè)務(wù)需求自行實(shí)現(xiàn)

總結(jié)

到此這篇關(guān)于Java SpringBoot點(diǎn)贊功能實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Java SpringBoot點(diǎn)贊功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring bean生命周期配置過程解析

    Spring bean生命周期配置過程解析

    這篇文章主要介紹了Spring bean生命周期配置過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java實(shí)現(xiàn)字符串轉(zhuǎn)為駝峰格式的方法詳解

    Java實(shí)現(xiàn)字符串轉(zhuǎn)為駝峰格式的方法詳解

    這篇文章主要介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)字符串轉(zhuǎn)為駝峰格式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-07-07
  • dubbo服務(wù)鏈路跟蹤方式

    dubbo服務(wù)鏈路跟蹤方式

    這篇文章主要介紹了dubbo服務(wù)鏈路跟蹤方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java實(shí)現(xiàn)一個(gè)桌球小游戲

    java實(shí)現(xiàn)一個(gè)桌球小游戲

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)一個(gè)桌球小游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • 如何設(shè)置springboot禁止日志輸出到控制臺(tái)

    如何設(shè)置springboot禁止日志輸出到控制臺(tái)

    文章總結(jié):本文主要介紹了SpringBoot項(xiàng)目中使用SLF4J記錄日志時(shí),日志默認(rèn)輸出到控制臺(tái)的原因及解決方法,日志框架如Logback默認(rèn)會(huì)將日志輸出到控制臺(tái),可以通過`logback-spring.xml`配置文件或配置類來禁止日志輸出到控制臺(tái),并設(shè)置日志輸出級(jí)別
    2025-01-01
  • Java?中如何創(chuàng)建按鈕單擊事件

    Java?中如何創(chuàng)建按鈕單擊事件

    我們使用事件偵聽器在Java中創(chuàng)建按鈕單擊事件,本文給大家講解Java中的按鈕單擊事件,結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05
  • Springboot Vue可配置調(diào)度任務(wù)實(shí)現(xiàn)示例詳解

    Springboot Vue可配置調(diào)度任務(wù)實(shí)現(xiàn)示例詳解

    這篇文章主要為大家介紹了Springboot Vue可配置調(diào)度任務(wù)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • SpringBoot+Echarts實(shí)現(xiàn)請(qǐng)求后臺(tái)數(shù)據(jù)顯示餅狀圖

    SpringBoot+Echarts實(shí)現(xiàn)請(qǐng)求后臺(tái)數(shù)據(jù)顯示餅狀圖

    這篇文章主要介紹了SpringBoot+Echarts實(shí)現(xiàn)請(qǐng)求后臺(tái)數(shù)據(jù)顯示餅狀圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • 避免多個(gè)jar通過maven打包導(dǎo)致同名配置文件覆蓋沖突問題

    避免多個(gè)jar通過maven打包導(dǎo)致同名配置文件覆蓋沖突問題

    這篇文章主要介紹了避免多個(gè)jar通過maven打包導(dǎo)致同名配置文件覆蓋沖突問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • springboot全局日期格式化的兩種方式

    springboot全局日期格式化的兩種方式

    這篇文章主要介紹了springboot全局日期格式化的兩種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評(píng)論

安丘市| 阜南县| 内江市| 无棣县| 景泰县| 阳江市| 新河县| 库尔勒市| 太白县| 榆中县| 琼海市| 高密市| 通州市| 榆中县| 旺苍县| 七台河市| 正镶白旗| 东丽区| 重庆市| 洞头县| 融水| 库尔勒市| 德令哈市| 武宁县| 香港| 确山县| 中超| 巴青县| 黄梅县| 洛川县| 吕梁市| 谷城县| 龙口市| 德钦县| 莆田市| 新龙县| 沙河市| 广平县| 瓦房店市| 洛宁县| 瓮安县|