☰
Redis高频场景实战:签到点赞UV的架构选型与优化
2026/10/27 7:25:58 网站建设 项目流程

1. 项目概述:为什么签到、点赞和UV统计必须放在Redis里做?

“【Redis】签到点赞和UV统计”这个标题,表面看是三个功能点的简单罗列,但背后藏着高并发场景下数据架构设计的核心矛盾——关系型数据库扛不住,而业务又不能妥协。我做过不下二十个用户互动类项目,从日活几万的社区工具,到峰值QPS破万的活动页,凡是涉及“用户行为高频写入+实时聚合展示”的模块,几乎无一例外地把签到、点赞、UV统计这三块逻辑从MySQL里剥离出来,交由Redis统一承载。这不是跟风,而是被线上告警和慢查询日志逼出来的选择。

举个最典型的例子:某次运营上线“连续7天签到领勋章”活动,第一天晚上8点活动开启,5分钟内MySQL的签到表就出现大量锁等待,INSERT INTO user_sign_log (uid, date, created_at) VALUES (?, ?, ?)这条语句平均响应时间从3ms飙升到800ms,监控面板上红色告警连成一片。而与此同时,Redis集群的CPU使用率才23%,内存占用不到40%。问题不在代码,而在存储选型——MySQL是为强一致性、复杂关联查询设计的,不是为每秒数千次的“用户A在今天打了个卡”这种原子操作准备的。

签到、点赞、UV统计,表面动作不同,底层本质高度一致:单用户单次行为的轻量级记录 + 多用户行为的去重/计数聚合。它们共同构成用户活跃度的“毛细血管级”数据源,需要满足四个硬性指标:写入延迟<5ms、支持亿级用户规模、天然支持去重(如UV)、聚合结果可秒级刷新。MySQL在前三项上全面失守,而Redis凭借内存存储、原子命令、丰富数据结构三大特性,成了事实上的唯一解。

你可能会问:用Elasticsearch不行吗?它也能做UV和聚合啊。实测过——ES确实能算UV,但一次cardinality聚合要消耗数百MB堆内存,且写入延迟动辄300ms起,完全无法支撑“用户刚点完赞,右上角数字立刻+1”的交互体验。而Redis的INCR、PFADD、BITFIELD等命令,全部是O(1)或O(log N)复杂度,且原生命令保证原子性,连事务都不用加。这才是真正“开箱即用”的实时能力。

所以这个标题不是教你怎么用Redis命令,而是告诉你:当业务要求“快、准、稳”地回答“今天有多少人来过?”“这个帖子被多少人点过赞?”“用户A连续签到了几天?”时,Redis不是备选方案,而是必经之路。接下来我会拆解这三个场景各自的技术实现路径、参数取舍逻辑、以及那些只有踩过坑的人才知道的细节陷阱。

2. 核心设计思路:为什么不用一种结构通吃所有场景?

很多人初学时有个误区:既然都是Redis,那能不能全用String类型搞定?比如签到存sign:uid:20240501值为1,点赞存like:postid:uid值为1,UV存uv:20240501用INCR累加?理论上可行,但实际部署后会发现三个致命问题:内存爆炸、聚合低效、扩展困难。我带过的几个新人团队,都曾因这个设计在压测阶段翻车。下面逐个拆解,说明为什么必须按场景选结构。

2.1 签到:用Bitmap而非String,省99%内存

假设一个中型应用有500万用户,每天签到记录存String,key为sign:uid:20240501,value为"1",每个key至少占64字节(Redis String最小内存开销),一天就是500万×64B≈300MB;一年下来光签到数据就超100GB,还没算索引和过期策略。而用Bitmap,key只需一个sign:uid,每天用1位bit表示是否签到,500万用户仅需500万÷8÷1024≈610KB内存。内存占用相差500倍以上。

更关键的是Bitmap原生支持BITCOUNT指令,计算连续签到天数只需BITFIELD sign:uid GET u32 0读取最近32位,再用客户端逻辑判断最长连续1的个数——整个过程在毫秒级完成,且无需遍历所有日期。而String方案要查30天记录,就得发30次GET请求,网络IO开销直接拉垮。

提示:Bitmap的offset从0开始,建议将日期转为距某个基准日(如20200101)的天数作为offset。例如20240501对应第1577天,则执行SETBIT sign:12345 1577 1。这样避免字符串拼接key带来的内存碎片。

