redis基础

记录学习redis的一点基础东西

基础

介绍

Redis是一个开源的高性能的key-value存储系统,主要解决海量数据和高并发需求。具有以下特点:

  1. Redis是非关系型(NoSQL)数据库。本质上也是数据库,但MySQL关系型数据库存储时必须定义数据词典,而Redis则不需要,它是作为关系型数据库的补充。
  2. Redis是**基于内存**的,所以比基于硬盘的MySQL要快很多,但非常吃内存
  3. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
  4. Redis提供String,list,set,sorted set,hash等数据结构的存储。
  5. Redis支持数据的备份,即master-slave模式的数据备份。
  6. 内部采用单线程机制工作。
  7. Redis是远程的,有客户端和服务端,我们一般说的是服务端。

Redis优势:

1、性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
2、丰富的数据类型 – Redis支持二进制案例的 String, List, Hash, Set 及 Sorted Set 数据类型操作。
3、原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
4、丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性

Redis的应用:

  1. 为热点数据加速查询
  2. 任务队列:如秒杀,抢票
  3. 即时信息查询:如排行榜,在线人数信息
  4. 时效信息:如验证码,投票信息
  5. 分布式数据共享

命令行基本操作:

set/get、clear、help

数据类型

string

简单的 key-value 键值对,value 不仅可以是 String,也可以是数字。String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr, decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

常用指令:

  1. SET key value //设置key=value
  2. GET key //获得key对应的value
  3. DEL key //删除
  4. mset key1 value1 key2 value2 //同时设置多个键值
  5. mget key1 key2 //同时获得多个值
  6. strlen key //获取key对应的value的字符串长度
  7. append key value //在key后追加上value,如果key不存在,就创建该键值对
  8. incr key //如果key对应的value是个数,就加一,否则报错
  9. incrby key increment //value增加increment
  10. Incrbyfloat key increment //用于增加小数
  11. decr key //value减一
  12. decrby key increment

hash

Key对应的Value内部实际就是一个HashMap,Value就是多对field和value,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,当成员数量增大时会自动转成真正的HashMap

使用hgetall时如果field过多,速度会非常慢

最多含有2^32 - 1个键值对

操作

  1. hset key field value //设置一个key中的hash表
  2. hget key field
  3. hgetall key
  4. hdel key field1 [field2] //删除key对应的value中的一个(或多个)键值对
  5. hmset、hmget //对应的一次添加、删除多个键值对
  6. hlen key //获取key对应的hash中字段数量
  7. hexists key field //key中是否存在field字段
  8. hkeys key、hvals key //获取key对应的hash表的所有键,获取key对应的hash表的所以值
  9. hincrby key field increment //增加指定字段的值

list

是简单的字符串列表,可以类比到C++中的std::list,简单的说就是一个链表或者说是一个队列。可以从头部或尾部向Redis列表添加元素。列表的最大长度为2^32 - 1,也即每个列表支持超过40亿个元素。

Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。获取全部列表时可以将结束索引设置为-1

应用:

Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表、粉丝列表等都可以用Redis的list结构来实现,再比如有的应用使用Redis的list类型实现一个简单的轻量级消息队列,生产者push,消费者pop/bpop。

操作:

  1. lpush key value1 [value2]、rpush key value //左插入,右插入
  2. lrange key start stop //获取一段范围内的
  3. lindex key index //获取指定位置的
  4. llen key //获取key对应的列表的长度
  5. lpop key、rpop key //删除
  6. blpop key1 [key2] value timeout //阻塞删除,在规定时间内删除,即在规定时间内只要队列中放入值就可以删除
  7. lrem key count value //移除key对应的列表中count个value值

set

可以理解为一堆值不重复的列表,类似数学领域中的集合概念,且Redis也提供了针对集合的求交集、并集、差集等操作。

set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

