java面试常见八股文整理,2025年9月持续更新!
1. 多线程编程下,怎么解决线程的数据安全问题?
- 如果线程存在竞争临界资源,多线程访问下添加同步代码块synchronized解决,或者分布式排他锁进行临界资源控制。
- 在分布式多线程环境下,线程的数据安全尽量不要产生连接资源,使用线程本地化ThreadLocal实现线程资源隔离。
2.SpringIOC依赖注入怎么理解,spring有几种方式
- 属性注入,setter
- 构建pojo实体类和有参构造方法
- 工厂方法注入 (此种用的不多,扩展)
3.SQL注入是怎么产生的,怎么避免?
SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL
语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令或访问未被授权的数据。 SQL 注入的原理主要有以下 4 点:
- 恶意拼接查询 我们知道,SQL 语句可以查询、插入、更新和删除数据,且使用分号来分隔不同的命令。例如:
SELECT * FROM users WHERE user_id = $user_id
其中,user_id 是传入的参数,如果传入的参数值为“1234;DELETE FROM users”,那么最终的查询语句会变为:
SELECT * FROM users WHERE user_id = 1234; DELETE FROM users
如果以上语句执行,则会删除 users 表中的所有数据。- 利用注释执行非法命令。
SQL 语句中可以插入注释。例如:
SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version=$version
如果 version 包含了恶意的字符串’-1’ OR 3 AND SLEEP(500)–,那么最终查询语句会变为:
SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version='-1' OR 3 AND SLEEP(500)--
以上恶意查询只是想耗尽系统资源,SLEEP(500) 将导致 SQL 语句一直运行。如果其中添加了修改、删除数据的恶意指令,那么将会造成更大的破坏。- 传入非法参数
- 添加额外条件在 SQL
语句中添加一些额外条件,以此来改变执行行为。条件一般为真值表达式。例如:UPDATE users SET userpass='$userpass' WHERE user_id=$user_id;
如果 user_id 被传入恶意的字符串“1234 OR TRUE”,那么最终的 SQL 语句会变为:
UPDATE users SET userpass= '123456' WHERE user_id=1234 OR TRUE;
这将更改所有用户的密码。
避免SQL注入 对于 SQL 注入,我们可以采取适当的预防措施来保护数据安全。下面是避免 SQL 注入的一些方法。
- 过滤输入内容,校验字符串
- 参数化查询
参数化查询目前被视作是预防 SQL 注入攻击最有效的方法。参数化查询是指在设计与数据库连接并访问数据时,在需要填入数值或数据的地方,使用参数(Parameter)来给值。
MySQL 的参数格式是以“?”字符加上参数名称而成,如下所示:
UPDATE myTable SET c1 = ?c1, c2 = ?c2, c3 = ?c3 WHERE c4 = ?c4
在使用参数化查询的情况下,数据库服务器不会将参数的内容视为 SQL 语句的一部分来进行处理,而是在数据库完成 SQL 语句的编译之后,才套用参数运行。因此就算参数中含有破坏性的指令,也不会被数据库所运行。- 安全测试、安全审计
除了开发规范,还需要合适的工具来确保代码的安全。我们应该在开发过程中应对代码进行审查,在测试环节使用工具进行扫描,上线后定期扫描安全漏洞。通过多个环节的检查,一般是可以避免SQL 注入的。
有些人认为存储过程可以避免 SQL注入,存储过程在传统行业里用得比较多,对于权限的控制是有一定用处的,但如果存储过程用到了动态查询,拼接 SQL,一样会存在安全隐患。下面是在开发过程中可以避免 SQL 注入的一些方法。
- 避免使用动态SQL
避免将用户的输入数据直接放入 SQL 语句中,最好使用准备好的语句和参数化查询,这样更安全。- 不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据,这样可以提供了另一级保护,以防攻击者成功地排出敏感数据。- 限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。- 避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关数据库的信息。
4. MyBatis中#占位符和$占位符有什么区别?
关于#{}和${}的区别
- #{}是预编译处理,$ {}是字符串替换。
- MyBatis在处理#{}时,会将SQL中的#{}替换为?号,使用PreparedStatement的set方法来赋值;MyBatis在处理$ { } 时,就是把 ${ } 替换成变量的值。
- 使用 #{} 可以有效的防止SQL注入,提高系统安全性。 预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
业务场景:
select * from user where id= "1";
上述 sql 中,我们希望对应的id可以变化,根据不同的id查询出不同的学生,在 Mapper.xml文件中使用如下的 sql 可以实现动态传递参数 id:
关于#的使用
select * from user where id= #{id};
当传来参数id=2的时候,解析为:
select * from user where id= ?;关于$的使用
select * from user where id= ${id};
当传来参数id=2的时候,解析为:
select * from user where id = "2";
小结: ${ } 变量的替换阶段是在动态 SQL 解析阶段,而 #{ }变量的替换是在 DBMS 中。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
5.springboot和springcloud区别。 boot里的starter是什么,@Configuration作用
springboot和springcloud的区别主要是:
- 作用不同:前者的作用是为了提供一个默认配置,从而简化配置过程;后者的作用是为了给微服务提供一个综合管理框架。
- 使用方式不同:springboot可以单独使用;springcloud必须在springboot使用的前提下才能使用。SpringBoot专注于快速方便的开发单个个体微服务,SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并且管理起来,为各个服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、精选决策、分布式会话等集成服务。
springboot和springcloud都是从spring生态圈中衍生出来的软件开发框架,但是二者的创作初衷是完全不同的,springboot的设计目的是为了在微服务开发过程中可以简化配置文件,提高工作效率,而springcloud的设计目的是为了管理同一项目中的各项微服务,因此二者是完全不同的两个软件开发框架。
6.常用设计模式,解决什么问题:工厂、策略、责任链
一、工厂模式最主要解决的问题就是创建者和调用者的耦合,那么代码层面其实就是取消对new的使用。
工厂模式可以分为:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式又叫 静态方法模式,因为工厂类中定义了一个静态方法用于创建对象。简单工厂让使用者不用知道具体的参数就可以创建出所需的 ”产品“类,即使用者可以直接消费产品而不需要知道产品的具体生产细节。
工厂类负责创建的对象比较少:由于创建的对象比较少,不会造成工厂方法中业务逻辑过于复杂。
客户端只需知道传入工厂类静态方法的参数,而不需要关心创建对象的细节。工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象, 这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。抽象工厂模式(Abstract Factory Pattern),提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。
但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。二、策略模式可以解决在有多种算法相似的情况下,使用if…else 或swith…case所带来的复杂性和臃肿性。
在日常业务开发中,策略模式适用于以下场景:
- 针对同一类型问题,有多种处理方式,每一种都能独立解决问题;
- 算法需要自由切换的场景;
- 需要屏蔽算法规则的场景。 策略模式中的上下文环境 (Context) ,其职责本来是隔离客户端与策略类的耦合,让客户端完全与上下文环境沟通,无需关系具体策略。
策略模式的优缺点
优点 :
- 策略模式符合开闭原则。
- 避免使用多重条件转移语句,如 if…else… 语句、 switch 语句
- 使用策略模式可以提高算法的保密性和安全性。
缺点 :
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多策略类,增加维护难度。
三、责任链模式 (Chain of Responsibility Pattern) 是将链中每一个节点看作是一个对象,每个节点处理的清求均不同,且内部自动维护—个下—节点对象。当—个清求从链式的首端发出时,会沿看链的路径依次传递给每—个节点对象,直至有对象处理这个清求为止。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解的是其模式(道)而不是其具体实现(术),责任链模式的独到之处是其将节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动了起来。责任链模式的优缺点 优点:
- 将请求与处理解耦;
- 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转 发给下一级节点对象;
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
- 链路结构灵活,可以通过改变链路结构动态地新增或删减责任;
- 易于扩展新的请求处理类(节点),符合开闭原则。
缺点:
- 责任链太长或者处理时间过长,会影响整体性能
- 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;
7. 常用的注解有哪些,有什么作用
一.用于创建对象的注解
作用:和在xml配置文件中编写一个标签实现的功能一样。
- @Component : 用于把当前类对象存入Spring容器中。 属性:value — 用于指定bean的id。如果不写该属性,id的默认值是当前类名,且首字母改为小写。
- @Controller : 一般用在表现层。
- @Service : 一般用在业务层。
二.用于注入数据的注解
作用:和在xml配置文件中的标签中写一个标签的功能一样。
- @Autowired : 自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。可以作用在变量或者方法上。
- @Qualifier : 在按照类型注入的基础之上再按照名称注入,它在给类成员注入时要和@Autowired配合使用,但是在给方法参数注入是可以单独使用。
- @Resource : 直接按照bean的id注入,可以独立使用。 属性:name — 用于指定bean的id。 备注:以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现。
- @Value : 用于注入基本类型和String类型的数据。 属性:value — 用于指定数据的值,它可以使用Spring中的SpEL(也就是Spring中的el表达式)。SpEL的写法:${表达式}
三.用于改变作用范围的注解
作用:和在xml配置文件中的标签中使用scope属性实现的功能一样。
- @Scope : 用于指定bean的作用范围。 属性:value — 指定范围的取值。常用取值:singleton(单例)和prototype(多例)。
四.和生命周期相关的注解
作用:和在xml配置文件中的标签中使用init-method和destory-method属性实现的功能一样。
- @PreDestory : 用于指定销毁方法。
- @PostConstruct : 用于指定初始化方法。
五.Spring新注解
- @Configuration : 用于指定当前类是一个配置类。
- @ComponentScan : 用于通过注解指定Spring在创建容器时要扫描的包。
- @Bean : 用于把当前方法的返回值作为bean对象存入Spring的IOC容器中。 属性:name — 用于指定bean的id。当不写时,默认值为当前方法的名称。
- @Import : 用于导入其他的配置类。 属性:value — 用于指定其他配置类的字节码。当我们使用@Import注解时,有@Import注解的类就是父配置类。
- @PropertySource : 用于指定properties文件的位置。 属性:value — 指定文件的名称和路径。关键字classpath表示类路径下。
- @Entity:@Table(name=”“):表明这是一个实体类。一般用于jpa这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略
8. JVM调优的目标是什么
JVM调优的主要目的:减少full gc、降低gc停顿时间、提高吞吐量;调优的顺序=“提高吞吐量”>“降低gc停顿时间”;在满足吞吐量的前提下,再降低gc停顿时间;若不能同时满足以上,则选择最适合系统的一种调优结果
一般JVM调优,重点在于调整JVM堆大小、调整垃圾回收器.
JVM常用命令:
- jstat命令用于监视虚拟机的运行状态。命令格式【jstat -参数命令 进程号 间隔毫秒数 总输出次数】
- jinfo可以查看启动jar包时未显式指定的系统默认值,以及动态修改这些默认值。命令格式【jinfo -flag 默认的参数 进程id】
- jmap查看java内存信息。命令格式【jmap -参数 进程id】
- jstack可以查看正在运行的线程的堆栈信息,比如查看后天没有相应的线程在做些什么,在等待什么资源。命令格式【jstack 参数项 进程号】
常见的调优策略
- 减少创建对象的数量。
- 减少使用全局变量和大对象。
- 调整新生代、老年代的大小到最合适。
- 选择合适的GC收集器,并设置合理的参数。
微服务体系栈
1. SpringCloud是什么/Cloud Alibaba是什么,有哪些组成与功能
注册中心/RPC/限流熔断、网关/接口治理、配置、事件总线;监控配套:调用链、性能监控;
spring cloud五大组件分别为:服务发现–Netflix Eureka 主要提供服务的自动注册和发现
客户端负载均衡–Netflix Ribbon
断路器–Netflix Hystrix
服务网关–Netflix Zuul
分布式配置–Spring Cloud Config此外Ribbon是和Feign以及Eureka紧密协作的,具体如下:
首先Ribbon会从Eureka Client里获取到对应的服务注册表,知道所有的服务都部署在了哪些机器上,在监听哪些端口号。
然后Ribbon就使用默认的Round Robin轮询算法,从中选择一台机器;
最后Feign就会针对这台机器,构造并发起请求。
微服务的框架一般都会分为以下一个大部分:
微服务网关-负责请求转发,路由,权限控制
注册、配置中心–服务发现和注册
客户端负载器—负责微服务内部的负载
限流控制–负责微服务调用的限流和流量控制
2. 了解或者使用过哪些主流厂商微服务框架
蚂蚁SOFA、阿里EDAS、腾讯TSF、京东JSF
3. 对于微服务的降级,限流,熔断怎么理解,项目中怎么使用的,都解决什么问题
熔断和降级的区别?
相同点
都是为了保证服务的可用性,防止系统发生崩溃
都导致了系统的某些服务、功能不可用
不同点
熔断是由某个下游服务故障引起的,降级一般从系统的整体负荷去考虑
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
服务熔断解决如下问题:
- 当所依赖的对象不稳定时,能够起到快速失败的目的;
- 快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复。
自动降级分类
1)超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
2)失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
3)故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
4)限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)
4. 对于RPC技术原理的理解,RPC整体调用过程
RPC 框架----- 远程过程调用协议RPC(Remote Procedure Call Protocol)-----允许像调用本地服务一样调用远程服务。
总的来说可以归纳为以下几步:
1,远程服务之间建立通讯协议
2,寻址:服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么
3,通过序列化和反序列化进行数据传递
4,将传递过来的数据通过java反射原理定位接口方法和参数
5. 微服务之前的循环依赖问题怎么产生的?有什么解决方案。
循环依赖的坏处
1、服务功能不内聚,耦合严重,导致研发效率低下
假设A依赖B,B又依赖A,会导致每次A和B部署的时候必须耦合,部署了A就得部署B,不然就会报错;
上面这些还不算严重的,比较麻烦的是A升级或者改了些接口,B要测试下;反过来B改了接口A也要测试下,浪费研发和测试资源;
2、增加了系统的复杂性,很容易出错
一些线上操作升级可能也会比较麻烦,像我们在升级JDK8的时候就必须梳理各种依赖关系,梳理不当就可能会有线上故障;
3、系统的扩展性受到限制
这里的扩展性分系统和业务2方面,系统上来说假如大促我要升级扩容,如果正常不依赖其它应用,或者只依赖少量应用,只升级自己就可以了,如果依赖了其它应用,还得考虑其它应用的容量;还涉及到部署的情况,当你的业务做的很大,以前只是垂直应用,现在升级成平台型应用的时候,对部署有了更高的要求,可能要单独的隔离环境进行部署,如果依赖了不依赖的应用,会使这些几乎不可能。
业务扩展性也是一样的,如果业务发展比较快,要支持更多的场景,那就必须考虑新的业务和这些依赖的应用是否兼容,增加研发成本,也不利于扩展。
如何避免循环依赖
按上面的情况,如果应用A依赖B,B依赖A,要做到不循环依赖,有以下办法:
1、改为异步调用
微服务解耦,做到服务自治,可以发送MQ消息,这样发送方就不用关心消费方的存在了。
2、微服务分层建设
对微服务进行分层规划,上层微服务可以调用下层微服务,下层微服务禁止调用上层微服务,把公用的部分沉淀下放到基础微服务层。
分布式体系栈
1. 分布式数据框架,分片规则与扩容,分片访问机制
- 数据分片的主要目标是,将一张数据表切分成较小的片,不同片存储到不同服务器上面去,通过分片的方式使用多台服务器存储一张数据表,避免一台服务器记录存储处理整张数据表带来的存储及访问压力。
- 数据分片的特点是,数据服务器之间相互独立,不共享任何信息,及时有部分服务器故障了,也不影响整个系统的可用性。另一个特点是通过分片键来定位分片,一个分片存储到哪个服务器上面去,到哪个服务器上面去查找,是通过分片键进行路由分区算法计算出来的。在SQL语句里面,只要包含了分片键,就可以访问特定的服务器,而不需要连接所有的服务器,跟其他的服务器进行通信。
- 数据分片的原理是,将数据以某种方式进行切分,通常是用分片键的路由算法进行计算,使每天服务器都只存储一部分数据。
- 数据分片的实现,现在有一个专门的分布式数据中间件,来做数据分片的工作Mycat。Mycat是一个专门的分布式数据库中间件,应用程序像连接数据库一样连接Mycat。而数据分片的操作完全交给Mycat去完成。例如,有3个分片数据库服务器-数据库服务器dn1、dn2、dn3,它们的分片规则是根据prov字段进行分片。那么我们执行一个查询操作“seletc * from orders where prov=wuhan”的时候,Mycat会根据分片规则将这条SQL操作路由到dn1这台服务器节点上。dn1执行数据数据查询操作返回结果后,Mycat再返回给应用程序。通过Mycat这样的分布式数据库中间件,无感知的使用分片数据库。同时Mycat还一定程度上支持分片数据库的联合join查询以及数据事务。
- 分片数据库的伸缩扩容
下面来看下分片数据库如何进行扩容伸缩,对于一个新业务刚开始的时候数据量不是很多,两个数据库服务器就够了,但是随着数据的不断增长,可能需要增加第三个、第四个、第五个,甚至更多的服务器。在增加服务器的过程中,分片规则需要改变。通常的做法是数据分片使用逻辑数据库,也就是说一开始虽然两个服务器就可以完成数据分片存储,但是依然在逻辑上把它切分为多个逻辑数据库。例如,我们将数据库切分为32个逻辑数据库,但是开始的时候只有两个物理服务器,我们把32个数据库分别启动在两个物理服务器上。那么路由算法就还是按照32进行路由分区,数据分片也是32片。当需要扩容的时候,只需要把这些逻辑数据库迁移到其他的物理服务器上,就可以完成扩容。因为迁移后数据分片还是32片,数据分片的算法不需要改变。数据迁移也仅仅是将逻辑数据库迁移到新的服务器上面去,而这种迁移通过数据库的主从复制就可以完成。
2. 对于分布式事务理解,有几种方案,具体落地实施。
事务
严格意义上的事务实现应该是具备原子性、一致性、隔离性和持久性,简称 ACID。
分布式事务
分布式事务顾名思义就是要在分布式系统中实现事务,它其实是由多个本地事务组合而成。
对于分布式事务而言几乎满足不了 ACID,其实对于单机事务而言大部分情况下也没有满足 ACID,不然怎么会有四种隔离级别呢?所以更别说分布在不同数据库或者不同应用上的分布式事务了。
2PC
2PC(Two-phase commit protocol),中文叫二阶段提交。 二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角>色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。
3PC
3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。
3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit。
TCC
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了!
TCC 指的是Try - Confirm - Cancel。
Try 指的是预留,即资源的预留和锁定,注意是预留。
Confirm 指的是确认操作,这一步其实就是真正的执行了。
Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。
还有一点要注意,撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等。
相对于 2PC、3PC ,TCC 适用的范围更大,但是开发量也更大,毕竟都在业务上实现,而且有时候你会发现这三个方法还真不好写。不过也因为是在业务上实现的,所以TCC可以跨数据库、跨不同的业务系统来实现事务。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
消息事务
RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。
再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
最大努力通知
本地消息表也可以算最大努力,事务消息也可以算最大努力。
就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了
事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。
所以最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
适用于对时间不敏感的业务,例如短信通知。
3. 分布式调度,与Quart区别,运行机制。
quartz
支持集群和分布式,可是没有友好的管理界面,功能单一,对于管理调用的任务比较困难。数据库
quartz使用数据库锁。在quartz的集群解决方案里有张表scheduler_locks,quartz采用了悲观锁的方式对triggers表进行行加锁,以保证任务同步的正确性。一旦某一个节点上面的线程获取了该锁,那么这个Job就会在这台机器上被执行,同时这个锁就会被这台机器占用。同时另一台机器也会想要触发这个任务,可是锁已经被占用了,就只能等待,直到这个锁被释放。
quartz的分布式调度策略是以数据库为边界资源的一种异步策略。各个调度器都遵照一个基于数据库锁的操做规则从而保证了操做的惟一性。同时多个节点的异步运行保证了服务的可靠。但这种策略有本身的局限性:集群特性对于高CPU使用率的任务效果很好,可是对于大量的短任务,各个节点都会抢占数据库锁,这样就出现大量的线程等待资源。这种状况随着节点的增长会愈来愈严重。服务器
缺点:quartz的分布式只是解决了高可用的问题,并无解决任务分片的问题,仍是会有单机处理的极限。
xxl-job
轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。负载均衡
主要特性框架:简单:支持经过Web页面对任务进行CRUD操做,操做简单,一分钟上手;
调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会从新分配任务;
分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"状况下,一次任务调度将会广播触发>集群中全部执行器执行一次任务,可根据分片参数开发分片任务;
动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增长分片数量,>协同进行业务处理;在进行大数据量业务操做时可显著提高任务处理能力和速度。
动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
一致性:“调度中心”经过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
其余特性运维
路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性>HASH、最不常用、最近最久未使用、故障转移、忙碌转移等;
故障转移:任务路由策略选择"故障转移"状况下,若是执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖以前调度;
任务超时控制:支持自定义任务超时时间,任务运行超时将会主动中断任务;
任务失败重试:支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;其中分片任务支持分片粒度的失败重试;
任务失败告警;默认提供邮件方式失败告警,同时预留扩展接口,可方面的扩展短信、钉钉等告警方式;
事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行以外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
任务进度监控:支持实时监控任务进度;
脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS、PHP、PowerShell等类型脚本;
调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
数据加密:调度中心和执行器之间的通信进行数据加密,提高调度信息安全性;
邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行;
功能齐全,文档也很齐全,有使用教程,可是上手门槛稍微高了;
Elastic-Job
分布式调度解决方案,由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成。
Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务。
Elastic-Job-Cloud使用Mesos + Docker的解决方案,额外提供资源治理、应用分发以及进程隔离等服务。
轻量级无中心化:Elastic-Job-Lite并没有做业调度中心节点,而是基于部署做业框架的程序在到达相应时间点时各自触发调度。
灵活的增删改查做业,集中式管理调度做业
支持高可用:一旦执行做业的服务器崩溃,等待执行的服务器将会在下次做业启动时替补执行。开启失效转移功能效果更好,能够保证在本次做业执行时崩溃,备机当即启动替补执行。
支持分片:做业分片一致性,保证同一分片在分布式环境中仅一个执行实例
任务监控:经过监听Elastic-Job-Lite的zookeeper注册中心的几个关键节点便可完成做业运行状态监控功能
一致性:使用zookeeper做为注册中心,为了保证做业的在分布式场景下的一致性,一旦做业与注册中心没法通讯,运行中的做业会马上中止执行,但做业的进程不会退出,这样作的目的是为了防止做业重分片时,将与注册中心失去联系的节点执行的分片分配给另外节点,致使同一分片在两个节点中同时执行。
同时支持动态扩容,将任务拆分为n个任务项后,各个服务器分别执行各自分配到的任务项。一旦有新的服务器加入集群,或现有服务器下线,elastic-job将在保留本次任务执行不变的状况下,下次任务开始前触发任务重分片
4. 分布式CAP定理-BASE理论理解
CAP是分布式系统的指导理论,是NoSQL数据库的理论基石。CAP其实就是对分布式系统的特性总结,即一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。
分区容错性,就是CAP中的P,这一项其实是必选项,CAP主要用在分布式系统中,所谓分布式就表示有多个服务器或节点,比如数据库的主从配置。如果不要P,那只是个单机服务,就不需要CAP来指导了。
数据一致性,就是CAP中C,这一项表示分布式系统中的每个服务或节点对外提供的数据必须得一致,比如数据在A数据库被修改了,必须立刻同步至其他(比如B)数据库服务器上。客户端访问B数据库时必须是最新的数据,否则不提供服务。
可用性,就是CAP中A,搞分布式系统最主要的目的是实现高可用,即一台服务器崩了,还有其他节点继续提供服务。同时还能实现数据的安全,比如数据库同步备份等。
其实同时实现数据一致性和可用性是互相矛盾的,想实现可用性,就要放弃数据一致性。分区容错性是必选的,所以留给我们的选项只有CP或AP。
CP,即实现一致性和分区容错性,此组合为数据强一致性模式,即要求多服务之间数据一定要一致,牺牲了可用性,比如操作A服务时,就要让其他服务不可用,一些对数据要求比较高的场景使用此方式,比如涉及金钱等。这种模式性能很低。实现强一致性的方案有2PC(两阶段提交)。
AP,即实现可用性和分区容错性,此组合为数据最终一致性模式,即要求所有服务都可用,牺牲了数据一致性,比如操作A服务修改数据,同时读取B服务查询时发现数据还是旧数据,但过一会数据就会一致,互联网分布式服务多数基于AP,Base模式也是基于AP组合。实现最终一致性的方案有TCC、消息队列、Saga等,其中消息队列用的最多。
BASE,即Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)。它是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论。互联网中对可用性要求非常高,但对一致性要求要少点,比如发一个消息给用户,用户不用立即收到,晚个一两秒也OK的。所以可以牺牲CAP中C,BASE理论大部分是对AP的补充和权衡。
Basically Available(基本可用),它是要求分布式系统中所有节点要做到高可用。
Soft State(软状态),它定义数据可以有一个中间状态,比如服务A更新数据,读取B服务时,允许存在一定的延迟,客户端读取的为中间状态。
Eventually Consistent(最终一致性),它允许数据存在中间状态,做不到强一致性,但要做到最终一致性,比如消息列表多刷几次,总归能刷出来。
5. 分布式序列:生成策略,全局唯一如何保证。
序列常用算法
UUID组成:
标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。
优点:
- 本地生成,无依赖,速度极快,序号个数无限。
- 序号全局唯一性。
不足:- 序列无规律,对索引不友好。
- 字段串由数字与字母成合,存储空间大。
雪化算法(snowflake)
优点:
- 本地生成,无依赖。
- 整型,趋势递增,有利于索引。
- 效率较高,理论每节点每秒最大能产生26万ID。
不足:
- 依赖机器时间,如果发生时钟回拨会导致可能生成id重复。
- workid节点有限,无存储容易碰撞,在超大服务集群使用有风险。
基于DB数据段算法
利用DB表的存储机制,表主要字段有:命名空间、当前最大值、步长、版本号。通过步长的大小及利用缓存数据段来控制,解决DB的性能瓶颈,提高效率。
优点:
- 高性能,序列无极限(取决于步长长度)。
- 整型,有顺序,趋势递增,有利于索引。
- 可以自定义value的大小,非常方便业务从原有的ID方式迁移过来。
不足:
- 服务强依赖DB,来存储业务场景、最大值、步长等信息。
- 中心化部署,序列号段缓存服务节点,对序列服务可靠性要求极高。
序列部署方式(中心化 & 分布式)
中心化部署:
中心化部署是指:序列以微服务的方式单部署,连接独立的序列库。提供Http、RPC、restful等协议的接口,给各应用调用并返回序列或号段。
分布式部署:
分布式部署是指:序列以组件JAR的方式,应用有机的结合一起部署,把序列的相关表,与应用的数据表放一个库,共用数据源。
目前的解决方案:都是在snowflake、基于DB数据段两大基础算法及结合自身的特点做优化。
中间件体系栈
1. 用过哪些中间件,什么作用,完成什么业务?
网关:Nginx、Kong、Zuul
缓存:Redis、MemCached、OsCache、EhCache
搜索:ElasticSearch、Solr
熔断:Hystrix、resilience4j
负载均衡:DNS、F5、LVS、Nginx、OpenResty、HAproxy
注册中心:Eureka、Zookeeper、Redis、Etcd、Consul
认证鉴权:JWT、SpringSecurity
消费队列:RabbitMQ、Kafka、RocketMQ、ActiveMQ、Redis
系统监控:Grafana、Prometheus、Influxdb、Telegraf、Lepus
文件系统:OSS、NFS、FastDFS、MogileFS
RPC框架: Dubbo、Motan、Thrift、grpc
构建工具:Maven、Gradle
集成部署:Docker、Jenkins、Git、Maven
分布式配置:Disconf、Apollo、Spring Cloud Config、Diamond
压测:LoadRunner、JMeter、AB、webbench
数据库:MySQL、Redis、MongoDB、PostgreSQL、Memcache、HBase
网络:专用网络VPC、弹性公网IP、CDN
数据库中间件:DRDS、Mycat、360 Atlas、Cobar
分布式框架:Dubbo、Motan、Spring-Could
分布式任务:XXL-JOB、Elastic-Job、Saturn、Quartz
分布式追踪:Pinpoint、CAT、zipkin
分布式日志:elasticsearch、logstash、Kibana 、redis、kafka
版本发布:蓝绿部署、A/B测试、灰度发布/金丝雀发布
2. redis:数据一致性,雪崩击穿穿透。
Redis概述:
redis是一个内存数据库, 因此数据基本上都存在于内存当中
但是Redis会定时以追加或者快照的方式刷新到硬盘中.
由于redis是一个内存数据库, 所以读取写入的速度是非常快的, 所以经常被用来做数据, 页面等的缓存。
redis保证缓存一致性:
- 先更新数据库,再删除缓存
这种方式可能存在以下两种异常情况
更新数据库失败,这时可以通过程序捕获异常,直接返回结果,不再继续删除缓存,所以不会出现数据不一致的问题
更新数据库成功,删除缓存失败。导致数据库是最新数据,缓存中的是旧数据,数据不一致
第2种情况应该怎么办呢?我们有两种方式:失败重试和异步更新。
(1)失败重试
如果删除缓存失败,我们可以捕获这个异常,把需要删除的 key 发送到消息队列。自己创建一个消费者消费,尝试再次删除这个 key,直到删除成功为止。
这种方式有个缺点,首先会对业务代码造成入侵,其次引入了消息队列,增加了系统的不确定性。
(2)异步更新缓存
因为更新数据库时会往 binlog 中写入日志,所以我们可以启动一个监听 binlog变化的服务(比如使用阿里的 canal开源组件),然后在客户端完成删除 key 的操作。如果删除失败的话,再发送到消息队列。
总之,对于删除缓存失败的情况,我们的做法是不断地重试删除操作,直到成功。无论是重试还是异步删除,都是最终一致性的思想。- 先删除缓存,再更新数据库
这种方式可能存在以下两种异常情况
删除缓存失败,这时可以通过程序捕获异常,直接返回结果,不再继续更新数据库,所以不会出现数据不一致的问题
删除缓存成功,更新数据库失败。在多线程下可能会出现数据不一致的问题
这时,Redis中存储的旧数据,数据库的值是新数据,导致数据不一致。这时我们可以采用延时双删的策略,即更新数据库数据之后,再删除一次缓存。
一、缓存穿透(查不到)
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于 是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是 都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
- 布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则 丢弃,从而避免了对底层存储系统的查询压力;- 缓存空对象
当缓存层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
二、缓存击穿(量太大,缓存过期)
大量用户请求去请求同一个东西,这个时候缓存某个key设置的过期时间是60s,60.1s缓存恢复了,在这0.1s的时间内,大量的请求就跑到了MySQL关系型数据库里面了,容易使数据库宕机,这就是击穿!
这里需要注意和缓存穿透的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一 个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访 问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。解决方案
- 设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。- 加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大!
三、缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。(比如说停电了,Redis宕机!)
产生雪崩的原因之一,比如马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然 形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
解决方案
- redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续 工作,其实就是搭建的集群。(异地多活!)- 限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。- 数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
3. redis:集群高可用原理,集群节点故障转移过程。
实现redis高可用机制的一些方法:
保证redis高可用机制需要redis主从复制、redis持久化机制、哨兵机制、keepalived等的支持。
redis主从复制原理
首先主从复制需要分为两个角色:master(主) 和 slave(从) ,注意:redis里面只支持一个主,不像Mysql、Nginx主从复制可以多主多从。
(1)redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
(2)通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。
Redis主从复制可以根据是否是全量分为全量同步和增量同步,Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。
故障转移
redis集群实现了高可用,当集群内少许节点出现故障时,经过故障转移能够保证集群正常对外提供服务。当集群里某个节点出现了问题,redis集群内的节点经过ping pong消息发现节点是否健康,是否有故障,其实主要环节也包括了:主观下线和客观下线;主观下线:指某个节点认为另外一个节点不可用,即下线状态,固然这个状态不是最终的故障断定,只能表明这个节点自身的意见,也有可能存在误判;
下线流程: A,节点a发送ping消息给节点b ,若是通讯正常将接收到pong消息,节点a更新最近一次与节点b的通讯时间; B,若是节点a与节点b通讯出现问题则断开链接,下次会进行重连,若是一直通讯失败,则它们的最后通讯时间将没法更新; 节点a内的定时任务检测到与节点b最后通讯时间超过cluster_note-timeout时,更新本地对节点b的状态为主观下线(pfail) 客观下线:指真正的下线,集群内多个节点都认为该节点不可用,达成共识,将它下线,若是下线的节点为主节点,还要对它进行故障转移 假如节点a标记节点b为主观下线,一段时间后节点a经过消息把节点b的状态发到其它节点,当节点c接受到消息并解析出消息体时,会发现节点b的pfail状态时,会触发客观下线流程; 当下线为主节点时,此时redis集群为统计持有槽的主节点投票数是否达到一半,当下线报告统计数大于一半时,被标记为客观下线状态。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
4. MQ:消息确认送达机制,高可用架构的原理
MQ要想尽量消息必达,架构上有两个核心设计点:
(1)消息落地
(2)消息超时、重传、确认
MQ消息投递
上半场MQ-client-sender到MQ-server流程:
(1)MQ-client将消息发送给MQ-server(此时业务方调用的是API:SendMsg)
(2)MQ-server将消息落地,落地后即为发送成功
(3)MQ-server将应答发送给MQ-client(此时回调业务方是API:SendCallback)
MQ消息投递下半场,MQ-server到MQ-client-receiver流程:
(1)MQ-server将消息发送给MQ-client(此时回调业务方是API:RecvCallback)
(2)MQ-client回复应答给MQ-server(此时业务方主动调用API:SendAck)
(3)MQ-server收到ack,将之前已经落地的消息删除,完成消息的可靠投递
MQ高可用架构的原理
RocketMQ要保证高可用的话,可以使用主从架构,也就是将Broker分为Master Broker和Slave Broker。
与其他传统的主从架构一样,Master负责接收数据之后,再将数据同步给Slave,Master和Slave都会向NameServer注册路由信息,同时每隔30秒发送一次心跳。RocketMQ主从架构数据同步采用的是Pull模式,Slave不停的发送请求到Master上去拉取消息
与其他传统的主从架构不一样的是,RocketMQ的主从架构并不是纯粹的读写分离。写的话还是依靠Master,而读的话,既可能是从Master读,也可能是从Slave读,这取决于Master
读消息的时候,第一次是从Master上去读的,之后是去Master还是Slave上读消息,是由Master根据自身的负载情况和Slave的数据同步情况来向消费者建议。如果Master的写负载已经很高的话,那么就会建议消费者下次拉取消息的话就去Slave上拉取;如果Slave数据同步较慢,还没有完全同步Master的消息的话,那么下一次还是会来Master上拉取消息
当Master节点宕机之后,一般来说会对Slave节点进行选举然后选出新的Master继续提供服务。
5. MQ:怎么保证消息的一致性,不重复消费。
MQ消息一致性
1、事务消息与普通消息的区别就在于消息生产环节,生产者首先预发送一条消息到MQ(这也被称为发送half消息)
2、MQ接受到消息后,先进行持久化,则存储中会新增一条状态为待发送的消息
3、然后返回ACK给消息生产者,此时MQ不会触发消息推送事件
4、生产者预发送消息成功后,执行本地事务
5、执行本地事务,执行完成后,发送执行结果给MQ
6、MQ会根据结果删除或者更新消息状态为可发送
7、如果消息状态更新为可发送,则MQ会push消息给消费者,后面消息的消费和普通消息是一样的
注意点:由于MQ通常都会保证消息能够投递成功,因此,如果业务没有及时返回ACK结果,那么就有可能造成MQ的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。
事务消息,producer不会因为consumer消费失败而做回滚,采用事务消息的应用,其所追求的是高可用和最终一致性,消息消费失败的话,MQ自己会负责重推消息,直到消费成功。因此,事务消息是针对生产端而言的,而消费端,消费端的一致性是通过MQ的重试机制来完成的。
解决消息重复消费,其实就是保证消息的消费幂等性。利用数据库的唯一约束
在进行消息消费,需要取一个唯一个标识,比如 id 作为唯一约束字段,先添加数据,如果添加失败,后续做错误提示,或者不做后续操作。
1.Redis 设置全局唯一id
每次生产者发送消息前设置一个全局唯一id放在消息体重,并存放的 redis 里,在消费端接口上先找在redis 查看是否存在全局id,如果存在,调用消费接口并删除全局id,如果不存在,不做后续操作。
2.多版本(乐观锁)机制
给业务数据添加一个版本号,每次更新数据前,比如当前版本和消息中的版本是否一致,如果一致就更新数据并且版本号+1,如果不一致就不跟新。这有点类似乐观锁处理机制。
数据库能力栈
1. 数据库设计的第一范式,第二范式和第三范式
第一范式
无重复的列
数据库表的每一列都是不可分割的原子数据项,而不能是集合,数组,记录等非原子数据项。如果实体中的某个属性有多个值时,必须拆分为不同的属性
在任何一个关系数据库中,第一范式(1NF)是对关系模式的设计基本要求,一般设计中都必须满足第一范式(1NF)。不过有些关系模型中突破了1NF的限制,这种称为非1NF的关系模型。换句话说,是否必须满足1NF的最低要求,主要依赖于所使用的关系模型。
第二范式
属性完全依赖于主键
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。
当存在多个主键的时候,才会发生不符合第二范式的情况。比如有两个主键,不能存在这样的属性,它只依赖于其中一个主键,这就是不符合第二范式。
如果存在不符合第二范式的情况,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。
第三范式
属性不能传递依赖于主属性(属性不依赖于其它非主键属性)
第三范式(3NF)是在第二范式(2NF)的基础上建立起来的,即满足第三范式(3NF)必须先满足第二范式(2NF)。
如果某一属性依赖于其他非主键属性,而其他非主键属性又依赖于主键,那么这个属性就是间接依赖于主键,这被称作传递依赖于主属性。
2. 数据库执行计划怎么查看,需要关注哪些?
什么是数据库执行计划:
利用一个SQL语句, 你可能要Server取出所有News表中的信息. 当Server收到的这条SQL的时候, 第一件事情并不是解析它. 如果这条SQL没有语法错误, Server才会继续工作. Server会决定最好的计算方式. Server会选择, 是读整个News表好呢, 还是利用索引会比较快些. Server会比较所有可能方法所耗费的资源. 最终SQL语句被物理性执行的方法被称做执行计划或者是查询计划.
数据库执行计划怎么查看?(使用PL/SQL、navicat等客户端工具查看)
- 通过SQL*PLUS中的autotrace命令查看
(1)set autotrace on --(得到执行计划,并输出结果)
(2)set autotrace traceonly --(得到执行计划,但不输出结果)
(3)set autotrace traceonly explain --(得到执行计划,不输出统计信息和结果,仅展现执行计划部分)
(4)set autotrace traceonly statistics --(不输出执行计划和结果,仅展现统计信息)- explain plan for获取
执行步骤如下:
SQL> set linesize 200;
SQL> set pagesize 2000;
SQL> explain plan for
select * from emp; --要解析的SQL脚本
Explained
SQL> select * from table(dbms_xplan.display());
查询执行计划需要关注哪些参数?
1)基数(Rows):Oracle估计的当前操作的返回结果集行数
2)字节(Bytes):执行该步骤后返回的字节数
3)耗费(Cost)、CPU耗费:Oracle估计的该步骤的执行成本,用于说明SQL执行的代价,理论上越小越好(该值可能与实际有出入)
4)时间(Time):Oracle估计的当前操作所需的时间
5)访问谓词:where后面的查询条件
3. 做过什么SQL优化
sql优化的方法:
1.SQL语句中IN包含的值不应过多,in 和 not in 也要慎用,否则会导致全表扫描
2.SELECT语句务必指明字段名称(SELECT增加很多不必要的消耗(CPU、IO、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新)
3.当只需要一条数据的时候,使用limit 1
4.如果排序字段没有用到索引,就尽量少排序
5.如果限制条件中其他字段没有索引,尽量少用or
6.尽量用union all代替union(union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据)
7.使用合理的分页方式以提高分页的效率
8.避免在where子句中对字段进行null值判断(对于null的判断会导致引擎放弃使用索引而进行全表扫描)
9.不建议使用%前缀模糊查询(LIKE“%name”或者LIKE“%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”)
10.避免在where子句中对字段进行表达式操作(比如:select user_id,user_project from user_base where age2=36;)
11.避免隐式类型转换
12.对于联合索引来说,要遵守最左前缀法则
13.避免频繁创建和删除临时表,以减少系统表资源的消耗
4. Oracle 分区是什么
什么是分区
分区的实质是把一张大表的数据按照某种规则使用多张子表来存储。然后这多张子表使用统一的表名对外提供服务,子表实际对用户不可见。类似于在多张子表上建立一个视图,然后用户直接使用该视图来访问数据。
为什么要使用分区
当一张表的数据量到达上亿行的时候,表的性能会严重降低,这个时候就需要用到分区了,通过划分成多个小表,并在每个小表上建立本地索引可以大大缩小索引数据文件的大小,从而更快的定位到目标数据来提升访问性能。
分区除了可以用来提升访问性能外,还因为可以指定分区所使用的表空间,因此也用来做数据的生命周期管理。当前需要频繁使用的活跃数据可以放到访问速度更快但价格也更贵的存储设备上,而2、3年前的历史数据,或者叫冷数据可以放到更廉价、速度更低的设备上。从而降低存储费用。
简单分区
常用的分区方法有RANGE、HASH、LIST 3 种。
range分区通常用来对时间或数字进行分区,list分区用来把不相关的数据组织到一起,hash让数据均匀的分布到各个分区中。另外为了方便对范围分区进行管理,oracle还提供了range分区的增强类型interval(间隔)分区,当有新写入的数据的分区值超过了当前分区范围的最大值的时候,它会以分区范围的当前最大值为基数,以指定的interval 为区间,自动创建一个新分区.
更多推荐
所有评论(0)