Redis 实现基于 IP 的访问频率限制与黑白名单

好的,各位观众,各位父老乡亲,欢迎来到今天的“Redis兵器谱”讲堂!今天我们要聊点啥呢? 铛铛铛!🎉🎉🎉

没错,就是“Redis 实现基于 IP 的访问频率限制与黑白名单”。

咳咳,别看这名字有点学术范儿,听起来像个严肃的科研课题。实际上,它跟咱们的生活息息相关。想想看,你有没有遇到过这种情况:

  • 网站被恶意攻击,流量瞬间爆炸,服务器不堪重负,眼看就要宕机?
  • 某些用户疯狂薅羊毛,一秒钟注册几百个账号,薅完就跑,留下一个烂摊子?
  • 你想只允许特定地区的用户访问你的网站,给他们提供尊贵的VIP服务,其他人嘛,就只能说抱歉了?

这时候,就需要咱们今天的主角登场了——Redis

Redis可不是一个简单的缓存工具,它就像一位身怀绝技的武林高手,不仅速度快如闪电,还能轻松应对各种复杂的场景。今天我们就来学习一下,如何用Redis这把利剑,斩断恶意流量,守护我们的网站安全。

第一章:Redis 基础知识回顾 (温故而知新)

在开始之前,咱们先来简单回顾一下Redis的基础知识。毕竟,磨刀不误砍柴工嘛!

Redis,全称Remote Dictionary Server,是一个基于内存的键值对数据库。它有以下几个特点:

  • 速度快如闪电:数据存储在内存中,读写速度非常快,可以轻松应对高并发的场景。
  • 支持多种数据结构:除了常见的字符串,还支持列表(List)、集合(Set)、哈希(Hash)、有序集合(ZSet)等数据结构,可以满足各种不同的需求。
  • 功能丰富强大:支持事务、发布/订阅、持久化等功能,可以用来构建各种复杂的应用。
  • 简单易用:API简洁明了,上手容易,深受广大程序员的喜爱。

你可以把Redis想象成一个超高速的字典,你可以通过键(key)来快速查找对应的值(value)。

常用数据结构和命令速查表

数据结构 描述 常用命令 应用场景
String 字符串,可以存储文本、数字、二进制数据等。 SET key value, GET key, INCR key, DECR key, APPEND key value 缓存、计数器、分布式锁、session共享
List 列表,按照插入顺序排序的字符串集合,可以从头部或尾部添加或删除元素。 LPUSH key value [value ...], RPUSH key value [value ...], LPOP key, RPOP key, LRANGE key start stop 消息队列、最新消息列表、文章列表
Set 集合,无序且唯一的字符串集合。 SADD key member [member ...], SREM key member [member ...], SMEMBERS key, SISMEMBER key member, SINTER key [key ...], SUNION key [key ...] 用户标签、好友关系、共同好友
Hash 哈希,键值对的集合,适合存储对象。 HSET key field value, HGET key field, HMSET key field value [field value ...], HMGET key field [field ...], HGETALL key, HDEL key field [field ...] 存储用户信息、商品信息等
ZSet 有序集合,每个成员都关联一个分数(score),集合中的元素按照分数排序。 ZADD key score member [score member ...], ZREM key member [member ...], ZRANGE key start stop [WITHSCORES], ZREVRANGE key start stop [WITHSCORES], ZSCORE key member, ZINCRBY key increment member 排行榜、热门文章、带权重的消息队列

第二章:访问频率限制 (Rate Limiting)

好了,基础知识回顾完毕,接下来我们进入正题:如何用Redis实现访问频率限制。

访问频率限制,顾名思义,就是限制某个IP地址在一定时间内访问某个接口的次数。比如,限制每个IP地址在1分钟内最多访问100次。

实现访问频率限制的思路很简单:

  1. 每次收到请求时,根据IP地址生成一个唯一的key。
  2. 使用Redis的INCR命令,对这个key的值进行自增。
  3. 判断自增后的值是否超过了设定的阈值。
    • 如果超过了阈值,则拒绝访问。
    • 如果没有超过阈值,则允许访问。
  4. 设置key的过期时间,确保一段时间后,访问次数会被重置。

