redis入门指南 万字超全解析

Redis是一种流行的内存键值数据库,被广泛用于构建高性能的缓存和消息队列应用

如果你的网站访问很卡顿,那么接入redis缓存热点数据,将会使你的网站访问体验得到质的飞跃

redis的体系很庞大,本课程就简单的带着大家入个门

redis安装

  1. phpstudy里面的redis

redis和mysql都是一个客户端,一个服务端

我发现有很多对这些软件的客户端和服务端不太清楚

我圈起来的那个就是服务端

它下面的那个是客户端,你可以不装,你可以装其他的客户端,装了之后你的命令行里面就有一个redis-cli的命令行客户端了

当然,你也可以用代码去连接,此时你的代码就充当了客户端

  1. docker
docker pull redis:5.0.5

docker run -itd --name redis --restart=always -p 6379:6379 redis:5.0.5 --requirepass "redis"

如果你只用docker装redis的话,你只能在宿主机上用redis的服务端

你不能在命令行里面用redis-cli的命令

连接redis

  1. 命令行连接
# 完整命令
redis-cli -h 127.0.0.1 -p 6379 -a password
# 简写
redis-cli
# 认证 进入redis之后
auth password
  1. 代码连接
package main

import (
  "fmt"
  "github.com/go-redis/redis"
)

var DB *redis.Client

func Connect() {
  redisDB := redis.NewClient(&redis.Options{
    Addr:     "127.0.0.1:6379", // 不写默认就是这个
    Password: "redis",          // 密码
    DB:       1,                // 默认是0
  })
  _, err := redisDB.Ping().Result()
  if err != nil {
    panic(err)
  }
  DB = redisDB
}

redis五大数据类型

面试必考题

  1. string (字符串)
  2. list (列表)
  3. set (集合)
  4. hash (哈希)
  5. zset (有序集合)

注意,redis中的数据都是在内存里面的,redis服务重启之后,数据也会被清空的

这几个我们先用命令行去操作一下

1. String

字符串 String 是 Redis 最简单的数据结构,可以存储字符串、整数或者浮点数。最常见的应用场景就是对象缓存

redis-cli
127.0.0.1:6379> auth redis  # 登录
OK
127.0.0.1:6379> set name fengfeng   # 设置
OK
127.0.0.1:6379> get name  # 获取
"fengfeng"
127.0.0.1:6379> exists name  # 判断这个key是否存在
(integer) 1
127.0.0.1:6379> exists name1 
(integer) 0
127.0.0.1:6379> del name  # 删除这个key
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> GETSET name1 lisi # 如果不存在值,则返回 nil,set是会执行的
(nil)
127.0.0.1:6379> GETSET name1 lisi #  如果存在值,获取原来的值,并设置新的值
"lisi"

批量操作

127.0.0.1:6379> mset n1 zhangsan n2 lisi
OK
127.0.0.1:6379> get n1
"zhangsan"
127.0.0.1:6379> get n2
"lisi"

计数

自增1,自减1

127.0.0.1:6379> get n
(nil)
127.0.0.1:6379> incr n  # 自增1
(integer) 1  # 当前这个n的值
127.0.0.1:6379> get n
"1"
127.0.0.1:6379> incr n
(integer) 2
127.0.0.1:6379> get n
"2"
127.0.0.1:6379> decr n  # 自减1
(integer) 1
127.0.0.1:6379> get n
"1"
127.0.0.1:6379> decr n
(integer) 0
127.0.0.1:6379> get n
"0"
127.0.0.1:6379> decr n
(integer) -1
127.0.0.1:6379> get n
"-1"

自增n,自减n

127.0.0.1:6379> get n
(nil)
127.0.0.1:6379>
127.0.0.1:6379> INCRBY n 10
(integer) 10
127.0.0.1:6379> INCRBY n 11
(integer) 21
127.0.0.1:6379> get n
"21"
127.0.0.1:6379> DECRBY n 5
(integer) 16
127.0.0.1:6379> get n
"16"
过期操作

setex 单位是秒

