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

Redis如何實現(xiàn)計數(shù)統(tǒng)計

 更新時間:2024年04月10日 11:12:02   作者:嘩嘩的世界  
這篇文章主要介紹了Redis如何實現(xiàn)計數(shù)統(tǒng)計方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

介紹

計數(shù)器大量應(yīng)用于互聯(lián)網(wǎng)上大大小小的項目,你可以在很多場景都能找到計數(shù)器的應(yīng)用范疇,單純以技術(shù)派項目為例,也有相當(dāng)多的地方會有計數(shù)相關(guān)的訴求,比如

  • 文章帶贊數(shù)
  • 收藏數(shù)
  • 評論數(shù)
  • 用戶粉絲數(shù)
  • ......

技術(shù)派中有兩種查詢計數(shù)相關(guān)的方案,一個是基于db中的操作記錄進行實施,一種是基于redis的incr特性來實現(xiàn)計數(shù)器

下面來看一下,redis的計數(shù)器是怎樣用于技術(shù)派的技術(shù)場景的

計數(shù)的業(yè)務(wù)場景

首先我們看一下技術(shù)派中使用到的計數(shù)器的場景,主要有兩大類(業(yè)務(wù)計數(shù)+pv/uv),三個細分領(lǐng)域(用戶、文章、站點)

用戶的相關(guān)統(tǒng)計信息

  • 文章數(shù),文章總閱讀數(shù),粉絲數(shù),關(guān)注作者數(shù),文章被收藏數(shù)、被點贊數(shù)量

站點的pv/uv等統(tǒng)計信息

  • 網(wǎng)站的總pv/uv,某一天的pv/uv
  • 某個uri的pv/uv

注意上面的幾個場景,這里主要介紹redis計數(shù)器的使用

那用戶與文章的相關(guān)統(tǒng)計將是我們的重點,因為這兩個的業(yè)務(wù)屬性很相似,因此我們選擇一個重點,以用戶統(tǒng)計來實現(xiàn)。

redis計數(shù)器

redis計數(shù)器,主要是借助原生的incr指令來實現(xiàn)原子的+1-1操作,更棒的是不僅redis的string數(shù)據(jù)結(jié)構(gòu)支持incr,hash、zset數(shù)據(jù)結(jié)構(gòu)同樣也是支持incr的

1.incr指令

Redis incr命令將key中存儲的數(shù)字值增值一。

  • 如果key不存在,那么key的值會先被初始化為0,然后在執(zhí)行INCR操作。
  • 如果值包含錯誤類型,或者字符串類型的值不能表示為數(shù)字,那么返回一個錯誤。
  • 本操作的值限制在64位有符號數(shù)字表示之內(nèi)。

接下來看項目封裝實現(xiàn)

    /**
     * 自增
     *
     * @param key
     * @param filed
     * @param cnt
     * @return
     */
    public static Long hIncr(String key, String filed, Integer cnt) {
        return template.execute((RedisCallback<Long>) con -> con.hIncrBy(keyBytes(key), valBytes(filed), cnt));
    }

2.用戶計數(shù)統(tǒng)計

我們將用戶的相關(guān)計數(shù),每個用戶對應(yīng)一個hash數(shù)據(jù)結(jié)構(gòu)

key: user_statistic_${userId}

filed: 

  • follCount: 關(guān)注數(shù)
  • fansCount: 粉絲數(shù)
  • articleCount: 已發(fā)布文章數(shù)
  • praiseCount: 文章點贊數(shù)
  • readCount: 文章被閱讀數(shù)
  • collectionCount: 文章被收藏數(shù)

計數(shù)器的核心就在于滿足條件之后,實現(xiàn)的計數(shù) + 1 / -1

通常的業(yè)務(wù)場景中,此類計數(shù)不太建議直接與業(yè)務(wù)代碼強耦合,舉個例子

用戶收藏了一篇文章,若按照正常的設(shè)計,就是在收藏這里,帶哦用計數(shù)器執(zhí)行 + 1 操作 

上面這樣實現(xiàn)有問題嗎? 

顯然是沒有額問題的,但是不夠好,不夠優(yōu)雅。

比如現(xiàn)在技術(shù)派的場景中,點贊之后,除了計數(shù)器更新之外,還有前面用戶說到的用戶活躍度更新,若所有的邏輯都放在業(yè)務(wù)中,會導(dǎo)致業(yè)務(wù)的耦合較重

技術(shù)派選擇消息機制來應(yīng)對這種場景(大一點的項目會設(shè)計自己額的消息總線,為了讓各自的業(yè)務(wù)邏輯內(nèi)聚,向外拋出自己額的狀態(tài)/業(yè)務(wù)變更消息,實現(xiàn)解耦)

對映的,計數(shù)實現(xiàn)邏輯在。src/main/java/com/github/paicoding/forum/service/statistics/listener/UserStatisticEventListener.java

