JAVA学习05

Redis

数据结构(针对 key-value 中的 value)

string(字符串):最常用,命令:get、set、del

hash(哈希):也叫散列,存储键值对,类似于 Java 中的 Map,命令:hset、hget、hgetall、hdel

list(列表):链表结构,类似于 List,有序,两端都可以推入,命令:lpush、rpush、lpop、

rpop、lindex(指定位置元素)、lrange(给定范围所有值)

set(集合):类似于 Set,无序,命令:sadd、srem(集合中存在则移除)、smembers(集合中所有的元素)、sismember(给定元素是否在集合内)

zset(有序集合):和散列一样,存储键值对,不过是有序的,键被称为成员(member1),值被称为分值(score),命令:zadd(将带有分值的成员添加到有序集合中)、zrange(根据排序位置,获取多个元素)、zrangebyscore(根据给定分值范围获取多个元素)、zrem(如果成员存在则删除)

单线程模型

Redis 是基于 Reactor 模式开发的网络事件处理器,叫文件事件处理器。文件事件处理器采用 IO多路复用程序同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器来处理这个事件。虽然文件处理器以单线程方式运行,但通过使用 IO 多路复用程序来监听多个 Socket,既实现了高性能的网络通信模型,又可以与 Redis 其他单线程运行的模块进行对接。IO 多路复用程序会将Socket 放入一个队列里面排队,每次从队列中取出一个 Socket 给文件事件分派器,文件事件分派器把 Scoket 给对应的事件处理器。当上一个 Socket 事件处理完之后,IO 多路复用程序才会将队列中的下一个 Scoket 给事件分派器。

持久化机制

RDB:将内存数据的快照写入磁盘,生成 RDB 文件,里面是数据库中所有键值对数据,恢复时是将快照文件直接读到内存里。分为自动触发和手动触发,自动触发条件为 900 秒中至少有一个 key值的变化,300 秒中有 10 个,60 秒中有 10000 个。手动触发指使用 save 和 bgsave 命令,save会阻塞 Redis 服务进程,bgsave 是通过创建子进程来生成文件,文件创建后,会替换之前的 RDB文件。

优点:

1、保存了某个时间点上的数据集,适用于备份和灾难恢复

2、创建子进程处理保存工作,主进程不进行磁盘 IO 操作

3、恢复大数据集比 AOF 快

缺点:

1、无法做到实时持久化或秒级持久化,如果执行期间宕机,会丢失最后一次快照后的所有修改的数

  1. 每次数据克隆,大致会有 2 倍的数据量生成,频繁执行影响性能

AOF:通过保存 Redis 服务器所执行的写命令来记录数据库状态,默认为上次同步 AOF 文件的时间距离现在超过一秒钟,再次对 AOF 文件进行同步,默认 AOF 不开启

优点:

1、提供多种同步频率,默认为 1 秒,也就是最多丢失一秒的数据

2、AOF 文件的格式可读性性强,为使用者提供更灵活的处理方式

缺点:

1、相同数据的 Redis,AOF 文件通常比 RDB 文件大

2、RDB 更健壮,快照包含所有数据,AOF 记录的只是每次的命令

AOF 文件重写:随着 Redis 运行,AOF 文件的体积也会不断增长,Redis 会重写 AOF 文件,使体积变小。会创建一个子进程,直接读取当前数据库的数据,来直接生成 AOF 文件,相当于删除了原有 AOF 文件中的冗余命令。可以设置 auto-aof-rewrite-percentage=100,auto-aof-rewrite?min-size=64mb,并启用 AOF 持久化。当 AOF 文件的体积大于 64M,并且 AOF 文件的体积比上一次重写之后的体积扩大至少 100%时,进行重写。

Redis 重启过程

1、是否开启 AOF 持久化,是的话就加载 AOF 文件,加载成功启动成功

2、未开启或 AOF 文件不存在则加载 RDB 文件,加载成功启动成功

3、文件都不存在直接启动

主从复制

新版复制使用 PSYNC 命令代替 SYNC 命令来执行复制时的同步操作,分为完整重同步和部分重同步