127.0.0.1:6379> setex name 20 fengfeng
OK
127.0.0.1:6379> get name
"fengfeng"
127.0.0.1:6379> ttl name  # 查看还有多久过期
(integer) 12
127.0.0.1:6379> ttl name  # -2表示已过期
(integer) -2
127.0.0.1:6379> get name
(nil)

expire

设置一个key的过期时间

如果不设置过期时间,ttl显示的结果是-1表示永不过期
127.0.0.1:6379> set name fengfeng
OK
127.0.0.1:6379> ttl name  # 现在是永不过期
(integer) -1
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name  # 现在是过期了
(integer) -2

使用场景

  1. 登录
  2. 限流
  3. 计数器

中文乱码问题

但是输入中文仍然是乱码,无解

redis-cli --raw
127.0.0.1:6379> set name ᄋ ̄ᄋ ̄
OK
127.0.0.1:6379> get name
枫枫

2. List

redis的列表就相当于go里面的切片

rpush list zhangsan lisi wangwu  xiaoming  # 从右边向左边推入四个元素
4 # 返回列表的长度
127.0.0.1:6379> llen list # 查列表的长度
4
127.0.0.1:6379> lrange list 0 -1  # 查看列表的全部元素
zhangsan
lisi
wangwu
xiaoming
127.0.0.1:6379> rpop list  # 从右边推一个元素出来
rpop list
xiaoming
127.0.0.1:6379> lrange list 0 -1
zhangsan
lisi
wangwu
127.0.0.1:6379> lpop list  # 从左边推一个元素出来
zhangsan
127.0.0.1:6379> lrange list 0 -1
lisi
wangwu

使用场景

  1. 任务队列
  2. 排行榜
  3. 分页查询

3. Hash

相当于go里面的map,python中的字典

127.0.0.1:6379> hset dict name fengfeng  # 设置一个hash key,并且给hash里面设置一组filed value
0
127.0.0.1:6379> hget dict name  # 获取hash里面对应filed的值
fengfeng
127.0.0.1:6379> hgetall dict # 数据量大时,谨慎使用!获取在哈希表中指定 key 的所有字段和值
name
fengfeng
127.0.0.1:6379> hkeys dict # 获取在哈希表中指定 key 的所有filed
name
127.0.0.1:6379> hmset info name fengfeng age 23  # 批量设置
OK
127.0.0.1:6379> hkeys info
name
age
127.0.0.1:6379> HEXISTS info name  # 判断某个key是否存在
1
127.0.0.1:6379> HEXISTS info name1
0
127.0.0.1:6379> HDEL info name  # 删除hash中的一个filed
1
127.0.0.1:6379> hkeys info
age
127.0.0.1:6379> hlen info  # 返回hash中filed的个数
1

注意,设置过期只能给hash的key设置过期,里面的键值对是不能单独再设置过期的

使用场景

  1. 记录网站某篇文章的浏览量
  2. 存储配置信息

4. Set

集合中的元素没有先后顺序

HashSet就是基于HashMap来实现的,HashSet,他其实就是说一个集合,里面的元素是无序的,他里面的元素不能重复

127.0.0.1:6379> sadd set a b c d # 添加元素
4
127.0.0.1:6379> sadd set a b c e  # 实际只有一个元素添加进去了
1
127.0.0.1:6379> scard set  # 获取集合的长度
5
127.0.0.1:6379> sismember set a # 判断某个元素是不是在集合里面
1
127.0.0.1:6379> sismember set f
0
127.0.0.1:6379> smembers set # 获取集合中的数据 无序的
e
d
b
a
c
127.0.0.1:6379> srem set a # 删除集合中的某个元素 可传多个
1

交集、并集和差集

sdiff 差集

sinter 交集

sunion 并集

127.0.0.1:6379> sadd set1 1 2 3
3
127.0.0.1:6379> sadd set2 2 3 4
3
127.0.0.1:6379> sdiff set1 set2   # 差集
1
127.0.0.1:6379> sdiff set2 set1
4
127.0.0.1:6379> sinter set1 set2 # 交集
2
3
127.0.0.1:6379> sunion set1 set2  # 并集
1
2
3
4

随机抽奖

使用spop命令用于移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素

