7uui# Nosql
not only sql,意思:不仅仅是sql
泛指非关系型的数据库、随着互联网web2.0兴起。传统的关系型数据库在应付2.0网站,特别是超大规模和高并发的SNS类型的纯动态网站已经显得力不从心,暴露了很多难以克服的问题。而非关系型的数据库则是由于其本身的特点得到了非常迅速的发展,nosql数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用的难题,包括超大规模数据的存储。
(例如谷歌或者facebook每天为他们的用户手机万亿比特的数据),这些类型的数据存储不需要固定的模式、无需多余操作就可以横向扩展。
传统的RDBMS和NOSQL
RDBMS
- 高度组织化结构化数据
- 结构化查询语言uui
- 数据和关系都存储在单独的表中
- 数据操纵语言、数据定义语言
- 严格的一致性
- 基础事务
NOSQL
- 代表着不仅仅是sql
- 没有声明性查询语言
- 没有预定义的模式
- 键值对存储,列存储、文档存储、图形数据库
- 最终一致性,而非acid属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能、高可用和可伸缩性
大数据时代的3v三高
**3v:**海量(Volume)、多样(Variety)、实时(Velocity)
**3高:**高并发、高扩展、高性能
NOSQL数据库的四大分类
KV键值、文档型数据库(bson)格式比较多
列存储数据库
图关系数据库
CAP+BASE
传统数据库事务的ACID
A(Atomicity)原子性
C(Consistency)一致性
I(Isolation)独立性
D(Durability)持久性
CAP
C(Consistency)强一致性
A(Availablility)可用性
P(Partition Tolerance)分区容错性
BASE
base就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案
base就是基本可行、软状态、最终一致
Redis
三个特点
1、Redis支持数据的持久化、可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
2、Redis不仅仅支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构的存储
3、Redis支持数据的备份、即master-slave模式的数据备份
redis的五大数据类型
1、string:一个key对应一个value,可以理解为memcached一摸一样,最大512M
2、list:列表
3、set:无序无重复
4、zset:有序无重复,关联一个都变了类型的分数
5、hash:类似Map<String,Object>
一些基本操作
String
List
List性能总结
字符串链表、left、right都可以插入添加
如果键不存在、就新建一个列表
如果已经存在、新增内容
如果值已经移除、对应的键也就消失
链表的操作无论是头和尾效率都极高。但假如对中间元素进行操作、效率就很惨淡了
Set
hash
ZSET
Redis.conf
deamonize yes
pidfile /var/ru,/redis.pid
port 6379
tcp-backlog 511
timeout
Bind
tcp-keepalive
Loglevel
Logfile
Syslog-enabled
Syslog-ident
Syslog-facility
Database
redis缓存的过期策略
不管是本地缓存还是分布式缓存,为了保证较高性能,都是使用内存来保存数据,由于成本和内存限制,当存储的数据超过缓存容量时,需要对缓存的数据进行剔除。
一般的剔除策略有 FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低的数据几种策略。
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
Redis持久化
RDB:Redis DataBase
语句:save second changes
stop-write-on-bgsave-error
当出现保存错误的时候停止写的操作。
默认是yes,如果配置为no,表示你不在乎数据一致性或者有其他的手段发现和控制
保存的是一个Dump.rdb文件
rdbcompression
对于存储到磁盘中的快照、可以设置是否进行压缩存储、如果是的话,redis会采用LZF算法进行压缩,如果你不想消耗CPU进行压缩的话可以选择关闭这个功能(效能不高)
rdbchecksum
在存储快照后,还可以让redis使用CRC64算法来进行数据校验、但是这样会增加大约10%的性能消耗、如果希望获取到最大的性能提升,可以关闭此功能。默认开启。
dbfilename
默认名字叫:dumo.rdb
dir
默认的地址,config get dir 可以获得
如何触发RDB快照
1、配置文件中默认的快照配置,拷贝后重新使用,可以cp dump.rdb dump_new.rdb。最好是两台机子存储
2、命令save或者bgsave可以迅速生产rdb文件。
save只是保存,其他不管,全部阻塞。
bgsave:redis会在后台异步进行快照操作,快照同时还可以相应客户端请求,可以通过lastsave命令获取最后一次成功执行快照的时间。
3、执行flushall命令,也会产生。但是没有意义
如何恢复
将备份文件dump.rdb移动到redis安装目录并重新启动服务即可。
优势
1、适合大规模数据恢复、对数据完整性以及一致性要求不高
劣势
在一定间隔时间做一个备份、所以如果redis意外down掉的 话,就会丢失最后一次快照后的所有修改
fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
如何停止
动态所有停止RDB保存规则的方法:redis-cli config set save “”
总结
AOF:Append Only File
以日志的形式来记录每个写操作,将redis执行过的所有写指令记录下来(读操作不记录),
只许追加文件但不可改写文件,redis启动之初会读取该文件重新构建数据、换言之、redis重启的话就根据日志文件的内容将写命令从前到后执行一次以完成数据的回复工作,会记录每一步的操作。甚至是flushall。优先于RDB。redis-check-aof可以修复语法中的错误
appendonly no
默认是关闭的
appendfilename
保存的是appendonly.aof文件
Appendfsync
**Always:**同步持久化,每次发生数据变更会被立即记录到磁盘、性能较差,但是数据完成性比较好。
**Everysec:**出厂默认推荐,异步操作,每秒记录,一秒内宕机。就会数据丢失
No-appendfsync-on-rewirte
重写时是否可以运用appendfsync,用默认no即可,保证数据安全性
Auto-aof-rewrite-min-size
设置重写的基准值
auto-aof-rewrite-prtcentage设置重写的基准值
AOF启动、修复、恢复
启动:appendonly设置为yes
恢复:将所有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复:redis-check-aof
rewrite
AOF采用文件追加方式、文件会越来越大为避免出现此种情况,新增了重写机制、当AOF文件的大小超过所设定的阈值时、redis会启动aof文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用bgrewriteaof
重写原理
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后在rename),遍历新进程的内存数据,每条记录有一条的set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
触发机制
redis会记录上次重写时的AOF的大小、默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
优势:
每秒同步:appendsync always、同步持久化,每次发生数据变更会被立即记录到磁盘、性能较差但是数据完整性比较好
每修改同步:appendfsync everysec,同步操作、每秒记录,如果一秒内宕机,有数据丢失
不同步:appendfsyncno从不同步
劣势
相同数据集的数据而言,aof的文件远大于rdb,恢复速度慢于rdb
aof的运行效率低于rdb,每秒同步策略效率较好,不同步效率和rdb相同
总结
RDBAOF整体总结
redis的事务
可以一次执行多个命令,本质是一组命令 的集合,一个事务中的所有命令都会序列化、顺序地串行化执行而不会被其他命令插入。不许加塞。
常用命令:
watch监控
三个阶段
三特性
Redis的发布订阅
进程间的通信模式,发送者(pub)发送消息,订阅者(sub)接受消息
redis主从复制
**主从复制:**主机数据更新后根据配置和策略、自动同步到备机的Master、Slaver机制、Master以写为主、Slaver以度为主
哨兵模式
Sentinel(哨兵)进程的工作方式:
1. Sentinel(哨兵)进程使用ping-pong机制以每秒钟一次的频率对Master主服务器进行监测,同时哨兵可以通过主服务器获取到从服务器的信息。
2. 如果哨兵距离最后一次有效回复 PING-PONG 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个Master主服务器会被 Sentinel(哨兵)认为宕机了。
3.当主服务器宕机后,哨兵通过投票(所有的哨兵参与投票)从节点服务器(从服务器)中选出一台服务器作为主机服务器,并将其他从机的主从关系(redis.conf文件)修改为新主机,此外将宕机的主服务器的主从关系修改为新服务器(旧主机修改为从机,当旧主机修复重启后作为从机).
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库装换为主库
总结:主机挂了。从机投票当主机。主机回来变成从机当小弟
缺点:
由于所有的写操作都先在Master上操作,然后同步更新到slave上。所以Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题可能会很严重,Slave机器数量的增加也会导致这个问题。
redis常用命令:
1.启动redis服务: redis-server redis.conf
2.关闭redis服务: redis-cli -p 端口号 shutdown
3.进入redis客户端: redis-cli -p 端口号
4.退出redis客户端: ctrl+c或exit
5.从机挂载主机(在从机客户端操作): slaveof 主机IP 主机端口
6.查看主从关系(在客户端操作): info replication
7.启动redis哨兵: redis-sentinel sentinel.conf
redis 的线程模型了解么?
Redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
多个 Socket
IO 多路复用程序
文件事件分派器
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 Socket,会将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
Cache Aside Pattern
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存?
原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。
比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,这个缓存到底会不会被频繁访问到?
举个栗子:一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。
实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。用到缓存才去算缓存。
其实删除缓存,而不是更新缓存,就是一个 Lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。
像 Mybatis,Hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 List,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。
缓存常见问题
缓存更新方式
这是决定在使用缓存时就该考虑的问题。
缓存的数据在数据源发生变更时需要对缓存进行更新,数据源可能是 DB,也可能是远程服务。更新的方式可以是主动更新。数据源是 DB 时,可以在更新完 DB 后就直接更新缓存。
当数据源不是 DB 而是其他远程服务,可能无法及时主动感知数据变更,这种情况下一般会选择对缓存数据设置失效期,也就是数据不一致的最大容忍时间。
这种场景下,可以选择失效更新,key 不存在或失效时先请求数据源获取最新数据,然后再次缓存,并更新失效期。
但这样做有个问题,如果依赖的远程服务在更新时出现异常,则会导致数据不可用。改进的办法是异步更新,就是当失效时先不清除数据,继续使用旧的数据,然后由异步线程去执行更新任务。这样就避免了失效瞬间的空窗期。另外还有一种纯异步更新方式,定时对数据进行分批更新。实际使用时可以根据业务场景选择更新方式。
数据不一致
第二个问题是数据不一致的问题,可以说只要使用缓存,就要考虑如何面对这个问题。缓存不一致产生的原因一般是主动更新失败,例如更新 DB 后,更新 Redis 因为网络原因请求超时;或者是异步更新失败导致。
解决的办法是,如果服务对耗时不是特别敏感可以增加重试;如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以。
缓存穿透
缓存穿透。产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。
解决的办法如下。
对不存在的用户,在缓存中保存一个空对象进行标记,防止相同 ID 再次访问 DB。不过有时这个方法并不能很好解决问题,可能导致缓存中存储大量无用数据。
使用 BloomFilter 过滤器,BloomFilter 的特点是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。非常适合解决这类的问题。
缓存击穿
缓存击穿,就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。
解决这个问题有如下办法。
可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。
使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。
针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效。
缓存雪崩
缓存雪崩,产生的原因是缓存挂掉,这时所有的请求都会穿透到 DB。
解决方法:
使用快速失败的熔断策略,减少 DB 瞬间压力;
使用主从模式和集群模式来尽量保证缓存服务的高可用。
实际场景中,这两种方法会结合使用。