微服务常见面试题(Java、数据库、Redis、SpringCloud面试题)
Redis本质上是一个 Key-Value 类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬 盘上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外
Redis
本质上是一个 Key-Value 类型的内存数据库,很像memcached
,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬 盘上进行保存。
因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。
Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个 value 的最大 限制是 1GB,不像 memcached 只能保存 1MB 的数据,因此 Redis 可以用来实现很多有用的功能。
比方说用他的 List 来做 FIFO 双向链表
,实现一个轻量级的高性 能消息队列服务,用他的 Set 可以做 高性能的 tag 系统等等。
另外 Redis 也可以对存入的 Key-Value 设置 expire 时间,因此也可以被当作一 个功能加强版的 memcached 来用。 Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能 读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
Redis 与 memcached 相比有哪些优势
1.memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
2.redis 的速度比 memcached 快很多 redis 的速度比 memcached 快很多
3.redis 可以持久化其数据
Redis 支持哪几种数据类型?
String、List、Set、Sorted Set、Hash
为什么 Redis 需要把所有数据放到内存中?
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。
所以 redis 具有快速和数据持久化的特征,如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。
Redis 哈希槽的概念
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
Redis 集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型, 每个节点都会有 N-1 个复制品.
Redis 中的管道有什么用?
一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务 器,而不用等待回复,最后在一个步骤中读取该答复。
这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3 协议已经实现支持这个 功能,大大加快了从服务器下载新邮件的过程。
怎么理解 Redis 事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会 被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
Redis 如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该 尽可能的将你的数据模型抽象到一个散列表里面。
比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而 是应该把这个用户的所有信息存储到一张散列表里面。
Redis 分布式锁怎么实现
先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。
应用场景:不同的定时任务有且只能一个节点执行
特性:
- 保证同一个方法在某一时刻只能在一台机器里一个进程中一个线程执行;
- 要保证是可重入锁(避免死锁)->同一个线程(已获取到锁)还可以获得锁;
- 要保证获取锁和释放锁的高可用;
setnx缺点:不支持超时时间,如果一个线程在执行时出现异常或超时导致锁未及时释放,那么这块资源将被一直锁住,其他线程也进不来.
Redis 2.6.12以上版本为set指令增加了可选参数,伪代码如下:set(key,1,30,NX),这样就可以取代setnx指令
场景1: 超时后使用del 导致误删其他线程的锁
线程A执行到超时时间,然后锁的超时时间已到自动释放,此时b线程进来,而这个时候A线程执行完毕,A需要删除锁(可能删除b线程的锁),所以在释放锁的时候需要用lua脚本:
String luaScript = 'if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end';
redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));
方案解决2: 让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。
当线程A执行完任务,会显式关掉守护线程。
Redission和RedisLockRegistry区别
- RedisLockRegistry通过本地锁(ReentrantLock)和redis锁,双重锁实现,Redission通过Netty Future机制、Semaphore (jdk信号量)、redis锁实现。
- RedisLockRegistry和Redssion都是实现的可重入锁。
- RedisLockRegistry对锁的刷新没有处理,Redisson通过Netty的TimerTask、Timeout 工具完成锁的定期刷新任务。
- RedisLockRegistry仅仅是实现了分布式锁,而Redisson处理分布式锁,还提供了了队列、集合、列表等丰富的API。
缓存穿透?如何避免?什么是缓存雪崩?何如避免?
缓存穿透
一般的缓存系统,都是按照 key 去缓存查询,如果不存在对应的 value,就应该去后端系统查找(比如 DB)。一些恶意的请求会故意查询不存在的 key,请求量很大,就会对后端系统造成很大的压力。这就叫 做缓存穿透。
如何避免?
- 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该 key 对应的数据 insert 了之后清
理缓存。 - 对一定不存在的 key 进行过滤。可以把所有的可能存在的 key 放到一个大的 Bitmap 中,查询时通过 该 bitmap 过滤。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压 力。导致系统崩溃。
如何避免?
- 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个
线程查询数据和写缓存,其他线程等待。 - 做二级缓存,A1 为原始缓存,A2 为拷贝缓存,A1 失效时,可以访问 A2,A1 缓存失效时间设置
为短期,A2 设置为长期 - 不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
Spring
Spring 框架是一个为 Java 应用程序的开发提供了综合、广泛的基础性支持的 Java 平台。
Spring 帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。
Spring 框架本身亦是按照设计模式精心打造,这使得我们可以在开发环境中安心的集成 Spring 框 架,不必担心 Spring 是如何在后台进行工作的。
Spring 框架至今已集成了 20 多个模块。这些模块主要分为核心容器、数据访问/集
成,、Web、AOP(面向切面编程)、工具、消息和测试模块。
使用 Spring 框架能带来哪些好处?
Dependency Injection(DI) 方法使得构造器和 JavaBean properties 文件中的依赖关系一
目了然。
与 EJB 容器相比较,IoC 容器更加趋向于轻量级。这样一来 IoC 容器在有限的内存和 CPU 资源的情况下进行应用程序的开发和发布就变得十分有利。
Spring 并没有闭门造车,Spring 利用了已有的技术比如 ORM 框架、logging 框架、J2EE、Quartz 和 JDK Timer,以及其他视图技术。
Spring 框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者仅 仅需要选用他们需要的模块即可。
要测试一项用 Spring 开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中 了。更加简单的是,利用 JavaBean 形式的 POJO 类,可以很方便的利用依赖注入来写入测试数据。
Spring 的 Web 框架亦是一个精心设计的 Web MVC 框架,为开发者们在 web 框架的选择上 提供了一个除了主流框架比如 Struts、过度设计的、不流行 web 框架的以外的有力选项。
Spring 提供了一个便捷的事务管理接口,适用于小型的本地事物处理(比如在单 DB 的环境下)和复杂的共同事物处理(比如利用 JTA 的复杂 DB 环境)。
什么是控制反转(IOC)?什么是依赖注入
控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对 象之间耦合关系在编译时通常是未知的。在传统的编程方式中,业 务逻辑的流程是由应用程序中的 早已被设定好关联关系的对象来决定的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配 器负责实例化,这种实现方式还可以将对象之间的关联关系的定 义抽象化。而绑定的过程是通过“依赖注入”实现的。
控制反转是一种以给予应用程序中目标组件更多控制为目的设计范式,并在我们的实际工作中起到 了有效的作用。
依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象 实例化的模式。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反 转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件?
在 Java 中依然注入有以下三种实现方式:
- 构造器注入
- Setter 方法注入
- 接口注入
请解释下 Spring 框架中的 IoC?
Spring 中的 org.springframework.beans
包和 org.springframework.context
包构成了 Spring 框架 IoC
容器的基础。
BeanFactory
接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。
ApplicationContex
接口对 BeanFactory
(是一个子接口)进行了扩展,在 BeanFactory
的基础上添加了其他功能,比如与 Spring 的 AOP 更容易集成,也提供了处理 message resource
的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对 Web 应用的 WebApplicationContext
。
org.springframework.beans.factory.BeanFactory
是 Spring IoC
容器的具体实现,
用来包装和管理前面提到的各种 bean
。BeanFactory
接口是 Spring IoC
容器的核心接口。
IOC:把对象的创建、初始化、销毁交给 spring 来管理,而不是由开发者控制,实现控制反转
BeanFactory 和 ApplicationContext 有什么区别?
BeanFactory 可以理解为含有 bean 集合的工厂类。BeanFactory 包含了种 bean 的定义,以便在接收到客户端请求时将对应的 bean 实例化。
BeanFactory 还能在实例化对象的时生成协作类之间的关系。此举将 bean 自身与 bean 客户端的 配置中解放出来。BeanFactory
还包含 了 bean 生命周期的控制,调用客户端的初始化方法 (initialization methods)和销毁方法(destruction methods)。
从表面上看,application context
如同 bean factory
一样具有 bean
定义、bean 关联关系的设 置,根据请求分发 bean
的功能。但 applicationcontext
在此基础上还提供了其他的功能。
- 提供了支持国际化的文本消息
- 统一的资源文件读取方式
- 已在监听器中注册的 bean 的事件
Spring 有几种配置方式?
- 基于 XML 的配置
- 基于注解的配置
- 基于 Java 的配置
请解释 Spring Bean 的生命周期?
- 通过构造器或工厂方法创建bean实例;(实例化) Instantiation
- 为bean的属性赋值;(属性赋值)Instantiation
- 调用bean的初始化方法;(初始化)Initialization
- 使用bean;
- 当容器关闭时,调用bean的销毁方法;(销毁)Destruction
说明:
- 实例化:指创建类实例(对象)的过程。比如使用构造方法new对象,为对象在内存中分配空间。(要创建对象,但是并未生成对象)
- 初始化:指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。简单理解为对象中的属性赋值的过程。(对象已经生成,为其属性赋值)
- 属性赋值:给类中注入的对象赋值或者其它成员变量
- 销毁:不会被立即gc,只是在调用时不推荐使用(销毁的bean可能少些东西)
一句话概括:一个对象是需要内存去存放的。所以会有一个分配内存的过程。分配了内存之后,jvm
便会开始创建对象,并将它赋值给 a 变量
。然后再去初始化A
中的一些属性,并执行A
的构造方法。在初始化的过程中,会先执行 static
代码块,再执行构造方法。除此之外,如果有父类,会优先父类的进行执行。
Spring Bean 的作用域之间有什么区别?
Spring 容器中的 bean 可以分为 5 个范围。所有范围的名称都是自说明的,但是为了避免混淆,还 是让我们来解释一下:
- singleton:这种 bean 范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个
bean 的实例,单例的模式由 bean factory 自身来维护。 - prototype:原形范围与单例范围相反,为每一个 bean 请求提供一个实例。
- request:在请求 bean 范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,
bean 会失效并被垃圾回收器回收。 - Session:与请求范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean
会随之失效。 - global- session:
global-session
和Portlet
应用相关。当你的应用部署在 Portlet 容器中工作时,它包含很多 portlet。如果 你想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在global-session
中。全局作用域与 Servlet 中的 session 作用域效果相同。
Spring 框架中的单例 Beans 是线程安全的么?
Spring 框架并没有对单例 bean 进行任何多线程的封装处理。关于单例 bean 的线程安全和并发问 题需要开发者自行去搞定。但实际上,大部分的 Spring bean 并没有可变的状态(比如 Serview 类 和 DAO 类),所以在某种程度上说 Spring 的单例 bean 是线程安全的。如果你的 bean 有多种状 态的话(比如 View Model 对象),就需要自行保证线程安全。
最浅显的解决办法就是将多态 bean 的作用域由“singleton”
变更为“prototype”
。
Spring Bean 的自动装配?
自动装配就是让应用程序上下文为你找出依赖项的过程。说的通俗一点,就是Spring会在上下文中自动查找,并自动给bean装配与其关联的属性!
举例解释@Required 注解?
@Required注释为为了保证所对应的属性必须被设置,@Required
注释应用于 bean
属性的 setter
方法,它表明受影响的 bean
属性在配置时必须放在 XML 配置文件
中,否则容器就会抛出一个 BeanInitializationException
异常。直接的理解就是,如果你在某个java
类的某个set
方法上使用了该注释,那么该set方法对应的属性在xml配置文件中必须被设置,否则就会 报错!!!
@Autowired 注解?
@Autowired 注解对自动装配何时何处被实现提供了更多细粒度的控制。
@Autowired 注解可 以像@Required
注解、构造器一样被用于在 bean 的设值方法上自动装配 bean 的属性,一个参数或者带有任意名称或带有多个参数的方法。
比如,可以在设值方法上使用@Autowired
注解来替代配置文件中的 <property>元 素
。当 Spring 容器
在 setter
方法上找到@Autowired
注解时,会尝试用 byType
自动装配。
@Qualifier 注解?
@Qualifier 注解意味着可以在被标注 bean 的字段上可以自动装配。Qualifier 注
解可以用来注入指定的bean
构造方法注入和设值注入有什么区别?
构造方法注入可能会出现循环依赖问题
setter
注入的原理是先调用无参构造方法来创建对象,然后掉用对应的set
方法来为属性赋值。用setter
注入的方式也可以解决那个有参构造方法的缺陷,并且可以提高可读性。
Spring 框架中有哪些不同类型的事件?
Spring
的 ApplicationContext
提供了支持事件和代码中监听器的功能。
我们可以创建 bean
用来监听在 ApplicationContext
中发布的事件。ApplicationEvent
类和在 ApplicationContext
接口中处理的事件,如果一个 bean 实现了
ApplicationListener
接口,当一个 ApplicationEvent
被发布以后,bean
会自动被通知。
FileSystemResource 和 ClassPathResource 有何区别?
在 FileSystemResource
中需要给出 spring-config.xml
文件在你项目中的相对路径或者
绝对路径。在 ClassPathResource
中 spring
会在 ClassPath
中自动搜寻配置文件,所以要把 ClassPathResource
文件放在 ClassPath
下。
如果将 spring-config.xml
保存在了 src
文件夹下的话,只需给出配置文件的名称即可,因为 src
文件夹是默认。
简而言之,ClassPathResource
在环境变量中读取配置文件,FileSystemResource
在配置文件中读取配置文件。
Spring 框架中都用到了哪些设计模式?
- 代理模式—在
AOP
和remoting
中被用的比较多。 - 单例模式—在
spring
配置文件中定义的bean
默认为单例模式。 - 策略模式-同一方法-不同的业务方法对应不同的业务执行
- 依赖注入—贯穿于
BeanFactory / ApplicationContext
接口的核心理念。 - 工厂模式—
BeanFactory
用来创建对象的实例
开发中主要使用 Spring 的什么技术 ?
- IOC 容器管理各层的组件
- 使用 AOP 配置声明式事务
- 整合其他框架.
简述 AOP 和 IOC 概念
AOP:
Aspect Oriented Program, 面向(方面)切面的编程;Filter(过滤器) 也是一种 AOP. AOP 是一种 新的方法论, 是对传统 OOP
(Object-Oriented Programming, 面向对象编程) 的补充. AOP
的 主要编程对象是切面(aspect
), 而切面模块化横切关注点.可以举例通过事务说明.
IOC:
Invert Of Control, 控制反转. 也成为 DI(依赖注入)其思想是反转 资源获取的方向. 传统
的资源查找方式要求组件向容器发起请求查找资源.作为 回应, 容器适时的返回资源. 而应用了 IOC
之后, 则是容器主动地将资源推送 给它所管理的组件,组件所要做的仅是选择一种合适的方式 来接受资源. 这种行 为也被称为查找的被动形式
在 Spring 中如何配置 Bean ?
通过全类名(反射)、通过工厂方法(静态工厂方法 & 实 例工厂方法)、 FactoryBean
IOC 容器对 Bean 的生命周期:
- 通过构造器或工厂方法创建 Bean 实例
- 为 Bean 的属性设置值和对其他 Bean 的引用
- 将 Bean 实 例 传 递 给 Bean 后 置 处 理 器 的 postProcessBeforeInitialization 方
法 - 调用 Bean 的初始化方法(init-method)
- 将 Bean 实 例 传 递 给 Bean 后 置 处 理 器 的 postProcessAfterInitialization 方法
- Bean 可以使用了
- 当容器关闭时, 调用 Bean 的销毁方法(destroy-method)
SpringBoot
简述
Spring Boot
已经建立在现有 spring 框架
之上。使用 spring 启动,我们避免了之前我们必须做的所有样板代码和配置。因此,Spring Boot 可以 帮助我们以最少的工作量,更加健壮地使用现有的 Spring 功能。
优缺点
减少开发,测试时间和努力。
使用 JavaConfig
有助于避免使用 XML
。
避免大量的 Maven
导入和各种版本冲突。
提供意见发展方法。
通过提供默认值快速开始开发。
没有单独的 Web 服务器
需要。这意味着你不再需要启动 Tomcat
,Glassfish
或其他任何东西。
需要更少的配置 因为没有 web.xml
文件。只需添加用@Configuration
注释的类,然后添加 用@Bean
注释的方法,Spring
将自动加载对象并像以前一样对其进行管理。您甚至可以将 @Autowired
添加到 bean 方法中,以使 Spring
自动装入需要的依赖关系中。
基于环境的配置使用这些属性,您可以将您正在使用的环境传递到应用程序:-
Dspring.profiles.active={enviornment}
。在加载主应用程序属性文件后,Spring
将在 (application{environment} .properties
)中加载后续的应用程序属性文件。
什么是 JavaConfig
Spring JavaConfig
是 Spring
社区的产品,它提供了配置 Spring IoC
容器的纯 Java 方法。因此 它有助于避免使用 XML 配置
。使用 JavaConfig
的优点在于:
面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的 面向对象功能。一个配置类可以继承另一个,重写它的@Bean
方法等。
减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML
和 Java
之间来回切换。JavaConfig
为开发人员提供了一种纯 Java 方法来配 置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置 容器是可行的,但实际上很多人认为将 JavaConfig
与 XML
混合匹配是理想的。
类型安全和重构友好。JavaConfig
提供了一种类型安全的方法来配置 Spring
容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或 基于字符串的查找。
SpringBoot如何运行的?
@springbootApplication
是一个组合注解,主要组合,@SpringBootConfiguration
,@enableAutoconfiguration
,@ComponentScan
,所以,可以直接引用这三个注解,就可以是程序的入口。
@enableAutoConfiguration
可以让springboot
根据类路径中的jar包依赖为当前项目进行自动配置。不配置扫描路径的情况下,默认是扫描启动类所在目录或者他的子目录下的controller
、service
、dao
等,所以解决办法有两个:
1、 用注解 @ComponentScan(basePackages = {"com.*", "com.frames"})
主动配置扫描路径;(@SpringBootApplication
注解等价于@Configuration
, @EnableAutoConfiguration
and @ComponentScan
)
2、 直接把启动类放到根目录下,让他自动扫描所有的包(这也是官方建议的做法)
Spring Boot 中的监视器是什么?
spring boot actuator
是 spring
启动框架中的重要功能之一。Spring boot
监视器可帮助您访 问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和 监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器 模块公开了一组可直接作为 HTTP URL 访问的 REST 端点来检查状态。
如何在 Spring Boot 中禁用 Actuator 端点安全性?
默认情况下,所有敏感的 HTTP 端点都是安全的,只有具有 ACTUATOR
角色的用户才能访 问它们。安全性是使用标准的 HttpServletRequest.isUserInRole
方法实施的。 我们可以使用management.security.enabled = false
来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。
什么是 YAML?
YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。
与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构
化,而且更少混淆。可以看出 YAML 具有分层配置数据。
如何实现 Spring Boot 应用程序的安全性?
引入security或者shiro
什么是 Spring Profiles?
Spring Profiles 允许用户根据配置文件(dev,test,prod 等)来标注并区分配置文件的内容信息等,使得开发人员以及运维人员调试或部署更加清晰明了。
Spring Boot 异常处理?
Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法。 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常。
注:特定过滤器抛出异常由框架提供指定异常处理类或者由自己做特殊处理
什么是 CSRF 攻击?
CSRF 代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的 Web 应用 程序上执行不需要的操作。CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。
SpringCloud
简述
Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序,提供与外部系统的集成。
Spring cloud Task,一个生命周期短暂的微服务框架,用于快速构建执行有限数据处理的应用程序。
优势
使用 Spring Boot 开发分布式微服务时,我们面临以下问题
- 与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。
- 服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
- 冗余-分布式系统中的冗余问题。
- 负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。
- 性能-问题 由于各种运营开销导致的性能问题。
- 部署复杂性-Devops 技能的要求。
负载平衡的意义什么?
在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算 资源的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源 的过载。使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务器进程
什么是 Hystrix?它如何实现容错?
Hystrix 是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的 故障时,停止级联故障并在复杂的分布式系统中实现弹性。
场景:
由于某些原因,employee-consumer 公开服务会引发异常。在这种情况下使用 Hystrix 我们定义了一个 回退方法。如果在公开服务中发生异常,则回退方法返回一些默认值。
断路器的目的是给第一页方法或第一页方法可能调用的其他方法 留出时间,并导致异常恢复。可能发生的情况是,在负载较小的情况下,导致异常的问题有更好的恢复机会。
什么是 Netflix Feign?它的优点是什么?
Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 启发的 java 客户端联编程序。Feign 的第一个目标是将约束分母的复杂性统一到 http apis
,而不考虑其稳定性。在 employee-consumer 的例子中,我们 使用了 employee-producer 使用 REST 模板公开的 REST 服务。但是我们必须编写大量代码才能执行以下步骤
- 使用功能区进行负载平衡。
- 获取服务实例,然后获取基本 URL。
- 利用 REST 模板来使用服务。
什么是 Spring Cloud Bus?
我们有多个应用程序使用 Spring Cloud Config 读取属性,而 Spring Cloud Config 从
GIT 读取这些属性。
下面的例子中多个员工生产者模块从 Employee Config Module 获取 Eureka 注册的财产。
因此,在上面的示例中,如果我们刷新 Employee Producer1,则会自动刷新所有其他必需的模块。如果我们有多个微服务启动并运行,这特别有用。这是通过将所有微服务连接到单个消息代理来实现的。无论何时刷新实例,此事件都会订阅到侦听 此代理的所有微服务,并且它们也会刷新。可以通过使用端点/总线/刷新来实现对任何单个实例的刷新
基础
ThreadLocal
作用:
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
API:
Set、 remove、 initvalue、 get
应用场景:
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized
来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal
使用场景为用来解决数据库连接、Session管理,存储共享变量等。
ArraysList和LinkedList
ArraysList
ArraysList底层是数组结构存储,其是有序的, 当查询数据时,直接通过下标获取,速度就快很多。当往集合内插入数据时有序添加,并且每次添加数据数组的大小容量就自增1。
添加机制
添加一个元素时,做了如下两步操作
1. 如果第一次添加,判断是否和初始化数组都为空,如果都为空返回默认容量为10,如果不是第一次添加,
如果大于10,则对数组进行扩容,进行添加数据。
2. 真正将元素放在列表的元素数组里面
删除机制
当删除数据时,可根据数据下标直接删除对应下标数据同时该位置后续的数据下标都会自减1
线程安全解决方案
解决措施①:使用Vector集合
protected static Vector<Object> arrayListSafe1 = new Vector<Object>();
Vector:因为该类数组内数据操作都加了synchronized关键字,每次操作数据-数组都会当前线程执行完毕才执行下次保证了安全
解决措施②:我们加上Collections.synchronizedList,它会自动将我们的list方法进行改变,最后返回给我们一个加锁了List
LinkedList
LinkedList是通过双向链表实现的,而双向链表就是通过Node类来体现的,类中通过item变量保存了当前节点的值,通过next变量指向下一个节点,通过prev变量指向上一个节点。
查询机制:
比较传入的索引参数index与集合长度size/2,如果是index小,那么从第一个顺序循环,直到找到为止;如果index大,那么从最后一个倒序循环,直到找到为止。
也就是说越靠近中间的元素,调用get(int index方法遍历的次数越多,效率也就越低,而且随着集合的越来越大,get(int index)执行性能也会指数级降低。
因此在使用LinkedList的时候,我们不建议使用这种方式读取数据,可以使用getFirst(),getLast()方法,将直接用到类中的first和last变量。
添加机制:
第一次添加如果链表无数据,即直接添加last,第二次添加有数据之后,会将第一个已经添加的数据,后节点添加并将下一个节点赋值第二个数据,然后第二个节点的数据上一个节点就是第一条数据的last数据
删除机制:
找到数据所在节点,判断前后是否有节点关联,如果有上一个节点的数据prev变更为要删除的节点的下一个节点值,删除节点之后的下一个节点的prev值就是要删除的上一个节点的next值
添加图示
HashMap
概述
- HashMap是一个散列表,它存储的是键值对(key-value)映射;
- HashMap继承AbstractMap,实现了Map,Cloneable,Serializable接口;
- HashMap的实现不是同步的,线程不安全,但是效率高;
- HashMap允许null键和null值,是基于哈希表的Map接口实现;
- 哈希表的作用是用来保证键的唯一性;
- HashMap的实例有两个参数影响其性能:初试容量和加载因子,当哈希表中的条目数超出加载因子与当前容量的乘积时,要对哈希表进行rehash操作(即重建内部数据结构),容量扩大约为之前的两倍,加载因子默认值为0.75;
数据结构
在 Java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是指针(引用),HashMap 就是通过这两个数据结构进行实现。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
底层实现原理
HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap
HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,在根据 equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该Entry。
HashMap为什么不安全?
如果多个线程同时使用put方法添加元素,而且假设正好存在两个put的key发生了碰撞(hash值一样),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。
如果多个线程同时检测到元素个数超过数组大小*loadFactor,这样就会发生多个线程同时对Node数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,也就是说其他线程的都会丢失,并且各自线程put的数据也丢失。
如何解决Hashmap安全问题
- 当有多个线程执行时,这边只有一个存储桶map键值对,这个时候给每个线程去获取共有资源时都加一把锁,共同占用一个资源,如果有其他线程在执行操作时其它线程等待,这样可以解决Hashmap安全问题
- ConcurrentHashMap
简述:
1.8源码基于cas/synchronized
实现,1.6版本插入时采用分段锁实现,底层依然采用数组+链表+红黑树
的存储结构。
1.8取消segments
字段,直接采用transient volatile HashEntry<K,V> table
保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap
类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树
的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。 - Collections.synchronizedMap(hashMap)和HashTable
Hashmap 什么时候进行扩容呢?
当 hashmap 中的元素个数超过数组大小 loadFactor 时,就会进行数组扩容,
loadFactor 的默认值为 0.75,也就是说,默认情况下,数组大小为 16,那么当
hashmap 中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 216=32,
即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操
作,所以如果我们已经预知 hashmap 中元素的个数,那么预设元素的个数能够有效
的提高 hashmap 的性能。
HashTable
HashTable相对于HashMap的最大特点就是线程安全,所有的操作都是被synchronized锁保护的
HashMap和HashTable区别
- HashMap是非同步的,没有对读写等操作进行锁保护,所以是线程不安全的,在多线程场景下会出现数据不一致的问题。而HashTable是同步的,所有的读写等操作都进行了锁(synchronized)保护,在多线程环境下没有安全问题。但是锁保护也是有代价的,会对读写的效率产生较大影响。
- HashMap结构中,是允许保存null的,Entry.key和Entry.value均可以为null。但是HashTable中是不允许保存null的。
- HashMap的迭代器(Iterator)是fail-fast迭代器,但是Hashtable的迭代器(enumerator)不是fail-fast的。如果有其它线程对HashMap进行的添加/删除元素,将会抛出ConcurrentModificationException,但迭代器本身的remove方法移除元素则不会抛出异常。这条同样也是Enumeration和Iterator的区别。
并发编程概念
- 原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
- 可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- 有序性
即程序执行的顺序按照代码的先后顺序执行。
Volatile
概念:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
举例说明:
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
解释: 每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
用volatile修饰
第一:使用volatile
关键字会强制将修改的值立即写入主存;
第二:使用volatile
关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
利弊
volatile不保证其原子性,只保证可见性
- 解决方案
-
Synchronized
使用Synchronized配合volatile使用,采用双重锁方式,一个保证每次插入都能够插入成功另一个保证每次读取到的值都是正确的值 -
Lock
使用Lock配合volatile使用,当要进行修改或递增时lock锁住方法体,保证每次必能正确修改,然后volatile保证每次读取的值为最新即可。
Synchronized
原理:
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
事务特性
-
原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
-
一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。(实例:转账,两个账户余额相加,值不变。)
-
隔离性(isolation):一个事务的执行不能被其他事务所影响。
-
持久性(durability):一个事务一旦提交,事物的操作便永久性的保存在DB中。即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
线程池
好处:
1. 重用线程池中已经存在的线程,减少了线程的创建和消亡多造成的性能开销。
2. 能够有效控制最大的并发线程数,提高了系统资源的使用率,并且还能够避免大量线程之间因为相互抢占系统资源而导致阻塞。
3. 能够对线程进行简单管理,并提供定时执行、定期执行、单线程、并发数控制等功能。
ThreadPoolExecutor
通过ExecutorService构建一个子实现ThreadPoolExecutor线程池,进行初始化线程池大小,核心线程数,最大线程数,线程超时时间长等。
执行规则
- 如果在线程池中的线程数量没有达到核心的线程数量,这时候就回启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行。
- 由于任务队列已满,无法将任务插入到任务队列中。这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会立即启动一个非核心线程来执行任务。
- 如果线程池中的数量达到了所规定的最大值,那么就会拒绝执行此任务,这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。
使用
可以用submit和execute去执行线程,execure没有返回值,因为这个无法判断线程是否执行成功,所以很少用到
submit用处较广,因为它执行完会有一个future返回,我们可以通过返回结果来判断线程是否执行成功。
- CompletableFuture(jdk1.8提供-待描述)
分布式事务解决方案
简述
一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
产生原因
数据库分库分表
解决方案
基于seata解决(AT模式解决)。
整合rabbitmq消息+最终一致性解决方案。
Seata-At模式:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
隔离性
A服务调用执行前开启本地事务,拿到本地锁,本地事务提交之前会获取一个全局锁,当前事务提交释放本地锁,B事务开启本地事务,拿到本地锁,本地事务提交前仍然会获取全局锁,如果获取不到全局锁即持续等待,保证脏写问题不发生。
读隔离:
Seata默认的隔离级别是读未提交
如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
Cap理论:一致性,可用性,分区容错性
Mysql执行顺序
- 首先进行与mysql客户端进行连接,验证请求是否符合请求并验证权限和账号密码,然后进行
- 查看是否是缓存查看,如果是缓存直接获取结果返回反之->走第三步
- 分析器对sql语句进行分析,识别sql语句语法是否符合规范,
- 如果sql查询有(索引)字段,用优化器进行优化
- 最后执行器进行执行,调用mysql的存储引擎查询返回结果(一般是innodb)。
Mysql事务
Dml(操作数据库表内数据的新增或修改)
Ddl(创建表修改表结构等)
Sql脚本执行时也需要事务控制
- Begin. 开启一个事物
- Commin 提交一个事物
- Rollback 回滚一个事物
Mysql索引执行流程
由于mysql索引执行都是建立在b+树上的,b+树分为好多磁盘模块,分节点存储,当查询字段和条件匹配时即定位到某个磁盘块上最终通过指针查找到结果并返回。
Redis哪些数据类型?
String
Kv存储
Hash
通过以key value的形式存储一种结构化的数据,比如1个对象
List
有序列表,可以通过list存储一些有序的数据结构
Set
基于set将数据去重,可以玩一些交集,并集,差集操作.
Sorted set
排序的set,既可以去重也可以排序。
Redis分布式锁的实现原理
当多个线程共享一个资源时,对该资源的访问方法执行之前加锁,使对资源访问始终保持一个线程,保证其不被篡改,执行完之后进行释放锁,然后再由其它线程进行业务处理。
Redis缓存穿透,击穿,雪崩->解决方案
- 缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
- 缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
- 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
穿透方案解决:当第一次查询时不存在,也可以将空数据对应一个key缓存redis,可以适当设置过期时间,当第二次查询时走缓存即可.
击穿方案解决:可以使用redis互斥锁,setnx当key不存在的时候设置缓存。
缓存雪崩方案解决:可以使用随机时间,将每个缓存失效时间分布开。
Http和https说明
http:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
https:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
https协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
区别
http协议传输数据未加密,也就是明文传输,为了保证信息传输安全性,于是诞生了ssl协议用于对http协议传输的数据进行加密,从而有了https, HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
工作原理
(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信。
nacos心跳机制(客户端和服务端)
只有NACOS服务与所注册的Instance之间才会有直接的心跳维持机制,换言之,这是一种典型的集中式管理机制。
- 在client这一侧是心跳的发起源,进入NacosNamingService,可以发现,只有注册服务实例的时候才会构造心跳包:
- BeatReactor维护了一个Map对象,记录了需要发送心跳的BeatInfo,构造了一个心跳包后,BeatReactor.addBeatInfo方法将BeatInfo放入Map中。然后,内部有一个定时器,每隔5秒发送一次心跳。
- 只返回了一个心跳间隔时长,将这个返回值用于client设置定时任务间隔,同时将scheduled置为false,表示完成了此次心跳发送任务,可以进行下次心跳。NACOS接到心跳后,会有一段instance判空的逻辑,如果找不到对应的instance,就会直接创建出来,也就是默认相信心跳的请求源是合理的。
- 具体详情请参照该文章:https://zhuanlan.zhihu.com/p/72172391
Gateway网关如何实现负载均衡
gateway主要是做路由负载、过滤, 主要是替代zuul 1.x 性能比zuul好 zuul是基于Servlet ,gateway是基于spring-webflux 用的netty+reactor
- 工作原理:
- 客户端发送请求到Gateway
- Gateway Handler Mapping判断请求是否匹配某个路由
- 发送请求到Gateway Web Handler,执行该请求的过滤器。过滤器可以在请求之前和之后执行不同逻辑。
- 当所有的预(pre)过滤请求执行完后,创建代理请求,创建好了代理请求后,才执行post请求
- 使用rabbion实现
从exchange对象中获取gatewayRequestUrl,gatewaySchemePrefix,如果url为空或者url的schema不是lb并且gatewaySchemePrefix前缀不是lb,则进入下个过滤器,否则去获取真正的url
- 使用Ribbon负载时,choose(exchange)调用逻辑
- 调用ribbonLoadBalancerClient的choose()方法
- GetLoadBalancer根据服务id获取负载均衡器,调用获取getInstance()方法
- 先从负类获取获取容器,如果没有则放入缓存
- 先创建容器在从容器中获取服务,根据负载均衡算法获取服务,ribbon默认加载ZoneAvoidanceRule负载策略来实现。
具体描述请参照:https://www.cnblogs.com/ns-study/p/14649392.html
消息中间件
Rabbitmq执行流程:首先创建交换机,队列,死信队列,消息发起者发起消息向指定交换机发送,交换机推送消息到指定队列,然后进行消息消费,首先根据消息的确认机制确保消息的正确接收,然后执行业务,如果执行业务中发生失败即放入死信队列进行执行,如果执行业务成功即通知生产者执行成功,以上就是消息执行流程。
消息丢失问题
- 如果生产者丢失消息,首先要添加异步线程日志记录,发之前就要记录,防止在消息发送方丢失消息
- 如果消费者丢失消息,首先要确保消息正常接收到(这里使用消息的确认机制),接收到后执行业务如果出现问题即放入死信队列并做消息执行异常记录,然后进行消息重试机制,执行死信队列时判断消息是否被重试过并且完成的,然后再进行消息消费,最后修改数据库消息异常记录改为正常。
中间件流量消峰
应用解耦:当要调用远程系统时,当存在订单系统和库存系统时,订单系统下单,库存系统需要收到订单后库存减一,这时候如果系统宕机,会造成订单丢失,把订单消息发入mq,库存系统再去mq消费,就能解决这一问题。
异步消费:传统的模式:用户下单—>邮件发送—>短信提醒,三个步骤全部完成,才能返回用户消费成功,因为后面两个步骤完全没有必须是当前时间完成,可以用户下单成功后,直接发送给mq,返回给用户消费成功,之后邮件发送和短信提醒,可以其他时间段来消费发送给用户。
流量削峰:大型双11活动时候,晚上0点有上亿并发,这时候数据库并不能承载那么大的数据冲击,而专门为高并发设计的mq可以承受住海量的请求,发送给mq,存储成功后,再消费。
Mysql隔离级别
QL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
解释:就好比还没确定的消息,你却先知道了发布出去,最后又变更了,就是说瞎话了。常说的脏读,读到了还未提交的。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
解释:只能读取到已经提交的事务。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
编者按:幻读。读到已提交的数据。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
编者按:事务顺序执行,没有并行,完全杜绝幻读。
这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。读取到未提交事务的数据。读取阶段。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就有几列数据是未查询出来的,如果此时插入和另外一个事务插入的数据,就会报错。读取到新提交的数据,发生在插入阶段。
Security认证授权流程
Oauth2认证授权流程
授权码模式
三方对接明文信息如何保证安全性
使用SHA-256对称加密算法进行数据加密,然后再用RSA/AES进行数据加密
==和equals区别
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
Equals 用来比较的是两个对象的内容是否相等,由于所有的类都是继承java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
heap 和 stack 有什么区别
Java 的内存分为两类,一类是栈内存,一类是堆内存。
栈内存是指程序进入一个方法 时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量, 当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使 用 new 创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部 变量使用 final 修饰后,放在堆中,而不是栈中。
Java 集合类框架的基本接口有哪些?
Collection:代表一组对象,每一个对象都是它的子元素。
Set:不包含重复元素的 Collection。
List:有顺序的 collection,并且可以包含重复元素。
Map:可以把键 (key) 映射到值 (value) 的对象,键不能重复。
悲观锁与乐观锁
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中java.util.concurrent.atomic 包下面的原子变量类就是使用了 乐观锁的一种实现方式 CAS 实现的。
乐观锁常见的两种实现方式
乐观锁一般会使用版本号机制或 CAS 算法实现。
- 版本号机制
一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次
数,当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数
据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当
前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
Synchronized
- 对于资源竞争较少(线程冲突较轻)的情况,使用
synchronized
同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗 cpu 资源;而 CAS 基于硬件实现,不需要进入内核,不需要切换线程, 操作自旋几率较少,因此可以获得更高的性能。 - 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较 大,从而浪费更多的 CPU 资源,效率低于
synchronized
。
LOCK
Lock
接口提供了与synchronized
相似的同步功能,和synchronized
(隐式的获取和释放锁,主要体现在线程进入同步代码块之前需要获取锁退出同步代码块需要释放锁)不同的是,Lock
在使用的时候是显示的获取和释放锁。虽然Lock
接口缺少了synchronized
隐式获取释放锁的便捷性,但是对于锁的操作具有更强的可操作性、可控制性以及提供可中断操作和超时获取锁等机制。
常见的算法
冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序
Java种8中基本数据类型
byte(位)、short(短整数)、int(整数)、long(长整数)、float(单精度)、double(双精度)、char(字符)、boolean(布尔值)
Filebeat
Filebeat是用于转发和集中日志数据的轻量级传送工具。Filebeat监视您指定的日志文件或位置,收集日志事件,并将它们转发到Elasticsearch或 kafka进行索引。
logstash
是一款开源的数据收集引擎,具有实时管道处理能力
JVM调优
回收算法的特点,触发时机,gc时的效果
Tomcat调优
从内存(catalina.sh)、并发(server.xml)、缓存。
Spring的两个特性
IOC(控制反转)、AOP(面向切面,应用:加一些日志、在某个方法执行之前执行某个方法,拦截器)
ArrayList和LinkedList区别
ArrayList是实现了基于动态数组(1.5倍扩容)的数据结构,查找和遍历的效率高。而LinkedList是基于双向链表(每个节点即指向前面一个节点,也指向后面一个节点)的数据结构,增加和删除的效率高。
HashMap和HashTable
共同点:都是双列集合
HashMap:非线程安全,效率高,key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null,默认大小16,为2倍扩容。
HashTable:线程安全,效率低,key和value都不能为null,默认大小11,为2倍+1扩容
HashMap实现原理
JDK1.7中采用数组+链表的存储形式
JDK1.8中采用数组+链表+红黑树的存储形式。当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。红黑树是特殊的二叉查找树
Redis持久化有几种方式?
一种是写RDB文件方式,另一种是写AOF文件,默认执行的是RDB文件持久化方式。
Socket和RabbitMQ区别?
Socket:基于Netty,是长连接,一般用于聊天,即时通讯
RabbitMQ:AMQP协议,短连接,一般用于消息
数据库优化
1、表中字段设得尽可能小,字段尽量设置NOT NULL
2、使用连接(JOIN)代替子查询 in ,使用exists 代替in
3、使用索引,索引应建立在那些将用于JOIN,WHERE判断和ORDERBY排序的字段上
数据库加了索引为什么快?
索引的底层数据结构是B+树,可以将无序内容转换为有序的一个集合(相对),查询时会先去索引列表中一次定位到特定值的行数,大大减少遍历匹配的行数。类似于新华字典里的目录。
更多推荐
所有评论(0)