高并发和海量数据下的 9 个 Redis 经典案例剖析!

2023-11-06

往期热门文章:

1、往期精选优秀博文都在这里了!》

2、人见人爱!收款码背后的原理是什么?

3、这么设计,Redis 10亿数据量只需要100MB内存

4、当Docker遇到Intellij IDEA,再次解放了生产力~

5闲鱼靠什么支撑起万亿的交易规模?

本文来源:码洞

业务背景

这次分享主要是围绕 Redis,分享在平时的日常业务开发中遇到的 9 个经典案例,希望通过此次分享可以帮助大家更好的将 Redis 的高级特性应用到日常的业务开发中来。 

首先介绍一下业务背景:总用户量大概是 5亿左右,月活 5kw,日活近 2kw 。服务端有 1000 多个 Redis 实例,100+ 集群,每个实例的内存控制在 20g 以下。

KV 缓存

第一个是最基础也是最常用的就是KV功能,我们可以用 Redis 来缓存用户信息、会话信息、商品信息等等。

下面这段代码就是通用的缓存读取逻辑。

def get_user(user_id):
    user = redis.get(user_id)
    if not user:
        user = db.get(user_id)
        redis.setex(user_id, ttl, user)  // 设置缓存过期时间
    return user
 
def save_user(user):
    redis.setex(user.id, ttl, user)  // 设置缓存过期时间
    db.save_async(user)  // 异步写数据库
 

这个过期时间非常重要,它通常会和用户的单次会话长度成正比,保证用户在单次会话内尽量一直可以使用缓存里面的数据。

当然如果贵公司财力雄厚,又极致注重性能体验,可以将时间设置的长点甚至干脆就不设置过期时间。

当数据量不断增长时,就使用 Codis 或者 Redis-Cluster 集群来扩容。

除此之外 Redis 还提供了缓存模式,Set 指令不必设置过期时间,它也可以将这些键值对按照一定的策略进行淘汰。

打开缓存模式的指令是:config set maxmemory 20gb ,这样当内存达到 20gb 时,Redis 就会开始执行淘汰策略,给新来的键值对腾出空间。

这个策略 Redis 也是提供了很多种,总结起来这个策略分为两块:划定淘汰范围,选择淘汰算法。

比如我们线上使用的策略是 allkeys-lru。这个 allkeys 表示对 Redis 内部所有的 key 都有可能被淘汰,不管它有没有带过期时间,而volatile只淘汰带过期时间的。

Redis 的淘汰功能就好比企业遇到经济寒冬时需要勒紧裤腰带过冬需要进行一轮残酷的人才优化。它会选择只优化临时工呢,还是所有人一律平等都可能被优化。

当这个范围圈定之后,会从中选出若干个名额,怎么选择呢,这个就是淘汰算法。

最常用的就是 LRU 算法,它有一个弱点,那就是表面功夫做得好的人可以逃过优化。

比如你乘机赶紧在老板面前好好表现一下,然后你就安全了。所以到了 Redis 4.0 里面引入了 LFU 算法,要对平时的成绩也进行考核,只做表面功夫就已经不够用了,还要看你平时勤不勤快。

最后还一种极不常用的算法 —— 随机摇号算法,这个算法有可能会把 CEO 也给淘汰了,所以一般不会使用它。

分布式锁

下面我们看第二个功能 —— 分布式锁,这个是除了 KV 缓存之外最为常用的另一个特色功能。

比如一个很能干的资深工程师,开发效率很快,代码质量也很高,是团队里的明星。所以呢诸多产品经理都要来烦他,让他给自己做需求。

如果同一时间来了一堆产品经理都找他,它的思路呢就会陷入混乱,再优秀的程序员,大脑的并发能力也好不到哪里去。

所以呢他就在自己的办公室的门把上挂了一个请勿打扰的牌子,当一个产品经理来的时候先看看门把上有没有这个牌子,如果没有呢就可以进来找工程师谈需求,谈之前要把牌子挂起来,谈完了再把牌子摘了。

