Redis实战入门

2022-10-26 18:33:01 浏览数 (1)

一. Redis 连接

1. 进入redis环境

代码语言:txt复制
docker run -d -p 6379:6379 --name redis redis:latest
docker exec -it redis redis-cli

2. 检测 Redis 是否正常

代码语言:txt复制
127.0.0.1:6379> ping
PONG

3. 退出连接

代码语言:txt复制
127.0.0.1:6379> quit
[root@VM-4-12-centos data]#

二. redis内容

1. key 操作命令

输入命令

含义

keys *

查看所有的key

dbsize

获取总key数

exists key

查询键是否存在

del key

删除key

type key

查询键类型(string,set..)

ttl key

查询key的生命周期(秒)

pttl key

查询key的生命周期(毫秒)

expire key

设置过期时间(秒)

pexpire key

设置过期时间(毫秒)

persist key

设置永不过期

rename key newkey

更改键名称

2. 字符串string 操作命令

输入命令

含义

举例

返回值

set key value

存放键值

set java1 test

OK

get key

获取键值

get java1

"test"

mset key1 value1 key2 value2

批量存放

mset java1 1 java2 2 java3 3

OK

mget key1 key2

批量获取

mget java1 java2

1) "test" 2) "2"

strlen key

获取值长度

strlen java1

4

append key value

追加内容

append java1 test2

(integer) 6

getrange key start end

获取部分内容

getrange java1 0 3

"1tes"

3. 无序集合Set 操作命令

相当于 Java 语言里面的 HashSet

语法

含义

举例

返回值

sadd key member...

存储值

sadd langs java php c go ruby python kotlin java

(integer) 7

smembers key

获取所有元素语法

smembers langs

1) "php"2) "kotlin"3)"c "4) "go"5) "ruby"6) "python"7) "java"

srandmember key count

随机获取元素语法

srandmember langs 3

1)"kotlin"2) "python"3) "c "

sismember key member

判断集合是否存在元素

sismember langs go

(integer) 1

scard key

获取集合元素个数

scard langs

(integer) 7

srem key member..

删除集合元素

srem langs java

(integer) 1

spop key count

弹出元素

spop langs 2

1) "python" 2) "go"

4. 有序集合zset

类似于 Java 的 SortedSet 和 HashMap 的结合体

和列表的区别:

1、列表使用链表实现,两头快,中间慢。有序集合是散列表和跳跃表实现的,即使读取中间的元素也比较快。

2、列表不能调整元素位置,有序集合能。

3、有序集合比列表更占内存。

输入命令

含义

举例

返回值

zadd key [NX|XX] [CH] [INCR] score member [score member ...]

存储值

zadd footCounts 16011 tid 20082 huny 2893 nosy

3

zscore key member

获取元素分数

zscore footCounts tid

"16011"

zincrby key increment member

增加指定元素分数

zincrby footCounts 2000 tid

"18011"

zcard key

获取集合元素个数

zcard footCounts

3

zrem key member [member ...]

删除指定元素

zrem footCounts huny

1

zrank key member

获取元素排名

zrank footCounts tid

2

zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

获取指定分数范围排名语法

zrangebyscore footCounts 3000 30000 withscores limit 0 1

1) "tid"2) "16011"

zcount key min max

获取指定范围分数个数

zcount footCounts 2000 20000

2

5. 列表操作命令list

Redis 中的 listJava 中的 LinkedList 很像,底层都是一种链表结构, list 的插入和删除操作非常快,时间复杂度为 0(1),不像数组结构插入、删除操作需要移动数据。

输入命令

含义

举例

返回值

lpush key value ...

左端存值语法

lpush list lily sandy

2

rpush key value ...

右端存值语法

rpush list tom kitty

4

lset key index value

索引存值语法

lset list 3 uto

OK

lpop key

左端弹出语法

lpop list

"broad"

rpop key

右侧弹出语法

rpop list

"kitty"

llen key

获取元素个数

llen list

2

lrange key start top

获取列表元素

lrange list 0 -1

1) "sandy" 2) "lily"3)"uto"

index key index

索引获取语法

lindex list 2

"ketty "

lrem key count value

根据值删除语法

lrem userids 0 111 //count=0 删除所有

2

ltrim key start stop

范围删除语法

ltrim list 2 4

OK

6. 散列表hash

Redis 中的 Hash 和 Java的 HashMap 更加相似, 都是 数组 链表 的结构,当发生 hash 碰撞时将会把元素追加到链表上,值得注意的是在 RedisHashvalue 只能是字符串.

输入命令

含义

举例

返回值

hset key field value

存放键值

hset user name javastack

1

hmset key field value field value ...

2

hmset user name javastack age 20 address china

4

hsetnx key field value

不存在时语法

hsetnx user tall 180

0

hget key field

获取字段值

