Redis入门基础命令与使用| 青训营笔记

148 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第15天。

学习了Redis入门的基础命令与使用。

基本介绍

Redis是NoSQL数据库,而不是传统的关系型数据库。

全称:Remote Dictionary Server,性能很高,单机能够达到15w qps,通常适合做缓存,也可以做持久化。

是完全开源免费且高性能的(Key/Value)分布式数据库,基于内存运行并支持持久化的NoSQL数据库,是最热门的NoSQL数据库之一,也称为数据结构服务器。

一般监听的端口:6379

运作机制简要概括是:代码发出指令(用客户端敲命令也可以啦),核心组件进行处理。

redis核心组件功能

  • 解析指令
  • 做相应数据操作处理

内存的五种数据结构

  • string、key-val
  • hash
  • list
  • set
  • zset(有序集合)

基本使用

Redis默认有16个数据库,初始默认使用0号库。

  • key-val [set]
  • 查看当前redis的所有key [key *]
  • 获取key对应的值 [get key]
  • 切换redis数据库 [select index]
  • 如何查看当前数据库的key-val数量 [dbsize]
  • 清空当前数据库的key-val和清空所有数据库的key-val [flushdb flushall]

Redis数据类型和CRUD

Redis五种数据类型:String、hash、list、set、zset。

String

  • 是redis最基本的数据类型,一个key对应一个value。
  • String是二进制安全的,除普通的字符串外,也可以存放图片等数据(但一般不会这么存的,因为图片大部分很大)。
  • redis中字符串value最大是512M

举例:存放于一个地址信息

  • address 北京天安门
  • key -> address
  • value -> 北京天安门

CRUD

  • set

    • 如果存在就相当于修改,不存在就是添加
    • set key value
  • setex(set with expire)

    • 实现定时任务,在设定的时间后redis就会把该值从库中删除
    • setex mess01 10 hello
  • mset

    • 同时设置一个或多个key-value对
    • mset key1 value1 key2 value2
  • mget

    • 同时获取多个键值
    • mget key1 key2

Hash

  • Redis hash是一个键值对集合。类似于:var user1 map[string]string
  • Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,注意key不能重复

CRUD

添加

  • hset key key-key value -> 一次性设置一个键值
  • hmset key key-key1 value1 key-key2 value2 key-key3 value3 -> 一次性设置多个键值

需要注意的是,如设置的键或值中包括空格,一定要用"",比如"golang coder"

获取

  • hget key key-keyx
  • hgetall -> 获取全部键值
  • hmget key key-keyx key-keyy -> 一次性获取指定键值

删除

  • hdel key -> 会删掉该键后的所有键
  • hlen key -> 统计一个hash中有几个元素
  • hexists key key-keyx -> 判断是否有某个字段,1为有,0为没有

List

  • 是简单的字符型的列表,按照插入的顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边)
  • List本质是一个链表,List的元素是有序的,元素的值可以重复

CRUD

添加

  • lpush key value1 value2 value3.. -> 从头部插入
  • rpush -> 从尾部插入

查询

遵循后进先出,比如在上述添加中,查询到的第一个元素为value3

  • lrange city 0 -1 -> 全部查询
  • lrange key start stop -> 返回列表指定区间的元素

弹出(删除)

  • lpop -> 从头部弹出数据
    • lpop key
  • rpop -> 从尾部弹出数据
    • rpop key

llen key -> 返回列表的长度

Set

  • 是string类型的无序集合
  • 底层是HashTable数据结构,Set也是存放很多字符串元素,字符串元素是无序的,而且元素的值不能重复

CRUD

添加

  • sadd key value1 value2...

查询

  • smembers key
  • sismemeber key value -> 判断value是否存在,有为1,无为0

删除

  • srem key value -> 删除指定的value,删除成功为1,失败为0

Go连接与操作Redis

安装第三方包

我拉的是这个:go get github.com/garyburd/redigo/redis

