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笔记)
我的需求是在处理几百上千万数据时避免缓存穿透以及缓存击穿情况出现,并且确保数据库和redis同步,为了方便我查询数据操作加了一些条件精准查询和模糊查询以及全字段模糊查询、分页、排序一些小玩意,redis存储是hash表key值也就是数据ID&…...
nodejs搭配express网站开发后端接口设计需要注意事项
nodejs搭配express网站开发后端接口设计需要注意事项!为了回避一些常见的误区,今天和大家汇总一下,最近我遇到的一些错误信息,虽然都是小问题,但是还是需要分享一下,以免大家再次犯错。 1:第一个…...
mysql 基于chunk机制是如何支持运行期间,动态调整buffer pool大小的
mysql 基于chunk机制是如何支持运行期间,动态调整buffer pool大小的 MySQL 的 InnoDB 存储引擎确实支持在运行期间动态调整缓冲池(buffer pool)的大小,但其机制与自定义缓存系统有所不同。InnoDB 通过内部优化和配置参数来实现这…...
智能客户服务:AI与大数据的革新力量
在当今信息技术日新月异的时代,大数据和人工智能(AI)正逐步成为推动各行各业变革的重要力量。尤其是在客户服务领域,大数据与AI的深度融合正引领着客服系统的全面革新。 一、大数据与AI在智能客服系统中的应用 智能客服系统是一种…...
Python日常使用的自动化脚本
Python日常使用的自动化脚本 LinkDescriptionsort_files根据文件扩展名将目录中的文件组织到子目录中remove_empty_folders删除所有空的文件夹rename_files批量重命名目录中的文件scrape_data从网站上抓取数据download_images从网站批量下载图片count_words统计指定文件中的单…...
代理模式(JDK,CGLIB动态代理,AOP切面编程)
代理模式是一种结构型设计模式,它通过一个代理对象作为中间层来控制对目标对象的访问,从而增强或扩展目标对象的功能,同时保持客户端对目标对象的使用方式一致。 代理模式在Java中的应用,例如 1.统一异常处理 2.Mybatis使用代理 3.Spring…...
【Leetcode 热题 100】236. 二叉树的最近公共祖先
问题背景 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 最近公共祖先的定义为:对于有根树 T T T 的两个节点 p p p、 q q q,最近公共祖先表示为一个节点 x x x,满足 x x x 是 p p p、 q q q 的祖先且 x x x 的深度尽可能大…...
Go框架比较:goframe、beego、iris和gin
由于工作需要,这些年来也接触了不少的开发框架,Golang的开发框架比较多,不过基本都是Web"框架"为主。这里稍微打了个引号,因为大部分"框架"从设计和功能定位上来讲,充其量都只能算是一个组件&…...
Kafka Streams 在监控场景的应用与实践
作者:来自 vivo 互联网服务器团队- Pang Haiyun 介绍 Kafka Streams 的原理架构,常见配置以及在监控场景的应用。 一、背景 在当今大数据时代,实时数据处理变得越来越重要,而监控数据的实时性和可靠性是监控能力建设最重要的一环…...
数据结构 -- 二叉树
目录 1、二叉树概念及结构 1.1、概念 1.2、特殊的二叉树 1.3、二叉树的性质 1.4、二叉树的存储结构 1.4.1、顺序存储 -- 看截图:二叉树的顺序存储 1.4.2、链式存储 -- 非完全二叉树用这种方式存储 2、二叉树的遍历 2.1、前序、中序以及后序遍历2.2、层序遍…...
redis数据转移
可能有时候因为硬件的原因我们我们需要更换服务器,如果更换服务器的话,那我们redis的数据该怎样转移呢,按照一下步骤即可完成redis数据的转移 1.进入redis客户端 2.使用 bgsave命令进行数据的备份,此命令完成后会在你的redis安装目…...
Ubuntu Netlink 套接字使用介绍
Netlink 套接字 是 Linux 特有的一种 IPC(进程间通信)机制,用于用户态进程和内核模块之间的通信。它可以用来完成路由管理、设备通知、网络状态更新等任务。 1. Netlink 的基本工作原理 Netlink 是一种双向通信机制。Netlink 消息分为请求和…...
spring boot密码加密方式
1. BCrypt 原理 BCrypt是一种专为密码哈希设计的算法,它被广泛认为是安全的选择之一。它不仅是一个单向函数(即只能加密不能解密),而且还内置了盐(salt)生成机制来防止彩虹表攻击。BCrypt的一个重要特点是…...
springboot根据租户id动态指定数据源
代码地址 码云地址springboot根据租户id动态指定数据源: springboot根据租户id指定动态数据源,结合mybatismysql多数源下的事务管理 创建3个数据库和对应的表 sql脚本在下图位置 代码的执行顺序 先设置主数据库的数据源配置目标数据源和默认数据源有了主库的数据源ÿ…...
使用C语言编写UDP循环接收并打印消息的程序
使用C语言编写UDP循环接收并打印消息的程序 前提条件程序概述伪代码C语言实现编译和运行C改进之自由设定端口注意事项在本文中,我们将展示如何使用C语言编写一个简单的UDP服务器程序,该程序将循环接收来自指定端口的UDP消息,并将接收到的消息打印到控制台。我们将使用POSIX套…...
【AI】✈️问答页面搭建-内网穿透公网可访问!
目录 👋前言 👀一、后端改动 🌱二、内网穿透 💞️三、前端改动 🍹四、测试 📫五、章末 👋前言 小伙伴们大家好,上次本地搭建了一个简单的 ai 页面,实现流式输出问答…...
计算机毕业设计原创定制(免费送源码):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热力图-笛卡尔坐标系上的热力图,附视频讲解与代码下载
引言: 热力图(Heatmap)是一种数据可视化技术,它通过颜色的深浅变化来表示数据在不同区域的分布密集程度。在二维平面上,热力图将数据值映射为颜色,通常颜色越深表示数据值越大,颜色越浅表示数…...
【Lua热更新】下篇
上篇链接:【Lua热更新】上篇 文章目录 三、xLua热更新📖1.概述📚︎2.导入xLua框架🔖3. C#调用Lua3.1Lua解析器3.2Lua文件夹的重定向3.3Lua解析器管理器3.4全局变量获取3.5全局函数获取3.6映射到List和Dictionary3.7映射到类3.8映…...
Facebook 与数字社交的未来走向
随着数字技术的飞速发展,社交平台的角色和形式也在不断演变。作为全球最大社交平台之一,Facebook(现Meta)在推动数字社交的进程中扮演了至关重要的角色。然而,随着互联网的去中心化趋势和新技术的崛起,Face…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