下面我们来看一段示例代码(Python):

import redis
import time

# Redis连接信息
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0

# 访问频率限制参数
MAX_REQUESTS = 100  # 1分钟内最多允许100次请求
TIME_WINDOW = 60  # 时间窗口为60秒

# 连接Redis
redis_client = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB)

def is_allowed(ip_address):
    """
    判断IP地址是否允许访问
    """
    key = f"rate_limit:{ip_address}"  # 生成唯一的key
    current_requests = redis_client.incr(key)  # 自增
    redis_client.expire(key, TIME_WINDOW)  # 设置过期时间

    if current_requests > MAX_REQUESTS:
        return False  # 超过阈值,拒绝访问
    else:
        return True  # 允许访问

# 示例用法
ip = "192.168.1.100"
if is_allowed(ip):
    print(f"IP {ip} 允许访问")
    # 处理请求
else:
    print(f"IP {ip} 访问过于频繁,请稍后再试")

这段代码的核心是is_allowed函数,它接收一个IP地址作为参数,然后:

  1. 生成一个keyrate_limit:{ip_address},例如rate_limit:192.168.1.100
  2. 使用INCR命令自增:如果key不存在,INCR命令会先将key的值初始化为0,然后再自增1。如果key已经存在,INCR命令会将key的值加1。
  3. 设置过期时间expire(key, TIME_WINDOW),设置key的过期时间为TIME_WINDOW秒,也就是60秒。

代码解释

  • redis_client.incr(key): 这是Redis命令,原子性的增加键的值。如果键不存在,则先创建并初始化为0,然后加1。
  • redis_client.expire(key, TIME_WINDOW): 这是设置键的过期时间。当键过期时,Redis会自动删除它。

注意事项

  • 原子性INCR命令是原子性的,这意味着即使在高并发的情况下,也能保证计数的准确性。
  • 过期时间:设置合理的过期时间非常重要。如果过期时间太短,访问频率限制的效果会大打折扣。如果过期时间太长,可能会占用过多的内存。
  • key的设计:key的设计要合理,避免key冲突。

更高级的实现方式

上面的代码只是一个简单的示例,实际应用中,可能需要更高级的实现方式,比如:

  • 使用Lua脚本:可以将多个Redis命令封装成一个Lua脚本,然后一次性执行,避免网络延迟,提高性能。
  • 使用令牌桶算法:令牌桶算法可以更平滑地限制访问频率,避免突发流量对服务器造成冲击。

第三章:黑白名单 (Blacklist and Whitelist)

除了访问频率限制,Redis还可以用来实现黑白名单。

黑名单,就是禁止某些IP地址访问你的网站。白名单,就是只允许某些IP地址访问你的网站。

实现黑白名单的思路也很简单:

  1. 将黑名单IP地址存储在一个Redis集合中。
  2. 将白名单IP地址存储在另一个Redis集合中。
  3. 每次收到请求时,先判断IP地址是否在白名单中。
    • 如果在白名单中,则允许访问。
  4. 如果不在白名单中,再判断IP地址是否在黑名单中。
    • 如果在黑名单中,则拒绝访问。
    • 如果不在黑名单中,则允许访问。

下面我们来看一段示例代码(Python):

import redis

# Redis连接信息
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0

# 黑白名单key
BLACKLIST_KEY = "blacklist"
WHITELIST_KEY = "whitelist"

# 连接Redis
redis_client = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB)

def is_allowed_by_blacklist_whitelist(ip_address):
    """
    根据黑白名单判断IP地址是否允许访问
    """
    if redis_client.sismember(WHITELIST_KEY, ip_address):
        return True  # 在白名单中,允许访问

    if redis_client.sismember(BLACKLIST_KEY, ip_address):
        return False  # 在黑名单中,拒绝访问

    return True  # 默认允许访问

# 示例用法
ip = "192.168.1.100"