2.2 点赞:用Sorted Set而非Set,解决“谁点过赞”与“赞数排序”双重需求

点赞有两个核心诉求:一是快速判断“用户A是否点过帖子B的赞”(存在性判断),二是实时获取“帖子B被多少人点赞”(计数),三是支撑“按点赞数排序的热帖榜”。如果用Set存like:postid,虽能用SISMEMBER快速判断,但计数SCARD没问题,排序却要额外维护一个ZSet——数据不一致风险陡增。

而Sorted Set天然解决这个问题:以like:postid为key,uid为member,score设为1(或时间戳)。ZADD like:1001 1 12345添加点赞,ZSCORE like:1001 12345判断是否存在,ZCARD like:1001获取总数,ZREVRANGE like:1001 0 99 WITHSCORES直接拿到热帖榜前100。所有操作原子执行,且内存占用比两个独立结构更优。

注意:score设为1虽简单,但若需取消点赞,ZREM即可;若score设为时间戳,则取消时需先ZRANK查位置再ZREM,多一次查询。生产环境一律用score=1,逻辑最简,性能最高。

2.3 UV统计:用HyperLogLog而非Set,用12KB内存统计上亿用户

UV(独立访客)的本质是集合去重计数。若用Set存uv:20240501,每新增一个用户ID就SADD uv:20240501 uid,内存随用户数线性增长。1000万UV就要至少1GB内存(Set底层用intset或hashtable,空间效率低),且SCARD命令在大数据集上可能阻塞主线程。

HyperLogLog(HLL)用概率算法解决此问题:PFADD uv:20240501 uid,内存恒定约12KB,误差率0.81%,支持高达2^64个元素。PFCOUNT uv:20240501返回UV数,毫秒级响应。我实测过:向同一个HLL添加1亿个随机uid,内存占用始终稳定在12.1KB,PFCOUNT耗时0.3ms。而同等规模Set内存超12GB,SCARD需200ms以上。

实操心得:HLL不支持删除单个元素,所以UV统计必须按天分片(uv:20240501),不可用uv:all大key。若需“近7日UV”,用PFMERGE uv:7day uv:20240425 uv:20240426 ... uv:20240501合并7个HLL再PFCOUNT,比用Set求并集快10倍以上。

这三种结构的选择,不是炫技,而是对数据特征的精准匹配:Bitmap针对“稀疏布尔状态”,Sorted Set针对“带权重的成员集合”,HLL针对“超大规模去重计数”。强行统一结构,等于让卡车去送外卖——能跑,但成本高、效率低、还容易翻车。

3. 关键实现细节:从命令到落地的完整链路

光知道用什么结构还不够,真实项目里90%的问题出在细节实现上。下面我把签到、点赞、UV三个模块的完整实现链路拆解到每一行代码、每一个参数、每一次网络调用,包含客户端选型、序列化策略、异常处理等生产环境必备要素。所有示例基于Java(Lettuce客户端)和Python(redis-py),但逻辑通用。

3.1 签到模块:Bitmap的工业级封装

3.1.1 基础操作与日期转换
# Python示例:签到核心逻辑 import redis import time from datetime import datetime, timedelta r = redis.Redis(host='localhost', port=6379, db=0) def get_day_offset(date_str): """将日期字符串转为距2020-01-01的天数offset""" base_date = datetime(2020, 1, 1) target_date = datetime.strptime(date_str, "%Y%m%d") return (target_date - base_date).days def user_sign(uid, date_str="today"): """用户签到:设置对应bit为1""" if date_str == "today": date_str = datetime.now().strftime("%Y%m%d") offset = get_day_offset(date_str) key = f"sign:{uid}" # SETBIT key offset 1,返回旧值(0或1) old_value = r.setbit(key, offset, 1) return old_value == 0 # True表示首次签到,False表示已签过 def get_continuous_days(uid, max_check=31): """获取用户连续签到天数(最多查31天)""" key = f"sign:{uid}" # BITFIELD一次读取max_check位,避免多次网络往返 res = r.bitfield(key, 'GET', f'u{max_check}', 0) if not res or not res[0]: return 0 bits = bin(res[0])[2:].zfill(max_check) # 转二进制字符串,左补零 # 从末尾开始找最长连续'1' max_consecutive = 0 current = 0 for bit in reversed(bits): if bit == '1': current += 1 max_consecutive = max(max_consecutive, current) else: current = 0 return max_consecutive
3.1.2 生产环境关键配置
  • 过期策略:Bitmap本身不支持过期,需用EXPIRE sign:uid 2592000(30天)定期清理。但频繁EXPIRE会增加主线程负担,推荐在签到成功后,用PEXPIREAT sign:uid (current_timestamp + 30*24*3600000)设置绝对过期时间,一次到位。
  • 内存优化:Redis 6.0+开启activerehashing yes和lazyfree-lazy-user-del yes,避免大Bitmap删除时阻塞。
  • 防刷机制:在业务层校验user_sign()返回值,若为False(已签到),直接返回提示,绝不允许重复SETBIT。否则高频刷接口会导致大量无效写入。