完整重同步:用于处理初次复制的情况,和 SYNC 命令基本一样,通过主服务器创建并发送 RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步

部分重同步:用来处理断线后重复制情况,主服务器将主从服务器断开期间执行的写命令发送给从服务器,从服务器会接受并执行这些写命令。

哨兵(Sentinel)模式

Sentinel 是 Redis 的高可用解决方案:有一个或多个 Sentinel(运行在特殊模式下的 Redis 服务器)实例组成 Sentinel 系统,可以监视多个主从服务器,并在被监视的主服务器进入下线状态,自动将某个从服务器升级为主服务器,然后由新的主服务器继续处理命令请求。缺点:使用的是一主多从,无法进行水平扩展,即无法使用多主多从。

状态监控:

每个 Sentinel 节点会以一秒一次的频率,对 Redis 节点和其他 Sentinel 节点发送 PING 命令,获取节点的状态

信息监控:

每个 Sentinel 节点以十秒一次的频率,向主从节点发送 INFO 命令,获取节点信息,如从服务器的增删、从服务器的优先级、从服务器的复制偏移量

每个 Sentinel 节点以两秒一次的频率,向主从节点发送 PUBLISH 命令,使用_sentinel_:hello 频道发布 Sentinel 节点自己的信息,以及主节点的相关配置

每个 Sentinel 节点也会通过 SUBSCRIBE 命令,订阅主从节点_sentinel_:hello 频道上的信息,获取正在监控主从节点的其他 Sentinel 节点

主服务器下线

主观下线:如果任意一个 Sentinel 节点发现主服务器距离最后一次有效回复 PING 命令的时间超过了 down-after-milliseconds,默认为 30 秒,会被 Sentinel 标记为主观下线

客观下线:当 Sentinel 将一个主服务器判断为主观下线后,会向同样监视该主服务器的其他Sentinel 进行询问,如果其他 Sentinel 也认为该主服务器已下线,并且认为已下线的 Sentinel 数量设置中的 quorum 参数值,将该主服务器标记为客观下线。如果没有足够数量的 Sentinel 同意主服务器下线,当主服务器重新向 Sentinel 的 PING 命令返回有效回复时,主服务器的主观下线状态会被移除。当主服务器被标记为下线时,Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率由十秒一次改为一秒一次。

选举领头 Sentinel

当一个主服务器被判断为下线时,监视该主服务器的各个 Sentinel 会进行协商,选举一个领头的Sentinel,并由领头 Sentinel 对下线服务器执行故障转移操作。选举规则和方法如下:

每个在线的 Sentinel 都有被选为领头 Sentinel 的资格无论选举是否成功,所有 Sentinel 的配置纪元(就是一个计数器)的值都会自增一次,领头 Sentinel

一旦确认,在这个配置纪元里就不能更改每个发现主服务器进入客观下线的 Sentinel 都会向其他 Sentinel 发送请求自己为局部领头

Sentinel

每个 Sentinel 都采用先到先得的规则,最先向目标 Sentinel 发送设置为局部领头 Sentinel 请求的源 Sentinel 会被接受,后来的都会拒绝如果某个 Sentinel 被半数以上的 Sentinel 设置成了局部领头 Sentinel,那么这个 Sentinel 成为领头的 Sentinel

如果在给定时间内,没有选举出来领头 Sentinel,会在一段时间之后再次选举,直到选举出来Sentinel 为止

故障转移

1、在已下线主服务器的所有从服务器中,挑选出一个从服务器,并将其转换成主服务器

2、让已下线主服务器的其他从服务器改为复制新的主服务器

3、将已下线主服务器设置为新的主服务器的从服务器,并将旧的主服务器重新上线

挑选机制:删除处于下线或断线的从服务器,删除最近五秒内没有回复过领头 Sentinel 的 INFO 命令的从服务器,删除过早与主服务器断开的从服务器。然后根据剩余从服务器的优先级进行排序,选出优先级最高的;如果优先级最高的有多个,选出复制偏移量最大的,复制偏移量最大的表示保存着最新数据;如果偏移量最大还有多个,就选出运行 ID 最小的