hget key field

"20"

hmget key field field ...

获取多个字段值

hmget user name age address

1) "javastack"2)"20"3) "china"

hgetall key

获取所有键与值语法

hgetall user

1) "name"2) "javastack"3) "age"4) "20"5) "address"6) "china"

hkeys key

获取所有字段语法

127.0.0.1:6379> hkeys user

1) "name"2) "address"3) "tall"4) "age"

hvals key

获取所有值语法

hvals user

1) "javastack"2) "china"3) "170"4) "20"

hexists key field

判断字段是否存在

hexists user address

1

hlen key

获取字段数量

hlen user

4

hincrby key field increment

递增/减

hincrby user tall -10

170

hdel key field field ...

删除字段

hdel user age

1

三. key的操作之get set

1. get、set去设置

代码语言:txt复制
// exampleClient redis连接客户端的代码
func exampleClient() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   err := rdb.Set(ctx, "key", "value", 0).Err()
   if err != nil {
      panic(err)
   }
   val, err := rdb.Get(ctx, "key").Result()
   if err != nil {
      panic(err)
   }
   fmt.Println("key", val)

   val2, err := rdb.Get(ctx, "key2").Result()
   if err == redis.Nil {
      fmt.Println("key2 does not exist")
   } else if err != nil {
      panic(err)
   } else {
      fmt.Println("key2", val2)
   }
}

2. 执行任意命令

go-redis 还提供了一个执行任意命令或自定义命令的 Do 方法,特别是一些 go-redis 库暂时不支持的命令都可以使用该方法执行。具体使用方法如下

代码语言:txt复制
// doCommand 基本的get set
func doCommand() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
   defer cancel()

   // 执行命令获取结果
   val, err := rdb.Get(ctx, "key").Result()
   fmt.Println("test1", val, err)

   // 先获取到命令对象
   cmder := rdb.Get(ctx, "key")
   fmt.Println("test2", cmder.Val())
   fmt.Println("test3", cmder.Err())

   // 直接执行命令获取错误
   // rdb.Set(ctx, "key", 10, time.Hour).Err()
   err = rdb.Set(ctx, "key", 3000, 0).Err()

   // 直接执行命令获取值
   value := rdb.Get(ctx, "key").Val()
   fmt.Println("test4", value)
}

3. redis.Nil

go-redis 库提供了一个 redis.Nil 错误来表示 Key 不存在的错误。因此在使用 go-redis 时需要注意对返回错误的判断。在某些场景下我们应该区别处理 redis.Nil 和其他不为 nil 的错误。

代码语言:txt复制
// exampleClient redis连接客户端的代码
func exampleClient() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   err := rdb.Set(ctx, "key", "value", 0).Err()
   if err != nil {
      panic(err)
   }
   val, err := rdb.Get(ctx, "key").Result()
   if err != nil {
      panic(err)
   }
   fmt.Println("key", val)

   val2, err := rdb.Get(ctx, "key2").Result()
   if err == redis.Nil {
      fmt.Println("key2 does not exist")
   } else if err != nil {
      panic(err)
   } else {
      fmt.Println("key2", val2)
   }
}

4. 扫描或遍历所有key

你可以使用 KEYS prefix:* 命令按前缀获取所有 key

代码语言:txt复制
func ScanKeysDemo2() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
   defer cancel()
   // 按前缀扫描key
   iter := rdb.Scan(ctx, 0, "prefix:*", 0).Iterator()
   // 此外,对于 Redis 中的 set、hash、zset 数据类型,go-redis 也支持类似的遍历方法。
   // iter := rdb.SScan(ctx, "set-key", 0, "prefix:*", 0).Iterator()
   // iter := rdb.HScan(ctx, "hash-key", 0, "prefix:*", 0).Iterator()
   // iter := rdb.ZScan(ctx, "sorted-hash-key", 0, "prefix:*", 0).Iterator()

   for iter.Next(ctx) {
      fmt.Println("[key]:", iter.Val())
   }
   if err := iter.Err(); err != nil {
      panic(err)
   }
}

四. zset命令

代码语言:txt复制
func ZSetDemo() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   zsetKey := "language_rank"
   languages := []*redis.Z{
      {Score: 90.0, Member: "Golang"},
      {Score: 98.0, Member: "Java"},
      {Score: 95.0, Member: "Python"},
      {Score: 97.0, Member: "JavaScript"},
      {Score: 99.0, Member: "C/C  "},
   }
   ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Microsecond)
   defer cancel()

   // ZADD
   err := rdb.ZAdd(ctx, zsetKey, languages...).Err()
   if err != nil {
      fmt.Printf("zadd failed, err:%vn", err)
      return
   }
   fmt.Println("zadd success!")

   // Goland分数加10
   newScore, err := rdb.ZIncrBy(ctx, zsetKey, 10.0, "Golang").Result()
   if err != nil {
      fmt.Printf("zincrby failed, err:%vn", err)
      return
   }
   fmt.Printf("Goland's score is %f now.n", newScore)

   // 取分数最高的3个
   ret := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Val()
   for _, z := range ret {
      fmt.Println(z.Member, z.Score)
   }
   fmt.Println("=================")

   // 取95~100分的
   op := redis.ZRangeBy{
      Min: "95",
      Max: "100",
   }
   ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, op).Result()
   if err != nil {
      fmt.Printf("zrangebyscore failed, err:%vn", err)
      return
   }
   for _, z := range ret {
      fmt.Println(z.Member, z.Score)
   }
}

