Redis介绍

Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外,通过复制、持久化和客户端分片等特性,我们可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。

Redis支持的数据结构

Redis支持诸如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、带范围查询的排序集合(sorted sets)、位图(bitmaps)、hyperloglogs、带半径查询和流的地理空间索引等数据结构(geospatial indexes)。

Redis应用场景

  • 缓存系统,减轻主数据库(MySQL)的压力。
  • 计数场景,比如微博、抖音中的关注数和粉丝数。
  • 热门排行榜,需要排序的场景特别适合使用ZSET。
  • 利用LIST可以实现队列的功能。

准备Redis环境

这里直接使用Docker启动一个redis环境,方便学习使用。

docker启动一个名为redis507的5.0.7版本的redis server示例:

  1. docker run --name redis507 -p 6379:6379 -d redis:5.0.7

注意:此处的版本、容器名和端口号请根据自己需要设置。

启动一个redis-cli连接上面的redis server:

  1. docker run -it --network host --rm redis:5.0.7 redis-cli

go-redis库

安装

区别于另一个比较常用的Go语言redis client库:redigo,我们这里采用https://github.com/go-redis/redis连接Redis数据库并进行操作,因为go-redis支持连接哨兵及集群模式的Redis。

使用以下命令下载并安装:

  1. go get -u github.com/go-redis/redis

连接

普通连接

  1. // 声明一个全局的rdb变量
  2. var rdb *redis.Client
  3. // 初始化连接
  4. func initClient() (err error) {
  5. rdb = redis.NewClient(&redis.Options{
  6. Addr: "localhost:6379",
  7. Password: "", // no password set
  8. DB: 0, // use default DB
  9. })
  10. _, err = rdb.Ping().Result()
  11. if err != nil {
  12. return err
  13. }
  14. return nil
  15. }

V8新版本相关

最新版本的go-redis库的相关命令都需要传递context.Context参数,例如:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/go-redis/redis/v8" // 注意导入的是新版本
  7. )
  8. var (
  9. rdb *redis.Client
  10. )
  11. // 初始化连接
  12. func initClient() (err error) {
  13. rdb = redis.NewClient(&redis.Options{
  14. Addr: "localhost:16379",
  15. Password: "", // no password set
  16. DB: 0, // use default DB
  17. PoolSize: 100, // 连接池大小
  18. })
  19. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  20. defer cancel()
  21. _, err = rdb.Ping(ctx).Result()
  22. return err
  23. }
  24. func V8Example() {
  25. ctx := context.Background()
  26. if err := initClient(); err != nil {
  27. return
  28. }
  29. err := rdb.Set(ctx, "key", "value", 0).Err()
  30. if err != nil {
  31. panic(err)
  32. }
  33. val, err := rdb.Get(ctx, "key").Result()
  34. if err != nil {
  35. panic(err)
  36. }
  37. fmt.Println("key", val)
  38. val2, err := rdb.Get(ctx, "key2").Result()
  39. if err == redis.Nil {
  40. fmt.Println("key2 does not exist")
  41. } else if err != nil {
  42. panic(err)
  43. } else {
  44. fmt.Println("key2", val2)
  45. }
  46. // Output: key value
  47. // key2 does not exist
  48. }

连接Redis哨兵模式

  1. func initClient()(err error){
  2. rdb := redis.NewFailoverClient(&redis.FailoverOptions{
  3. MasterName: "master",
  4. SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxx.xxx.xxx.xxx:26379"},
  5. })
  6. _, err = rdb.Ping().Result()
  7. if err != nil {
  8. return err
  9. }
  10. return nil
  11. }

连接Redis集群

  1. func initClient()(err error){
  2. rdb := redis.NewClusterClient(&redis.ClusterOptions{
  3. Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
  4. })
  5. _, err = rdb.Ping().Result()
  6. if err != nil {
  7. return err
  8. }
  9. return nil
  10. }

基本使用

