1.MySQL
1.1什么是MySQL?
MySQL 是⼀种关系型数据库,在 Java 企业级开发中⾮常常⽤,由于 MySQL 是开源免费的,并 且⽅便扩展。阿⾥巴巴数据库体系也⼤量⽤到了 MySQL ,因此它的稳定性是有保障的。 MySQL 是开放源代码的,因此任何⼈都可以在 GPL(General Public License) 的许可下下载并根据个性 化的需要对其进⾏修改。 MySQL 的默认端⼝号是 3306 。 1.2存储引擎
检察MySQL提供的全部存储引擎 show engines;
MySQL 当前默认的存储引擎是InnoDB,并且在5.7版本全部的存储引擎中 只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB ⽀持事务。
MyISAM和InnoDB区别
1. 是否⽀持⾏级锁 : MyISAM 只有表级锁 ,⽽ InnoDB ⽀持⾏级锁 和表级锁 , 默以为⾏级锁。 2. 是否⽀持事务和瓦解后的安全规复: MyISAM 强调的是性能,每次查询具有原⼦性 , 其执⾏ 速率⽐ InnoDB 类型更快,但是不提供事务⽀持。但是 InnoDB 提供事务⽀持事务,外部键等 ⾼级数据库功能。 具有事务 、回滚 和瓦解修复能⼒ 的事务安全 型表。 3. 是否⽀持外键: MyISAM 不⽀持,⽽InnoDB ⽀持。 4. 是否⽀持 MVCC(多版本并发控制) :仅 InnoDB ⽀持。应对⾼并发事务 , MVCC ⽐单纯的加锁更⾼效 ;MVCC 只在 READ COMMITTED (读已提交) 和 REPEATABLE READ (可重复读) 两个隔离级别下⼯作 ;MVCC 可以使⽤ 乐观(optimistic) 锁 和 悲观(pessimistic) 锁来实现 ; 各数据库中 MVCC 实现并不统⼀。 悲观锁(Pessimistic Locking):
悲观锁的核心思想是,在整个数据处置惩罚过程中,始终假设最坏的环境会发生,即以为其他线程随时大概修改数据,因此在访问数据之前就进行加锁操作,确保每次只有一个线程能够访问数据。
乐观锁(Optimistic Locking):
乐观锁的核心思想是,以为在数据处置惩罚过程中不会发生并发辩论,因此不加锁,而是在更新数据时进行版本检查,通过比较版本号来判定数据是否被修改。假如版本号相同,则可以更新数据;假如版本号差别,则说明数据已经被其他线程修改,需要进行相应的处置惩罚。
1.3字符集及校对规则
字符集指的是⼀种从⼆进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规 则。 MySQL 中每⼀种字符集都会对应⼀系列的校对规则。 MySQL采⽤的是类似继承的⽅式指定字符集的默认值,每个数据库以及每张数据表都有⾃⼰的默认值,他们逐层继承。 1.4索引
MySQL 索引使⽤的数据结构主要有 B+Tree 索引 和 哈希索引 。对于哈希索引来说,底层的数据结 构就是哈希表,因此在绝⼤多数需求为单条记载查询的时间,可以选择哈希索引,查询性能最 快;其余⼤部分场景,建议选择 B+Tree 索引。 MyISAM: B+Tree 叶节点的 data域存放的是数据记载的地址。索引⽂件和数据⽂件是分离的。在索引检索的时间,⾸先按照B+Tree搜索算法搜索索引,假如指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记载。这被称为“ ⾮聚簇索引 ” 。 InnoDB: 其数据⽂件本身就是索引⽂件。 其表数据⽂件本身就是按B+Tree 组织的⼀个索引结构,树的叶节点 data 域生存了完整的数据记载。这个索引的 key 是数据表的主键,因此 InnoDB 表数据⽂件本身就是主索引。这被称为 “ 聚 簇索引(或聚集索引)”。⽽其余的索引都作为二级索引,二级索引的data 域存储相应记载主 键的值⽽不是地址,这也是和MyISAM差别的地⽅。在根据聚集索引搜索时,直接找到key所在的节点即可取出数据;在根据二级索引查找时,则需要先取出主键的值,再⾛⼀遍聚集索引(回表)。 因此,在设计表的时间,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮唯一的字段作为主键,这样会造成主索引频繁分裂。 1.5查询缓存的使⽤
开启查询缓存后在同样的查询条件以及数据环境下,会直接在缓存中返回结果 。这⾥的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等⼀些大概影响结果的信息。因此任何两个查询在任何字符上的差别都会导致缓存不命中。此外,假如查询中包含任何⽤户⾃定义函数、存储函数、⽤户变量、临时表、MySQL 库中的体系表,其查询结果也不会被缓存。 缓存建⽴之后, MySQL 的查询缓存体系会跟踪查询中涉及的每张表,假如这些表(数据或结构) 发⽣变化,那么和这张表相关的全部缓存数据都将失效。 缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做⼀ 次缓存操作,失效后还要销毁。 因此,开启缓存查询要谨慎,尤其对于写麋集的应⽤来说更是如 此。假如开启,要注意公道控制缓存空间⼤⼩,⼀般来说其⼤⼩设置为⼏⼗ MB ⽐较符合。 1.6什么是事务?
事务是逻辑上的⼀组操作的集合,要么都执⾏,要么都不执⾏。
1.7事物的四⼤特性(ACID)
1. 原⼦性( Atomicity ): 事务是最⼩的执⾏单位,不答应分割。事务的原⼦性确保动作要么 全部完成,要么完全不起作⽤; 2. ⼀致性( Consistency ): 执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结 果是相同的; 3. 隔离性( Isolation ): 并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发 事务之间数据库是独⽴的; 4. 长期性( Durability ): ⼀个事务被提交之后。它对数据库中数据的改变是长期的,纵然数 据库发⽣故障也不应该对其有任何影响。 1.8并发事务带来哪些题目?
1.9事务隔离级别有哪些?MySQL的默认隔离级别是?
InnoDB 存储引擎在(可重读)事务隔离级别下使⽤的是Next-Key Lock 锁算法,因此可以避免幻读的产⽣,这与其他数据库体系(如 SQL Server) 是差别的。所以说InnoDB 存储引擎的默认⽀持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即到达了SQL尺度的 (可串⾏化) 隔离级别。
由于隔离级别越低,事务请求的锁越少,所以⼤部分数据库体系的隔离级别都是 READ-COMMITTED(读取提交内容) ,但是你要知道的是 InnoDB 存储引擎默认使⽤ REPEAaTABLE-READ(可重读) 并不会有任何性能丧失。InnoDB 存储引擎在 分布式事务 的环境下⼀般会⽤到 SERIALIZABLE(可串⾏化) 隔离级别。
分布式事务:一次大的操作由差别的小操作组成,这些小的操作分布在差别的服务器上,且属于差别的应用,分布式事务需要保证这些小操作要么全部乐成,要么全部失败。本质上来说,分布式事务就是为了保证数据库中的数据一致性。
1.10锁机制与InnoDB锁算法
MyISAM 采⽤表级锁 (table-level locking) 。 InnoDB ⽀持⾏级锁 (row-level locking) 和表级锁 , 默以为⾏级锁 表级锁: MySQL 中锁定 粒度最⼤ 的⼀种锁,对当前操作的整张表加锁,实现简单,资源消 耗也⽐较少,加锁快,不会出现死锁。其锁定粒度最⼤,触发锁辩论的概率最⾼,并发度最 低, MyISAM 和 InnoDB 引擎都⽀持表级锁。 ⾏级锁: MySQL 中锁定 粒度最⼩ 的⼀种锁,只针对当前操作的⾏进⾏加锁。 ⾏级锁能⼤ ⼤减少数据库操作的辩论。其加锁粒度最⼩,并发度⾼,但加锁的开销也最⼤,加锁慢,会 出现死锁。 InnoDB 存储引擎的锁的算法有三种: Record lock :行锁,单个⾏记载上的锁 Gap lock:间隙锁,锁定⼀个范围,不包括记载本身 Next-key lock :临键锁, record+gap 锁定⼀个范围,包含记载本身 1. innodb 对于⾏的查询使⽤ next-key lock 2. Next-locking keying 为了解决 Phantom Problem 幻读题目 3. 当查询的索引含有唯⼀属性时,将 next-key lock 降级为 record key 4. Gap 锁设计的⽬的是为了阻⽌多个事务将记载插⼊到同⼀范围内,⽽这会导致幻读题目的产 ⽣ 5. 有两种⽅式显式关闭 gap 锁:(除了外键约束和唯⼀性检查外,其余环境仅使⽤ record lock ) A. 将事务隔离级别设置为 RC B. 将参数 innodb_locks_unsafe_for_binlog 设置为 1 1.11⼤表优化
当MySQL单表记载数过⼤时,数据库的CRUD性能会显着降落,⼀些常⻅的优化步伐如下:
1.限定数据的范围 务必禁⽌不带任何限制数据范围条件的查询语句。⽐如:我们当⽤户在查询订单汗青的时间,我 们可以控制在⼀个⽉的范围内; 2.读 / 写分离 经典的数据库拆分⽅案,主库负责写,从库负责读; 3.垂直分库/表(表结构不一样) 根据数据库⾥⾯数据表的相关性进⾏拆分。 例如,⽤户表中既有⽤户的登录信息⼜有⽤户的基 本信息,可以将⽤户表拆分成两个单独的表,甚⾄放到单独的库做分库。 简单来说垂直拆分是指数据表列的拆分,把⼀张列⽐较多的表拆分为多张表。 垂直拆分的优点: 可以使得列数据变⼩,在查询时减少读取的 Block 数,减少 I/O 次数。此 外,垂直分区可以简化表的结构,易于维护。 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起 Join 操作,可以通过在应⽤ 层进⾏ Join 来解决。此外,垂直分区会让事务变得更加复杂; 4.⽔平分库/表(表结构一样) 保持数据表结构稳定,通过某种策略存储数据分⽚。这样每⼀⽚数据分散到差别的表或者库中, 到达了分布式的⽬的。 ⽔平拆分可以⽀撑⾮常⼤的数据量。 ⽔平拆分是指数据表⾏的拆分,表的⾏数凌驾 200 万⾏时,就会变慢,这时可以把⼀张的表的数 据拆成多张表来存放。举个例⼦:我们可以将⽤户信息表拆分成多个⽤户信息表,这样就可以避 免单⼀表数据量过⼤对性能造成影响。 ⽔平拆分可以⽀持⾮常⼤的数据量。需要注意的⼀点是:分表仅仅是解决了单⼀表数据过⼤的问 题,但由于表的数据还是在同⼀台机器上,其实对于提升 MySQL 并发能⼒没有什么意义,所以 ⽔平拆分最好分库 。
数据库分⽚的两种常⻅⽅案: 客户端代理: 分⽚逻辑在应⽤端,封装在 jar 包中,通过修改或者封装 JDBC 层来实现。 当 当⽹的 Sharding-JDBC 、阿⾥的 TDDL 是两种⽐常⽤的实现。 中心件代理: 在应⽤和数据中心加了⼀个代理层。分⽚逻辑统⼀维护在中心件服务中。 我 们现在谈的 Mycat 、 360 的 Atlas 、⽹易的 DDB 等等都是这种架构的实现。 1.12解释⼀下什么是池化设计思想。什么是数据库毗连池?为什么需要数据库毗连池?
我们常⻅的如 java 线程池、 jdbc 毗连池、 redis 毗连池等就是这类设计的代表实现。这种设计会初始预设资源,解决的题目就是抵消每次获取资源的消耗,如创建线程的开销,获取远程毗连的开销等。就好⽐你去⻝堂打饭,打饭的⼤妈会先把饭盛好⼏份放那⾥,你来了就直接拿着饭盒加菜即可,不⽤再临时⼜盛饭⼜打菜,服从就⾼了。除了初始化资源,池化设计还包括如下这些特征:池⼦的初始值、池⼦的活泼值、池⼦的最⼤值等,这些特征可以直接映射到java 线程池和数据库毗连池的成员属性中。 数据库毗连本质就是⼀个 socket 的毗连。数据库服务端还要维护⼀些缓存和⽤户权限信息之类 的 所以占⽤了⼀些内存。我们可以把数据库毗连池是看做是维护的数据库毗连的缓存,以便未来 需要对数据库的请求时可以重⽤这些毗连。为每个⽤户打开和维护数据库毗连,尤其是对动态数 据库驱动的⽹站应⽤步伐的请求,既昂贵⼜浪费资源。 在毗连池中,创建毗连后,将其放置在池 中,并再次使⽤它,因此不必建⽴新的毗连。假如使⽤了全部毗连,则会建⽴⼀个新毗连并将其 添加到池中。 毗连池还减少了⽤户必须等待建⽴与数据库的毗连的时间。 1.13分库分表之后,id 主键怎样处置惩罚?
由于要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要⼀个全局唯⼀的 id 来⽀持。 ⽣玉成局 id 有下⾯这⼏种⽅式: UUID :不适相助为主键,由于太⻓了,并且⽆序不可读,查询服从低。⽐较适合⽤于⽣成唯⼀的名字的标示⽐如⽂件的名字。 数据库⾃增 id : 两台数据库分别设置差别步⻓,⽣成不重复 ID 的策略来实现⾼可⽤。这种⽅ 式⽣成的 id 有序,但是需要独⽴摆设数据库实例,成本⾼,还会有性能瓶颈。 利⽤ redis ⽣成 id : 性能⽐较好,灵活⽅便,不依赖于数据库。但是,引⼊了新的组件造成 体系更加复杂,可⽤性低落,编码更加复杂,增加了体系成本。 Twitter 的 snowflake 算法 美团的 Leaf 分布式 ID ⽣成体系 : Leaf 是美团开源的分布式 ID ⽣成器,能保证全局唯⼀性、 趋势递增、单调递增、信息安全,⾥⾯也提到了⼏种分布式⽅案的对⽐,但也需要依赖关系 数据库、 Zookeeper等中心件。 1.14⼀条SQL语句在MySQL中怎样执⾏的
1.MySQL 主要分为 Server 层和引擎层,Server 层主要包括毗连器、查询缓存、分析器、优化器、执行器,同时另有一个日志模块(binlog),这个日志模块全部执行引擎都可以共用,redolog 只有 InnoDB 有。•引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
2•SQL 的执行过程分为两类,一类对于查询等过程如下:权限校验---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎
3•对于更新等语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log prepare---》binlog---》redo log commit
1.15MySQL⾼性能优化规范建议
1.16⼀条SQL语句执⾏得很慢的缘故原由有哪些?
1、大多数环境下很正常,偶尔很慢,则有如下缘故原由
(1)、数据库在革新脏页,例如 redo log 写满了需要同步到磁盘。
(2)、执行的时间,遇到锁,如表锁、行锁。
2、这条 SQL 语句一直执行的很慢,则有如下缘故原由。
(1)、没有效上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。
(2)、数据库选错了索引。
2.Redis
2.1简单介绍⼀下 Redis 呗!
简单来说 Redis 就是⼀个使⽤ C 语⾔开发的数据库 ,不过与传统数据库差别的是 Redis 的数据 是存在内存中的 ,也就是它是内存数据库,所以读写速率⾮常快,因此 Redis 被⼴泛应⽤于缓存 ⽅向。别的,Redis 除了做缓存之外, Redis 也经常⽤来做分布式锁,甚⾄是消息队列。 Redis 提供了多种数据类型来⽀持差别的业务场景。 Redis 还⽀持事务 、长期化、 Lua 脚本、多 种集群⽅案 2.2分布式缓存常⻅的技能选型⽅案有哪些?
Memcached 和 Redis(主)
分布式缓存主要解决的是单机缓存的容量受服务器限制并且⽆法生存通⽤的信息。由于,当地缓 存只在当前服务⾥有效,⽐如假如你摆设了两个相同的服务,他们两者之间的缓存数据是⽆法共 同的。 2.3说⼀下 Redis 和 Memcached 的区别和共同点
共同点 : 1. 都是基于内存的数据库,⼀般都⽤来当做缓存使⽤。 2. 都有过期策略。 3. 两者的性能都⾮常⾼。 区别 : 1. Redis ⽀持更丰富的数据类型(⽀持更复杂的应⽤场景) 。 Redis 不仅仅⽀持简单的 k/v (key,value) 类型的数据,同时还提供 list , set , zset , hash 等数据结构的存储。 Memcached 只⽀持最简单的 k/v 数据类型。 2. Redis ⽀持数据的长期化,可以将内存中的数据保持在磁盘中,重启的时间可以再次加载进 ⾏使⽤ , ⽽ Memecache 把数据全部存在内存之中。 3. Redis 有灾难规复机制。 由于可以把缓存中的数据长期化到磁盘上。 4. Redis 在服务器内存使⽤完之后,可以将不⽤的数据放到磁盘上。但是, Memcached 在服 务器内存使⽤完之后,就会直接报异常。 5. Memcached 没有原⽣的集群模式,需要依赖客户端来实现往集群中分⽚写⼊数据;但是 Redis ⽬前是原⽣⽀持 cluster 模式的. 6. Memcached 是多线程,⾮壅闭 IO 复⽤的⽹络模型; Redis 使⽤单线程的多路 IO 复⽤模 型。 ( Redis 6.0 引⼊了多线程 IO ) 7. Redis ⽀持发布订阅模型、 Lua 脚本、事务等功能,⽽ Memcached 不⽀持。并且,Redis ⽀持更多的编程语⾔。 8. Memcached 过期数据的删除策略只⽤了惰性删除,⽽ Redis 同时使⽤了惰性删除与定期删 除。 2.4缓存数据的处置惩罚流程是怎样的?
1. 假如⽤户请求的数据在缓存中就直接返回。 2. 缓存中不存在的话就看数据库中是否存在。 3. 数据库中存在的话就更新缓存中的数据。 4. 数据库中不存在的话就返回空数据。 2.5为什么要⽤ Redis/为什么要⽤缓存?
是为了提升⽤户体验以及应对更多的⽤户。
⾼性能 : 我们设想这样的场景: 假如⽤户第⼀次访问数据库中的某些数据的话,这个过程是⽐较慢,究竟是从硬盘中读取的。但是,假如说,⽤户访问的数据属于⾼频数据并且不会经常改变的话,那么我们就可以很放⼼地将该⽤户访问的数据存在缓存中。 这样有什么好处呢? 那就是保证⽤户下⼀次再访问这些数据的时间就可以直接从缓存中获取了。 操作缓存就是直接操作内存,所以速率相当快。 不过,要保持数据库和缓存中的数据的⼀致性。 假如数据库中的对应数据改变的之后,同步改变 缓存中相应的数据即可! ⾼并发: ⼀般像 MySQL 这类的数据库的 QPS ⼤概都在 1w 左右( 4 核 8g ) ,但是使⽤ Redis 缓存之后 很轻易到达 10w+ ,甚⾄最⾼能到达 30w+ (就单机 redis 的环境, redis 集群的话会更⾼)。 QPS(Query Per Second):服务器每秒可以执⾏的查询次数;
2.6Redis 常⻅数据结构以及使⽤场景分析
1.string
1. 介绍 : string 数据结构是简单的 key-value 类型。虽然 Redis 是⽤ C 语⾔写的,但是 Redis 并没有使⽤ C 的字符串表现,⽽是⾃⼰构建了⼀种 简单动态字符串 ( simple dynamic string , SDS )。相⽐于 C 的原⽣字符串, Redis 的 SDS 不光可以生存⽂本数据还可以生存 ⼆进制数据,并且获取字符串⻓度复杂度为 O(1) ( C 字符串为 O(N) ) , 除此之外 ,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。 2. 常⽤命令 : set,get,strlen,exists,dect,incr,setex 等等。 3. 应⽤场景 :⼀般常⽤在需要计数的场景,⽐如⽤户的访问次数、热点⽂章的点赞转发数量等 等。 2.list
1. 介绍 : list 即是 链表 。链表是⼀种⾮常常⻅的数据结构,特点是易于数据元素的插⼊和删除 并且且可以灵活调整链表⻓度,但是链表的随机访问困难。很多⾼级编程语⾔都内置了链表的实现⽐如 Java 中的 LinkedList ,但是 C 语⾔并没有实现链表,所以 Redis 实现了⾃⼰的链表数据结构。Redis 的 list 的实现为⼀个 双向链表 ,即可以⽀持反向查找和遍历,更⽅便操作,不过带来了部分额外的内存开销。 2. 常⽤命令 : rpush,lpop,lpush,rpop,lrange , llen 等。 3. 应⽤场景 : 发布与订阅或者说消息队列、慢查询。 3.hash
1. 介绍 : hash 类似于 JDK1.8 前的 HashMap ,内部实现也差不多 ( 数组 + 链表 ) 。不过,Redis 的 hash 做了更多优化。别的, hash 是⼀个 string 类型的 field 和 value 的映射表, 特殊适合⽤于存储对象 ,后续操作的时间,你可以直接仅仅修改这个对象中的某个字段的值。 ⽐如我们可以 hash 数据结构来存储⽤户信息,商品信息等等。 2. 常⽤命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。 3. 应⽤场景 : 体系中对象数据的存储。 4.set
1. 介绍 : set 类似于 Java 中的 HashSet 。 Redis 中的 set 类型是⼀种⽆序集合,集合中的元素没有先后顺序。当你需要存储⼀个列表数据,⼜不盼望出现重复数据时,set 是⼀个很好的选择,并且 set 提供了判定某个成员是否在⼀个 set 集合内的重要接⼝,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。⽐如:你可以将⼀个⽤户全部的关注⼈存在⼀个集合中,将其全部粉丝存在⼀个集合。Redis 可以⾮常⽅便的实现如共同关注、共同粉丝、共同喜欢等功能。这个过程也就是求交集的过程。 2. 常⽤命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。 3. 应⽤场景 : 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景 5.sorted set
1. 介绍: 和 set 相⽐, sorted set 增加了⼀个权重参数 score ,使得集合中的元素能够按 score进⾏有序分列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap和 TreeSet 的联合体。 2. 常⽤命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。 3. 应⽤场景: 需要对数据根据某个权重进⾏排序的场景。⽐如在直播体系中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以明确为按消息维度的消息排⾏榜)等信息 2.7Redis 单线程模型详解
Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处置惩罚模型 ,这套事件处置惩罚模型对应的是 Redis中的⽂件事件处置惩罚器(file event handler )。由于⽂件事件处置惩罚器( file event handler )是单线程⽅式运⾏的,所以我们⼀般都说 Redis 是单线程模型。 既然是单线程,那怎么监听⼤量的客户端毗连呢? Redis 通过 IO 多路复⽤步伐 来监听来⾃客户端的⼤量毗连(或者说是监听多个 socket ),它会 将感爱好的事件及类型 ( 读、写)注册到内核中并监听每个事件是否发⽣。这样的好处⾮常显着: I/O 多路复⽤技能的使⽤让 Redis 不需要额外创建多余的线程来监听客户 端的⼤量毗连,低落了资源的消耗 。别的, Redis 服务器是⼀个事件驱动步伐,服务器需要处置惩罚两类事件: 1. ⽂件事件 ; 2. 时间事件。我们打仗最多的还是 ⽂件事件 (客户端进⾏读取写⼊等操作,涉及⼀系列⽹络信)。 ⽂件事件处置惩罚器( file event handler )主要是包含 4 个部分: 1.多个 socket (客户端毗连) 2.IO 多路复⽤步伐(⽀持多个客户端毗连的关键) 3.⽂ 件事件分派器(将 socket 关联到相应的事件处置惩罚器) 4.事件处置惩罚器(毗连应答处置惩罚器、命令请求处置惩罚器、命令回复处置惩罚器) 2.8Redis 没有使⽤多线程?为什么不使⽤多线程?
虽然说 Redis 是单线程模型,但是, 现实上, Redis 在 4.0 之后的版本中就已经加⼊了对多线程 的⽀持。 不过, Redis 4.0 增加的多线程主要是针对⼀些⼤键值对的删除操作的命令,使⽤这些命令就会使⽤主处置惩罚之外的其他线程来“ 异步处置惩罚 ” 。 ⼤体上来说,Redis 6.0 之前主要还是单线程处置惩罚。 那, Redis6.0 之前 为什么不使⽤多线程? 我以为主要缘故原由有下⾯ 3 个: 1. 单线程编程轻易并且更轻易维护; 2. Redis 的性能瓶颈不再在 CPU ,主要在内存和⽹络; 3. 多线程就会存在死锁、线程上下⽂切换等题目,甚⾄会影响性能。 2.9Redis6.0 之后为何引⼊了多线程?
Redis6.0 引⼊多线程主要是为了提⾼⽹络 IO 读写性能 ,由于这个算是 Redis 中的⼀个性能瓶颈 ( Redis 的瓶颈主要受限于内存和⽹络)。虽然,Redis6.0 引⼊了多线程,但是 Redis 的多线程只是在⽹络数据的读写这类耗时操作上使⽤了, 执⾏命令仍旧是单线程顺序执⾏。因此,你也不需要担⼼线程安全题目。Redis6.0 的多线程默认是禁⽤的,只使⽤主线程。如需开启需要修改 redis 配置⽂件 redis.conf。开启多线程后,还需要设置线程数,否则是不⽣效的。同样需要修改 redis 配置⽂件 redis.conf : 2.10Redis 给缓存数据设置过期时间有啥⽤?
有助于缓解内存的消耗,以及保证过期时间
由于内存是有限的,假如缓存中的全部数据都是⼀直生存的话,分分钟直接 Out of memory 。Redis ⾃带了给缓存数据设置过期时间的功能 Redis 中除了字符串类型有⾃⼰独有设置过期时间的命令 setex 外,其他⽅法都需要依赖 expire 命令来设置过期时间 。别的, persist 命令可以移除⼀个键的过期时间 很多时间,我们的业务场景就是需要某个数据只在某⼀时间段内存在,⽐如我们的短信验证码可 能只在 1 分钟内有效,⽤户登录的 token 大概只在 1 天内有效。 假如使⽤传统的数据库来处置惩罚的话,⼀般都是⾃⼰判定过期,这样更麻烦并且性能要差很多。 2.11Redis是怎样判定数据是否过期的呢?
Redis 通过⼀个叫做过期字典(可以看作是 hash 表)来生存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key( 键 ) ,过期字典的值是⼀个 long long 类型的整数,这个整数生存了 key 所 指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。 过期字典是存储在redisDb这个结构⾥的
2.12过期的数据的删除策略了解么?
1. 惰性删除 :只会在取出 key 的时间才对数据进⾏过期检查。这样对 CPU 最友爱,但是大概会 造成太多过期 key 没有被删除。 2. 定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期 key 操作。并且, Redis 底层会通过限 制删除操作执⾏的时⻓和频率来减少删除操作对 CPU 时间的影响。 定期删除对内存更加友爱,惰性删除对CPU 更加友爱。两者各有千秋,所以 Redis 采⽤的是 定期 删除 + 惰性 / 懒汉式删除 。 但是,仅仅通过给 key 设置过期时间还是有题目的。由于还是大概存在定期删除和惰性删除漏掉 了很多过期 key 的环境。这样就导致⼤量过期 key 堆积在内存⾥,然后就 Out of memory 了。 怎么解决这个题目呢?答案就是: Redis 内存淘汰机制。 2.13Redis 内存淘汰机制了解么?
1. volatile-lru ( least recently used ) :从已设置过期时间的数据会合挑选最近最少使⽤的数据淘汰 2. volatile-ttl :从已设置过期时间的数据集 中挑选将要过期的数据淘汰 3. volatile-random :从已设置过期时间的数据集 中任意选择数据淘汰 4. allkeys-lru(least recently used) :当内存不⾜以容纳新写⼊数据时,在键空间中,移除 最近最少使⽤的 key 5. allkeys-random :从数据集 中任意选择数据淘汰 6. no-eviction :禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报 错。 4.0 版本后增加以下两种: 7. volatile-lfu ( least frequently used ) :从已设置过期时间的数据集 中挑选最不经常使⽤的数据淘汰 8. allkeys-lfu ( least frequently used ) :当内存不⾜以容纳新写⼊数据时,在键空间中,移 除最不经常使⽤的 key 2.14Redis 长期化机制(怎么保证 Redis 挂掉之后再重启数据可以进⾏规复)
Redis 差别于 Memcached 的很重要⼀点就是, Redis ⽀持长期化,⽽且⽀持两种差别的长期化 操作。 Redis 的⼀种长期化⽅式叫快照( snapshotting , RDB ),另⼀种⽅式是只追加⽂件 ( append-only file, AOF )。 快照( snapshotting )长期化( RDB ) Redis 可以通过创建快照来得到存储在内存⾥⾯的数据在某个时间点上的副本。 Redis 创建快照 之后,可以对快照进⾏备份,可以将快照复制到其他服务器从⽽创建具有相同数据的服务器副本 ( Redis 主从结构,主要⽤来提⾼ Redis 性能),还可以将快照留在原地以便重启服务器的时间 使⽤。快照长期化是 Redis 默认采⽤的长期化⽅式 在 Redis.conf 配置⽂件中默认有此下配置:
AOF ( append-only file )长期化 与快照长期化相⽐, AOF 长期化 的实时性更好,因此已成为主流的长期化⽅案。默认环境下Redis 没有开启 AOF ( append only file )⽅式的长期化,可以通过 appendonly 参数开启:
开启 AOF 长期化后每执⾏⼀条会更改 Redis 中的数据的命令, Redis 就会将该命令写⼊硬盘中的 AOF ⽂件。 AOF ⽂件的生存位置和 RDB ⽂件的位置相同,都是通过 dir 参数设置的,默认的⽂件名是 appendonly.aof 。 在 Redis 的配置⽂件中存在三种差别的 AOF 长期化⽅式,它们分别是:
为了兼顾数据和写⼊性能,⽤户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步⼀次 AOF ⽂件, Redis 性能⼏乎没受到任何影响。⽽且这样纵然出现体系瓦解,⽤户最多只会丢失⼀ 秒之内产⽣的数据。当硬盘忙于执⾏写⼊操作的时间, Redis 还会优雅的放慢⾃⼰的速率以便适 应硬盘的最⼤写⼊速率。 Redis 4.0 对于长期化机制的优化 Redis 4.0 开始⽀持 RDB 和 AOF 的混淆长期化。AOF 重写的时间就直接把 RDB 的内容写到 AOF ⽂件开头。这样做的好处是可以联合 RDB 和 AOF 的优点 , 快速加载同时避免丢失过多的数据。当然缺点也是有的,AOF ⾥⾯的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
AOF 重写 AOF 重写可以产⽣⼀个新的 AOF ⽂件,这个新的 AOF ⽂件和原有的 AOF ⽂件所生存的数据库 状态⼀样,但体积更⼩。 AOF 重写是⼀个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,步伐⽆须对现有 AOF ⽂件进⾏任何读⼊、分析或者写⼊操作。在执⾏ BGREWRITEAOF(bgrewriteAOF) 命令时, Redis 服务器会维护⼀个 AOF 重写缓冲区,该缓冲区会在⼦进程创建新 AOF ⽂件期间,记载服务器执⾏的全部写命令。当⼦进程完成创建新 AOF ⽂件的⼯作之后,服务器会将重写缓冲区中的全部内容追加到新 AOF ⽂件的末尾,使得新旧两个 AOF ⽂件所生存的数据库状态⼀致。末了,服务器⽤新的 AOF ⽂件更换旧的 AOF ⽂件,以此来完成AOF ⽂件重写操作 2.15Redis 事务
Redis 可以通过 MULTI , EXEC , DISCARD 和 WATCH 等命令来实现事务 (transaction) 功能。 使⽤ MULTI 命令后可以输⼊多个命令。 Redis 不会⽴即执⾏这些命令,⽽是将它们放到队列,当 调⽤了 EXEC 命令将执⾏全部命令。 Redis 是不⽀持 roll back 的,因⽽不满⾜原⼦性的(⽽且不满⾜长期性)。 Redis 官⽹也解释了⾃⼰为啥不⽀持回滚。简单来说就是 Redis 开发者们以为没须要⽀持回滚,这 样更简单便捷并且性能更好。 Redis 开发者以为纵然命令执⾏错误也应该在开发过程中就被发现 ⽽不是⽣产过程中。 你可以将 Redis 中的事务就明确为 : Redis 事务提供了⼀种将多个命令请求打包的功能。然后, 再按顺序执⾏打包的全部命令,并且不会被中途打断。 2.16缓存穿透
什么是缓存穿透? 缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本 没有颠末缓存这⼀层。举个例⼦:某个⿊客故意制造我们缓存中不存在的 key 发起⼤量请求,导 致⼤量请求落到数据库。 缓存穿透环境的处置惩罚流程是怎样的? ⽤户的请求最终都要跑到数据库中查询⼀遍。 有哪些解决办法? 最基本的就是⾸先做好参数校验,⼀些不正当的参数请求直接抛出异常信息返回给客户端。⽐如 查询的数据库 id 不能⼩于 0 、传⼊的邮箱格式不对的时间直接返回错误消息给客户端等等。 1 )缓存⽆效 key 假如缓存和数据库都查不到某个 key 的数据就写⼀个到 Redis 中去并设置过期时间,详细命令如 下: SET key value EX 10086 。这种⽅式可以解决请求的 key 变化不频繁的环境,假如⿊客恶意 攻击,每次构建差别的请求 key ,会导致 Redis 中缓存⼤量⽆效的 key 。很显着,这种⽅案并不 能从根本上解决此题目。假如⾮要⽤这种⽅式来解决穿透题目的话,尽量将⽆效的 key 的过期时 间设置短⼀点⽐如 1 分钟。 2 )布隆过滤器 布隆过滤器是⼀个⾮常神奇的数据结构,通过它我们可以⾮常⽅便地判定⼀个给定数据是否存在 于海量数据中。我们需要的就是判定 key 是否正当,有没有感觉布隆过滤器就是我们想要找的那 个 “ ⼈ ” 。 详细是这样做的:把全部大概存在的请求的值都存放在布隆过滤器中,当⽤户请求过来,先判定 ⽤户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户 端,存在的话才会⾛下⾯的流程。 但是,需要注意的是布隆过滤器大概会存在误判的环境。总结来说就是: 布隆过滤器说某个元素 存在,⼩概率会误判。布隆过滤器说某个元素不在,那么这个元素⼀定不在。 当⼀个元素加⼊布隆过滤器中的时间,会进⾏哪些操作: 1. 使⽤布隆过滤器中的哈希函数对元素值进⾏计算,得到哈希值(有⼏个哈希函数得到⼏个哈希值)。 2. 根据得到的哈希值,在位数组中把对应下标的值设置为 1 。 当我们需要判定⼀个元素是否存在于布隆过滤器的时间,会进⾏哪些操作: 1. 对给定元素再次进⾏相同的哈希计算; 2. 得到值之后判定位数组中的每个元素是否都为 1 ,假如值都为 1 ,那么说明这个值在布隆过滤器中,假如存在⼀个值不为 1 ,说明该元素不在布隆过滤器中。 然后,⼀定会出现这样⼀种环境: 差别的字符串大概哈希出来的位置相同。 (可以适当增加位数 组⼤⼩或者调整我们的哈希函数来低落概率) 2.17缓存雪崩
1.缓存在同⼀时间⼤⾯积的失效,后⾯的请求都直接落到了数据库上,造成数据库短时间内蒙受⼤量请求。
2.有⼀些被⼤量访问数据(热点缓存)在某⼀时候⼤⾯积失效,导致对应的请求直接落到了数据库上。
有哪些解决办法? 针对 Redis 服务不可⽤的环境: 1. 采⽤ Redis 集群,避免单机出现题目整个缓存服务都没办法使⽤。 2. 限流,避免同时处置惩罚⼤量的请求。 针对热点缓存失效的环境: 1. 设置差别的失效时间⽐如随机设置缓存的失效时间。 2. 缓存永不失效。 2.18缓存击穿
给某一个key设置了过期时间,当key过期的时间,恰恰这时间点对这个key有大量的并发请求过来,这些并发的请求大概会瞬间把DB压垮
2.18怎样保证缓存和数据库数据的⼀致性(双写一致性)?
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致
答应延时一致的业务 ,采用异步关照 ① 使用MQ中心中心件,更新数据之后,关照缓存删除 ② 使用canal中心件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓存 强一致性的 ,采用Redisson提供的读写锁 ① 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作 ② 排他锁:独占锁writeLock也叫,加锁之后,壅闭其他线程读写操作
延迟双删:假如先删除缓存,那么再次缓存时若数据库还未更新则又不一致了。假如先操作数据库再删除缓存,假如返回缓存在删除缓存后面,那么缓存里面的值还是旧的
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |