当前位置: 首页 > news >正文

go语言实现高性能自定义ip管理模块(ip黑名单)

ip黑名单设计

对于IPV4而言,理论上有256^4个,也就是约42亿个。我想了好久,也查了挺多资料,但是,确实没有通用现成的解决方案。

PS:以下方案的讨论,适用于对于IP管理不那么严苛的情况。当然也可以改进为严苛的方案,无非是加锁,加内存。

非常欢迎有更好方案的大佬指点一下,因为我想了好久,实在没有更好的思路了,比如有大型业务系统的ip访问管理经验的,分享一下心得。

考虑过的方案

  1. 只管理大陆的ip,对于其余ip直接不给通过
    当初我想过只包含国内的ip,也有渠道获取国内的ip列表,好像也有上亿。上亿的量不算大。可以用数组来存储。麻烦的是做映射和更新。所以放弃
  2. 用一维数组,结合位运算存储
    可以算一下,假设以int64的数组来存储,一个int64可以存储64个ip,256^4/64*8/1024/1024=512MB,也就是只需要这么多内存。这也是网上能查到资料给到的可考虑方案之一。
    但是,这种方案肯呢个做最基本的黑名单记录,某个位上标记为1,就表示它被拉黑了。为0就没被拉黑,但是,如果我需要记录ip访问的次数,封禁的时长这些呢?这个方案做不到。你说再用一个结构体来存储这些需要的信息?那不是得不偿失?
  3. redis来存储?不说内存占用多大,redis 是有网络开销的,性能就不达标了
  4. 对应IPv4 的四段关系,采用四层引用来关系来存储(当前方案)。存储结构如[256]*[256]*[256]*[4]int64

第4方案优缺点

存储IP的方法

例如我要存储ip127.0.0.1,那么,我就会存储成[127]*[0]*[0]*[4]int64,上,最后这个1存在哪里,就是用位运算去处理,这里不再细说

缺点
  1. 当ip存储量很大时,占用的空间多余直接一维数组的方式,因为指针和空值也是要占用空间的,而且不少
  2. 和第二种方案一样,同样无法记录更多的信息
优点
  1. 链式索引结构,查找性能和一维数组的形式无明显差异,性能非常好
  2. 当ip量不大时,我不需要像一维数组那样初始化就搞个那么大的数组,这个方案最初只需要初始化[256]个空值,占用空间4096字节(64位机器上)
  3. 公网上很多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。而不是一个结构体,因为这样可以节省很多空间。

简化的方案固然能表达的信息就少很多了:

  1. 指针索引为nil[256]int8所在位置上的值为0,表示该ip未记录
  2. [256]int8上记录的值大于0,表示该IP的访问次数。由于int8正数最大127,所以最多记录127次访问
  3. [256]int8上记录的值小于0,表示该IP被禁用时长,单位分钟。由于int8负数最小负128,且-128保留作为永封的标记,所以最多表示的封禁时长是127分钟。
  4. 如上所说-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而言&#xff0c;理论上有256^4个&#xff0c;也就是约42亿个。我想了好久&#xff0c;也查了挺多资料&#xff0c;但是&#xff0c;确实没有通用现成的解决方案。 PS&#xff1a;以下方案的讨论&#xff0c;适用于对于IP管理不那么严苛的情况。当然也可…...

检索增强生成架构详解【RAG】

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

高清动态壁纸软件Live Wallpaper Themes 4K mac中文版功能

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

Kafka配置SASL认证密码登录

​​​​​​1、修改config/server.properties&#xff0c;添加如下内容 listenersSASL_PLAINTEXT://内网ip:9092 advertised.listenersSASL_PLAINTEXT://外网ip:9092 security.inter.broker.protocolSASL_PLAINTEXT sasl.mechanism.inter.broker.protocolPLAIN sasl.enabled.…...

两年功能五年自动化测试面试经验分享

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

大数据基础设施搭建 - Kafka(with ZooKeeper)

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

[JVM] 京东一面~说一下Java 类加载过程

系统加载 Class 类型的文件主要三步&#xff1a;加载->连接->初始化。连接过程又可分为三步&#xff1a;验证->准备->解析。 通过全限定名来加载生成 class 对象到内存中&#xff0c;然后进行验证这个 class 文件&#xff0c;包括文件格式校验、元数据验证&#xf…...

2023 年 认证杯 小美赛 ABC题 国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在认证杯 小美赛前为大家提供了许多资料的内容呀&am…...

N-134基于java实现捕鱼达人游戏

开发工具eclipse,jdk1.8 文档截图&#xff1a; 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&#xff1a;4xCortex-A53 up to 2.0Ghz/4xCortex-A53 up to 1.5GhzGraphics&#xff1a;IMG GE8320 Up to 650MhzProcess&#xff1a;12nmMemory&#xff1a;1xLP3 9…...

仿ChatGPT对话前端页面(内含源码)

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

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系统 步骤① 点击下侧任务栏中的“启动台”&#xff0c;进入程序坞&#xff0c;点击"其他",选择“启动转换助理” 步骤② 点击“继续”&#xff0c;接着点击“恢复”&#xff0c;即可卸载Windows系统 2、安装Windows系统 …...

