一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數據庫技術|

服務器之家 - 數據庫 - Redis - Redis基于Bitmap實現用戶簽到功能

Redis基于Bitmap實現用戶簽到功能

2021-08-10 18:04大雜草 Redis

很多應用上都有用戶簽到的功能,尤其是配合積分系統一起使用。本文主要介紹了Redis基于Bitmap實現用戶簽到功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下

很多應用上都有用戶簽到的功能,尤其是配合積分系統一起使用。現在有以下需求:

  • 簽到1天得1積分,連續簽到2天得2積分,3天得3積分,3天以上均得3積分等。
  • 如果連續簽到中斷,則重置計數,每月重置計數。
  • 顯示用戶某月的簽到次數和首次簽到時間。
  • 在日歷控件上展示用戶每月簽到,可以切換年月顯示。
  • ...

功能分析

對于用戶簽到數據,如果直接采用數據庫存儲,當出現高并發訪問時,對數據庫壓力會很大,例如雙十一簽到活動。這時候應該采用緩存,以減輕數據庫的壓力,Redis是高性能的內存數據庫,適用于這樣的場景。

如果采用String類型保存,當用戶數量大時,內存開銷就非常大。

如果采用集合類型保存,例如Set、Hash,查詢用戶某個范圍的數據時,查詢效率又不高。

Redis提供的數據類型BitMap(位圖),每個bit位對應0和1兩個狀態。雖然內部還是采用String類型存儲,但Redis提供了一些指令用于直接操作BitMap,可以把它看作一個bit數組,數組的下標就是偏移量。

它的優點是內存開銷小,效率高且操作簡單,很適合用于簽到這類場景。缺點在于位計算和位表示數值的局限。如果要用位來做業務數據記錄,就不要在意value的值。

Redis提供了以下幾個指令用于操作BitMap:

 

命令 說明 可用版本 時間復雜度
SETBIT 對 key 所儲存的字符串值,設置或清除指定偏移量上的位(bit)。 >= 2.2.0 O(1)
GETBIT 對 key 所儲存的字符串值,獲取指定偏移量上的位(bit)。 >= 2.2.0 O(1)
BITCOUNT 計算給定字符串中,被設置為 1 的比特位的數量。 >= 2.6.0 O(N)
BITPOS 返回位圖中第一個值為 bit 的二進制位的位置。 >= 2.8.7 O(N)
BITOP 對一個或多個保存二進制位的字符串 key 進行位元操作。 >= 2.6.0 O(N)
BITFIELD BITFIELD 命令可以在一次調用中同時對多個位范圍進行操作。 >= 3.2.0 O(1)

 

考慮到每月要重置連續簽到次數,最簡單的方式是按用戶每月存一條簽到數據。Key的格式為 u:sign:{uid}:{yyyMM},而Value則采用長度為4個字節的(32位)的BitMap(最大月份只有31天)。BitMap的每一位代表一天的簽到,1表示已簽,0表示未簽。

例如 u:sign:1225:202101 表示ID=1225的用戶在2021年1月的簽到記錄

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 用戶1月6號簽到
SETBIT u:sign:1225:202101 5 1 # 偏移量是從0開始,所以要把6減1
 
# 檢查1月6號是否簽到
GETBIT u:sign:1225:202101 5 # 偏移量是從0開始,所以要把6減1
 
# 統計1月份的簽到次數
BITCOUNT u:sign:1225:202101
 
# 獲取1月份前31天的簽到數據
BITFIELD u:sign:1225:202101 get u31 0
 
# 獲取1月份首次簽到的日期
BITPOS u:sign:1225:202101 1 # 返回的首次簽到的偏移量,加上1即為當月的某一天

示例代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
 
/**
* 基于Redis Bitmap的用戶簽到功能實現類
*
* 實現功能:
* 1. 用戶簽到
* 2. 檢查用戶是否簽到
* 3. 獲取當月簽到次數
* 4. 獲取當月連續簽到次數
* 5. 獲取當月首次簽到日期
* 6. 獲取當月簽到情況
*/
public class UserSignDemo
{
    private IDatabase _db;
 
    public UserSignDemo(IDatabase db)
    {
        _db = db;
    }
 
    /**
     * 用戶簽到
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return 之前的簽到狀態
     */
    public bool DoSign(int uid, DateTime date)
    {
        int offset = date.Day - 1;
        return _db.StringSetBit(BuildSignKey(uid, date), offset, true);
    }
 
    /**
     * 檢查用戶是否簽到
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return 當前的簽到狀態
     */
    public bool CheckSign(int uid, DateTime date)
    {
        int offset = date.Day - 1;
        return _db.StringGetBit(BuildSignKey(uid, date), offset);
    }
 
    /**
     * 獲取用戶簽到次數
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return 當前的簽到次數
     */
    public long GetSignCount(int uid, DateTime date)
    {
        return _db.StringBitCount(BuildSignKey(uid, date));
    }
 