集群(Cluster)模式

RedisCluster 是 Redis 的分布式解决方案,支持分片(把整个数据划分到多个节点),是官方推荐

的方案。优点:去中心化,支持水平扩展。缺点:不支持多个键映射在不同的槽的批量操作;复制

结构只支持单层结构,不支持级联;键事务支持有限,当 key 分布在不同节点无法使用事务,同一个节点是支持事务的;槽指派及命令执行Redis 集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为 16384 个槽(slot),数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或16384 个槽。

在对数据库中的 16384 个槽都进行了指派之后,集群就可以使用了,当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽在当前节点上。如果键所在的槽刚好在当前节点上,那么节点直接执行这个命令。如果键所在的槽不在当前节点上,那么该节点会向客户端返回一个 MOVED 错误,并引导客户端重定向到正确的节点,并再次发送之前想要执行的命令。

重新分片

Redis 集群可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。重新分片操作可以在线上执行,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。

复制

集群中的节点分为主节点和从节点,主节点用于处理槽,从节点用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。

故障检测

集群中的每个节点都会定期向集群中的其他节点发送 PING 消息,以此来检测对方是否在线,如果没有正常返回,那么发送 PING 消息的节点会将接收消息的节点标记为疑似下线。然后各个节点会通过互发消息来交换集群中各个节点的状态信息,如果半数以上负责处理槽的主节点都将某个主节点标记为疑似下线,那么该主节点会被标记为已下线,然后在集群广播该主节点为已下线的消息,所有收到这条消息的节点都会将该主节点标记为已下线。

故障转移

1、下线主节点的所有从节点里面,会有一个从节点被选中成为新的主节点

2、新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己

3、新的主节点向集群中广播消息,通知其他主节点当前从节点变成了主节点,并且已经接管了下线节点负责处理的槽

4、新的主节点开始接收和自己负责的槽有关的命令请求

选举过程

1、集群的配置纪元是一个自增计数器,初始值为 0,发送故障转移操作时,就增一

2、对于每个配置纪元,集群中每个负责处理槽的主节点都有一次投票机会,而第一个向主节点要求

投票的从节点将获得主节点的投票

3、当从节点发现自己复制的主节点处于已下线状态时,会向集群中广播一条消息,让其他主节点给

当前从节点投票

4、当一个从节点得到的票数超过一半(>=N/2+1),则当前从节点变为新的主节点

5、如果没有一个从节点得票数超过一半,则配置纪元增一,再次进行选举,直到选出新的主节点为止

缓存穿透

查询一个一定不存在的数据,通常可能是遇到了黑客攻击,由于缓存是不命中时被动写的,如果数据库查不到数据则不写入缓存,导致不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。流量大时,导致数据库宕机

解决方案:

1、常用的是使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一定不存在

的数据会被这个 bitmap 拦截掉,从而避免了对数据库的压力

布隆过滤器可以理解为一个不怎么精确的 Set 结构,当使用它的 contains 方法判断某个对象是否存在时,可能会误判,只要参数设置合适,它的精确度可以控制的相对足够精确,误判概率很小。当布隆过滤器判断某个值存在时,这个值可能不存在;当判断某个值不存在时,这个值肯定不存在。

缺点:

会存在一定的误判率;对新增的数据无法进行布隆过滤;数据的 key 不会频繁更改

  1. 一个查询返回的数据为空,仍然将这个空结果进行缓存,给它一个很短的过期时间,不超过五分钟

缓存雪崩

设置的缓存采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,数据库压力瞬间过大而雪崩;或是 Redis 本身崩溃了

解决方案:

1、将缓存失效时间分散开,在原有失效时间上增加一个随机值

2、设置 Redis 集群和数据库集群的高可用,如果 Redis 宕机,可以立即由别的机器顶替上来

缓存击穿

一些设置了过期时间的 key,在某个时间点被超高并发访问,与缓存雪崩的区别在于缓存击穿针对某一个 key 缓存,雪崩是很多 key

