diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 077affe..c22370a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -259,13 +259,8 @@ - - - 1581441609925 - 1581519773712 @@ -603,7 +598,14 @@ - - @@ -681,29 +682,22 @@ - - - - - - - - - - + - - + + - + @@ -730,22 +724,18 @@ - - - - @@ -754,10 +744,10 @@ - + - + @@ -766,10 +756,10 @@ - + - + diff --git a/Rocket.md b/Rocket.md index be09645..e47b9bd 100644 --- a/Rocket.md +++ b/Rocket.md @@ -2,7 +2,21 @@ ## ZooKeeper ### CAP定理: -一个分布式系统不可能同时满足以下三种,一致性(C:Consistency),可用性(A:Available),分区容错性(P:Partition Tolerance).在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。(Base理论CA强一致性和最终一致性) +一个分布式系统不可能同时满足以下三种,一致性(C:Consistency),可用性(A:Available),分区容错性(P:Partition Tolerance).在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。 + +### BASE理论 + +BASE理论是基本可用,软状态,最终一致性三个短语的缩写。BASE理论是对CAP中一致性和可用性(CA)权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求。 +1. 基本可用:基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。比如正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒。 +2. 软状态:软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。 +3. 最终一致性:最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 + +### ZooKeeper特点 + +1. 顺序一致性:同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 +2. 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 +3. 单一系统映像:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 +4. 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 ### ZAB协议: @@ -31,7 +45,7 @@ ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Z 1. 纯内存操作 2. 单线程操作,避免了频繁的上下文切换 3. 合理高效的数据结构 -4. 采用了非阻塞I/O多路复用机制 +4. 采用了非阻塞I/O多路复用机制(有一个文件描述符同时监听多个文件描述符是否有数据到来) ### Redis 的数据结构及使用场景 @@ -54,6 +68,10 @@ Redis 中数据过期策略采用定期删除+惰性删除策略 5. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。 6. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。 +### Redis的set和setnx + +Redis中setnx不支持设置过期时间,做分布式锁时要想避免某一客户端中断导致死锁,需设置lock过期时间,在高并发时 setnx与 expire 不能实现原子操作,如果要用,得在程序代码上显示的加锁。使用SET代替SETNX ,相当于SETNX+EXPIRE实现了原子性,不必担心SETNX成功,EXPIRE失败的问题。 + ### Redis的LRU具体实现: 传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶,但是用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改进。Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。 @@ -131,7 +149,7 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 * 最上层的服务类似其他CS结构,比如连接处理,授权处理。 * 第二层是Mysql的服务层,包括SQL的解析分析优化,存储过程触发器视图等也在这一层实现。 -* 最后一层是存储引擎的实现,类似于Java接口的实现,Mysql的执行器在执行SQL的时候只会关注API的调用,完全屏蔽了不同引擎实现间的差异。比如Select语句,先会判断当前用户是否拥有权限,其次到缓存(内存)查询是否有相应的结果集,如果没有再执行解析sql,优化生成执行计划,调用API执行。 +* 最后一层是存储引擎的实现,类似于Java接口的实现,Mysql的执行器在执行SQL的时候只会关注API的调用,完全屏蔽了不同引擎实现间的差异。比如Select语句,先会判断当前用户是否拥有权限,其次到缓存(内存)查询是否有相应的结果集,如果没有再执行解析sql,检查SQL 语句语法是否正确,再优化生成执行计划,调用API执行。 ### SQL执行顺序 @@ -512,8 +530,12 @@ HashSet的value存的是一个static finial PRESENT = newObject()。而HashSet ### 阻塞非阻塞与同步异步的区别 -1. 同步和异步关注的是消息通信机制,所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。 -2. 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 +1. 同步和异步关注的是消息通信机制,所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。 +2. 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。 + +### Java SPI + +由于双亲委派模型损失了一丢丢灵活性。就比如java.sql.Driver这个东西。JDK只能提供一个规范接口,而不能提供实现。提供实现的是实际的数据库提供商。提供商的库总不能放JDK目录里吧。Java从1.6搞出了SPI就是为了优雅的解决这类问题——JDK提供接口,供应商提供服务。编程人员编码时面向接口编程,然后JDK能够自动找到合适的实现。 ## Spring @@ -523,15 +545,7 @@ HashSet的value存的是一个static finial PRESENT = newObject()。而HashSet 2. 第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值对象也没有被初始化) 3. 第三级缓存:singletonFactories单例对象工厂缓存。 -### 创建Bean的整个过程 - -1. getBean方法肯定不陌生,必经之路,然后调用doGetBean,进来以后首先会执行transformedBeanName找别名,看你的Bean上面是否起了别名。然后进行很重要的一步,getSingleton,这段代码就是从你的单例缓存池中获取Bean的实例。那么你第一次进来肯定是没有的,缓存里肯定是拿不到的。也就是一级缓存里是没有的。那么它怎么办呢?他会尝试去二级缓存中去拿,但是去二级缓存中拿并不是无条件的,首先要判断isSingletonCurrentlyInCreation(beanName)他要看你这个对象是否正在创建当中,如果不是直接就退出该方法,如果是的话,他就会去二级缓存earlySingletonObjects里面取,如果没拿到,它还接着判断allowEarlyReference这个东西是否为true。它的意思是说,是否允许让你从单例工厂对象缓存中去拿对象。默认为true。好了,此时如果进来那么就会通过singletonFactory.getObject()去单例工厂缓存中去拿。然后将缓存级别提升至二级缓存也就早期暴露的缓存。 -2. getSingleton执行完以后会走dependsOn方法,判断是否有dependsOn标记的循环引用,有的话直接卡死,抛出异常。比如说A依赖于B,B依赖于A 通过dependsOn注解去指定。此时执行到这里就会抛出异常。这里所指并非是构造函数的循环依赖。 -3. beforeSingletonCreation在这里方法里。就把你的对象标记为了早期暴露的对象。提前暴露对象用于创建Bean的实例。 -4. 紧接着就走创建Bean的流程开始。在创建Bean之前执行了一下resolveBeforeInstantiation。它的意思是说,代理AOPBean定义注册信息但是这里并不是实际去代理你的对象,因为对象还没有被创建。只是代理了Bean定义信息,还没有被实例化。把Bean定义信息放进缓存,以便我想代理真正的目标对象的时候,直接去缓存里去拿。 -5. 接下来就真正的走创建Bean流程,首先走进真正做事儿的方法doCreateBean然后找到createBeanInstance这个方法,在这里面它将为你创建你的Bean实例信息(Bean的实例)。如果说创建成功了,那么就把你的对象放入缓存中去(将创建好的提前曝光的对象放入singletonFactories三级缓存中)将对象从二级缓存中移除因为它已经不是提前暴露的对象了。但是。如果说在createBeanInstance这个方法中在创建Bean的时候它会去检测你的依赖关系,会去检测你的构造器。然后,如果说它在创建A对象的时候,发现了构造器里依赖了B,然后它又会重新走getBean的这个流程,当在走到这里的时候,又发现依赖了A此时就会抛出异常。为什么会抛出异常,因为,走getBean的时候他会去从你的单例缓存池中去拿,因为你这里的Bean还没有被创建好。自然不会被放进缓存中,所以它是在缓存中拿不到B对象的。反过来也是拿不到A对象的。造成了死循环故此直接抛异常。这就是为什么Spring IOC不能解决构造器循环依赖的原因。因为你还没来的急放入缓存你的对象是不存在的。所以不能创建。同理@Bean标注的循环依赖方法也是不能解决的,跟这个同理。那么多例就更不能解决了。为什么?因为在走createBeanInstance的时候,会判断是否是单例的Bean定义信息mbd.isSingleton();如果是才会进来。所以多例的Bean压根就不会走进来,而是走了另一段逻辑,这里不做介绍。至此,构造器循环依赖和@Bean的循环依赖还有多例Bean的循环依赖为什么不能解决已经解释清楚。然后如果说,Bean创建成功了。那么会走后面的逻辑。 -6. 将创建好的Bean放入缓存,addSingletonFactory方法就是将你创建好的Bean放入三级缓存中。并且移除早期暴露的对象。 -7. 通过populateBean给属性赋值,我们知道,创建好的对象,并不是一个完整的对象,里面的属性还没有被赋值。所以这个方法就是为创建好的Bean为它的属性赋值。并且调用了我们实现的的XXXAware接口进行回调初始化,。然后调用我们实现的Bean的后置处理器,给我们最后一次机会去修改Bean的属性。 +三级缓存详解:[根据 Spring 源码写一个带有三级缓存的 IOC](https://zhuanlan.zhihu.com/p/144627581) ### Spring如何解决循环依赖问题 @@ -549,23 +563,38 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给 2. CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 3. 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。 +### @Transactional 在private上会生效么 + +@Transactional注解只能应用在public方法上。当标记在protected、private、package-visible方法上时,不会产生错误,但也不会表现出为它指定的事务配置。可以认为它作为一个普通的方法参与到一个public方法的事务中。 + ### Spring的的事务传播机制 -1. REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。 +1. REQUIRED(默认,常用):支持使用当前事务,如果当前事务不存在,创建一个新事务。eg:方法B用REQUIRED修饰,方法A调用方法B,如果方法A当前没有事务,方法B就新建一个事务(若还有C则B和C在各自的事务中独立执行),如果方法A有事务,方法B就加入到这个事务中,当成一个事务。 2. SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。 3. MANDATORY:强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。 -4. REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。 +4. REQUIRES_NEW(常用):创建一个新事务,如果当前事务存在,把当前事务挂起。eg:方法B用REQUIRES_NEW修饰,方法A调用方法B,不管方法A上有没有事务方法B都新建一个事务,在该事务执行。 5. NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。 6. NEVER:无事务执行,如果当前有事务则抛出Exception。 7. NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。 +### Spring中Bean的生命周期 + +1. 实例化 Instantiation +2. 属性赋值 Populate +3. 初始化 Initialization +4. 销毁 Destruction + ### Spring的后置处理器 -1. BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。 -2. InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。 +1. BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。(before和after两个回调中间只处理了init-method) +2. InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作(TargetSource的AOP创建代理对象就是通过该接口实现) 3. BeanFactoryPostProcessor:Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。 4. BeanDefinitionRegistryPostProcessor:继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前真执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。 +### Spring MVC的工作流程(源码层面) + +参考文章:[自己写个Spring MVC](https://zhuanlan.zhihu.com/p/139751932) + ## 消息队列 ### 为什么需要消息队列 @@ -768,6 +797,13 @@ void quick_sort(int a[], int low, int high){ * 降级:服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好。 * 限流:限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。 +### 负载均衡算法: + +1. 轮询 +2. 加权轮询 +3. 随机算法 +4. 一致性Hash + ### 常见的限流算法: 常见的限流算法有计数器、漏桶和令牌桶算法。漏桶算法在分布式环境中消息中间件或者Redis都是可选的方案。发放令牌的频率增加可以提升整体数据处理的速度,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度。而漏桶不行,因为它的流出速率是固定的,程序处理速度也是固定的。