    /**
     * 獲取當月連續簽到次數
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return 當月連續簽到次數
     */
    public long GetContinuousSignCount(int uid, DateTime date)
    {
        int signCount = 0;
        string type = $"u{date.Day}";   // 取1號到當天的簽到狀態
 
        RedisResult result = _db.Execute("BITFIELD", (RedisKey)BuildSignKey(uid, date), "GET", type, 0);
        if (!result.IsNull)
        {
            var list = (long[])result;
            if (list.Length > 0)
            {
                // 取低位連續不為0的個數即為連續簽到次數,需考慮當天尚未簽到的情況
                long v = list[0];
                for (int i = 0; i < date.Day; i++)
                {
                    if (v >> 1 << 1 == v)
                    {
                        // 低位為0且非當天說明連續簽到中斷了
                        if (i > 0) break;
                    }
                    else
                    {
                        signCount += 1;
                    }
                    v >>= 1;
                }
            }
        }
        return signCount;
    }
 
    /**
     * 獲取當月首次簽到日期
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return 首次簽到日期
     */
    public DateTime? GetFirstSignDate(int uid, DateTime date)
    {
        long pos = _db.StringBitPosition(BuildSignKey(uid, date), true);
        return pos < 0 ? null : date.AddDays(date.Day - (int)(pos + 1));
    }
 
    /**
     * 獲取當月簽到情況
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return Key為簽到日期,Value為簽到狀態的Map
     */
    public Dictionary<string, bool> GetSignInfo(int uid, DateTime date)
    {
        Dictionary<string, bool> signMap = new Dictionary<string, bool>(date.Day);
        string type = $"u{GetDayOfMonth(date)}";
        RedisResult result = _db.Execute("BITFIELD", (RedisKey)BuildSignKey(uid, date), "GET", type, 0);
        if (!result.IsNull)
        {
            var list = (long[])result;
            if (list.Length > 0)
            {
                // 由低位到高位,為0表示未簽,為1表示已簽
                long v = list[0];
                for (int i = GetDayOfMonth(date); i > 0; i--)
                {
                    DateTime d = date.AddDays(i - date.Day);
                    signMap.Add(FormatDate(d, "yyyy-MM-dd"), v >> 1 << 1 != v);
                    v >>= 1;
                }
            }
        }
        return signMap;
    }
 
    private static string FormatDate(DateTime date)
    {
        return FormatDate(date, "yyyyMM");
    }
 
    private static string FormatDate(DateTime date, string pattern)
    {
        return date.ToString(pattern);
    }
 
    /**
     * 構建簽到Key
     *
     * @param uid  用戶ID
     * @param date 日期
     * @return 簽到Key
     */
    private static string BuildSignKey(int uid, DateTime date)
    {
        return $"u:sign:{uid}:{FormatDate(date)}";
    }
 
    /**
     * 獲取月份天數
     *
     * @param date 日期
     * @return 天數
     */
    private static int GetDayOfMonth(DateTime date)
    {
        if (date.Month == 2)
        {
            return 28;
        }
        if (new int[] { 1, 3, 5, 7, 8, 10, 12 }.Contains(date.Month))
        {
            return 31;
        }
        return 30;
    }
 
    static void Main(string[] args)
    {
        ConnectionMultiplexer connection = ConnectionMultiplexer.Connect("192.168.0.104:7001,password=123456");
 
        UserSignDemo demo = new UserSignDemo(connection.GetDatabase());
        DateTime today = DateTime.Now;
        int uid = 1225;
 
        { // doSign
            bool signed = demo.DoSign(uid, today);
            if (signed)
            {
                Console.WriteLine("您已簽到:" + FormatDate(today, "yyyy-MM-dd"));
            }
            else
            {
                Console.WriteLine("簽到完成:" + FormatDate(today, "yyyy-MM-dd"));
            }
        }
 
        { // checkSign
            bool signed = demo.CheckSign(uid, today);
            if (signed)
            {
                Console.WriteLine("您已簽到:" + FormatDate(today, "yyyy-MM-dd"));
            }
            else
            {
                Console.WriteLine("尚未簽到:" + FormatDate(today, "yyyy-MM-dd"));
            }
        }
 
        { // getSignCount
            long count = demo.GetSignCount(uid, today);
            Console.WriteLine("本月簽到次數:" + count);
        }
 
        { // getContinuousSignCount
            long count = demo.GetContinuousSignCount(uid, today);
            Console.WriteLine("連續簽到次數:" + count);
        }
 
        { // getFirstSignDate
            DateTime? date = demo.GetFirstSignDate(uid, today);
            if (date.HasValue)
            {
                Console.WriteLine("本月首次簽到:" + FormatDate(date.Value, "yyyy-MM-dd"));
            }
            else
            {
                Console.WriteLine("本月首次簽到:無");
            }
        }
 
        { // getSignInfo
            Console.WriteLine("當月簽到情況:");
            Dictionary<string, bool> signInfo = new Dictionary<string, bool>(demo.GetSignInfo(uid, today));
            foreach (var entry in signInfo)
            {
                Console.WriteLine(entry.Key + ": " + (entry.Value ? "√" : "-"));
            }
        }
    }
}