package com.github.paicoding.forum.service.statistics.listener;
 
import com.github.paicoding.forum.api.model.enums.ArticleEventEnum;
import com.github.paicoding.forum.api.model.event.ArticleMsgEvent;
import com.github.paicoding.forum.api.model.vo.notify.NotifyMsgEvent;
import com.github.paicoding.forum.core.cache.RedisClient;
import com.github.paicoding.forum.service.article.repository.dao.ArticleDao;
import com.github.paicoding.forum.service.article.repository.entity.ArticleDO;
import com.github.paicoding.forum.service.comment.repository.entity.CommentDO;
import com.github.paicoding.forum.service.user.repository.entity.UserFootDO;
import com.github.paicoding.forum.service.user.repository.entity.UserRelationDO;
import com.github.paicoding.forum.service.statistics.constants.CountConstants;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
 
import javax.annotation.Resource;
 
/**
 * 用戶活躍相關(guān)的消息監(jiān)聽器
 *
 * @author YiHui
 * @date 2023/8/19
 */
@Component
public class UserStatisticEventListener {
    @Resource
    private ArticleDao articleDao;
 
    /**
     * 用戶操作行為,增加對應(yīng)的積分
     *這段代碼是一個使用Spring框架的事件監(jiān)聽器注解。
     * 它使用了@EventListener注解來指定要監(jiān)聽的事件類型為NotifyMsgEvent.class,并且使用了@Async注解來表示該方法是異步執(zhí)行的。
     *
     * 當(dāng)NotifyMsgEvent事件被發(fā)布時,該事件監(jiān)聽器方法將被自動調(diào)用。由于使用了@Async注解,
     * 該方法將在單獨的線程中異步執(zhí)行,不會阻塞主線程。
     * @param msgEvent
     */
    @EventListener(classes = NotifyMsgEvent.class)
    @Async
    public void notifyMsgListener(NotifyMsgEvent msgEvent) {
        switch (msgEvent.getNotifyType()) {
            //評論/回復(fù)
            case COMMENT:
            case REPLY:
                CommentDO comment = (CommentDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + comment.getArticleId(), CountConstants.COMMENT_COUNT, 1);
                break;
             //刪除評論/回復(fù)
            case DELETE_COMMENT:
            case DELETE_REPLY:
                comment = (CommentDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + comment.getArticleId(), CountConstants.COMMENT_COUNT, -1);
                break;
                //收藏
            case COLLECT:
                UserFootDO foot = (UserFootDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + foot.getDocumentUserId(), CountConstants.COLLECTION_COUNT, 1);
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + foot.getDocumentId(), CountConstants.COLLECTION_COUNT, 1);
                break;
                //取消收藏
            case CANCEL_COLLECT:
                foot = (UserFootDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + foot.getDocumentUserId(), CountConstants.COLLECTION_COUNT, -1);
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + foot.getDocumentId(), CountConstants.COLLECTION_COUNT, -1);
                break;
                //點贊
            case PRAISE:
                foot = (UserFootDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + foot.getDocumentUserId(), CountConstants.PRAISE_COUNT, 1);
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + foot.getDocumentId(), CountConstants.PRAISE_COUNT, 1);
                break;
                //取消點贊
            case CANCEL_PRAISE:
                foot = (UserFootDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + foot.getDocumentUserId(), CountConstants.PRAISE_COUNT, -1);
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + foot.getDocumentId(), CountConstants.PRAISE_COUNT, -1);
                break;
            case FOLLOW:
                UserRelationDO relation = (UserRelationDO) msgEvent.getContent();
                // 主用戶粉絲數(shù) + 1
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + relation.getUserId(), CountConstants.FANS_COUNT, 1);
                // 粉絲的關(guān)注數(shù) + 1
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + relation.getFollowUserId(), CountConstants.FOLLOW_COUNT, 1);
                break;
            case CANCEL_FOLLOW:
                relation = (UserRelationDO) msgEvent.getContent();
                // 主用戶粉絲數(shù) + 1
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + relation.getUserId(), CountConstants.FANS_COUNT, -1);
                // 粉絲的關(guān)注數(shù) + 1
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + relation.getFollowUserId(), CountConstants.FOLLOW_COUNT, -1);
                break;
            default:
        }
    }
 
    /**
     * 發(fā)布文章,更新對應(yīng)的文章計數(shù)
     *
     * @param event
     */
    @Async
    @EventListener(ArticleMsgEvent.class)
    public void publishArticleListener(ArticleMsgEvent<ArticleDO> event) {
        ArticleEventEnum type = event.getType();
        if (type == ArticleEventEnum.ONLINE || type == ArticleEventEnum.OFFLINE || type == ArticleEventEnum.DELETE) {
            Long userId = event.getContent().getUserId();
            int count = articleDao.countArticleByUser(userId);
            RedisClient.hSet(CountConstants.USER_STATISTIC_INFO + userId, CountConstants.ARTICLE_COUNT, count);
        }
    }
}

