redis基本数据结构-string

news2024/11/24 0:15:51

文章目录

    • 1. redis的string数据结构
    • 2. 常见的业务场景
      • 2.1 缓存功能
        • 案例讲解
          • 背景
          • 优势
          • 解决方案
          • 代码实现
      • 2.2 计数器
        • 案例讲解
          • 背景
          • 优势
          • 解决方案
          • 代码实现
      • 2.3 分布式锁
        • 案例讲解
          • 背景
          • 优势
          • 解决方案
          • 代码实现
      • 2.4 限流
        • 案例讲解
          • 背景
          • 优势
          • 解决方案
          • 代码实现
      • 2.5 共享session
        • 案例讲解
          • 背景
          • 优势
          • 解决方案
          • 代码实现

1. redis的string数据结构

参考链接:https://mp.weixin.qq.com/s/srkd73bS2n3mjIADLVg72A
redis 的 string 数据结构是 redis 中最基本的数据类型,它可以存储任何形式的数据,最大可以存储 512MB 的字符串。string 类型可以存储文本、数字和二进制数据等。
具有以下特性:

  1. 简单性:String 是最简单的类型,可以用来存储简单的键值对。
  2. 多样性:可以存储任意类型的数据,包括文本、数字、甚至是序列化后的对象。
  3. 原子性:对 String 的操作是原子性的,即在并发情况下操作串的安全性。

以下是一些与 String 相关的常用 Redis 命令:

  • SET key value:设置指定 key 的值。
  • GET key:获取指定 key 的值。
  • DEL key:删除指定 key。
  • EXISTS key:检查指定 key 是否存在。
  • INCR key:将 key 的值加 1。
  • DECR key:将 key 的值减 1。
  • MSET key1 value1 key2 value2 …:同时设置多个 key-value 对。
  • MGET key1 key2 …:同时获取多个 key 的值。
  • SETEX key seconds value:设置 key 的值,同时设置过期时间(以秒为单位)。
xxxxxx:6379> SET person:1 '{"name": "Alice", "age": 30, "city": "New York"}'
OK

xxxxxx:6379> get person:1
"{\"name\": \"Alice\", \"age\": 30, \"city\": \"New York\"}"

xxxxxx:6379> exists person:1
(integer) 1

xxxxxx:6379> exists person:2
(integer) 0

xxxxxx:6379> set art:122 0
OK

xxxxxx:6379> incr art:122
(integer) 1

xxxxxx:6379> get art:122
"1"

xxxxxx:6379> decr art:122
(integer) 0

xxxxxx:6379> get art:122
"0"

xxxxxx:6379> mget person:1 art:122
1) "{\"name\": \"Alice\", \"age\": 30, \"city\": \"New York\"}"
2) "0"

xxxxxx:6379> SETEX config:site 600 '{"theme": "dark", "language": "en"}'
OK

xxxxxx:6379> mget person:1 art:122 config:site
1) "{\"name\": \"Alice\", \"age\": 30, \"city\": \"New York\"}"
2) "0"
3) "{\"theme\": \"dark\", \"language\": \"en\"}"

2. 常见的业务场景

2.1 缓存功能

string类型常用于缓存经常访问的数据,如数据库查询结果、网页内容等,以提高访问速度和降低数据库的压力。一般多是用于读多写少的场景。比如,商品的价格,描述等信息,用户的资料等信息。

案例讲解
背景

在商品系统中,商品的详细信息如描述、价格、库存等数据通常不会频繁变动,但会被频繁查询。每次用户访问商品详情时,都直接从数据库查询这些信息会导致不必要的数据库负载。

