官网:Redis
go-redis:redis package - github.com/go-redis/redis/v8 - Go Packages

utools markdown存储位置 - 搜索

1. Linux 安装及使用

  • 安装:

    1
    2
    3
    4
    5
    # 安装
    sudo apt-get install redis
    # 使用
    redis-cli ping
    redis
  • 启动redis,测试是否联通:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 指定配置文件启动redis-server
    root@hecs-264482:~# redis-server /etc/redis/redis.conf
    % 指定端口号启动redis-cli
    root@hecs-264482:~# redis-cli -p 6379

    # 查看是否联通

    127.0.0.1:6379> ping
    PONG

    # 查看所有的key

    127.0.0.1:6379> keys *
    1. "username"
    2. "mylist"
  • 查看服务是否已经启动:
    ps -ef|grep redis
    xo_gallery/main/gallery/20230302181609.png)

  • 关闭redis:

    1
    127.0.0.1:6379> shutdown

2. Redis 基本知识

1
2
3
4
5
6
7
8
9
10
11
12
# 切换数据库(Redis共有16个数据库)
select 1

# 查看当前数据库的所有键
keys *

# 查看当前数据库的大小
dbsize

# 清空数据库
flushdb
flushall

Redis 是单线程的!
Redis 是基于内存操作,CPU不是Redis的性能瓶颈
Redis是单线程的为什么还会这么快?
核心:Redis 是将所有数据全部存入内存中的,所以说用单线程去操作就是最快的!
(因为在多线程中,CPU需要频繁的上下文切换,切换操作同样需要一定的时间,对于内存系统,没有上下文切换时效率就是最高的!)

五大基本数据类型

1. String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
##################################################
set key value

get key

# 设置失效时间
expire key sec

# 查看剩余时间
tll key

exists key

# 追加字符,如果不存在,则新建一个
append key world

# 求字符串长度
strlen key

##################################################
# 自增/自减 1
incr views
decr views

# 自增/自减指定步长
incrby views 10
decrby views 10

# 截取字符串 [start, end]
getrange key start end
# 替换指定位置开始的字符串
setrange key offset value

##################################################
# 设置过期时间
setex key second value
# 不存在再设置,存在则不设置
setnx key val

##################################################
# 批量设置值
mset k1 v1 k2 v2 ...

# 批量获取值
mget k1 k2 ...

# 同时设置值,原子性操作,要么同时成功,要么同时失败
msetnx k1 v1 k2 v2 ...

##################################################
# 设置对象
set user:1 "{name:wxs, age:3}"

mset user:1:name wxs user:1:age 23
mget user:1:name user:1:age
1) "wxs"
2) "23"

##################################################
# 获取值并设置值
getset key value

2. List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
##################################################
# 列表左侧/右侧添加元素
LPUSH key val
RPUSH key val
# 列表左侧/右侧移除元素
LPOP key
RPOP key
# 查看列表长度
LLEN key
# 移除列表指定元素
LREM key conut val
# 截取列表内容 key[start,end]
LTRIM key start end
# 设置已经存在列表中的某个位置的值(key不存在则报错,所以要先判断列表存在不存在)
LSET key index val
# 在列表某个值前/后插入元素
LINSERT key BEFORE|AFTER val newVal

# 示例
127.0.0.1:6379> LPUSH list 1
(integer) 1
127.0.0.1:6379> LPUSH list 2
(integer) 2
127.0.0.1:6379> LPUSH list 3
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "3"
2) "2"
3) "1"

##################################################
# 移除列表最后一个元素,并移动到其他列表中
RPOPLPUSH src dest
1
2
3
4
5
**实际上是一个链表**

在两边插入值/改动值效率最高!操作中间元素效率偏低

既可以作为队列(消息队列),又可以作为栈

3. Set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
##################################################
# 集合内添加元素
SADD key val

# 查看集合内元素
SMEMBERS key

# 查看集合内是否存在某个值(1:存在,0:不存在)
SISMEMBER key val

# 查看集合长度
SCARD key

# 从集合中删除元素
SREM key val

##################################################
# 随机取出元素
SRANDMEMBER key [count]

# 随机删除元素
SPOP key [count]

# 将一个值从一个集合中移动到另一个集合中
SMOVE key1 key2 val

##################################################
# 求差集
SDIFF key1 key2 ...

