@ -37,6 +37,16 @@ zk类似于linux中的目录节点树方式的数据存储, 即分层命名空
client端会对某个znode建立一个watcher事件, 当该znode发生变化时, 这些client会收到zk的通知, 然后client可以根据znode变化来做出业务上的改变等。
### zk实现分布式锁
zk实现分布式锁主要利用其临时顺序节点, 实现分布式锁的步骤如下:
1. 创建一个目录mylock
2. 线程A想获取锁就在mylock目录下创建临时顺序节点
3. 获取mylock目录下所有的子节点, 然后获取比自己小的兄弟节点, 如果不存在, 则说明当前线程顺序号最小, 获得锁
4. 线程B获取所有节点, 判断自己不是最小节点, 设置监听比自己次小的节点
5. 线程A处理完, 删除自己的节点, 线程B监听到变更事件, 判断自己是不是最小的节点, 如果是则获得锁
## Redis
### 应用场景
@ -110,20 +120,43 @@ Redis中setnx不支持设置过期时间, 做分布式锁时要想避免某一
### Redis的持久化机制
Redis为了保证效率, 数据缓存在了内存中, 但是会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中, 以保证数据的持久化。Redis的持久化策略有两种:
1. RDB: 快照形式是直接把内存中的数据保存到一个dump的文件中, 定时保存, 保存策略。
当Redis需要做持久化时, Redis会fork一个子进程, 子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后, 将原来的RDB替换掉。
1. AOF: 把所有的对Redis的服务器进行修改的命令都存到一个文件里, 命令的集合。
1. RDB: 快照形式是直接把内存中的数据保存到一个dump的文件中, 定时保存, 保存策略。当Redis需要做持久化时, Redis会fork一个子进程, 子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后, 将原来的RDB替换掉。
2. AOF: 把所有的对Redis的服务器进行修改的命令都存到一个文件里, 命令的集合。
使用AOF做持久化, 每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次, 在这种配置下, 就算发生故障停机, 也最多丢失一秒钟的数据。
缺点是对于相同的数据集来说, AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略, AOF的速度可能会慢于RDB。
Redis默认是快照RDB的持久化方式。对于主从同步来说, 主从刚刚连接的时候, 进行全量同步( RDB) ; 全同步结束后, 进行增量同步(AOF)。
### Redis和memcached的区别
### Redis的事务
1. Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令, 一个事务中所有命令都会被序列化。在事务执行过程, 会按照顺序串行化执行队列中的命令, 其他客户端提交的命令请求不会插入到事务执行命令序列中。总结说: redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
2. Redis事务没有隔离级别的概念, 批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
3. Redis中, 单条命令是原子性执行的, 但事务不保证原子性, 且没有回滚。事务中任意命令执行失败, 其余的命令仍会被执行。
### Redis事务相关命令
1. watch key1 key2 ... : 监视一或多个key,如果在事务执行之前, 被监视的key被其他命令改动, 则事务被打断( 类似乐观锁)
2. multi : 标记一个事务块的开始( queued)
3. exec : 执行所有事务块的命令( 一旦执行exec后, 之前加的监控锁都会被取消掉)
4. discard : 取消事务,放弃事务块中的所有命令
5. unwatch : 取消watch对所有key的监控
### Redis和 memcached 的区别
1. 存储方式上: memcache会把数据全部存在内存之中, 断电后会挂掉, 数据不能超过内存大小。redis有部分数据存在硬盘上, 这样能保证数据的持久性。
2. 数据支持类型上: memcache对数据类型的支持简单, 只支持简单的key-value, , 而redis支持五种数据类型。
3. 用底层模型不同: 它们之间底层实现方式以及与客户端之间通信的应用协议不一样。redis直接自己构建了VM机制, 因为一般的系统调用系统函数的话, 会浪费一定的时间去移动和请求。
4. value的大小: redis可以达到1GB, 而memcache只有1MB。
### Redis的哨兵机制
哨兵是一个分布式系统,你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于Master是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。
每个哨兵会向其它哨兵、master、slave定时发送消息,以确认对方是否活着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机”)。
若“哨兵群“中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
### Redis并发竞争key的解决方案
1. 分布式锁+时间戳
@ -248,6 +281,53 @@ MySQL为了保证ACID中的一致性和持久性, 使用了WAL(Write-Ahead Logg
6. or操作有至少一个字段没有索引
7. 需要回表的查询结果集过大(超过配置的范围)
### explain命令概要
1. id:select选择标识符
2. select_type:表示查询的类型。
3. table:输出结果集的表
4. partitions:匹配的分区
5. type:表示表的连接类型
6. possible_keys:表示查询时,可能使用的索引
7. key:表示实际使用的索引
8. key_len:索引字段的长度
9. ref:列与索引的比较
10. rows:扫描出的行数(估算的行数)
11. filtered:按表条件过滤的行百分比
12. Extra:执行情况的描述和说明
### explain 中的 select_type( 查询的类型)
1. SIMPLE(简单SELECT, 不使用UNION或子查询等)
2. PRIMARY(子查询中最外层查询, 查询中若包含任何复杂的子部分, 最外层的select被标记为PRIMARY)
3. UNION(UNION中的第二个或后面的SELECT语句)
4. DEPENDENT UNION(UNION中的第二个或后面的SELECT语句, 取决于外面的查询)
5. UNION RESULT(UNION的结果, union语句中第二个select开始后面所有select)
6. SUBQUERY(子查询中的第一个SELECT, 结果不依赖于外部查询)
7. DEPENDENT SUBQUERY(子查询中的第一个SELECT, 依赖于外部查询)
8. DERIVED(派生表的SELECT, FROM子句的子查询)
9. UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
### explain 中的 type( 表的连接类型)
1. system: 最快, 主键或唯一索引查找常量值, 只有一条记录, 很少能出现
2. const: PK或者unique上的等值查询
3. eq_ref: PK或者unique上的join查询, 等值匹配, 对于前表的每一行(row),后表只有一行命中
4. ref: 非唯一索引, 等值匹配, 可能有多行命中
5. range: 索引上的范围扫描, 例如: between/in
6. index: 索引上的全集扫描, 例如: InnoDB的count
7. ALL: 最慢, 全表扫描(full table scan)
### explain 中的 Extra( 执行情况的描述和说明)
1. Using where:不用读取表中所有信息, 仅通过索引就可以获取所需数据, 这发生在对表的全部的请求列都是同一个索引的部分的时候, 表示mysql服务器将在存储引擎检索行后再进行过滤
2. Using temporary: 表示MySQL需要使用临时表来存储结果集, 常见于排序和分组查询, 常见 group by ; order by
3. Using filesort: 当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”
4. Using join buffer: 改值强调了在获取连接条件时没有使用索引, 并且需要连接缓冲区来存储中间结果。如果出现了这个值, 那应该注意, 根据查询的具体情况可能需要添加索引来改进能。
5. Impossible where: 这个值强调了where语句会导致没有符合条件的行( 通过收集统计信息不可能存在结果) 。
6. Select tables optimized away: 这个值意味着仅通过使用索引, 优化器可能仅从聚合函数结果中返回一行
7. No tables used: Query语句中使用from dual 或不含任何from子句
### 数据库优化指南
1. 创建并使用正确的索引
@ -345,6 +425,13 @@ JVM引入动态年龄计算, 主要基于如下两点考虑:
4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5. 由Eden区、From Space区向To Space区复制时, 对象大小大于To Space可用内存, 则把该对象转存到老年代, 且老年代的可用内存小于该对象大小
### 对象什么时候进入老年代
1. 大对象直接进入老年代。 虚拟机提供了一个阈值参数, 令大于这个设置值的对象直接在老年代中分配。如果大对象进入新生代, 新生代采用的复制算法收集内存, 会导致在Eden区和两个Survivor区之间发生大量的内存复制, 应该避免这种情况。
2. 长期存活的对象进入老年代。 虚拟机给每个对象定义了一个年龄计数器, 对象在Eden区出生, 经过一次Minor GC后仍然存活, 并且能被Survivor区容纳的话, 将被移动到Survivor区中, 此时对象年龄设为1。然后对象在Survivor区中每熬过一次 Minor GC, 年龄就增加1, 当年龄超过设定的阈值时, 就会被移动到老年代中。
3. 动态对象年龄判定: 如果在 Survivor 空间中所有相同年龄的对象,大小总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就直接进入老年代,无须等到阈值中要求的年龄。
4. 空间分配担保: 如果老年代中最大可用的连续空间大于新生代所有对象的总空间,那么 Minor GC 是安全的。如果老年代中最大可用的连续空间大于历代晋升到老年代的对象的平均大小,就进行一次有风险的 Minor GC, 如果小于平均值, 就进行 Full GC 来让老年代腾出更多的空间。因为新生代使用的是复制算法,为了内存利用率,只使用其中一个 Survivor 空间来做轮换备份,因此如果大量对象在 Minor GC 后仍然存活,导致 Survivor 空间不够用,就会通过分配担保机制,将多出来的对象提前转到老年代,但老年代要进行担保的前提是自己本身还有容纳这些对象的剩余空间,由于无法提前知道会有多少对象存活下来,所以取之前每次晋升到老年代的对象的平均大小作为经验值,与老年代的剩余空间做比较。
### TLAB
在Java中, 典型的对象不再堆上分配的情况有两种: TLAB和栈上分配( 通过逃逸分析) 。JVM在内存新生代Eden Space中开辟了一小块线程私有的区域, 称作TLAB( Thread-local allocation buffer) 。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢, 它们不存在线程共享也适合被快速GC, 所以对于小对象通常JVM会优先分配在TLAB上, 并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。也就是说, Java中每个线程都会有自己的缓冲区称作TLAB( Thread-local allocation buffer) , 每个TLAB都只有一个线程可以操作, TLAB结合bump-the-pointer技术可以实现快速的对象分配, 而不需要任何的锁进行同步, 也就是说, 在对象分配的时候不用锁住整个堆, 而只需要在自己的缓冲区分配即可。
@ -563,6 +650,13 @@ AQS有两个队列, 同步对列和条件队列。同步队列依赖一个双
6. threadFactory: 线程工厂, 用于创建线程, 一般用默认的即可。
7. handler: 拒绝策略。当任务太多来不及处理, 如何拒绝任务。
### 线程池的执行流程
1. 如果正在运行的线程数量小于 corePoolSize, 那么马上创建线程运行这个任务
2. 如果正在运行的线程数量大于或等于 corePoolSize, 那么将这个任务放入队列
3. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize, 那么还是要创建非核心线程立刻运行这个任务
4. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize, 那么线程池会抛出异常RejectExecutionException
### 线程池都有哪几种工作队列
1. ArrayBlockingQueue: 底层是数组, 有界队列, 如果我们要使用生产者-消费者模式,这是非常好的选择。