优势
  1. 快速数据访问:Redis作为内存数据库,提供极速的读写能力,大幅降低数据访问延迟,提升用户体验。
  2. 减轻数据库压力:缓存频繁访问的静态数据,显著减少数据库查询,从而保护数据库资源,延长数据库寿命。
  3. 高并发支持:Redis设计用于高并发环境,能够处理大量用户同时访问,保证系统在流量高峰时的稳定性。
  4. 灵活的缓存策略:易于实现缓存数据的更新和失效,结合适当的缓存过期和数据同步机制,确保数据的实时性和一致性。
解决方案

使用Redis String类型来缓存商品的静态信息。当商品信息更新时,相应的缓存也更新或失效。
在这里插入图片描述

代码实现
package main

import (
    "context"
    "encoding/json"
    "github.com/go-redis/redis/v8"
)

var (
    rdb *redis.Client   // 为了表现代码的完整性,这里省略了初始化代码
    ctx context.Context // 为了表现代码的完整性,这里省略了初始化代码
)

// generateProductCacheKey generates the cache key for the product.
func generateProductCacheKey(productID string) string {
    return "product:" + productID
}

// cacheProductInfo caches the product information in Redis.
func cacheProductInfo(productID string, productInfo map[string]interface{}) {
    cacheKey := generateProductCacheKey(productID)
    // 序列化商品信息为JSON格式
    productJSON, _ := json.Marshal(productInfo)
    // 将序列化后的商品信息存储到Redis===> set product:apple {"name":"apple","price":100, "description":"a nice apple"}
    rdb.Set(ctx, cacheKey, string(productJSON), 0) // 0表示永不过期,实际使用时可以设置过期时间
}

// getProductInfoFromCache gets the product information from Redis.
func getProductInfoFromCache(productID string) (map[string]interface{}, error) {
    cacheKey := generateProductCacheKey(productID)
    // 从Redis获取商品信息
    productJSON, err := rdb.Get(ctx, cacheKey).Result() // get product:apple
    if err != nil {
       return nil, err
    }
    if len(productJSON) == 0 {
       // 未在缓存中找到商品信息, 需要从数据库中获取, 同时更新缓存
       return nil, nil
    }
    // 反序列化JSON格式的商品信息
    var productInfo map[string]interface{}
    err = json.Unmarshal([]byte(productJSON), &productInfo)
    if err != nil {
       return nil, err
    }
    return productInfo, nil
}

func updateProductInfoAndCache(productID string, newProductInfo map[string]interface{}) {
    // 更新数据库中的商品信息

    // 更新Redis缓存中的商品信息
    cacheProductInfo(productID, newProductInfo)
}

在日常实践中,感觉这里还是会有一点小问题:

  1. 如果是在商品信息插入,对于一个新商品可能缓存肯定是不存在的,如果某一个商城大量上新新产品,这个时候如果流量大量进来,是否会造成缓存击穿的情况?因此对于这种插入情况多基本没更新的时候,上面的流程可能有点问题。
  2. 在用户查询商品信息的时候,如果存在大量缓存失败的情况,也会导致数据库崩溃的情况,所以在日常实践中,如果在redis中查询不到,那可以直接返回。因此在写入的时候需要双写;且要异步数据同步。定义一个定时任务,每隔一段时间将数据库的信息增量同步到redis,一天全量同步一次商品信息插入的时候双写
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/go-redis/redis/v8"
)

var (
    rdb *redis.Client   // 为了表现代码的完整性,这里省略了初始化代码
    ctx context.Context // 为了表现代码的完整性,这里省略了初始化代码
)

// generateProductCacheKey generates the cache key for the product.
func generateProductCacheKey(productID string) string {
    return "product:" + productID
}

// cacheProductInfo caches the product information in Redis.
func cacheProductInfo(productID string, productInfo map[string]interface{}) {
    cacheKey := generateProductCacheKey(productID)
    // 序列化商品信息为JSON格式
    productJSON, _ := json.Marshal(productInfo)
    // 将序列化后的商品信息存储到Redis===> set product:apple {"name":"apple","price":100, "description":"a nice apple"}
    rdb.Set(ctx, cacheKey, string(productJSON), 0) // 0表示永不过期,实际使用时可以设置过期时间
}

