读写分离
读写分离的基本实现
- 数据库服务器搭建主从集群,一主一从、一主多从都可以。
- 数据库主机负责读写操作,从机只负责读操作。
- 数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
- 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
复制延迟问题
- 写操作后的读操作指定发给数据库主服务器
- 读从机失败后再读一次主机
- 关键业务读写操作全部指向主机,非关键业务采用读写分离(常用)
分配机制
程序代码封装: 程序代码封装指在代码中抽象一个数据访问层(所以有的文章也称这种方式为“中间层封装”),实现读写操作分离和数据库服务器连接的管理。例如,基于Hibernate进行简单封
装,就可以实现读写分离
目前开源的实现方案中,淘宝的TDDL(Taobao Distributed Data Layer,外号:头都大了)是比较有名的。它是一个通用数据访问层,所有功能封装在jar包中提供给业务代码调
用。其基本原理是一个基于集中式配置的 jdbc datasource实现,具有主备、读写分离、动态数据库配置等功能
中间件封装: 中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。中间件对业务服务器提供SQL兼容的协议,业务服务器无须自己进行读写分离。对于业务服务
器来说,访问中间件和访问数据库没有区别,事实上在业务服务器看来,中间件就是一个数据库服务器。
目前的开源数据库中间件方案中,MySQL官方先是提供了MySQL Proxy,但MySQL Proxy一直没有正式GA,现在MySQL官方推荐MySQL Router。MySQL Router的主要功能有读写分离、故障自动切换、负载均衡、连接池等, 奇虎360公司也开源了自己的数据库中间件Atlas,Atlas是基于MySQL Proxy实现的.
分库分表
业务分库
业务分库指的是按照业务模块将数据分散到不同的数据库服务器。例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上,而不是将所有数据都放在一台数据库服务器上。
引申出来的一些问题:
- join操作问题, 无法直接join关联
- 事务问题, 需要使用分布式事务等
- 成本问题, 需要多台服务器组建集群
分表
1.垂直分表
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。
垂直分表引入的复杂性主要体现在表操作的数量要增加。原来只要一次查询就可以获取,现在需要两次及以上的查询。
2.水平分表
水平分表适合表行数特别大的表,有的公司要求单表行数超过5000万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过1000万就要分表了;而对于一些简单的表,即使存储数据超过1亿行,也可以不分表。但不管怎样,当看到表的数据量达到千万级别时, 就要警觉起来, 因为这很可能是架构的性能瓶颈或者隐患。
水平分表相比垂直分表,会引入更多的复杂性,主要表现在下面几个方面:
路由: 水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算,这个算法会引入一定的复杂性.
join操作: 水平分表后,数据分散在多个表中,如果需要与其他表进行join查询,需要在业务代码或者数据库中间件中进行多次join查询,然后将结果合并.
count()操作: 水平分表后,虽然物理上数据分散到多个表中,但某些业务逻辑上还是会将这些表当作一个表来处理。例如,获取记录总数用于分页或者展示.
order by操作: 水平分表后,数据分散到多个子表中,排序操作无法在数据库中完成,只能由业务代码或者数据库中间件分别查询每个子表中的数据,然后汇总进行排序。
高性能NoSQL
关系型数据库的缺点(NoSQL数据库产生的原因)
- 关系数据库存储的是行记录,无法存储数据结构
- 关系数据库的schema扩展很不方便
- 关系数据库在大数据场景下I/O较高
- 关系数据库的全文搜索功能比较弱
常见的NoSQL数据库类型
- K-V存储:解决关系数据库无法存储数据结构的问题,以Redis为代表。
- 文档数据库:解决关系数据库强schema约束的问题,以MongoDB为代表。
- 列式数据库:解决关系数据库大数据场景下的I/O问题,以HBase为代表。
- 全文搜索引擎:解决关系数据库的全文搜索性能问题,以Elasticsearch为代表。
缓存架构
缓存相关问题
- 缓存穿透
- 缓存数据在数据库中就不存在
- 数据从数据库中加载到缓存中时间很长, 导致压力集中在数据库上(也叫缓存击穿)
解决方案: 在缓存中设置默认值
解决方案: 可以进行缓存预热, 提前加载到缓存中
- 缓存雪崩
缓存雪崩指的是当缓存失效(过期)后引起的系统性能急剧下降的情况, 大量请求同一时间查询数据库并同时进行数据缓存, 对数据库和缓存造成巨大的压力
解决方案:
1. 更新锁, 对缓存更新操作加锁, 只有获取到锁之后才能更新缓存, 其他的请求直接拒绝
2. 后台更新, 由后台线程来更新缓存, 而不是由业务线程来更新缓存, 简单来说由后台来对缓存进行管理(缓存预热也可以用这种方案)
3. 双key策略, 要缓存的key过期时间是t,key1没有过期时间。每次缓存读取不到key时就返回key1的内容,然后触发一个事件。这个事件会同时更新key和key1。