首页
搜索
1
【开发】NekoIPinfo:基于GO语言的高性能IP查询服务端
1 阅读
2
招聘PHP开发工程师哪个平台更专业?
1 阅读
3
平安融易以“AI in all”大语言模型+智能企微中台重塑金融服务体验,入选为“2026金融消保与服务创新年度案例”
0 阅读
4
易语言工具条减法使用技巧
0 阅读
5
蒋易孙天宇语言天赋藏不住了
0 阅读
技术分享
源码分类
工具分享
采集专区
登录
搜索
私人云
累计撰写
44
篇文章
累计收到
0
条评论
首页
栏目
技术分享
源码分类
工具分享
采集专区
页面
搜索到
1
篇与
的结果
2026-03-06
【开发】NekoIPinfo:基于GO语言的高性能IP查询服务端
高性能IP查询服务端开发日志 之前使用IP信息查询的服务也不少了,观察了不少的接口,大部分返回的数据都是json格式的数据,于是我就在想,我能不能也自己开发一个类似的查询工具玩一下?说干就干,在想清楚了软件的架构设计后,我决定开工。{lamp/}架构设计与编程语言选型 在架构设计方面,我打算进行前后端彻底解耦,前端为静态网页,后端为纯API程序。对于前端,我计划利用JS来从API获取信息并更新页面对于后端,就做最纯粹的API服务,根据查询请求响应JSON字符串在亲眼见证过PHP,Python这类动态解释型语言的效率噩梦后,我打算使用GO语言进行开发,理由很简单:GO是编译型语言,运行效率比解释型语言要高得多。GO天生就适合开发后端,抗并发能力强。相比于C语言,GO语言要更适合上手。{lamp/}构建前端 不得不说,就目前的情况,Gemini仍然是前端之神,我直接让Gemini根据我的想法设计了一套网页,还是相当不错的:<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>猫娘IP地址查询</title> <!-- 引入 Tailwind CSS 进行快速样式构建 --> <script src="https://cdn.tailwindcss.com"></script> <style> /* 自定义可爱波点纹理背景 */ body { background-color: #fff0f5; /* 极淡的粉色底色 */ background-image: radial-gradient(#ffb6c1 15%, transparent 16%), radial-gradient(#ffb6c1 15%, transparent 16%); background-size: 40px 40px; background-position: 0 0, 20px 20px; font-family: 'Nunito', 'PingFang SC', 'Microsoft YaHei', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem; } /* 隐藏滚动条但保留滚动功能(适配部分长列表情况) */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #fff0f5; } ::-webkit-scrollbar-thumb { background: #ffb6c1; border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: #ff8da1; } /* 动画效果 */ @keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-10px); } 100% { transform: translateY(0px); } } .float-animation { animation: float 3s ease-in-out infinite; } </style> </head> <body class="text-gray-700 w-full flex flex-col items-center"> <!-- 全局包裹容器,控制最大宽度 --> <div class="w-full max-w-2xl relative pt-8 md:pt-16 px-4 pb-12"> <!-- 装饰性猫耳 (直接悬浮在最外层) --> <div class="absolute top-4 left-4 md:left-0 text-5xl float-animation z-10" style="animation-delay: 0s;">🐱</div> <div class="absolute top-4 right-4 md:right-0 text-5xl float-animation z-10" style="animation-delay: 1.5s;">🐾</div> <!-- 头部:标题与当前IP (直接放在背景上) --> <header class="text-center mb-8 mt-2 relative z-10"> <h1 class="text-4xl md:text-5xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 to-rose-400 tracking-wide drop-shadow-sm mb-4"> 猫娘IP地址查询 </h1> <p class="text-pink-600 font-medium text-lg bg-white/70 backdrop-blur-sm inline-block px-6 py-2 rounded-full border border-pink-200 shadow-sm"> 您的 IP 地址为 <span id="current-ip" class="font-bold text-pink-700 ml-1">正在获取喵...</span> </p> </header> <!-- 主体结构 --> <main> <!-- 搜索输入区域 (直接放在背景上) --> <div class="flex flex-col md:flex-row items-center justify-center mb-10 w-full group relative z-10"> <div class="relative w-full flex shadow-sm rounded-full"> <span class="absolute left-6 top-1/2 transform -translate-y-1/2 text-pink-400 text-xl">🔍</span> <input type="text" id="ip-input" placeholder="请输入要查询的 IP 地址喵~" class="w-full bg-white/90 backdrop-blur-sm border-2 border-pink-200 text-pink-700 placeholder-pink-300 px-14 py-4 rounded-full md:rounded-r-none focus:outline-none focus:border-pink-400 focus:bg-white transition-all duration-300" > </div> <button id="search-btn" class="w-full md:w-auto mt-4 md:mt-0 bg-gradient-to-r from-pink-400 to-rose-400 hover:from-pink-500 hover:to-rose-500 text-white font-bold text-lg px-8 py-4 rounded-full md:rounded-l-none md:rounded-r-full shadow-md hover:shadow-pink-300/50 transform hover:-translate-y-0.5 transition-all duration-300 whitespace-nowrap active:scale-95" > 查询喵 🐾 </button> </div> <!-- 结果列表卡片 (独立卡片,竖向列表) --> <div class="bg-white/90 backdrop-blur-sm border-4 border-pink-200 rounded-[2.5rem] p-6 md:p-10 w-full shadow-[0_10px_40px_rgba(255,182,193,0.5)]"> <div id="results-container" class="opacity-100 transition-opacity duration-500"> <ul class="flex flex-col divide-y-2 divide-pink-100/60"> <!-- 列表项 1: 国家 --> <li class="flex items-center justify-between py-4 px-2 hover:bg-pink-50 rounded-2xl transition-colors group"> <div class="flex items-center gap-4"> <span class="text-2xl group-hover:scale-110 transition-transform bg-pink-100/50 p-2 rounded-full">🌍</span> <span class="text-pink-500 font-bold tracking-wider">国家</span> </div> <span id="res-country" class="text-gray-700 font-semibold text-lg">-</span> </li> <!-- 列表项 2: 省份 --> <li class="flex items-center justify-between py-4 px-2 hover:bg-pink-50 rounded-2xl transition-colors group"> <div class="flex items-center gap-4"> <span class="text-2xl group-hover:scale-110 transition-transform bg-pink-100/50 p-2 rounded-full">🗺️</span> <span class="text-pink-500 font-bold tracking-wider">省份</span> </div> <span id="res-province" class="text-gray-700 font-semibold text-lg">-</span> </li> <!-- 列表项 3: 城市 --> <li class="flex items-center justify-between py-4 px-2 hover:bg-pink-50 rounded-2xl transition-colors group"> <div class="flex items-center gap-4"> <span class="text-2xl group-hover:scale-110 transition-transform bg-pink-100/50 p-2 rounded-full">🏙️</span> <span class="text-pink-500 font-bold tracking-wider">城市</span> </div> <span id="res-city" class="text-gray-700 font-semibold text-lg">-</span> </li> <!-- 列表项 4: ISP --> <li class="flex items-center justify-between py-4 px-2 hover:bg-pink-50 rounded-2xl transition-colors group"> <div class="flex items-center gap-4"> <span class="text-2xl group-hover:scale-110 transition-transform bg-pink-100/50 p-2 rounded-full">🏢</span> <span class="text-pink-500 font-bold tracking-wider">ISP 运营商</span> </div> <span id="res-isp" class="text-gray-700 font-semibold text-lg">-</span> </li> <!-- 列表项 5: 经纬度 --> <li class="flex items-center justify-between py-4 px-2 hover:bg-pink-50 rounded-2xl transition-colors group"> <div class="flex items-center gap-4"> <span class="text-2xl group-hover:scale-110 transition-transform bg-pink-100/50 p-2 rounded-full">🧭</span> <span class="text-pink-500 font-bold tracking-wider">经纬度</span> </div> <span id="res-latlon" class="text-gray-700 font-semibold text-lg">-</span> </li> </ul> </div> <!-- API 提示按钮 --> <div class="mt-6 flex justify-end relative z-10 border-t-2 border-dashed border-pink-100/60 pt-4"> <button id="api-info-btn" class="text-sm text-pink-400 hover:text-pink-600 font-medium flex items-center gap-1 transition-colors group"> <span class="group-hover:animate-bounce">💡</span> 开发者 API 说明 </button> </div> </div> </main> </div> <!-- API 说明弹窗 (Modal) --> <div id="api-modal" class="fixed inset-0 z-50 hidden flex items-center justify-center p-4"> <!-- 黑色半透明背景遮罩 --> <div id="api-modal-overlay" class="absolute inset-0 bg-black/30 backdrop-blur-sm opacity-0 transition-opacity duration-300"></div> <!-- 弹窗主体 --> <div id="api-modal-content" class="bg-white border-4 border-pink-200 rounded-[2rem] p-6 md:p-8 max-w-lg w-full shadow-2xl relative z-10 opacity-0 scale-95 transition-all duration-300"> <div class="flex justify-between items-center mb-6"> <h2 class="text-2xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-rose-400"> 🛠️ API 调用说明 </h2> <button id="close-modal-top" class="text-pink-300 hover:text-pink-500 text-3xl transition-colors leading-none">×</button> </div> <div class="text-gray-600 text-sm space-y-4"> <p>您可以通过发送 <code class="bg-pink-50 text-pink-600 px-1 py-0.5 rounded font-bold">GET</code> 请求到我们的接口来获取 IP 详细信息喵:</p> <div class="bg-pink-50 p-4 rounded-xl border border-pink-100 font-mono text-xs overflow-x-auto text-pink-700 shadow-inner"> GET https://api.maoniang.example/v1/ip?address=<span class="text-rose-500">{ip}</span> </div> <p class="font-bold text-gray-700 mt-4">📦 返回数据格式示例 (JSON):</p> <pre class="bg-slate-800 text-pink-200 p-4 rounded-xl text-xs overflow-x-auto font-mono shadow-inner leading-relaxed"> { "code": 200, "msg": "success", "data": { "country": "美国", "province": "加利福尼亚州", "city": "圣克拉拉", "isp": "阿里云", "latitude": "37.355701", "longitude": "-121.955002" } }</pre> </div> <div class="mt-8 text-center"> <button id="close-modal-bottom" class="bg-pink-100 hover:bg-pink-200 text-pink-600 font-bold py-2.5 px-8 rounded-full transition-colors active:scale-95 shadow-sm"> 我知道了喵 🐾 </button> </div> </div> </div> <!-- 交互逻辑 --> <script> document.addEventListener('DOMContentLoaded', () => { const currentIpSpan = document.getElementById('current-ip'); const searchBtn = document.getElementById('search-btn'); const ipInput = document.getElementById('ip-input'); // 结果节点 const resCountry = document.getElementById('res-country'); const resProvince = document.getElementById('res-province'); const resCity = document.getElementById('res-city'); const resIsp = document.getElementById('res-isp'); const resLatlon = document.getElementById('res-latlon'); // 弹窗相关节点 const apiModal = document.getElementById('api-modal'); const apiModalOverlay = document.getElementById('api-modal-overlay'); const apiModalContent = document.getElementById('api-modal-content'); const apiInfoBtn = document.getElementById('api-info-btn'); const closeModalTop = document.getElementById('close-modal-top'); const closeModalBottom = document.getElementById('close-modal-bottom'); // 1. 获取用户当前IP (使用免费的 ipify API) fetch('https://api.ipify.org?format=json') .then(response => response.json()) .then(data => { currentIpSpan.textContent = data.ip; }) .catch(error => { currentIpSpan.textContent = "获取失败喵 T_T"; console.error('获取IP失败:', error); }); // ================= 弹窗控制逻辑 ================= const openModal = () => { apiModal.classList.remove('hidden'); // 强制重绘以触发动画 void apiModal.offsetWidth; apiModalOverlay.classList.remove('opacity-0'); apiModalOverlay.classList.add('opacity-100'); apiModalContent.classList.remove('opacity-0', 'scale-95'); apiModalContent.classList.add('opacity-100', 'scale-100'); }; const closeModal = () => { apiModalOverlay.classList.remove('opacity-100'); apiModalOverlay.classList.add('opacity-0'); apiModalContent.classList.remove('opacity-100', 'scale-100'); apiModalContent.classList.add('opacity-0', 'scale-95'); // 等待动画结束后隐藏元素 setTimeout(() => { apiModal.classList.add('hidden'); }, 300); // 对应 duration-300 }; apiInfoBtn.addEventListener('click', openModal); closeModalTop.addEventListener('click', closeModal); closeModalBottom.addEventListener('click', closeModal); apiModalOverlay.addEventListener('click', closeModal); // 点击遮罩层也可关闭 // 2. 点击查询按钮的逻辑 searchBtn.addEventListener('click', () => { const ipToSearch = ipInput.value.trim(); if (!ipToSearch) { alert('请输入要查询的 IP 地址喵!'); return; } // 添加加载动画效果 searchBtn.innerHTML = '查询中... 🐾'; searchBtn.classList.add('opacity-80', 'cursor-not-allowed'); setTimeout(() => { // 适配你提供的 JSON 格式模拟返回数据 const mockData = { "country": "美国", "province": "加利福尼亚州", "city": "圣克拉拉", "isp": "阿里云", "latitude": "37.355701", "longitude": "-121.955002" }; // 更新 DOM 数据 resCountry.textContent = mockData.country; resProvince.textContent = mockData.province; resCity.textContent = mockData.city; resIsp.textContent = mockData.isp; resLatlon.textContent = `${mockData.latitude}, ${mockData.longitude}`; // 恢复按钮状态 searchBtn.innerHTML = '查询喵 🐾'; searchBtn.classList.remove('opacity-80', 'cursor-not-allowed'); // 小彩蛋互动 ipInput.value = ''; ipInput.placeholder = '查询成功了喵!继续输入吧~'; }, 600); // 模拟网络延迟 }); // 支持回车查询 ipInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { searchBtn.click(); } }); }); </script> </body> </html>但是仔细观察,你会发现使用了实时构建的 Tailwind CSS ,其实这样做对于一个静态页面不太友好,于是我让他按照 Tailwind CSS 的样式,原生帮我写了一版样式。因为处理后代码变多了,我就不在这里放出来了。最后静态文件就三个:[chocola@Neko-X99 static]$ tree . ├── favicon.png ├── index.html └── main.css 1 directory, 3 files后端设计因为项目的定位是轻量,所以我不打算依赖外部的数据库。再加上IP查询这个业务场景几乎全是读操作,所以使用SQLite是没问题的,读取这方面没有瓶颈。为了实现高效的 IP 查询,数据库表结构的设计至关重要。核心思路是将文本格式的 IP 地址转换为无符号 32 位整数(uint32),这样不仅运算效率高,也便于进行范围比较。数据库表(例如 ip_info)主要包含三个字段:network_start (INTEGER): 存储该 IP 段的起始地址,以 uint32 格式保存。这是查询的核心,我们会根据这个字段建立索引(CREATE INDEX idx_network_start ON ip_info (network_start);),以确保查询性能。network_end (INTEGER): 存储该 IP 段的结束地址,同样以 uint32 格式保存。用于在查找到候选记录后,进行二次验证,确保目标 IP 确实落在该网段内。ip_info_json (TEXT): 将该 IP 段对应的地理位置、运营商等详细信息,序列化成 JSON 字符串后存储在此字段。这种方案灵活度高,可以方便地增减返回的字段,而无需修改表结构。查询时,我们首先将待查询的 IP 转换为整数,然后利用 WHERE network_start <= ? ORDER BY network_start DESC LIMIT 1 这样的 SQL 语句,借助索引快速定位到最接近且小于等于目标 IP 的那条记录。接着,再用 network_end 字段验证目标 IP 是否在该记录的范围内,从而得出最终结果。这种“先定位,后校验”的方式,配合索引,能够将查询耗时稳定在微秒级别。测试环境:CPU:i5-2410M内存:4GB DDR3 1600MHz硬盘:希捷 320G SATA2 机械硬盘Nginx:模拟真实环境,开启SSL进行反代第一版代码package main import ( "database/sql" "encoding/json" "flag" "fmt" "log" "net" "net/http" "strings" _ "github.com/mattn/go-sqlite3" ) var db *sql.DB // 定义完全匹配前端需求的数据结构 type IPInfo struct { IP string `json:"ip"` Country string `json:"country"` Province string `json:"province"` City string `json:"city"` ISP string `json:"isp"` Latitude string `json:"latitude"` Longitude string `json:"longitude"` } // 定义标准的 API 响应格式 type APIResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` // 用 interface{} 以便在出错时返回 nil } // 辅助函数:获取客户端真实 IP func getClientIP(r *http.Request) string { ip := r.Header.Get("X-Real-IP") if ip == "" { ip = r.Header.Get("X-Forwarded-For") } if ip == "" { ip, _, _ = net.SplitHostPort(r.RemoteAddr) } // X-Forwarded-For 可能是逗号分隔的多个IP,取第一个真实的 ip = strings.Split(ip, ",")[0] // 清理可能包含的空格,提升健壮性 return strings.TrimSpace(ip) } func apiHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // 提取查询参数并去除两端空白字符 queryIPStr := strings.TrimSpace(r.URL.Query().Get("ip")) targetIP := queryIPStr if targetIP == "" { targetIP = getClientIP(r) } // 【安全防线 1】:严格限制为合法的 IPv4 格式。任何注入代码都会在这里被直接拦截。 parsedIP := net.ParseIP(targetIP) if parsedIP == nil || parsedIP.To4() == nil { json.NewEncoder(w).Encode(APIResponse{Code: 400, Msg: "非法的 IPv4 地址喵!", Data: nil}) return } // 【安全防线 2】:转为 uint32 整型。彻底杜绝字符型注入。 ipInt := uint32(parsedIP.To4()[0])<<24 | uint32(parsedIP.To4()[1])<<16 | uint32(parsedIP.To4()[2])<<8 | uint32(parsedIP.To4()[3]) var infoJSON string var networkEnd uint32 // 🌟 记得提前声明这个变量喵 // 【性能优化核心】:利用索引极速向下查找最近的一条记录 err := db.QueryRow(` SELECT network_end, ip_info_json FROM ip_info WHERE network_start <= ? ORDER BY network_start DESC LIMIT 1`, ipInt).Scan(&networkEnd, &infoJSON) if err != nil { if err == sql.ErrNoRows { json.NewEncoder(w).Encode(APIResponse{Code: 404, Msg: "数据库里没有找到这个 IP 喵~", Data: nil}) } else { // 避免将底层的数据库错误直接暴露给前端(防止信息泄露) log.Printf("数据库查询错误: %v\n", err) json.NewEncoder(w).Encode(APIResponse{Code: 500, Msg: "数据库查询出错了喵", Data: nil}) } return } // 【关键逻辑校验】:虽然找到了最近的起始 IP,但必须确认目标 IP 是否在这个网段的覆盖范围内 if ipInt > networkEnd { json.NewEncoder(w).Encode(APIResponse{Code: 404, Msg: "数据库里没有找到这个 IP 喵~", Data: nil}) return } var rawData map[string]string if err := json.Unmarshal([]byte(infoJSON), &rawData); err != nil { log.Printf("JSON 解析错误: %v\n", err) json.NewEncoder(w).Encode(APIResponse{Code: 500, Msg: "数据解析失败喵", Data: nil}) return } result := IPInfo{ IP: targetIP, Country: rawData["country"], Province: rawData["province"], City: rawData["city"], ISP: rawData["isp"], Latitude: rawData["latitude"], Longitude: rawData["longitude"], } json.NewEncoder(w).Encode(APIResponse{ Code: 200, Msg: "success", Data: result, }) } func main() { // ================= 定义启动参数 ================= // flag.String("参数名", "默认值", "说明文字") dbPath := flag.String("db", "ip_info.db", "SQLite 数据库文件路径") port := flag.String("port", "8080", "API 服务监听端口") // 解析命令行参数 flag.Parse() var err error // 使用参数指定的数据库路径 db, err = sql.Open("sqlite3", *dbPath) if err != nil { log.Fatal("无法打开数据库: ", err) } defer db.Close() http.HandleFunc("/ipinfo", apiHandler) // 拼接监听地址 addr := fmt.Sprintf(":%s", *port) fmt.Printf("猫娘纯净 API 服务启动于 %s 喵... 🐾\n", addr) fmt.Printf("当前使用的数据库文件: %s\n", *dbPath) log.Fatal(http.ListenAndServe(addr, nil)) } content_copy测试下来直接就踩了一个大坑:因为没有设置索引,数据库查询效率非常低下,以至于到了难以置信的慢。通过这个命令:CREATE INDEX idx_network_start ON ip_info (network_start); content_copy建立好索引后,查询速度直接起飞。从原来每秒只能处理10个请求飙升至每秒可以处理5000+个请求,性能直接翻了500倍!太夸张了!第二版代码虽然第一版代码的性能已经可以做到很优秀,但是我还是希望尝试进一步优化,那就是——数据库存入内存!而而且我不但要存入内存,还要将其转化为Go原生的数据存储格式,进一步优化性能。package main import ( "database/sql" "encoding/json" "flag" "fmt" "log" "net" "net/http" "sort" "strings" "time" _ "github.com/mattn/go-sqlite3" ) var db *sql.DB // --- 全局配置与缓存 --- var ( ipCache []IPRule // 全量内存切片 useMemoryCache bool // 内存模式开关 enableDetailLog bool // 详细日志开关 ) type IPRule struct { NetworkStart uint32 NetworkEnd uint32 Info IPInfo } type IPInfo struct { IP string `json:"ip"` Country string `json:"country"` Province string `json:"province"` City string `json:"city"` ISP string `json:"isp"` Latitude string `json:"latitude"` Longitude string `json:"longitude"` } type APIResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } // 辅助函数:获取客户端真实 IP func getClientIP(r *http.Request) string { ip := r.Header.Get("X-Real-IP") if ip == "" { ip = r.Header.Get("X-Forwarded-For") } if ip == "" { ip, _, _ = net.SplitHostPort(r.RemoteAddr) } ip = strings.Split(ip, ",")[0] return strings.TrimSpace(ip) } // 统一处理响应和日志打印 func sendJSONResponse(w http.ResponseWriter, clientIP, targetIP string, resp APIResponse) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Access-Control-Allow-Origin", "*") // 将结构体序列化为 JSON 字节 respBytes, err := json.Marshal(resp) if err != nil { log.Printf("响应序列化失败: %v", err) http.Error(w, `{"code":500,"msg":"系统内部错误","data":null}`, http.StatusInternalServerError) return } // 如果开启了日志,就在这里统一输出 if enableDetailLog { log.Printf("[访问日志] 来源IP: %-15s | 查询IP: %-15s | 结果: %s", clientIP, targetIP, string(respBytes)) } // 写入响应 w.Write(respBytes) } func loadDataToMemory() error { log.Println("正在将数据库载入内存,请稍候喵...") startTime := time.Now() rows, err := db.Query(`SELECT network_start, network_end, ip_info_json FROM ip_info ORDER BY network_start ASC`) if err != nil { return err } defer rows.Close() for rows.Next() { var start, end uint32 var infoJSON string if err := rows.Scan(&start, &end, &infoJSON); err != nil { return err } var rawData map[string]string if err := json.Unmarshal([]byte(infoJSON), &rawData); err != nil { continue } info := IPInfo{ Country: rawData["country"], Province: rawData["province"], City: rawData["city"], ISP: rawData["isp"], Latitude: rawData["latitude"], Longitude: rawData["longitude"], } ipCache = append(ipCache, IPRule{ NetworkStart: start, NetworkEnd: end, Info: info, }) } log.Printf("载入完成!共加载了 %d 条规则,耗时 %v 喵!", len(ipCache), time.Since(startTime)) return nil } func apiHandler(w http.ResponseWriter, r *http.Request) { clientIP := getClientIP(r) // 提取实际访问者的 IP,用于日志记录 queryIPStr := strings.TrimSpace(r.URL.Query().Get("ip")) targetIP := queryIPStr if targetIP == "" { targetIP = clientIP } parsedIP := net.ParseIP(targetIP) if parsedIP == nil || parsedIP.To4() == nil { sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 400, Msg: "非法的 IPv4 地址喵!", Data: nil}) return } ipInt := uint32(parsedIP.To4()[0])<<24 | uint32(parsedIP.To4()[1])<<16 | uint32(parsedIP.To4()[2])<<8 | uint32(parsedIP.To4()[3]) if useMemoryCache { idx := sort.Search(len(ipCache), func(i int) bool { return ipCache[i].NetworkStart > ipInt }) if idx > 0 { rule := ipCache[idx-1] if ipInt <= rule.NetworkEnd { result := rule.Info result.IP = targetIP sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 200, Msg: "success", Data: result}) return } } sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 404, Msg: "内存库里没有找到这个 IP 喵~", Data: nil}) } else { var infoJSON string var networkEnd uint32 err := db.QueryRow(` SELECT network_end, ip_info_json FROM ip_info WHERE network_start <= ? ORDER BY network_start DESC LIMIT 1`, ipInt).Scan(&networkEnd, &infoJSON) if err != nil { if err == sql.ErrNoRows { sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 404, Msg: "数据库里没有找到这个 IP 喵~", Data: nil}) } else { log.Printf("数据库查询错误: %v\n", err) sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 500, Msg: "数据库查询出错了喵", Data: nil}) } return } if ipInt > networkEnd { sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 404, Msg: "数据库里没有找到这个 IP 喵~", Data: nil}) return } var rawData map[string]string if err := json.Unmarshal([]byte(infoJSON), &rawData); err != nil { log.Printf("JSON 解析错误: %v\n", err) sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 500, Msg: "数据解析失败喵", Data: nil}) return } result := IPInfo{ IP: targetIP, Country: rawData["country"], Province: rawData["province"], City: rawData["city"], ISP: rawData["isp"], Latitude: rawData["latitude"], Longitude: rawData["longitude"], } sendJSONResponse(w, clientIP, targetIP, APIResponse{Code: 200, Msg: "success", Data: result}) } } func main() { dbPath := flag.String("db", "ip_info.db", "SQLite 数据库文件路径") port := flag.String("port", "8080", "API 服务监听端口") memFlag := flag.Bool("mem", false, "是否开启全量内存模式(内存换取极致性能喵~)") logFlag := flag.Bool("log", false, "是否开启详细访问日志输出") flag.Parse() useMemoryCache = *memFlag enableDetailLog = *logFlag // 赋值给全局变量 var err error db, err = sql.Open("sqlite3", *dbPath) if err != nil { log.Fatal("无法打开数据库: ", err) } defer db.Close() if useMemoryCache { if err := loadDataToMemory(); err != nil { log.Fatal("致命错误:无法将数据加载到内存喵: ", err) } } http.HandleFunc("/ipinfo", apiHandler) addr := fmt.Sprintf(":%s", *port) fmt.Printf("猫娘 API 服务启动于 %s 喵...\n", addr) fmt.Printf("当前数据库文件: %s\n", *dbPath) if useMemoryCache { fmt.Println("当前运行模式: [极致性能] 全量内存 + 二分查找") } else { fmt.Println("当前运行模式: [省内存] SQLite 实时查询 (可加 -mem=true 提速)") } if enableDetailLog { fmt.Println("详细访问日志: 已开启 (将在控制台打印每次请求细节)") } else { fmt.Println("详细访问日志: 未开启 (可加 -log=true 开启)") } log.Fatal(http.ListenAndServe(addr, nil)) }另外,我还加上了日志输出的功能,方便进行API调用的监控,可以防范滥用。经过测试,在把数据库存进内存后,可以抗的并发请求从 5000+ 请求/秒 提升到了 7000+ 请求/秒,提升了40%!不过我也观察到,此时Nginx的CPU占用率明显上升,大约占据了40%+的CPU,而Go程序本身的CPU占用率则从50%左右降低到了35%!不过有点奇怪的是,大约有20%的CPU占用率不知道是被什么程序占据了,BTOP没有显示出来。其实在第一版代码的时候,Nginx的CPU占用已经挺高了,请求数量提升后更高。值得注意的是,两个版本的服务端进行极限压力测试的时候,整机的CPU占用率几乎都是100%。不知道各位对这个性能表现感觉如何?分析:“消失”的20% CPU占用去哪了?在测试中,我观察到一个有趣的现象:Nginx 占用了约 40%,Go 程序占用了约 35%,加起来只有 75% 左右,但整机 CPU 却已经打满(100%)。剩下的 20% 去哪了?难道被猫娘偷吃了吗?其实,这并非 BUG,而是高性能网络服务的典型特征——内核态开销(System CPU)。当 QPS 达到 7000+ 时,服务器每秒需要处理数万个网络数据包的接收、发送、上下文切换以及系统调用。这些操作并不发生在 Nginx 或 Go 程序的“用户态”代码中,而是直接由 Linux 内核 接管处理:网络协议栈处理:TCP/IP 包的解析、校验、重组。软中断(SoftIRQs):网卡中断后的数据搬运。线程调度:在数千个并发连接间快速切换 CPU 时间片。结论:这“消失”的 20% 实际上是被 操作系统内核 消耗掉了,用于支撑如此高频率的网络吞吐。这也侧面证明了我们的程序优化已经非常到位,瓶颈不再在于应用层代码,而在于底层的网络 IO 处理能力。如果再想提升,可能就需要考虑内核参数调优(如调整 TCP 缓冲区、开启 RSS 多队列网卡等)或者升级更强的单核 CPU 了喵!提升程序的兼容性因为我们的项目依赖了go-sqlite3,这要求必须开启CGO进行编译。如果在一般的Linux发行版(如Ubuntu、CentOS)上直接编译,默认会动态链接系统的C语言标准库(glibc)。这必然会导致把程序放到不同年代、不同发行版的服务器上运行时,出现“找不到glibc版本”等兼容性报错。为了达到“一次编译,到处运行”的完美兼容性,我们需要进行纯静态链接打包。但是,在普通发行版上强行静态编译 glibc 是非常痛苦且容易踩坑的。因此,我们需要借助musl这个极其精简的C标准库。使用musl的典型代表就是Alpine Linux(这也是老朋友了,我的云服务器基本上都是这个发行版)。我们可以非常优雅地在 Alpine 环境下进行全静态编译操作:先安装必要的 C 语言编译工具链:apk add go gcc musl-dev content_copy进入代码目录后,初始化模块并拉取依赖:go mod init github.com/Chocola-X/NekoIPinfo go mod tidy content_copy最后,使用“静态编译参数”进行编译:CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags="-linkmode external -extldflags -static -s -w" -o neko-ip-api main.go content_copy这样编译出来的二进制文件内部已经打包了所有需要的底层库。你把它丢到绝大部分 x86_64 架构的 Linux 发行版上,都可以做到直接开箱即用!结尾项目已经开源到了Github:NekoIPinfo并且我也自己搭建了查询服务,感兴趣的可以来玩玩:NekoIPinfo Demo
2026年03月06日
1 阅读
0 评论
0 点赞