// getProductInfoFromCache gets the product information from Redis.
func getProductInfoFromCache(productID string) (map[string]interface{}, error) {
    cacheKey := generateProductCacheKey(productID)
    // 从Redis获取商品信息
    productJSON, err := rdb.Get(ctx, cacheKey).Result() // get product:apple
    if err != nil {
       return nil, err
    }
    // 反序列化JSON格式的商品信息
    var productInfo map[string]interface{}
    err = json.Unmarshal([]byte(productJSON), &productInfo)
    if err != nil {
       return nil, err
    }
    return productInfo, nil
}

func updateProductInfoAndCache(productID string, newProductInfo map[string]interface{}) {
    // 三个步骤如果有任何一个步骤失败,都需要回滚
    // 更新数据库中的商品信息
    // 更新Redis缓存中的商品信息
    cacheProductInfo(productID, newProductInfo)
    // 查一遍缓存,确保缓存中的商品信息已经更新
    info, err := getProductInfoFromCache(productID)
    if err != nil {
       // 处理错误
    }
    // 处理info
    fmt.Printf("product info: %v\n", info)
}

// 补充一个定时任务,可以使用crontab, 方便管理也可以使用airflow等工具

2.2 计数器

利用INCR和DECR命令,String类型可以作为计数器使用,适用于统计如网页访问量、商品库存数量等 。

案例讲解
背景

对于文章的浏览量的统计,每篇博客文章都有一个唯一的标识符(例如,文章ID)。每次文章被访问时,文章ID对应的浏览次数在Redis中递增。可以定期将浏览次数同步到数据库,用于历史数据分析。在这里插入图片描述

优势
  • 实时性:能够实时更新和获取文章的浏览次数。
  • 高性能:Redis的原子操作保证了高并发场景下的计数准确性。
解决方案

通过Redis实现对博客文章浏览次数的原子性递增和检索,以优化数据库访问并实时更新文章的浏览统计信息。

代码实现
package main

import (
    "context"
    "errors"
    "fmt"
    "github.com/go-redis/redis/v8"
    "log"
    "strconv"
)

var (
    rdb *redis.Client   // 为了表现代码的完整性,这里省略了初始化代码
    ctx context.Context // 为了表现代码的完整性,这里省略了初始化代码
)

// recordArticleView 记录文章浏览次数
func recordArticleView(articleID string) {
    res, err := rdb.Incr(ctx, articleID).Result()
    if err != nil {
       // 如果发生错误,记录错误日志
       log.Printf("Error incrementing view count for article %s: %v", articleID, err)
       return
    }
    // 可选:记录浏览次数到日志或进行其他业务处理
    fmt.Printf("Article %s has been viewed %d times\n", articleID, res)
}

// getArticleViewCount 从Redis获取文章的浏览次数
func getArticleViewCount(articleID string) (int, error) {
    // 从Redis获取文章的浏览次数
    viewCount, err := rdb.Get(ctx, articleID).Result()
    if err != nil {
       if errors.Is(err, redis.Nil) {
          // 如果文章ID在Redis中不存在,可以认为浏览次数为0
          return 0, nil
       } else {
          // 如果发生错误,记录错误日志
          log.Printf("Error getting view count for article %s: %v", articleID, err)
          return 0, err
       }
    }
    // 将浏览次数从字符串转换为整数
    count, err := strconv.Atoi(viewCount)
    if err != nil {
       log.Printf("Error converting view count to integer for article %s: %v", articleID, err)
       return 0, err
    }
    return count, nil
}

// renderArticlePage 渲染文章页面,并显示浏览次数
func renderArticlePage(articleID string) {
    // 在渲染文章页面之前,记录浏览次数
    recordArticleView(articleID)

    // 获取文章浏览次数
    viewCount, err := getArticleViewCount(articleID)
    if err != nil {
       // 处理错误,例如设置浏览次数为0或跳过错误
       viewCount = 0
    }
    log.Printf("Rendering article %s with view count %d\n", articleID, viewCount)
}

