NoSQL概述

为什么要用NoSQL

  1. 单机MySQL年代
    一个网站的访问量一般不会太大,单个数据库完全足够[略]尚硅谷教程笔记 - 图1
  2. Memcached(缓存)+MySQL+垂直拆分(读写分离)
    主要解决读的压力
    网站80%发情况都是在读,每次都去查询数据库效率很低,为减轻压力,可以使用缓存来保证效率。
    发展过程:优化数据结构和索引 -> 文件缓存(IO)-> Memcached[略]尚硅谷教程笔记 - 图2
  3. 分库分表+水平拆分(MySQL集群)
    主要解决写的压力

[略]尚硅谷教程笔记 - 图3

  1. 近些年
    用户的个人信息、社交网络、地理位置、用户日志等等爆发式增长,变化很快,某些时候MySQL等关系型数据库不够用,效率也低。

什么是NoSQL

NoSQL = Not only SQL

泛指非关系型数据库。随着web2.0时代的到来,传统的关系型数据库很难对付现有的应用场景。很多数据类型,如用户的个人信息、地理位置等等,这些数据类型的存储不需要一个固定的格式,无需多余操作就可以横向拓展。

NoSQL特点:

  1. 方便拓展:数据之间没有关系,容易拓展
  2. 大数据量高性能:比如redis一秒可以写8万次,读取11万次
  3. 数据类型多样:不需要事先设计数据库,随取随用

NoSQL数据模型简介

对比关系型数据库和非关系型数据库

以一个电商客户、订单、支付、收货 地址模型进行对比:

  • 关系型数据库:1对1、1对多、多对多等

[略]尚硅谷教程笔记 - 图4

  • NoSQL:以Bson为例,可合可拆[略]尚硅谷教程笔记 - 图5
  • 两者对比,问题和难点:
    • 高并发的操作不太建议有关联查询,互联网公司用冗余数据来避免关联查询
    • 分布式事务支持不了太多并发

聚合模型

  • k-v键值对
  • Bson:类似json
  • 列族:按列存储数据,最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询具有非常大的IO优势

NoSQL四大分类

  • K-V键值:redis
  • 文档型数据库(bson格式比较多):MongoDB
  • 列存储数据库:Cassandra、Hbase、分布式文件系统
  • 图关系数据库:Neo4j、InfoGrid

[略]尚硅谷教程笔记 - 图6

分布式数据库CAP

  • C: Consistency 强一致性
  • A: Availability 高可用性
  • P: Partition tolerance 分区容错性

CAP理论的核心是:一个分布式系统不可能同时很好地满足一致性、可用性和分区容错性这三个需求,最多只能同时较好地满足两个。因此。根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  • CA - 单点集群,满足一致性,可用性的系统,通常在可拓展性上不太强大
  • CP - 满足一致性、分区容错性的系统,通常性能不是很高
  • AP - 满足可用性,分区容错性的系统,通常对一致性要求低一些

[略]尚硅谷教程笔记 - 图7

CAP的三进二

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以,分区容错性是我们必须要实现的,我们只能在一致性和可用性之间进行权衡。

  • CA - 传统关系型数据库如Oracle
  • AP - 大多数网站架构的选择
  • CP - redis、Mongodb

一致性与可用性的抉择

对于web2.0网站来说,关系型数据库的很多主要特性却往往无用武之地

  • 数据库事务一致性需求:很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求不高。允许实现最终一致性
  • 数据库的写实时性和读实时性需求:很多web应用并不要求那么高的实时性、
  • 对复杂的SQL查询,特别是多表关联查询的需求:很多大数据量的web系统,都很忌讳复杂的关联查询,更多的只是单表的主键查询、分页查询等

BASE

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

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

其思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上的改观。

redis概述

是什么:

Redis:REmote DIctionary Server(远程字典服务器)

是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key-value)分布式内存数据库,基于内存运行,也支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器。

能干什么:

  • 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
  • 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合中
  • 模拟类似于HttpSession这种需要设定过期时间的功能
  • 发布、订阅消息系统
  • 定时器、计数器

