Code前端首页关于Code前端联系我们

项目中常见的redis使用场景

terry 2年前 (2023-09-26) 阅读数 41 #数据库

Cache

> set User:1:name shanyue EX 100 NX
OK
> get User:1:name
"shanyue"
复制代码

Cache是​​redis 最常见的使用场景,只能使用set/get来实现,但是有几点需要考虑

  • 如何更好地设置缓存
  • 如何保持缓存与上游数据的一致性
  • 如何解决缓存崩溃和缓存失效问题

会话:用户登录和验证码

> set 5d27e60e6fb9a07f03576687 '{"id": 10086, role: "ADMIN"}' EX 7200
OK
> get 5d27e60e6fb9a07f03576687
"{\"id\": 10086, role: \"ADMIN\"}"
复制代码

这也是一个很常见的场景,但与有状态会话相比,你也可以考虑使用 JWT,每种都有自己的优点和缺点。

消息队列

> lpush UserEmailQueue 1 2 3 4
lpop UserEmailQueue
> rpop UserEmailQueue
1
> rpop UserEmailQueue
2
复制代码

redis队列可以被认为是分布式队列。用作消息队列时,生产者向一端填充数据,消费者向另一端输出数据:(lpush /rpop,rpush/lpop)。不过有一些缺陷,这些缺陷可能是致命的,但是对于一些丢失几条消息并不重要的场景还是可以考虑的

  1. 没有确认,消息可能会丢失
  2. 需要做

    redis 持久化配置

过滤器(dupefilter)

> sadd UrlSet http://1
(integer) 1
> sadd UrlSet http://2
(integer) 1
> sadd UrlSet http://2
(integer) 0
> smembers UrlSet
1) "http://1"
2) "http://2"
复制代码

scrapy-redis 作为分布式爬虫框架,使用 redis Set 数据结构来对爬取的 URL 进行重复数据删除。

# https://github.com/rmax/scrapy-redis/blob/master/src/scrapy_redis/dupefilter.py
def request_seen(self, request):
    """Returns True if request was already seen.
    Parameters
    ----------
    request : scrapy.http.Request
    Returns
    -------
    bool
    """
    fp = self.request_fingerprint(request)
    added = self.server.sadd(self.key, fp)
    return added == 0
复制代码

但是,当url太多时,就会出现内存占用过多的问题

分布式锁

set Lock:User:10086 06be97fc-f258-4202-b60b-8d5412dd5605 EX 60 NX

# 释放锁,一段 LUA 脚本
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
复制代码

这是最简单的单机版本的分布式锁,有以下几点

  • EX 的意思,这样锁过期后就会被释放
  • NX 确保原子性
  • 解锁时比较对应资源生成的 UUID,避免误解锁

使用时分布式锁是为了解决一些性能问题,比如防止分布式定时任务的多次执行(使其幂等),并且考虑到单点redis断掉的几率很小,这是一个您可以使用的分布式锁的独立版本。

限速

限速是指每个时间单位只允许一定数量的请求。有两个关键参数

  • 窗口、单位时间
  • max和最大请求​​次数

最常见的场景:短信验证码每分钟只能发送两次

FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
    ERROR "too many requests per second"
ELSE
    value = INCR(ip)
    IF value == 1 THEN
        EXPIRE(ip,1)
    END
    PERFORM_API_CALL()
END
复制代码

可以使用API​​请求计数器来限制,但要注意更多的问题

  1. 平滑滑动窗口期间,极端情况下会翻倍 请求数量
  2. Race Condition

目前可以根据

> set 5d27e60e6fb9a07f03576687 '{"id": 10086, role: "ADMIN"}' EX 7200
OK
> get 5d27e60e6fb9a07f03576687
"{\"id\": 10086, role: \"ADMIN\"}"
复制代码

TTL key 进行编程进一步限制 或使用

LIST 来维护每个请求。对传入时间戳执行实时过滤。以下是节点实施速率限制器。参考源码node-rate-limiter-flexible
this.client
  .multi()
  .set(rlKey, 0, 'EX', secDuration, 'NX')
  .incrby(rlKey, points)
  .pttl(rlKey)
  .exec((err, res) => {
    if (err) {
      return reject(err);
    }

    return resolve(res);
  })

if (res.consumedPoints > this.points) {
  // ...
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
  // ...
  setTimeout(resolve, delay, res);
} else {
  resolve(res);
}
复制代码
  • node-rate-limiter-flexible
  • 发送邮件、限流、漏桶和令牌桶

分布式websocket

可以通过redis使用 PUB/SUB 用于 Websocket 服务器之间的通信。可以参考以下项目

  • socket.io-redis

作者:shanyue
来源:掘金

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门