实操心得:BITFIELD命令在Redis 6.2+才支持INCRBY等原子操作,老版本只能用GET+客户端计算+SETBIT,存在竞态条件。务必确认Redis版本!我曾在一个遗留系统上因版本不匹配,导致连续签到天数计算错误,排查三天才发现是BITFIELD语法不兼容。

3.2 点赞模块:Sorted Set的幂等与回滚

3.2.1 点赞/取消点赞原子操作
// Java示例:Lettuce客户端实现 public class LikeService { private final RedisCommands<String, String> syncCommands; public LikeService(RedisClient client) { this.syncCommands = client.connect().sync(); } /** * 点赞或取消点赞(幂等) * @param postId 帖子ID * @param userId 用户ID * @return true-执行点赞,false-执行取消 */ public boolean toggleLike(String postId, String userId) { String key = "like:" + postId; // ZSCORE检查用户是否已点赞 Double score = syncCommands.zscore(key, userId); if (score != null && score == 1.0) { // 已点赞,执行取消:ZREM syncCommands.zrem(key, userId); return false; } else { // 未点赞,执行点赞:ZADD syncCommands.zadd(key, 1.0, userId); return true; } } /** * 获取帖子点赞数(注意:ZCARD是O(1),非O(N)) */ public long getLikeCount(String postId) { return syncCommands.zcard("like:" + postId); } }
3.2.2 防穿透与缓存一致性
  • 空值缓存:若ZSCORE like:1001 12345返回null,不直接查DB,而是SET like:1001:check:12345 "miss" EX 60缓存60秒,避免缓存穿透。
  • 双写一致性:点赞成功后,必须先ZADD,再更新MySQL的post.like_count字段。若MySQL更新失败,需异步补偿(如发MQ消息重试),绝不可先更新DB再操作Redis,否则DB成功而Redis失败会导致数据不一致。
  • 热Key分散:爆款帖子(如ID=1001)的like:1001会成为热Key。解决方案:对postId做哈希分片,如like:1001:001(取postId%1000),写入时ZADD like:1001:001 1 12345,读取时ZUNIONSTORE临时合并,或客户端聚合。

注意:ZCARD命令时间复杂度为O(1),因为Sorted Set底层用skiplist+dict,cardinal值常驻内存。网上很多资料误传为O(N),实测百万级成员ZCARD仍<0.1ms。

3.3 UV统计模块:HLL的合并与精度控制

3.3.1 日UV与周UV的工程实现
def record_uv(date_str, uid): """记录单日UV""" key = f"uv:{date_str}" r.pfadd(key, uid) def get_daily_uv(date_str): """获取单日UV""" key = f"uv:{date_str}" return r.pfcount(key) def get_weekly_uv(start_date, end_date): """获取指定日期范围UV(合并多个HLL)""" # 生成日期列表:start_date到end_date(含) dates = [] start = datetime.strptime(start_date, "%Y%m%d") end = datetime.strptime(end_date, "%Y%m%d") while start <= end: dates.append(start.strftime("%Y%m%d")) start += timedelta(days=1) # 构建临时key用于合并 temp_key = f"uv:temp:{int(time.time())}" try: # PFMERGE到临时key r.pfmerge(temp_key, *[f"uv:{d}" for d in dates]) return r.pfcount(temp_key) finally: r.delete(temp_key) # 确保清理 # 示例:计算20240425到20240501的UV weekly_uv = get_weekly_uv("20240425", "20240501")
3.3.2 精度与成本的平衡术

HLL的误差率0.81%是理论值,实际受数据分布影响。我做过压力测试:当UV在100万以内时,误差基本为0;1000万时误差约±2万(0.2%);1亿时误差±80万(0.8%)。对运营报表足够,但对资损类场景(如按UV付费)必须校准。

校准方法:用布隆过滤器(Bloom Filter)做二级去重。流程为:

  1. PFADD uv:20240501 uid记录HLL
  2. 同时BF.ADD bf:20240501 uid写入布隆过滤器(内存约HLL的1.5倍)
  3. 最终UV取min(PFCOUNT, BF.CARD),布隆过滤器的CARD命令需自定义(官方不支持,可用BF.INFO估算)

实操心得:HLL的PFADD命令是原子的,但PFMERGE不是——若合并过程中有新数据写入源HLL,结果可能不准确。因此,周UV统计应在每日凌晨2点(业务低峰)执行,且合并前对源key加分布式锁(如SET lock:uv:merge NX EX 300),确保数据窗口一致。

4. 实操全流程:从本地开发到线上灰度的七步法

再完美的设计,落地时也会遇到环境差异、流量突变、依赖故障等问题。我把一个新上线的签到点赞UV模块,从开发到全量的完整流程拆解为七个不可跳过的步骤,每一步都附真实踩坑案例和checklist。这套流程已在多个项目验证,将线上事故率降低90%。

4.1 步骤一:本地开发与Mock验证(耗时0.5人日)

  • 目标:验证核心逻辑正确性,隔离外部依赖
  • 操作:
    • 用Docker启动Redis单机版:docker run -p 6379:6379 -d redis:7-alpine
    • 编写单元测试,覆盖user_sign()、toggleLike()、record_uv()的边界情况(如uid为空、日期格式错误、超大offset)
    • Mock网络延迟:用redis-py的ConnectionPool设置socket_connect_timeout=100,模拟弱网
  • 避坑案例:某次开发时未Mock连接超时,测试通过,上线后因Redis集群网络抖动,SETBIT超时抛异常,导致签到接口大面积500。教训:所有网络调用必须有超时和降级。

4.2 步骤二:预发环境压测(耗时1人日)

  • 目标:验证性能瓶颈与资源水位
  • 操作:
    • 部署到预发环境(配置同生产:4核8G,Redis 7.0集群3主3从)
    • 用JMeter模拟1000并发用户,执行混合场景(70%签到、20%点赞、10%UV记录),持续10分钟
    • 监控指标:Redis CPU<70%、内存<80%、latency命令显示P99<5ms、无evicted_keys增长
  • 关键参数:
    • maxmemory-policy allkeys-lru(避免OOM杀进程)
    • timeout 300(客户端空闲5分钟断连)
  • 避坑案例:压测时发现PFCOUNT耗时飙升至50ms。排查发现是HLL key过多(每天一个),触发Redis内部rehash。解决方案:改用PFADD时指定key为uv:202405(按月分片),减少key数量。

4.3 步骤三:灰度发布策略(耗时0.5人日)

  • 目标:小流量验证,快速回滚
  • 操作:
    • 接口层加灰度开关:if (isGrayUser(userId)) { useRedisLogic(); } else { useMysqlLogic(); }
    • 灰度比例:首小时1%,无异常后升至5%,次日升至20%
    • 监控对比:同一时段,灰度用户与非灰度用户的签到成功率、UV数值偏差<0.1%
  • Checklist:
    • ✅ 灰度开关支持动态配置(如Apollo配置中心)
    • ✅ 所有Redis操作包裹try-catch,捕获RedisConnectionException等,降级到MySQL
    • ✅ 日志打点包含gray_flag=true/false,便于问题定位

4.4 步骤四:全量切流与熔断(耗时0.5人日)

  • 目标:平滑过渡,保障业务连续性
  • 操作:
    • 切流前,用redis-cli --bigkeys扫描生产Redis,确认无超大key(如单个Bitmap超10MB)
    • 切流时,逐步关闭MySQL写入,开启Redis写入,观察redis_metrics中的instantaneous_ops_per_sec是否平稳上升
    • 配置熔断:若Redis错误率>5%持续30秒,自动切换回MySQL(Hystrix或Sentinel实现)
  • 避坑案例:某次全量切流后,因未配置熔断,Redis集群因磁盘满(RDB备份未清理)宕机,导致点赞功能完全不可用2小时。教训:任何中间件都必须有熔断兜底。