上面直接基于當(dāng)下技術(shù)派拋出的各種消息事件,來實現(xiàn)用戶/文章對應(yīng)計數(shù)變更

不一樣的地方則在于用戶的文章數(shù)統(tǒng)計,因為消息發(fā)布時,并沒有告知這個文章是 從 未上線狀態(tài)到發(fā)布, 發(fā)布到下線/刪除 ,因此無法進行+1 -1。我們直接采用的是全量的更新策略。

注:

全量更新策略指的是**在數(shù)據(jù)同步或更新過程中,每次都對整個數(shù)據(jù)集進行處理,而不是只更新發(fā)生變化的部分**。

這種策略的優(yōu)點包括:

- **簡單直觀**:由于不需要考慮數(shù)據(jù)的增量變化,因此實現(xiàn)起來相對簡單,易于理解和操作。
- **數(shù)據(jù)一致性**:每次全量更新可以確保目標系統(tǒng)中的數(shù)據(jù)與源系統(tǒng)保持完全一致,避免了因部分更新而導(dǎo)致的數(shù)據(jù)不一致問題。

然而,全量更新策略也存在一些缺點:

- **資源消耗大**:當(dāng)數(shù)據(jù)量龐大或者更新頻率較高時,全量更新可能會占用大量的網(wǎng)絡(luò)帶寬和存儲資源,導(dǎo)致效率低下。
- **系統(tǒng)壓力大**:頻繁的全量更新可能會給系統(tǒng)帶來較大的處理壓力,尤其是在數(shù)據(jù)量持續(xù)增長的情況下,可能會超出系統(tǒng)的處理能力。

此外,在某些情況下,全量更新策略可能不是最佳選擇。例如,在數(shù)據(jù)倉庫中,如果源數(shù)據(jù)庫的數(shù)據(jù)量非常大,而且只有少量數(shù)據(jù)發(fā)生變更,使用全量更新策略就不如增量更新策略高效。增量更新策略只針對發(fā)生變化的數(shù)據(jù)進行處理,這樣可以大大減少數(shù)據(jù)處理的工作量和系統(tǒng)資源的消耗。

總的來說,全量更新策略適用于數(shù)據(jù)量較小或更新頻率較低的場景,而在數(shù)據(jù)量大且更新頻繁的環(huán)境中,可能需要考慮其他更高效的數(shù)據(jù)更新策略。在實際應(yīng)用中,應(yīng)根據(jù)具體的業(yè)務(wù)需求和系統(tǒng)條件來選擇合適的更新策略。

3.用戶統(tǒng)計信息查詢

前面實現(xiàn)了用戶的相關(guān)統(tǒng)計數(shù),查詢用戶的統(tǒng)計信息則相對簡單了,直接hgetall即可。

4.緩存一致性

基本上到上面,一個完整的計數(shù)服務(wù)就已經(jīng)成型了,但是我們在實際的生產(chǎn)服務(wù)中,再自信的人也不保證它沒問題100分。

通常我們會做一個校對/定時同步任務(wù)來保證緩存與實際數(shù)據(jù)中的一致性

技術(shù)派中選擇簡單的定時同步方案來實現(xiàn)

  • 用戶統(tǒng)計信息每天全量同步

                

  • 文章統(tǒng)計信息每天全量同步

總結(jié)

基于redis的incr ,很容易就可以實現(xiàn)計數(shù)相關(guān)的需求支撐,但是為啥我們要用redis來實現(xiàn)一個計數(shù)器呢?直接用數(shù)據(jù)庫的原始數(shù)據(jù)進行統(tǒng)計有什么問題嗎?

通常而言,項目初期,或者項目本身非常簡單,訪問量低,只希望快速上線支撐業(yè)務(wù)時,使用db進行統(tǒng)計即可,優(yōu)勢時簡單,敘述,不容易出問題;缺點則是每次都是實時統(tǒng)計性能差,擴展性不強。