# 先添加一些IP到黑白名单中
redis_client.sadd(WHITELIST_KEY, "192.168.1.101")
redis_client.sadd(BLACKLIST_KEY, "192.168.1.100")

if is_allowed_by_blacklist_whitelist(ip):
    print(f"IP {ip} 允许访问")
    # 处理请求
else:
    print(f"IP {ip} 被禁止访问")

这段代码的核心是is_allowed_by_blacklist_whitelist函数,它接收一个IP地址作为参数,然后:

  1. 判断是否在白名单中sismember(WHITELIST_KEY, ip_address),如果IP地址在白名单集合中,则返回True,否则返回False。
  2. 判断是否在黑名单中sismember(BLACKLIST_KEY, ip_address),如果IP地址在黑名单集合中,则返回True,否则返回False。

代码解释

  • redis_client.sismember(KEY, value): 这是一个Redis命令,用于检查集合(set)中是否存在指定的值。如果存在,返回True,否则返回False。
  • redis_client.sadd(KEY, value): 这是一个Redis命令,用于向集合(set)中添加一个或多个成员。

注意事项

  • 白名单优先:通常情况下,白名单的优先级高于黑名单。也就是说,如果一个IP地址既在白名单中,又在黑名单中,那么它仍然可以访问。
  • 黑白名单的维护:黑白名单的维护需要一个专门的模块来负责,可以从数据库中加载,也可以通过API动态更新。

更高级的实现方式

  • 使用IP地址段:可以存储IP地址段,而不仅仅是单个IP地址,这样可以更灵活地控制访问权限。
  • 结合地理位置信息:可以根据IP地址的地理位置信息,来判断是否允许访问。

第四章:实战演练 (Hands-on Practice)

光说不练假把式,接下来我们来做一个简单的实战演练。

假设我们有一个在线商店,我们想限制每个用户每天最多只能下单5次。

我们可以这样实现:

import redis
import time
import hashlib

# Redis连接信息
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0

# 订单限制参数
MAX_ORDERS_PER_DAY = 5
TIME_WINDOW = 24 * 60 * 60  # 24小时

# 连接Redis
redis_client = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB)

def can_place_order(user_id):
    """
    判断用户是否可以下单
    """
    today = time.strftime("%Y%m%d")  # 获取今天的日期
    key = f"order_limit:{user_id}:{today}"  # 生成唯一的key
    current_orders = redis_client.incr(key)  # 自增
    if current_orders == 1:  # 第一次下单时设置过期时间
        redis_client.expire(key, TIME_WINDOW)

    if current_orders > MAX_ORDERS_PER_DAY:
        return False  # 超过阈值,拒绝下单
    else:
        return True  # 允许下单

# 示例用法
user_id = "user123"
if can_place_order(user_id):
    print(f"用户 {user_id} 可以下单")
    # 处理订单
else:
    print(f"用户 {user_id} 今天下单次数已达上限")

这段代码的关键在于key的设计:order_limit:{user_id}:{today}

  • order_limit:表示这是一个订单限制相关的key。
  • {user_id}:表示用户ID。
  • {today}:表示今天的日期,格式为YYYYMMDD。

通过将用户ID和日期都包含在key中,我们可以实现每天对每个用户进行独立的订单限制。

代码解释

  • time.strftime("%Y%m%d"): 获取当前日期,并格式化为YYYYMMDD的字符串。
  • if current_orders == 1:: 只有在第一次下单时才设置过期时间,避免重复设置。

第五章:总结与展望 (Conclusion and Future)

好了,各位观众,今天的“Redis兵器谱”讲堂就到这里了。

我们学习了如何用Redis实现基于IP的访问频率限制和黑白名单。这些技术可以帮助我们有效地防御恶意攻击,保护网站安全。

当然,Redis的功能远不止这些。它还可以用来实现缓存、分布式锁、消息队列等等。只要你敢想,Redis就能帮你实现!

最后,送给大家一句话:技术是工具,关键在于如何使用。

希望大家能够灵活运用Redis,打造更加安全、高效、稳定的应用!

感谢大家的观看,我们下期再见! 👋👋👋

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注