# 求交集(例如求共同关注)
SINSERT key1 key2 ...

# 求并集
SUNION key1 key2 ...

4. Hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
##################################################
# 往Hash表key中添加字段(field,val)
hset key field val

# 查看Hash表中的字段field
hget key field

# 批量设置/获取键值对
hmset key field val [field val...]
hmget key field [field...]

# 获取所有键值对
hgetall key

# 删除Hash表指定的key-val
hdel key field

# 查看Hash表长度
hlen key

# 判断Hash表中的key是否存在
hexists key field

# 获取所有的key/val
hkeys key
hvals key

# 自增/自减Hash表字段
hincrby key field increment
hdecrby key field decrement

# 如果不存在则设置成功,存在则设置失败
hsetnx key field val
1
Hash更适合于对象的存储!

5. Zset

在 set 的基础上增加了一个值,变成一个有序集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 添加值
zadd key [NX|XX] [CH] [INCR] score member [score member ...]

# 查看集合
zrange key start end

# 实现排序 (min/max 可以是 -inf/+inf:即正/负无穷)
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

# 移除元素
zrem key member

# 获取有序集合中的个数
zcard key

# 获取某个区间的值
zcount key min max

三种特殊类型

1. Geospatial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 只有六个命令

# 添加地理位置 (经度、纬度、成员)
# 两级无法添加,一半会下载城市数据,直接编写代码一次性读入
geoadd key longitude latitude member ...

# 获取经纬度
geopos key member ...

# 查看两地距离,unit为单位,可以是 m、km...
geodist key member1 member2 [unit]

# 通过半径查询某经纬度周围的成员(可以用来实现查看附近的人的操作)
georadius key longitude latitude radius [unit] [withcoord] [withdist] [count num]

# 通过半径查询某成员周围的成员
georadiusbymember key member radius [unit] [withcoord] [withdist] [count num]

# 将二维经纬度信息转化为一维字符串
geohash key member ...
1
2
3
4
5
collapse: open

Geospatial 底层的实现原理其实就是 **Zset**

我们可以通过 Zset 命令来操作 Geo

2. Hyperloglog

概念引入:基数(不重复的元素)f
Hyperloglog 可以用来统计基数
可以用来统计网站的访问人数(UV),可以过滤同一用户的多次访问

1
2
3
4
5
6
7
8
# 添加元素
pfadd key val ...

# 统计长度
pfcount key

# 合并多个 key
pfmerge destkey sourcekey [sourcekey ...]
1
2
3
4
5
6
7
Hyperloglog 类似于特化的 Set,能够保存大量元素并统计去重后得元素个数

优点是**占用固定内存**,存放 $2^{64}$ 个元素也只需占用 12KB 内存

缺点是功能单一,且存在一定的**错误率**(0.81%)

如果允许**容错**,就一定可以使用 Hyperloglog!

3. Bitmap

位存储,类似于用 0/1 来当作 bool 变量存储逻辑值
如:全国 14 亿人每个人是否阳过,就可以用 14 亿个 bit 来存储信息

1
2
3
4
5
6
7
8
# 设置位的值
setbit key index val

# 查看某个位的值
getbit key index

# 查看值为 1 的位数
bitcount key [start end]

3. 事务

Redis 事务