2.3 分布式锁

分布式锁:通过SETNX命令(仅当键不存在时设置值),String类型可以实现分布式锁,保证在分布式系统中的互斥访问 。

案例讲解
背景

在分布式系统中,如电商的秒杀活动或库存管理,需要确保同一时间只有一个进程或线程可以修改共享资源,以避免数据不一致的问题。
在这里插入图片描述

优势
  1. 互斥性:确保同一时间只有一个进程可以访问共享资源,防止数据竞争和冲突。
  2. 高可用性:分布式锁能够在节点故障或网络分区的情况下仍能正常工作,具备自动故障转移和恢复的能力。
  3. 可重入性:支持同一个进程或线程多次获取同一个锁,避免死锁的发生。
  4. 性能开销:相比于其他分布式协调服务,基于Redis的分布式锁实现简单且性能开销较小。
解决方案

使用Redis的SETNX命令实现分布式锁的获取和释放,通过Lua脚本确保释放锁时的原子性,并在执行业务逻辑前尝试获取锁,业务逻辑执行完毕后确保释放锁,从而保证在分布式系统中对共享资源的安全访问。

代码实现
package main

import (
    "context"
    "github.com/go-redis/redis/v8"
    "log"
    "strconv"
    "time"
)

var (
    rdb *redis.Client   // 为了表现代码的完整性,这里省略了初始化代码
    ctx context.Context // 为了表现代码的完整性,这里省略了初始化代码
)

// 尝试获取分布式锁
func tryGetDistributedLock(lockKey string, val string, expireTime int) bool {
    // 使用SET命令结合NX和PX参数尝试获取锁
    // NX表示如果key不存在则可以设置成功
    // PX指定锁的超时时间(毫秒)
    // 这里的val是一个随机值,用于在释放锁时验证锁是否属于当前进程
    result, err := rdb.SetNX(ctx, lockKey, val, time.Duration(expireTime)*time.Millisecond).Result()
    if err != nil {
       // 记录错误,例如:日志记录
       log.Printf("Error trying to get distributed lock for key %s: %v", lockKey, err)
       return false
    }
    // 如果result为1,则表示获取锁成功,result为0表示锁已被其他进程持有
    return result
}

// 释放分布式锁, 这里的val是一个随机值,用于在释放锁时验证锁是否属于当前进程
func releaseDistributedLock(lockKey string, val string) {
    // 使用Lua脚本来确保释放锁的操作是原子性的
    script := `
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
    `
    // 执行Lua脚本
    result, err := rdb.Eval(ctx, script, []string{lockKey}, val).Result()
    if err != nil {
       // 记录错误
       log.Printf("Error releasing distributed lock for key %s: %v", lockKey, err)
    }
    // 如果result为1,则表示锁被成功释放,如果为0,则表示锁可能已经释放或不属于当前进程
    if result == int64(0) {
       log.Printf("Failed to release the lock, it might have been released by others or expired")
    }
}

// 执行业务逻辑,使用分布式锁来保证业务逻辑的原子性
func executeBusinessLogic(lockKey string) {
    val := generateRandomValue()                    // 生成一个随机值,作为锁的值
    if tryGetDistributedLock(lockKey, val, 30000) { // 尝试获取锁,30秒超时
       defer releaseDistributedLock(lockKey, val) // 无论业务逻辑是否成功执行,都释放锁
       // 执行具体的业务逻辑
       // ...
    } else {
       // 未能获取锁,处理重试逻辑或返回错误
       // ...
    }
}
// generateRandomValue 生成一个随机值作为锁的唯一标识
func generateRandomValue() string {
    return strconv.FormatInt(time.Now().UnixNano(), 10)
}

2.4 限流

