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

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

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

介紹

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

  • 文章帶贊數(shù)
  • 收藏?cái)?shù)
  • 評(píng)論數(shù)
  • 用戶粉絲數(shù)
  • ......

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

下面來(lái)看一下,redis的計(jì)數(shù)器是怎樣用于技術(shù)派的技術(shù)場(chǎng)景的

計(jì)數(shù)的業(yè)務(wù)場(chǎng)景

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

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

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

站點(diǎn)的pv/uv等統(tǒng)計(jì)信息

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

注意上面的幾個(gè)場(chǎng)景,這里主要介紹redis計(jì)數(shù)器的使用

那用戶與文章的相關(guān)統(tǒng)計(jì)將是我們的重點(diǎn),因?yàn)檫@兩個(gè)的業(yè)務(wù)屬性很相似,因此我們選擇一個(gè)重點(diǎn),以用戶統(tǒng)計(jì)來(lái)實(shí)現(xiàn)。

redis計(jì)數(shù)器

redis計(jì)數(shù)器,主要是借助原生的incr指令來(lái)實(shí)現(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中存儲(chǔ)的數(shù)字值增值一。

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

接下來(lái)看項(xiàng)目封裝實(shí)現(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.用戶計(jì)數(shù)統(tǒng)計(jì)

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

key: user_statistic_${userId}

filed: 

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

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

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

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

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

顯然是沒(méi)有額問(wèn)題的,但是不夠好,不夠優(yōu)雅。

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

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

對(duì)映的,計(jì)數(shù)實(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)聽(tīng)器
 *
 * @author YiHui
 * @date 2023/8/19
 */
@Component
public class UserStatisticEventListener {
    @Resource
    private ArticleDao articleDao;
 
    /**
     * 用戶操作行為,增加對(duì)應(yīng)的積分
     *這段代碼是一個(gè)使用Spring框架的事件監(jiān)聽(tīng)器注解。
     * 它使用了@EventListener注解來(lái)指定要監(jiān)聽(tīng)的事件類型為NotifyMsgEvent.class,并且使用了@Async注解來(lái)表示該方法是異步執(zhí)行的。
     *
     * 當(dāng)NotifyMsgEvent事件被發(fā)布時(shí),該事件監(jiān)聽(tīng)器方法將被自動(dòng)調(diào)用。由于使用了@Async注解,
     * 該方法將在單獨(dú)的線程中異步執(zhí)行,不會(huì)阻塞主線程。
     * @param msgEvent
     */
    @EventListener(classes = NotifyMsgEvent.class)
    @Async
    public void notifyMsgListener(NotifyMsgEvent msgEvent) {
        switch (msgEvent.getNotifyType()) {
            //評(píng)論/回復(fù)
            case COMMENT:
            case REPLY:
                CommentDO comment = (CommentDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + comment.getArticleId(), CountConstants.COMMENT_COUNT, 1);
                break;
             //刪除評(píng)論/回復(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;
                //點(diǎn)贊
            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;
                //取消點(diǎn)贊
            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ā)布文章,更新對(duì)應(yīng)的文章計(jì)數(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ù)派拋出的各種消息事件,來(lái)實(shí)現(xiàn)用戶/文章對(duì)應(yīng)計(jì)數(shù)變更

不一樣的地方則在于用戶的文章數(shù)統(tǒng)計(jì),因?yàn)橄l(fā)布時(shí),并沒(méi)有告知這個(gè)文章是 從 未上線狀態(tài)到發(fā)布, 發(fā)布到下線/刪除 ,因此無(wú)法進(jìn)行+1 -1。我們直接采用的是全量的更新策略。

注:

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

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

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

然而,全量更新策略也存在一些缺點(diǎn):

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

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

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

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

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

4.緩存一致性

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

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

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

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

                

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

總結(jié)

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

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

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

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

相關(guān)文章

  • Redis 底層運(yùn)行機(jī)制與原理流程分析

    Redis 底層運(yùn)行機(jī)制與原理流程分析

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

    Redis中ZSet的具體使用

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

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

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

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

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

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

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

    Redis跨主機(jī)連接超時(shí)問(wèn)題的解決方案

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

    Redis事務(wù)機(jī)制與Springboot項(xiàng)目中的使用方式

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

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

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

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

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

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

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

最新評(píng)論

会泽县| 潍坊市| 商丘市| 自贡市| 塘沽区| 从化市| 甘泉县| 同仁县| 上饶县| 祁阳县| 武定县| 叶城县| 亳州市| 新沂市| 博野县| 应用必备| 华容县| 房产| 城市| 巩留县| 疏勒县| 井研县| 贞丰县| 定安县| 蓬安县| 柳州市| 米林县| 彭山县| 鄂托克前旗| 娄烦县| 类乌齐县| 屏东县| 盐边县| 灌云县| 读书| 偃师市| 游戏| 临汾市| 英山县| 鹤山市| 苏尼特右旗|