这样其它产品经理也要来烦他的时候,如果看见这个牌子挂在那里,就可以选择睡觉等待或者是先去忙别的事。如是这位明星工程师从此获得了安宁。

这个分布式锁的使用方式非常简单,就是使用 Set 指令的扩展参数如下

# 加锁
set lock:$user_id owner_id nx ex=5
# 释放锁
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
# 等价于
del_if_equals lock:$user_id owner_id
 

一定要设置这个过期时间,因为遇到特殊情况 —— 比如地震(进程被 kill -9,或者机器宕机),产品经理可能会选择从窗户上跳下去,没机会摘牌,导致了死锁饥饿,让这位优秀的工程师成了一位大闲人,造成严重的资源浪费。

同时还需要注意这个 owner_id,它代表锁是谁加的 —— 产品经理的工号。以免你的锁不小心被别人摘掉了。

释放锁时要匹配这个 owner_id,匹配成功了才能释放锁。这个 owner_id 通常是一个随机数,存放在 ThreadLocal 变量里(栈变量)。

官方其实并不推荐这种方式,因为它在集群模式下会产生锁丢失的问题 —— 在主从发生切换的时候。官方推荐的分布式锁叫 RedLock,作者认为这个算法较为安全,推荐我们使用。

不过我们一直还使用上面最简单的分布式锁。为什么我们不去使用 RedLock 呢,因为它的运维成本会高一些,需要 3 台以上独立的 Redis 实例,用起来要繁琐一些。

另外,Redis 集群发生主从切换的概率也并不高,即使发生了主从切换出现锁丢失的概率也很低,因为主从切换往往都有一个过程,这个过程的时间通常会超过锁的过期时间,也就不会发生锁的异常丢失。

还有呢就是分布式锁遇到锁冲突的机会也不多,这正如一个公司里明星程序员也比较有限一样,总是遇到锁排队那说明结构上需要优化。

延时队列

下面我们继续看第三个功能 —— 延时队列。

前面我们提到产品经理在遇到「请勿打扰」的牌子时可以选择多种策略,

  1. 干等待

  2. 睡觉

  3. 放弃不干了

  4. 歇一会再干

干等待就是 spinlock,这种方式会烧 CPU,飙高 Redis 的QPS。睡觉就是先 sleep 一会再试,这会浪费线程资源,还会增加响应时长。

放弃不干呢就是告知前端用户待会再试,现在系统压力大有点忙,影响用户体验。

最后一种呢就是现在要讲的策略 —— 待会再来,这是在现实世界里最普遍的策略。

这种策略一般用在消息队列的消费中,这个时候遇到锁冲突该怎么办?不能抛弃不处理,也不适合立即重试(spinlock),这时就可以将消息扔进延时队列,过一会再处理。

图片

有很多专业的消息中间件支持延时消息功能,比如 RabbitMQ 和 NSQ。Redis 也可以,我们可以使用 zset 来实现这个延时队列。

zset 里面存储的是 value/score 键值对,我们将 value 存储为序列化的任务消息,score 存储为下一次任务消息运行的时间(deadline),然后轮询 zset 中 score 值大于 now 的任务消息进行处理。

# 生产延时消息
zadd(queue-key, now_ts+5, task_json)
# 消费延时消息
while True:
  task_json = zrevrangebyscore(queue-key, now_ts, 0, 0, 1)
  if task_json:
    grabbed_ok = zrem(queue-key, task_json)
    if grabbed_ok:
      process_task(task_json)
  else:
    sleep(1000)  // 歇 1s
 

当消费者是多线程或者多进程的时候,这里会存在竞争浪费问题。当前线程明明将 task_json 从 zset 中轮询出来了,但是通过 zrem 来争抢时却抢不到手。

这时就可以使用 LUA 脚本来解决这个问题,将轮询和争抢操作原子化,这样就可以避免竞争浪费。

local res = nil
local tasks = redis.pcall("zrevrangebyscore", KEYS[1], ARGV[1], 0, "LIMIT", 0, 1)
if #tasks > 0 then
  local ok = redis.pcall("zrem", KEYS[1], tasks[1])
  if ok > 0 then
    res = tasks[1] 
  end