限流:使用EXPIRE命令,结合INCR操作,可以实现API的限流功能,防止系统被过度访问

案例讲解
背景

一个在线视频平台提供了一个API,用于获取视频的元数据。在高流量事件(如新电影发布)期间,这个API可能会收到大量并发请求,这可能导致后端服务压力过大,甚至崩溃。
在这里插入图片描述

优势
  1. 稳定性保障:通过限流,可以防止系统在高负载下崩溃,确保核心服务的稳定性。
  2. 服务公平性:限流可以保证不同用户和客户端在高并发环境下公平地使用服务。
  3. 防止滥用:限制API的调用频率,可以防止恶意用户或爬虫对服务进行滥用。
解决方案
  1. 请求计数:每次API请求时,使用INCR命令对特定的key进行递增操作。
  2. 设置过期时间:使用EXPIRE命令为计数key设置一个过期时间,过期时间取决于限流的时间窗口(例如1秒)。
  3. 检查请求频率:如果请求计数超过设定的阈值(例如每秒100次),则拒绝新的请求或进行排队。
代码实现
package main

import (
    "context"
    "github.com/go-redis/redis/v8"
    "log"
    "time"
)

var (
    rdb *redis.Client   // 为了表现代码的完整性,这里省略了初始化代码
    ctx context.Context // 为了表现代码的完整性,这里省略了初始化代码
)

// 伪代码:API限流器
func rateLimiter(apiKey string, threshold int, timeWindow int) bool {
    currentCount, err := rdb.Incr(ctx, apiKey).Result()
    if err != nil {
       log.Printf("Error incrementing API key %s: %v", apiKey, err)
       return false
    }

    // 如果当前计数超过阈值,则拒绝请求
    if int(currentCount) > threshold {
       return false
    }

    // 重置计数器的过期时间
    _, err = rdb.Expire(ctx, apiKey, time.Duration(timeWindow)).Result()
    if err != nil {
       log.Printf("Error resetting expire time for API key %s: %v", apiKey, err)
       return false
    }

    return true
}

// 在API处理函数中调用限流器
func handleAPIRequest(apiKey string) {
    if rateLimiter(apiKey, 100, 1) { // 限流阈值设为100,时间窗口为1秒
       // 处理API请求
    } else {
       // 限流,返回错误或提示信息
    }
}

2.5 共享session

在多服务器的Web应用中,用户在不同的服务器上请求时能够保持登录状态,实现会话共享。

案例讲解
背景

考虑一个大型电商平台,它使用多个服务器来处理用户请求以提高可用性和伸缩性。当用户登录后,其会话信息(session)需要在所有服务器间共享,以确保无论用户请求到达哪个服务器,都能识别其登录状态。

优势
  1. 用户体验:用户在任何服务器上都能保持登录状态,无需重复登录。
  2. 系统可靠性:集中管理session减少了因服务器故障导致用户登录状态丢失的风险。
  3. 伸缩性:易于扩展系统以支持更多服务器,session管理不受影响。
解决方案

使用Redis的String类型来集中存储和管理用户session信息。

  • 存储Session:当用户登录成功后,将用户的唯一标识(如session ID)和用户信息序列化后存储在Redis中。
  • 验证Session:每次用户请求时,通过请求中的session ID从Redis获取session信息,验证用户状态。
  • 更新Session:用户活动时,更新Redis中存储的session信息,以保持其活跃状态。
  • 过期策略:设置session信息在Redis中的过期时间,当用户长时间不活动时自动使session失效。
代码实现
package main

import (
    "context"
    "encoding/json"
    "github.com/go-redis/redis/v8"
    "strconv"
    "time"
)

var (
    rdb *redis.Client   // 为了表现代码的完整性,这里省略了初始化代码
    ctx context.Context // 为了表现代码的完整性,这里省略了初始化代码
)