解决方案:

使用互斥锁(mutex key),在缓存失效的时候,先使用 SETNX 去设置一个互斥锁,当操作返回成功时再进行查询数据库的操作,查询到了设置到缓存中,否则重试整个获取缓存的方法

缓存更新策略

通常是先更新数据库,再让缓存失效;

如果先失效缓存,查询的时候,会将一个数据库之前的脏数据放入缓存中;先更新数据库,再让缓存失效,导致第一次查询缓存的时候未找到,会去查询数据库,然后再放入缓存,这样导致缓存不一样的概率会降低,所以缓存过期时间必须设置。

为什么是让缓存失效,而不是更新缓存呢?

直接更新会导致并发写操作出现脏数据,如果两个线程对资源的值进行操作,一个修改为 1,另一个修改为 2,线程 A 修改数据库更新完,没来得及更新缓存,线程 B 修改 DB 为 2,更新缓存为2,线程 A 再去更新缓存为 1,导致数据不一致,缓存失效都会再查一次 DB,不一致概率会降低。

Bean 的生命周期

1、Spring 启动,查找并加载需要被 Spring 管理的 bean,进行 Bean 的实例化

2、Bean 实例化后对将 Bean 的引入和值注入到 Bean 的属性中

3、如果 Bean 实现了 BeanNameAware 接口的话,Spring 将 Bean 的 Id 传递给 setBeanName()

方法

4、如果 Bean 实现了 BeanFactoryAware 接口的话,Spring 将调用 setBeanFactory()方法,将BeanFactory 容器实例传入

5、如果 Bean 实现了 ApplicationContextAware 接口的话,Spring 将调用 Bean 的

setApplicationContext()方法,将 bean 所在应用上下文引用传入进来

6、如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的

postProcessBeforeInitialization()方法。

7、如果 Bean 实现了 InitializingBean 接口,Spring 将调用他们的 afterPropertiesSet()方法。类似的,如果 bean 使用 init-method 声明了初始化方法,该方法也会被调用

8、如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的

postProcessAfterInitialization()方法。

9、此时,Bean 已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。

10、如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destory()接口方法,同样,如果 bean 使用了 destory-method 声明销毁方法,该方法也会被调用。

Spring IoC 的流程

IOC,控制反转,在传统的程序设计中,我们直接在对象内部通过 new 来创建对象。在 Spring

中,所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象的生命周期的是 Spring,所以叫控制反转

DI,依赖注入,组件之间的依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中

Bean,由 Spring 容器实例化、组装、管理的对象,即注入到 Spring 容器中的 Java 对象实例

BeanDefinition,bean 的定义,用来存储 bean 的所有属性方法定义

核心流程介绍

一、容器构建启动入口

容器构建启动的入口有多种,以常用的 web.xml 来说。

首先,会在 web.xml 中配置 ContextLoaderListener 监听器,当 Tomcat 启动时,会触发

ContextLoadListenser 的 contextInitialized 方法,从而开始 IOC 的构建流程

另一个常用的参数是 contextConfigLocation,用于指定 Spring 配置文件的路径

二、ApplicationContext 刷新前配置

容器构建在正式进入容器的刷新前,会进行一些前置操作。

1、确认要使用的容器,通常使用的是:XmlWebApplicationContext,如果使用 SpringBoot,一般是 AnnotationConfigApplication,都继承 AbstractApplicationContext,核心逻辑都在该抽

象类中实现

三、初始化 BeanFactory、加载 Bean 定义

1、创建一个 BeanFactory,默认为 DefaultListableBeanFactory

2、根据 web.xml 中的 contextConfigLocation 配置的路径,读取 Spring 配置文件,并封装成Resource

3、根据 Resource 加载 XML 配置文件,并解析成 Document 对象

4、从根节点开始,遍历解析 Document 中的节点。针对默认命名空间的节点,先将 bean 节点内容解析封装成 BeanDefinition,然后将 beanName、beanDefinition 放到 BeanFactory 的缓存中,用于后续创建 bean 实例时使用;对于自定义命名空间的节点,会拿到自定义命名空间对应的解析器,对节点进行解析处理