操作String类型

func StringOperate() {
   c, err := redis.Dial("tcp", "127.0.0.1:6379")
   if err != nil {
      fmt.Println("connect redis fail")
      return
   }
   defer c.Close()
   _, err = c.Do("set", "key1", 666)
   if err != nil {
      fmt.Println(err)
   }
   // 返回的r是空接口,而key1是字符串,故要转换
   r, err := redis.String(c.Do("get", "key1"))
   if err != nil {
      fmt.Println("get key1 failed")
      return
   }

   fmt.Println(r)
}

操作Hash类型

func HashOperate() {
   c, err := redis.Dial("tcp", "127.0.0.1:6379")
   if err != nil {
      fmt.Println("connect redis fail")
      return
   }
   defer c.Close()
   _, err = c.Do("hmset", "user01", "name", "jack", "age", 30)
   if err != nil {
      fmt.Println(err)
   }
   // 返回的r是空接口,而key1是字符串,故要转换
   r, err := redis.Strings(c.Do("hgetall", "user01"))
   if err != nil {
      fmt.Println("get key1 failed", err)
      return
   }
   for i, v := range r {
      fmt.Printf("r[%v]=%v\n", i, v)
   }
}

其实,无论操作什么类型,在DO()中写原生的redis命令,命令行中的空格在代码中用""与,区分和分隔来即可,需要注意的是在获取数据的时候要判断好类型。

连接池

连接池是其中有一些不关闭的连接(连接数据库),当客户端要使用redis时,直接在连接池中取一个连接用,使用完成后再返回连接池。

流程

  • 事先初始化一定数量的连接,放入到连接池
  • 当需要操作redis时,直接从redis连接池中取出连接即可
  • 由此,节省了临时获取redis连接的时间,提升了效率

Demo

var Pool *redis.Pool

func Init() {
   Pool = &redis.Pool{
      MaxIdle:     8,   // 最大空闲连接数
      MaxActive:   0,   // 表示和数据库的最大连接数,0表示没有限制
      IdleTimeout: 100, // 最大空闲时间,如果超过这个时间连接都没有被使用,则会返回连接池
      Dial: func() (redis.Conn, error) { //初始化连接池的代码
         return redis.Dial("tcp", "127.0.0.1:6379")
      },
   }
}

func main() {

   conn := Pool.Get()
   defer conn.Close()

   _, err := conn.Do("set", "name", "durant")
   if err != nil {
      fmt.Println("coon.Do err=", err)
      return
   }

   r, err := redis.String(conn.Do("Get", "name"))
   if err != nil {
      fmt.Println(err)
      return
   }

   fmt.Println(r)
}

go-redis

由于后来发现开发中常用的是go-redis,故做一次更新,以下是go-redis的一些常用操作。

获取go-redis

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

初始化

var rdb *redis.Client

func init() {
   rdb = redis.NewClient(&redis.Options{
      Addr:     "127.0.0.1:6379",
      Password: "",
      DB:       0,
   })
}

String类型

GetSet

打印key的旧值,但会同时把新值存入

oldVal, err := rdb.GetSet(ctx, "name", "rose").Result()
if err != nil {
   panic(err)
}
fmt.Println("key", oldVal)

SetNx

如果Key不存在,则设置这个key值,若存在则不会进行操作

err := rdb.SetNX(ctx,"name","jack",0).Err()
if err != nil {
   panic(err)
}

MGet

批量查询

vals, err := rdb.MGet(ctx, "name", "name1", "name2").Result()
if err != nil {
   panic(err)
}
fmt.Println(vals)

MSet

批量设置

Incr,IncrBy(针对integer)

Incr函数每次加一

val, err := rdb.Incr(ctx, "key").Result()
if err != nil {
   panic(err)
}
fmt.Println("最新值", val)

IncrBy函数,可以指定每次递增多少