// 伪代码:用户登录并存储session
func userLogin(username string, password string) (string, error) {
    // 验证用户名和密码

    // 创建session ID
    sessionID := generateSessionID()

    // 序列化用户信息
    userInfo := map[string]string{"username": username}
    serializedInfo, err := json.Marshal(userInfo)
    if err != nil {
       // 处理错误
       return "", err
    }

    // 存储session信息到Redis,设置过期时间
    err = rdb.Set(ctx, sessionID, string(serializedInfo), time.Duration(30)*time.Minute).Err()
    if err != nil {
       // 处理错误
       return "", err
    }

    return sessionID, nil
}

// 伪代码:从请求中获取并验证session
func validateSession(sessionID string) (map[string]string, error) {
    // 从Redis获取session信息
    serializedInfo, err := rdb.Get(ctx, sessionID).Result()
    if err != nil {
       // 处理错误或session不存在
       return nil, err
    }

    // 反序列化用户信息
    var userInfo map[string]string
    err = json.Unmarshal([]byte(serializedInfo), &userInfo)
    if err != nil {
       // 处理错误
       return nil, err
    }

    return userInfo, nil
}

// 伪代码:生成新的session ID
func generateSessionID() string {
    return strconv.FormatInt(time.Now().UnixNano(), 36)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2123135.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker突然解封,直接拉取!

文章目录 Docker突然解封,直接拉取!封禁的原因是什么?解封的原因是什么?封禁对开发的影响经验教训 最近开始公众号文章也开始同步更新了,对Java、大数据、人工智能、开发运维相关技术分享,文章对您有用的话…

Linux 8250串口控制器

1 8250串口类型的识别 Intel HW都使用DesignWare 8250: drivers/mfd/intel-lpss-pci.c drivers/tty/serial/8250/8250_dw.c IIR寄存器的高2位bit7、bit6用来识别8250串口的类型: 0 - 8250,无FIFO 0 - 并且存在SCR(Scratch registe…

SQL优化(二)统计信息

收集统计信息 数据库的统计信息非常重要,如果没有正确地收集表的统计信息,或者没有及时地更新表的统计信息,SQL就有可能走错执行计划,也就会出现性能问题。 统计信息主要分为表的统计信息、列的统计信息、索引的统计信息、系统的…

TeamTalk数据库代理服务器

文章目录 main函数主流程关键步骤线程池redis缓存未读消息计数未读消息计数-单聊未读消息计数-群聊 群成员管理 main函数主流程 关键步骤 初始化epoll 线程池数据入口 reactor CProxyConn::HandlePduBuf异步task任务封装,把任务放入线程池;线程池里的…

【AI学习】AI科普:专有名词介绍

这里是阿川的博客,祝您变得更强 ✨ 个人主页:在线OJ的阿川 💖文章专栏:AI入门到进阶 🌏代码仓库: 写在开头 现在您看到的是我的结论或想法,但在这背后凝结了大量的思考、经验和讨论 目录 1.AI序…

TCP通信三次握手、四次挥手

前言 前面我说到了,UDP通信的实现,但我们经常说UDP通信不可靠,是因为他只会接收和发送,并不会去验证对方收到没有,那么我们说TCP通信可靠,就是因为他会进行验证接收端是否能够接收和发送,并且只…

给大家推荐好用的AI网站

地址:https://ai.ashuiai.com/auth/register?inviteCode8E8DIC1QCR 个人觉得挺好用的,可以免费,免费有限制次数,也可以会员升级200永久免费,我用的200永久免费。 可以在国内使用以下ai模型 回答问题更智能&#xff…

计算机毕业设计 校内跑腿业务系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点…

分享6个我喜欢的常用网站,来看看有没有你感兴趣的!

分享6个我自己很喜欢的常用网站,平时工作生活中经常会打开,来看看有没有你感兴趣的! 1.Crazygames crazygames.com/ 一个超赞的在线小游戏平台,上面有超过7000种游戏任你选。不管你喜欢冒险、解谜、闯关,还是装扮、赛…

概要设计例题

答案:A 知识点: 概要设计 设计软件系统的总体结构:采用某种方法,将一个复杂的系统按照功能划分成模块;确定每个模块的功能;确定模块之间的调用关系;确定模块之间的接口,即模块之间…

返工(Rework)与返修(Repair)有何不一样

IATF 16949 汽车业质量管理体系,以客户需求为基础,组织透过相关单位了解客户需求后,向内部流程展开,目的是确保从研发到出货每个环节都能满足客户需求,同时管控制造过程的效率及良率,使产线能够稳定及准时交货给客户。 IATF 16949 条文中,针对「返工(Rework)」与「返修(…

linux工具的使用

1.yum和apt的概念与使用 yum 和 apt 是两种不同的包管理工具,用于在 Linux 系统上管理软件包。 yum (Yellowdog Updater, Modified) 发行版: 用于基于 RPM 的发行版,如 CentOS、RHEL 和 Fedora。基本命令: 更新包列表: sudo yum update安装包: sudo y…

Sky Takeaway

软件开发整体介绍 软件开发流程 角色分工 软件环境 苍穹外卖 项目介绍 定位:专门为餐饮企业定制的一款软件产品 技术选型 前端环境搭建 阅读readme文档 nginx.exe放入无中文目录运行并启动 后端环境搭建 项目结构 Nginx反向代理 优点 配置 Nginx反向代理 负…

QXlsx编译静态库-配置为Qt模块

Qt读写Excel–QXlsx编译为静态库-配置为Qt模块🍆 文章目录 Qt读写Excel--QXlsx编译为静态库-配置为Qt模块🍆[toc]1、概述🥔2、准备工作🥕3、配置环境🌽4、加载QXlsx静态库🥒 👉QXlsx使用&#x…

《深度学习》OpenCV 高阶 图像金字塔 用法解析及案例实现

目录 一、图像金字塔 1、什么是图像金字塔 2、图像金字塔作用 1)金字塔尺度间的图像信息补充 2)目标检测与识别 3)图像融合与拼接 4)图像增强与去噪 5)图像压缩与编码 二、用法解析 1、向下采样 1)概念…