end
 
return res

为什么我要将分布式锁和延时队列一起讲呢,因为很早的时候线上出了一次故障。

故障发生时线上的某个 Redis 队列长度爆表了,导致很多异步任务得不到执行,业务数据出现了问题。

后来查清楚原因了,就是因为分布式锁没有用好导致了死锁,而且遇到加锁失败时就 sleep 无限重试结果就导致了异步任务彻底进入了睡眠状态不能处理任务。

那这个分布式锁当时是怎么用的呢?用的就是 setnx + expire,结果在服务升级的时候停止进程直接就导致了个别请求执行了 setnx,但是 expire 没有得到执行,于是就带来了个别用户的死锁。

但是后台呢又有一个异步任务处理,也需要对用户加锁,加锁失败就会无限 sleep 重试,那么一旦撞上了前面的死锁用户,这个异步线程就彻底熄火了。

因为这次事故我们才有了今天的正确的分布式锁形式以及延时队列的发明,还有就是优雅停机,因为如果存在优雅停机的逻辑,那么服务升级就不会导致请求只执行了一半就被打断了,除非是进程被 kill -9 或者是宕机。

定时任务

分布式定时任务有多种实现方式,最常见的一种是 master-workers 模型。

master 负责管理时间,到点了就将任务消息仍到消息中间件里,然后worker们负责监听这些消息队列来消费消息。

著名的 Python 定时任务框架 Celery 就是这么干的。但是 Celery 有一个问题,那就是 master 是单点的,如果这个 master 挂了,整个定时任务系统就停止工作了。

图片

另一种实现方式是 multi-master 模型。这个模型什么意思呢,就类似于 Java 里面的 Quartz 框架,采用数据库锁来控制任务并发。

会有多个进程,每个进程都会管理时间,时间到了就使用数据库锁来争抢任务执行权,抢到的进程就获得了任务执行的机会,然后就开始执行任务,这样就解决了 master 的单点问题。

这种模型有一个缺点,那就是会造成竞争浪费问题,不过通常大多数业务系统的定时任务并没有那么多,所以这种竞争浪费并不严重。

还有一个问题它依赖于分布式机器时间的一致性,如果多个机器上时间不一致就会造成任务被多次执行,这可以通过增加数据库锁的时间来缓解。

图片

现在有了 Redis 分布式锁,那么我们就可以在 Redis 之上实现一个简单的定时任务框架。

# 注册定时任务
hset tasks name trigger_rule
# 获取定时任务列表
hgetall tasks
# 争抢任务
set lock:${name} true nx ex=5
# 任务列表变更(滚动升级)
# 轮询版本号,有变化就重加载任务列表,重新调度时间有变化的任务
set tasks_version $new_version
get tasks_version

如果你觉得 Quartz 内部的代码复杂的让人看不懂,分布式文档又几乎没有,很难折腾,可以试试 Redis,使用它会让你少掉点头发。

Life is Short,I use Redis
https://github.com/pyloque/taskino


频率控制

如果你做过社区就知道,不可避免总是会遇到垃圾内容。一觉醒来你会发现首页突然会某些莫名其妙的广告帖刷屏了。如果不采取适当的机制来控制就会导致用户体验收到严重影响。

控制广告垃圾贴的策略非常多,高级一点的通过 AI,最简单的方式是通过关键词扫描。还有比较常用的一种方式就是频率控制,限制单个用户内容生产速度,不同等级的用户会有不同的频率控制参数。

频率控制就可以使用 Redis 来实现,我们将用户的行为理解为一个时间序列,我们要保证在一定的时间内限制单个用户的时间序列的长度,超过了这个长度就禁止用户的行为。它可以是用 Redis 的 zset 来实现。

图片

图中绿色的部门就是我们要保留的一个时间段的时间序列信息,灰色的段会被砍掉。统计绿色段中时间序列记录的个数就知道是否超过了频率的阈值。

# 下面的代码控制用户的 ugc 行为为每小时最多 N 次
 