127.0.0.1:6379> sadd order 1 2 3 4 5 6 7 8
8
127.0.0.1:6379> spop order # 随机移除一个
5 # 随机的
127.0.0.1:6379> spop order
7
127.0.0.1:6379> spop order 3 # 随机移除3个  3.2版本才有效 redis-server -v 查看版本 或者
7

查看版本

两种方法

C:\Users\枫枫>redis-server -v

127.0.0.1:6379> info server

使用场景

  1. 共同好友
  2. 统计网站的独立ip
  3. 标签

5. sorted set

有序集合

sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列

也是不能重复的

127.0.0.1:6379> zadd class 88 fengfeng 46 zhangsan 76 wangwu 36 lisi  # 向有序集合添加一个或多个成员
(integer) 4
127.0.0.1:6379> zrange class 0 -1  # 通过索引,按照分数从低到高返回
1) "lisi"
2) "zhangsan"
3) "wangwu"
4) "fengfeng"
127.0.0.1:6379> zrevrange class 0 -1  # 通过索引,按照分数从高到低返回
1) "fengfeng"
2) "wangwu"
3) "zhangsan"
4) "lisi"
127.0.0.1:6379> zcard class  # 获取有序集合的成员数
(integer) 4
127.0.0.1:6379> ZSCORE class zhangsan # 查看某个成员的分数
"46"
127.0.0.1:6379> ZRANK class wangwu # 查看成员的排名 从小到大排序
(integer) 2
127.0.0.1:6379> ZREVRANK class wangwu # 查看成员的排名 从大到小排序
(integer) 1
127.0.0.1:6379> zcount class 40 70 # 计算在有序集合中指定区间分数的成员数
(integer) 1
127.0.0.1:6379> zrangebyscore class 40 70  # 通过分数返回有序集合指定区间内的成员
1) "zhangsan"
127.0.0.1:6379> zrevrangebyscore class 100 0 # 返回有序集中指定分数区间内的成员,分数从高到低排序
1) "fengfeng"
2) "wangwu"
3) "zhangsan"
4) "lisi"
127.0.0.1:6379> zrange class 0 -1 withscores  # 把成员和分数一起显示出来
1) "lisi"
2) "36"
3) "zhangsan"
4) "46"
5) "wangwu"
6) "76"
7) "fengfeng"
8) "88"
127.0.0.1:6379> zrem class fengfeng  # 移除一个成员
(integer) 1
127.0.0.1:6379> zrange class 0 -1
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> ZREMRANGEBYRANK class 0 0  # 移除有序集合中给定的排名区间的所有成员(第一名是0)(低到高排序) 现在是把lisi移除了 
(integer) 1
127.0.0.1:6379> zrange class 0 -1
1) "zhangsan"
2) "wangwu"
127.0.0.1:6379> ZREMRANGEBYSCORE class 40 50 # 移除有序集合中给定的分数区间的所有成员
(integer) 1
127.0.0.1:6379> zrange class 0 -1
1) "wangwu"
127.0.0.1:6379> ZSCORE class wangwu
"76"
127.0.0.1:6379> ZINCRBY class 2 wangwu  # 给王五+2
"78"
127.0.0.1:6379> ZINCRBY class -2 wangwu # 给王五-2
"76"
127.0.0.1:6379> ZSCORE class wangwu
"76"

使用场景

  1. 排行榜
  2. 订单支付超时(下单时插入,member为订单号,score为订单超时时间戳,然后写个定时任务每隔一段时间执行zrange)

通过go操作五大数据类型

前面会了命令之后,使用go去操作redis

你会发现和命令的使用方式一模一样

String

var DB *redis.Client
func RedisString() {
  DB.Set("name", "枫枫", 0) // 0 就是永不过期
  stringCmd := DB.Get("name")
  fmt.Println(stringCmd.Val())          // 枫枫
  fmt.Println(stringCmd.Result())       // 枫枫 nil
  fmt.Println(DB.Exists("name").Val())  // 1
  fmt.Println(DB.Exists("name1").Val()) // 0
  DB.Set("age", 12, 0)
  DB.Incr("age")                     // 自增1
  DB.IncrBy("age", 10)               // 自增10
  fmt.Println(DB.Get("age").Int64()) // 23
  fmt.Println(DB.TTL("name").Val())  // -1s
  DB.Expire("name", 2*time.Second) // 设置2秒的过期时间
  fmt.Println(DB.TTL("name").Val()) // 2s
  time.Sleep(2 * time.Second)
  fmt.Println(DB.TTL("name").Val()) // -2s
}