四、注册 BeanPostProcessor

注册所有的 BeanPostProcessor,将所有实现了 BeanPostProcessor 接口的类加载到

BeanFactory 中。BeanPostProcessor 接口是 Spring 初始化 Bean 是对外暴露的扩展点,SpringIOC 容器允许 BeanPostProcessor 在容器初始化 bean 的前后,添加自己的逻辑处理。在这边只是注册到 BeanFactory,具体调用是在 bean 初始化的时候

五、实例化所有剩余的非懒加载单例 bean

1、遍历所有被加载到缓存中的 beanName,触发所有剩余的非懒加载单例 bean 的实例化

2、根据 BeanDefinition,使用构造函数创建 bean 实例

3、根据 BeanDefinition,进行 bean 实例属性填充

4、执行 bean 实例的初始化。检查 Aware 相关接口并设置相关依赖。触发 BeanPostProcessor 的

postProcessBeforeInitialization 方法。如果 bean 实现了 InitalizingBean 接口,则触发

afterPropertiesSet 方法。如果 bean 设置了 init-method 属性,则触发 init-method 指定的方

法。触发 BeanPostProcessor 的 postProcessAfterInitialization 方法。

5、将创建好的 bean 实例放到缓存中,用于之后使用

Spring 解决循环依赖

循环依赖即循环引用,指存在两个或两个以上 bean 互相持有对方的引用,形成闭环,如 A -> B-> C -> A。

使用了三级缓存,都是 Map 结构,

singletonObjects:单例对象的缓存,beanName -> 单例 bean 对象;

earlySingletonObjects:提前曝光的单例对象的缓存,用于检测循环引用,与

singletonFactories 互斥,beanName -> 早期单例 bean 对象

singletonFactories:单例工厂的缓存,beanName -> ObjectFactory

如何解决

Spring 的单例对象的初始化主要分为三步:调用对象的构造方法实例化对象,对 bean 的依赖属性进行填充,对 bean 进行初始化使用构造函数创建一个不完美的 bean 实例(之所以说不完美,是因为还未进行第二步和第三步),并且提前曝光该 bean 实例的 objectFactory,提前曝光是指将 objectFactory 放到singletonFactories 缓存中,通过 objectFactory 可以拿到该 bean 实例的引用,虽然是不完美的,由于是单例,所以在后续初始化完成之后,该 bean 实例的引用地址并不会改变,最终获取到的还是完美的 bean 实例

如 A->B->A,当 A 完成初始化的第一步,并将自己提前曝光在 singletonFactories 中,然后进行初始化的第二步,发现自己依赖对象 B,先从一级缓存 singletonObjects 中尝试获取 B;如果一级缓存未找到 B,并且 B 正在创建,从二级缓存 earlySingletonObjects 中获取 B;如果二级缓存还没有 B,从三级缓存 singletonFactories 中获取 B 的单例工厂,如果存在单例工厂,则通过单例工厂创建单例对象 B,然后将 B 添加到二级缓存中,并从三级缓存中移除 B 的单例工厂。这样对象 A就能获取到对象 B 的引用

而 B 进行初始化第二步的时候,也是通过这样的流程去获取 A 的引用的最后对象 A、B 在顺利完成自己的三步初始化之后,将自己放入到一级缓存 singletonObjects 中

Spring 的事务传播行为有哪些,讲下嵌套事务

七种事务传播行为

required:支持当前事务,如果当前方法有就加入当前事务中;如果没有就新建一个事务

supports:支持当前事务,如果当前方法有就加入当前事务中;如果没有就以非事务的方式运行

mandatory:支持当前事务,如果当前方法有就加入当前事务中;如果没有就抛出异常

requires_new:新建事务,如果当前存在事务,就把当前事务挂起,新建一个事务;如果没有就新建一个事务

not_supported:以非事务方式执行,如果当前方法存在事务就挂起当前事务,以非事务的方式执行;如果当前方法不存在事务,就以非事务方式执行