hist_key = "ugc:${user_id}"
with redis.pipeline() as pipe:
  # 记录当前的行为
  pipe.zadd(hist_key, ts, uuid)
  # 保留1小时内的行为序列
  pipe.zremrangebyscore(hist_key, 0, now_ts - 3600)
  # 获取这1小时内的行为数量
  pipe.zcard(hist_key)
  # 设置过期时间,节约内存
  pipe.expire(hist_key, 3600)
  # 批量执行
  _, _, count, _ = pipe.exec()
  return count > N


服务发现

技术成熟度稍微高一点的企业都会有服务发现的基础设施。通常我们都会选用 zookeeper、etcd、consul 等分布式配置数据库来作为服务列表的存储。

它们有非常及时的通知机制来通知服务消费者服务列表发生了变更。那我们该如何使用 Redis 来做服务发现呢?

图片

这里我们要再次使用 zset 数据结构,我们使用 zset 来保存单个服务列表。多个服务列表就使用多个 zset 来存储。

zset 的 value 和 score 分别存储服务的地址和心跳的时间。服务提供者需要使用心跳来汇报自己的存活,每隔几秒调用一次 zadd。服务提供者停止服务时,使用 zrem 来移除自己。

zadd service_key heartbeat_ts addr
zrem service_key addr

这样还不够,因为服务有可能是异常终止,根本没机会执行钩子,所以需要使用一个额外的线程来清理服务列表中的过期项

zremrangebyscore service_key 0 now_ts - 30  # 30s 都没来心跳

接下来还有一个重要的问题是如何通知消费者服务列表发生了变更,这里我们同样使用版本号轮询机制。当服务列表变更时,递增版本号。消费者通过轮询版本号的变化来重加载服务列表。

if zadd() > 0 || zrem() > 0 || zremrangebyscore() > 0:
incr service_version_key

但是还有一个问题,如果消费者依赖了很多的服务列表,那么它就需要轮询很多的版本号,这样的 IO 效率会比较低下。

这时我们可以再增加一个全局版本号,当任意的服务列表版本号发生变更时,递增全局版本号。

这样在正常情况下消费者只需要轮询全局版本号就可以了。当全局版本号发生变更时再挨个比对依赖的服务列表的子版本号,然后加载有变更的服务列表。

图片

位图

我们的签到系统做的比较早,当时用户量还没有上来,设计上比较简单,就是将用户的签到状态用 Redis的 hash 结构来存储,签到一次就在 hash 结构里记录一条,签到有三种状态,未签到、已签到和补签,分别是 0、1、2 三个整数值。

hset sign:${user_id} 2019-01-01 1
hset sign:${user_id} 2019-01-02 1
hset sign:${user_id} 2019-01-03 2
...

这非常浪费用户空间,到后来签到日活过千万的时候,Redis 存储问题开始凸显,直接将内存飚到了 30G+,我们线上实例通常过了 20G 就开始报警,30G 已经属于严重超标了。

这时候我们就开始着手解决这个问题,去优化存储。我们选择了使用位图来记录签到信息,一个签到状态需要两个位来记录,一个月的存储空间只需要 8 个字节。这样就可以使用一个很短的字符串来存储用户一个月的签到记录。

优化后的效果非常明显,内存直接降到了 10 个G。因为查询整个月的签到状态 API 调用的很频繁,所以接口的通信量也跟着小了很多。

图片

但是位图也有一个缺点,它的底层是字符串,字符串是连续存储空间,位图会自动扩展,比如一个很大的位图 8m 个位,只有最后一个位是 1,其它位都是零,这也会占用1m 的存储空间,这样的浪费非常严重。

所以呢就有了咆哮位图这个数据结构,它对大位图进行了分段存储,全位零的段可以不用存。

另外还对每个段设计了稀疏存储结构,如果这个段上置 1 的位不多,可以只存储它们的偏移量整数。这样位图的存储空间就得到了非常显著的压缩。

这个咆哮位图在大数据精准计数领域非常有价值,感兴趣的同学可以了解一下。

https://juejin.im/post/5cf5c817e51d454fbf5409b0

模糊计数