数据库的事务的基本特性,事务的隔离级别,事务隔离级别如何在java代码中使用,使用MySQL数据库演示不同隔离级别下的并发问题

文章目录 数据库的事务的基本特性事务的四大特性(ACID)4.1、原子性&#xff08;Atomicity&#xff09;4.2、一致性&#xff08;Consistency&#xff09;4.3、隔离性&#xff08;Isolation&#xff09;4.4、持久性&#xff08;Durability&#xff09; 事务的隔离级别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,到底是什么?

相信大家在初学进程时&#xff0c;对fork函数创建进程一定会有很多的困惑&#xff0c;比如&#xff1a; 1.fork做了什么事情?? 2.为什么fork函数会有两个返回值?3.为什么fork的两个返回值&#xff0c;会给父进程谅回子进程pid&#xff0c;给子进程返回0?4.fork之后:父子进…...

基于SpringBoot+vue的token验证

后端&#xff1a; 1&#xff0c;写一个验证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安装完成以后&#xff0c;配置了一个默认的存储空间&#xff0c; 这个只能配置一个目录&#xff0c;如果要使用多个磁盘目录&#xff0c;则需要配置磁盘组策略 查看当前的存储策略 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…...

别再手动改MTL了!一个Python脚本搞定ENVI打开Landsat8 Collection2 Level2数据

别再手动改MTL了&#xff01;一个Python脚本搞定ENVI打开Landsat8 Collection2 Level2数据 遥感数据处理中&#xff0c;最令人头疼的莫过于遇到格式兼容性问题。最近在USGS下载的Landsat8 Collection2 Level2数据就给我带来了这样的困扰——ENVI竟然无法直接读取其MTL元数据文件…...

从调光到伽马校正:手把手教你用ILI9341命令优化TFT屏幕显示效果(实战避坑)

从调光到伽马校正&#xff1a;手把手教你用ILI9341命令优化TFT屏幕显示效果&#xff08;实战避坑&#xff09; 在嵌入式开发中&#xff0c;TFT屏幕的显示效果往往直接影响用户体验。许多开发者在使用ILI9341驱动芯片时&#xff0c;虽然能够完成基础显示功能&#xff0c;却常常忽…...

嵌入式系统HLS技术:原理、优化与应用实践

1. 嵌入式系统高级综合技术概述高级综合&#xff08;High-Level Synthesis, HLS&#xff09;技术正在彻底改变传统硬件设计流程。作为连接软件算法与硬件实现的关键桥梁&#xff0c;HLS允许开发者使用C/C等高级语言描述功能&#xff0c;然后自动转换为可综合的RTL代码&#xff…...

STM32F030硬件SPI调试踩坑实录:为什么读写数据总是不对?

STM32F030硬件SPI调试实战&#xff1a;从异常波形到数据访问的深度解析 当你在STM32F030上调试硬件SPI时&#xff0c;是否遇到过这样的场景&#xff1a;所有配置看起来都正确&#xff0c;逻辑分析仪显示的时钟信号也正常&#xff0c;但读回来的数据就是不对&#xff1f;这不是个…...

在Petalinux里像操作内存一样控制FPGA逻辑:ZYNQ7020 AXI_EMC Linux驱动开发指南

在Petalinux中实现用户空间直接操控FPGA逻辑&#xff1a;ZYNQ7020 AXI_EMC开发实战 当我们需要在ZYNQ平台上实现PS与PL的高效交互时&#xff0c;传统的内核驱动开发模式往往会成为性能瓶颈。想象一下这样的场景&#xff1a;你的FPGA逻辑需要实时响应来自Linux应用层的控制信号&…...

告别sudo!手把手教你无root权限在Linux服务器上源码编译安装PostgreSQL 14

告别sudo&#xff01;手把手教你无root权限在Linux服务器上源码编译安装PostgreSQL 14 在共享开发环境或受限权限的服务器上&#xff0c;数据库部署常常面临权限壁垒。想象这样一个场景&#xff1a;你刚拿到实验室服务器的普通账号&#xff0c;急需搭建PostgreSQL进行数据分析&…...

如何快速掌握网盘直链下载助手:八大网盘下载加速终极教程

如何快速掌握网盘直链下载助手&#xff1a;八大网盘下载加速终极教程 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天…...

Unity场景道具写实

Unity场景道具SuburbNeighborhoodHousePack资源-CSDN下载...

告别Office依赖!用Qt和QXlsx 1.4.3独立读写Excel的保姆级教程

告别Office依赖&#xff01;用Qt和QXlsx 1.4.3独立读写Excel的保姆级教程 在跨平台应用开发中&#xff0c;处理Excel文件一直是个令人头疼的问题。传统方案依赖Office或WPS组件&#xff0c;不仅增加部署复杂度&#xff0c;在Linux服务器、嵌入式设备等环境中更是难以实现。本文…...

别再手动改仿真值了!用LabVIEW 2020 + mbslave实现Modbus TCP数据自动读写与监控

LabVIEW 2020与Modbus TCP自动化监控实战指南 在工业自动化测试领域&#xff0c;手动修改仿真参数的时代已经过去。想象一下这样的场景&#xff1a;凌晨三点的生产线突然出现异常&#xff0c;而你的系统能够自动捕捉数据变化、触发警报并记录完整的过程数据——这正是现代自动…...