地址

Redis视频教程

DAL
dal是数据访问层的英文缩写,即为数据访问层(Data Access Layer)。
其功能主要是负责数据库的访问。简单地说就是实现对数据表的Select(查询)、Insert(插入)、Update(更新)、Delete(删除)等操作。

数据库架构

最简单的数据库架构

1560694324080

上述架构下,我们来看看数据存储的瓶颈是什么?

  1. 数据量的总大小一个机器放不下时
  2. 数据的索引(B+Tree)一个机器的内存放不下
  3. 访问量(读写混合,读和写都在同一库)一个实例不能承受

Memcached(缓存)+MySQL+垂直拆分的架构

1560694618982

大量的使用缓存技术来缓解数据库的压力,优化数据库的结构和索引。
开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续增大的时候,多台Web机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品

Mysql主从读写分离架构

  • 主从复制
    (主库有什么操作,从库迅速跟随)
  • 读写分离

由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。
读写集中在一个数据库上让数据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性.
Mysql的master-slave模式成为这个时候的网站标配了。

1560695019682

简单来讲就是:
写的操作放在主库,读的操作都在从库去读

分表分库+水平拆分+mysql集群架构

Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时 MySQL的主库的写压力开始出现瓶颈,而数据量的持续猛增,由于 MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高并发 MySQL应用开始使用
InnoDB引擎代替 MyISAM

同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题。这个时候,分表分库成了一个热门技术。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然 MySQL推出了 MySQL Cluster集群,但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。

1560695378719

NoSQL

NoSQL:Not only SQL,不仅仅是SQL,泛指非关系型数据库

非关系型数据库的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

固定的模式:MySQL的一张表的字段总是固定的有限的

  • 冷数据,不常改变的数据
    使用MySQL
  • 多文字类
    使用MongDB
  • 热点高频信息:
    使用Redis

NoSQL的四个分类:

  • KV键值
  • 文档型数据库
  • 列存储数据库
  • 图关系数据库

传统的ACID:

  • A(Atimicity):原子性
  • C(Consitency):一致性
  • I(Isolation):独立性
  • D(Durability):持久性
  • 原子性:
    事务里的所有操作要么全部做完,要么都不做
  • 一致性:
    数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束
  • 独立性:
    并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。
    比如现有有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加的100元
  • 持久性:
    持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上出现岩机也不会丢失

NoSQL的CAP:

  • C(Consistency):强一致性
  • A(Availability):可用性
  • P(Parition tolerance):分区容错性

注意:NoSQL并不能同时做到上面三点,只能三选二.而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡.
现在大多数网站架构的选择都是AP

为什么都是选择AP:
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多Web应用来说,并不要求这么高的实时性,比方说发一条消息之后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。

BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。

  • 基本可用(Basically Available)
  • 软状态(Soft state)
  • 最终一致(Eventually consistent)

它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法

分布式和集群的区别:

  • 分布式:不同的多台服务器上面部署不同的服务模块(工程),他们之间通过RpC/Rmi之间信和调用,对外提供服务和组内协作。
  • 集群:不同的多台服务器上面部署相同的服务模块、通过分布式调度软件进行统一的调度,对外提供服务和访问。

Redis基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 切换到5号数据库
select 5

# 查看数据库大小
DBSIZE

# 查询数据库的所有keys
keys *

# 查询以k开头的key
# 也可以使用keys k??
keys k*

# 清空当前数据库
FLUSHDB

# 清空所有数据库
FLUSHALL

1560737187994

1
2
3
4
5
# 根据键获取值:
get key1

# 设置:
set key2 val2

Redis五大数据类型:

  • 字符串String
  • 列表List
  • 集合Set
  • 哈希Hash
  • 有序集合Zset(sorted set)

二进制安全是什么?
比如Redis的String类型就是二进制安全的,意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象

Key键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 查询全部的键
keys *

# 判断某个key是否存在
exists key1 key2 key3

# 将某个key1移动到3号数据库
move key1 3