【C++11 ——— 可变参数模板】

C11 ——— 可变参数模板 可变参数模板的概念可变参数模板的定义方式参数包的展开递归式展开参数包逗号表达式展开参数包 emplaceemplace 的使用emplace 的优势 可变参数模板的概念 在C11之前,函数模板和类模板中的模板参数数量是固定的。可变参数模板打破了这个限制,提供了一…

Visual Studio汇编代码高亮与自动补全

高亮插件:AsmDude (可以按照我的颜色进行设置,或者你自己改) 代码自动补全:CodeGeex (功能很多,支持的语言很多),按Tab补全

Gitea Action注册runner

我的是gitea也可以和github 兼容,只是没有github 那么靓而已 安装一个gitea仓库 docker run -d --name gitea \-p3000:3000 -p2222:22 \-v /git/data:/data \ -v /etc/timezone:/etc/timezone:ro \-v /etc/localtime:/etc/localtime:ro \gitea/gitea:1.21.1setti…

嵌入式实时操作系统(RTOS):原理、应用与发展

摘要:本文围绕嵌入式实时操作系统(RTOS)展开。首先介绍嵌入式系统与实时操作系统的概念,阐述嵌入式 RTOS 的体系结构。接着分析其关键特性,包含任务管理(如任务的创建与删除、调度、同步与通信)…

基于SSM架构的农产品朔源系统

项目描述 这是一款基于SSM架构的农产品朔源系统 模块描述 农产品溯源系统 1、农产品管理 农产品列表 新增农产品 2、二维码管理 二维码列表 3、溯源管理 溯源列表 溯源图表 4、 企业管理 设置 添加企业 截图