我们的NGINX的IP封禁功能基于Redis实现。当只支持单IP封禁时,直接以IP作为KEY,调用”GET”命令,根据Value判断是否需要封禁该IP。若要支持网段封禁,需要取出所有的CIDR段,然后判断IP是否在CIDR范围内。随着CIDR越来越多,从Redis中取出的数据则越来越多,性能消耗越来越大。为了减少数据传输量,则可以将判断逻辑改由Redis来完成。
Redis本身支持Lua脚本的执行,可以由Lua来实现相应逻辑。不过Lua语言本身不支持位运算(5.2之后支持),需要第三方库支持。所以,我们直接通过修改Redis代码扩展Redis命令来实现该功能。
Redis中所有命令的相关信息存储在redisCommandTable结构中。我们在其中添加上我们自己的命令,如”setcidr”, “checkcidr”等。
1 | struct redisCommand redisCommandTable[] = { |
其中指定了参数个数,回调函数等相应信息,具体参考源码中该结构上方的注释。
当Redis收到某命令时,会调用指定的回调函数。比如,Redis收到”setcidr”命令时,会调用函数setcidrCommand。
我们来看该函数的实现:
1 | void setcidrCommand(redisClient *c) { |
我们直接使用用Redis自身的SET类型来存储CIDR。根据参数KEY查找相应的SET。若没有,则调用setTypeCreate来创建。我们使用RawString对象存储CIDR数据,根据参数依次调用createRawStringObject来创建RawString对象,存在SET中。
CIDR数据的结构为:
1 | typedef struct { |
当调用checkCIDR命令时,会调用checkcidrCommand函数。
1 | void checkcidrCommand(redisClient *c) { |
函数中首先根据KEY获取存储CIDR的SET,然后依次进行检查IP是否在CIDR段内,分别返回0或1。
对Redis进行扩展非常简单,这种方法可以实现很多需求。不过由于Redis为单线程,功能实现代码里不能阻塞,否则会影响Redis本身性能。