乘风破浪,遇见云原生(Cloud Native)之Docker Desktop for Windows 运行Redis多实例并实现主从(Master-Slave)部署
话说数据存储演化史
单实例时代
上个世纪90年代,那时候网站还都是静态为主,动态的都不多,单实例数据库完全可以轻松应付。
这样的单实例架构会遇到如下瓶颈
- 数据量的总大小,一个机器放不下时
- 数据的索引(B+ Tree),一个机器的内存放不下时
- 访问量(读写混合),一个实例不能承受
- ...
缓存+多实例+垂直拆分
随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,Web程序不再仅仅专注在功能上,同时也在追求性能。程序员们开始大量的使用缓存技术来缓解数据库的压力,优化数据库的结构和索引。
开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续增大的时候,多台Web机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。
多实例+主从读写分离
由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。
读写集中在一个数据库上让数据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性。Mysql的Master-Slave模式成为这个时候的网站标配了。
多实例集群+分表分库+水平拆分
在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用InnoDB
引擎代替MyISAM
。
同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题。这个时候,分表分库成了一个热门技术,是面试的热门问题也是业界讨论的热门技术问题。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。
扩展性瓶颈
MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。
关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。
NoSQL的诞生
第三方平台(如:Baidu,Weibo等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。
我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。
NoSQL
什么是NoSQL
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”
泛指非关系型的数据库。随着互联网Web2.0网站的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
百度或Weibo每天为他们的用户收集万亿比特的数据,这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
易扩展性
NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。
数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。
大数据高性能
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。
MySQL一般使用Query Cache,每次表的更新Cache就失效,是一种大粒度的Cache,在针对Web2.0的交互频繁的应用,Cache性能不高。
NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多了。
数据模型灵活多样
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。
在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。
关系型数据库和非关系型数据库对比
关系型数据库(RDBMS)
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
非关系型数据库(NoSQL)
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
互联网应用三V三高
3V指代
- 海量(Volume)
- 多样(Variety)
- 实时(Velocity)
三高指代
- 高并发
- 高可扩
- 高性能
四大分类
主要分为下面四大类
- 键值(Key-Value)存储数据库
- 文档型数据库
- 列存储数据库
- 图形(Graph)数据库
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo Cabinet/Tyrant,Redis,Voldemort,Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 | Key指向Value的键值对,通常用hashtable来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra,HBase,Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB,MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图形(Graph)数据库 | Neo4J,InfoGrid,Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。 |
数据库理论
ACID概念
- A (Atomicity) 原子性
- C (Consistency) 一致性
- I (Isolation) 独立性
- D (Durability) 持久性
CAP概念
- C (Consistency) 强一致性
- A (Availability) 可用性
- P (Partition Tolerance) 分区容错性
CAP理论
一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
BASE理论
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
- 基本可用 (Basically Available)
- 软状态 (Soft state)
- 最终一致 (Eventually Consistent)
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。
为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法。
Redis
什么是Redis
Redis全称Remote Dictionary Server(远程字典服务),它是一个基于内存实现的键值型非关系(NoSQL)数据库,由意大利人Salvatore Sanfilippo使用C语言编写。
Redis与其他Key-Value缓存产品有以下三个特点
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
- Redis支持数据的备份,即master-slave模式的数据备份
使用场景
- 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
- 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
- 模拟类似于HttpSession这种需要设定过期时间的功能
- 发布、订阅消息系统
- 定时器、计数器
基础知识
单进程
Redis使用单进程模型来处理客户端的请求。对读写等事件的响应。是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率。
Epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
默认数据库
Redis默认有16个数据库,类似数组下表从零开始,初始默认使用零号库。
常用命令
- Select,命令可以切换不同的数据库
- Dbsize,查看当前数据库的key的数量
- Flushdb,清空当前库
- Flushall,通杀全部库
密码管理
Redis的16个库都是同样密码,要么都OK要么一个也连接不上。
索引起始
Redis索引都是从零开始的。
默认端口
- 6379
数据类型
String(字符串)
String是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个Key对应一个Value。
String类型是二进制安全的。意思是Redis的String可以包含任何数据。比如Jpg图片或者序列化的对象。
String类型是Redis最基本的数据类型,一个Redis中字符串Value最多可以是512M
Hash(哈希)
Redis Hash是一个键值对集合。
Redis Hash是一个String类型的Field和Value的映射表,Hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
它的底层实际是个链表。
Set(集合)
Redis的Set是String类型的无序集合。它是通过HashTable实现实现的。
Zset(有序集合)
Redis的Zset(Sorted Set)和Set一样也是String类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个Double类型的分数。
Redis正是通过分数来为集合中的成员进行从小到大的排序。Zset的成员是唯一的,但分数(Score)却可以重复。
持久化(RDB)
什么是RDB
Redis Database(RDB)是在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
如何操作
保存数据到快照:
Rdb保存的是dump.rdb
文件,使用save
或者bgsave
命令即可触发RDB快照,
Save
,Save时只管保存,其它不管,全部阻塞Bgsave
,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求Lastsave
,获取最后一次成功执行快照的时间
恢复快照数据
将备份文件(dump.rdb
)移动到Redis安装目录并启动服务即可。
优劣
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
劣势
- 在一定间隔时间做一次备份,所以如果Redis意外Down掉的话,就会丢失最后一次快照后的所有修改
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
持久化(AOF)
什么是AOF
AOF全称是Append Only File
是指以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
如何操作
AOF保存的是appendonly.aof
文件,使用前修改默认的appendonly no
,改为yes
,恢复时重启redis便会自动加载。
优劣
优势
- 修改同步:
Appendfsync Always
同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好 - 每秒同步:
Appendfsync EverySec
异步操作,每秒记录如果一秒内宕机,有数据丢失
劣势
- 相同数据集的数据而言AOF文件要远大于RDB文件,恢复速度慢于RDB
- AOF运行效率要慢于RDB,每秒同步策略效率较好,不同步效率和RDB相同
事务
- 开启:以
MULTI
开始一个事务 - 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由
EXEC
命令触发事务 - 放弃:
discard
- 监控:
Watch
指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
主从复制
什么是主从复制
主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
复制操作
Slave启动成功连接到Master后会发送一个Sync命令
Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,Master将传送整个数据文件到Slave,以完成一次完全同步。
复制模式
- 全量复制:而Slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
- 增量复制:Master继续将新的所有收集到的修改命令依次传给Slave,完成同步
只要是重新连接Master,一次完全同步(全量复制)将被自动执行
特点
一主二仆
一个Master两个Slave,主机复制写,备机负责读。
薪火相传
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力。
反客为主
使当前数据库停止与其他数据库的同步,转成主数据库。
管道
通常使用Redis的方式是,发送命令,命令排队,Redis执行,然后返回结果,这个过程称为Round Triptime(简称RTT,往返时间)。但是如果有多条命令需要执行时,需要消耗N次RTT,经过N次IO传输,这样效率明显很低。
于是Redis管道(Pipeline)便产生了,一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(Pipelining),减少了RTT,提升了效率。
使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。
发布订阅
发布订阅是一种消息模式,发送者(Sub)发送消息,订阅者(Pub)接收消息。
发布订阅基于频道实现的,同一个频道可以有多个订阅者,多个发布者。其中任意一个发布者发布消息到频道中,所以订阅者都可以收到该消息。