事务本质:一组命令的集合
一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行!
特性:一次性、顺序性、排他性
MySQL中的事务:ACID
Redis 单条命令是保证原子性的,但是事务不保证原子性
Redis 事务没有隔离级别的概念,所以不存在类似 MySQL 中的幻读、脏读等情况
所有的命令在事务中并没有直接被执行!只有发起执行命令的时候才会执行
Redis 的事务:

  • 开启事务(multi
  • 命令入队(...
  • 执行事务(exec
  • 放弃事务(discard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
      127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> set k1 v1
    QUEUED
    127.0.0.1:6379(TX)> set k2 v2
    QUEUED
    127.0.0.1:6379(TX)> get k2
    QUEUED
    127.0.0.1:6379(TX)> set k3 v3
    QUEUED
    127.0.0.1:6379(TX)> exec
    1) OK

    2) OK

    3) "v2"

    4) OK

    1
    2
    3
    4
    5

    ```ad-tip
    "编译型异常"(代码语法就有问题,会被直接检查出来):事务中的**所有命令都不会执行**(如命令缺少参数等)

    "运行时异常":在执行命令时,有问题的命令会抛出异常,**其他命令会正常执行**(如对字符串执行加1操作等)

Redis 实现乐观锁

悲观锁

  • 很悲观,认为什么时候都可能会出现问题,所以无论做什么都会加锁!
  • 这样会大大影响程序执行效率

乐观锁

  • 很乐观,认为什么时候都不不会出问题,所以不会上锁!
  • 更新数据的时候会做出判断,在此期间是否有人修改过数据(在 MySQL 中通过 version 字段实现)

监控

  • 在事务开启前,对 key 进行监控(相当于 加锁/getVersion()

  • 如果启动事务后,被监控的 key 被其他线程修改,则事务必定执行失败!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    set money 10
    # 监控 key

    watch money

    # 开启事务

    multi

    incrby money 10

    # 此时其他线程对 money 进行了修改

    exec
    (nil)

    # 执行失败以后,重新执行需要先放弃监控

    unwatch money

    watch money


4. Redis 持久化

1
**工作和面试的重点!**

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘中,一旦服务器进程退出,服务器中的数据库状态也会消失,所以 Redis 提供了持久化功能

RDB(Redis DataBase)

在 RDB 模式下,Redis 会将数据库状态保存在文件:dump.rdb 中,在 Redis 启动时会自动检查 dump.rdb 文件,恢复其中的数据!

1
2
3
4
5
6
7
8
9
# dump.rdb文件位置:
# 在redis客户端:
127.0.0.1:6379> config get dir
1) "dir"
2) "/var/lib/redis"

# 在终端:
find / -name dump.rdb
/var/lib/redis/dump.rdb

RDB工作流程:

触发生成 dump.rdb 的规则:

  1. 满足配置文件中设置的 save 规则

    1
    2
    # 每3600s对key有1次操作就生成一次 dump.rdb,以此类推
    save 3600 1 300 100 60 10000
  2. 执行 flushall 命令

  3. 退出 redis

优点:
1. 适合大规模的数据恢复
2. 如果对数据完整性要求不高时可以使用
缺点:
1. 需要一定的时间间隔进行操作,如果在时间间隔内 redis 宕机,则数据就消失了
2. fork 进程的时候需要占用一定内存空间

AOF(Append Only File)

所有命令(不包括读操作)以日志的形式记录下来,保存在一个文件中(appendonly.aof),Redis 会在启动之初根据这个日志文件的内容将写指令从前到后执行一次以完成数据恢复工作!

默认是不开启的,只需要将 redis.conf 文件中的 appendonly no 改为 yes ,重启即可生效。

如果 appendonly.aof 有错误,Redis 是启动不起来的,我们可以通过工具 redis-check-aof --fix 来修复文件

AOF 工作流程:

1
2
3
4
5
6
7
8
9
10
# 三种同步方式 

# appendfsync always
appendfsync everysec
# appendfsync no

# 重写规则
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

三种同步方式各自优点:
1. 每次修改都同步,文件完整性更好
2. 每秒同步一次,可能会丢失 1s 的数据
3. 从不同步,效率最高
缺点:
1. 对于数据文件来说,AOF 远远大于 RDB,修复的速度也比 rdb 慢
2. AOF 运行效率比 RDB 慢

拓展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
collapse: open
1、RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储

2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Rdis协议追加保存每次写的操作到文件末尾,Redisi还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

3、**只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化**

4、同时开启两种持久化方式

在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。

RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合

用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的BUG,留着作为一个万一的手段。

5、性能建议

因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。

如果Enable AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只Ioad自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。

如果不Enable AOF,仅靠Master-Slave Repllcation实现高可用性也可以,能省掉一大笔IO,也减少了rewriteB时带来的系统波动,代价是如果 Master/Slave 同时宕机,会丢失十几分钟的数据,启动脚本要比较两个 Master/Slave 中的 RDB 文件,载入较新的那个。(微博就是这种架构)

5. Redis 发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。类似于微博、微信等的关注系统。

发布订阅命令:

订阅端:

发布端:

使用场景:

  1. 实时消息系统
  2. 实时聊天(频道当作聊天室,将信息回显给所有订阅者)
  3. 订阅、关注系统
1
稍微复杂的场景,就会使用一些消息中间件,如 MQ、KAFKA 等

6. 主存复制💖

7. Redis 缓存穿透和雪崩