never:以非事务方式执行,如果当前方法存在事务,就抛出异常;如果当前方法不存在事务,就以非事务方式执行

nested:如果当前方法存在事务,则在嵌套事务内执行;如果当前方法没有事务,则新建一个事务嵌套事务

nested 中才会出现,它是当前事务中的一个真正的子事务,当嵌套事务开始执行时,它会取得一个保存点,如果嵌套事务失败,会回滚到该保存点。嵌套事务是当前事务的一部分,只有外部事务结束后它才会被真正提交;如果外部事务失败进行回滚,嵌套事务也会回滚;使用 nested 可以在嵌套事务失败后,回滚到保存点,可以进行异常处理,去执行其他的业务,起到了分支执行的效果。

Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次

结构, 发现其 Template 实现是 JdbcTransactionObjectSupport, 常用的

DatasourceTransactionManager, HibernateTransactionManager 中的 TransactonObject 都

是它的子类

REQUIRES_NEW 和 NESTED 的区别

requires_new 是将当前事务挂起,启用一个新的事务,这个事务最终 commit 或者 rollback 不依赖当前事务,拥有自己的隔离范围。新的事务开始执行时,当前事务挂起,新的事务结束时,当前事务将继续执行。nested 真正的提交和回滚依赖当前事务

一个没有 Transactional 注解的方法调用本类中的有 Transactional 注解的方法,不

会开启一个事务Spring 在扫描 bean 的时候会扫描方法上是否包含 Transactional 注解,如果包含,Spring 会为这个 bean 动态生成一个代理类,代理类继承了原来的 bean。此时,当这个有注解的方法被调用时,实际上是由调用的代理类,代理类在被调用之前就会开启事务。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用没有通过代理类,而是直接通过原来的那个bean,所以没有开启事务。

可以在没有 Transactional 注解的方法中,采用 AopContext.currentProxy() 获取代理对象,使用代理对象再去调用有注解的方法,这样就可以开启一个事务了

Spring AOP 流程

Joinpoint(连接点):可以被增强的方法

Pointcut(切点):指定一组 Joinpoint,代表在这一组 Joinpoint 中织入我们的逻辑,真正被增强的方法

Advice(通知/增强):具体的增强逻辑

Aspect(切面):包含多个切点和相关的增强定义

Weaving(织入):将 Advice 连接到 Pointcut 指定的 Joinpoint 处的过程

Target(目标对象):含有增强逻辑和目标方法的实例对象

核心流程

1、解析 AOP 注解,并注册对应的内部管理的自动代理者的 bean,常用的是

AnnotationAwareAspectJAutoProxyCreator

2、当 bean 初始化完毕后,会触发所有 BeanPostProcessor 的 postProcessAfterInitialization 方法,此时会调用 AnnotationAwareAspectJAutoProxyCreator 的 postProcessAfterInitialization

方法。该方法会查找我们定义的切面类(使用@Aspect 注解),创建切面类定义的增强器(使用

@Before、@After、@Around 等注解),并根据@Pointcut 的 execution 表达式筛选出适合当前遍历 bean 的增强器,将适用于当前遍历 bean 的增强器作为参数之一创建对应的 AOP 代理

3、当调用到被 AOP 代理的方式时,会走到对应的代理方法:JdkDynamicAopProxy 的 invoke 方法或 DynamicAdvisedInterceptor 的 intercept 方法,该方法会创建

ReflectiveMethodInvocation,通过责任链的方式来执行所有的增强器和被代理的方法

Spring 其他问题

什么情况下对象不能被代理

基于 JDK 动态代理的目标类必须要实现接口,没有接口可以使用 CGLIB 动态代理,CGLIB 原理是生成目标类的子类, 目标类不能用 final 修饰

@Resource 和 @Autowire 的区别

@Resource 是 JDK 注解,默认按照名称(by-name)进行装配,名称可以通过 name 属性指定。

如果没有指定 name,当注解在字段上,默认去 name=字段名称装配;当注解在 setter 方法上时,默认去 name=属性名称装配。

当按照名称无法匹配时,则按照类型(by-type)装配,如果显式指定 name 属性后,只能按照名称进行装配

