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

Golang Gin Redis+Mysql 同步查询更新删除操作(我的小GO笔记)

我的需求是在处理几百上千万数据时避免缓存穿透以及缓存击穿情况出现,并且确保数据库和redis同步,为了方便我查询数据操作加了一些条件精准查询和模糊查询以及全字段模糊查询、分页、排序一些小玩意,redis存储是hash表key值也就是数据ID,name值是数据表名和redis同步的,别问为什么,我懒!!

使用示例:
params := utils.QueryParams{Name:  "users",Limit: 10,Order: "id",Sort:  1,Where: map[string]interface{}{"name": "张",  // 将进行模糊查询"age":  18,   // 将进行精确匹配"*": "李",  // 将进行全字段模糊查询},
}
results, err := utils.CustomRedisQuery(db, redisClient, params)
if err != nil {// 处理错误
}result, err := GetRedisById(rdb, "users", 1)result, err := GetRedisByWhere(rdb, "users", map[string]interface{}{"status": 1, "type": "vip"}, 1)err = DeleteRedisById(rdb, "users", 1)err = UpdateRedisById(db, rdb, "users", 1)

完整代码 

/*
+--------------------------------------------------------------------------------
| If this code works, it was written by Xven. If not, I don't know who wrote it.
+--------------------------------------------------------------------------------
| Statement: An Ordinary Person
+--------------------------------------------------------------------------------
| Author: Xven <QQ:270988107>
+--------------------------------------------------------------------------------
| Copyright (c) 2024 Xven All rights reserved.
+--------------------------------------------------------------------------------
*/
package utilsimport ("context""encoding/json""fmt""sort""strings""sync""time""github.com/go-redis/redis/v8""gorm.io/gorm"
)type QueryParams struct {Name  string                 // 表名Limit int                    // 分页数量Order string                 // 排序字段Sort  int                    // 排序方式 1:升序 2:降序Where map[string]interface{} // 查询条件
}/*** 检查字符串是否包含子串* @Author Xven <270988107@qq.com>* @param {string} str* @param {string} substr* @return {bool}*/
func containsString(str, substr string) bool {return strings.Contains(strings.ToLower(str), strings.ToLower(substr))
}/*** 检查字符串是否包含通配符* @Author Xven <270988107@qq.com>* @param {string} str* @return {bool}*/
func hasWildcard(str string) bool {return strings.Contains(str, "*")
}/*** 对数据进行排序* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {string} orderField* @param {int} sortType* @return {void}*/
func sortData(data []map[string]interface{}, orderField string, sortType int) {sort.Slice(data, func(i, j int) bool {if sortType == 1 { // 升序return fmt.Sprint(data[i][orderField]) < fmt.Sprint(data[j][orderField])}return fmt.Sprint(data[i][orderField]) > fmt.Sprint(data[j][orderField])})
}/*** 自定义redis查询* @Author Xven <270988107@qq.com>* @param {*gorm.DB} db* @param {*redis.Client} rdb* @param {QueryParams} params* @return {[]map[string]interface{}, error}*/
func CustomRedisQuery(db *gorm.DB, rdb *redis.Client, params QueryParams) ([]map[string]interface{}, error) {ctx := context.Background()var result []map[string]interface{}// 参数校验,防止缓存穿透if params.Name == "" {return nil, fmt.Errorf("表名不能为空")}// 构建 Redis keyredisKey := params.Name + ":list"// 使用分布式锁防止缓存击穿lockKey := fmt.Sprintf("lock:%s", redisKey)lock := rdb.SetNX(ctx, lockKey, "1", 10*time.Second)if !lock.Val() {// 等待100ms后重试time.Sleep(100 * time.Millisecond)return CustomRedisQuery(db, rdb, params)}defer rdb.Del(ctx, lockKey)// 1. 先查询 Redis 缓存vals, err := rdb.HGetAll(ctx, redisKey).Result()if err == nil && len(vals) > 0 {// 将缓存数据解析为结果集for _, v := range vals {var item map[string]interface{}if err := json.Unmarshal([]byte(v), &item); err == nil {result = append(result, item)}}// 如果有查询条件,则进行过滤if len(params.Where) > 0 {result = filterData(result, params.Where)}// 处理排序if params.Order != "" {sortData(result, params.Order, params.Sort)}// 处理分页if params.Limit > 0 && len(result) > params.Limit {result = result[:params.Limit]}return result, nil}// 2. Redis 没有数据,从数据库查询var dbResult []map[string]interface{}// 使用连接池控制并发pool := make(chan struct{}, 10)var wg sync.WaitGroupvar mu sync.Mutex// 使用游标分批查询数据库,避免一次性加载过多数据err = db.Table(params.Name).FindInBatches(&dbResult, 1000, func(tx *gorm.DB, batch int) error {wg.Add(1)pool <- struct{}{} // 获取连接go func(data []map[string]interface{}) {defer func() {<-pool // 释放连接wg.Done()}()pipe := rdb.Pipeline()// 批量写入Redisfor _, item := range data {// 将每条记录序列化为JSONjsonData, err := json.Marshal(item)if err != nil {continue}// 使用ID作为field,JSON作为value写入hashid := fmt.Sprint(item["id"])pipe.HSet(ctx, redisKey, id, string(jsonData))}// 执行管道命令_, err := pipe.Exec(ctx)if err != nil {// 写入失败时重试写入数据for _, item := range data {jsonData, _ := json.Marshal(item)id := fmt.Sprint(item["id"])rdb.HSet(ctx, redisKey, id, string(jsonData))}}mu.Lock()result = append(result, data...)mu.Unlock()}(dbResult)return nil}).Errorwg.Wait()if err != nil {// 设置空值缓存,防止缓存穿透rdb.Set(ctx, redisKey+"_empty", "1", 5*time.Minute)return nil, err}// 处理排序if params.Order != "" {sortData(result, params.Order, params.Sort)}// 处理分页if params.Limit > 0 && len(result) > params.Limit {result = result[:params.Limit]}return result, nil
}/*** 过滤数据* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {map[string]interface{}} where* @return {[]map[string]interface{}}*/
func filterData(data []map[string]interface{}, where map[string]interface{}) []map[string]interface{} {var filteredResult []map[string]interface{}// 先处理精确匹配条件hasExactMatch := falsefor field, value := range where {if field != "*" {if strValue, ok := value.(string); ok && !hasWildcard(strValue) {hasExactMatch = truebreak} else if !ok {hasExactMatch = truebreak}}}if hasExactMatch {filteredResult = exactMatch(data, where)if len(filteredResult) > 0 {filteredResult = fuzzyMatch(filteredResult, where)}} else {filteredResult = fuzzyMatch(data, where)}return filteredResult
}/*** 精确匹配* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {map[string]interface{}} where* @return {[]map[string]interface{}}*/
func exactMatch(data []map[string]interface{}, where map[string]interface{}) []map[string]interface{} {var result []map[string]interface{}for _, item := range data {matched := truefor field, value := range where {if field == "*" {continue}if strValue, ok := value.(string); ok {if !hasWildcard(strValue) {if itemValue, exists := item[field]; !exists || itemValue != value {matched = falsebreak}}} else {if itemValue, exists := item[field]; !exists || itemValue != value {matched = falsebreak}}}if matched {result = append(result, item)}}return result
}/*** 模糊匹配* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {map[string]interface{}} where* @return {[]map[string]interface{}}*/
func fuzzyMatch(data []map[string]interface{}, where map[string]interface{}) []map[string]interface{} {var result []map[string]interface{}// 处理指定字段的模糊查询for _, item := range data {matched := truefor field, value := range where {if field == "*" {continue}if strValue, ok := value.(string); ok && hasWildcard(strValue) {if itemValue, exists := item[field]; exists {if strItemValue, ok := itemValue.(string); ok {pattern := strings.ReplaceAll(strValue, "*", "")if !strings.Contains(strings.ToLower(strItemValue), strings.ToLower(pattern)) {matched = falsebreak}}}}}if matched {result = append(result, item)}}// 处理全字段模糊查询if wildcardValue, exists := where["*"]; exists {var globalResult []map[string]interface{}searchData := resultif len(searchData) == 0 {searchData = data}if strValue, ok := wildcardValue.(string); ok {for _, item := range searchData {matched := falsefor _, fieldValue := range item {if strFieldValue, ok := fieldValue.(string); ok {if containsString(strFieldValue, strValue) {matched = truebreak}}}if matched {globalResult = append(globalResult, item)}}}result = globalResult}return result
}/*** 根据ID查询单条数据* @Author Xven <270988107@qq.com>* @param {*redis.Client} rdb* @param {string} name* @param {interface{}} id* @return {map[string]interface{}, error}*/
func GetRedisById(rdb *redis.Client, name string, id interface{}) (map[string]interface{}, error) {ctx := context.Background()redisKey := name + ":list"// 从Redis查询jsonData, err := rdb.HGet(ctx, redisKey, fmt.Sprint(id)).Result()if err == nil {// Redis命中,解析JSON数据var result map[string]interface{}err = json.Unmarshal([]byte(jsonData), &result)if err == nil {return result, nil}}return nil, err
}/*** 根据条件查询数据* @Author Xven <270988107@qq.com>* @param {*redis.Client} rdb* @param {string} name* @param {map[string]interface{}} where* @param {int} is* @return {interface{}, error}*/
func GetRedisByWhere(rdb *redis.Client, name string, where map[string]interface{}, is int) (interface{}, error) {ctx := context.Background()redisKey := name + ":list"// 获取所有数据values, err := rdb.HGetAll(ctx, redisKey).Result()if err != nil {return nil, err}var results []map[string]interface{}// 遍历所有数据进行条件匹配for _, jsonStr := range values {var item map[string]interface{}err := json.Unmarshal([]byte(jsonStr), &item)if err != nil {continue}// 检查是否匹配所有条件match := truefor k, v := range where {if item[k] != v {match = falsebreak}}if match {results = append(results, item)// 如果是单条查询且已找到,则直接返回if is == 0 {return item, nil}}}if is == 0 {return nil, nil}return results, nil
}/*** 删除指定ID的数据* @Author Xven <270988107@qq.com>* @param {*redis.Client} rdb* @param {string} name* @param {interface{}} id* @return {error}*/
func DeleteRedisById(rdb *redis.Client, name string, id interface{}) error {ctx := context.Background()redisKey := name + ":list"maxRetries := 3for i := 0; i < maxRetries; i++ {err := rdb.HDel(ctx, redisKey, fmt.Sprint(id)).Err()if err == nil {return nil}// 重试前等待短暂时间time.Sleep(time.Millisecond * 100)}return fmt.Errorf("failed to delete after %d retries", maxRetries)
}/*** 更新指定ID的数据* @Author Xven <270988107@qq.com>* @param {*gorm.DB} db* @param {*redis.Client} rdb* @param {string} name* @param {interface{}} id* @return {error}*/
func UpdateRedisById(db *gorm.DB, rdb *redis.Client, name string, id interface{}) error {ctx := context.Background()redisKey := name + ":list"maxRetries := 3// 从数据库查询数据var result map[string]interface{}err := db.Table(name).Where("id = ?", id).Take(&result).Errorif err != nil {return err}// 序列化数据jsonData, err := json.Marshal(result)if err != nil {return err}// 重试更新Redisfor i := 0; i < maxRetries; i++ {err = rdb.HSet(ctx, redisKey, fmt.Sprint(id), string(jsonData)).Err()if err == nil {return nil}time.Sleep(time.Millisecond * 100)}return fmt.Errorf("failed to update after %d retries", maxRetries)
}

 

 

相关文章:

Golang Gin Redis+Mysql 同步查询更新删除操作(我的小GO笔记)

我的需求是在处理几百上千万数据时避免缓存穿透以及缓存击穿情况出现&#xff0c;并且确保数据库和redis同步&#xff0c;为了方便我查询数据操作加了一些条件精准查询和模糊查询以及全字段模糊查询、分页、排序一些小玩意&#xff0c;redis存储是hash表key值也就是数据ID&…...

nodejs搭配express网站开发后端接口设计需要注意事项

nodejs搭配express网站开发后端接口设计需要注意事项&#xff01;为了回避一些常见的误区&#xff0c;今天和大家汇总一下&#xff0c;最近我遇到的一些错误信息&#xff0c;虽然都是小问题&#xff0c;但是还是需要分享一下&#xff0c;以免大家再次犯错。 1&#xff1a;第一个…...

mysql 基于chunk机制是如何支持运行期间,动态调整buffer pool大小的

mysql 基于chunk机制是如何支持运行期间&#xff0c;动态调整buffer pool大小的 MySQL 的 InnoDB 存储引擎确实支持在运行期间动态调整缓冲池&#xff08;buffer pool&#xff09;的大小&#xff0c;但其机制与自定义缓存系统有所不同。InnoDB 通过内部优化和配置参数来实现这…...

智能客户服务:AI与大数据的革新力量

在当今信息技术日新月异的时代&#xff0c;大数据和人工智能&#xff08;AI&#xff09;正逐步成为推动各行各业变革的重要力量。尤其是在客户服务领域&#xff0c;大数据与AI的深度融合正引领着客服系统的全面革新。 一、大数据与AI在智能客服系统中的应用 智能客服系统是一种…...

Python日常使用的自动化脚本

Python日常使用的自动化脚本 LinkDescriptionsort_files根据文件扩展名将目录中的文件组织到子目录中remove_empty_folders删除所有空的文件夹rename_files批量重命名目录中的文件scrape_data从网站上抓取数据download_images从网站批量下载图片count_words统计指定文件中的单…...

代理模式(JDK,CGLIB动态代理,AOP切面编程)

代理模式是一种结构型设计模式&#xff0c;它通过一个代理对象作为中间层来控制对目标对象的访问&#xff0c;从而增强或扩展目标对象的功能&#xff0c;同时保持客户端对目标对象的使用方式一致。 代理模式在Java中的应用,例如 1.统一异常处理 2.Mybatis使用代理 3.Spring…...

【Leetcode 热题 100】236. 二叉树的最近公共祖先

问题背景 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 最近公共祖先的定义为&#xff1a;对于有根树 T T T 的两个节点 p p p、 q q q&#xff0c;最近公共祖先表示为一个节点 x x x&#xff0c;满足 x x x 是 p p p、 q q q 的祖先且 x x x 的深度尽可能大…...

Go框架比较:goframe、beego、iris和gin

由于工作需要&#xff0c;这些年来也接触了不少的开发框架&#xff0c;Golang的开发框架比较多&#xff0c;不过基本都是Web"框架"为主。这里稍微打了个引号&#xff0c;因为大部分"框架"从设计和功能定位上来讲&#xff0c;充其量都只能算是一个组件&…...

Kafka Streams 在监控场景的应用与实践

作者&#xff1a;来自 vivo 互联网服务器团队- Pang Haiyun 介绍 Kafka Streams 的原理架构&#xff0c;常见配置以及在监控场景的应用。 一、背景 在当今大数据时代&#xff0c;实时数据处理变得越来越重要&#xff0c;而监控数据的实时性和可靠性是监控能力建设最重要的一环…...

数据结构 -- 二叉树

目录 1、二叉树概念及结构 1.1、概念 1.2、特殊的二叉树 1.3、二叉树的性质 1.4、二叉树的存储结构 1.4.1、顺序存储 -- 看截图&#xff1a;二叉树的顺序存储 1.4.2、链式存储 -- 非完全二叉树用这种方式存储 2、二叉树的遍历 2.1、前序、中序以及后序遍历2.2、层序遍…...

redis数据转移

可能有时候因为硬件的原因我们我们需要更换服务器&#xff0c;如果更换服务器的话&#xff0c;那我们redis的数据该怎样转移呢&#xff0c;按照一下步骤即可完成redis数据的转移 1.进入redis客户端 2.使用 bgsave命令进行数据的备份&#xff0c;此命令完成后会在你的redis安装目…...

Ubuntu Netlink 套接字使用介绍

Netlink 套接字 是 Linux 特有的一种 IPC&#xff08;进程间通信&#xff09;机制&#xff0c;用于用户态进程和内核模块之间的通信。它可以用来完成路由管理、设备通知、网络状态更新等任务。 1. Netlink 的基本工作原理 Netlink 是一种双向通信机制。Netlink 消息分为请求和…...

spring boot密码加密方式

1. BCrypt 原理 BCrypt是一种专为密码哈希设计的算法&#xff0c;它被广泛认为是安全的选择之一。它不仅是一个单向函数&#xff08;即只能加密不能解密&#xff09;&#xff0c;而且还内置了盐&#xff08;salt&#xff09;生成机制来防止彩虹表攻击。BCrypt的一个重要特点是…...

springboot根据租户id动态指定数据源

代码地址 码云地址springboot根据租户id动态指定数据源: springboot根据租户id指定动态数据源,结合mybatismysql多数源下的事务管理 创建3个数据库和对应的表 sql脚本在下图位置 代码的执行顺序 先设置主数据库的数据源配置目标数据源和默认数据源有了主库的数据源&#xff…...

使用C语言编写UDP循环接收并打印消息的程序

使用C语言编写UDP循环接收并打印消息的程序 前提条件程序概述伪代码C语言实现编译和运行C改进之自由设定端口注意事项在本文中,我们将展示如何使用C语言编写一个简单的UDP服务器程序,该程序将循环接收来自指定端口的UDP消息,并将接收到的消息打印到控制台。我们将使用POSIX套…...

【AI】✈️问答页面搭建-内网穿透公网可访问!

目录 &#x1f44b;前言 &#x1f440;一、后端改动 &#x1f331;二、内网穿透 &#x1f49e;️三、前端改动 &#x1f379;四、测试 &#x1f4eb;五、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;上次本地搭建了一个简单的 ai 页面&#xff0c;实现流式输出问答…...

计算机毕业设计原创定制(免费送源码):NodeJS+MVVM+MySQL 樱花在线视频网站

目 录 摘要 1 1 绪论 1 1.1研究背景 1 1.2系统设计思想 1 1.3B/S体系工作原理 1 1.4node.js主要功能 2 1.5论文结构与章节安排 3 2 樱花在线视频网站分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5 …...

ECharts热力图-笛卡尔坐标系上的热力图,附视频讲解与代码下载

引言&#xff1a; 热力图&#xff08;Heatmap&#xff09;是一种数据可视化技术&#xff0c;它通过颜色的深浅变化来表示数据在不同区域的分布密集程度。在二维平面上&#xff0c;热力图将数据值映射为颜色&#xff0c;通常颜色越深表示数据值越大&#xff0c;颜色越浅表示数…...

【Lua热更新】下篇

上篇链接&#xff1a;【Lua热更新】上篇 文章目录 三、xLua热更新&#x1f4d6;1.概述&#x1f4da;︎2.导入xLua框架&#x1f516;3. C#调用Lua3.1Lua解析器3.2Lua文件夹的重定向3.3Lua解析器管理器3.4全局变量获取3.5全局函数获取3.6映射到List和Dictionary3.7映射到类3.8映…...

Facebook 与数字社交的未来走向

随着数字技术的飞速发展&#xff0c;社交平台的角色和形式也在不断演变。作为全球最大社交平台之一&#xff0c;Facebook&#xff08;现Meta&#xff09;在推动数字社交的进程中扮演了至关重要的角色。然而&#xff0c;随着互联网的去中心化趋势和新技术的崛起&#xff0c;Face…...

AI原生应用:解决幻觉难题的有效途径

AI原生应用:解决幻觉难题的有效途径 关键词:AI原生应用、大模型幻觉、检索增强生成(RAG)、验证模块、智能系统架构 摘要:大语言模型(LLM)的“幻觉”(Hallucination)问题——生成与事实不符的内容,正成为AI应用落地的最大障碍。本文将从“AI原生应用”的视角出发,用通…...

3步终结告警疲劳:Keep平台的智能告警管理实践

3步终结告警疲劳&#xff1a;Keep平台的智能告警管理实践 【免费下载链接】keep The open-source alerts management and automation platform 项目地址: https://gitcode.com/GitHub_Trending/kee/keep 智能告警管理已成为现代运维体系的核心能力。根据Gartner最新报告…...

Vue-Vben-Admin主题定制实战指南:从原理到实现的深度探索

Vue-Vben-Admin主题定制实战指南&#xff1a;从原理到实现的深度探索 【免费下载链接】vue-vben-admin vbenjs/vue-vben-admin: 是一个基于 Vue.js 和 Element UI 的后台管理系统&#xff0c;支持多种数据源和插件扩展。该项目提供了一个完整的后台管理系统&#xff0c;可以方便…...

cobalt家谱研究者助手:家族历史与档案管理方案

cobalt家谱研究者助手&#xff1a;家族历史与档案管理方案 引言&#xff1a;家谱研究的数字时代痛点与解决方案 你是否还在为散乱的家族史料整理而困扰&#xff1f;是否经历过珍贵的口述历史随时间流逝而湮灭&#xff1f;cobalt家谱研究者助手&#xff08;家族历史与档案管理方…...

长上下文不可强求:从 Gemini 到 Opus,1M context 为什么还没体现出应有价值

长上下文不可强求&#xff1a;从 Gemini 到 Opus&#xff0c;1M context 为什么还没体现出应有价值 摘要 过去一年&#xff0c;long context 一直是大模型产品最容易被拿来宣传的能力之一。32K 不够&#xff0c;就上 128K&#xff1b;128K 还不够&#xff0c;就上 1M。看起来&a…...

AI 辅助 CAPL 脚本编写实战

专栏&#xff1a;《AI 汽车电子测试实战》第 6 篇 作者&#xff1a;一线汽车电子测试工程师 适合人群&#xff1a;CANoe 测试工程师、想学习 CAPL 的新手、想提升脚本效率的测试人员开篇&#xff1a;CAPL 脚本的痛点 CAPL&#xff08;Communication Access Programming Languag…...

公开信息整理|2026年3月26日:科学进展、词元活动、食品安全、护理保险与部分国际动态速览

&#x1f525;个人主页&#xff1a;杨利杰YJlio❄️个人专栏&#xff1a;《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》 《Python》 《Kali Linux》 《那些年未解决的Windows疑难杂症》&#x1f31f; 让复杂的事情更…...

React篇——第一章 React的基础知识(上篇)

目录 1. React简介 1.1 什么是React 1.2 React的核心优势 组件化开发 虚拟DOM 丰富的生态系统 跨平台支持 1.3 React的市场地位 2. 开发环境搭建 2.1 使用create-react-app创建项目 2.2 其他创建React项目的方式 3. JSX基础 3.1 什么是JSX 3.2 JSX的优势 3.3 JS…...

模型缓存优化:nanobot热加载速度提升3倍实测

模型缓存优化&#xff1a;nanobot热加载速度提升3倍实测 1. 问题背景与优化动机 最近在本地部署OpenClaw时&#xff0c;我发现一个影响体验的痛点&#xff1a;每次调用nanobot模型都需要重新加载&#xff0c;导致响应延迟明显。特别是在频繁交互的场景下&#xff0c;这种等待…...

LFM2.5-1.2B-Thinking-GGUF开源大模型:低成本GPU算力高效利用实践指南

LFM2.5-1.2B-Thinking-GGUF开源大模型&#xff1a;低成本GPU算力高效利用实践指南 1. 模型概述 LFM2.5-1.2B-Thinking-GGUF是Liquid AI推出的轻量级文本生成模型&#xff0c;专为低资源环境优化设计。这个1.2B参数的模型采用GGUF格式&#xff0c;能够在消费级GPU甚至CPU上高效…...