List

func RedisList() {
  DB.RPush("list", "zhangsan", "lisi", "wangwu", "xiaoming")
  fmt.Println(DB.LLen("list").Val())          // 4
  fmt.Println(DB.LRange("list", 0, -1).Val()) // [zhangsan lisi wangwu xiaoming]
}

Hash

func RedisHash() {
  DB.HSet("info", "文章1", 23)
  fmt.Println(DB.HGet("info", "文章1").Int64())  // 23
  fmt.Println(DB.HGetAll("info").Val())        // map[文章1:23]
  fmt.Println(DB.HKeys("info").Val())          // [文章1]
  fmt.Println(DB.HExists("info", "文章1").Val()) // true
  fmt.Println(DB.HExists("info", "文章2").Val()) // false
  fmt.Println(DB.HLen("info").Val())           // 1
}

Set

func RedisSet() {
  DB.SAdd("set", "a", "b", "c", "d")
  fmt.Println(DB.SIsMember("set", "a").Val()) // true
  fmt.Println(DB.SMembers("set").Val())       //[b d c a] 无序的

  DB.SAdd("set1", 1, 2, 3)
  DB.SAdd("set2", 2, 3, 4)
  fmt.Println(DB.SDiff("set1", "set2").Val())  // 差集
  fmt.Println(DB.SInter("set1", "set2").Val()) // 交集
  fmt.Println(DB.SUnion("set1", "set2").Val()) // 并集
}

Zset

func RedisZset() {
  DB.ZAdd("class", redis.Z{Score: 80, Member: "张三"}, redis.Z{Score: 46, Member: "李四"})
  fmt.Println(DB.ZRange("class", 0, -1).Val())                                          // [李四 张三]
  fmt.Println(DB.ZRevRange("class", 0, -1).Val())                                       // [张三 李四]
  fmt.Println(DB.ZRangeByScore("class", redis.ZRangeBy{Min: "0", Max: "100"}).Val())    // [李四 张三]
  fmt.Println(DB.ZRevRangeByScore("class", redis.ZRangeBy{Min: "0", Max: "100"}).Val()) //  [张三 李四]
  fmt.Println(DB.ZRangeWithScores("class", 0, -1).Val())                                // [{46 李四} {80 张三}]
  fmt.Println(DB.ZScore("class", "李四").Val())                                           // 46
}

redis常用命令

这都是一些很常用的一些命令

127.0.0.1:6379> select 2 # 切数据库
OK
127.0.0.1:6379[2]> select 0
OK
127.0.0.1:6379> keys *  # 查看所有的key
1) "zset"
2) "order"
127.0.0.1:6379> keys s* # 查看s开头的key
1) "set"
2) "set2"
3) "set1"
127.0.0.1:6379> ping # 查看服务是否运行
PONG
127.0.0.1:6379> move name 1 # 将当前数据库的key移动到数据库1当中
(integer) 1
127.0.0.1:6379[1]> PERSIST name # 移除key的过期时间,key永不过期
(integer) 1
127.0.0.1:6379[1]> type name # 查看key的数据类型
string
127.0.0.1:6379[1]> type info
hash
127.0.0.1:6379[1]> type class
zset
127.0.0.1:6379[1]> type set
set
127.0.0.1:6379[1]> type list
list
127.0.0.1:6379[1]> CLIENT LIST  # 获取连接到服务器的客户端连接列表
id=3 addr=127.0.0.1:52216 fd=8 name= age=386 idle=0 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=4 addr=127.0.0.1:52377 fd=7 name= age=5 idle=3 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
127.0.0.1:6379[1]> dbsize # 获取当前数据库key的数量
(integer) 5
127.0.0.1:6379[1]> FLUSHDB # 删除当前数据库的所有key
OK
127.0.0.1:6379[1]> FLUSHALL  # 删除全部数据库的所有key
OK


