数据库、Redis和系统设计相关的问题
Redis和Memcached的异同。
Memcached
- 可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS;
- 只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。
- 无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。
- 无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。
- Memcached内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时会造成内存利用率降低, 并引发低利用率时依然出现踢出等问题。需要用户注重value设计。
Redis
- 支持多种数据结构,如string、 list、hash、set、zset等;
- 支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段;
- 支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制来实现HA;
- 单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题,但性能受限于CPU性能,故单实例CPU最高才可能达到5-6wQPS每秒;
- 支持pub/sub消息订阅机制,可以用来进行消息订阅与通知;
- Redis在string类型上会消耗较多内存,可以使用hash表压缩存储以降低内存耗用。
Redis作为分布式缓存可能会存在哪些问题,怎么解决?
- 缓存穿透预防及优化:缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中;缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义;解决方法:缓存空对象和布隆过滤器拦截;
- 缓存雪崩问题优化:由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况;预防和解决缓存雪崩问题,可以从以下三个方面进行着手:保证缓存层服务高可用性、依赖隔离组件为后端限流并降级、提前演练。
- 缓存热点key重建优化:开发人员使用缓存和过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但如果热点Key和重建缓存耗时两个问题同时出现,可能就会对应用造成致命的危害;解决方法:互斥锁(只允许一个线程重建缓存)、永远不过期(唯一不足的就是重构缓存期间会出现数据不一致的情况)。
谈谈对Mysql索引的认识。
- 主键查询走索引,我们一般使用的索引都是Btree索引;
- MyISAM和InnoDB索引结构有很大差异,这里以InnoDB为例,InnoDB的叶节点存储的是数据的行,而除了主键之外的列索引存储的是主键key,也就是说在查询的时候需要二次查询,先通过列索引找到主键,再通过主键索引找到row。
- 针对我们经常查询的多列场景,我们可以建组合索引,组合索引在可以尽可能多的运用列的查询规则。说到组合索引那么必须说一下最左前缀原则:指的的是在sql where子句中一些条件或表达式中出现的列的顺序要保持和多索引的一致或以多列索引顺序出现,只要出现非顺序出现、断层都无法利用到多列索引。
- 索引的区分度,主要是衡量索引值不相同的程度,区分度越大,越有利于索引的查询。
- 组合索引的顺序和区分度:对于多个列构成的组合索引,在查询过滤的时候也是和列的位置有关的,这也是最左前缀规则说的事情,也就是说如果在第一次能过滤掉大量的数据,那么后续的索引匹配就能减少很多消耗。所以在选择索引顺序的时候最好是要考虑到区分度的问题,将区分度比较高的列放在前面。
- 只有当索引的列顺序和Order By子句的顺序完全一致时,并且所有的列的排序方向都一样时,才能使用索引对子句进行排序。
- 应该注意的几点:在使用索引查询的时候,需要保证索引类型和查询的数据类型一致,经常混用的是用int型查询varchar类型的数据或反过来,这样会导致索引失效;range查询要尽量放在后面,因为在range后面的查询不会走索引,这一点在设计索引时要注意;Like查询不能前缀模糊匹配,也就是说不可以like ‘%123’,因为like的后缀模糊 like ‘123%’可以转化为range查询,但是前缀模糊不可以;索引不是越多越好,索引十分大时不仅会影响查询效率,同时会为数据的插入造成很大的负担;对于重复索引需要删除,规划好索引是高效率的前提。
- 主键索引和唯一索引的区别:主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键;主键不允许为空值,唯一索引列允许空值;一个表只能有一个主键,但是可以有多个唯一索引主键可以被其他表引用为外键,唯一索引列不可以;主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构,两者有本质的差别。
MySql的事务隔离级别有哪几种?
- 隔离级别用于表述并发事务之间的相互干扰程度,其基于锁机制进行并发控制。
- 可序列化(Serializable):事务一个接一个的执行,完全相互独立;实现可序列化要求在选定对象上的读锁和写锁保持直到事务结束后才能释放;在SELECT的查询中使用一个WHERE子句来描述一个范围时应该获得一个“范围锁”。
- 可重复度(Repeatable Read):事务A读取数据之后,对涉及的数据加锁,不允许其他事务进行修改,由于其他事务会插入新的数据,因此会产生幻读;对选定对象的读锁和写锁一直保持到事务结束,但不要求“范围锁”,因此可能会发生幻读;可重复读是MySQL的默认事务隔离级别。
- 读取已提交(Read Committed):只能看到其他事务已经提交的数据,避免了脏读,但存在不可重复读、幻读;DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在SELECT操作完成后马上释放,且不要求“范围锁”。
- 读取未提交(Read Uncommitted):可以看到其他事务没有提交的数据,出现脏读、不可重复读、幻读;
[PS补充]
不可重复读的重点是修改:同样的条件,读取过的数据,再次读取出来发现值不一样了。
幻读的重点在于新增或者删除:同样的条件,第1次和第2次读出来的记录数不一样。
MySql的MVVC有什么作用?
- 多版本并发控制(MVCC),来实现MySQL上的多事务并发访问时,隔离级别控制;
- 数据版本:并发事务执行时,同一行数据有多个版本;
- 事务版本:每个事务都有一个事务版本;
- 版本有序:版本是通过时间来标识的;
- 通过MVCC实现的效果是同一时刻、同一张表、多个并发事务,看到的数据是不同的。
- MVCC本质使用了copy-on-write,为每个数据保留多份snapshot,不同snapshot之间,使用指针连接成链表;update操作,能看到的snapshot是受限的,是链表上小于等于当前事务版本的最大版本(读取已提交:离当前事务最近的已提交版本);
如何设计高性能、高并发、高可用的系统。
- 系统架构三个利器:RPC服务组件、消息中间件(交互异步化、流量削峰)、配置管理(灰度发布、降级);
- 无状态:接口层最重要的就是无状态,将有状态的数据剥离到数据库或缓存中;
- 如何改善延时:找关键路径(“28原则”)、空间换时间,如各级缓存;时间换空间,如传输压缩,解决网络传输的瓶颈;多核并行,减少锁竞争;更适合的算法和数据结构;通过性能测试和监控找出瓶颈;减少系统调用和上下文切换;
- 如何提高吞吐量:复制、扩容、异步化、缓存;
- 如何保障稳定性:提高可用性、分组和隔离、限流、降级、监控和故障切换;
- 理解高可用系统:要做到数据不丢,就必需要持久化;要做到服务高可用,就必需要有备用(复本),无论是应用结点还是数据结点;要做到复制,就会有数据一致性的问题;我们不可能做到100%的高可用,也就是说,我们能做到几个9个的SLA;