@Autowire 是 Spring 注解,默认按照类型(by-type)进行装配,默认情况下要求依赖对象必须存在,如果允许对象为 null,则需设置 required 属性为 false。如果有多个相同的 type,则使用name 再次选择注入。如果要强制使用按照名称(by-name)装配,需结合@Qualifier 注解使用,在该注解中添加类名,@Qualifier("beanName")两个 id 相同的 bean,在哪个阶段报错如果在同一个配置文件,不能存在 id 相同的两个 bean,否则会报错。

但是在两个不同的配置文件中,可以存在 id 相同的两个 bean,启动时不会报错,后加载配置文件的 bean 会覆盖先加载配置文件的 bean。DefaultListableBeanFactory 类中有个属性allowBeanDefinitionOverriding,默认为 true,允许覆盖,如果设置该值为 false,则不会进行覆盖,而是抛出异常。

Mybatis,调用 DAO 接口时是怎么调用到 SQL 的

1、扫描注册 basePackage 包下的所有 bean,将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造参数传入

MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate

属性。sqlSessionFactory 的 mapperLocations 属性会指定所有 mapper 文件的位置。

2、解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成

MappedStatement,放到 mappedStatements 缓存中,key 为 namespace + id,例如:

com.xiaodai.app.mapper.UserDao.getById,value 为 MappedStatement。并将解析过的

mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,

value 为 MapperProxyFactory

3、创建 DAO 的 bean 时,通过 mapperInterface 从 knownMappers 缓存中获取到

MapperProxyFactory 对象,通过 JDK 动态代理创建 MapperProxyFactory 实例对象,

InvocationHandler 为 MapperProxy

4、DAO 中的接口被调用时,通过动态代理,调用 MapperProxy 的 invoke 方法,最终通过

mapperInterface 从 mappedStatement 缓存中拿到对应的 MappedStatement,执行相应的操

Dubbo

Dubbo 是什么

基于 Java 的高性能 RPC 分布式服务框架

都支持什么协议,推荐用哪种

dubbo://(推荐)、rmi://、http://、 webservice://、redis://等

服务注册与发现的流程图

有哪几种节点角色

Provider:暴露服务的服务提供方

Consumer:调用远程服务的服务消费方

Registry:服务注册与发现的注册中心

Moniter:统计服务调用次数和调用时间的监控中心

Container:服务运行容器

消息队列

RabbitMQ 的高可用性

镜像集群模式:这种模式,才是所谓的 rabbitmq 的高可用模式,跟普通集群模式不一样的是,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,然后每次你写消息到queue 的时候,都会自动把消息到多个实例的 queue 里进行消息同步

如何保证消息的顺序性

拆分多个 queue,每个 queue 对应一个 consumer,就是多一些 queue,同时针对更新使用乐观锁,加上原值判断

如果让你写一个消息队列,该如何进行架构设计

1、MQ 支持可伸缩性,在需要的时候快速扩容,就可以增加吞吐量,设计分布式系统,可以增加机器,存放更多的数据

2、MQ 的数据支持存入磁盘,顺序写,磁盘顺序读写的性能很高

3、高可用性,采用主从,选举机制

4、防止丢失,采用消息确认机制

MongoDB

MongoDB 是一个文档数据库,非关系型数据库。采用 BSON 存储文档数据。

BSON 是一种类 json 的一种二进制形式的存储格式,相对于 json 多了 date 类型和二进制数组。

MongoDB 的优势有哪些

面向文档的存储:以 JSON 格式的文档保存数据

自动分片

丰富的查询功能

MongoDB 中的分片什么意思

分片是将数据水平切分到不同的物理节点。当应用数据越来越大的时候,数据量也会越来越大。当数据量增长时,单台机器有可能无法存储数据或可接受的读取写入吞吐量。利用分片技术可以添加更多的机器来应对数据量增加以及读写操作的要求。

MongoDB 支持哪些数据类型

String、Integer、Double、Boolean、Object、Arrays、Datetime、Regular Expression 等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值