set/get示例

  1. func redisExample() {
  2. err := rdb.Set("score", 100, 0).Err()
  3. if err != nil {
  4. fmt.Printf("set score failed, err:%v\n", err)
  5. return
  6. }
  7. val, err := rdb.Get("score").Result()
  8. if err != nil {
  9. fmt.Printf("get score failed, err:%v\n", err)
  10. return
  11. }
  12. fmt.Println("score", val)
  13. val2, err := rdb.Get("name").Result()
  14. if err == redis.Nil {
  15. fmt.Println("name does not exist")
  16. } else if err != nil {
  17. fmt.Printf("get name failed, err:%v\n", err)
  18. return
  19. } else {
  20. fmt.Println("name", val2)
  21. }
  22. }

zset示例

  1. func redisExample2() {
  2. zsetKey := "language_rank"
  3. languages := []redis.Z{
  4. redis.Z{Score: 90.0, Member: "Golang"},
  5. redis.Z{Score: 98.0, Member: "Java"},
  6. redis.Z{Score: 95.0, Member: "Python"},
  7. redis.Z{Score: 97.0, Member: "JavaScript"},
  8. redis.Z{Score: 99.0, Member: "C/C++"},
  9. }
  10. // ZADD
  11. num, err := rdb.ZAdd(zsetKey, languages...).Result()
  12. if err != nil {
  13. fmt.Printf("zadd failed, err:%v\n", err)
  14. return
  15. }
  16. fmt.Printf("zadd %d succ.\n", num)
  17. // 把Golang的分数加10
  18. newScore, err := rdb.ZIncrBy(zsetKey, 10.0, "Golang").Result()
  19. if err != nil {
  20. fmt.Printf("zincrby failed, err:%v\n", err)
  21. return
  22. }
  23. fmt.Printf("Golang's score is %f now.\n", newScore)
  24. // 取分数最高的3个
  25. ret, err := rdb.ZRevRangeWithScores(zsetKey, 0, 2).Result()
  26. if err != nil {
  27. fmt.Printf("zrevrange failed, err:%v\n", err)
  28. return
  29. }
  30. for _, z := range ret {
  31. fmt.Println(z.Member, z.Score)
  32. }
  33. // 取95~100分的
  34. op := redis.ZRangeBy{
  35. Min: "95",
  36. Max: "100",
  37. }
  38. ret, err = rdb.ZRangeByScoreWithScores(zsetKey, op).Result()
  39. if err != nil {
  40. fmt.Printf("zrangebyscore failed, err:%v\n", err)
  41. return
  42. }
  43. for _, z := range ret {
  44. fmt.Println(z.Member, z.Score)
  45. }
  46. }

输出结果如下:

  1. $ ./06redis_demo
  2. zadd 0 succ.
  3. Golang's score is 100.000000 now.
  4. Golang 100
  5. C/C++ 99
  6. Java 98
  7. JavaScript 97
  8. Java 98
  9. C/C++ 99
  10. Golang 100

根据前缀获取Key

  1. vals, err := rdb.Keys(ctx, "prefix*").Result()

执行自定义命令

  1. res, err := rdb.Do(ctx, "set", "key", "value").Result()

按通配符删除key

当通配符匹配的key的数量不多时,可以使用Keys()得到所有的key在使用Del命令删除。 如果key的数量非常多的时候,我们可以搭配使用Scan命令和Del命令完成删除。

  1. ctx := context.Background()
  2. iter := rdb.Scan(ctx, 0, "prefix*", 0).Iterator()
  3. for iter.Next(ctx) {
  4. err := rdb.Del(ctx, iter.Val()).Err()
  5. if err != nil {
  6. panic(err)
  7. }
  8. }
  9. if err := iter.Err(); err != nil {
  10. panic(err)
  11. }

Pipeline

Pipeline 主要是一种网络优化。它本质上意味着客户端缓冲一堆命令并一次性将它们发送到服务器。这些命令不能保证在事务中执行。这样做的好处是节省了每个命令的网络往返时间(RTT)。

Pipeline 基本示例如下:

  1. pipe := rdb.Pipeline()
  2. incr := pipe.Incr("pipeline_counter")
  3. pipe.Expire("pipeline_counter", time.Hour)
  4. _, err := pipe.Exec()
  5. fmt.Println(incr.Val(), err)