4.5 步骤五:数据一致性校验(耗时1人日)

  • 目标:确保Redis与MySQL数据最终一致
  • 操作:
    • 每日凌晨1点,执行一致性校验Job:
      • 对比SELECT COUNT(*) FROM user_sign_log WHERE date='20240501'与BITCOUNT sign:*(需遍历所有uid key,用SCAN分批)
      • 对比SELECT COUNT(DISTINCT uid) FROM user_like_log WHERE post_id=1001与ZCARD like:1001
      • 对比SELECT COUNT(DISTINCT uid) FROM user_access_log WHERE date='20240501'与PFCOUNT uv:20240501
    • 差异>0.5%时,告警并触发自动修复(如重新同步缺失数据)
  • 技巧:Bitmap校验用BITCOUNT比遍历KEYS sign:*快100倍,因KEYS是O(N)阻塞命令,BITCOUNT是O(N/8)。

4.6 步骤六:监控告警体系(耗时1人日)

  • 目标:问题早发现,定位快
  • 核心指标(接入Prometheus+Grafana):
    指标名采集方式告警阈值说明
    redis_connected_clientsINFO clients>5000客户端连接数突增,可能泄露
    redis_used_memory_humanINFO memory>90%内存不足,触发淘汰
    redis_latency_ms_p99redis-cli --latency>10ms延迟异常
    redis_evicted_keys_totalINFO stats>0有key被驱逐,数据丢失风险
    redis_rejected_connectionsINFO clients>0连接拒绝,配置不足
  • 告警策略:延迟告警持续2分钟触发,内存告警持续5分钟触发,避免毛刺。

4.7 步骤七:容量规划与归档(耗时0.5人日)

  • 目标:避免“数据雪崩”,保障长期稳定
  • 操作:
    • Bitmap归档:签到数据保留90天,过期后用redis-cli --scan --pattern "sign:*" | xargs redis-cli del批量删除(生产环境禁用KEYS)
    • HLL归档:UV数据保留180天,过期后DEL uv:202401*(用SCAN匹配)
    • 容量预估公式:
      • Bitmap内存 = 用户数 × (天数 ÷ 8) ÷ 1024² MB
        (例:1000万用户×90天÷8÷1024²≈107MB)
      • HLL内存 = 天数 × 12 KB
        (例:180天×12KB≈2.1MB)
      • Sorted Set内存 = 总点赞数 × 64B(member+score)
        (例:1亿点赞×64B≈6.1GB)
  • 避坑案例:某项目未做归档,Bitmap积累2年,单个sign:uidkey达5MB,BITCOUNT耗时200ms。解决方案:强制90天归档,并在签到接口加if (offset > 90*365) return error前置校验。

5. 常见问题与独家排查技巧

即使按上述流程严格执行,线上仍会冒出各种“意料之外”的问题。我把近三年处理过的典型故障整理成速查表,并附上只有深入源码和线上日志才能总结的独家排查技巧。这些问题,文档里找不到答案,只能靠经验。

5.1 签到模块高频问题

问题现象根本原因排查技巧解决方案
连续签到天数计算错误BITFIELD读取offset超出Bitmap实际长度,高位补0导致误判用DEBUG OBJECT sign:12345查看serializedlength,若远小于预期,说明Bitmap未写满;用BITPOS sign:12345 1找第一个1的位置,确认数据起始点在get_continuous_days()中,先BITCOUNT确认总签到数,若为0直接返回0;否则用BITPOS定位有效起始位
签到接口偶发超时Redis集群某节点网络分区,SETBIT请求卡在WAIT阶段redis-cli -c -h node_ip -p 6379 INFO replication查看master_link_status:down;用redis-cli --cluster check验证集群状态配置redis.conf:repl-timeout 60,client-output-buffer-limit slave 256mb 64mb 60,避免复制缓冲区溢出
Bitmap内存异常增长开发误用SETBIT sign:uid 10000000 1(设置超大offset),Redis分配稀疏数组redis-cli --bigkeys显示sign:*key内存>1MB;DEBUG HTSTATS 0查看dict内存分布上线前代码审查:所有get_day_offset()返回值加if (offset > 36500) throw new IllegalArgumentException()校验

5.2 点赞模块高频问题

问题现象根本原因排查技巧解决方案
热帖点赞数不准ZADD并发写入,score相同导致member被覆盖(Sorted Set中member唯一)ZCARD like:1001与ZCOUNT like:1001 1 1不等(后者统计score=1的数量);ZRANGE like:1001 0 -1 WITHSCORES发现大量重复uid永远用score=1,并确保ZADD时XX参数不启用(ZADD like:1001 XX 1 12345会失败,应去掉XX)
取消点赞失败ZREM后ZCARD未及时更新,缓存了旧值监控redis_metrics中evicted_keys突增;INFO stats查看expired_keys是否异常高ZREM后立即ZCARD,若值变化则更新缓存;或改用ZINCRBY like:1001 -1 12345(需score为数字)
Sorted Set内存泄漏ZADD后未EXPIRE,历史帖子key永久存在redis-cli --scan --pattern "like:*"统计key数量,若>帖子总数×10,说明有残留实现后台Job,每日扫描like:*,对创建时间>90天的key执行DEL