前面提到这个签到系统,如果产品经理需要知道这个签到的日活月活怎么办呢?通常我们会直接甩锅——请找数据部门。

但是数据部门的数据往往不是很实时,经常前一天的数据需要第二天才能跑出来,离线计算是通常是定时的一天一次。那如何实现一个实时的活跃计数?

最简单的方案就是在 Redis 里面维护一个 set 集合,来一个用户,就 sadd 一下,最终集合的大小就是我们需要的 UV 数字。

但是这个空间浪费很严重,仅仅为了一个数字要存储这样一个庞大的集合似乎非常不值当。那该怎么办?

这时你就可以使用 Redis 提供的 HyperLogLog 模糊计数功能,它是一种概率计数,有一定的误差,误差大约是 0.81%。

但是空间占用很小,其底层是一个位图,它最多只会占用 12k 的存储空间。而且在计数值比较小的时候,位图使用稀疏存储,空间占用就更小了。

# 记录用户
pfadd sign_uv_${day} user_id
# 获取记录数量
pfcount sign_uv_${day}

微信公众号文章的阅读数可以使用它,网页的 UV 统计它都可以完成。但是如果产品经理非常在乎数字的准确性,比如某个统计需求和金钱直接挂钩,那么你可以考虑一下前面提到的咆哮位图。

它使用起来会复杂一些,需要提前将用户 ID 进行整数序列化。Redis 没有原生提供咆哮位图的功能,但是有一个开源的 Redis Module 可以拿来即用。

https://github.com/aviggiano/redis-roaring

布隆过滤器

最后我们要讲一下布隆过滤器,如果一个系统即将会有大量的新用户涌入时,它就会非常有价值,可以显著降低缓存的穿透率,降低数据库的压力。

这个新用户的涌入不一定是业务系统的大规模铺开,也可能是因为来自外部的缓存穿透攻击。
 

def get_user_state0(user_id):
  state = cache.get(user_id)
  if not state:
    state = db.get(user_id) or {}
    cache.set(user_id, state)
  return state
 
def save_user_state0(user_id, state):
  cache.set(user_id, state)
  db.set_async(user_id, state)

比如上面就是这个业务系统的用户状态查询接口代码,现在一个新用户过来了,它会先去缓存里查询有没有这个用户的状态数据

因为是新用户,所以肯定缓存里没有。然后它就要去查数据库,结果数据库也没有。如果这样的新用户大批量瞬间涌入,那么可以预见数据库的压力会比较大,会存在大量的空查询。

我们非常希望 Redis 里面有这样的一个 set,它存放了所有用户的 id,这样通过查询这个 set 集合就知道是不是新用户来了。

当用户量非常庞大的时候,维护这样的一个集合需要的存储空间是很大的。

这时候就可以使用布隆过滤器,它相当于一个 set,但是呢又不同于 set,它需要的存储空间要小的多。

比如你存储一个用户 id 需要 64 个字节,而布隆过滤器存储一个用户 id 只需要 1个字节多点。但是呢它存的不是用户 id,而是用户 id 的指纹,所以会存在一定的小概率误判,它是一个具备模糊过滤能力的容器。

图片

当它说用户 id 不在容器中时,那么就肯定不在。当它说用户 id 在容器里时,99% 的概率下它是正确的,还有 1% 的概率它产生了误判。

不过在这个案例中,这个误判并不会产生问题,误判的代价只是缓存穿透而已。

相当于有 1% 的新用户没有得到布隆过滤器的保护直接穿透到数据库查询,而剩下的 99% 的新用户都可以被布隆过滤器有效的挡住,避免了缓存穿透。

def get_user_state(user_id):
  exists = bloomfilter.is_user_exists(user_id)
  if not exists:
    return {}
  return get_user_state0(user_id)
 
def save_user_state(user_id, state):
  bloomfilter.set_user_exists(user_id)
  save_user_state0(user_id, state)

布隆过滤器的原理有一个很好的比喻,那就是在冬天一片白雪覆盖的地面上,如果你从上面走过,就会留下你的脚印。