運行結果

 Redis基于Bitmap實現用戶簽到功能

更多應用場景

  • 統計活躍用戶:把日期作為Key,把用戶ID作為offset,1表示當日活躍,0表示當日不活躍。還能使用位計算得到日活、月活、留存率等數據。
  • 用戶在線狀態:跟統計活躍用戶一樣。

總結

  • 位圖優點是內存開銷小,效率高且操作簡單;缺點是位計算和位表示數值的局限。
  • 位圖適合二元狀態的場景,例如用戶簽到、在線狀態等場景。
  • String類型最大長度為512M。 注意SETBIT時的偏移量,當偏移量很大時,可能會有較大耗時。 位圖不是絕對的好,有時可能更浪費空間。
  • 如果位圖很大,建議分拆鍵。如果要使用BITOP,建議讀取到客戶端再進行位計算。

參考資料

基于Redis位圖實現用戶簽到功能

Redis 深度歷險:核心原理與應用實踐

Redis:Bitmap的setbit,getbit,bitcount,bitop等使用與應用場景

BITFIELD SET command is not working

到此這篇關于Redis基于Bitmap實現用戶簽到功能的文章就介紹到這了,更多相關Redis Bitmap用戶簽到內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/liang24/p/14449835.html

延伸 · 閱讀

精彩推薦
  • RedisRedis全量復制與部分復制示例詳解

    Redis全量復制與部分復制示例詳解

    這篇文章主要給大家介紹了關于Redis全量復制與部分復制的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Redis爬蟲具有一定的參考學習...

    豆子先生5052019-11-27
  • Redisredis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    redis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    這篇文章主要給大家介紹了關于redis中如何使用lua腳本讓你的靈活性提高5個逼格的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具...

    一線碼農5812019-11-18
  • Redis詳解Redis復制原理

    詳解Redis復制原理

    與大多數db一樣,Redis也提供了復制機制,以滿足故障恢復和負載均衡等需求。復制也是Redis高可用的基礎,哨兵和集群都是建立在復制基礎上實現高可用的...

    李留廣10222021-08-09
  • RedisRedis如何實現數據庫讀寫分離詳解

    Redis如何實現數據庫讀寫分離詳解

    Redis的主從架構,能幫助我們實現讀多,寫少的情況,下面這篇文章主要給大家介紹了關于Redis如何實現數據庫讀寫分離的相關資料,文中通過示例代碼介紹...

    羅兵漂流記6092019-11-11
  • RedisRedis 事務知識點相關總結

    Redis 事務知識點相關總結

    這篇文章主要介紹了Redis 事務相關總結,幫助大家更好的理解和學習使用Redis,感興趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • Redisredis實現排行榜功能

    redis實現排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用來實現排行榜功能,本文就來簡單的介紹一下如何使用,具有一定的參考價值,感興趣的小伙伴們...

    乘月歸5022021-08-05
  • RedisRedis的配置、啟動、操作和關閉方法

    Redis的配置、啟動、操作和關閉方法

    今天小編就為大家分享一篇Redis的配置、啟動、操作和關閉方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    大道化簡5312019-11-14
  • Redisredis 交集、并集、差集的具體使用

    redis 交集、并集、差集的具體使用

    這篇文章主要介紹了redis 交集、并集、差集的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友...

    xiaojin21cen10152021-07-27
主站蜘蛛池模板: 亚洲人成网站在线观看青青 | 无码人妻少妇色欲AV一区二区 | 91制片厂 果冻传媒 天美传媒 | 成人影院在线观看免费 | 喜马拉雅听书免费版 | 国产午夜成人无码免费看 | 香港三级系列在线播放 | 国产一级在线免费观看 | 人禽l交视频在线播放 视频 | a级在线看 | 含羞草传媒网站免费进入欢迎 | 免费看国产一级特黄aa大片 | 国产嫩草视频 | sao虎影院桃红视频在线观看 | 极品美女a∨片在线看 | 6080欧美一区二区三区四区 | www.毛片网站| 双性总裁(h) | 俄罗斯一级淫片 | 久久精品国产只有精品 | 男男18视频免费网站 | 为什么丈夫插我我却喜欢被打着插 | 九九九九在线视频播放 | 国产亚洲欧美日韩俺去了 | 羞羞漫画免费漫画页面在线看漫画秋蝉 | 四虎精品永久免费 | 91制片厂制作传媒破解版免费 | 精品福利一区 | 男人天堂网站在线 | 久久国产精品无码视欧美 | 欧美综合亚洲图片综合区 | 九九99香蕉在线视频免费 | 亚洲AV久久无码精品蜜桃 | 99青青青精品视频在线 | 午夜桃色剧场 | 精品一区二区三区高清免费不卡 | 18岁的老处女| 乳色吐息讲的是什么 | 九九99精品 | 91在线亚洲精品一区 | 热99re久久精品精品免费 |