#为给定的key3设置10秒的过期时间
expire key3 10

# 查看还有多少秒过期(-1为永不过期,-2为已过期)
# ttl -- time to live(生存时间)
ttl key

# 移除mykey的过期时间(变成永久)
persist mykey

# 查看key的类型
type key

注意 ttl key可以检查任何不存在的键而不出错:ttl 这是一个不存在的键,得到(integer) -2

同理,expire也可以给不存在的键设置过期时间(虽然没有效果,但是不会出错)

鉴于设置过期时间就可以删除数据,所以我们可以使用expire来实现删除功能:
expire mykey 0
当然,也可以使用del mykey

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
# 如果key已经存在并且是一个字符串,APPEND 命令将 value 追加到 key 原来的值的末尾。
# 如果key不存在,APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。
append mykey myvalue

# mykey的长度
strlen mykey

# 增加:incr
# 减少decr
set k2 2
incr k2

# incrby:给k3增加5(为8)
# decrby
set k3 2
incrby k3 5

# getrange/setrange:获取指定区间范围内的值,类似于between..and..的关系
set k1 hyl456
getrange k1 0 -1 # hyl456
getrange k1 0 3 # hyl4

# 语法:setrange key offset value
# setrange会覆盖原先的字符串
set k1 hyl456
setrange k1 0 xxxxx # xxxxx6

# setex/setnx (set with expire/set if not exists)
# 语法:setex key seconds value
setex k4 10 v4
# 语法:setnx key value(如果存在则不会覆盖)
setnx k4 v4

# mset/mget/msetnx(merge set/merge get/merge setnx)
# 合并创建/合并获取
mset k1 v1 k2 v2
mget k1 k2
# 1) "v1"
# 2) "v2"

# 只要有一个存在,就会全部失效
mset k1 v1 k2 v2
msetnx k1 V1 k2 V2 k3 V3
mget k1 k2 k3
# 1) "v1"
# 2) "v2"
# 3) (nil)

注意:msetnx只要有一个key存在,那么就会全部失效

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
34
35
36
37
38
39
40
41
42
43
#lpush/rpush/lrange
# 语法: lpush key v1 v2 v3
lpush mylist 1 2 3 4 5

# 对于列表,不可以使用get获取,要使用lrange
# 语法:lrange key start stop
lrange mylist 0 0
# 1) "5"
lrange mylist 0 -1
# 1) "5"
# 2) "4"
# 3) "3"
# 4) "2"
# 5) "1"

# lpop/rpop
lpop mylist # 5
rpop mylist # 1

#lindx:按照索引下标获取元素
# 语法:lindex key index
lindex mylist 0 # 5

llen mylist # 5

# 语法:lrem key count value
# 从表头开始,删除两个5
lrem mylist 2 5

# trim(修剪)让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
# 只保留idx为1到2的元素
ltrim mylist 1 2

# 语法:rpoplpush source destination
lpoplpush mylist mylist

# 语法:lset key index value
# 将mylist中index为2的值设置为hh
lset mylist 2 hh

# 语法:linsert key before/after pivot vlaue
# 在mylist中,hh值的前面插入java
linsert mylist before hh java

注意:lindex这里的llpushl是不一样的意思:lindex的代表list,lpush的l代表left

lrem:
根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素

  • count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
  • count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
  • count = 0 : 移除表中所有与 VALUE 相等的值。

其中count的数量可以大于list中的数量

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
# 语法:sadd key member1 member2...
# 语法:smembers key
# 语法:sismember key member
# 创建集合set1
sadd set1 1 2 3 4 5 55 4 2 3
# 查看集合成员
smember set1
# 判断是否为集合成员
sismember set1 10000000000

# 获取集合里面的元素个数
scard set1

# 删除集合中的元素
# (如果set中包含java,不包含python,那只会删除java)
srem set1 java python

# 从集合set1中随机出5个数
srandmember set1 5

# 随机出栈5个元素(如果set1少于5个则全部弹出)
spop set1 5

