面试题
-
面向对象中的组合和聚合区别
组合和聚合是面向对象中两种形式的关联。
组合中,一个对象是另一个的拥有者
聚合则是指一个对象使用另一个对象。
如果对象 A 是由对象 B组合的,则 A 不存在的话,B 一定不存在
如果 A 对象聚合了一个对象 B,则即使 A 不存在了,B 也可以单独存在。
-
数据库范式,为什么要有范式
为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
第一范式(确保每列保持原子性)
数据库中所有的属性字段都是不可分解的
第二范式(确保表中的每列都和主键相关)
表中的属性字段必须依赖于表的主键
第三范式(确保每列都和主键列直接相关,而不是间接相关)
不能出现传递依赖
-
mysql隔离级别
读未提交:事务A读取到事务B未提交的数据,也称之为脏读
读已提交:假设事务A先读取数据,B事务修改数据但未提交,此时事务A读取不到事务B未提交的数据,当事务B提交之后,事务A就可以读取到事务B提交后的数据
可重复读:事务A读取M条数据,事务B新增了N条数据,当事务B提交之后事务A也读取不到事务B提交后的数据,直到事务A提交后再次发起事务才可以读取到事务B提交后的数据。这种方式在insert时会出现幻读(事务A根据条件索引得到了N条数据,事务B改变或新增了符合事务A搜索的N条数据之外的M条数据,事务A再次读取就会得到N+M条数据)
可串行化:事务之间完全隔离,假如事务A在修改数据的过程中并未提交,serialization会锁定字段,此时事务B进来会一直等待(可能会超时),等事务A事务解锁之后事务B才可以进来读取数据,这种完全隔离级别会锁定对应的表哥数据,会有效率问题
-
springaop属于什么设计模式
代理模式:代理模式的核心作用就是通过代理,控制对对象的访问。它的设计思路时:定义一个抽象角色,让代理角色和真实角色分别去实现它。
-
springmvc执行流程
- 发起请求到前端控制器
- 前端控制器请求HandlerMapping查找Handler,可以根据xml配置、注解进行查找
- 处理器映射器(HandlerMapping)向前端控制器返回Handler
- 前端控制器调用处理器适配器去执行Handler
- 处理器适配器去执行Handler
- 执行完毕返回给适配器ModelAndView
- 处理器适配器向前端控制器返回ModelAndView
- 前端控制器请求视图解析器去解析ModelAndView
- 视图解析器返回具体的View
- 前端控制器进行视图渲染
- 向用户响应结果
组件:
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器适配器(HandlerAdapter)
处理器(Handler)
视图解析器(View resovler)
视图(View)
-
连接池
一个数据库连接对象都对应一个物理数据库连接,每次操作都打开关闭连接,这样会造成系统性能低下,数据哭连接池的解决方案就是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
最小连接数:是连接池一直保证的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过这个数量,则后续连接请求将被加入到等待队列中,会影响之后的数据库操作
-
线程池
线程池主要是先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。如果超出核心线程数则会创建新的线程去执行,执行后则会关闭这个线程,只保留核心线程数大小的线程,超出最大线程数则需要看线程的拒绝策略是放到队列进行等待,还是直接拒绝掉
-
aop的应用
日志
权限控制
-
sql优化
- 考虑在where及order by涉及的列上建立索引
- 尽量避免在where子句中对字段进行null值判断
- 避免在where子句中使用 != 或 <> 操作符
- 避免在where子句中使用or来连接条件
- 慎用 in 和 not in
- like使用最左匹配
- 尽量避免在where子句中对字段进行表达式操作
- 尽量避免在where子句中对字段进行函数操作
- 用 exists 代替 in 是一个好的选择
- 建索引需要慎重考虑,因为提高了select效率,同时也降低了 insert 级 update 的效率
-
springboot特点
快速构建应用
简洁的安全策略集成
没有复杂的配置文件
自动管理依赖
自带应用监控
内嵌Tomcat、Jetty容器
-
wait和sleep区别,如果sleep还未到时间我要怎么唤醒它
sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁
wait()方法调用的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态
wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException
为什么用springcloud,与dubbo对比
Dubbo的优势:
- 单一应用架构,网站流量小的时候,只需要一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改茶工作量的数据访问框架(ORM)是关键。
- 垂直应用架构,当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。】
- 分布式服务架构,当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
- 流动计算架构,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率,此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
考虑 Spring Cloud 的原因有如下几点:
- Spring Cloud 来源于 Spring,质量、稳定性、持续性都可以得到保证。
- Spirng Cloud 天然支持 Spring Boot,更加便于业务落地。
- Spring Cloud 发展非常的快,从 2016 年开始接触的时候相关组件版本为 1.x,到现在将要发布 2.x 系列。
- Spring Cloud 是 Java 领域最适合做微服务的框架。
- 相比于其它框架,Spring Cloud 对微服务周边环境的支持力度最大。
- 对于中小企业来讲,使用门槛较低。
- Spring Cloud 是微服务架构的最佳落地方案。
SpringCloud与Dubbo对比:
- cloud注册中心使用Eureka,dubbo通过zk实现
- dubbo是RPC调用,cloud基于REST API调用方式
- Dubbo需要集成第三方服务网关,cloud使用Zuu、GateWay网关
- 熔断Dubbo可集成Hystrix实现熔断,cloud已经集成了Hystrix
- 分布式配置cloud集成了config
- 追踪系统cloud集成了sleuth
- 消息总线cloud集成了bus
- 批量任务cloud集成了task
整体比较:
- Dubbo由于是二进制的传输,占用带宽会更少
- Cloud是Http协议传输,带宽会比较多,同时使用Http协议一般会使用JSON报文,消耗会更大
- Dubbo的开发难度较大,原因是Dubbo对jar包依赖问题很多大型工程无法解决
- Cloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
- Dubbo的注册中心可以选择ZK、Redis等,Cloud的注册中心用Eureka、Nacos
-
mysql用什么隔离级别,考虑过用RC吗
“读未提交(RU),读已提交(RC),可重复读(RR),串行化四个隔离级别,MySQL默认是可重复读”
RC隔离级别会出现不可重复读,幻读
ps
:在RC隔离级别下并不是不会出现死锁,只是出现几率比RR低而已! -
那互联网项目中Mysql也是用默认隔离级别,不做修改么?
不是的,我们在项目中一般用**读已提交(Read Commited)**这个隔离级别
缘由一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
缘由二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
缘由三:在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性!
-
在RC级别下,主从复制用什么binlog格式?
-
https://blog.csdn.net/wwwdc1012/article/details/88373440
MySQL 的二进制日志 binlog 可以说是 MySQL 最重要的日志,它记录了所有的 DDL 和 DML 语句(除了数据查询语句select、show等),以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的。binlog 的主要目的是复制和恢复。
在该隔离级别下,用的binlog为row格式,是基于行的复制!Innodb的创始人也是建议binlog使用该格式
-
在RC级别下,不可重复读问题需要解决么?
不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle的默认隔离级别就是RC
-
redis数据类型用那种多一点 分布式锁
数据类型:
-
string
string是redis的最基本数据类型,一个key对应一个value,每个value最大可存储512M。string一般用来存图片或者序列化的数据
-
hash
相当于一个string类型的映射表。特别适合用来存储对象。例如可以存储用户信息,用户ID作为hash类型里的每一个key。
案例:我们这边需要对接微信粉丝的数据到我们自己的平台上,但微信提供的接口只支持单天查询,那么如果我们想要查看最近一个月微信粉丝的状况,就需要循环30次调用微信的接口。一个月勉强还可以接受,那么如果想要查半年,甚至一年呢?那么我们的接口里就需要循环365次调微信的接口,这就会使我们的接口变得非常慢,甚至超时。还有这些数据,比如单天新增粉丝数,是不会变得,而且每天都有一个数据,这样就特别适合存在redis的hash类型里,以日期(2018-10-10)作为hash的key。
-
list
list类型是简单的字符串列表,每个列表可以存储232 - 1 个值。可以从头部或者尾部顺序插入数据。list类型可以用来做电商里的秒杀营销系统或关注列表。
-
set
set是string类型的无序集合。该集合是通过哈希实现的,添加、删除的复杂度都是O(1),所以查找非常快。
案例:我们这边是以手机号为唯一标示符,防止重复用户注册,会判断该手机号有没有注册过,那么如果用set类型存储注册过的用户手机号,就会很快判断出该用户是否注册过,而不用去查数据库了。
-
zset
和set一样,但zset多了一个score来让set变得有序,且不允许有重复的成员。
案例:我们这边有一个账户记录需要按记录时间排序,那么就可以将时间戳当作score存储zset中。
-
-
RC RR 解决幻读
-
多线程方面用过什么线程池,哪几种,怎么用的
newCachedThreadPool 一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同的线程池。
使用异步编排时使用到了该线程池
-
spring aop原理
分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散步在所有对象层次中,而与它所散步到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散步在各处的无关的代码被称之为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利用各个模块的重用。
- 将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
- 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
- 开启基于注解的aop模式;@EnableAspectAutoProxy
-
数据库查询优化,索引
- 不要有超过5个以上的表连接
- 考虑使用临时表或表变量存放中间结果
- 少用子查询
- 视图嵌套不要过深,一般试图嵌套不要超过2个为宜。
-
数据库锁表怎么解决
一、解决方案
-
先使用命令查询查询数据库阻塞的进程
SELECT * FROM information_schema.innodb_trx
-
再杀掉线程
kill trx_mysql_thread_id (杀死对应线程) 例:trx_mysql_thread_id 值为4399634 kill 4399634
二、被锁原因
-
锁表发生在insert update delete中
-
锁表的原理是数据库使用独占式封锁机制,当执行上面的语句时,对表进行锁住,直到发生 commite 或者回滚或者退出数据库用户
-
锁表的原因
- A程序执行了对 table A 的 insert,并还未 commite 时,B程序也对 table A 进行 insert 则此时会发生资源正忙的异常就是锁表
- 锁表常发生于并发而不是并行(并行时,一个线程操作数据库时,另一个线程是不能操作数据库的,cpu和i/o分配原则)
-
减少锁表的概率
减少insert update delete 语句执行到 commite 之间的时间。具体点批量执行改为单个执行、优化sql自身的非执行速度
如果异常对事务进行回滚
-
-
java syncronized 原理
-
volatile
-
spring ioc & aop
IOC:控制反转,也可以称为依赖倒置
将对象的创建维护交给Spring框架进行管理,IOC操作分为:IOC配置文件方式和IOC的注解方式
AOP:面向切面编程
AOP即在不改变原有代码的基础上增加新的功能,比如事务控制,以及日志记录
-
-
工厂模式:spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
-
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Spring 中 bean 的默认作用域就是 singleton(单例)的。除了 singleton 作用域,Spring 中 bean 还有下面几种作用域:
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式
-
代理设计模式:AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关, 却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来 ,便于 减少系统的重复代码 , 降低模块间的耦合度 ,并 有利于未来的可拓展性和可维护性 。
-
模板方法:模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
Spring 中 jdbcTemplate 、 hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
-
观察者模式:观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
Spring 事件驱动模型就是观察者模式很经典的一个应用。
-
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配 Controller 。
-
-
jvm 组成
三大部分:类加载器,运行时数据区和执行引擎
-
String StringBuilder StringBuffer
-
String:在Java中属于对象,用来创建和操作字符串,String值不可变,每次对String的操作都会生成新的String对象
-
StringBuffer 和 StringBuilder 类的对象和String不同的是能够被多次的修改,并且不产生新的未使用对象。
-
StringBuilder 和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
-
区别:
- String:不可变字符序列
- StringBuffer:可变字符序列、效率低、线程安全
- StringBuilder:可变字符序列、效率高、线程不安全
-
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。
-
-
一般通过生产端保证可靠性投递
- 保证消息的成功发出
- 保证MQ节点的成功接收
- 发送端收到MQ节点(Broker)的确认应答
- 完善的消息补偿机制
-
什么情况内存泄露
-
对象内存过大
保存了多个好用内存过大的对象,造成内存超出限制。
-
资源释放
程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源得不到释放造成内存泄露。
-
static关键字的使用
static 是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例,就可能会造成内存的泄露。
针对static的解决方案:
应该尽量避免static成员变量引用资源耗费过多的实例,比如Context.
Context尽量使用ApplicationContext的生命周期比较长,引用它不会出现内存泄露。
使用WeakReference代替强引用。比如可以使用WeakReference mContext;
-
线程倒置内存溢出
线程产生内存泄露的主要原因在于线程生命周期的不可控。如当我们切换横竖屏的时候,一般会重新创建Activity,老的Activity应该被销毁。但是此时我们在子线程中正在进行耗时的操作,老的Activity不会被销毁,这个时候就会出现内存泄露。
解决方案:
将线程的内部类,改为静态内部类。
在线程内部采用弱引用保存Context引用。
-
-
静态方法能覆盖吗
不能,在子类中声明一个完全相同的方法不会报编译时错误,因为方法覆盖基于运行时的动态绑定,静态方法在编译时使用静态绑定进行绑定。虽然可以在子类声明相同名称和方法签名的方法,看起来像是覆盖静态方法,但实际上这是方法隐藏,Java不会在运行时解析方法调用,简单说,通过父类类型调用就会执行父类的静态方法,子类类型调用就会执行子类静态方法。
-
间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别而已的。
默认情况下,InnoDB工作在可重复读隔离级别下,并且会以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
Gap Lock在InnoDB的唯一作用就是防止其他事务的插入操作,以此防止幻读的发生。
-
eureka原理与zk区别
-
基本原理
上图是来自eureka的官方架构图,这是基于集群配置的eureka;
- 处于不同节点的eureka通过Replicate进行数据同步
- Application Service为服务提供者
- Application Client为服务消费者
- Make Remote Call完成一次服务调用服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为
DOWN
状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。
-
Eureka的自我保护机制
在默认配置中,Eureka Server在默认90s没有得到客户端的心跳,则注销该实例,但是往往因为微服务跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,但是因为网络分区故障时,Eureka Server注销服务实例则会让大部分微服务不可用,这很危险,因为服务明明没有问题。
为了解决这个问题,Eureka 有自我保护机制,通过在Eureka Server配置如下参数,可启动保护机制
eureka.server.enable-self-preservation=true
它的原理是,当Eureka Server节点在短时间内丢失过多的客户端时(可能发送了网络故障),那么这个节点将进入自我保护模式,不再注销任何微服务,当网络故障回复后,该节点会自动退出自我保护模式。
自我保护模式的架构哲学是
宁可放过一个,决不可错杀一千
-
Eureka比Zookeeper好在哪里
著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。在此Zookeeper保证的是CP, 而Eureka则是AP。
-
Zookeeper保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
-
Eureka保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
-
-
-
四种解决方案:
-
2PC(2阶提交)
2PC(Two-phase commit protocol),中文叫二阶段提交。 二阶段提交是一种强一致性设计,
2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。
2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。
-
3PC
3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。
3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:
CanCommit、PreCommit 和 DoCommit
。看起来是把 2PC 的提交阶段变成了预提交阶段和提交阶段,但是 3PC 的准备阶段协调者只是询问参与者的自身状况,比如你现在还好吗?负载重不重?这类的。
3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但是不能保证数据一致,除非挂了的那个参与者恢复。
-
TCC(补偿事务)
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了!
TCC 指的是
Try - Confirm - Cancel
。- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。
还有一点要注意,撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等。
-
本地消息表(异步确保)
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。
-
消息事务
RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。
再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。
可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。
可以看到消息事务实现的也是最终一致性。
-
最大努力通知
其实我觉得本地消息表也可以算最大努力,事务消息也可以算最大努力。
就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了。
事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。
所以最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
适用于对时间不敏感的业务,例如短信通知。
-
-
- 扫描优化
- JVM 参数调优
- 替换容器
-
根据业务能力进行服务拆分
业务能力通常指这个组织的业务是做什么,它们通常是稳定的。一旦确定了业务能力,就可以为每个能力或相关能力组定义服务。如餐馆的三个能力:餐馆信息管理、订单管理、会计记账。
围绕能力组织服务的关键好处是它们是稳定的,所以最终的架构也相对稳定。但它们可能也会随着时间推移而变化。
根据子域进行服务拆分
领域模型以解决具体问题的方式包含了一个领域内的知识,定义了当前领域相关团队的词汇表,DDD有两个重要概念:子域和限界上下文
领域驱动为每一个子域定义单独的领域模型。子域是领域的一部分,领域是DDD中用于描述应用程序问题域的一个术语。识别子域的方式跟识别业务能力一样:分析业务并识别业务的不同专业领域, 分析产出的子域定义结果也会跟业务能力非常接近, 如一个外卖系统的子域包含订单管理,会计,送餐等。
领域模型的边界称为限界上下文。它包含实现这个模型的代码集合。微服务架构下,每个限界上下文对应一个或一组服务。
拆分的指导原则
单一职责原则:
改变一个类应该只有一个理由。
在设计微服务架构时,设计小的,内聚的,仅仅含有单一职责的服务。这会缩小服务的大小,提升其稳定性。
闭包原则:
在包中包含的所有类应该是对同类的变化的一个集合,也就是说,如果对包做出修改,需要调整的类应该都在这个包之内。
在设计微服务时,把根据同样原因进行变化的服务放在一个组件内。这样可以控制服务的数量,当需求发生变化时,变更和部署也更容易。
-
为什么从单体服务过度到微服务
单体服务优点:
- 架构简单
- 系统复杂度低
- 部署简单等等
当业务场景简单,开发人数少,功能并不复杂的时候,就比较适合单体架构。随着开发人数变多,功能变得复杂,单体服务就会出现痛点:
- 服务耦合,互相影响
- 团队开发变得复杂,版本管理变得复杂,容易出现打架情况
-
微服务监控怎么做的?
通过
日志埋点
来实现业务监控和行为分析主要需要以下4个步骤- 数据生成(埋点)
- 数据收集
- 数据解析(结构化)
- 数据落盘
- 数据使用(展示/分析)
日志埋点
只是其中一种埋点手段而已,优点是系统无入侵且灵活;日志收集、解析、落盘等都可以灵活搭配选择不同的中间件,并且不需要修改源系统的代码;并且可以方便对接其他分析平台(例如: 大数据平台)PS:业务监控是否可以不做日志埋点,直接查询业务的数据库呢?(不建议这样做)
- 使用日志埋点能实现监控数据与业务数据分离,监控平台不会影响或增加业务数据库的压力
- 使用日志埋点能方便实现实时业务数据预警
举个栗子:日志收集后面添加流计算中间件,计算某个时间窗口内优惠卷日志的数量或者金额大于某个阀值,则发出预警
-
如何评估服务应用需要扩容
评估总访问量
评估平均访问量
评估峰值访问量压测
单机极限 qps适当的冗余服务
-
如何及时发现系统异常的情况?
系统监控,对于异常日志,使用 ELK 进行关键日志报警。另外针对关键接口进行响应延时,吞吐量等监控,一旦出现下降,进行报警。
-
- REST over HTTP(S)
- 通过Message Broker进行消息传递
- RPC (跨语言或单语言)
MQ 系列问题
-
mq消费失败,如何重发
RabbitMQ相关配置
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest listener: simple: retry: #抛异常会按retry策略重发(建议不在程序内抛异常,记录失败消息,然后确认) enabled: true #允许重发 max-attempts: 5 #重发次数 initial-interval: 30000 #重发间隔时间 acknowledge-mode: manual #不确认宕机重启时会重新消费,确认失败会一直重发 publisher-confirms: true # 如果消息没有到exchange,则confirm回调,ack=false, # 如果消息到达exchange,则confirm回调,ack=true publisher-returns: true #exchange到queue成功,则不回调return #exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
消息手动确认模式的几点说明:
- 监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败
- 如果不手动确认,也不抛出异常,消息不会自动重新推送(包括其他消费者),因为对于 rabbitmq 来说始终没有接收到消息消费是否成功的确认,并且 Channel 是在消费端有缓存的,没有断开连接
- 如果rabbitmq断开,连接后会自动重新推送(不管是网络问题还是宕机)
- 如果消费端应用重启,消息会自动重新推送
- 如果消费端处理消息的时候宕机,消息会自动推给其他的消费者
- 如果监听消息的方法抛出异常,消息会按照listener.retry的配置进行重发,但是重发次数完了之后还抛出异常的话,消息不会重发,也不会重发到其他消费者,只有应用重启后会重新推送。因为retry是消费端内部处理的,包括异常也是内部处理,对于rabbitmq是不知道的
- spring.rabbitmq.listener.retry配置的重发是在消费端应用内处理的,不是rabbitmq重发
消息失败重发
-
设置消息持久化
-
利用 confirm 模式,发送失败重新推送
(很多帖子说,confirm模式但是confirm回调测试没有消息数据无法重发,建议:https://www.cnblogs.com/xujishou/p/6288623.html)
-
return exchange到队列失败回调,可以获取到消息相关消息可重发
confirm模式 重发消息,生成CorrelationData,重新发送
private CorrelationData getCorrelationData(String exchange, String routeKey, byte[] body) { MessageProperties messageProperties = new MessageProperties(); messageProperties.setReceivedExchange(exchange); messageProperties.setReceivedRoutingKey(routeKey); Message message = new Message(body, messageProperties); CorrelationData correlationData = new CorrelationData(); correlationData.setReturnedMessage(message); return correlationData; } @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { if (ack) { System.out.println("confirm消息发送成功:" + cause); } else { String msg = new String(correlationData.getReturnedMessage().getBody()); System.out.println("confirm消息发送失败:" + msg); MessageProperties messageProperties = correlationData.getReturnedMessage().getMessageProperties(); rabbitTemplate.convertAndSend(messageProperties.getReceivedExchange(), messageProperties.getReceivedRoutingKey(), correlationData.getReturnedMessage(), correlationData); } }
publisher-confirms: true # 如果消息没有到exchange,则confirm回调,ack=false, # 如果消息到达exchange,则confirm回调,ack=true publisher-returns: true #exchange到queue成功,则不回调return #exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
-
重复消费问题
其实重复消费不可怕,可怕的是你没考虑到重复消费之后,怎么保证幂等性。
给你举个例子吧。假设你有个系统,消费一条往数据库里插入一条,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下已经消费过了,直接扔了,不就保留了一条数据?
一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性
幂等性,我通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。
其实还是得结合业务来思考,我这里给几个思路:
(1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧
(2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性
(3)比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
还有比如基于数据库的唯一键来保证重复数据不会重复插入多条,我们之前线上系统就有这个问题,就是拿到数据的时候,每次重启可能会有重复,因为kafka消费者还没来得及提交offset,重复数据拿到了以后我们插入的时候,因为有唯一键约束了,所以重复数据只会插入报错,不会导致数据库中出现脏数据
如何保证MQ的消费是幂等性的,需要结合具体的业务来看
-
RabbitMQ消息丢失的3种情况
-
消息在生产者传入到MQ的过程中丢失
此时可以选择用 RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ 事务
channel.txSelect
,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback
,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit
但是问题是,RabbitMQ 事务机制(同步)一搞,基本上吞吐量会下来,因为太耗性能。
所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启
confirm
模式,在生产者那里设置开启confirm
模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个ack
消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个nack
接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。事务机制和
cnofirm
机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm
机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。所以一般在生产者这块避免数据丢失,都是用
confirm
机制的。 -
RabbitMQ收到消息,暂存内存中,还没消费,自己挂掉了,内存中的数据搞丢
开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,可能导致少量数据丢失,但是这个概率较小。
设置持久化有两个步骤:
-
创建 queue 的时候将其设置为持久化
-
第二个是发送消息的时候将消息的
deliveryMode
设置为 2就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。
注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。
所以,持久化可以跟生产者那边的
confirm
机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack
了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到ack
,你也是可以自己重发的。 -
-
消费者消费到了这个消息,但是还没来得及处理,就挂了,RabbitMQ以为消息已经被处理
开启手动ack,手动确认消息是否消费
-
Dubbo 系列问题
-
负载均衡在消费者实现还是服务提供者实现?
dubbo的负载均衡机制原理上来说是客户端负载均衡,也就是dubbo消费者客户端根据服务提供者列表进行算法分配,来选择调用的服务端。
-
dubbo 默认使用 Random LoadBalance -随机-
dubbo提供的四种负载均衡策略
- 随机 Random LoadBalance
- 轮询 RoundRobin LoadBalance
- 最少活跃调用数(权重)LeastActive LoadBalance
活跃数指调用前后计数差,优先调用高的,相同活跃数的随机。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 - 一致性Hash ConsistentHash LoadBalance
-
配置
dubbo提供的几种配置级别
服务端服务级别 <dubbo:service interface="..." loadbalance="roundrobin" /> 客户端服务级别 <dubbo:reference interface="..." loadbalance="roundrobin" /> 服务端方法级别 <dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:service> 客户端方法级别 <dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:reference>
用注解来实现
服务端服务级别 @Service(version = "1.0.0",loadbalance="roundrobin",group="2",mock = "force:return null") 客户端服务级别 @Reference(version = "1.0.0",group="2",mock = "force:return %null",loadbalance="roundrobin",check = false)
-
-
服务动态发现如何实现
基于注册 中心的事件通知(订阅与发布),一切支持事件订阅与发布的框架都可以作为Dubbo注册中心的选型。
- 服务提供者在暴露服务时,会向注册中心注册自己,具体就是在${service.interface}/providers目录下添加一个节点(临时),服务提供者需要与注册中心保持长连接,一旦连接断掉(重试连接)会话信息失效后,注册中心会认为该服务提供者不可用(提供者节点会被删除)
- 消费者在启动时,首先也会向注册中心注册自己,具体在${interface interface}/consumers目录下创建一个节点。
- 消费者订阅${service interface}/ [ providers、configurators、routers ]三个目录,这些目录下的节点删除、新增事件都胡通知消费者,根据通知,重构服务调用器(Invoker)。
以上就是Dubbo服务注册与动态发现机制的原理与实现细节。
基础知识题
-
String 对象主要存储在哪块区域
常量池
-
- HashSet实现了Set接口, 仅存储对象; HashMap实现了 Map接口, 存储的是键值对.
- )HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true.
-
HashMap、HashTable 和 ConcurrentHashMap 的区别
HashTable
- 底层数组+链表实现,无论key还是value都不能为null,
- 线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
- 初始size为11,扩容:newsize = olesize*2+1
- 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
HashMap
- 底层数组+链表实现,可以存储null键和null值,线程不安全
- 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
- 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
- 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
- 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
- 计算index方法:index = hash & (tab.length – 1)
- HashMap的初始值还要考虑加载因子:
- 哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。
- 加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。
- 空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。
ConcurrentHashMap
- 底层采用分段的数组+链表实现,线程安全
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
-
-
强引用:以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
-
软引用(SoftReference):如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。 -
弱引用(WeakReference):如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 -
虚引用(PhantomReference):"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在世纪程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
-
-
线程的生命周期
线程的生命周期一共分为五个部分分别是:新建,就绪,运行,阻塞以及死亡。由于cpu需要在多条线程中切换因此线程状态也会在多次运行和阻塞之间切换,当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态。尤其是当线程启动以后,它不能一直“霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
-
新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
-
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源
-
运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
-
堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
-
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
-
-
sleep和wait的区别
sleep的作用
sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。
wait的作用
调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
sleep与wait差异总结
1、来自不同的类:sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
2、有没有释放锁(释放资源):sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。
3、一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
-
死锁产生的原因
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。
死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和
解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态
的情况下占用资源。因此,对资源的分配要给予合理的规划。 -
写一下 HashSet 的实现 HashSet原理
HashSet实际上是一个HashMap实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用null元素。HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象private static final Object PRESENT = new Object();
HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。
所以判断key是否存在就要重写元素的类的equals()和hashCode()方法,当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的equals()比较两个对象是否相同,相同则不能被添加。
-
AES 与 RSA 的关系
AES全称是高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是目前对称密钥加密中比较通用的一种加密方式。
AES密钥有什么用
支付宝开放平台所有OpenAPI均支持对接口的请求内容和响应内容进行AES加密。加密后,在网络上传输的接口报文内容将会由明文内容变为密文内容,可以大大提升接口内容传输的安全性。- AES密钥是对接口请求和响应内容进行加密,密文无法被第三方识别,从而防止接口传输数据泄露。
- RSA密钥是对接口请求和响应内容进行签名,开发者和支付宝开放平台分别加签验签,以确认接口传输的内容没有被篡改。不论接口内容是明文还是密文,RSA均可正常签名。
- 开发者可对请求参数先做AES加密,然后对密文进行RSA签名。
-
MD5 加密以后能解密么
不能,MD5加密后不可逆的
-
final 关键字的作用,final 在多线程并发条件下的作用
-
final修饰基本数据类型、引用数据类型
基本数据类型:一旦赋值后就不允许修改
引用数据类型:final只保证这个引用类型变量所引用的地址不会发生改变
-
final修饰方法,此方法不能被子类重写
-
修饰类,不可被继承
-
final关键字不同于finally关键字,后者用于异常处理。
-
final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
-
接口中声明的所有变量本身是final的。
-
final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
-
final方法在编译阶段绑定,称为静态绑定(static binding)。
-
按照Java代码惯例,final变量就是常量,而且通常常量名要大写
-
-
线程安全
就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,
就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。存在竞争的线程不安全,不存在竞争的线程就是安全的
-
Map 接口的实现有哪几种,实现的特性是什么
Map接口是java定义的一种键值对映射的数据结构接口,其实现方式有四种:
HashMap、LinkedHashMap、HashTable、TreeMap
-
HashMap
Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null(HashSet的实现就是在hashmap的值为空的情况下);HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
-
LinkedHashMap
LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关
-
Hashtable:
Hashtable和HashMap类似,它继承自Dictionary类,不同的是它不允许键或值为空。hashtable支持线程同步。即同一时刻只能有一个线程对hashtable进行操作。这也导致了它的写入较慢。
-
TreeMap
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
-
-
- 在LinkedHashMap中,是通过双联表的结构来维护节点的顺序的。上文中的程序,实际上在内存中的情况如下图所示,每个节点都进行了双向的连接,维持插入的顺序(默认)。
head
指向第一个插入的节点,tail
指向最后一个节点。 - LinkedHashMap是HashMap的亲儿子,直接继承HashMap类。LinkedHashMap中的节点元素为
Entry<K,V>
,直接继承HashMap.Node<K,V>
- 在LinkedHashMap中,是通过双联表的结构来维护节点的顺序的。上文中的程序,实际上在内存中的情况如下图所示,每个节点都进行了双向的连接,维持插入的顺序(默认)。
-
synchronized同步方法和同步代码块的区别
同步方法默认使用this或者当前类做为锁。
**同步代码块可以选择以什么来加锁,比同步方法更精确,**我们可以选择只有会在同步发生同步问题的代码加锁,而并不是整个方法。
同步方法使用synchronized修饰,而同步代码块使用synchronized(this){}修饰。
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。 -
哪些方法可以创建线程池「代码示例」
可通过 new 线程池进行创建,或者使用 Executors 创建线程池
方法:
-
newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
-
newFixedThreadPool(2)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,超出的线程会在队列中等待。
-
newScheduledThreadPool(2)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
-
newSingleThreadExecutor()
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
-
newSingleThreadScheduledExecutor()
创建一个单例线程池,定期或延时执行任务。
-
newWorkStealingPool(3)
创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不穿如并行级别参数,将默认为当前系统的CPU个数。
-
-
http和https的区别
https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。
http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。
http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
-
产生原因
哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的索引值。这时候就产生了哈希冲突(两个值都需要同一个地址索引位置)。
产生哈希冲突的影响因素
装填因子(装填因子=数据总数 / 哈希表长)、哈希函数、处理冲突的方法
解决哈希冲突的四种方法
- 开放地址方法(再散列法)
- 链式地址法(HashMap的哈希冲突解决方法)
- 建立公共溢出区
- 再哈希法
-
数组和链表的区别
1、数组和链表的定义
数组和链表是两种不同的数据存储方式
数组的定义:
- 数组是一组具有相同数据类型的变量的集合,这些变量称之为集合的元素
- 每个元素都有一个编号,称之为下标,可以通过下标来区别并访问数组元素,数组元素的个数叫做数据的长度
链表的定义
- 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
- 链表的特性是在中间任意位置插入和删除元素都非常快,不需要移动其它元素
- 对于单向链表而言,链表中的每一个元素都要保存一个指向下一个元素的指针
- 对于双向链表而言,链表中的每个元素既要保存指向下一个元素的指针,又要保存指向上一个元素的指针
- 对于双向循环链表而言,链表中的最后一个元素保存一个指向第一个元素的指针
2、数组和链表的区别
比较项 数组 链表 逻辑结构 1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素 (1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存 内存结构 数组从栈上分配内存,使用方便,但是自由度小 链表从堆上分配内存,自由度大,但是要注意内存泄漏 访问效率 数组在内存中顺序存储,可通过下标访问,访问效率高 链表访问效率低,如果想要访问某个元素,需要从头遍历 越界问题 数组的大小是固定的,所以存在访问越界的风险 只要可以申请得到链表空间,链表就无越界风险 3、数组和链表的使用场景
比较 数组 链表 空间 数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据 链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储 时间 数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构 链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构 -
深拷贝和浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了
studentB
的subject
,但是studentA
的subject
也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 对于有多层对象的,每个对象都需要实现Cloneable
并重写clone()
方法,进而实现了对象的串行层层拷贝。
(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。 -
final,finalize, finally有什么区别
final
final 修饰的属性一旦赋值不可改变
final 修饰的方法不可重写
final 修饰的类不可被继承
接口中声明的属性都是 final 类型
finally:异常处理的一部分,标注的代码块表示这段语句最终一定会被执行
finalize() 是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
使用finalize还需要注意一个事,调用super.finalize();一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
-
HashSet 类中的 add() 方法源码中调用的 map.put() 方法,方法中传入值以及PRESENT(Map用来匹配Map中后面的对象的一个虚拟值),put() 方法中通过 for 循环遍历 table 中的元素:
1、如果hash码值不相同,存
2、hash码相同,equals相等,不存
3、hash码相同,equals不等,存
-
有哪些常见的加密算法
-
MD5加密算法
-
DES加密算法
DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法, DES算法的入口参数有三个:Key、Data、Mode。其中Key为7个字节共56位,是DES算法的工作密钥;Data为8个字节64位,是要被加密或被解密的数据。
-
DSA加密算法
DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准)。
-
RSA加密算法
RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。RSA是被研究得最广泛的公钥算法,从提出到现在,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前秀的公钥方案之一。
-
IDEA加密算法
IDEA(International Data Encryption Algorithm)国际数据加密算法:使用 128 位密钥提供非常强的安全性;
-
AES加密算法
AES加密算法又称Rijndael加密法,目前已经被多方分析且广为全世界所使用。经过五年发展,AES加密算法已然成为对称密钥加密中的算法之一。
-
Elgamal
ElGamal算法,是一种较为常见的加密算法,它是基于1984年提出的公钥密码体制和椭圆曲线加密体系。既能用于数据加密也能用于数字签名。
-
Base64加密算法
Base64加密算法是网络上最常见的用于传输8bit字节代码的编码方式之一,Base64编码可用于在HTTP环境下传递较长的标识信息。
-
SHA1加密算法
SHA1是和MD5一样流行的消息摘要算法。SHA加密算法模仿MD4加密算法。SHA1主要适用于数字签名标准里面定义的数字签名算法。
-
PKCS加密算法
PKCS是由美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。
-
-
static的静态方法能不能被继承
- Java中所有方法都能被继承,包括私有方法(但不可见)和静态方法。
- 静态方法是编译时绑定的,方法重写是运行时绑定的。
-
final修饰不同类,方法,基本数据类型的区别,修饰string类以后会怎么样
修饰类不可被继承
修饰方法不可被重写
修饰类型不可改变值
String 类本身就是通过 final 修饰的
-
事务是什么
是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)
四大特性:ACID
原子、一致、隔离、持续
-
面向对象的思想
面向对象 (Object Oriented,OO) 的思想对软件开发相当重要,它的概念和应用甚至已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD 技术、人工智能等领域。面向对象是一种 对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向过程 (Procedure Oriented) 是一种 以过程为中心 的编程思想。这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁在受影响。与面向对象明显的不同就是 封装、继承、类。
-
HashMap原理
https://www.jianshu.com/p/c561bab28e19
https://zhuanlan.zhihu.com/p/28501879
https://zhuanlan.zhihu.com/p/28587782
-
- 哈希碰撞的定义:有a,b两条数据,且a != b,对于这组数据,如果有hash(a) == hash(b),则哈希发生碰撞
- 原因:hash函数的返回值是一个int类型的数据,int的取值是有范围的,而散列表的key是没有范围的,可以是任何值。将多数key映射到少数hashCode,必然会有多个key对应同一个hashCode的情况。
- 可能性:许多人认为int的取值范围够大,在实际使用中很少会发生碰撞。其实不然,可以参见生日悖论,简单解释一下生日悖论:23人里有两人同一天生日的机率超50%。(更加具体的说明请自行搜索“生日悖论”)同理,哈希碰撞的可能性是很大的。
- 下标冲突:与哈希的碰撞类似,在将hashCode映射到数组下标时也是有可能重复的。在往数组的某个下标插入节点的时候发现该下标已经有其他节点,即为下标冲突
hashCode发生碰撞,则下标一定冲突;而下标冲突,hashCode并不一定碰撞
解决:
在jdk1.8以前HashMap的实现是散列表 = 数组 + 链表 ,但是到目前为止我们还没有看到链表起到的作用。事实上,HashMap引入链表的用意就是解决下标冲突。
如上图所示,左边的竖条,是一个大小为16的数组,其中存储的是链表的头结点,我们知道,拥有链表的头结点即可访问整个链表,所以认为这个数组中的每个下标都存储着一个链表。其具体做法是,如果发现下标冲突,则后插入的节点以链表的形式追加到前一个节点的后面。
这种使用链表解决冲突的方法叫做:拉链法(又叫链地址法)。HashMap使用的就是拉链法,拉链法是冲突发生以后的解决方案。
-
HashMap
的线程不安全主要体现在下面两个方面:
1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。
2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。 -
用什么与HashMap有关的数据结构可以实现线程安全
使用HashTable或者Collections.synchronizedMap,但是这两位选手都有一个共同的问题:性能。因为不管是读还是写操作,他们都会给整个集合上锁,导致同一时间的其他操作被阻塞。
虽然HashTable和Collections.synchronizedMap解决了HashMap的线程不安全的问题,但是带来了运行效率不佳的问题。
基于以上所述,兼顾了线程安全和运行效率的ConcurrentHashMap就出现了。ConcurrentHashMap是一个双层哈希表。在一个总的哈希表下面,有若干个子哈希表。(这样的双层结构,类似于数据库水平拆分来理解)
ConcurrentHashMap如此的设计,优势主要在于:
每个segment的读写是高度自治的,segment之间互不影响。这称之为“锁分段技术”;https://blog.csdn.net/V_Axis/article/details/78616700?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-1.no_search_link&spm=1001.2101.3001.4242
-
用户态和核心态的区别
- 内核态(Kernel Mode):运行操作系统程序
- 用户态(User Mode):运行用户程序
-
ArrayList和LinkedList区别 深入理解
存储结构
ArrayList和Vector是按照顺序将元素存储(从下表为0开始),删除元素时,删除操作完成后,需要使部分元素移位,默认的初始容量都是10.
ArrayList和Vector是基于数组实现的,LinkedList是基于双向链表实现的(含有头结点)。
线程安全
ArrayList不具有有线程安全性,在单线程的环境中,LinkedList也是线程不安全的,如果在并发环境下使用它们,可以用Collections类中的静态方法synchronizedList()对ArrayList和LinkedList进行调用即可。
Vector实现线程安全的,即它大部分的方法都包含关键字synchronized,但是Vector的效率没有ArraykList和LinkedList高。
扩容机制
从内部实现机制来讲,ArrayList和Vector都是使用Object的数组形式来存储的,当向这两种类型中增加元素的时候,若容量不够,需要进行扩容。ArrayList扩容后的容量是之前的1.5倍,然后把之前的数据拷贝到新建的数组中去。而Vector默认情况下扩容后的容量是之前的2倍。
Vector可以设置容量增量,而ArrayList不可以。在Vector中,有capacityIncrement:当大小大于其容量时,容量自动增加的量。如果在创建Vector时,指定了capacityIncrement的大小,则Vector中动态数组容量需要增加时,如果容量的增量大于0,则增加的是大小是capacityIncrement,如果增量小于0,则增大为之前的2倍。
在这里需要说一下可变长度数组的原理:当元素个数超过数组的长度时,会产生一个新的数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中。
增删改查的效率
ArrayList和Vector中,从指定的位置检索一个对象,或在集合的末尾插入、删除一个元素的时间是一样的,时间复杂度都是O(1)。但是如果在其他位置增加或者删除元素花费的时间是O(n),LinkedList中,在插入、删除任何位置的元素所花费的时间都是一样的,时间复杂度都为O(1),但是他在检索一个元素的时间复杂度为O(n).
所以如果只是查找特定位置的元素或只在集合的末端增加移动元素,那么使用ArrayList或Vector都是一样的。如果是在指定位置的插入、删除元素,最好选择LinkedList
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uL8AuUNa-1632428537787)(/Users/and/Library/Application Support/typora-user-images/截屏2021-09-22 上午11.26.07.png)]
-
(java)List如何删除元素
方式一,使用 Iterator ,顺序向下,如果找到元素,则使用 remove 方法进行移除。
方式二,倒序遍历 List ,如果找到元素,则使用 remove 方法进行移除。
方式三,正序遍历 List ,如果找到元素,则使用 remove 方法进行移除,然后进行索引 “自减”。
方式四,使用jdk1.8新增的Stream流操作为什么不能使用 forEach
foreach方式遍历元素的时候,是生成iterator,然后使用iterator遍历。在生成iterator的时候,会保存一个expectedModCount参数,这个是生成iterator的时候List中修改元素的次数。如果你在遍历过程中删除元素,List中modCount就会变化,如果这个modCount和exceptedModCount不一致,就会抛出异常。这个是为了安全的考虑。如果使用iterator遍历过程中,使用List修改了元素,可能会出现不正常的现象。如果使用iterator的remove方法则会正常,因为iterator的remove方法会在内部调用List的remove方法,但是会修改excepedModCount的值,因此会正常运行。
-
了解HashMap么
https://blog.csdn.net/zhangdx001/article/details/105092130
https://www.cnblogs.com/wyhb008/p/13367912.html
-
HashMap1.8之后为什么要采用数组+链表+红黑树的储存方式
整体从代码我们可以看到hashmap的储存过程就可以明白HashMap1.8之后为什么要采用数组+链表+红黑树的储存方式?因为一开始数据存数组如果发生hash冲突,这个时候需要把冲突的数据放到后面的链表中(链地址法),如果hash冲突的数据过多,就会让链表过长,查询效率会变低,所以jdk1.8之后当链表长度大于8时就是转化为红黑树。其中换会牵涉到一个数组扩容
https://blog.csdn.net/weixin_44470090/article/details/106240736
-
死锁产生的原因和解决办法
https://www.cnblogs.com/JimmyFanHome/p/9914562.html
-
反射的定义和作用
https://www.jianshu.com/p/9e9cb74cae95
-
1.==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
2.==是指对内存地址进行比较 equals()是对字符串的内容进行比较
3.==指引用是否相同 equals()指的是值是否相同
-
单例模式的应用场景
Spring Bean默认就是单例模式
-
Cookie与Session的作用与原理
https://blog.csdn.net/guoweimelon/article/details/50886092
-
必看
-
并发synchronized和lock区别,synchronized升级过程?偏向锁释放了,再来一个线程会变成轻量级锁吗?
https://www.php.cn/java/base/464183.html
https://www.jianshu.com/p/36eedeb3f912
https://blog.csdn.net/qq9808/article/details/104867285/
-
es线程池参数设置调过没
https://www.cnblogs.com/bonelee/p/7840629.html
-
redis线上问题遇到过没有
https://www.cnblogs.com/niejunlei/p/12900578.html
日常我们使用redis 缓存时,经常会遇到各种各样的问题,其中redis 偶发性连接超时,是经常遇到的一个问题,下面介绍一下我们之前是如何处理的这个问题。
1、redis 服务监控
通过监控工具,首先排查一下redis 服务端是否是超时,可以从服务器cpu ,内存使用情况,qps等判断server 端是否超时。如果server 侧没有问题,就需要排查客户端。如果server 侧存在问题,就需要排查服务器哪里出了问题,单机性能使用率太高是否可以升级成哨兵模式或者高可用集群模式。
2、redis 客户端排查
首先查看业务日志,查看一下redis 使用情况是否是存在连接数占满或者创建失败的异常,如果存在,在客户端服务器,使用top 指令,查看使用率高的线程,然后jstack pid,查看当前线程的使用情况。如果出现大量的线程状态显示time_waiting 或者waiting 。则表示连接数一直没有释放,可以通过调整客户端配置的redis 连接池参数,比如配置max连接数和min连接数,time_out超时时间等等。
3、redis 热key排查
排查redis 热key,腾讯云或者阿里云服务器可以使用监控热key的工具。redis 4.0 以后,提供了—hotkey 指令,可以通过热key 指令来监控热key。如果发现异常热key,比如spring-redis-session的热key,存储的是一段时间戳,并且访问率非常高,qps 几十万/s。这时候需要考虑热key是否对业务产生影响,可以通过配置spring.session.store-type=none,关闭存储redis.这时候热key访问量下降,业务key 可以正常访问。
通过以上方式,排查生产中遇到的redis 连接问题,可以排查线上遇到的问题,基本都可以解决掉。
-
redis rehash过程
https://www.cnblogs.com/williamjie/p/11205593.html
-
dubbo用过没有
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC(一种远程调用) 分布式服务框架(SOA),致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
-
为什么有rpc调用
因为良好的rpc调用是面向服务的封装,针对服务的可用性和效率等都做了优化。单纯使用http调用则缺少了这些特性。
-
实现查找附近的人,你怎么设计
https://blog.csdn.net/weixin_35770958/article/details/113440703
-
索引存储的数据结构
https://blog.csdn.net/shaowei6969/article/details/107717262
-
最左前缀
https://blog.csdn.net/zzx125/article/details/79678770
-
Spring的加载过程
https://blog.csdn.net/u011246915/article/details/82716009
-
反射机制的原理
https://www.cnblogs.com/hongxinlaoking/p/4684652.html
-
List实现类,详细介绍
List 特点:有序、可重复、可通过索引访问
ArrayList:底层数据结构使数组结构,查询速度快,增删改慢
LinkList:底层使用链表结构,增删速度快,查询稍慢
Vector:底层是数组结构,Vector是线程同步的,所以它也是线程安全的。而ArratList是线程异步的,不安全。如果不考虑安全因素,一般用Arralist效率比较高;
可变长度数组不断new数组:
(1) ArrayList当初始化容量超过10时,会new一个50%de ,把原来的东西放入这150%中;
(2) Vector:当容量超过10时,会new一个100%的浪费内存;https://blog.csdn.net/zxx901221/article/details/82997683
-
get和post区别
- Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
- Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
- Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
- Get执行效率却比Post方法好。Get是form提交的默认方法。
-
http状态码
- 200(“OK”)
一切正常。实体主体中的文档(若存在的话)是某资源的表示。 - 400(“Bad Request”)
客户端方面的问题。实体主题中的文档(若存在的话)是一个错误消息。希望客户端能够理解此错误消息,并改正问题。 - 500(“Internal Server Error”)
服务期方面的问题。实体主体中的文档(如果存在的话)是一个错误消息。该错误消息通常无济于事,因为客户端无法修复服务器方面的问题。 - 301(“Moved Permanently”)
当客户端触发的动作引起了资源URI的变化时发送此响应代码。另外,当客户端向一个资源的旧URI发送请求时,也发送此响应代码。 - 404(“Not Found”) 和410(“Gone”)
当客户端所请求的URI不对应于任何资源时,发送此响应代码。404用于服务器端不知道客户端要请求哪个资源的情况;410用于服务器端知道客户端所请求的资源曾经存在,但现在已经不存在了的情况。 - 409(“Conflict”)
当客户端试图执行一个”会导致一个或多个资源处于不一致状态“的操作时,发送此响应代码。
- 200(“OK”)
-
redis运行机制
Redis是基于单线程的,Redis效率比较高,由于Redis是基于内存操作,所以CPU不是性能瓶颈,机器的内存和宽带才是Redis的瓶颈。
-
为什么Redis为单线程还那么快?
- 多线程涉及到cpu之间的切换,CPU的切换会造成资源的浪费,所以多线程并没有单线程快;
- 存储空间操作效率,CPU读取速率>内存读取速率>硬盘读取速率,Redis为单线程,只需要一个CPU执行,读取速率更快;
- redis所有的数据都存放在内存中,所以单线程去操作速率最高,多线程需要上下文切换,对于内存来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,基于内存来说,这个就是最佳方案。
-
说下mysql为什么用B+树,不用b树
1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
3、B+树更便于遍历:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
4、B+树更适合基于范围的查询:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。
-
innodb和myinsam引擎的区别。myisam适合用于哪些场景
-
事务处理
MyISAM是非事务安全型的,而InnoDB是事务安全型的(支持事务处理等高级处理);
-
锁机制不同
MyISAM是表级锁,而InnoDB是行级锁;
-
select ,update ,insert ,delete 操作
MyISAM:如果执行大量的SELECT,MyISAM是更好的选择
InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表
-
查询表的行数不同
MyISAM:select count() from table,MyISAM只要简单的读出保存好的行数,注意的是,当count()语句包含 where条件时,两种表的操作是一样的
InnoDB : InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行
-
外键支持
mysiam表不支持外键,而InnoDB支持
应用场景
MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。
InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且行锁定的机会比较大的情况。
-
-
mysql的锁机制。gap锁怎么加的,gap锁会有什么问题?
说白了gap就是索引树中插入新记录的空隙。相应的gap lock就是加在gap上的锁,还有一个next-key锁,是记录+记录前面的gap的组合的锁。
-
mysql的sql优化,你看哪些参数。
explain 查询出相关参数
- select_type:select的类型,常见有SIMPLE(简单表)、PRIMARY(主查询)、UNION、SUBQUERY(子查询中的第一个SELECT)等。
- table:输出结果集的表
- type:在表中找到所需行的方式,也叫访问类型。性能为:ALL(全表)<idnex(全索引)<range(索引范围扫描)<ref(非唯一索引或者唯一索引前缀)<eq_ref(唯一索引)<const,system(单表中最多只有一个匹配行)<NULL(不需访问表或索引)
- possible_keys:可能使用的索引
- key:实际使用的索引
- key_len:使用到索引字段的长度
- rows:扫描行的数量
- Extra:执行情况的说明
-
rdb和aof区别,aof下always的安全性
https://blog.csdn.net/tongshuixu8025/article/details/86487006?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-0.no_search_link&spm=1001.2101.3001.4242
-
集群模式和哨兵模式有啥区别,选举过程,raft协议,gossip协议
区别
- 哨兵模式监控权交给了哨兵系统,集群模式中是工作节点自己做监控
- 哨兵模式发起选举是选举一个leader哨兵节点来处理故障转移,集群模式是在从节点中选举一个新的主节点,来处理故障的转移
当slave发现自己的master变为FAIL状态时,便尝试发起选举,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
- slave发现自己的master变为FAIL
- 将自己记录的集群currentEpoch(选举轮次标记)加1,并广播信息给集群中其他节点
- 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送结果
- 尝试选举的slave收集master返回的结果,收到超过半数master的统一后变成新Master
- 广播Pong消息通知其他集群节点。
https://www.cnblogs.com/nijunyang/p/12508098.html
Raft协议:http://www.cnblogs.com/likehua/p/5845575.html
gossip协议:https://zhuanlan.zhihu.com/p/308641354
-
redis数据过期策略
定时删除
- 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
- 优点:保证内存被尽快释放
- 缺点:
- 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
- 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
- 没人用
惰性删除
- 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
- 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
- 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
定期删除
- 含义:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key操作
- 优点:
- 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点
- 定期删除过期key–处理"惰性删除"的缺点
- 缺点
- 在内存友好方面,不如"定时删除"
- 在CPU时间友好方面,不如"惰性删除"
- 难点
- 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)
定时删除和定期删除为主动删除:Redis会定期主动淘汰一批已过去的key
惰性删除为被动删除:用到的时候才会去检验key是不是已过期,过期就删除
惰性删除为redis服务器内置策略
定期删除可以通过:
- 第一、配置redis.conf 的hz选项,默认为10 (即1秒执行10次,100ms一次,值越大说明刷新频率越快,最Redis性能损耗也越大)
- 第二、配置redis.conf的maxmemory最大值,当已用内存超过maxmemory限定时,就会触发主动清理策略
Redis采用的过期策略
惰性删除+定期删除
- 惰性删除流程
- 在进行get或setnx等操作时,先检查key是否过期,
- 若过期,删除key,然后执行相应操作;
- 若没过期,直接执行相应操作
- 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
- 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
- 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
- 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
- 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
- 判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。
- 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
- 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
-
redis如何作为分布式锁的
Redis实现分布式锁基于SetNx命令,因为在redis中key是保证是唯一的。所以当多个线程同时创建setNx时,只要谁能够创建成功谁就能获取到锁。
Set命令:每次set时,可以修改原来旧值;
SetNx命令:每次SetNx检查key是否已经存在,如果已经存在的话就不会执行任何操作,返回0;反之,新增该key。
获取锁的时候:当多个线程同时创建SetNx key,只要谁能够创建成功谁就能够获取到锁。
释放锁:可以对该key设置一个有效期可以避免死锁的现象。 -
redis删除策略
1、被动删除:在上一章中已经提到过,dbsize中获得key个数包含过期的key,只有在key再次被操作的时候,redis才会去检测该key是否已经过期,如果过期则将它删除,这对于cpu来说,能节约出删除该key的时间来;但是对于内存来说,假如该key一直甚至永远不被调用的话,它将一直占着内存,当这种key越来越多的时候,内存会被这种可以称得上是垃圾key占满,对于吃内存的redis而言,无疑是一场噩梦。
2、主动删除:主要由redis.c/serverCron来实现,作为时间事件来进行,每隔一段时间就会自动运行一次,serverCron 会一直定期执行,直到服务器关闭为止。配置文件中的hz就是用来配置这个的频率的,默认是10,表示serverCron每秒执行10次。
3、maxmemory 删除:当前已用内存超过maxmemory限定时,将会触发。有6策略:
volatile-lru:只对设置了过期时间的key进行lru算法的出来的key(默认值)
allkeys-lru : 删除lru算法的key
volatile-random:随机删除即将过期key
allkeys-random:随机删除key
volatile-ttl : 删除即将过期的key(离过期时间最少的那个)
noeviction : 永不过期,返回错误注意:配置文件中 maxmemory-samples配置的是随机抽取数,默认值是5。
-
消息队列特性
异步通信、降低工程之间的依赖程度、避免数据冗余、便于扩展、便于过载保护,防止访问量大增处理不过来而超负荷崩溃、增加了信息的可恢复性
-
list
Redis
中的list
和Java
中的LinkedList
很像,底层都是一种链表结构,list
的插入和删除操作非常快,时间复杂度为 0(1),不像数组结构插入、删除操作需要移动数据。- 朋友圈的点赞列表、评论列表、排行榜:
lpush
命令和lrange
命令能实现最新列表的功能,每次通过lpush
命令往列表里插入新的元素,然后通过lrange
命令读取最新的元素列表。
set
Redis
中的set
和Java
中的HashSet
有些类似,它内部的键值对是无序的、唯一 的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值 NULL。当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。- 好友、关注、粉丝、感兴趣的人集合:
sinter
命令可以获得A和B两个用户的共同好友;sismember
命令可以判断A是否是B的好友;scard
命令可以获取好友数量;- 关注时,
smove
命令可以将B从A的粉丝集合转移到A的好友集合
- 首页展示随机:美团首页有很多推荐商家,但是并不能全部展示,set类型适合存放所有需要展示的内容,而
srandmember
命令则可以从中随机获取几个。 - 存储某活动中中奖的用户ID ,因为有去重功能,可以保证同一个用户不会中奖两次。
hash
Redis
中的Hash
和 Java的HashMap
更加相似,都是数组+链表
的结构,当发生 hash 碰撞时将会把元素追加到链表上,值得注意的是在Redis
的Hash
中value
只能是字符串.
- 购物车:
hset [key] [field] [value]
命令, 可以实现以用户Id
,商品Id
为field
,商品数量为value
,恰好构成了购物车的3个要素。 - 存储对象:
hash
类型的(key, field, value)
的结构与对象的(对象id, 属性, 值)
的结构相似,也可以用来存储对象。
-
Java中有哪些锁,悲观锁和乐观锁区别。countdownlatch是干什么的
https://blog.csdn.net/weixin_38894058/article/details/78952585
https://www.cnblogs.com/zhangweicheng/p/12679116.html
-
threadlocal是什么,在你们项目中是怎么使用的
https://www.jianshu.com/p/3c5d7f09dfbd
threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,
-
InnerDB 索引底层数据结构是什么?
- 表数据文件本身就是按B+Tree组织的一个索引结构文件
- 聚集索引-叶子节点包含了完整的数据记录
- 为什么InnoDB表必须有主键,并且推荐使用整型的自增主键?
- 为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省存储空间)
-
InnerDB 事务如何执行?
https://www.cnblogs.com/yinjw/p/11880568.html
-
redis如何进行单机热点数据的统计?
https://blog.csdn.net/fuqianming/article/details/99682764
-
Redis分布式锁是怎么做到的?锁的过期时间怎么去设计的?如何保证当前锁不会释放掉其他锁?
setnx,过期时间必须保证锁释放前业务执行完毕,可以设计时间相对长一些,业务结束释放锁
-
mysql的undulog,redulog,binlog说一说
https://blog.csdn.net/u010002184/article/details/88526708
-
索引数据结构,主键索引和非主键索引区别
主键索引和非主键索引的区别是:非主键索引的叶子节点存放的是主键的值,而主键索引的叶子节点存放的是整行数据,其中非主键索引也被称为二级索引,而主键索引也被称为聚簇索引。
-
缓存一致性问题
双删,以数据库为基准,达到最终一致性
采用延时双删策略
(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠1秒,再次淘汰缓存总的来说有两种方案: 1.设置过期时间 2.增加一个MQ保证Redis中数据最终一定是正确的
https://blog.csdn.net/hukaijun/article/details/81010475
-
缓存穿透、缓存击穿、缓存雪崩区别和解决方案
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁参考代码如下:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
-
mq使用场景?咋做到低延时高吞吐的,做了啥优化。
https://blog.csdn.net/u011001084/article/details/73135689
https://blog.csdn.net/zollty/article/details/53958656
-
mq优化 零拷贝问题?
https://zhuanlan.zhihu.com/p/402129882
-
dubbo配合zookeeper怎么进行服务注册与发现
https://blog.csdn.net/CDW2328/article/details/94769531
-
springboot是怎么读取dubbo.xml文件的,启动spring项目的时候是怎么实例化dubbo里面的类的,分析具体细节与源码
可通过@ImportResource(“classpath:dubbo-provider.xml”)来读取dubbo.xml配置文件
项目启动时候spring是如何加载和实例化各类的
1.对于注解为默认的,也就是@score不写的,默认是单例的类,这些类在项目启动的时候会依次被加载并被实例化。默认调用的是无参数的构造器。
2.而对于注解是prototype类型的,即@Scope(“prototype”),在首次被用到的时候加载。
-
mysql的索引结构,每个结点怎么存,mysql一张表最大能存多少数据
B+树
只有最底层的叶子节点(文件)保存数据,非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中
-
聚簇索引和非聚簇索引的区别与底层原理
https://blog.csdn.net/qq_36381855/article/details/82112436
-
lock底层AQS怎么实现,CLH队列怎么存储数据,具体细节
https://blog.csdn.net/weixin_40388441/article/details/115707839
-
线程池适合什么样的场景,有哪些参数,配置的参数有哪些考虑
线程池适合单系统的大量的异步任务处理,比如发送短信、保存日志。
七大参数:https://blog.csdn.net/ye17186/article/details/89467919
- corePoolSize 线程池核心线程大小
- maximumPoolSize 线程池最大线程数量
- keepAliveTime 空闲线程存活时间
- unit 空闲线程存活时间单位
- workQueue 工作队列
- threadFactory 线程工厂
- handler 拒绝策略
-
分布式唯一id生成选型有哪些考量
UUID
优点:本地生成ID,不需要进行远程调用,时延低,性能高。
缺点;
- UUID过长,16字节128位,通常以36长度的字符串表示,很多场景不适用,比如用UUID做数据库索引字段。
- 没有排序,无法保证趋势递增。
Flicker方案
优点:充分借助数据库的自增ID机制,可靠性高,生成有序的ID。
缺点:
- ID生成性能依赖单台数据库读写性能。
- 依赖数据库,当数据库异常时整个系统不可用。
类snowflake方案
优点:
- 时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序。
- 性能高,每秒可生成几百万ID。
- 可以根据自身业务需求灵活调整bit位划分,满足不同需求。
缺点:
- 依赖机器时钟,如果机器时钟回拨,会导致重复ID生成。
- 在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。
TDDL序列生成方式
优点:
- 相比flicker方案,大大降低数据库写压力,数据库不再是性能瓶颈。
- 相比flicker方案,生成ID性能大幅度提高,因为获取一个可用号段后在内存中直接分配,相对于每次读取数据库性能提高了几个量级。
- 不同业务不同的ID需求可以用seqName字段区分,每个seqName的ID获取相互隔离,互不影响。
缺点:
- 强依赖数据库,当数据库异常时整个系统不可用。
发号器实现方案
”发号器“—达达分布式ID生成系统,是以snowflake算法为基础,实现了生成全局唯一ID的功能,解决了在分布式系统唯一ID生成问题。在实现高可用性方面,采用水平集群部署、心跳检测等方案为系统保驾护航。该系统目前已在达达商城等项目中使用,每天提供大量服务。
https://blog.csdn.net/hl_java/article/details/78462283
-
自定义注解怎么实现
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME)
-
泛型
https://www.jianshu.com/p/fcc6a37eb5c2
-
spring IOC 对象注入有哪几种方式?如何解决循环依赖?
构造器注入,Set注入、实例工厂注入
-
elasticsearch 能立马查询到吗?
https://blog.csdn.net/chuxue1989/article/details/115657529
-
mybatis 代理模式怎么实现的?
https://www.jianshu.com/p/df9753fb9950
-
分布式锁(zookeeper怎么实现)
https://blog.csdn.net/u010285974/article/details/80556480
-
分布式id(雪花算法如何保证集群下的每个实例机器码不重复)
https://www.jianshu.com/p/3b75c254a7c8
-
docker 如何运行Java项目
https://www.cnblogs.com/levcon/p/12442662.html
-
es原理
https://www.jianshu.com/p/0ddd61c781b3
-
Runnable 和 Thread 关系,Callable、FutureTask 关系,讲讲1.8 中 Future 有什么改进,或者有什么新类?
Runnable 和 Thread 关系
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
Callable具有返回值
FutureTask实现了RunnableFuture接口,同时具有Runnable、Future的能力,即既可以作为Future得到Callable的返回值,又可以作为一个Runnable。
https://blog.csdn.net/kxkxyzyz/article/details/103835224
-
hashcode 和 equals 方法关系和 为啥重写 equals 还要重写 hashcode 呢?
我们采用HashSet构造数据,其中new RewriteEqHashCode(1001, “张三”)我们构造了两次,我们认为这两次是重复的数据,第一次我们同时注释equals()和hashCode()方法测试输出结果没有去重,第二次我们启用equals()方法、注释hashCode()方法测试输出结果任然没有去重,第三次我们同时开启equals()方法、hashCode()方法测试输出结果正常去重了。
因为HashSet判断对象是否相同是根据其hashCode()值比较,而equals()默认比较也是采用hashCode()。当我们什么都不重写,默认创建的对象对应的hashCode值就是不一致的;但是当我们重写equeals()时,说明我们想更改判断对象是否相等的方式,因此我们需要同时重写hashCode(),才能保证对象判断方式是我们想要的效果。这在JDK定义equals()方法时候,注释部分也有相关说明的。
特别提示:
建议大家重写equals()、hashCode()方法时候采用IDEA编辑器自动生成的,不要自己去写,因为自己去写的往往不是很严谨。
https://blog.csdn.net/raoyanhui_java/article/details/100149477
-
怎么理解rocketmq推拉模式?
https://blog.csdn.net/chongshui129727/article/details/101006216
-
AQS了解吗
https://blog.csdn.net/oldshaui/article/details/102692646
-
Mysql索引类型有哪些?
https://blog.csdn.net/liutong123987/article/details/79384395
-
Spring中enable注解的实现原理?例如EnableEurake
https://blog.csdn.net/w47_csdn/article/details/86611237
-
Redis 一个key最大存储多大数据?
512M
-
Redis GEO类型用法?
https://blog.csdn.net/ghosind/article/details/109191613
-
Eurake的原理有看过吗?
https://blog.csdn.net/zhuyanlin09/article/details/89598245
-
Eurake挂了,a服务还能调到b服务吗?
https://blog.csdn.net/weixin_39790738/article/details/110650664
-
聊一下springcloud的组件?
-
Ribbon的负载均衡策略有哪些?https://blog.csdn.net/u013099854/article/details/104005947
- 随机
- 权重
- 轮询
- 一致性哈希
- 负载均衡器
-
Ribbon是怎么实现灰度发布的?
https://blog.csdn.net/u010882691/article/details/82427203
-
-
Redis中bitmap应用场景?
https://blog.csdn.net/qq_26249609/article/details/103563391
-
Redis Cluster负载均衡原理是什么?
https://blog.csdn.net/weixin_43184769/article/details/91443577
-
Cap理论有了解吗?zookeeper是cp还是ap?
CAP:一致性、可用性、分区容错性
zk:CP 强一致性
Eureka:AP 高可用
-
mysql建表如果不声明主键会怎么样,为什么一般要主动声明主键
InnoDB会自动帮你创建一个不可见的、长度为6字节的row_id,而且InnoDB维护了一个全局的dictsys.row_id,所以未定义主键的表都会共享该row_id,每次插入一条数据都把全局row_id当成主键id,然后全局row_id加1。
该全局row_id在代码实现上使用的事bigint unsigned类型,但实际上只给row_id保留了6字节,所以这种设计就会存在一个问题:如果全局row_id一直涨,直到2的48次幂-1时,这个时候再加1,row_id的低48位都会变为0,如果再插入新一行数据时,拿到的row_id就为0,这样的话就存在主键冲突的可能,所以为了避免这种隐患,每个表都需要一个主键。
-
ThreadLocal 里为啥要用 WeakReference
https://blog.csdn.net/cyxinda/article/details/93709143
-
为什么阿里规范不推荐使用Executors创建线程池
https://blog.csdn.net/weixin_41888813/article/details/90769126?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link
-
Spring:
-
@resource 和@Autowire区别
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。(通过类型匹配找到多个candidate,在没有@Qualifier、@Primary注解的情况下,会使用对象名作为最后的fallback匹配)如下:
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
-
BeanFactory 和FactoryBean区别
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
-
如何自定义starter
在resource下创建banner.txt添加自定义starter
-
注解的扫描流程
https://zhuanlan.zhihu.com/p/321249040
-
-
栈溢出和堆溢出如何排查
一)、是否有递归调用
二)、是否有大量循环或死循环
三)、全局变量是否过多
四)、 数组、List、map数据是否过大
五)使用DDMS工具进行查找大概出现栈溢出的位置
-
假设核心线程数10,最大线程数20,阻塞队列100,此时来了120个请求,问此时应用中有多少线程在跑
20个
-
公平非公平锁的实现
https://www.cnblogs.com/little-fly/p/10365109.html