1. Java线程start方法和run方法的区别
- start方法启动了一个新的线程,而run方法不能启动一个新线程,还是在main线程下运行,程序依然是主线程一个线程在运行。
- 调用start方法可以启动线程,而run方法只是thread的一个普通方法还是在主线程中执行。
- 通过start()方法来启动的新线程,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。start()不能被重复调用。而run方法能被重复调用,因为它就是一个普通的成员方法。
- start方法在执行线程体中代码时,在不执行完的情况下可以进行线程切换,而run方法不能,run方法只能进行顺序执行。
2. 多线程编程下,怎么解决线程的数据安全问题?
- 如果线程存在竞争临界资源,多线程访问下添加同步代码块synchronized解决,或者分布式排他锁进行临界资源控制。
- 在分布式多线程环境下,线程的数据安全尽量不要产生连接资源,使用线程本地化ThreadLocal实现线程资源隔离。
3.SpringIOC依赖注入怎么理解,spring有几种方式
- 属性注入,setter
- 构建pojo实体类和有参构造方法
- 工厂方法注入 (此种用的不多,扩展)
4.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 语句一直运行。如果其中添加了修改、删除数据的恶意指令,那么将会造成更大的破坏。- 传入非法参数
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
- 添加额外条件在 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 语句中,最好使用准备好的语句和参数化查询,这样更安全。- 不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据,这样可以提供了另一级保护,以防攻击者成功地排出敏感数据。- 限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。- 避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关数据库的信息。
5. 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 中。
6.springboot和springcloud区别。 boot里的starter是什么,@Configuration作用
springboot和springcloud的区别主要是:
- 作用不同:前者的作用是为了提供一个默认配置,从而简化配置过程;后者的作用是为了给微服务提供一个综合管理框架。
- 使用方式不同:springboot可以单独使用;springcloud必须在springboot使用的前提下才能使用。SpringBoot专注于快速方便的开发单个个体微服务,SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并且管理起来,为各个服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、精选决策、分布式会话等集成服务。
springboot和springcloud都是从spring生态圈中衍生出来的软件开发框架,但是二者的创作初衷是完全不同的,springboot的设计目的是为了在微服务开发过程中可以简化配置文件,提高工作效率,而springcloud的设计目的是为了管理同一项目中的各项微服务,因此二者是完全不同的两个软件开发框架。
7.常用设计模式,解决什么问题:工厂、策略、责任链
一、工厂模式最主要解决的问题就是创建者和调用者的耦合,那么代码层面其实就是取消对new的使用。
工厂模式可以分为:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式又叫 静态方法模式,因为工厂类中定义了一个静态方法用于创建对象。简单工厂让使用者不用知道具体的参数就可以创建出所需的 ”产品“类,即使用者可以直接消费产品而不需要知道产品的具体生产细节。
工厂类负责创建的对象比较少:由于创建的对象比较少,不会造成工厂方法中业务逻辑过于复杂。
客户端只需知道传入工厂类静态方法的参数,而不需要关心创建对象的细节。工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象, 这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。抽象工厂模式(Abstract Factory Pattern),提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。
但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。二、策略模式可以解决在有多种算法相似的情况下,使用if…else 或swith…case所带来的复杂性和臃肿性。
在日常业务开发中,策略模式适用于以下场景:
- 针对同一类型问题,有多种处理方式,每一种都能独立解决问题;
- 算法需要自由切换的场景;
- 需要屏蔽算法规则的场景。 策略模式中的上下文环境 (Context) ,其职责本来是隔离客户端与策略类的耦合,让客户端完全与上下文环境沟通,无需关系具体策略。
策略模式的优缺点
优点 :
- 策略模式符合开闭原则。
- 避免使用多重条件转移语句,如 if…else… 语句、 switch 语句
- 使用策略模式可以提高算法的保密性和安全性。
缺点 :
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多策略类,增加维护难度。
三、责任链模式 (Chain of Responsibility Pattern) 是将链中每一个节点看作是一个对象,每个节点处理的清求均不同,且内部自动维护—个下—节点对象。当—个清求从链式的首端发出时,会沿看链的路径依次传递给每—个节点对象,直至有对象处理这个清求为止。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解的是其模式(道)而不是其具体实现(术),责任链模式的独到之处是其将节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动了起来。责任链模式的优缺点 优点:
- 将请求与处理解耦;
- 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转 发给下一级节点对象;
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
- 链路结构灵活,可以通过改变链路结构动态地新增或删减责任;
- 易于扩展新的请求处理类(节点),符合开闭原则。
缺点:
- 责任链太长或者处理时间过长,会影响整体性能
- 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
8. 常用的注解有哪些,有什么作用
一.用于创建对象的注解
作用:和在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可以省略
9. 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
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
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片,数据分片的算法不需要改变。数据迁移也仅仅是将逻辑数据库迁移到新的服务器上面去,而这种迁移通过数据库的主从复制就可以完成。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
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可以跨数据库、跨不同的业务系统来实现事务。
本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
消息事务
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将在保留本次任务执行不变的状况下,下次任务开始前触发任务重分片
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
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数据段两大基础算法及结合自身的特点做优化。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
中间件体系栈
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形式存储,在控制层先进行校验,不符合则 丢弃,从而避免了对底层存储系统的查询压力;- 缓存空对象
当缓存层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
标签:服务,数据库,SQL,面试,Java,分片,八股文,id,分布式 From: https://blog.csdn.net/2401_87704405/article/details/143153649篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取