# 将key1中的值移动key2
smove key1 key2 key1中的某个值

# sdiff差集
# sinter交集
# sunion并集
sdiff key1 key2 key3

Hash

KV模式不变,但是V是一个键值对

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
# hset/hget/hmset/hmget/hgetall/hdel
hset user id 311600418
hset user name hyl
hset user phonne 6632678

hget user id # 311600418

hmset friend id 11 name czj age 21
hmget friend id name age
# 1) "11"
# 2) "czj"
# 3) "21"

hgetall friend
# 1) "id"
# 2) "11"
# 3) "name"
# 4) "czj"
# 5) "age"
# 6) "21"

# 删除user哈希表中的Key:name
hdel user name

hlen user

# 在哈希表里是否存在的某个值key
hexists friend id

# 哈希表的所有键/值
hkeys friend
hvals friend

# hincrby/hincrbyfloat
# 给哈希表friend的age键对应的值添加100
hincrby friend age 100
hincrbyfloat friend age 10000.5

# 语法:hsetnx key field value
hsetnx friend shool gdgy

Zset

在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是k1 score1 v1 score2 v2

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
# zadd/zrange
zadd zset1 60 v1 70 v2
zrange zset1 0 -1
# 1) "v1"
# 2) "v2"

zrange zset1 0 -1 withscores
# 1) "v1"
# 2) "60"
# 3) "v2"
# 4) "70"

zrangebyscore zset1 20 200
# 1) "v1"
# 2) "v2"

zrangebyscore zset1 20 200 withscores
# 1) "v1"
# 2) "60"
# 3) "v2"
# 4) "70"

