• Redis之golang编程实战


    Redis 介绍

    官网:Redis - The Real-time Data Platform

    Redis 可作为数据库、缓存、流引擎和消息代理的开源内存数据存储。被用在不计其数的应用中。Redis 连续 5 年被评为最受欢迎的数据库,是开发人员、架构师和开源贡献者参与社区的中心。

    Redis 是一个开源(BSD 许可)的内存数据结构存储,用作数据库、缓存、消息代理和流引擎。 Redis 提供数据结构,例如字符串、散列、列表、集合、具有范围查询的排序集合、位图、Hyper日志、地理空间索引和流。 Redis 具有内置复制、Lua 脚本、LRU 驱逐、事务和不同级别的磁盘持久性,并通过 Redis Sentinel 和 Redis Cluster 自动分区提供高可用性。

    您可以对这些类型运行原子操作,例如附加到字符串;增加哈希值;将元素推送到列表中;计算集交、并、差;或获取排序集中排名最高的成员。 为了达到最佳性能,Redis 使用内存数据集。根据您的用例,Redis 可以通过定期将数据集转储到磁盘或将每个命令附加到基于磁盘的日志来持久化您的数据。如果您只需要一个功能丰富的网络内存缓存,您也可以禁用持久性。

    Redis 支持异步复制,具有快速非阻塞同步和自动重新连接以及网络拆分上的部分重新同步。

    Redis 还包括:

    • 事务

    • 发布/订阅

    • Lua 脚本

    • 有限生命周期的 Key

    • LRU 策略的 Key 清理

    • 自动故障转移

    Redis 支持多种语言客户端,支持几乎全部的主流语言。参见:Connect with Redis clients | Docs

    Redis 适合的典型业务逻辑场景:

    • 热数据,临时缓存

    • 计数、统计相关,布隆过滤

    • 排行榜类

    • 分布式锁,秒杀

    • 队列,流数据处理

    Go 操作 Redis 的客户端

    常用包

    有很多开源好用的 Go 操作 Redis 的包,列举Gihub上Star超过500的包如下:

    选择 GitHub - redis/go-redis: Redis Go client 包,完成 Redis 操作,go-redis 的主要特点是:

    • 默认支持连接池

    • 类型安全

    • 内置 Cluster、Sentinel、Ring 等多种类型客户端

    • 支持 OpenTelemetry 指标数据统计

    • 用基数大

    • 还可以作为 kvrocks 的客户端使用

    Golang Redis client

    redis package - github.com/go-redis/redis/v9 - Go Packages

    安装 go-redis

    go-redis/redis 包有不同版本,其中:

    如果使用 Redis6,则:

     go get github.com/go-redis/redis/v8

    如果使用 Redis 7(Redis7于2022 Apr 27 发布),则:

     go get github.com/go-redis/redis/v9

    如果不能确定Redis版本,使用命令 redis-server --version 或客户端登录后,使用名 info server 可以查询:

     # docker exec -it redis-dev redis-server --version
     Redis server v=7.0.5 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=a507a0b937977997
     127.0.0.1:6379>info server
     # Server
     redis_version:7.0.5
     ​

    连接单点模式服务

    单点模式服务指的是使用单一的redis服务器实现Redis服务,最简单的一种模式。

    启动单点模式服务

    启动,Docker 方式:

     $ docker run --rm --name redis-single -p 6379:6379 -d \
         redis redis-server --requirepass yourPassword
     ​

    连接(使用连接池)

    经典的方式是通过地址建立连接:

     import "github.com/go-redis/redis/v9"
     ​
     rdb := redis.NewClient(&redis.Options{
             Addr:        "192.168.157.135:6379",
             Username:    "default",       // default user
             Password:    "some-pass",     // no password set
             DB:          0,               // use default DB
             DialTimeout: 1 * time.Second, // 1 second
         })
     ​

    另一种方式是基于连接字符串建立连接:

     import "github.com/go-redis/redis/v9"
     ​
     opt, err := redis.ParseURL("redis://default:some-pass@192.168.157.135:6379/0?dial_timeout=1")
         if err != nil {
             panic(err)
         }
     ​
     rdb := redis.NewClient(opt)

    常用选项:

     Network string // 网络类型 Default is tcp
     Addr string // 地址 host:port
     Username string // ACL 用户名
     Password string // ACL 密码
     DB int // 所选数据库
     DialTimeout time.Duration // 拨号连接超时时间,default 5 seconds
     ReadTimeout time.Duration // 读操作超时时间
     WriteTimeout time.Duration // 写操作超时时间
     PoolTimeout time.Duration // 连接池等待超时时间
     PoolSize int // 连接池大小

    注意,NewClient() 操作,不会立即对 Redis 服务器建立连接,仅用来配置连接选项。只有当对 Redis 服务器发出第一次操作时,才会建立连接。例如,rdb.Ping() 可用来测试服务器的连接情况:

     status := rdb.Ping(context.Background())
     fmt.Println(status.Result())

    go-redis 采用连接池方案管理连接,做到连接复用。

    连接集群模式服务

    连接哨兵模式服务

    连接池

    单连接

    使用 TLS

    通过 SSH

    执行命令

    执行 Redis 命令,需要选择特定命令,同时传递 context 对象。

    Context

    redis.Nil

    redis.Nil 是 go-redis 定义的特定错误,用于表示获取的 Key 不存在。通常获取类的命令都会使用 redis.Nil,例如 GET、BLPOP、ZSCORE 等。

    const Nil = RedisError("redis: nil") // nolint:errname

    之所以需要定义 redis.Nil,是因为 go 在获取结果时,不容易区分是空字符串“”还是 Key 不存在,例如:

    val, _ := client.Get(ctx, "someKey").Result()
    fmt.Printf("%#v", val) // ""

    在 someKey 不存在的情况下,value 的值是 "",也就是如果不去判断错误,不能确定是 someKey 不存在或者是 someKey 的值本就是“”。

    因此,当需要确定 key 是否存在时,通常的逻辑如下:

    val, err := client.Get(ctx, "someKey").Result()
    switch {
        case err == redis.Nil:
        fmt.Println("key does not exist")
        case err != nil:
        fmt.Println("Get failed", err)
        case val == "":
        fmt.Println("value is empty")
    }

    String字符串操作

    String 介绍

    Redis字符串存储字节序列,包括文本、序列化对象和二进制数组。因此,字符串是最基本的Redis数据类型。它们通常用于缓存,但它们支持额外的功能,允许您实现计数器并执行按位操作。

    默认情况下,string 的最大尺寸为:512MB。

    大多数的 string 操作时间复杂度为:O(1)。

    但 SUBSTR, GETRANGE, SETRANGE 命令的时间复杂度为O(n)。

    操作方法说明

    设置

    // 设置
    func (c Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
    // 设置,使用参数配置。Mode(`NX` or `XX` or empty),TTL,ExpireAt,Get,KeepTTL
    func (c Client) SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd
    
    // 设置,同时设置有效期
    func (c Client) SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
    // 设置,前提是Key不存在
    func (c Client) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd
    // 设置,前提是Key存在
    func (c Client) SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd
    // 注意:SetEx,SetNx,SetXX 的功能可以由 Set、SetArgs完成,因此建议以使用 Set、SetArgs 为主
    
    // 设置多个
    func (c Client) MSet(ctx context.Context, values ...interface{}) *StatusCmd
    // 设置多个,前提是全部的key都要不存在
    func (c Client) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd
    

    其中:

    • NX 表示Not Exists,不存在时才执行

    • XX 表示 Exists,存在时才执行

    获取

    // 获取
    func (c Client) Get(ctx context.Context, key string) *StringCmd
    // 获取并删除
    func (c Client) GetDel(ctx context.Context, key string) *StringCmd
    // 获取并设置有效期
    func (c Client) GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd
    
    // 获取多个
    func (c Client) MGet(ctx context.Context, keys ...string) *SliceCmd
    
    // 获取字符串长度
    func (c Client) StrLen(ctx context.Context, key string) *IntCmd

    追加和递增递减

    // 追加字符串
    func (c Client) Append(ctx context.Context, key, value string) *IntCmd
    
    // 递减,字符串作为 64位整数存储
    func (c Client) Decr(ctx context.Context, key string) *IntCmd
    func (c Client) DecrBy(ctx context.Context, key string, decrement int64) *IntCmd
    
    // 递增
    func (c Client) Incr(ctx context.Context, key string) *IntCmd
    func (c Client) IncrBy(ctx context.Context, key string, value int64) *IntCmd
    func (c Client) IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd

    其中:

    • value 被认为是64bit的整数

    子串操作

    // 设置特定字符串的特定索引内容
    func (c Client) SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd
    // 获取特定范围的字符串(substr)
    func (c Client) GetRange(ctx context.Context, key string, start, end int64) *StringCmd

    其中:

    • start,end,是索引,都是闭区间

    • 负值表示从后计数第几个,-1表示倒数第一个字节

    • 不会越界

    Do

    LCS key1 key2 [LEN] [IDX] [MINMATCHLEN len] [WITHMATCHLEN]

    Bitmap位图操作

    Bitmap 介绍

    image.png

    Redis位图是字符串数据类型的扩展,可以将字符串视为位向量。您还可以对一个或多个字符串执行按位操作。位图用例的一些示例包括:

    • 集合的成员对应于整数0-N的情况下的有效集合表示。

    • 对象权限,其中每个位代表一个特定的权限,类似于文件系统存储权限的方式。

    限制:

    • 最大支持 2^32 个bit,也就是字符串的限制 512 MB

    性能:

    • SETBIT 和 GETBIT 时间复杂度是 O(1)

    • BITOP 时间复杂度是 O(n)

    操作方法说明

    设置位

    // 设置特定位
    func (c Client) SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd
    

    获取位

    // 获取特定位
    func (c Client) GetBit(ctx context.Context, key string, offset int64) *IntCmd
    // 统计位数量
    func (c Client) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd
    // 获取特定位的位置
    func (c Client) BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
    

    字符串操作位图

    // 设置
    func (c Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
    // 获取
    func (c Client) Get(ctx context.Context, key string) *StringCmd

    字符串:

    • "\xHH" 支持16进制编码字符转义

    • 支持常规字符串,ASCII字符

    位运算

    // 位与
    func (c Client) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd
    // 位非
    func (c Client) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
    // 位或
    func (c Client) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd
    // 位异或
    func (c Client) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd

    多位复合操作

    // 一次性设置多个位
    func (c Client) BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd

    支持设置、递增、获取特定的位。同时支持整数的有无符号和宽度编码。

    通用操作

    通用操作指的是绝大多数数据类型都支持的操作,例如拷贝、设置有效期、删除、判定是否存在等。

    常用

    // 拷贝
    func (c Client) Copy(ctx context.Context, sourceKey, destKey string, db int, replace bool) *IntCmd
    // 删除
    func (c Client) Del(ctx context.Context, keys ...string) *IntCmd
    // 异步删除
    func (c Client) Unlink(ctx context.Context, keys ...string) *IntCmd
    // 判定是否存在
    func (c Client) Exists(ctx context.Context, keys ...string) *IntCmd
    // 重命名
    func (c Client) Rename(ctx context.Context, key, newkey string) *StatusCmd
    // 获取数据类型
    func (c Client) Type(ctx context.Context, key string) *StatusCmd
    // 返回值的序列化内容
    func (c Client) Dump(ctx context.Context, key string) *StringCmd
    // 基于串行化的数据重新存储
    func (c Client) Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd
    // 获取全部key
    func (c Client) Keys(ctx context.Context, pattern string) *StringSliceCmd
    // 分片遍历key
    func (c Client) Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd
    // 随机获取key
    func (c Client) RandomKey(ctx context.Context) *StringCmd

    有效期

    // 设置有效期,时间段
    func (c Client) Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd
    // 设置有效期,要求新有效期大于原来有效期
    func (c Client) ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd
    // 设置有效期,要求新有效期小于原来有效期
    func (c Client) ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd
    // 设置有效期,要求key不存在
    func (c Client) ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd
    // 设置有效期,要求key存在
    func (c Client) ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd
    // 设置有效期,时间
    func (c Client) ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd
    // 获取有效期
    func (c Client) TTL(ctx context.Context, key string) *DurationCmd
    // 移除有效期
    func (c Client) Persist(ctx context.Context, key string) *BoolCmd
    
    // P前缀,表示毫秒时间级别 milliseconds
    func (c Client) PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd
    func (c Client) PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd
    func (c Client) PTTL(ctx context.Context, key string) *DurationCmd
    
    // 更新key的访问时间
    func (c Client) Touch(ctx context.Context, keys ...string) *IntCmd

    排序

    // 排序
    func (c Client) Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd
    func (c Client) SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd
    func (c Client) SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd

    内部信息

    // 获取对象内部编码
    func (c Client) ObjectEncoding(ctx context.Context, key string) *StringCmd
    // 获取对象的空闲时间
    func (c Client) ObjectIdleTime(ctx context.Context, key string) *DurationCmd
    // 获取对象的引用计数
    func (c Client) ObjectRefCount(ctx context.Context, key string) *IntCmd

    转移

    // 转移到不同库
    func (c Client) Move(ctx context.Context, key string, db int) *BoolCmd
    // 转移到不同服务器
    func (c Client) Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd

    Do

    OBJECT FREQ key

    SCAN, SSCAN, HSCAN, ZSCAN 命令

    Redis 的 SCAN 命令及其相关命令 SSCAN, HSCAN ZSCAN 命令都是用于增量遍历集合中的元素。

    • SCAN 命令用于迭代当前数据库中的数据库键

    • SSCAN 命令用于迭代集合键中的元素

    • HSCAN 命令用于迭代哈希键中的键值对

    • ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)

    List列表操作

    List 介绍

    List 是字符串值的链表。List 经常用于:

    • 实现堆栈stacks 和队列queues。

    • 通过索引检索元素

    List 允许的最大长度(元素个数):2^32 -1。

    List 支持:

    • 通过索引操作元素

    • 在头部和尾部插入和取出元素

    • 在特定索引位置插入元素

    • 支持特定命令的阻塞操作,命令通常以B(Block)开头

    性能:

    • 通常的时间复杂度为O(n),例如 LINDEX

    • 头部和尾部元素操作为O(1),例如 LPUSH

    因此,若使用大 List,尽量使用头部和尾部操作为主的设计。

    操作方法说明

    添加元素

    // 在头部插入元素
    func (c Client) LPush(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 在头部插入元素,前提是 key 对应的 List 已经存在
    func (c Client) LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 在尾部插入元素
    func (c Client) RPush(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 在尾部插入元素,前提是 key 对应的 List 已经存在
    func (c Client) RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd

    取出元素

    // 弹出头部的第一个元素
    func (c Client) LPop(ctx context.Context, key string) *StringCmd
    // 弹出头部的特定数量的元素
    func (c Client) LPopCount(ctx context.Context, key string, count int) *StringSliceCmd
    // 弹出尾部的第一个元素
    func (c Client) RPop(ctx context.Context, key string) *StringCmd
    // 弹出尾部的特定数量的元素
    func (c Client) RPopCount(ctx context.Context, key string, count int) *StringSliceCmd
    

    删除元素

    // 删除特定数量的特定元素
    func (c Client) LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd

    获取长度

    // 获取长度
    func (c Client) LLen(ctx context.Context, key string) *IntCmd

    基于索引操作

    // 利用索引获取元素
    func (c Client) LIndex(ctx context.Context, key string, index int64) *StringCmd
    // 设置特定索引对应的元素
    func (c Client) LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd
    // 在某个元素前或后插入元素
    func (c Client) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
    // 在某个元素后插入
    func (c Client) LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd
    // 在某个元素前插入
    func (c Client) LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd
    // 获取匹配元素的一个索引
    func (c Client) LPos(ctx context.Context, key string, value string, a LPosArgs) *IntCmd
    // 获取匹配元素的多个索引
    func (c Client) LPosCount(ctx context.Context, key string, value string, count int64, a LPosArgs) *IntSliceCmd
    
    // 基于索引裁剪 List
    func (c Client) LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd

    操作部分元素

    // 获取指定范围的元素
    func (c Client) LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd

    多队列间操作

    // 移动元素
    func (c Client) LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd
    // 从 source 弹出,然后插入到 destination 中
    func (c Client) RPopLPush(ctx context.Context, source, destination string) *StringCmd

    阻塞操作

    // 支持阻塞的 LMove,当 source 为空时,会阻塞连接,直到其他客户端插入元素或超时
    func (c Client) BLMove(ctx context.Context, source, destination, srcpos, destpos string, ...) *StringCmd
    // 支持阻塞的 LPop
    func (c Client) BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
    // 支持阻塞的 RPop
    func (c Client) BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
    // 支持阻塞的 RPopLPush
    func (c Client) BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd

    操作方法说明

    添加元素

    // 在头部插入元素
    func (c Client) LPush(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 在头部插入元素,前提是 key 对应的 List 已经存在
    func (c Client) LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 在尾部插入元素
    func (c Client) RPush(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 在尾部插入元素,前提是 key 对应的 List 已经存在
    func (c Client) RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd

    获取长度

    // 获取长度
    func (c Client) LLen(ctx context.Context, key string) *IntCmd

    取出元素

    // 弹出头部的第一个元素
    func (c Client) LPop(ctx context.Context, key string) *StringCmd
    // 弹出头部的特定数量的元素
    func (c Client) LPopCount(ctx context.Context, key string, count int) *StringSliceCmd
    // 弹出尾部的第一个元素
    func (c Client) RPop(ctx context.Context, key string) *StringCmd
    // 弹出尾部的特定数量的元素
    func (c Client) RPopCount(ctx context.Context, key string, count int) *StringSliceCmd
    // 删除特定数量的特定元素
    func (c Client) LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd

    基于索引操作

    // 利用索引获取元素
    func (c Client) LIndex(ctx context.Context, key string, index int64) *StringCmd
    // 设置特定索引对应的元素
    func (c Client) LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd
    // 在某个元素前或后插入元素
    func (c Client) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
    // 在某个元素后插入
    func (c Client) LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd
    // 在某个元素前插入
    func (c Client) LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd
    // 获取匹配元素的一个索引
    func (c Client) LPos(ctx context.Context, key string, value string, a LPosArgs) *IntCmd
    // 获取匹配元素的多个索引
    func (c Client) LPosCount(ctx context.Context, key string, value string, count int64, a LPosArgs) *IntSliceCmd
    
    // 基于索引裁剪 List
    func (c Client) LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd

    操作部分元素

    // 获取指定范围的元素
    func (c Client) LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd

    多队列间操作

    // 移动元素
    func (c Client) LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd

    阻塞操作

    // 支持阻塞的 LMove,当 source 为空时,会阻塞连接,直到其他客户端插入元素或超时
    func (c Client) BLMove(ctx context.Context, source, destination, srcpos, destpos string, ...) *StringCmd
    // 支持阻塞的 LPop
    func (c Client) BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
    // 支持阻塞的 RPop
    func (c Client) BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
    // 支持阻塞的 RPopLPush
    func (c Client) BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd

    Set集合操作

    Set 介绍

    image.png

    Redis 集是唯一字符串(成员)的无序集合。您可以使用 Redis 集高效地:

    • 跟踪唯一项目(例如,跟踪访问给定博客文章的所有唯一 IP 地址)。

    • 表示关系(例如,具有给定角色的所有用户的集合)。

    • 执行常见的集合运算,例如交集、并集和差集。

    Set 的最大成员数量为:2^32-1。

    性能:

    大多数的操作时间复杂度为 O(1)。

    但获取全部成员 SMEMBERS 类的操作时间复杂度为O(n),因此使用时要格外注意,如果可以,SSCAN 也是一个选择。

    操作方法说明

    添加成员

    // 添加成员
    func (c Client) SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd
    

    获取成员

    // 随机获取成员
    func (c Client) SRandMember(ctx context.Context, key string) *StringCmd
    // 随机获取N个成员
    func (c Client) SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd
    // 随机获取并删除成员
    func (c Client) SPop(ctx context.Context, key string) *StringCmd
    // 随机获取并删除N个成员
    func (c Client) SPopN(ctx context.Context, key string, count int64) *StringSliceCmd
    
    // 获取全部成员
    func (c Client) SMembers(ctx context.Context, key string) *StringSliceCmd
    // 获取全部成员,返回 Map 结构
    func (c Client) SMembersMap(ctx context.Context, key string) *StringStructMapCmd
    
    // 分片获取成员
    func (c Client) SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd

    删除成员

    // 删除一个或多个成员
    func (c Client) SRem(ctx context.Context, key string, members ...interface{}) *IntCmd

    统计

    // 统计成员数量
    func (c Client) SCard(ctx context.Context, key string) *IntCmd
    // 判定成员是否存在
    func (c Client) SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd
    // 判定给出的每个成员是否存在
    func (c Client) SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd

    移动成员

    // 将成员从一个集合移动到另一个集合
    func (c Client) SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd

    集合运算

    // 差集
    func (c Client) SDiff(ctx context.Context, keys ...string) *StringSliceCmd
    // 计算差集并存储结果
    func (c Client) SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd
    // 交集
    func (c Client) SInter(ctx context.Context, keys ...string) *StringSliceCmd
    // 计算交集并存储结果
    func (c Client) SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd
    // 并集
    func (c Client) SUnion(ctx context.Context, keys ...string) *StringSliceCmd
    // 计算并集并存储结果
    func (c Client) SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd

    image.png

    Sorted Set排序集合操作

    Sorted Set 介绍

    image.png

    Redis 排序集是由相关分数排序的唯一字符串(成员)的集合。当多个字符串具有相同的分数时,这些字符串按字典顺序排列。排序集的一些用例包括:

    • 排行榜。例如,您可以使用排序集轻松维护大型在线游戏中最高分的有序列表。

    • 速率限制器。特别是,您可以使用排序集来构建滑动窗口速率限制器,以防止过多的 API 请求。

    性能:

    大多数的命令时间复杂度为:O(long(n))。若执行 ZRANGE 命令,获取 m 个元素时,时间复杂度为:O(log(n) + m)。

    操作方法说明

    设置成员

    // 添加成员
    func (c Client) ZAdd(ctx context.Context, key string, members ...*Z) *IntCmd
    // 添加成员,指定选项,支持:NX,XX,LT, GT, Ch, Members 选项,LT,GT,NX 为互斥选项
    func (c Client) ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd
    // 递增成员的分值
    func (c Client) ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd
    // 添加成员,指定选项,支持:NX,XX,LT, GT, Ch, Members 选项,做递增操作
    func (c Client) ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd
    
    // 添加成员,返回被修改(添加的和更新的)的成员数量
    func (c Client) ZAddCh(ctx context.Context, key string, members ...*Z) *IntCmd
    // 添加成员,仅添加新成员,不更新成员
    func (c Client) ZAddNX(ctx context.Context, key string, members ...*Z) *IntCmd
    // 添加成员,仅添加新成员,不更新成员,返回添加的成员数量
    func (c Client) ZAddNXCh(ctx context.Context, key string, members ...*Z) *IntCmd
    // 添加成员,仅更新成员,不添加新成员
    func (c Client) ZAddXX(ctx context.Context, key string, members ...*Z) *IntCmd
    // 添加成员,仅更新成员,不添加新成员,返回更新的成员数量
    func (c Client) ZAddXXCh(ctx context.Context, key string, members ...*Z) *IntCmd
    
    // `ZADD key INCR score member`, 将弃用
    func (c Client) ZIncr(ctx context.Context, key string, member *Z) *FloatCmd
    // `ZADD key NX INCR score member`, 将弃用
    func (c Client) ZIncrNX(ctx context.Context, key string, member *Z) *FloatCmd
    // `ZADD key XX INCR score member`, 将弃用
    func (c Client) ZIncrXX(ctx context.Context, key string, member *Z) *FloatCmd

    获取成员

    // 获取给定的成员分值
    func (c Client) ZScore(ctx context.Context, key, member string) *FloatCmd
    // 获取给定的成员(多个)分值
    func (c Client) ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd
    
    // 随机获取 count 个成员
    func (c Client) ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd
    // 随机获取 count 个成员,返回带有分值
    func (c Client) ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd

    Range 操作

    // 获取特定范围的成员
    func (c Client) ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
    // 获取特定范围的成员,利用参数获取,参数:Key,Start,Stop,ByScore,ByLex,Rev,Offset,Count
    func (c Client) ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd
    
    // 获取特定范围的成员,返回成员及分数
    func (c Client) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd
    // 获取特定范围的成员,利用参数获取,返回成员及分数
    func (c Client) ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd
    
    // 获取特定范围的成员并存储
    func (c Client) ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd
    
    // 获取特定范围的成员,利用参数获取,ByLex = true
    func (c Client) ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
    // 获取特定范围的成员,利用参数获取,ByScore = true
    func (c Client) ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
    // 获取特定范围的成员,利用参数获取,ByScore = true,返回成员及分数
    func (c Client) ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd
    // 以下Rev的都是对应方法的倒序版
    func (c Client) ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
    func (c Client) ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
    func (c Client) ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
    func (c Client) ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd
    func (c Client) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd
    
    // 分片遍历全部成员
    func (c Client) ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd

    Range 支持不同的查询类型

    • Rank,索引顺序

    • Score,分值

    • Lex,成员词法顺序

    不同的查询类型,start 和 stop 表示的含义如下:

    分值范围:

    • 默认为闭区间: start, stop 表示 [start, stop]

    • 可是设置任意一端为开区间,例如:(start, (stop 表示 (start, stop)

    • -inf, +inf 表示负无穷到正无穷

    索引范围:

    • 默认为闭区间索引,例如:1, 2 表示 [1, 2]

    • 负数索引表示从集合的后边计数索引,例如 -1 表示最后一个元素

    • 索引越界不会触发错误

    词法范围:

    • +, - 表示词法的正负无穷

    • 有效的词法语法要以 ( 或 [ 开头,表示开或闭区间

    删除成员

    // 删除一个或多个成员
    func (c Client) ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd
    // 删除特定词法范围的成员
    func (c Client) ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd
    // 删除特定排名范围的成员
    func (c Client) ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd
    // 删除特定分值范围的成员
    func (c Client) ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd

    Pop 操作

    // 获取并删除分值最高的 count 个成员
    func (c Client) ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd
    // 获取并删除分值最低的 count 个成员
    func (c Client) ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd

    统计

    // 统计成员数量
    func (c Client) ZCard(ctx context.Context, key string) *IntCmd
    // 统计分值在特定区间内的成员数量
    func (c Client) ZCount(ctx context.Context, key, min, max string) *IntCmd
    // 统计成员在某个词法区间的数量
    func (c Client) ZLexCount(ctx context.Context, key, min, max string) *IntCmd
    // 统计成员的索引位置
    func (c Client) ZRank(ctx context.Context, key, member string) *IntCmd
    // ZRank 的逆序版
    func (c Client) ZRevRank(ctx context.Context, key, member string) *IntCmd

    集合运算

    // 差集,返回成员列表
    func (c Client) ZDiff(ctx context.Context, keys ...string) *StringSliceCmd
    // 差集,返回成员及分数列表
    func (c Client) ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd
    // 差集并存储
    func (c Client) ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd
    
    // 交集,返回成员列表
    func (c Client) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd
    // 交集,返回成员及分数列表
    func (c Client) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd
    // 交集并存储
    func (c Client) ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd
    
    // 并集,返回成员列表
    func (c Client) ZUnion(ctx context.Context, store ZStore) *StringSliceCmd
    // 并集并存储
    func (c Client) ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd
    // 并集,返回成员及分数列表
    func (c Client) ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd
    

    支持阻塞

    //  BPopMax 的阻塞版,阻塞到其中一个pop成功
    func (c Client) BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd
    //  BPopMin 的阻塞版
    func (c Client) BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd

    Hash哈希操作

    Hash 介绍

    Redis Hash是作为字段值对的集合构造的记录类型。您可以使用哈希来表示基本对象和存储计数器分组等。

    限制:

    每个Hash的键值对存储上限为:2^32-1。

    性能:

    大多数的Hash操作的时间复杂度为:O(1)。

    部分命令,HKEYS, HVALS, 和 HGETALL 的时间复杂度为:O(n)

    Client 支持的 Hash 的命令

    // 删除字段,支持多个
    func (c Client) HDel(ctx context.Context, key string, fields ...string) *IntCmd
    // 判断某个字段是否存在
    func (c Client) HExists(ctx context.Context, key, field string) *BoolCmd
    // 获取特定字段值
    func (c Client) HGet(ctx context.Context, key, field string) *StringCmd
    // 获取全部字段及值
    func (c Client) HGetAll(ctx context.Context, key string) *MapStringStringCmd
    // 字段递增
    func (c Client) HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd
    // 字段递增,浮点数
    func (c Client) HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
    // 获取全部字段
    func (c Client) HKeys(ctx context.Context, key string) *StringSliceCmd
    // 获取Hash长度
    func (c Client) HLen(ctx context.Context, key string) *IntCmd
    // 获取多个字段值
    func (c Client) HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
    // 设置字段值,支持多个(建议使用 HSet 代替,该命令已过期)
    func (c Client) HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
    // 获取随机字段,支持多个
    func (c Client) HRandField(ctx context.Context, key string, count int) *StringSliceCmd
    // 获取随机字段及值,支持多个
    func (c Client) HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd
    // 遍历字段及值
    func (c Client) HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
    // 设置字段值,支持多个
    func (c Client) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
    // 设置特定字段值,前提是该字段不存在
    func (c Client) HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
    // 获取全部字段值
    func (c Client) HVals(ctx context.Context, key string) *StringSliceCmd

    不支持的命令:

    # 获取特定字段长度
    HSTRLEN key field
    
    func (c *Client) Do(ctx context.Context, "HTRRLEN", "key", "field") *Cmd

    示例:

    fmt.Println(
        client.HSet(ctx, "students", "42001", "马士兵", "42002", "GoLang", "42003", "Redis").
        	Result()) // 3 
    
    fmt.Println(
        client.HGetAll(ctx, "students").
       		Result()) // map[42001:马士兵 42002:GoLang 42003:Redis] 
    
    fmt.Println(
        client.HLen(ctx, "students").
        	Result()) // 3 
    
    fmt.Println(
        client.HRandField(ctx, "students", 2).
        	Result()) // [42003 42001] , [42002 42003] 
    fmt.Println(
        client.HRandField(ctx, "students", -2).
        	Result()) // [42003 42001] , [42003 42003] 
    fmt.Println(
        client.HRandFieldWithValues(ctx, "students", 2).
        	Result()) // [{42002 GoLang} {42003 Redis}] 
    
    fmt.Println(
        client.HExists(ctx, "students", "42003").
        	Result()) // true 
    fmt.Println(
        client.HExists(ctx, "students", "42006").
       		Result()) // false 
    
    fmt.Println(
        client.HDel(ctx, "students", "42003").
        	Result()) // 1 
    fmt.Println(
        client.HExists(ctx, "students", "42003").
        	Result()) // true 
    
    fmt.Println(
        client.Do(ctx, "HSTRLEN", "students", "42001").
        	Result()) // 9 
    
    

    HScan 操作用于增量遍历Hash中的字段,与其他集合类型的用法一致,参见 SCAN 命令。

    Bitmap位图操作

    Bitmap 介绍

    Redis位图是字符串数据类型的扩展,可以将字符串视为位向量。您还可以对一个或多个字符串执行按位操作。位图用例的一些示例包括:

    • 集合的成员对应于整数0-N的情况下的有效集合表示。

    • 对象权限,其中每个位代表一个特定的权限,类似于文件系统存储权限的方式。

    性能:

    • SETBIT 和 GETBIT 时间复杂度是 O(1)

    • BITOP 时间复杂度是 O(n)

    操作方法说明

    设置位

    // 设置特定位
    func (c Client) SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd

    获取位

    // 获取特定位
    func (c Client) GetBit(ctx context.Context, key string, offset int64) *IntCmd
    // 统计位数量
    func (c Client) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd
    // 获取特定位的位置
    func (c Client) BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd

    位操作

    // 位与
    func (c Client) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd
    // 位非
    func (c Client) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
    // 位或
    func (c Client) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd
    // 位异或
    func (c Client) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd
    // 
    func (c Client) BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd

    HyperLogLog 草图操作

    HyperLogLog 介绍

    HyperLogLog 是一种用于估计集合基数的数据结构。作为一种概率数据结构,HyperLogLog 以完美的准确性换取高效的空间利用。 Redis HyperLogLog 实现最多使用 12 KB,并提供 0.81% 的标准错误。

    限制:

    草图结构最多可以估计 2^64个成员。

    性能:

    写PFADD和读PFCOUNT操作会在常量时间和空间内完成。

    合并操作时间复杂度:O(n)

    操作方法介绍

    添加成员

    // 添加成员
    func (c Client) PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd

    统计成员数量

    // count
    func (c Client) PFCount(ctx context.Context, keys ...string) *IntCmd

    合并

    // 合并多个 HLL 到一个
    func (c Client) PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd

    Geospatial Indices地理空间索引操作

    Geospatial 说明

    Redis 地理空间索引允许存储坐标并搜索它们。此数据结构对于查找给定半径或边界框内的附近点非常有用。

    地理索引结构是一个排序集合。

    每个地理位置项由:经度、纬度、名称(longitude, latitude, name)组成。

    操作方法说明

    添加地点成员

    // 添加
    func (c Client) GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd

    获取地点成员信息

    // 获取两个地点间的距离
    func (c Client) GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd
    // 获取地点成员,返回Hash值
    func (c Client) GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
    // 获取地点成员的坐标
    func (c Client) GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd

    搜索地点成员

    // 搜索地点
    func (c Client) GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd
    // 搜索地点,同时返回位置、距离、Hash等信息
    func (c Client) GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd
    // 搜索地点并存储
    func (c Client) GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd

    Stream 流操作

    Stream 介绍

    Redis 流是一种数据结构,其作用类似于附加日志。您可以使用流实时记录和同时联合事件。 Redis 流用例示例包括:

    • 事件溯源(例如,跟踪用户操作、点击等)

    • 传感器监控(例如,现场设备的读数)

    • 通知(例如,将每个用户的通知记录存储在单独的流中)

    Redis 为每个流条目生成一个唯一的 ID。您可以使用这些 ID 稍后检索它们的关联条目,或者读取和处理流中的所有后续条目。

    Redis 流支持多种修剪策略(以防止流无限制地增长)和不止一种消费策略(请参阅 XREAD、XREADGROUP 和 XRANGE)。

    操作方法说明

    func (c Client) XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd
    func (c Client) XAdd(ctx context.Context, a *XAddArgs) *StringCmd
    func (c Client) XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd
    func (c Client) XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd
    func (c Client) XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd
    func (c Client) XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd
    func (c Client) XDel(ctx context.Context, stream string, ids ...string) *IntCmd
    func (c Client) XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd
    func (c Client) XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd
    func (c Client) XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd
    func (c Client) XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd
    func (c Client) XGroupDestroy(ctx context.Context, stream, group string) *IntCmd
    func (c Client) XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd
    func (c Client) XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd
    func (c Client) XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd
    func (c Client) XInfoStream(ctx context.Context, key string) *XInfoStreamCmd
    func (c Client) XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd
    func (c Client) XLen(ctx context.Context, stream string) *IntCmd
    func (c Client) XPending(ctx context.Context, stream, group string) *XPendingCmd
    func (c Client) XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd
    func (c Client) XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd
    func (c Client) XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd
    func (c Client) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd
    func (c Client) XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd
    func (c Client) XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd
    func (c Client) XRevRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd
    func (c Client) XRevRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd
    func (c Client) XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd
    func (c Client) XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd
    func (c Client) XTrimMinID(ctx context.Context, key string, minID string) *IntCmd
    func (c Client) XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd

    Transaction 事务操作

    Redis 事务介绍

    Redis 事务允许在单个步骤中执行一组命令,它们以命令 MULTI、EXEC、DISCARD 和 WATCH 为中心。 Redis 事务有两个重要的保证:

    事务中的所有命令都被序列化并按顺序执行。另一个客户端发送的请求永远不会在 Redis 事务执行过程中得到处理。这保证了命令作为单个独立操作执行。

    EXEC 命令触发事务中所有命令的执行,因此如果客户端在调用 EXEC 命令之前在事务上下文中丢失与服务器的连接,则不会执行任何操作,相反,如果调用 EXEC 命令,执行所有操作。使用 append-only file 时,Redis 确保使用单个 write(2) 系统调用将事务写入磁盘。但是,如果 Redis 服务器崩溃或被系统管理员以某种硬方式杀死,则可能只注册了部分操作。 Redis 会在重启时检测到这种情况,并会报错退出。使用 redis-check-aof 工具可以修复将删除部分事务的仅附加文件,以便服务器可以重新启动。

    在交易过程中,可能会遇到两种命令错误:

    • 一个命令可能无法入队,所以在调用EXEC之前可能会出错。例如,该命令可能在语法上是错误的(参数数量错误,命令名称错误,...),或者可能存在一些临界条件,如内存不足条件(如果服务器配置为使用 maxmemory 具有内存限制指示)

    • 调用 EXEC 后命令可能会失败,例如,因为我们对具有错误值的键执行操作(例如对字符串值调用列表操作)

    监控

    深入

    连接池的设计

    执行命令过程

  • 相关阅读:
    docker 第二次学习笔记
    【信创】 JED on 鲲鹏(ARM) 调优步骤与成果 | 京东云技术团队
    HTML5 游戏开发实战 | 推箱子
    搜索&推荐&广告算法岗区别
    Angular 学习 之 Hello World !
    软件测试 | 测试工程师都能看懂的redis,进阶测试开发工程师......
    FutureTask和CompletableFuture的模拟使用
    [ROS2系列] ORBBEC(奥比中光)AstraPro相机在ROS2进行rtabmap 3D建图
    Mysql-怎么添加用户和设置权限?
    数据分享|基于Python、Hadoop零售交易数据的Spark数据处理与Echarts可视化分析
  • 原文地址:https://blog.csdn.net/weixin_51568389/article/details/141037732