如果地面上有你的脚印,那么就可以大概率断定你来过这个地方,但是也不一定,也许别人的鞋正好和你穿的一模一样。可是如果地面上没有你的脚印,那么就可以 100% 断定你没来过这个地方

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

高并发和海量数据下的 9 个 Redis 经典案例剖析! 的相关文章

  • nodejs websockets 订阅多个频道

    我的 Nodejs 程序的最终目标是订阅多个频道 提供实时金融信息并处理流数据 在 python 中 可以使用 functools partial 和 websocket 订阅多个频道 这允许一个 python 程序订阅多个频道 就我而言
  • 将 StackExchange.Redis 客户端与 Redis 集群结合使用

    如何告诉 StackExchange Redis v1 0 481 它即将连接到 Redis 集群 v3 2 6 如果重要的话 而不仅仅是独立 复制实例 例如 当我使用 redis cli 时 我必须传递 c 标志以使其具有集群感知能力 S
  • Azure Redis 缓存 - GET 调用超时

    我们在 Azure 中有多个 Web 和辅助角色通过 StackExchange Redis 库连接到我们的 Azure Redis 缓存 并且我们经常收到超时 这使得我们的端到端解决方案陷入停滞 其中之一的示例如下 System Time
  • 在 Mac OSX 上卸载 Redis。安装更新后正在运行旧版本

    在Mac上 如何找到旧版本的Redis并将其完全卸载 我使用 OSX 并使用以下命令安装 Redisbrew install redis 由brew安装的版本状态redis 3 0 7 但是 当我运行命令时 redis server输出表明
  • StackExchange.Redis 简单 C# 示例

    我正在寻找一个非常简单的 C 入门应用程序来使用 StackExchange Redis 我在网上搜索并发现StackExchange Redis https github com StackExchange StackExchange R
  • Express 和 Redis 会话的过期时间

    我正在使用express和redis来使会话在我的系统上保持活动状态 我在设置 sessionCookie 上的 maxAge 时遇到一些问题 默认情况下 我读到的时间是 24 小时 但这对于保持其存活来说是很长的时间 我想设置大约 30
  • django:redis:CommandError: 您尚未设置运行服务器所需的 ASGI_APPLICATION

    我正在尝试在 django 中创建套接字 我按照这个安装了asgi redislink https realpython com getting started with django channels 当我运行命令 python mana
  • 如何判断sidekiq是否连接到redis服务器?

    使用控制台 如何判断 sidekiq 是否连接到 Redis 服务器 我希望能够做这样的事情 if sidekiq is connected to redis psuedo code MrWorker perform async do wo
  • StackExchange.Redis 和 StackExchange.Redis.StrongName 之间有什么区别?

    当我关注Azure时文档 http azure microsoft com en us documentation articles cache dotnet how to use azure redis cache 关于如何在Azure
  • Redis 写入 .ssh/authorized_keys

    当前设置 2 个主服务器 12 个工作服务器 工作人员通过 ssh copy id 连接到主设备 主设备和工作人员正在主设备上的 redis 队列中写入数据 过去一周我遇到的问题是 Redis 正在将数据写入authorized keys
  • Redis部署配置-主从复制

    目前我有两台服务器 我已经部署了基于node js Express JS的Web服务API 我正在使用 Redis 来缓存 JSON 字符串 将此设置部署到生产中的最佳选择是什么 我懂了here https stackoverflow co
  • Docker&Celery - 错误:Pidfile (celerybeat.pid) 已存在

    应用程序包括 姜戈 雷迪斯 芹菜 码头工人 Postgres 在将项目合并到 docker 之前 一切都运行顺利且正常 但是一旦将其移入容器 就开始出现问题 起初它开始得很好 但过了一会儿我确实收到了以下错误 celery beat 1 E
  • 使用 EVAL、SCAN 和 DEL 的 Redis 通配符删除脚本返回“非确定性命令后不允许写入命令”

    因此 我正在寻求构建一个 lua 脚本 该脚本使用 SCAN 根据模式查找键并删除它们 原子地 我首先准备了以下脚本 local keys local done false local cursor 0 repeat local resul
  • connect-redis - 如何保护会话对象免受竞争条件影响

    我使用 nodejs 和 connect redis 来存储会话数据 我将用户数据保存在会话中 并在会话生命周期中使用它 我注意到两个更改会话数据的请求之间可能存在竞争条件 我尝试过使用 redis lock 来锁定会话 但这对我来说有点问
  • Redis 块推送直到列表有空位

    我正在寻找类似的东西BLPUSH该命令将阻塞 直到列表的长度低于指定值max size 目的是防止生产者运行速度快于消费者时列表无限增长 功能与 python 非常相似Queue put https docs python org 3 li
  • python 3.5 中的 json.loads 和 Redis

    我使用 json dumps 创建了一个 JSON 对象 并在 Redis 列表中将其 RPUSH ed 当使用 LRANGE redis lrange 返回 JSON 时 我收到一个二进制字符串 b si 00 ff 所以 json lo
  • 如何在多个Lua State(多线程)之间传递数据?

    我在中启动Redis连接池redis lua 通过从 C 调用 我得到了redis lua state 此 Lua 状态全局启动一次 仅在其他线程中启动get从中 当有一个 HTTP 请求 工作线程 时 我需要从redis lua stat
  • redis-cli 重定向到 127.0.0.1

    我在PC1上启动Redis集群 然后在PC2上连接它 当需要重定向到另一个集群节点时 它会显示Redirected to slot 7785 located at 127 0 0 1 但应该显示Redirected to slot 7785
  • 如何批量删除Redis中数十万个带有特殊字符的key

    我们有一个包含数十万个 Redis 键的列表 其中包含各种特殊字符 我们希望批量删除它们 对于这个问题上的类似问题 有一些很好的答案 如何使用 Redis 自动删除与模式匹配的键 https stackoverflow com questi
  • 通过 StackExchange.Redis 连接到 Redis Servier

    我尝试使用以下方法制作一个测试项目Redis https redis io服务器 通过 Virtual Box 安装在 Linux Ubuntu 虚拟机上 Linux 机器通过 Virtual Box 的桥接适配器与本地网络连接 Virtu