三个特点:

  • 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用
  • 不仅仅支持简单的key-value类型的数据,同时提供list、set、zset、hash等数据结构的存储
  • 支持数据的备份,即master-slave模式的数据备份

启动后杂项基础知识:

  • 单进程
    • 使用单进程模型来处理客户端请求,对读写等事件的响应是通过对epoll函数的包装来做到的,redis的实际处理速度完全依靠主进程的执行效率
    • Epoll是Linux内核为处理大批量文件描述符而做了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,可以显著提高程序在大量并发连接中只有少数活跃的情况下的系统CPU利用率
  • 默认16个数据库,类似数组下标从零开始,初始默认使用零号库
  • Select:切换数据库
  • Dbsize:查看当前数据库的key的数量
  • Flushdb:清空当前库
  • Flushall:通杀全部库
  • 统一密码管理:16个库都是同样密码,要么都ok要么一个都连不上
  • Redis索引都是从零开始

redis键:

  • keys * :查看所有键值
  • exists key_name:判断某个key是否存在
  • move key_name db_index :将当前库的key转移到其他库,当前库中该key被移除
  • expire key_name seconds:为给定key设置过期时间(秒)
  • ttl key_name:查看还有多少秒过期,-1表示永不过期,-2表示已过期
  • type key_name:查看key对应value的数据类型

五大基本数据类型

String(字符串)

  • redis中最基本的类型,一个key对应一个value。
  • string类型是二进制安全的,意思是redis的string可以包含任何数据,比如图片或者序列化对象
  • redis中一个字符串value最多可以是512MB

常用:

  • set/get/del/append/strlen:赋值/取值/删除/value后附加/字符串长度
  • Incr/decr/incrby/decrby:数字的增减,一定要是数字才能进行加减
  • getrange/setrange:获取/设置指定区间范围内的值,类似between…and的关系
  • setex(set with expire):设置存活时间,例setex k1 10 v1设置10秒存活时间
  • setnx(set if not exist):如果原键值不存在则设置
  • mset/mget/msetnx:一次性操作多个键值对,例mset k1 v1 k2 v2则是插入两个键值对

应用场景:

List(列表)

  • redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或尾部
  • 它的底层实际上是个链表

常用:

  • lpush/rpush/lrange:lpush是从栈顶入栈,类似堆栈,rpush是从栈底入栈,类似队列

[略]尚硅谷教程笔记 - 图8

  • lpop/rpop:从栈顶/栈底每次弹出一个,被弹出的则从列表中删除

[略]尚硅谷教程笔记 - 图9

  • lindex:按照索引下标获得元素(从上到下,从零开始)
  • llen:列表长度
  • lrem key n value:删除N个value,如 lren list03 2 3 删除2个值为3的key

[略]尚硅谷教程笔记 - 图10

  • ltrim key 开始index 结束index:截取指定范围的值后再赋值给key,即根据index取到一些值,更新原来的列表

[略]尚硅谷教程笔记 - 图11

  • rpoplpush 源列表 目标列表:源列表栈底出栈目标列表栈顶进栈

[略]尚硅谷教程笔记 - 图12

  • lset key index value:对下标设置某个具体值

[略]尚硅谷教程笔记 - 图13

  • linsert key before/after value1 value2:在value1的前/后插入value2

[略]尚硅谷教程笔记 - 图14

  • 性能总结:
    • 它是一个字符串链表,left、right都可以插入
    • 如果键(list)不存在,创建新的链表
    • 如果键(list)已存在,则新增内容
    • 如果值全移除,对应的键(list)也就消失了
    • 链表的操作无论是从头还是尾效率都极高,但如果是对中间元素进行操作,效率则会很差

应用场景:

Set(集合)

  • redis的set是string类型的无序集合
  • 它是通过HashTable实现的

常用:

  • sadd/smembers/sismember:添加(去重)/查看元素/元素是否存在集合中

[略]尚硅谷教程笔记 - 图15

  • scard:获取集合中元素个数,scard set01
  • srem key value:删除集合中元素,srem set01 3
  • srandmember key k:随机获取集合中的k个值,srandmember set01 3
  • spop key:随机出栈,spop set01
  • smove key1 key2 value:将key1中的值value赋值给key2,smove set01 set02 3
  • 数学集合类:
    • 差集:sdiff key1 key2 ... 在第一个set里面而不在后面任何一个set里面的值
    • 交集:sinter
    • 并集:sunion

