go语言实现高性能自定义ip管理模块(ip黑名单)
ip黑名单设计
对于IPV4而言,理论上有256^4个,也就是约42亿个。我想了好久,也查了挺多资料,但是,确实没有通用现成的解决方案。
PS:以下方案的讨论,适用于对于IP管理不那么严苛的情况。当然也可以改进为严苛的方案,无非是加锁,加内存。
非常欢迎有更好方案的大佬指点一下,因为我想了好久,实在没有更好的思路了,比如有大型业务系统的ip访问管理经验的,分享一下心得。
考虑过的方案
- 只管理大陆的ip,对于其余ip直接不给通过
当初我想过只包含国内的ip,也有渠道获取国内的ip列表,好像也有上亿。上亿的量不算大。可以用数组来存储。麻烦的是做映射和更新。所以放弃 - 用一维数组,结合位运算存储
可以算一下,假设以int64的数组来存储,一个int64可以存储64个ip,256^4/64*8/1024/1024=512MB,也就是只需要这么多内存。这也是网上能查到资料给到的可考虑方案之一。
但是,这种方案肯呢个做最基本的黑名单记录,某个位上标记为1,就表示它被拉黑了。为0就没被拉黑,但是,如果我需要记录ip访问的次数,封禁的时长这些呢?这个方案做不到。你说再用一个结构体来存储这些需要的信息?那不是得不偿失? - redis来存储?不说内存占用多大,redis 是有网络开销的,性能就不达标了
- 对应IPv4 的四段关系,采用四层引用来关系来存储(当前方案)。存储结构如
[256]*[256]*[256]*[4]int64
,
第4方案优缺点
存储IP的方法
例如我要存储ip127.0.0.1
,那么,我就会存储成[127]*[0]*[0]*[4]int64
,上,最后这个1存在哪里,就是用位运算去处理,这里不再细说
缺点
- 当ip存储量很大时,占用的空间多余直接一维数组的方式,因为指针和空值也是要占用空间的,而且不少
- 和第二种方案一样,同样无法记录更多的信息
优点
- 链式索引结构,查找性能和一维数组的形式无明显差异,性能非常好
- 当ip量不大时,我不需要像一维数组那样初始化就搞个那么大的数组,这个方案最初只需要初始化[256]个空值,占用空间4096字节(64位机器上)
- 公网上很多ip段是不能被使用的,如果我们为这些IP段去做映射,可以节省空间,但是映射的规则非常复杂,增加代码的复杂度,降低可维护性。而采取这种方案,这些不可能出现的IP段也不会占用空间,所以根本无需考虑做映射。这也是比一维数组方案的一大优势。
改进
针对上述缺点,可以考虑,对于百万级别的ip,需要多大的业务量才达得到这个量级。我想全国每天能有百万个ip访问的系统,应该都是非常知名的了,所以,完全不用考虑这么多,所以,第一条缺点,不存在,反而证明了第二条优点。
我最开始想要设计这个ip管理模块是因为想为自己的网站防御ddos攻击,虽然我不太懂ddos攻击,但是看来拉不拉黑名单对ddos没啥用,他是阻塞带宽从而使正常的访问无法进入。
但是,我已经花了好多时间去向这个方案了。虽然对ddos没用,但是对系统稳定性有用嘛
如何才能记录更多的信息
我想要做的不仅仅是黑名单,而是ip管理,也就是可以记录IP是否访问,访问次数,什么时候访问,最近一次访问,是否被封禁,封禁时长,永久封禁,如何解封等
记录信息,那么就需要有这个一个结构体,搞一堆字段,最后的结构应该是[256]*[256]*[256]*ipManage{}
。这样固然好,记录什么信息都可以自由拓展。当然,你需要为他付出更多的内存空间。保存后面保存到数据库的空间。
所以,我没钱租那么大内存的服务器,所以我选择简化方案。
最终方案
最终我综合考虑自己的业务需求,设计了如何结构体:
type ipVisitS struct {IpList [256]*[256]*[256]*[256]int8limit int8 // 设置的每cycleSecond的访问上限cycleSecond int32 // 每次循环检查的间隔时长,单位秒visitLimitBanTime int8 // 访问超限的封禁时长,单位分钟
}
IpList
就是我拿来储存访问的IP信息的数组,我最后采用int8
。而不是一个结构体,因为这样可以节省很多空间。
简化的方案固然能表达的信息就少很多了:
- 指针索引为
nil
或[256]int8
所在位置上的值为0,表示该ip未记录 [256]int8
上记录的值大于0,表示该IP的访问次数。由于int8
正数最大127,所以最多记录127次访问[256]int8
上记录的值小于0,表示该IP被禁用时长,单位分钟。由于int8
负数最小负128,且-128
保留作为永封的标记,所以最多表示的封禁时长是127分钟。- 如上所说
-128
表达为永封,当然你可以根据实际需求把更多负数赋予特殊含义
是的,局限也很大,但是是我考虑实际得出的方案,不满足就可以换成int16
,甚至是结构体。思路是一样的。
另外解释下其他属性的含义:
limit :每个ip在单位时间内(也就是cycleSecond)的访问上限,虽然
int8
正数最大127,但是未必就一定要让他的上限等于127,支持自定义
cycleSecond:一个周期的时长,单位是秒,关系到limit 的记录周期,以及后面一个巡检的定时器周期,后面在介绍定时器
visitLimitBanTime:访问超过limit后的封禁时长,单位是分钟
代码实现
ip管理模块实现如下接口:
type IBlacklist2 interface {/**- @description: 将一个ip 字符串添加到名单里,单线程下添加一千万ip耗时约不到4秒- @param {string} ipStr 形如127.0.0.1- @return {*}0: 输入的ip格式错误(0,128): cycleSecond时间内访问次数(-128,0):封禁时长-128: 永封*/Add(ipStr string) int8/**- @description: 增加封禁时长,如果还未被封禁,则等于封禁时长,如果已被封禁,则增加banTime- @param {string} ipStr 封禁的IP地址- @param {int8} banTime 用负数表示,数值表示增加的时长,单位分钟,-128表示永封- @return {int8} 0: 输入的ip格式错误(0,128): cycleSecond时间内访问次数(-128,0):封禁时长-128: 永封*/AddBanTime(ipStr string, banTime int8) int8 // 给一个ip增加封禁时长/*** @description: 判断一个ip是否被封禁,如果被封禁,则返回封禁时长,否则返回访问次数* @param {string} ipStr* @return {int8} 第一个返回值的含义:0:表示该ip未记录;>0:表示该ip的访问次数;<0:表示该ip的封禁时长;-128:表示该ip永久封禁{bool} 第二个返回值的含义:false:输入的ip有误,true:输入的ip正确*/IsBan(ipStr string) (int8, bool) // 判断一个ip是否被禁用GetLen() int //获取黑名单列表长度GetAll() []string // 获取黑名单列表,升序排序GetSizeOf() uintptr // 获取黑名单列表占用的内存空间
}
额外实现两个方法:
/*** @description: 初始化* @param {int8} limit 设置的每cycleSecond的访问上限* @param {int32} cycleSecond 每次循环检查的间隔时长,单位秒* @param {int8} visitLimitBanTime 访问超限的封禁时长,单位分钟* @return {*ipVisitS} 对象* @return {error} 错误*/
func InitIpVisit(limit int8, cycleSecond int32, visitLimitBanTime int8,stopChan chan bool) (*ipVisitS, error) {if !(limit >= 0 && limit <= 127) {return nil, errors.New("limit的取值范围是 [0,127]")} else if !(cycleSecond >= 1 && cycleSecond <= 3600) {return nil, errors.New("cycleSecond的取值范围是 [1,3600]秒")} else if !(visitLimitBanTime >= 1 && visitLimitBanTime <= 127) {return nil, errors.New("visitLimitBanTime的取值范围是 [1,127]分钟")}ipVisit := &ipVisitS{limit: limit,cycleSecond: cycleSecond,visitLimitBanTime: visitLimitBanTime,}ipVisit.CheckBlackList(stopChan) // 启动定时器return ipVisit, nil
}
/*** @description: 定时任务,每cycleSecond循环检查一次,当取值范围为[-127,0)时,加1,表示封禁时长减一分钟。当取值范围为(0,127]时,减1,表示访问次数清零。且将不再记录ip段重置为空值,防止一直占用内存* @return {*} *time.Ticker的指针*/
func (ip *ipVisitS) CheckIPList() *time.Ticker {
······省略
功能就是每个周期巡检IP列表
1.封禁时长减1
2.访问次数清零,重新记录。是的,直接清零,这也是该方案的一个缺陷,因为无依据判断访问的时间,索性直接清零
3.清理历史记录的空间,释放内存
}
性能测试
测试代码:
ipStart := time.Now()ipList := test.CreateIp(1000000, 4) // 创建ip 一百万个,4个协程同时进行fmt.Println("创建ip耗时:", time.Since(ipStart))//初始化stopchan:=make(chan bool,1)onlyIp, _ := ipmanage.InitIpVisit(127, 60, 5,stopchan)startTime := time.Now()// 单线程运行添加ipfor i := 0; i < len(ipList); i++ {onlyIp.Add(ipList[i])}//模拟第二次访问for i := 0; i < 10000; i++ {onlyIp.Add(ipList[i])}// 模拟第三次访问for i := 0; i < 10000; i++ {onlyIp.Add(ipList[i])}onlyIp.AddBanTime("0.0.0.1", -5) // 模拟封禁5分钟onlyIp.AddBanTime("127.0.0.1", -128) // 模拟永封fmt.Println("记录ip耗时:", time.Since(startTime))// 等待十次巡检for i := 0; i < 10; i++ {sizeStart := time.Now()fmt.Println("ip队列占用内存(字节):", onlyIp.GetSizeOf())fmt.Println("计算ip队列占用内存耗时:", time.Since(sizeStart))allStart := time.Now()fmt.Println("ip队列长度:", onlyIp.GetLen())fmt.Println("计算ip队列长度耗时:", time.Since(allStart))time.Sleep(time.Minute)}
创建ip耗时: 65.4136ms
记录ip耗时: 404.0073ms
ip队列占用内存(字节): 390746112
计算ip队列占用内存耗时: 14.0027ms
ip队列长度: 1000002
计算ip队列长度耗时: 369.1569ms
可以看出,单线程应对百万IP,也仅需300多毫秒,我觉很OK了,当然,我没有加锁。追求极致性能。因为我的业务量下, 为了那极低概率会出现的线程不安全问题,加锁简直就是浪费,对于那偶尔的计算出错,无所谓。
完整代码:https://gitee.com/lsjWeiYi/ip-manage
相关文章:
go语言实现高性能自定义ip管理模块(ip黑名单)
ip黑名单设计 对于IPV4而言,理论上有256^4个,也就是约42亿个。我想了好久,也查了挺多资料,但是,确实没有通用现成的解决方案。 PS:以下方案的讨论,适用于对于IP管理不那么严苛的情况。当然也可…...

