当前位置: 首页 > 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…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...