文章目录
NoSQL
泛指菲关系型数据库。大规模数据存储,这些类型的数据存储不需要固定的模式,无需多余的操作就可以横向扩展
特性
-
易扩展
去掉关系数据库的关系型特性,数据之间无关系,这样就非常容易扩展。
-
大数据量高性能
非常高的读写性能,关系型数据库使用Query Cache,每次表更新Cache就失效,Cache性能不高。NoSQL的Cache是记录级的,性能高
-
多样灵活的数据模型
-
传统RDBMS和NoSQL
RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
-键 - 值对存储,列存储,文档存储,图形数据库 - 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
大数据的3V和3高
-
海量
-
多样
-
实时
-
高并发
-
高可扩
-
高性能
四大分类
-
KV键值
- redis
- memcache 多线程,非阻塞IO复用的网络模型
- tail
- BerkeleyDB
-
文档型数据库
-
MongoDB
-
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
-
-
CouchDB
-
-
列存储数据库
- HBase
- Cassandra
- 分布式文件系统
-
图关系数据库
- Neo4J
- InfoGrid
分布式数据库中的CAP原理CAP+BASE
- C:Consistency(强一致性)
- A:Availability(可用性)
- P:Partition tolerance(分区容错性)
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。
而由于当前的网络硬件肯定会出现延迟丢包等问题,所以
分区容忍性是我们必须需要实现的。
所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。
C:强一致性 A:高可用性 P:分布式容忍性(分区容错性)
-
CA 传统Oracle数据库
-
AP 大多数网站架构的选择
-
CP Redis、Mongodb
注意:分布式架构的时候必须做出取舍。
一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。
因此牺牲C换取P,这是目前分布式数据库产品的方向
一致性与可用性的决择
对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地
数据库事务一致性需求
很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低, 有些场合对写一致性要求并不高。允许实现最终一致性。
数据库的写实时性和读实时性需求
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。
对复杂的SQL查询,特别是多表关联查询的需求
任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角 度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。
Redis
Remote Dictionary Server远程字典服务器。C语言编写
是一个高性能的KV键值型分布式内存数据库,基于内存运行,并支持持久化的NoSQL数据库。(数据结构数据库)
使用Redis因为它读取数据快,直接从内存中读取,支持持久化,支持丰富的数据类型,支持事务,且拥有丰富的特性,可用于缓存,消息,设置key过期时间等等
特点
- Redis使用单线程的IO复用模型
- 支持数据的持久化,先将数据存在内存当中,可已将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
- 不仅支持简单的KV类型的数据,还提供了String,Hash,Set,ZSet,List
- 支持数据的备份,即master-slave模式的数据备份
应用场景
- 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务(缓存:提高网站访问速度,降低数据库压力)
- 取最新N个数据的操作:如将最新的10条评论的ID放到Redis的List集合中
- 模拟类似于HttpSession这种需要设定过期时间的功能
- 发布、订阅消息系统
- 定时器,计数器,排行榜
Redis详解
-
单进程模型来处理客户端的请求。对读写等时间的响应是通过对epoll函数的包装来做到的。
-
默认16个数据库,类似数组下表从0开始,初始默认使用0号库
-
select命令切换数据库(0-15)
- redis-server /opt/redis-3.0.4/redis.config服务端启动
- redis-cli -p 3679客户端连接
-
Dbsize查看当前数据库的key的数量
- keys *查看所有key
- keys k1(keys k?)
-
del 键名删除key
- set runoob “hello”
- dek runoob
-
flushDB删除当前库数据(flushAll删除所有库数据)
-
统一密码管理,16个库一个密码
-
redis索引从0开始
-
默认端口6379
Redis数据类型
即value的数据类型
-
String
- 和Memcached一样的类型,一个key对应一个value
- 二进制安全的,即string可以包含任何数据,如jpg图片或者序列化的对象
- 字符串value最多可以是512M
-
Hash哈希
- 类似java中的Map< String,Object>
- 键值对集合
- string类型的field和value的映射表,适合用于存储对象
- 命令HMset Hget
- HMSET runoob field1 “h1” field2 “h2”
- HGET runoob field1
- del runoob
- 命令HMset Hget
-
List列表
- 简单的字符串列表
- 按照插入顺序排序,可以添加到头或尾
-
Set集合
- redis的set是String类型的无序集合。通过HashTable实现的
-
Zset有序集合(Sorted Set)
- 不允许重复的成员
- 每个元素都会关联一个double类型的分数
- 通过分数为集合中成员进行从小到大的排序
- zset的成员是惟一的,但分数可以重复
Redis命令
key关键字
- keys *:查询所有key
- exists (key) keyname :判断某个key是否存在
- move keyname db:将当前库的key移到db库
- expire keyname 秒:为给定的key设置过期时间
- ttl keyname:查看还有多少秒过期,-1表示永不过期,-2表示已经过期
- type keyname查看你的key所存储的值的类型(五大数据类型)
- del keyname
String字符串
- get/set/sppend/strlen
- set k1 v1
- get k1
- append k1 123
- strlen k1:5
- incr/decr/incrby/decrby 数字类型才能进行加减
- set k1 1
- incr k1:单路递增
- decr k1:单路递减
- incrby k1 3:多路递增
- decrby k1 3:多路递减
- getrange/setrange 获得区间范围的值
- set k1 abc123
- getrange k1 0 -1:取全部,左闭右闭
- getrange k1 0 2:返回abc
- setrange k1 3 0:返回6,k1为 abc023
- setrange k1 3 0000:返回7,k1为abc0000
- setex(set with expire)/setnx(set if not exist)
- setex k1 10 v1:设值并设置过期时间
- setnx k1 v11:已存在,设值失败
- mget/mset/msetnx(多值设置)
- mset k1 v1 k2 v2 k3 v3
- mget k1 k2 k3
List列表
单值多value
实际上是一个字符串链表,left right都可以插入
如果键不存在,创建新的链表
已存在,新增内容
值全部移除,对应的键也就消失
- lpush/rpush/lrange
- lpush list1 1 2 3 4 5
- lrange list1 0 -1:顺序显示5 4 3 2 1
- lpush list2 1 2 3 4 5
- lrange list1 0 -1:顺序显示1 2 3 4 5
- lpop/rpop:左右移出元素
- lindex:按照索引下标获得元素
- llen:返回list长度
- lrem keyname N value:删除N个value
- rpush list1 1 1 1 2 2 2 3 3 3
- lrem list1 2 3:删除2个3
- lrange list1 0 -1:顺序显示1 1 1 2 2 3
- ltrim keyname 开始index 结束index,截取指定范围的值后再赋值给key(左闭右闭)
- rpush list1 1 2 3 4 5
- ltrim list1 3 4
- lrange list1:顺序显示4 5
- rpoplpush
- rpush list1 1 2 3
- rpush list2 4 5 6
- rpoplpush list1 list2
- lrange list2 0 -1:顺序显示3 4 5 6
- lset keyname index value 设置某个索引value
- rpush list1 1 2 3
- lset list1 1 x
- lrange list1 0 -1:顺序显示1 x 3
- linsert keyname before/after value1 value2前后插值
- linsert list1 before x java
- lrange list1 0 -1:顺序显示1 java x 2
Set集合
单值多value
-
sadd/smembers/sismember
- sadd set1 0 1 2 3 3
- smembers set1:顺序显示1 2 3
- sismeber set1 x:查看set1中是否有x
-
scard 获取集合里面的元素个数
-
srem keyname value 删除集合中元素
-
srandmember keyname N 随机出集合中N个数
-
spop keyname 随机出栈
-
smove key1 key2 key1中某个值:将key1中某个值赋给key2
-
sdiff/sinter/sunion 差交并
Hash哈希
KV模式不变,但value是一个键值对
-
hset/hget/hmset/hmget/hgetall/hdel
- hset user name mike
- hget user name
- hmset customer id 1 name lucy age 26
- hmget customer id name age
- hgetall customer
- hdel user name
-
hlen 返回hash中键值对个数
-
hexists keyname key
- hexists customer id:返回1
-
hkeys/hvals keyname:返回hash的键集合和值集合
-
hincrby/hincrbyfloat:自增
- hincrby customer age 2
Zset有序集合
- zadd/zrange
- zadd zset01 60 v1 70 v2 80 v3
- zrange zset01 0 -1 (withsocres)
- zrangebyscore keyname 开始score 结束score:取分数范围元素
- zrem keyname 某score对应的value值:删除对应元素
- zcard/zcount/zrank/zscore
- zcard keyname 返回zset元素个数
- zcount zset01 60 70 :返回分数区间元素个数
- zrank zset01 v3:获得下标值,返回2
- zscore zset01 v2:获得分数,返回70
- zrevrank 逆序获得下标值
- zrevrank zset01 v3:返回0
- zrevrange 逆序输出
- zrevrangebyscore zset01 80 60:逆序输出分数区间元素
Redis配置
/opt/redis-xx-xx/redis.config
Redis持久化
持久化:将内存的数据写到磁盘上去,防止服务器宕机内存数据丢失
RDB
Redis DataBase
RDB文件:dumn.rdb
功能函数rdbSave生成RDB文件和rdbLoad从文件加载到内存中。
利用fork命令的copy on write机制,在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有数据,将数据写成RDB文件替换旧的RDB文件进行持久化。恢复时将快照文件直接读取到内存里。
缺点:数据库出问题,最后一次持久化后的数据可能丢失
应用场景
整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方
式要比AOF方式更加的高效。
触发机制
RDB文件是整个内存压缩过的snapshot快照,RDB的数据结构可以配置复合 的默认快照触发条件
-
save < seconds> < changes>
-
save 900 1 (15min修改过1次)
-
save 300 10 (5min修改过10次)
-
save 60 10000 (1min修改过10000次)
-
save " " 禁用
-
save/bgsave命令可立即备份(/usr/local/bin)
- save:只管保存,其他不管,全部阻塞
- bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求,可以通过lastsave命令获得最后一次成功执行快照的时间
- 执行flushall命令,也会产生dumo.rdb文件,但里面是空的,无意义
动态停止RDB保存规则的方法:redis-cli config set save " "
总结
- RDB保存RDB文件时,父进程只需要fork出一个子进程,持久化全部由子进程完成,父进程不需要做其它IO操作。RDB持久化方式可以最大化redis的性能
- 与AOF相比,在恢复大的数据集时,RDB方式会更快一点
- 数据丢失风险大,完成性不保证
- 数据集较大时,fork过程非常耗时,可能导致Redis在毫秒级不能响应客户端请求
AOF
Append Only File
AOF文件:appendonly.aof
配置位置:redis.config中 appendonly no
将Redis执行的每次写命令记录到独立的日志文件中,Redis重启时再次执行AOF文件中的命令恢复数据
注意:写命令包括flushall
使用AOF时,如果同时存在dump.rdb和appendonly.aof时,可以共存,先加载appendonly.aof
修复aof文件:redis-check-aof --fix appendonly.aof
配置之Appendfsync
- Always:同步持久化,性能较差,但完整性好
- Everysec:默认,异步操作,每秒记录,如果一秒内宕机,有数据丢失
- no:从不同步
重写机制(Rewrite)
-
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,
当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,
只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
-
重写原理:
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),
遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,
而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似 -
触发机制:Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
总结
-
每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
-
每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
-
不同步:appendfsync no 从不同步
-
相同数据集而言aof文件要远大于rdb文件,恢复速度慢于rdb
-
AOF运行效率慢于RDB,每秒同步策略好,不同步效率与RDB相同
对比使用
-
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
-
AOF持久化方式记录每次对服务器的写操作,当服务器重启时重写执行这些命令恢复数据。文件追加方式记录,文件过大时支持后台重写
-
只做缓存,可以不使用任何持久化方式
-
同时开启两种持久化方式
- redis重启时优先载入AOF文件来恢复原始的方式,因为AOF文件保存的数据集更加完整
- RDB不实时,但更适合用于备份数据库(AOF在不断变化不好备份)
Redis的事务
可以一次执行多个命令,本质是一组命令的集合。
一次事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不允许加塞
一次队列中,一次性、顺序性、排他性地执行一系列命令
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
事务命令
序号 | 命令及描述 |
---|---|
1 | DISCARD 取消事务,放弃执行事务块内的所有命令。 |
2 | EXEC 执行所有事务块内的命令。 |
3 | MULTI 标记一个事务块的开始。 |
4 | UNWATCH 取消 WATCH 命令对所有 key 的监视。 |
5 | [WATCH key key …] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
正常执行
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a aaa
QUEUED
redis 127.0.0.1:6379> set b bbb
QUEUED
redis 127.0.0.1:6379> set c ccc
QUEUED
redis 127.0.0.1:6379> exec
1) OK
2) OK
3) OK
放弃事务
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a a4
QUEUED
redis 127.0.0.1:6379> discard
OK
全体连坐
一次事务中出现错误(语法出现错误),事务内所有命令执行失败,exec报错所有语句得不到执行
部分支持事务
语法本身没有错误,但适用对象错误,例如incr ,exec会执行正确的语句,并跳过有问题的语句
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
watch监控
乐观锁机制,对key开启监控后,如果另一线程对key进行修改,当前线程开启事务multi,对key进行修改,exec执行事务失败。
监视一个或多个key,如果在事务执行之前这些key被其他命令所动,那么事务将被打断
redis 127.0.0.1:6379> set k1 v1
redis 127.0.0.1:6379> watch k1
redis 127.0.0.1:6379> multi
OK
此时另一个终端进行操作
redis 127.0.0.1:6379> set k1 v2
OK
执行完毕后,当前终端继续执行
redis 127.0.0.1:6379> set k1 k2
QUEUED
redis 127.0.0.1:6379> exec
(nil)
事务失败
unwatch取消对所有key的监控,一旦执行了exec之前加的监控锁都被取消
Redis的发布和订阅
进程间的通信方式:消息队列,管道,套接字,共享内存,信号量
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
实例
以下实例演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为 redisChat:
redis 127.0.0.1:6379> SUBSCRIBE c1 c2 c3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 1
1) "subscribe"
2) "c3"
3) (integer) 1
现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。
redis 127.0.0.1:6379> PUBLISH c1 "message to c1"
(integer) 1
redis 127.0.0.1:6379> PUBLISH c2 "message to c2"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "c1"
3) "message to c1"
1) "message"
2) "c2"
3) "message to c2"
Redis 发布订阅命令
下表列出了 redis 发布订阅常用命令:
序号 | 命令及描述 |
---|---|
1 | [PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。 |
2 | [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。 |
3 | PUBLISH channel message 将信息发送到指定的频道。 |
4 | [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。 |
5 | [SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。 |
6 | [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。 |
Redis复制
也就是我们所说的主从复制,主机数据更新后根据配置和策略,
自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
作用
- 读写分离
- 异地容灾
例:
配置6379 6380 6381端口的redis主机
通过info replication
指令查看三个redis主机信息,role均为master
如在6379进行设值
redis 127.0.0.1:6379> set k1 v1
OK
另外两个转为6379的从库,配置命令slaveof 主库IP 主库端口
redis 127.0.0.1:6379> slaveof 127.0.0.1 6379
OK
redis 127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
6379再次设值
redis 127.0.0.1:6379> set k2 v2
OK
此时两个从库数据已经从主库中全部复制过来,且执行info replication
指令发现从库role变为slave
一主二从
读写分离只有主机能写,从机只能读
主机SHUTDOWN后,从机原地待命,角色不变;主机启动,恢复原状
从机SHUTDOWN,与master断开,每次断开之后都需要重新连接,除非配置进redis.config文件
薪火相传
-
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他
slaves的连接和同步请求,那么该slave作为了链条中下一个的master,
可以有效减轻master的写压力 -
中途变更转向:会清除之前的数据,重新建立拷贝最新的
-
slaveof 新主库IP 新主库端口
反客为主
通过命令SLAVEOF no one
可以配置为master角色
复制原理
复制原理 |
---|
slave启动成功连接到master后会发送一个sync命令 |
Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步 |
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。 |
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步 |
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行 |
哨兵模式(sentinel)
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
自定义sentinel.config文件,配置哨兵,填写内容
sentinel monitor 被监控数据库名字(自定义) 127.0.0.1 6379 1(票数)
启动哨兵
redis-sentinel /myredis/sentinel.conf
之前的master重启回来变为从库
复制延迟
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。