检索增强生成架构详解【RAG】
生成式AI技术很强大,但它们受到知识的限制。 虽然像 ChatGPT 这样的LLM可以执行许多任务,但每个LLM的基线知识都存在基于其训练数据的差距。 如果你要求LLM写一些关于最近趋势或事件的文章,LLM不会知道你在说什么,而且回答最好是混…...

高清动态壁纸软件Live Wallpaper Themes 4K mac中文版功能
Live Wallpaper & Themes 4K mac是一款提供各种高清动态壁纸和主题的应用程序。该应用程序提供了大量的动态壁纸和主题,包括自然、动物、城市、抽象等各种类别,可以满足用户不同的需求。除了壁纸和主题之外,该应用程序还提供了许多其他功…...

Kafka配置SASL认证密码登录
1、修改config/server.properties,添加如下内容 listenersSASL_PLAINTEXT://内网ip:9092 advertised.listenersSASL_PLAINTEXT://外网ip:9092 security.inter.broker.protocolSASL_PLAINTEXT sasl.mechanism.inter.broker.protocolPLAIN sasl.enabled.…...

两年功能五年自动化测试面试经验分享
最近有机会做一些面试工作,主要负责面试软件测试人员招聘的技术面试。 之前一直是应聘者的角色,经历了不少次的面试之后,多少也积累一点面试的经验,现在发生了角色转变。初次的面试就碰到个工作年限比我长的,也没有时…...