valBy, err := rdb.IncrBy(ctx, "key", 2).Result()
if err != nil {
   panic(err)
}
fmt.Println("最新值", valBy)

IncrByFloat

可以指定每次递增多少,跟IncrBy的区别是累加的是浮点数

valBy, err := rdb.IncrByFloat(ctx, "key", 2.2).Result()
if err != nil {
 panic(err)
}
fmt.Println("最新值", valBy)

Decr

递减函数相关,用法和函数命名与Incr类似,不再赘述

Del

删除Key操作,支持批量删除

rdb.Del(ctx, "key")

删除多个key


err := rdb.Del(ctx, "name", "name1", "name2").Err()
if err != nil {
   panic(err)
}

Expire

设置Key的过期时间

rdb.Expire(ctx, "key", 3 * time.Second)

当然在Set的时候也可以设置这个参数的,如果为0则为不删除即持久化。

Hash类型

HSet

err := rdb.HSet(ctx, "user1", "name", "durant", "hobby", "basketball", "count", 0).Err()
if err != nil {
   panic(err)
}

HGet

username, err := rdb.HGet(ctx, "user1", "name").Result()
if err != nil {
   panic(err)
}
fmt.Println(username)

HGetAll

data, err := rdb.HGetAll(ctx, "user1").Result()

HincrBy

根据Key和Field字段,累加字段的数据

例:累加count字段的值
count, err := rdb.HIncrBy(ctx, "user1", "count", 2).Result()
if err != nil {
   panic(err)
}
fmt.Println(count)

HKeys

根据Key返回所有字段名,返回的keys是一个String数组

keys, err := rdb.HKeys(ctx, "user1").Result()
if err != nil {
   panic(err)
}
fmt.Println(keys)

HLen

根据Key,查询Hash的字段数量

size, err := rdb.HLen(ctx, "user1").Result()
if err != nil {
   panic(err)
}
fmt.Println(size)

List

LRem

删除列表中的数据

例:从列表左边开始,删除100,如果出现重复元素,仅删除1次,也就是删除第一个
dels, err := rdb.LRem(ctx,"key", 1,100).Result()
if err != nil {
   panic(err)
}
例:如果存在多个100,则从列表左边开始删除2个100
rdb.LRem(ctx,"key",2,100)
例:如果存在多个100,则从列表右边开始删除2个100
rdb.LRem(ctx,"key",-2,100)

LInsert

在指定位置插入数据

例:在列表中5的前面插入4
err := rdb.LInsert(ctx,"key","before",5,4).Err()
if err != nil {
   panic(err)
}
例:在列表中zhangsan元素的前面插入 欢迎你
rdb.LInsert(ctx,"key","before","zhangsan","欢迎你")
例:在列表中zhangsan元素的后面插入 2022
rdb.LInsert(ctx,"key","after","zhangsan","2022")

Zset

ZAdd

添加一个或者多个元素到集合,如果元素已经存在则更新分数

例:添加一个元素到集合中,这个元素的分数是2.5,元素名是zhangsan
err := rdb.ZAdd(ctx, "key", &redis.Z{Score: 2.5, Member: "zhangsan"}).Err()
if err != nil {
   panic(err)
}

ZCard

返回集合元素个数

size, err := rdb.ZCard(ctx,"key").Result()
if err != nil {
   panic(err)
}
fmt.Println(size)
}

ZCount

统计某个分数范围内的元素个数

例:返回:1 <= 分数 <=5 的元素个数,注意:"1","5"两个参数是字符串
size, err := rdb.ZCount(ctx, "Key","1","5").Result()
if err != nil {
   panic(err)
}
fmt.Println(size)
例:返回:1 < 分数 <= 5的元素个数

