欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

介紹

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

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

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

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

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

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

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

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

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

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

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

那用戶與文章的相關統(tǒng)計將是我們的重點,因為這兩個的業(yè)務屬性很相似,因此我們選擇一個重點,以用戶統(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)計

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

key: user_statistic_${userId}

filed: 

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

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

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

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

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

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

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

技術派選擇消息機制來應對這種場景(大一點的項目會設計自己額的消息總線,為了讓各自的業(yè)務邏輯內(nèi)聚,向外拋出自己額的狀態(tài)/業(yè)務變更消息,實現(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;
 
/**
 * 用戶活躍相關的消息監(jiān)聽器
 *
 * @author YiHui
 * @date 2023/8/19
 */
@Component
public class UserStatisticEventListener {
    @Resource
    private ArticleDao articleDao;
 
    /**
     * 用戶操作行為,增加對應的積分
     *這段代碼是一個使用Spring框架的事件監(jiān)聽器注解。
     * 它使用了@EventListener注解來指定要監(jiān)聽的事件類型為NotifyMsgEvent.class,并且使用了@Async注解來表示該方法是異步執(zhí)行的。
     *
     * 當NotifyMsgEvent事件被發(fā)布時,該事件監(jiān)聽器方法將被自動調(diào)用。由于使用了@Async注解,
     * 該方法將在單獨的線程中異步執(zhí)行,不會阻塞主線程。
     * @param msgEvent
     */
    @EventListener(classes = NotifyMsgEvent.class)
    @Async
    public void notifyMsgListener(NotifyMsgEvent msgEvent) {
        switch (msgEvent.getNotifyType()) {
            //評論/回復
            case COMMENT:
            case REPLY:
                CommentDO comment = (CommentDO) msgEvent.getContent();
                RedisClient.hIncr(CountConstants.ARTICLE_STATISTIC_INFO + comment.getArticleId(), CountConstants.COMMENT_COUNT, 1);
                break;
             //刪除評論/回復
            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);
                // 粉絲的關注數(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);
                // 粉絲的關注數(shù) + 1
                RedisClient.hIncr(CountConstants.USER_STATISTIC_INFO + relation.getFollowUserId(), CountConstants.FOLLOW_COUNT, -1);
                break;
            default:
        }
    }
 
    /**
     * 發(fā)布文章,更新對應的文章計數(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);
        }
    }
}

上面直接基于當下技術派拋出的各種消息事件,來實現(xiàn)用戶/文章對應計數(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)保持完全一致,避免了因部分更新而導致的數(shù)據(jù)不一致問題。

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

- **資源消耗大**:當數(shù)據(jù)量龐大或者更新頻率較高時,全量更新可能會占用大量的網(wǎng)絡帶寬和存儲資源,導致效率低下。
- **系統(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ù)更新策略。在實際應用中,應根據(jù)具體的業(yè)務需求和系統(tǒng)條件來選擇合適的更新策略。

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

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

4.緩存一致性

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

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

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

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

                

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

總結(jié)

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

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

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

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

相關文章

  • 淺談Redis存儲數(shù)據(jù)類型及存取值方法

    淺談Redis存儲數(shù)據(jù)類型及存取值方法

    這篇文章主要介紹了淺談Redis存儲數(shù)據(jù)類型及存取值方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-05-05
  • CentOS 7下安裝 redis 3.0.6并配置集群的過程詳解

    CentOS 7下安裝 redis 3.0.6并配置集群的過程詳解

    這篇文章主要給大家介紹了CentOS 7下安裝 redis 3.0.6并配置集群的過程,文中通過示例代碼和詳細的步驟介紹的很相信,對大家具有一定的參考價值,有需要的朋友們下面來一起看看吧。
    2017-01-01
  • Redis 數(shù)據(jù)庫忘記密碼找回或重置的解決方法

    Redis 數(shù)據(jù)庫忘記密碼找回或重置的解決方法

    對于 Redis 數(shù)據(jù)庫,如果忘記了密碼,可以通過密碼重置來找回密碼,今天通過本文給大家分享Redis 數(shù)據(jù)庫忘記密碼找回或重置的解決方法,感興趣的朋友一起看看吧
    2024-01-01
  • 一文了解發(fā)現(xiàn)并解決Redis熱key與大key問題

    一文了解發(fā)現(xiàn)并解決Redis熱key與大key問題

    熱key是服務端的常見問題,本文主要介紹Redis熱key與大key問題的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-05-05
  • Redis 的 GeoHash詳解

    Redis 的 GeoHash詳解

    這篇文章主要介紹了Redis 的 GeoHash詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • 使用Redis實現(xiàn)UA池的方案

    使用Redis實現(xiàn)UA池的方案

    這篇文章主要介紹了使用Redis實現(xiàn)UA池的方案,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-11-11
  • 詳解Redis中的雙鏈表結(jié)構(gòu)

    詳解Redis中的雙鏈表結(jié)構(gòu)

    這篇文章主要介紹了Redis中的雙鏈表結(jié)構(gòu),包括listNode結(jié)構(gòu)的API,需要的朋友可以參考下
    2015-08-08
  • 關于Redis解決Session共享問題

    關于Redis解決Session共享問題

    這篇文章主要介紹了Redis解決Session共享問題,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • Redis整合MySQL主從集群的示例代碼

    Redis整合MySQL主從集群的示例代碼

    本文主要介紹了Redis整合MySQL主從集群的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • redis.clients.jedis.exceptions.JedisBusyException無法處理異常的解決方法

    redis.clients.jedis.exceptions.JedisBusyException無法處理異常的解決方法

    redis.clients.jedis.exceptions.JedisBusyException異常通常不是 Jedis客戶端直接拋出的標準異常,本文就來介紹一下異常的解決方法,感興趣的可以了解一下
    2024-05-05

最新評論