执行上面的函数将得到如下输出结果。

代码语言:txt复制
zadd success
Golang's score is 100.000000 now.
Golang 100
C/C   99
Java 98
Python 95
JavaScript 97
Java 98
C/C   99
Golang 100

五. Pipeline

Redis Pipeline 允许通过使用单个 client-server-client 往返执行多个命令来提高性能。区别于一个接一个地执行100个命令,你可以将这些命令放入 pipeline 中,然后使用1次读写操作像执行单个命令一样执行它们。这样做的好处是节省了执行命令的网络往返时间(RTT)。

1. 写法一. pipeline

代码语言:txt复制
func PipelineWithNoExecDemo() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   ctx, _ := context.WithTimeout(context.Background(), time.Hour)

   var incr *redis.IntCmd
   _, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
      incr = pipe.Incr(ctx, "pipeline_counter")
      pipe.Expire(ctx, "pipeline_counter", time.Hour)
      return nil
   })
   if err != nil {
      panic(err)
   }
   // 在执行pipe.Exec之后才能获取到结果
   fmt.Println(incr.Val())
}

也可以使用 Pipelined 方法,它会在函数退出时调用 Exec. 代码会更加简洁

2. 写法二. pipelined

代码语言:txt复制
func PipelineWithNoExecMulDemo() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   ctx, _ := context.WithTimeout(context.Background(), time.Hour)
   _, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
      for i := 0; i < 100; i   {
         pipe.Set(ctx, fmt.Sprintf("key%d", i), fmt.Sprintf("key%d", i), time.Hour)
      }
      return nil
   })
   if err != nil {
      panic(err)
   }

   cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
      for i := 0; i < 100; i   {
         pipe.Get(ctx, fmt.Sprintf("key%d", i))
      }
      return nil
   })
   if err != nil {
      panic(err)
   }

   for _, cmd := range cmds {
      fmt.Println(cmd.(*redis.StringCmd).Val())
   }
}

六. 加锁

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

在这种场景我们需要使用 TxPipeline 或 TxPipelined 方法将 pipeline 命令使用 MULTIEXEC 包裹起来。

代码语言:txt复制
// ZTransactionTxPipeDemo 这里只是相当于加锁,TxPipeline/TxPipelined
func ZTransactionTxPipeDemo() {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Microsecond)
   defer cancel()

   // TxPipeline demo
   pipe := rdb.TxPipeline()
   pipe.Incr(ctx, "tx_pipeline_counter")
   pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
   _, err := pipe.Exec(ctx)
   if err != nil {
      panic(err)
   }

   // TxPipelined demo
   var incr2 *redis.IntCmd
   _, err = rdb.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
      incr2 = pipe.Incr(ctx, "tx_pipeline_counter")
      pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
      return nil
   })

   fmt.Println(incr2.Val(), err)
}

七. 事务-watch

我们通常搭配 WATCH 命令来执行事务操作。从使用 WATCH 命令监视某个 key 开始,直到执行 EXEC 命令的这段时间里,如果有其他用户抢先对被监视的 key 进行了替换、更新、删除等操作,那么当用户尝试执行 EXEC 的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。

Watch方法接收一个函数和一个或多个key作为参数

代码语言:txt复制
// WatchDemo 配合TxPipeline/TxPipelined完成增删改查
func WatchDemo() error {
   rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
   ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Microsecond)
   defer cancel()
   key := "key"
   return rdb.Watch(ctx, func(tx *redis.Tx) error {
      n, err := tx.Get(ctx, key).Int()
      if err != nil; err != redis.Nil {
         return err
      }
      // 假设操作耗时5秒
      // 5秒内我们通过其他的客户端修改key,当前事务就会失败
      time.Sleep(5 * time.Second)
      _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
         pipe.Set(ctx, key, n 1, time.Hour)
         return nil
      })
      return err
   }, key)
}

参考文章

  1. Go语言操作Redis
  2. https://github.com/go-redis/redis
  3. Redis 常用操作命令,非常详细!
  4. Redis基础系列短视频《2. Redis简单实战》
  5. redis中文官网
  6. Redis 常用命令

0 人点赞