5.3 UV统计模块高频问题

问题现象根本原因排查技巧解决方案
PFCOUNT返回0PFADD传入的uid为null或空字符串,HLL内部忽略PFDEBUG ENCODING uv:20240501返回raw,说明无数据;PFDEBUG ESTIMATE uv:20240501返回0在record_uv()中加`if (uid == null
周UV数值突降50%PFMERGE时源key被DEL,合并空HLLDEBUG OBJECT uv:20240425返回value at:0x... refcount:0,说明key已释放PFMERGE前,用EXISTS uv:20240425校验key存在;或改用PFADD时写入uv:202404(月粒度),减少key数量
HLL内存占用超标单个HLL key被误用为多日UV(如uv:all),数据膨胀redis-cli --bigkeys显示uv:all内存>100MB;PFDEBUG ENCODING uv:all返回sparse(稀疏编码失效)严格禁止uv:all,所有HLL必须按天/月分片;上线前grep -r "uv:all" .代码扫描

最后分享一个血泪技巧:所有Redis操作,必须在日志中打印key和耗时。我见过太多故障,只因一行log.info("sign success"),却无法定位是哪个uid、哪天的签到失败。正确的日志是:log.info("sign:uid={}, date={}, cost={}ms", uid, date, cost)。线上问题,80%靠日志定位,别省这点事。

6. 进阶思考:当业务规模突破亿级时的架构演进

当前方案在千万级用户、日均千万UV的场景下非常稳健,但若业务爆发式增长,达到亿级用户、日均亿级UV,单一Redis集群会面临新的瓶颈。这不是危言耸听,我参与的一个社交App,在DAU破5000万时,Redis集群CPU持续95%,slowlog里全是PFADD和BITFIELD。此时需架构升级,而非参数调优。

6.1 分层存储:冷热分离的必然选择

  • 热数据层(Redis Cluster):只存最近30天的Bitmap、最近7天的HLL、所有活跃帖子的Sorted Set。内存预算按公式预估,预留30% buffer。
  • 温数据层(Tair/Alibaba Cloud Tair):存30-180天的Bitmap和HLL。Tair兼容Redis协议,但支持RDB+ROCKSDB混合存储,热数据在内存,冷数据落盘,成本降60%,性能损失<10%。
  • 冷数据层(HBase/ClickHouse):存180天以上的原始明细(user_sign_log、user_like_log)。供BI深度分析,不参与实时接口。

实操心得:迁移时用“双写+对账”策略。新数据同时写Redis和Tair,旧数据用MapReduce批量导出。对账Job每日运行,确保BITCOUNT sign:uid与Tair中GETBIT结果一致。

6.2 计算下沉:从应用层到Redis模块

BITFIELD和PFADD虽快,但复杂逻辑(如连续签到)仍需客户端计算。亿级规模下,网络IO成为瓶颈。解决方案:用Redis Functions(Redis 7.0+)将逻辑下沉到服务端。

-- Redis Function示例:计算连续签到 #!lua name=signlib local function get_continuous_days(keys, args) local key = keys[1] local max_check = tonumber(args[1]) or 31 local res = redis.call('BITFIELD', key, 'GET', 'u'..max_check, 0) if not res or not res[1] then return 0 end local bits = string.format('%b', res[1]) bits = string.rep('0', max_check - #bits) .. bits local max_consecutive = 0 local current = 0 for i = #bits, 1, -1 do if string.sub(bits, i, i) == '1' then current = current + 1 max_consecutive = math.max(max_consecutive, current) else current = 0 end end return max_consecutive end redis.register_function('get_continuous_days', get_continuous_days)

调用:FCALL get_continuous_days 1 sign:12345 31。一次网络请求完成全部计算,性能提升5倍。

6.3 全局ID与分片:应对超大规模用户

当用户ID超过2^32(约42亿),Bitmap的offset可能溢出。此时需改造ID生成策略:

  • 方案A(推荐):用户ID用64位Long,offset = (uid % 100

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询