當(dāng)我們項目發(fā)展起來,借助redis直接存儲最終結(jié)果。再展示層直接俄獲取即可,性能更強,滿足高并發(fā),缺點是數(shù)據(jù)的一致性保障難度高。先選擇一個實現(xiàn)代價小的,再重構(gòu)哈啊哈哈。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Redis 底層運行機制與原理流程分析

    Redis 底層運行機制與原理流程分析

    Redis是一個基于內(nèi)存的鍵值存儲系統(tǒng),采用單線程事件驅(qū)動架構(gòu)和epoll/kqueue實現(xiàn)I/O多路復(fù)用,本文給大家介紹Redis 底層運行機制與原理流程分析,感興趣的朋友一起看看吧
    2025-11-11
  • Redis中ZSet的具體使用

    Redis中ZSet的具體使用

    本文主要介紹了Redis中ZSet的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Redis分布式可重入鎖實現(xiàn)方案

    Redis分布式可重入鎖實現(xiàn)方案

    在單進程環(huán)境下,要保證一個代碼塊的同步執(zhí)行,直接用synchronized 關(guān)鍵字或ReetrantLock 即可,在分布式環(huán)境下,要保證多個節(jié)點的線程對代碼塊的同步訪問,就必須要用到分布式鎖方案,本文介紹一下基于 Redis實現(xiàn)的分布式鎖方案,感興趣的朋友一起看看吧
    2024-02-02
  • 使用高斯Redis實現(xiàn)二級索引的方法

    使用高斯Redis實現(xiàn)二級索引的方法

    本文介紹了如何通過高斯Redis搭建二級索引,二級索引在電商、圖(hexastore)、游戲等領(lǐng)域具有廣泛的應(yīng)用場景,高斯redis現(xiàn)網(wǎng)亦有很多類似應(yīng)用,需要的朋友跟隨小編一起看看吧
    2022-07-07
  • 基于Redis實現(xiàn)雙加密Token的示例代碼

    基于Redis實現(xiàn)雙加密Token的示例代碼

    在現(xiàn)代分布式系統(tǒng)中,Token管理是身份驗證和授權(quán)的核心部分,本文將深入分析一個基于Redis的Token管理實現(xiàn),探討其設(shè)計思路、關(guān)鍵代碼邏輯以及實現(xiàn)細節(jié),通過對源碼的逐層剖析,幫助讀者更好地理解Token管理的實現(xiàn)原理,需要的朋友可以參考下
    2025-01-01
  • Redis跨主機連接超時問題的解決方案

    Redis跨主機連接超時問題的解決方案

    在微服務(wù)架構(gòu)中,服務(wù)間通信的穩(wěn)定性是系統(tǒng)可用性的重要保障,我們在近期一次線上排查中,遇到了一個 Redis 跨主機連接頻繁超時的問題,所以本文給大家分享一下Redis跨主機連接超時問題的解決方案,需要的朋友可以參考下
    2025-09-09
  • Redis事務(wù)機制與Springboot項目中的使用方式

    Redis事務(wù)機制與Springboot項目中的使用方式

    Redis事務(wù)機制允許將多個命令打包在一起,作為一個原子操作來執(zhí)行,開啟事務(wù)使用MULTI命令,執(zhí)行事務(wù)使用EXEC命令,取消事務(wù)使用DISCARD命令,監(jiān)視一個或多個鍵使用WATCH命令,Redis事務(wù)的核心思想是將多個命令放入一個隊列中
    2025-03-03
  • 詳解redis中的下載和安裝(最新推薦)

    詳解redis中的下載和安裝(最新推薦)

    本文詳細介紹了如何在Linux和Docker上安裝、配置、啟動和關(guān)閉Redis,包括下載安裝包、解壓、編譯安裝、配置文件修改、前臺和后臺啟動、關(guān)閉以及Docker容器化部署等步驟,感興趣的朋友一起看看吧
    2025-03-03
  • 使用JMeter插件Redis Data Set如何實現(xiàn)高性能數(shù)據(jù)驅(qū)動測試

    使用JMeter插件Redis Data Set如何實現(xiàn)高性能數(shù)據(jù)驅(qū)動測試

    RedisDataSet插件是JMeter的一個插件,可以實現(xiàn)從Redis中動態(tài)加載數(shù)據(jù),并將其用作測試參數(shù),本文詳細介紹如何在JMeter中使用RedisDataSet插件,幫助你實現(xiàn)高效的數(shù)據(jù)驅(qū)動測試
    2025-01-01
  • Redis數(shù)據(jù)庫中實現(xiàn)分布式鎖的方法

    Redis數(shù)據(jù)庫中實現(xiàn)分布式鎖的方法

    這篇文章主要介紹了Redis數(shù)據(jù)庫中實現(xiàn)分布式鎖的方法,Redis是一個高性能的主存式數(shù)據(jù)庫,需要的朋友可以參考下
    2015-06-06

最新評論

隆安县| 内乡县| 张掖市| 莱芜市| 南皮县| 民和| 泸水县| 东莞市| 大城县| 红安县| 庆城县| 曲沃县| 许昌县| 郯城县| 无棣县| 云安县| 淳化县| 久治县| 米易县| 隆昌县| 东乡族自治县| 延长县| 抚宁县| 新巴尔虎右旗| 珠海市| 太和县| 邹城市| 青州市| 洞头县| 朝阳区| 蒙山县| 铁岭市| 綦江县| 芮城县| 张家界市| 米泉市| 武城县| 手机| 鹤岗市| 茶陵县| 岐山县|