事务

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

因为我们的程序是并发的,你在一个程序里面设置值,然后取值,这很正常

但是如果并发存在,那么肯定就会存在,取值的时候不是我自己设置的那个值

基于上面的问题,那我在一个客户端操作的时候,把所有的指令一次性按照顺序排他的放在一个队列中,执行完了之后再让其他的客户端操作

127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name ff
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) "ff"

取消事务

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379>
127.0.0.1:6379> set age 12
QUEUED
127.0.0.1:6379> discard  # 取消事务
OK
127.0.0.1:6379> get age  # 数据未改动
(nil)

如果在事务过程中,存在命令性错误,则执行exec的时候,所有命令都不会执行

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set age 12
QUEUED
127.0.0.1:6379> x
(error) ERR unknown command 'x'
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

go处理redis事务

和命令操作稍微有点不太一样

func PipeLine() {
  tx := DB.TxPipeline()
  tx.Set("name", "枫枫", 0)
  val := tx.Get("name")
  fmt.Println(val.Val()) // 这里是获取不到结果的
  _, err := tx.Exec()
  if err != nil {
    fmt.Println("提交事务失败", err)
    return
  }
  fmt.Println(DB.Get("name").Val()) // 获取事务结果
  fmt.Println(val.Val())            // 获取事务结果
}

多个客户端有可能同时操作同一组数据,并且该数据一旦被操作修改后,将不适用于继续操作

在操作之前锁定要操作的数据,一旦发生变化,终止当前操作

127.0.0.1:6379[1]> set price 100
OK
127.0.0.1:6379[1]> WATCH price // 检测这个key,如果key在事务期间发生了变化,则事务不成功
OK
127.0.0.1:6379[1]> MULTI
OK
127.0.0.1:6379[1]> set price 101
QUEUED
127.0.0.1:6379[1]> get price
QUEUED
127.0.0.1:6379[1]> exec // 在exec之前,再用一个客户端去修改这个price的值
(nil)
127.0.0.1:6379[1]> get price
"1"

go处理redis锁

func RedisWatch() {
  key := "price"
  err := DB.Watch(func(tx *redis.Tx) error {
    n, err := tx.Get(key).Int()
    // 判断是不是存在错误
    if err != nil && err != redis.Nil {
      return err
    }
    _, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
      // price + 1
      // 再这个期间去修改price的值
      time.Sleep(10 * time.Second)
      pipe.Set(key, n+1, 0)
      return nil
    })
    return err
  }, key)
  fmt.Println(err)
}

持久化

Redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失

辛好Redis还为我们提供了持久化的机制,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上;

AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。

redis数据存磁盘的过程:

  1. 客户端向服务端发送写操作(数据在客户端的内存中)。
  2. 数据库服务端接收到写请求的数据(数据在服务端的内存中)。
  3. 服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
  4. 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
  5. 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。

RDB持久化

默认是开启的,默认的存储文件是 dump.rdb

可以修改这个文件的名称

dbfilename dump.rdb
dir ./

触发rdb备份

  1. 配置自动备份

默认是 一分钟内修改了一万次,5分钟内修改了10次,30分钟内修改了1次

save 3600 1
save 300 100
save 60 10000

  1. 手动命令备份

save:save时只管保存,其他不管,全部阻塞,手动保存,不建议使用。

bgsave:redis会在后台异步进行快照操作,快照同时还可以响应客户端情况。

可以使用lastsave命令获取最后一次成功生成快照的时间(时间戳)

AOF持久化

AOF默认是关闭的,需要手动开启的。开启方式:修改redis.conf配置文件来开启AOF

如果是phpstudy的话,要改redis目录的conf文件,改软件里面的那个配置文件每次重启都没有了

这是我的目录,D:\client\phpstudy\phpstudy_pro\Extensions\redis3.0.504

重启之后,在这个目录下就有一个 appendonly.aof文件

appendonly yes
appendfilename "appendonly.aof"
appendfsync always
dir ./   

appendfsync always:每次写入立即同步

appendfsync everysec:每秒同步

appendfsync no:不主动同步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值