说明:默认第二,第三个参数是大于等于和小于等于的关系 如果加上 ( 则表示大于或者小于,相当于去掉了等于关系。

size, err := rdb.ZCount(ctx, "key","(1","5").Result()

ZRangeByScore

根据分数范围返回集合元素,元素根据分数从小到大排序,支持分页

// 初始化查询条件,Offset和Count用于分页
op := redis.ZRangeBy{
   Min:    "2",  // 最小分数
   Max:    "10", // 最大分数
   Offset: 0,    // 类似sql的LIMIT,表示初始偏移量
   Count:  5,    // 一次返回多少数据
}

vals, err := rdb.ZRangeByScore(ctx, "key", &op).Result()
if err != nil {
   panic(err)
}

for _, val := range vals {
   fmt.Println(val)
}

ZRangeByScoreWithScores

用法跟ZRangeByScore一样,区别是除了返回集合元素,同时也返回元素对应的分数

ZScore

查询对应的分数

ZRank

根据元素名,查询集合元素在集合中的排名,从0开始算,集合元素按分数从小到大排序

发布订阅

subscriber

Redis提供了发布订阅功能,可以用于消息的传输,Redis的发布订阅机制包括三个部分,发布者、订阅者和Channel。

image.png

发布者和订阅者都是Redis客户端,Channel则为Redis服务器端,发布者将消息发送到某个的频道,订阅了这个频道的订阅者就能收到这条消息。

publisher

package main

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

func init() {
   rdb = redis.NewClient(&redis.Options{
      Addr:     "127.0.0.1:6379",
      Password: "",
      DB:       0,
   })
}

func publish() {
   ctx := context.Background()
   rdb.Publish(ctx, "channel1", "msg")
}

两者通信

package main

import (
   "context"
   "fmt"
)

func subscribe() {
   ctx := context.Background()
   sub := rdb.Subscribe(ctx, "channel1")

   // 扫描方式1
   //for ch := range sub.Channel(){
   // fmt.Println(ch.Channel)       // 名称
   // fmt.Println(ch.Payload)
   //}

   // 扫描方式2
   for {
      message, err := sub.ReceiveMessage(ctx)
      if err != nil {
         panic(err)
      }
      fmt.Println(message.Channel) // 名称
      fmt.Println(message.Payload)
   }

}

事务处理

TxPipeline

redis事务可以一次性执行多个命令,并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部不执行
func main() {
   ctx := context.Background()
   // 开启一个Pipeline的方式操作事务
   pipe := rdb.TxPipeline()

   // 执行事务操作,可以通过pipe读写redis
   incr := pipe.Incr(ctx, "tx_pipeline_counter")
   pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
   // 上述代码等同于执行下面的Redis命令
   // MUL TI
   // INCR pipeline_counter
   // EXPIRE pipeline_counts 3600
   // EXEC

   // 通过Exec函数提交Redis事务,提交后才会执行
   _, err := pipe.Exec(ctx)

   // 提交事务后,可以查询事务操作的结果
   // 前面执行的Incr函数,在没有执行exec函数之前,实际上还没开始运行
   fmt.Println(incr.Val(), err)
   }

Watch

redis乐观锁支持,可以通过watch监听一些Key,如果这些Key的值没有被其他人改变的话,才可以提交事务。

// 定义一个回调函数,用于处理事务逻辑
func main() {
   fn := func(tx *redis.Tx) error {
      // 先查询当前watch监听的key的值
      v, err := tx.Get(ctx, "key").Int()
      if err != nil && err != redis.Nil {
         return err
      }
      // 这里可以处理业务
      v++
      // 只有key的值没有改变的情况下,Pipelined函数才会调用成功
      _, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
         // 这里给key设置最新值
         pipe.Set(ctx, "key", v, 0)
         return nil
      })
      return err
   }

   // 使用Watch监听一些key,同时绑定一个回调函数fn,监听Key后的逻辑在写在fn这个回调函数里面
   // 如果想监听多个key,可以这么写:client.Watch(ctx, fn, "key1", "key2", "key3")
   rdb.Watch(ctx, fn, "key")
}

以上内容若有不正之处,恳请您不吝指正!