操作:

  1. sadd key member1 [member2] //添加数据
  2. smembers key //获取key对应的set
  3. srem key member1 [member2] //删除
  4. scard key //获取key对应的set中元素的个数
  5. sismember key member //判断member是否是key对应的set中的值
  6. srandmember key count //随机获取key对应的set中的count个元素
  7. spop key //随机获取元素并删除
  8. sinter key1 [key2]、sinterstore destination key1 [key2] //求交集(并存到destinatio中)
  9. sunion key1 [key2]、sinterstore destination key1 [key2] //求并集(并存到destinatio中)
  10. sdiff key1 [key2]、sinterstore destination key1 [key2] //求差集(并存到destinatio中)
  11. smove source destination member //将member从一个set移到另一个

sorted_set

有序集合类似Redis集合,不同的是增加了一个功能,即集合是有序的。一个有序集合的每个成员带有分数,用于进行排序

Redis有序集合添加、删除和测试的时间复杂度均为O(1)(固定时间,无论里面包含的元素集合的数量)。列表的最大长度为2^32- 1元素(4294967295,超过40亿每个元素的集合)。

Redis sorted set的内部使用**HashMap和跳跃表(SkipList)**来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

使用场景:

又比如用户的积分排行榜需求就可以通过有序集合实现。还有上面介绍的使用List实现轻量级的消息队列,其实也可以通过Sorted Set实现有优先级或按权重的队列。

操作:

  1. zadd key score1 member1 [score2 member2] //添加数据
  2. zrange key start stop [withscores] //获取正序排列中指定个数的值
  3. zrevrange key start stop [withscores] //获取反序排列中指定个数的值
  4. zrem key member1 [member2] //删除key对应的有序set中指定的member
  5. zrangebyscore key min max [withscores] [limit] //获取正序set中指定范围
  6. zremrangebyrank key start stop //删除指定个数的值
  7. zremrangebyscore key min max //删除指定范围的值
  8. zcard key //获取key对应的sorted_set中的个数
  9. zcount key min max //获取指定范围中的个数
  10. zinterstore destination numkeys key1 [key2] //获得并存储numkeys个set的交集
  11. zunionstore destination numkeys key1 [key2] //获得并存储numkeys个set的并集
  12. zrank key member、zrevrank key member //获取member在集合中的排名
  13. zscore key member //获取key对应的set中member的值
  14. zincrby key increment member //member的score增加increment

通用指令

key通用指令

key是一个字符串,查询所用到的键,对于key可以设计关于自身状态、有效性、快速查询相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
del key			//删除key
exists key //是否存在
type //获取key对应值的类型

expire key seconds //设置一个key的有效时间
pexpire key milliseconds //有效时间是毫秒
expireat key timestamp //有效时间是时间戳
ttl //获取一个key剩余存活时间,永存的返回-1,过期了返回-2
pttl //返回的是毫秒
persist key //将一个key设置为永存

keys pattern //查询有哪些key,pattern类似正则表达式,*是任意字符任意长度,?是任意字符一个,[]是括号内的任意一个,用法如下图

rename key newkey //改名字,如果已存在会覆盖
renamenx key newkey //改名字,如果已存在则失败
sort //对list,set,sorted_set的值排序
trWO1S.png

数据库通用指令

redis为每个服务提供16个数据库,从0到15,每个数据库之间相互独立

1
2
3
4
5
6
7
8
9
10
11
select index 		//	切换数据库

quit //退出
ping //检查是否连上数据库
echo //进行打印

move key db //将一个key转移到另一个数据库中,如果目标库中已存在key,则转移失败

dbsize //获取数据库中key的个数
flushdb //删除现在所处数据库中数据
flushall //删除所有库中的数据

Jedis

Jedis是redis的java版本的客户端实现,提供了连接池管理。一般不直接使用jedis,而是在其上在封装一层,作为业务的使用。

jedis里方法和redis指令一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
public JedisPool(GenericObjectPoolConfig poolConfig,
String host,
int port);