应用场景:

Hash(哈希)

  • redis hash是一个键值对集合
  • redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象
  • 类似Java中的Map<String,Object>,k-v模式不变,但v是一个键值对

常用:

  • hset/hget/hmset/hmget/hgetall/hdel

[略]尚硅谷教程笔记 - 图16

  • hlen:hash中的键值对个数
  • hexists key:hash中是否存在某个key的键值对,hexists user id
  • hkeys/hvals:hash所有的键/值
  • hincrby/hincrbyfloat:将某个数值型的变量增加,hincrby user age 2
  • hsetnx:如果某个键值对不存在则添加

应用场景:

Zset(有序集合)

  • redis zset和set一样也是string类型元素的集合,且不允许有重复的成员
  • 不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数却可以重复

常用:

  • zadd/zrange

[略]尚硅谷教程笔记 - 图17

  • zrangebyscore key minscore maxscore:根据分数范围筛选值
    • ( :不包含
    • limit 开始下标步 多少步:返回限制

[略]尚硅谷教程笔记 - 图18[略]尚硅谷教程笔记 - 图19

  • zrem key 某score下对应的value值:删除元素,zrem zset01 v5
  • zcard/zcount key score区间:value个数/根据score区间获得value个数
  • zrank key value/zscore key value:获得value对应的下标/分数

[略]尚硅谷教程笔记 - 图20

  • zrevrank key value:逆序获得下标值

应用场景:

解析配置文件

Units单位:

  1. # 1k => 1000 bytes
  2. # 1kb => 1024 bytes
  3. # 1m => 1000000 bytes
  4. # 1mb => 1024*1024 bytes
  5. # 1g => 1000000000 bytes
  6. # 1gb => 1024*1024*1024 bytes
  7. #
  8. # units are case insensitive so 1GB 1Gb 1gB are all the same.
  • 开头定义了一些基本的度量单位,只支持bytes,不支持bit
  • 对大小写不敏感

Includes包含:

# include .\path\to\local.conf
# include c:\path\to\other.conf

可以通过includes包含,redis.conf可以作为总闸,包含其他

General通用:

daemonize

# 是否在后台运行;no:不是后台运行
daemonize yes

Pidfile

# 将redis的进程文件写入redis-server.pid 
pidfile /var/run/redis/redis-server.pid

Port

# 默认6379
# redis监听的端口号
port 6379

Timeout

# 空闲N秒后关闭连接,0表示不关闭
timeout 0

Bind

# 指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求
#bind 127.0.0.1
#bind 0.0.0.0

Loglevel

# 指定了服务端日志的级别。
# 级别包括:
# debug(很多信息,方便开发、测试),
# verbose(许多有用的信息,但是没有debug级别信息多),
# notice(适当的日志级别,适合生产环境),
# warn(只有非常重要的信息)
loglevel notice

# 指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。
logfile /var/log/redis/redis-server.log

Databases

# 数据库的数量,默认使用的数据库是DB 0。可以通过SELECT命令选择一个db
databases 16

Limits限制

  • maxclients:设置redis同时可与多少个客户端连接,默认最大为10000
  • maxmemory:最大占用的内存
  • maxmemory-policy:达到最大内存时的移除策略,默认为noeviction
    • volatile-lru:最近最少使用算法移除key,只对设置了过期时间的键
    • allkeys-lru:最近最少使用算法移除key
    • volatile-random:在过期集合中随机移除key,只对设置了过期时间的键
    • allkeys-random:在过期集合中随机移除key
    • volatile-ttl:移除TTL值最小的key,即那些最近要过期的key
    • noeviction:不进行移除,针对写操作,只是返回错误信息
  • maxmemory-samples:设置样本数量。LRU和最小TTL算法都并非精确的算法,而是估算值,所以你可以设置样本大小,redis默认会检查这么多个key并选择其中LRU的那个

redis.conf常见配置

#是否在后台运行;no:不是后台运行
daemonize yes

#是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只会本地进行访问,拒绝外部访问。
protected-mode yes

#redis的进程文件
pidfile /var/run/redis/redis-server.pid

#redis监听的端口号。
port 6379

#此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。在/etc/sysctl.conf中添加:net.core.somaxconn = 2048,然后在终端中执行sysctl -p。
tcp-backlog 511

#指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求
#bind 127.0.0.1
#bind 0.0.0.0

#配置unix socket来让redis支持监听本地连接。
# unixsocket /var/run/redis/redis.sock

#配置unix socket使用文件的权限
# unixsocketperm 700

# 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。
timeout 0

#tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值,使用keepalive有两个好处:检测挂掉的对端。降低中间设备出问题而导致网络看似连接却已经与对端端口的问题。在Linux内核中,设置了keepalive,redis会定时给对端发送ack。检测到对端关闭需要两倍的设置值。
tcp-keepalive 0

#指定了服务端日志的级别。级别包括:debug(很多信息,方便开发、测试),verbose(许多有用的信息,但是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有非常重要的信息)
loglevel notice

#指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。
logfile /var/log/redis/redis-server.log

#是否打开记录syslog功能
# syslog-enabled no

#syslog的标识符。
# syslog-ident redis

#日志的来源、设备
# syslog-facility local0

#数据库的数量,默认使用的数据库是DB 0。可以通过SELECT命令选择一个db
databases 16

# redis是基于内存的数据库,可以通过设置该值定期写入磁盘。
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000

#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误
stop-writes-on-bgsave-error yes

#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes

#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。
rdbchecksum yes

#rdb文件的名称
dbfilename dump.rdb

#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /data

redis持久化

RDB(Redis Database)

简介:

  • 在指定的时间间隔内生成内存中整个数据集的持久化快照。快照文件默认被存储在当前文件夹中,名称为dump.rdb,可以通过 dir 和 dbfilename 参数来修改默认值。
  • Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
  • 整个过程中,主进程是不进行任何的IO操作的,这就确保了极高的性能。

fork:

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

应用场景:

  • 数据备份
  • 可容忍部分数据丢失
  • 跨数据中心的容灾备份

配置文件:

# redis是基于内存的数据库,可以通过设置该值定期写入磁盘。
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000

#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误
stop-writes-on-bgsave-error yes

#对于存储到磁盘中的快照,可以设置是否进行压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes

#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。
rdbchecksum yes

#rdb文件的名称
dbfilename dump.rdb

#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /data

如何触发RDF快照备份:

  1. 通过配置文件中的save条件(可自己配置)
save 900 1
save 300 10
save 60 10000
  1. 手动通过 save 和 bgsave 命令
    • save:save时只管保存,其他不管,全部阻塞
    • bgsave:redis会在后台异步的进行快照操作,同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间
  2. 通过flushall命令,也会产生dump.rdb文件,但是里面是空的,无意义。
  3. 通过shutdown命令,安全退出,也会生成快照文件(和异常退出形成对比,比如:kill杀死进程的方式)

如何恢复:

  1. 将备份文件(dump.rdb)移动到redis安装目录并启动服务即可
  2. 或者可以修改配置文件,指定备份文件的目录
appendonly no
dbfilename dump.rdb
dir /var/lib/redis  #可以自行指定

设置 appendonly no,redis 启动时会把 /var/lib/redis 目录下的 dump.rdb 中的数据恢复。dir 和dbfilename 都可以设置。

优势:

  • 恢复数据的速度很快,适合大规模的数据恢复,而又对部分数据不敏感的情况
  • 对数据完整性和一致性要求不高
  • dump.db文件是一个压缩的二进制文件,文件占用空间小

劣势:

  • 在一定时间间隔做一次备份,当出现异常退出时,会丢失最后一次快照后的数据
  • 当fork的时候,内存的中的数据会被克隆一份,大致两倍的膨胀需要考虑。而且,当数据过大时,fork操作占用过多的系统资源,造成主服务器进程假死。

AOF(Append Only File)

简介:

  • 日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取改文件重新构建数据(即根据日志文件的内容将写指令从前到后执行一次已完成数据的恢复工作)。
  • AOF保存的是appendonly.aof文件
  • aof机制默认关闭,可以通过appendonly = yes参数开启aof机制,通过appendfilename = myaoffile.aof指定aof文件名称。

配置文件:

aof持久化策略

#aof持久化策略的配置
#no:表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
#always:表示每次写入都执行fsync,以保证数据同步到磁盘,可保证数据一定不会丢失,性能较差但数据完整性比较好
#everysec:默认配置,异步操作,每秒记录一次,如果一秒内宕机,则会有数据丢失。
appendfsync everysec

重写策略

是什么:

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

重写原理:

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

触发机制:

  • redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64mb时触发

相关配置:

  • 对于触发aof重写机制也可以通过配置文件来进行设置
# aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
# 设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64mb
  • aof重写时会引发重写和持久化追加同时发生的问题,可以通过no-appendfsync-on-rewrite no进行配置
# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题。如果对延迟要求很高的应用,这个字段可以设置为yes,设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,不会造成阻塞的问题(因为没有磁盘竞争),等rewrite完成后再写入,这个时候redis会丢失数据。Linux的默认fsync策略是30秒。可能丢失30秒数据。因此,如果应用系统无法忍受延迟,而可以容忍少量的数据丢失,则设置为yes。如果应用系统无法忍受数据丢失,则设置为no。
no-appendfsync-on-rewrite no

如何恢复:

正常恢复

  • 将文件放到dir指定的文件夹下,当redis启动的时候会自动加载数据,注意:aof文件的优先级比dump大

异常恢复

  • 有些操作可以直接到 appendonly.aof 文件里去修改。
    eg:使用了flushall这个命令,此刻持久化文件中就会有这么一条命令记录,把它删掉就可以了
  • 写坏的文件可以通过 redis-check-aof --fix进行修复

优势:

  1. 根据不同的策略,可以实现每秒,每一次修改操作的同步持久化,就算在最恶劣的情况下只会丢失不会超过两秒数据。
  2. 当文件太大时,会触发重写机制,确保文件不会太大。
  3. 文件可以简单的读懂

劣势:

  1. 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
  2. aof运行效率要慢于rdb,每秒同步策略较好,不同步效率和rdb相同

如何选择

两者分析

  • RDB持久化方式能够在指定的时间间隔对内存中的数据进行快照存储;
  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾;Redis还能够对AOF文件进行后台重写,使aof文件体积不至于过大;
  • 如果你只是用作缓存,只希望数据在程序运行的时候存在,那么就可以不使用任何持久化方式;
  • 官方推荐两种方式同时开启:在两种方式同时开启的情况下,redis启动的时候会优先加载aof文件来恢复原始数据,因为在通常情况下aof文件保存的数据集比rdb文件保存的数据集要完整,rdb的数据会不实时。那要不要只使用aof呢?建议不要,因为rdb更适合用于备份数据库(aof在不断变化,不好备份),快速重启,而不会有aof可能存在的潜在bug,留着作为一个补救的手段;

性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
  • 如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构

事务

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

常用命令

命令 描述
multi 标记一个事务的开始
exec 执行所有事务块内的命令
discard 取消事务,放弃执行事务块内的所有命令
watch key [key] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
unwatch 取消watch命令对所有 key 的监视。

特殊情况

全体连坐:

  • 输入命令过程中报错,整个事务无法执行。

[略]尚硅谷教程笔记 - 图21

冤头债主:

  • 执行时某条命令报错不影响其他正常执行的命令。这里可以看出redis对事务的支持是部分支持,没有强一致性。

[略]尚硅谷教程笔记 - 图22

watch监控

悲观锁:

  • 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等。读锁,写锁等都是在做操作前先上锁。

乐观锁:

  • 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。可以使用版本号等机制实现(提交版本必须大于记录当前版本才能执行更新)。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

watch监控:

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

三阶段

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

三特性

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

redis复制

主从复制:主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,master以写为主,salve以读为主

应用场景

  • 读写分离
  • 容灾恢复

一主二从

[略]尚硅谷教程笔记 - 图23

  1. 修改配置文件(3个)
    • 拷贝多个redis.conf文件
    • 开启daemonize yes
    • 修改pid文件名
    • 指定启动端口(6379、6380、6381)
    • 修改log文件名
    • 修改dump.rdb文件名
  2. 启动redis服务
redis-server redis6379.conf
redis-server redis6380.conf
redis-server redis6381.conf

redis-cli -h 127.0.0.1 -p 6379
redis-cli -h 127.0.0.1 -p 6380
redis-cli -h 127.0.0.1 -p 6381
  1. 增加主从关系
slaveof <主库ip> <主库port>

# 查看当前服务器状态 
info replication

master:

[略]尚硅谷教程笔记 - 图24

slave:

[略]尚硅谷教程笔记 - 图25

注意:

  • 主机写,从机读
  • 主机shutdowm后从机进入待机状态,等主机回来后,主机新增记录从机可以顺利复制
  • 从机shutdowm后,每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件
  • 从机复制到的数据,会被本机持久化。就算shutdown断开连接依然会有数据。
  • 重新连接或者变更master,会清除之前的数据,重新建立拷贝最新的数据

薪火相传

  • 指上一个Slave可以是下一个Slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力去中心化降低风险。

[略]尚硅谷教程笔记 - 图26

  • 对于上面的三台机器:

    # 6379作为6380的master
    slaveof 127.0.0.1 6379
    # 6380作为6381的master(6380的角色依然是master)
    slaveof 127.0.0.1 6380
    
  • 注意:

    • 中途变更转向:会清除之前的数据,重新建立拷贝最新的
    • 风险是一旦某个slaver宕机,后面的slaver都没法备份

哨兵模式

简介:

  • 能够后台监控Master库是否故障,如果故障了根据投票数自动将slave库转换为主库。一组sentinel能同时监控多个Master

[略]尚硅谷教程笔记 - 图27

配置:

  • 在Master对应redis.conf同目录下新建sentinel.conf文件
  • 配置哨兵,在sentinel.conf文件中填入内容(可以配置多个)

    • 说明:最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机。
    • sentinel monitor 被监控数据库名字 <ip> <port> 1
  • 启动哨兵模式(路径按照自己的需求进行配置):redis-sentinel /myredis/sentinel.conf

注意:

  • 当master挂掉后,会通过选票进行选出下一个master。而且只有使用了sentinel.conf启动的才能开启选票
  • 原来的master回来后,很不幸变成了slave

复制原理

  • Slave启动成功连接到master后会发送一个sync命令
  • Master接到命令后启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master

将传送整个数据文件到slave,以完成一次完全同步

  • 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
  • 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

复制的缺点

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

常用命令总结

命令 作用
slaveof 主库ip 主库端口 配置从库
info replication 查看redis主从复制的情况
slaveof no one 使当前数据库停止与其他数据库的同步,转成主数据库
sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1 配置哨兵,监视master
redis-sentinel /myredis/sentinel.conf 以哨兵模式启动redis

jedis

  • jedis是redis官方推荐的java连接开发工具,使用Java操作redis的中间件。

基本使用

  1. 创建maven项目,导入对应依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>
  1. 连接数据库
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 6379;
        Jedis jedis = new Jedis(host, port);
        System.out.println(jedis.ping());
    }
    // 输出:PONG
  1. 常用API
  • 所有的API命令就对应终端操作redis的指令

事务提交

监控balance是否改动:

  • 正常情况下,事务执行过程中balance没有被改动,输出exec得到[90,10],分别是两条指令执行的结果
  • 若sleep的三秒钟内,我使用命令行给balance重新赋值,则输出exec得到null,即事务回滚,可用余额减10操作没有执行
    public static void transMethod(Jedis jedis) throws InterruptedException {
        jedis.watch("balance");  // 监控该key是否改动
        Transaction transaction = jedis.multi();
        transaction.decrBy("balance", 10);  // 可用余额减10
        // 假设watch过程中balance被其他进程修改了
        Thread.sleep(3000);
        transaction.incrBy("debt", 10); // 欠额加10
        List<Object> exec = transaction.exec();
        System.out.println(exec);
        jedis.close();
    }

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushAll();
        jedis.set("balance", "100");  // 可用余额
        jedis.set("debt", "0");  // 欠额
        transMethod(jedis);
    }