上面的代码相当于将以下两个命令一次发给redis server端执行,与不使用Pipeline相比能减少一次RTT。

  1. INCR pipeline_counter
  2. EXPIRE pipeline_counts 3600

也可以使用Pipelined

  1. var incr *redis.IntCmd
  2. _, err := rdb.Pipelined(func(pipe redis.Pipeliner) error {
  3. incr = pipe.Incr("pipelined_counter")
  4. pipe.Expire("pipelined_counter", time.Hour)
  5. return nil
  6. })
  7. fmt.Println(incr.Val(), err)

在某些场景下,当我们有多条命令要执行时,就可以考虑使用pipeline来优化。

事务

Redis是单线程的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。但是,Multi/exec能够确保在multi/exec两个语句之间的命令之间没有其他客户端正在执行命令。

在这种场景我们需要使用TxPipelineTxPipeline总体上类似于上面的Pipeline,但是它内部会使用MULTI/EXEC包裹排队的命令。例如:

  1. pipe := rdb.TxPipeline()
  2. incr := pipe.Incr("tx_pipeline_counter")
  3. pipe.Expire("tx_pipeline_counter", time.Hour)
  4. _, err := pipe.Exec()
  5. fmt.Println(incr.Val(), err)

上面代码相当于在一个RTT下执行了下面的redis命令:

  1. MULTI
  2. INCR pipeline_counter
  3. EXPIRE pipeline_counts 3600
  4. EXEC

还有一个与上文类似的TxPipelined方法,使用方法如下:

  1. var incr *redis.IntCmd
  2. _, err := rdb.TxPipelined(func(pipe redis.Pipeliner) error {
  3. incr = pipe.Incr("tx_pipelined_counter")
  4. pipe.Expire("tx_pipelined_counter", time.Hour)
  5. return nil
  6. })
  7. fmt.Println(incr.Val(), err)

Watch

在某些场景下,我们除了要使用MULTI/EXEC命令外,还需要配合使用WATCH命令。在用户使用WATCH命令监视某个键之后,直到该用户执行EXEC命令的这段时间里,如果有其他用户抢先对被监视的键进行了替换、更新、删除等操作,那么当用户尝试执行EXEC的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。

  1. Watch(fn func(*Tx) error, keys ...string) error

Watch方法接收一个函数和一个或多个key作为参数。基本使用示例如下:

  1. // 监视watch_count的值,并在值不变的前提下将其值+1
  2. key := "watch_count"
  3. err = client.Watch(func(tx *redis.Tx) error {
  4. n, err := tx.Get(key).Int()
  5. if err != nil && err != redis.Nil {
  6. return err
  7. }
  8. _, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
  9. pipe.Set(key, n+1, 0)
  10. return nil
  11. })
  12. return err
  13. }, key)

最后看一个V8版本官方文档中使用GET和SET命令以事务方式递增Key的值的示例,仅当Key的值不发生变化时提交一个事务。

  1. func transactionDemo() {
  2. var (
  3. maxRetries = 1000
  4. routineCount = 10
  5. )
  6. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  7. defer cancel()
  8. // Increment 使用GET和SET命令以事务方式递增Key的值
  9. increment := func(key string) error {
  10. // 事务函数
  11. txf := func(tx *redis.Tx) error {
  12. // 获得key的当前值或零值
  13. n, err := tx.Get(ctx, key).Int()
  14. if err != nil && err != redis.Nil {
  15. return err
  16. }
  17. // 实际的操作代码(乐观锁定中的本地操作)
  18. n++
  19. // 操作仅在 Watch 的 Key 没发生变化的情况下提交
  20. _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
  21. pipe.Set(ctx, key, n, 0)
  22. return nil
  23. })
  24. return err
  25. }
  26. // 最多重试 maxRetries 次
  27. for i := 0; i < maxRetries; i++ {
  28. err := rdb.Watch(ctx, txf, key)
  29. if err == nil {
  30. // 成功
  31. return nil
  32. }
  33. if err == redis.TxFailedErr {
  34. // 乐观锁丢失 重试
  35. continue
  36. }
  37. // 返回其他的错误
  38. return err
  39. }
  40. return errors.New("increment reached maximum number of retries")
  41. }
  42. // 模拟 routineCount 个并发同时去修改 counter3 的值
  43. var wg sync.WaitGroup
  44. wg.Add(routineCount)
  45. for i := 0; i < routineCount; i++ {
  46. go func() {
  47. defer wg.Done()
  48. if err := increment("counter3"); err != nil {
  49. fmt.Println("increment error:", err)
  50. }
  51. }()
  52. }
  53. wg.Wait()
  54. n, err := rdb.Get(context.TODO(), "counter3").Int()
  55. fmt.Println("ended with", n, err)
  56. }