public static Jedis getJedis() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(30);
jedisPoolConfig.setMaxIdle(10);
String host = "127.0.0.1";
int port = 6379;
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port);
return jedisPool.getResource();
}

高级

持久化

什么是持久化

Redis 的数据 全部存储内存 中,如果 突然宕机,数据就会全部丢失,因此必须有一套机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的 持久化机制,它会将内存中的数据库状态 保存到磁盘 中:

  1. 客户端向数据库 发送写命令 (数据在客户端的内存中)
  2. 数据库 接收 到客户端的 写请求 (数据在服务器的内存中)
  3. 数据库 调用系统 API 将数据写入磁盘 (数据在内核缓冲区中)
  4. 操作系统将 写缓冲区 传输到 磁盘控控制器 (数据在磁盘缓存中)
  5. 操作系统的磁盘控制器将数据 写入实际的物理媒介(数据在磁盘中)

一般有两种持久化方法,保存当前数据状态——快照和保存操作过程——日志:

RDB

快照,直接将当前数据库中数据全部保存下来放在一个.rdb文件中,有两种执行方式:

  • 通过save指令手动保存,可以在配置文件中设置保存路径等参数。因为redis是单线程的,在save时就可能造成阻塞
  • 通过bgsave指令手动保存,该指令是在后台fork一个子进程来创建rdb文件

在配置文件中通过save second changes 来自动执行bgsave指令,在规定时间内达到规定的变化次数就进行保存。

注:save只有在数据真正发送变化时才会进行保存,比如get指令没有改变数据,所以就不计入changes中。

在重新启动redis时通过配置文件就会自动恢复数据了。

优点

  1. rdb是二进制文件,所以保存的文件很小
  2. 保存的是某个时间的快照,所以适合数据的备份
  3. 恢复数据速度较快

缺点

  1. 不能实时持久化,比如断电时就可能会丢失数据
  2. bgsave要fork子进程,牺牲性能
  3. redis不同版本的rdb文件格式不统一,可能会出现不同版本数据不兼容的问题

AOF

append only file,以独立日志方式记录每次写命令,服务启动时执行所有aof文件中的指令来恢复数据,实时性较强,是目前redis持久化主流方式。

过程:

image-20200608202237923

同步到AOF中时有三种条件:

  1. always:每执行一个写入操作保存一次(零误差,性能低)
  2. everysec:每秒保存一次(误差较小,性能高)
  3. no:由系统控制保存周期,不可控

需要在配置文件中设置:

1
2
appendonly yes|no		//打开或关闭AOF
appendsync always|everysec|no //设置写数据策略

AOF重写

随着写命令越来越多,文件越来越大,redis通过重写来压缩文件体积,同时也可以加快持久化速度和恢复速度。重写就是将对一个数据的若干操作指令转化成一个最终操作指令

重写规则:

  1. 忽略无效的命令,只保留最终数据写入的命令
  2. 进程内已超时的数据不再写入
  3. 对同一数据的多条写命令合并为一条命令

配置文件:

1
2
auto-aof-rewrite-min-size size		//当前缓冲的数据量达到规定值时重写
auto-aof-rewrite-percentage percentage

重写工作原理:

image-20200608204239519

RDB和AOF对比:

RDB AOF
占用储存空间 小(压缩数据) 大(保存指令)
存储速度
恢复速度
数据安全性 会丢失数据 以策略决定
资源消耗
启动优先级

Redis 4.0 混合持久化

重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。

Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小,于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

事务

一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。 事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。 Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。用discard可以取消事务

事务流程:

image-20200610111226160

watch key1 [key2] 对key添加一个监视锁,在执行exec前如果key发生变化,则终止事务。必须在事务开始前监视

unwatch 取消对所有key的监视

setnx lock-key value 在操作一个key前先把它锁住

del lock-key 删除这个锁