# 70上限不在内
zrangebyscore zset1 20 (70
# 1) "v1"
zrangebyscore zset1 (60 (70
# (empty list or set)

# 从第一个开始限制一个
zrangebyscore zset1 20 200 limit 1 1
# 1) "v2"
# 从第二个开始限制三个
zrangebyscore zset1 20 200 limit 2 3

zrem zset1 v1 v2

# 查看zset1数量
zcard zset1
# 查看zset1分数在[20,40]的数量
zcount zset1 20 40
# 区间为(20,40)
zcount zset1 (20 (40

# 注意zrank排行从0开始
zrange zset1 0 -1
# 1) "v3"
# 2) "v4"
# 3) "v5"
zrank zset1 v3
# (integer) 0
zrank zset1 v4
# (integer) 1

# v3对应的分数
zscore zset1 v3

# 逆序的zrank(逆序获得下标值)
zrevrank zset1 v3

# zrevrange:逆序的zrank
zrange zset1 0 -1
# 1) "v3"
# 2) "v4"
# 3) "v5"
zrevrange zset1 0 -1
# 1) "v5"
# 2) "v4"
# 3) "v3"

# zrevrangebyscore:逆序的zrangebyscore
# 注意max和min的位置颠倒
zrevrangebyscore zset1 90 30
# 1) "v5"
# 2) "v4"
# 3) "v3"

解析配置文件redis.config

  1. Units单位
  2. INCLUDES包含
  3. GENERAL通用
  4. SNAPSHOTTING快照
  5. REPLICATION复制
  6. SECURITY安全
  7. LIMITS限制
  8. APPEND ONLY MODE追加
  9. 常见配置 redis.conf介绍

Units单位

对于Redis来说,1M1MB是不同的,同理,1k和1kb,1g和1gb都是不同的

  • 1m => 1000000 bytes
  • 1mb => 1024*1024 bytes

INCLUDES包含

和Struts2配置文件类似,可以通过includes 包含,redis.conf可以作为总闸,包含一个或多个其他配置文件。

GENERAL通用

通用配置

  • daemonize : 默认情况下,Redis不作为守护进程运行。如果需要,请使用“是”。

    daemonize : 独立于终端而存在的进程,不会因为终端结束而结束,一直伴随系统进程而存在,不设置成守护进程在后台运行,那么你退出终端后redis的进程也就终止了。

  • bind : 绑定的IP , 默认127.0.0.1

  • port : 绑定端口 , 默认6379

  • protected-mode : 默认情况下,已启用保护模式。只有当您确定希望来自其他主机的客户机连接到Redis时(即使未配置身份验证)

  • tcp-backlog : 设置tcp的backlog

    • backlog其实是一个连接队列
    • backlog队列总和=未完成三次握手队列+已经完成三次握手队列。
    • 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
    • 注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值
    • 所以需要确认增大 somaxconn和 tcp_max_syn_backlog两个值来达到想要的效果
  • timeout : 在客户端空闲N秒后关闭连接(0表示禁用 , 一直连着,不关闭)

  • Tcp-keepalive : 单位为秒,如果设置为0,则不会进行 Keepalive检测,建议设置成60

  • loglevel : 日志级别

  • logfile : 日志文件

  • syslog-enabled : 是否开启系统日志(是否写入syslog) , 默认为NO

  • syslog-ident : 指定syslog里的日志标志(也就是日志内容的前缀)

  • databases : 数据表的数量

SNAPSHOTTING快照

将数据库保存在磁盘上

  • Save

    • Redis默认配置文件中提供了三个条件:

      • save 900 1
      • save 300 10
      • save 60 10000
    • save 秒钟 写操作次数

      RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,
      默认
      是1分钟内改了1万次,
      或5分钟内改了10次,
      或15分钟内改了1次。

    • 分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。也就是说,生成快照的触发条件就是900秒内有1个更改或者300秒内有10个更改或者60秒内有10000个更改

    • 禁用

      如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以

      save ""表示禁用RDB持久化的策略

  • stop-writes-on-bgsave-error

    如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制

    bgsave : 即background save , 后台保存

  • rdbcompression

    rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用
    LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能

  • rdbchecksum

    rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约
    10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

  • dbfilename

    指定本地数据库文件名,默认值为dbfilename dump.rdb

  • dir ./ 指定本地数据库存放目录

save 和 bgsave的区别:

  • save:save时只管保存,其它不管,全部阻塞
  • BGSAVE:Reds会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间

REPLICATION复制

SECURITY安全

访问密码的查看、设置和取消

  • config get requirepass 获取密码
  • config set requirepass "123456" 设置密码
  • auth 123456 登录

LIMITS限制

  • maxclients : 一旦达到限制,Redis将关闭所有新连接,并发送一个错误“max number of clients reached”

  • maxmemory : 最大的内存

  • maxmemory-policy : 最大内存的策略(过期内存的移除策略)

    也就是说 , 当达到了最大内存 , 移除过期内存的策略

    1. volatile-lru -> 使用LRU算法移除key,只对设置了过期时间的键
    2. allkeys-lru -> 使用LRU算法移除key , 对所有的Key(也就是说,只要你最近很少使用就移除)
    3. volatile-random -> 在过期集合中移除随机的key,只对设置了过期时间的键
    4. allkeys-random -> 移除随机的key
    5. volatile-ttl -> 移除那些TTL值最小的key,即那些即将过期的key
    6. noeviction -> 不进行移除。针对写操作,只是返回错误信息

    lru : Least Recently Used 最近最少使用

  • maxmemory-samples : 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小 , redis默认会检查这么多个key并选择其中LRU的那个 . 默认为5

APPEND ONLY MOD 追加

  • appendonly : 是否开启AOF , 默认为No
  • appendfilename : 默认appendonly.aof
  • appendfsync
    • always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
    • everysec:出厂默认推荐,异步操作,每秒记录 如果一秒内宕机,有数据丢失
    • no : 不同步(阻塞)
  • no-appendfsync-on-rewrite:重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。(NO即重写时阻塞进程)
  • auto-aof-rewrite-min-size:设置重写的基准值
  • auto-aof-rewrite-percentage:设置重写的基准值

总结

redis.conf 配置项说明如下:

  1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

daemonize no

  1. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

pidfile /var/run/redis.pid

  1. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

  2. port 6379

  3. 绑定的主机地址 bind 127.0.0.1

  4. timeout 300 当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

  5. loglevel verbose 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

  6. logfile stdout 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

  7. databases 16 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

  8. save <seconds> <changes>指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

  9. Redis默认配置文件中提供了三个条件:

    • save 900 1
    • save 300 10
    • save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。也就是说,生成快照的触发条件就是900秒内有1个更改或者300秒内有10个更改或者60秒内有10000个更改

save ""表示禁用RDB持久化的策略

  1. rdbcompression yes 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

  2. dump.rdb 指定本地数据库文件名,默认值为dbfilename dump.rdb

  3. dir ./ 指定本地数据库存放目录

  4. slaveof <masterip> <masterport>设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

  5. masterauth <master-password>当master服务设置了密码保护时,slav服务连接master的密码

  6. requirepass foobared 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

  7. maxclients 128 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

  8. maxmemory <bytes> 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

  9. appendonly no 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

  10. appendfilename appendonly.aof 指定更新日志文件名,默认为appendonly.aof

  11. 指定更新日志条件,共有3个可选值:

    • no:表示等操作系统进行数据缓存同步到磁盘(快)
    • always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    • everysec:表示每秒同步一次(折衷,默认值)
    • appendfsync everysec
  12. vm-enabled no 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

  13. vm-swap-file /tmp/redis.swap 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

  14. vm-max-memory 0 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

  15. vm-page-size 32 Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

  16. vm-pages 134217728 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

  17. vm-max-threads 4 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

  18. glueoutputbuf yes 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

  19. hash-max-zipmap-entries 64 hash-max-zipmap-value 512 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

  20. activerehashing yes 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

  21. include /path/to/local.conf 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

Redis持久化

  • RDB:(Redis DataBase)
  • AOF(Append Only File)

RDB(Redis DataBase)

RDB是什么

  • 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot快照
  • 它恢复时是将快照文件直接读到内存里

RDB怎么做

  • Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件
  • 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能.
  • 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效.
  • RDB的缺点是最后一次持久化后的数据可能丢失

eg:

  1. 现在RDB时间设置为5min , 第一个5min就将内存中的数据块写入临时文件中 ,
  2. 第二个5min后 , 这个内存数据块变得更大 , 再将这个内存数据块写入临时文件中(会覆盖掉原来的文件内容)

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

如何使用

  1. 当我们redis所在目录中 ,存在dump.rdb文件的时候,就会自动将dump.rdb的内容重写写入内存数据库
  2. 将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务就可以自动恢复数据

如何停止

动态所有停止RDB保存规则的方法:redis-cli config set save ""

save和bgsave命令

我们在redis命令行中可以调用save命令手动生成dump.rdb文件

  • Save:save时只管保存,其它不管,全部阻塞
  • BGSAVE:Redis会在后台异步进行快照操作,
    快照同时还可以响应客户端请求。可以通过lastsave
    命令获取最后一次成功执行快照的时间

flushall和shutdown命令

redis执行shutdown 或者flushdb 命令的时候 , 一样会生成dump.rdb文件(实际后台的操作就是 : 斩断内存,并且重新生成dump.rdb , 也就是说,此时的dump.rdb为空)

简单来说 : 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

优势劣势

  • 优势
    • 适合大规模的数据恢复
    • 对数据完整性和一致性要求不高
  • 劣势
    • 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
    • fork的时候,内存中的数据被克隆了一份,导致大致2倍的膨胀性
  • RDB是一个非常紧凑的文件
  • RDB ,在保存RDB文件时父进程唯需要做的就是fork出一个子进程接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能
  • 与AOF相比在恢复大的数据集的时候,RDB方武会更快一些
  • 数据丢失风险大
  • RDB需要经常fork子进程来保存数据集到硬盘上 , 当数据集比较大的时候fork的过程是非常耗时的 , 可能会导致redis在一些毫秒级不能相应客户端请求

AOF(Append Only File)

AOF是什么

  • 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),
  • 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,
  • 换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
  • Aof保存的是appendonly.aof文件

启动/恢复/异常处理

  • 启动AOF : conf里设置appendonly yes

  • 将有数据的aof文件复制一份保存到对应目录(config get dir)

  • appendonly.aofdump.rdb文件可以共存 , 如果两者都存在 , 首先加载append.aof .

    所以 , 如果应该机器错误导致appendonly.aof文件出错 , 那么此时redis将无法正常启动

  • 但redis因为appendonly.aof文件出错导致无法正常启动 , 就会生成redis-benchmark , redis-check-aof , redis-check-dump文件 , 此时我们可以执行

    1
    redis-check-aof --fix appendonly.aof

    进行修复

    当然也可以使用redis-check-dump文件进行修复

rewrite

  • AOF采用文件追加方式,文件会越来越大 .为避免出现此种情况,新增了重写机制,
  • 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.
  • 可以使用命令bgrewriteaof

重写原理

  • AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),
  • 重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似

重写触发机制

  • Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
  • 通过设置auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage 修改配置
    • auto-aof-rewrite-min-size:设置重写的基准值 ,默认64mb
    • auto-aof-rewrite-percentage:设置重写的基准值 ,默认100

优势劣势

  • 优势
    • 每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
    • 每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失(也就是说 , 最多丢失1秒的数据)
    • 不同步:appendfsync no 不同步(阻塞)
  • 劣势
    • 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
    • aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
  • AOF文件是一个只进行追加的日志文件
  • redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写
  • AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis协议的格式保存,因此AOF文件的内容非常容被人读懂,对文件进行分析也很轻松
  • 对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积
  • 根据所使用的 fsync策略,AOF的速度可能会慢于RDB

总结(Which one)

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

  • AOF持久化方式记录每次对服务器写的操作

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

  • 同时开启两种持久化方式

    • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,
      因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
    • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?
      建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
  • 性能建议

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

    • 如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单 , 只load自己的AOF文件就可以了。

      • 代价一 : 带来了持续的IO,
      • 代价二 : rewrite使用异步 , 但是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的

      只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。

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

事务

是什么

可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞

能干嘛

一个队列中,一次性、顺序性、排他性的执行一系列命令

常用命令

命令 描述
DISCARD 取消事务 , 放弃执行事务快内的所有命令
EXEC 执行事务块内的所有命令
MULTI 标记一个事务块的开始
UNWATCH 取消WATCH命令对所有key的监视
WATCH key [key….] 监视一个(或多个key) , 如果在事务执行之前这个(或者写)key被其他命令所改动,那么事务将被打断

case1:正常事务操作

1
2
3
4
5
MUTIL
set k1 v1
get k2
set k3 v3
EXEC

Case2:放弃事务操作

1
2
3
4
5
MUTIL
set k1 v1
get k2
set k3 v3
DISCARD

Case3:全体连坐

事务中只要有一个命令有语法错误 ,全部操作都会失效

1
2
3
4
5
6
7
8
MUTIL
set k1 v1
set k2 v2
getset k3 # 语法错误,没有getset命令
set k4 v4
EXEC # EXECABORT

get k4 # nil

Case4:冤头债主

事务中如果有一个命令有执行错误 , 那么只有这个命令会失效

1
2
3
4
5
6
7
8
set k1 aa

MUTIL
inc k1 # 语法是正确的,但是执行会出错(因为aa是字符串,不能inc)
set k2 v2
EXEC

get k2 # v2

Case3 : 全体连坐Case4 : 冤头债主的区别:

  1. Case3 是语法错误
  2. Case4 是执行错误

因为使用事务 , redis会先将命令存入Queue , 然后再一起执行 ,

  • 所以,只要是语法正确的都会入队
  • 语法错误则一开始就无法入队

简单来说 , 执行错误只有在运行的时候才能发现错误 , 语法错误一开始就能检测的到

所以得到 : redis对事物的支持是部分支持

Case5:watch监控事务

缓存的数据大家都来拿 , 而对于关键数据则不希望被修改 , 这是就需要监控这些数据

悲观锁
  • 悲观锁(Pessimistic Lock)顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
  • 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁
乐观锁
  • 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
  • 乐观锁适用于多读的应用类型,这样可以提高吞吐量

乐观锁策略 : 提交版本必须大于记录当前版本才能执行更新

CAS(Check And Set)
  • watch : 监视一个(或多个)key , 如果在事务执行(也就是EXEC)之前这个key被其他命令所改动,那么事务将被打断
  • 在 Redis 中使用 watch 命令可以决定事务是执行还是回滚。
  • 当 Redis 使用 exec 命令执行事务的时候,它首先会去比对被 watch 命令所监控的键值对,如果没有发生变化,那么它会执行事务队列中的命令,提交事务;如果发生变化,那么它不会执行任何事务中的命令,而去事务回滚。

Redis执行事务过程

以信用卡可用余额(目前还可以透支消费的额度)和欠额为例 :

1
2
3
4
5
6
7
8
9
10
11
12
# 一开始可用余额100,欠额0
set balance 100
set debt 0

# 监控balance
WATCH balance
MUTIL
decrby balance 20
incby debt 20
EXEC
# (integer 80)
# (integer 20)

小结

  • Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
  • 通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,
    EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败

事务三阶段

  • 开启:以MULTI开始一个事务
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
  • 执行:由EXEC命令触发事务

事务三特性

  • 单独的隔离操作:
    事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念:
    队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在事务内的查询要看到事务里的更新,在事务外查询不能看到这个让人万分头痛的问题
  • 不保证原子性:
    redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

Redis的发布订阅

是什么

  • 进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
  • 订阅/发布消息图

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

img

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

img

发布订阅命令

命令 描述
PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道。
PUBSUB subcommand [argument [argument …]] 查看订阅与发布系统状态。
PUBLISH channel message 将信息发送到指定的频道。
PUNSUBSCRIBE [pattern [pattern …]] 退订所有给定模式的频道。
SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel …]] 指退订给定的频道。

先订阅后发布后才能收到消息,

示例一 :

  1. 可以一次性订阅多个,SUBSCRIBE c1 c2 c3
  2. 消息发布,PUBLISH c2 hello-redis

示例二 :

  1. 订阅多个,通配符*, PSUBSCRIBE new*
  2. 收取消息, PUBLISH new1 redis2015

Redis的复制(Master/Slave)

是什么

也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主Slave以读为主

设备分为主设备和从设备,

  • 主设备负责分配工作并整合结果,或作为指令的来源;
  • 从设备负责完成工作,一般只能和主设备通信

核心思想是基于分而治之的思想,将一个原始任务分解为若干个语义等同的子任务,并由专门的工作者线程来并行执行这些任务,原始任务的结果是通过整合各个子任务的处理结果形成的

说人话 :

  • 为了数据完整性,容灾备份 , 当主数据库插入数据的时候 , 从数据库立马随着也插一条
  • 主数据库以写为主 , 从数据库以读为主

所以是 : 以主从复制技术来达到读写分离的效果

主从复制与集群的区别

最本质的区别,其实也就是data-sharing和nothing-sharing的区别。

  • 集群是共享存储的。
  • 主从复制中没有任何共享。每台机器都是独立且完整的系统。

能干嘛

  • 读写分离
  1. 做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。

  2. 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。

  3. 读写分离,使数据库能支撑更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。

  • 容灾恢复
  • 容灾系统是指在相隔较远的异地,建立两套或多套功能相同的IT系统,互相之间可以进行健康状态监视和功能切换,当一处系统因意外(如火灾、地震等)停止工作时,整个应用系统可以切换到另一处,使得该系统功能可以继续正常工作。
  • 容灾技术是系统的高可用性技术的一个组成部分,容灾系统更加强调处理外界环境对系统的影响,特别是灾难性事件对整个IT节点的影响,提供节点级别的系统恢复功能。

配置

配从(库)不配主(库)

从库配置:slaveof 主库IP 主库端口

  • 每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件

  • eg : slaveof 127.0.0.1 6379

事前准备

  1. 制作三个redis.conf ,
  2. 设置daemonize yes,
  3. 设置端口为6379,6380,6381
  4. 开启三个redis进程

使用

一主二从

从机跟随主机

从机数据和主机数据永远会保持一致

info replication : 查看主从复制的相关属性

1
2
3
4
5
6
7
8
9
$ info replication
# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
1
2
3
4
5
6
7
8
# 设置6379redis的值
set name hyl

# 设置6380redis,6381redis为6379的从机
slaveof 127.0.0.1 6379

# 在6380redis,6381redis获取值
get name #hyl
只有主机能写,从机不能写

现在主机执行

1
set num 1

从机执行

1
set num 2

是不可以的 , 读写分离 , 顾名思义,只有主机能写,从机不能写

主机从机身份变化

现在有一台主机 , 两台从机 .

  1. 如果主机挂了 , 那么两台从机在原地待命(也就是从机还是从机,不会变成主机)

    之后主机重新上线 , 那么就是视为无事发生,

  2. 如果从机挂了 , 之后从机重新上线 , 那么这台从机将变成主机(除非将设置写入配置文件)

    每次与master断开之后,都需要重新连接 (重新执行slaveof 127.0.0.1 6379),除非你配置进redis.conf文件

  3. 原因 : 执行slaveof 127.0.0.1 6379 其实就是设置了这台主机的master属性 ,

    • 这个属性不会跟着主机的下线而改变(也就是不会从机上位)
    • 但是这个属性会跟着从机的下线而消失(废话 , 对象都消失了,属性自然跟着消失)
    • 如果我们将属性配置进redis.conf文件 , 那么每次生成新的redis对象时,就会自动设置它的maser属性,进而变成从机

薪火相传

  • 我们可以一主二从 , 也可以一主多从 , 但是这样会造成中心化太严重.
  • 所以我们可以设置从机的从机 , 实现去中心化
  • 上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力

1573901747414

去中心化的缺陷

  1. 数据在传递过程中的失真
  2. 复制延时 , 数据传递速度慢
  • 中途变更转向:会清除之前的数据,重新建立拷贝最新的
  • 命令slaveof 新主库IP 新主库端口

反客为主

命令 :SLAVEOF no one 使当前数据库停止与其他数据库的同步,转成主数据库

  1. 6379是主机 , 下面有6380,6381两个从机
  2. 6379挂了 , 6380执行SALVEOF on one , 那么6380变成主机
  3. 此时的6381依旧是从机 ,他的领导依旧是6379

复制原理

  • slave启动成功连接到master后, slave会发送一个sync命令
  • Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
  • 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
  • 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

简单来说就是 :

  1. 当slave首次执行slaveof命令,就执行一次全量复制
  2. 之后每次slave的更新 , 都是增量复制

哨兵模式(sentinel)

是什么

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

使用步骤

  • 主机6379下面带着6380、6381两个从机

  • /redis目录下新建sentinel.conf文件

  • 配置哨兵,填写内容

    • sentinel monitor 被监控数据库名字(名字随便取) 127.0.0.1 6379 1
    • 上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机(谁的票数多于一票谁就是新的老大)

    简单来说 , sentinel.conf需要两个配置

    1. 监控哪个主机
    2. 如果主机挂了 , 投票选举的方式

    eg:

    1
    sentinel monitor my_master_redis 127.0.0.1 6379 1
  • 启动哨兵

    • 命令 : redis-sentinel /redis/sentinel.conf
    • 上述目录依照各自的实际情况配置,可能目录不同
  • 问题:如果之前的master重启回来,会不会双master冲突?

    老领导重新上线回来之后 , 就不再是领导了 , 直接变成从机

同时监控多个Master

一组sentinel能同时监控多个Master , 这样就支持了去中心化的架构设计

复制的缺点

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。