golang操作redis/go-redis库的更多相关文章

  1. go语言之行--golang操作redis、mysql大全

    一.redis 简介 redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写key-value存储系统,它由C语言编写.遵守BSD协议.支持网 ...

  2. golang 操作 Redis & Mysql & RabbitMQ

    golang 操作 Redis & Mysql & RabbitMQ Reids 安装导入 go get github.com/garyburd/redigo/redis import ...

  3. Python开发【十一章】:数据库操作Memcache、Redis

    一.Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的 ...

  4. 十一天 python操作rabbitmq、redis

    1.启动rabbimq.mysql 在""运行""里输入services.msc,找到rabbimq.mysql启动即可 2.启动redis 管理员进入cmd, ...

  5. Python操作MongoDB和Redis

    1. python对mongo的常见CURD的操作 1.1 mongo简介 mongodb是一个nosql数据库,无结构化.和去中心化. 那为什么要用mongo来存呢? 1. 首先.数据关系复杂,没有 ...

  6. Python之路:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...

  7. Python操作 Memcache、Redis

    Python操作 Memcached.Redis 一.Memcached和Redis对比 1.1 Memcached和Redis的数据类型对比 memcached只有一种数据类型,key对应value ...

  8. 使用python操作Memcache、Redis、RabbitMQ、

    Memcache 简述: Memcache是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的.需要 ...

  9. 【转】Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...

随机推荐

  1. codeforces 292E. Copying Data

    We often have to copy large volumes of information. Such operation can take up many computer resourc ...

  2. hdu5437 Alisha’s Party

    Problem Description Princess Alisha invites her friends to come to her birthday party. Each of her f ...

  3. Git 初始化及仓库创建及操作

    一.基本信息设置 1.初始化设置用户名 2.初始化设置用户名邮箱 备注:该设置在Github仓库主页显示谁提交了该文件. 二.初始化一个新的Git仓库 1.创建文件夹 mkdir test 2.在文件 ...

  4. 一句话木马的简单例子 网站webshell & 远程连接

    一  概述 本地 kail  linux 目标 windows nt 服务器 二 过程 首先编写一句话木马  index.php 一句话木马的原理就是把C=xxx 字符串当成php语句执行 注意这里用 ...

  5. linux多线程模拟银行家算法

    题外话: 这应该是最近有点难度的作业了,起码比之前的理发师,读写,哲学家问题要难. 但是做好程序的结构,自顶向下,就还是不难的. 银行家算法简介:                 代码: init() ...

  6. mysql 查询,天,周,月等写法

    1.查询当天的数据 select * from 表名 where TO_DAYS(时间字段)=TO_DAYS(NOW()); 2.查询当周的数据 select * from 表名 where YEAR ...

  7. mybatis(四)缓存机制

    转载:https://www.cnblogs.com/wuzhenzhao/p/11103043.html 缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力.跟Hibe ...

  8. Mybatis基础:Mybatis映射配置文件,Mybatis核心配置文件,Mybatis传统方式开发

    一.Mybatis快速入门 1.1 框架介绍 框架是一款半成品软件,我们可以基于这个半成品软件继续开发,来完成我们个性化的需求! 框架:大工具,我们利用工具,可以快速开发项目 (mybatis也是一个 ...

  9. Chrome console & Command Line API

    Chrome console & Command Line API $ && $$ querySelector querySelectorAll Command Line AP ...

  10. React 权限管理

    React 权限管理 react in depth JWT token access_token & refresh_token access token & refresh toke ...