expire lock-key second 为锁增加一个期限,到期后自动解锁

删除策略

过期删除

针对的是设置了有效期的数据,通过ttl指令可以得到其状态:

  1. xxxx: 表示还能存活xxxx时间
  2. -1:表示永久有效
  3. -2:表示已经过期或者不存在或者被删除了

redis服务器中使用了redisDb数据结构,其中有一个过期字典(expires): 保存数据库中所有键的过期时间,过期时间用UNIX时间戳表示,且值为long long整数

有三种删除策略:

  1. 定时删除:创建一个定时器,当过期时,定时器立刻对过期的key删除(时间换空间)
  2. 惰性删除:过期后不立刻进行删除,而是当下次访问该key时再删除(空间换时间)
  3. 定期删除:周期性轮询访问redis数据库中的数据的有效性,随机抽取删除![image-20200610154513240](/Users/sutrue/Library/Application Support/typora-user-images/image-20200610154513240.png)

RDB和AOF中对过期键的处理:

  • 生成RDB文件程序会数据库中的键进行检查,已过期的键不会保存到新创建的RDB文件中

  • AOF文件写入:当过期键被删除后,会在AOF文件增加一条DEL命令,来显式地记录该键已被删除。

  • AOF重写:已过期的键不会保存到重写的AOF文件中

淘汰删除

reids会设置一个最大存储值maxmemory ,表示占用物理空间的比例,每次存储时检查空间是否足够,当redis空间不足时进行淘汰删除,有三种策略

  1. 优先删除易失数据

    1
    2
    3
    4
    volatile-lru:挑选最久未使用的数据
    volatile-lfu:挑选最少使用的数据
    volatile-ttl:挑选快过期的数据
    volatile-random:随机挑选数据
  2. 一视同仁,检查全库

    1
    2
    3
    allkeys-lru
    allkeys-lfu
    allkeys-random
  3. no-enviction:禁止驱逐,就是不淘汰数据
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    通过在配置文件中进行设置:`maxmemory-policy volatile-lru `



    ## 高级数据类型

    ### Bitmaps

    当只想储存一个数据的状态时,比如有或无,是或否这种,用bitmaps可以节省空间。

    它是一个字符串,但是只有0或1,每一位代表一个数据的状态,提供下面一些接口来操作:

    getbit key offset //获取key对应偏移位上的bit值 setbit key offset value //设置key对应偏移位上bit值,0或1

bitop op destKey key1 [key2] //对指定的多个key进行位操作,op有or and xor not四种
bitcount key [start end] //统计key中指定范围内的bit为1的个数

1
2
3
4
5
6
7



### HyperLogLog

存储一个列表,只统计基数,就是不含重复值。操作:

pfadd key element [element2] //添加
pfcount key [key] //统计数据
pfmerge destKey key1 [key2] //合并key对应的数据

1
2
3
4
5
6
7
8
9
10
11

但是它通过估算算法,存在一定误差



### GEO

GeoHash 算法是用于 **地理位置距离排序** 的一个算法,将 **二维的经纬度** 数据映射到 **一维** 的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。

在redis中通过这个算法存储位置信息,并提供一些操作计算距离,范围内的值个数等。

geoadd key longitude latitude member //添加坐标点,注意是在一个key中添加多个点,不同key中的点无法进行计算
geopos key member //获取坐标
geodist key member1 member2 //计算距离

georadius key longitude latitude radius m|km|mi //根据坐标求范围内的数据
georadiusbymember key member radius m|km|mi //根据点求范围内的数据
geohash key member //获取指定点对应的坐标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
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95



# 集群

## 主从复制

到目前为止,我们所学习的 Redis 都是 **单机版** 的,这也就意味着一旦我们所依赖的 Redis 服务宕机了,我们的主流程也会受到一定的影响,这当然是我们不能够接受的。