大数据基础设施搭建 - Kafka(with ZooKeeper)
文章目录 一、简介二、单机部署2.1 上传压缩包2.2 解压压缩包2.3 修改配置文件(1)配置zookeeper地址(2)修改kafka运行日志(数据)存储路径 2.4 配置环境变量2.5 启动/关闭2.6 测试(1)查看当前服务器中的所有…...

[JVM] 京东一面~说一下Java 类加载过程
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。 通过全限定名来加载生成 class 对象到内存中,然后进行验证这个 class 文件,包括文件格式校验、元数据验证…...

2023 年 认证杯 小美赛 ABC题 国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析
当大家面临着复杂的数学建模问题时,你是否曾经感到茫然无措?作为2022年美国大学生数学建模比赛的O奖得主,我为大家提供了一套优秀的解题思路,让你轻松应对各种难题。 cs数模团队在认证杯 小美赛前为大家提供了许多资料的内容呀&am…...

N-134基于java实现捕鱼达人游戏
开发工具eclipse,jdk1.8 文档截图: package com.qd.fish;import java.awt.Graphics; import java.io.File; import java.util.ArrayList; import java.util.List;import javax.imageio.ImageIO;public class Fishes {//定义一个集合来管理鱼List<Fish> fish…...

MTK联发科MT6762/MT6763/MT6765安卓核心板参数规格比较
MT6762安卓核心板 MTK6762安卓核心板是一款工业级高性能、可运行 android9.0 操作系统的 4G智能模块。 CPU:4xCortex-A53 up to 2.0Ghz/4xCortex-A53 up to 1.5GhzGraphics:IMG GE8320 Up to 650MhzProcess:12nmMemory:1xLP3 9…...

仿ChatGPT对话前端页面(内含源码)
仿ChatGPT对话前端页面(内含源码) 前言布局样式和Js部分关键点全部源码 前言 本文主要讲解如何做出类似ChatGPT的前端页面。具体我们的效果图是长这样,其中除了时间是动态的之外,其他都是假数据。接下来让我们从布局和样式的角度…...

js粒子效果(一)
效果: 代码: <!doctype html> <html> <head><meta charset"utf-8"><title>HTML5鼠标经过粒子散开动画特效</title><style>html, body {position: absolute;overflow: hidden;margin: 0;padding: 0;width: 100%;height: 1…...
程序员必备工具篇 / 程序员必备基础:Git
前言 掌握 Git 命令是每位程序员必备的基础,之前一直是用 smartGit 工具,直到看到大佬们都是在用 Git 命令操作的,回想一下,发现有些 Git 命令我都忘记了,于是写了这篇博文,复习一下~ https://github.com/whx123/JavaHome 公众号:顺哥轻创 文章目录 Git 是什么?Git …...
MacBook使用指南
一、安装及卸载Windows系统 1、卸载Windows系统 步骤① 点击下侧任务栏中的“启动台”,进入程序坞,点击"其他",选择“启动转换助理” 步骤② 点击“继续”,接着点击“恢复”,即可卸载Windows系统 2、安装Windows系统 …...

数据库的事务的基本特性,事务的隔离级别,事务隔离级别如何在java代码中使用,使用MySQL数据库演示不同隔离级别下的并发问题
文章目录 数据库的事务的基本特性事务的四大特性(ACID)4.1、原子性(Atomicity)4.2、一致性(Consistency)4.3、隔离性(Isolation)4.4、持久性(Durability) 事务的隔离级别5.1、事务不…...

Robust taboo search for the quadratic assignment problem-二次分配问题的鲁棒禁忌搜索
文章目录 摘要关键字结论研究背景1. Introduction 常用基础理论知识2. The quadratic assignment problem3. Taboo search3.1. Moves3.2 Taboo list3.3. Aspiration function3.4. Taboo list size4. Random problems5. Parallel taboo search 研究内容、成果7. Conclusion 潜在…...

Linux:创建进程 -- fork,到底是什么?
相信大家在初学进程时,对fork函数创建进程一定会有很多的困惑,比如: 1.fork做了什么事情?? 2.为什么fork函数会有两个返回值?3.为什么fork的两个返回值,会给父进程谅回子进程pid,给子进程返回0?4.fork之后:父子进…...
基于SpringBoot+vue的token验证
后端: 1,写一个验证token的拦截器 import com.fasterxml.jackson.databind.ObjectMapper; import com.ffyc.news.model.CommonData; import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest; impor…...
Clickhouse设置多磁盘存储策略
设置多磁盘存储 clickhouse安装完成以后,配置了一个默认的存储空间, 这个只能配置一个目录,如果要使用多个磁盘目录,则需要配置磁盘组策略 查看当前的存储策略 select name, path, formatReadableSize(free_space) as free, fo…...

Python开发运维:Django 4.2.7 使用Celery 5.3.5 完成异步和定时任务
目录 一、实验 1.Django使用Celery完成异步和定时任务 二、问题 1. 如何查看Django版本 一、实验 1.Django使用Celery完成异步和定时任务 (1)安装Django (2)新建Django项目 (3)初始框架 (4)urls.py引用视图views from django.contrib import admin from django.urls imp…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
零基础在实践中学习网络安全-皮卡丘靶场(第十一期-目录遍历模块)
经过前面几期的内容我们学习了很多网络安全的知识,而这期内容就涉及到了前面的第六期-RCE模块,第七期-File inclusion模块,第八期-Unsafe Filedownload模块。 什么是"遍历"呢:对学过一些开发语言的朋友来说应该知道&…...

RFID推动新能源汽车零部件生产系统管理应用案例
RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域,电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式,存在单点位单独头溯源、网关布线…...
【Java基础】向上转型(Upcasting)和向下转型(Downcasting)
在面向对象编程中,转型(Casting) 是指改变对象的引用类型,主要涉及 继承关系 和 多态。 向上转型(Upcasting) ⬆️ 定义 将 子类对象 赋值给 父类引用(自动完成,无需强制转换&…...