随机推荐

  • proj.4的下载和编译

    简介 下载 编译 生成lib 简介 没啥好说的 地图坐标转换的库 下载 https github com OSGeo PROJ 选择tags下的最新版7 2 0 安装方法参考git下的安装说明 gt https proj org insta
  • Web3.0是什么?只需要记住这三个关键词

    作者 赛联区块链教育张群 2020年以后 科技发展一日千里 稍有不慎我们就会被社会淘汰 区块链 Web3是2022年最大风口 也是互联网未来发展趋势 Web3 0到底是什么 在给学员讲区块链课时我给大家用了这这三个词 保证你瞬时就能深刻理解
  • 2、k8s pod原理详解

    2 k8s pod原理详解 Kubernetes Pod 介绍 Pod 介绍与原理 POD操作实战 POD的创建和删除 pod的生命周期管理 资源的配额和限制 静态pod Init Containers k8s健康检查 POD镜像升级 po
  • 软件测试用例模板和例子_如何设计一个好的测试用例

    什么是测试用例 测试用例也叫测试案例 是在执行测试之前由测试人员编写的指导测试过程的重要文档 主要包括 用例编号 测试目的 测试步骤 预期结果等 注意 不同公司使用的用例模板可能存在差异 但都大同小异 为什么要写测试用例 1 防止测试点的遗
  • 数百款惠普打印机易受严重RCE漏洞影响

    聚焦源代码安全 网罗国内外最新资讯 编译 代码卫士 惠普发布了关于三个严重漏洞的安全公告 它们影响数百款 LaserJet Pro Pagewide Pro Office Jet Enterprise Large Format 和 Desk
  • C++程序如何编译运行

    下面是C 入门的hello world程序源码 include
  • 企业做数字化转型,该如何选择适合自己的数据可视化工具?

    企业该如何选择适合自己的数据可视化工具 A 公司准备进行数字化转型了 老板让我找一个BI工具 但是市面上的选择太多了 我到底是该看品牌还是该看功能呢 B 你了解过BI工具的设计路线吗 其实很多人都忽略了 这也是选BI的重要参考点之一 A B
  • axios拦截器作用及工作流程详解

    axios 拦截器 在请求或响应被then或catch处理之前拦截它们 作用 主要是在axios请求和响应之前拦截请求和响应数据 拦截器有两种 请求拦截器一般会统一在请求头中添加token 响应拦截一般会处理异常错误信息 以及401跳转登录
  • 使用delphi开发人工智能程序(环境搭建)

    1 下载P4D组件 1 下载 2 安装 设置libraries路径 D python4delphi master Source 编译和安装 成功 运行demo测试 D python4delphi master Demos Demo02
  • OGAI详解:AIStation调度平台如何实现大模型高效长时间持续训练

    大模型是当前通用人工智能产业发展创新的核心技术 目前国内已发布的生成式AI模型超过了100个 面向以大模型为核心的生成式AI开发与应用场景 近日浪潮信息发布了大模型智算软件栈OGAI Open GenAI Infra 元脑生智 为大模型业务
  • 【毕业设计】基于超声波智能跟随小车 - 单片机 物联网 stm32 c51

    文章目录 1 项目简介 2 课题背景 3 硬件说明 3 1 小车硬件设计 3 2 小车底座 3 3 无线收发器 3 4 超声波模块 3 5 直流电机和电源 3 6 目标携带装置 3 7 整体电路图 4 跟随系统的软件设计 4 1 方案一 4
  • Java+Selenium3方法篇30-Selenium中截图方法-TakeScreenshot

    前面几篇介绍了Actions类中两种方法 本来打算介绍右键 然后类似点击向下箭头这样操作 但是在现在的firefox和chrome都不能正常实现 记得之前selenium2 53是可以在firefox上实现的 本篇介绍Selenium中截图
  • 【小程序开发必备】微信小程序常用API全介绍,附示例代码和使用场景

    文章目录 人工智能福利文章 1 网络请求相关API 1 1 wx request 1 2 wx uploadFile 1 3 wx downloadFile 1 4 wx connectSocket 2 页面跳转相关API 2 1 wx n
  • android14预览版介绍及解读

    前言 android14快要来了 最近2月8日 android14的第一个开发者预览版发布了 正式版大约会和往常一样 大概率在六月份左右推出 八九月份时会有国内会有第一批手机支持安卓14 所以 本文就带你来了解一下安卓14的变化吧 本文主要
  • 完全用Linux工作之二(什么是 Windows 能干而 Linux 干不了的事情?)

    原文地址 http www chinaunix net jh 4 16102 html 什么是 Windows 能干而 Linux 干不了的事情 Windows 能干而 Linux 干不了的事情 那就是不需要干的事情 有个朋友看我半年没有用
  • flutter中文教程

    https flutterchina club setup windows
  • ubuntu vim保存退出命令_vim入门

    1 vim 在Linux系统 我们最常用的操作方式就是通过枯燥乏味的命令行对系统进行各种操作 而随着我们不断的深入使用会发现 几乎所有的服务都需要通过修改配置文件里的参数进行相应的配置 那么问题来了 不像是windows或者MacOS的各种
  • go语言基础-----01-----go语言特性

    一 go语言特性 1 1 开发环境搭建 实际上就是安装go环境与GoLand IDE工具 go语言环境 go语言环境下载地址 这个下载时需要注意 首先使用下面命令查看对应的win系统架构 在选择对应的版本 否则在vscode下载插件时 dl
  • 手把手教你在vue中使用自定义指令全局封装防抖节流函数

    第一步在src下创建utils文件夹并创建common js文件 utils common js 文件 function throttle bindObj fn delay bindObj prevTime Date now return
  • 高并发和海量数据下的 9 个 Redis 经典案例剖析!

    往期热门文章 1 往期精选优秀博文都在这里了 2 人见人爱 收款码背后的原理是什么 3 这么设计 Redis 10亿数据量只需要100MB内存 4 当Docker遇到Intellij IDEA 再次解放了生产力 5 闲鱼靠什么支撑起万亿的交