**主从复制**,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 **主节点(master)**,后者称为 **从节点(slave)**。且数据的复制是 **单向** 的,只能由主节点到从节点。Redis 主从复制支持 **主从同步** 和 **从从同步** 两种,后者是 Redis 后续版本新增的功能,以减轻主节点的同步负担。

### 主从复制主要的作用

- **数据冗余:** 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- **故障恢复:** 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 *(实际上是一种服务的冗余)*。
- **负载均衡:** 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 *(即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点)*,分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
- **高可用基石:** 除了上述作用以外,主从复制还是哨兵和集群能够实施的 **基础**,因此说主从复制是 Redis 高可用的基础。



**配置文件**:在从服务器的配置文件中加入:`slaveof <masterip> <masterport>`

### 工作流程

分为三个阶段:

1. 建立连接阶段

使得slave端保存master的ip和端口号,master保存slave的端口号

<img src="/Users/sutrue/Library/Application Support/typora-user-images/image-20200611205048783.png" alt="image-20200611205048783" style="zoom: 33%;" />

2. 数据同步阶段

slave初次连接master后,复制master所有的数据到slave

<img src="/Users/sutrue/Library/Application Support/typora-user-images/image-20200611210003786.png" alt="image-20200611210003786" style="zoom:33%;" />

3. 命令传播阶段

在建立连接和完成复制后,使用心跳机制进行主从之间的数据同步

<img src="/Users/sutrue/Library/Application Support/typora-user-images/image-20200611210224702.png" alt="image-20200611210224702" style="zoom:33%;" />

### 常见问题

1. 频繁的全量复制
2. 频繁的网络中断
3. slave间数据不同步



## 哨兵

Sentinel(哨兵)是一个分布式系统,用于对主从服务器的每台服务器进行监视,在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器并将所有从服务器连到新的主服务器。

### 作用

1. 监控:不断检测master和slave情况
2. 通知:当被监控哨兵出现问题时向其他的哨兵,客户端发送通知
3. 自动故障转移:从从服务器中选举出新的主服务器并将所有从服务器连到新的主服务器

启动哨兵:`redis-sentinel sentinel-端口号.conf`

### 工作原理

1. 监控阶段:
1. 哨兵先给master发送一个info指令拿到master所有信息,并建立连接
2. 从master信息里得到它的slave信息,再去连接slave
3. 新的哨兵也给master发info并连接它,然后发现这个master已经有哨兵了,就会根据信息去找那个哨兵并在哨兵之间建立连接
4. 再去连接slave
2. 通知阶段:
1. 所有的哨兵都会不停的访问所有的master,确认其是否存活
2. 在一个哨兵发现某个master断了后会告诉其他的所有哨兵
3. 其他的哨兵再去访问该master,确定其是否真的断了,如果超过半数认为它断了,就确定它断了
3. 故障转移阶段:
1. 在哨兵中推选出一个负责处理故障的哨兵
2. 该哨兵在备选服务器中选择一个master,挑选原则是:
1. 在线的
2. 响应快的
3. 与原master断开时间最近的
4. 优先级高的:看偏移量offset,服务器ID runid
3. 发送指令给被选中的slave,进行钦定
4. 发送指令被选定的slaveid给其余的所有slave,让他们连接新主



## 集群

在速度或者容量达不到要求时用集群解决。用网络将若干计算器连接起来提供服务,提供统一管理方式。

集群中的每个服务器都有自己的存储空间,地址互不相同,同时每个服务器记有别的服务器的存储空间信息。所以最多2次可以查找到想要的数据(先去a中找,a中不存在的话,会告诉在哪个地方,然后去那个地方找)

**指令配置文件**

cluster-enable yes
cluster-config-file nodes-port.conf
cluster-nodes-timeout 10000 //超时等待时间

1
2
3

通过一个脚本文件trib.rb创建集群

redis-cli -c //启动集群中的客户端


集群中master断了后会自动选取一个slave顶上,如果之前的master连上了,会成为slave