Java中高级开发工程师最新面试题整理

1. 设计一个秒杀系统,30分钟没付款就自动关闭交易

MQ延迟队列,设置ttl30分钟,时间到了取消订单,同时往redis的list中加一个

2. 线上系统突然变得异常缓慢,你如何查找问题

看一下内存是不是占满了,是的话就清,或者把内存整大点。看下是不是其他程序抢CPU了,是的话关了。

3. 后台系统怎么防止请求重复提交

在前端设计的时候提交一次按钮变黑。后端的话,可以发短信验证码,或者使用redis 的setnx 也可以

4. 如果你有一张表 一个月有几千万上亿的数据 你怎么保证每个月获取什么样的数据

用分区表

如果一年前的只是备份待查,分离出来另存.
如果一年前的会用到,但用得少,用分区.
如果一年前的仍然要频繁使用,用分区,但要加一个磁盘. 

Socket是什么呢?
       Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

 TCP的keep alive是检查当前TCP连接是否活着;HTTP的Keep-alive是要让一个TCP连接活久点。

5. httphttps的区别

http超文本传输协议,被用于在web浏览器和网站服务器之间传递信息,http以明文方式发送内容,不进行数据加密,容易被黑客截取盗用,不适合传输一些敏感信息,比如:银行卡卡号,支付密码等。

https安全套接字层超文本传输协议,在http的基础上加入了ssl协议,ssl依靠证书来验证服务器的身份,为浏览器和服务器之间的通信加密

区别:

  1. https协议需要ca申请证书,需要一定的费用
  2. http是超文本传输协议,信息是明文传输,https是具有安全性的ssl加密传输协议
  3. http和https使用的是完全不同的连接方式,端口不一样,http是80,https是443
  4. http的连接简单,是无状态的;https协议是有ssl+http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全

6. 长连接短连接

长连接:HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。

而从HTTP/1.1起,默认使用长连接,用以保持连接特性。在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP协议与TCP/IP协议的关系

HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。 IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP协议是可靠的、面向连接的。

7. TCP连接

当网络通信时采用TCP协议时,在真正的读写操作之前,客户端与服务器端之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时可以释放这个连接。连接的建立依靠“三次握手”,而释放则需要“四次握手”,所以每个连接的建立都是需要资源消耗和时间消耗的。

8. SetHashSetTreeSet

*Set无序、不可重复

*HashSet无序不可重复

什么是HashSet,操作过程是什么?

HashSet底层是一个HashMap,也是采用了hash表数据结构

Hash表又叫做散列表,哈希表底层是一个数组,这个数组中的每一个元素是一个单项列表,每个列表都有一个独有的hash值,代表数组的下标。在某个单向链表中的每一个节点上的 hash 值是相同的。hash 值实际上是 key 调用 hashCode 方法,再通过"hash function"转换成的值。

HashMap 和 HashSet 初始化容量是16,默认加载因子是0.75。

HashSet 完全继承了 Set、Collection 里的方法实现 add、addAll、clear、isEmpty、size、contains、iteretor、remove 等。HashSet 是对 HashMap 的一层封装,类似一维数组,而一维数组里的元素又是一个单链表。

HashMap是如何保证元素唯一的


根据元素的hash值以及调用equals方法实现的。Hashmap在添加元素的时候,首先会计算元素的hash值,得到的结果作为数组的唯一索引。然后去调用equals方法,比较元素的值是否相同,如果相同就说明是同一个对象,就不再添加进去,这样就保证了hashmap的唯一性。对hahset的函数调用都会转换成核是的hashmap方法,同理。

注意:这里计算处理的hash值只是类似于数组的索引,不一定就是数组的索引,这样只是便于理解。在new hashmap的时候,最好是指定大小,因为如果使用默认的大小,当元素填满时,会自动扩容,这时重新计算hash值,会占用大量的资源,影响效率。

*TreeSet有序不可重复

Treeset是一种树形结构,是红黑树,一种近似平衡的二叉树。Treeset除了具有hashset的唯一性外,还具有有序性。Treeset是对treemap的一层封装。

Treemap的有序性是如何做到的?

Treemap是一个红黑树,在添加元素时,有可能会破坏树的平衡,这时会自动的做相应的旋转,来保持树的平衡。

保持平衡的规则是:父节点的值要比左子树的值大,比右子树的值小。如果添进来的元素破坏了平衡,就会做响应的调整,从而保证元素的有序性。对于treeset的函数调用都会转换成合适的treemap方法,同理。

注意:treeset在添加元素时,元素必须有可比较性。由于基本类型的包装类以及string类已经重写了hashcode()和equals(),所以可以直接添加。但是如果添加的是自定义实体类的话,必须要实现comparable接口,或者自定义一个比较器,实现comparator接口,才能添加进去

9.ArrayListHashSet的区别

1) .arraylist是用数组实现的。Hashset的底层是hashmap。调用arraylist的add方法时在数组的下一个位置添加了一个对象。调用hashset的add方法时,实际上是向hashmap增加了一行(key-value键值对),该行的key就是向hashset增加的那个对象,该行的value就是一个object类型的常量。

2) Arraylist中保存的数据是有序的,hashset中保存的数据是无序的

3) Arraylist可以保存重复的数据,而hashset不能。保证hashset的数据是唯一的,要重写equals()和hashCode()。

10.Class类的理解

类的加载过程:

1.程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。

接着使用java.exe命令对某个字节码文件进行解析运行。相当于将某个字节码文件加载到内存中。这个过程就是类的加载。加载到内存中的类,就成为运行时类,这个运行时类,就作为Class的一个实例

  1. 也就是说,Class的实例就对应着一个运行时类
  2. 加载到内存中的运行时类,会缓存一定的时间,此时间之内,可以通过不同的方式来获取 此运行时类

Lambda表达式

(o1,o2) -> Integer.compare(o1,o2)

->:lambda操作符也叫做箭头操作符

->左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)

->右边:lambda体(其实就是重写的抽象方法的方法体)

对称加密算法 DES、3DES、AES、DESX、Blowfish、、RC4、RC5、RC6。

非对称加密算法:RSA、DSA(数字签名用)、ECC(移动设备用)

散列算法(Hash算法---单向加密算法): MD5(Message Digest Algorithm 5):RSA数据安全公司开发的一种单向散列算法,非可逆,相同的明文产生相同的密文。
       SHA(Secure Hash Algorithm):可以对任意长度的数据运算生成一个160位的数值;

11.抽象类和接口区别

含有abstract修饰符的class即为抽象类,abstract 类不能创建实例对象

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中的抽象方法的访问类型可以是publicprotected和(默认类型,虽然

eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5. 抽象类中可以包含静态方法,接口中不能包含静态方法

6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

7. 一个类可以实现多个接口,但只能继承一个抽象类。

 

12.stringbuffstringbuilder

StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

3在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低

 

13.String能被继承吗?为什么用final修饰?

能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。

14.listsetmap区别

List接口的集合主要有:ArrayListLinkedListVector

ArrayList是一个动态数组,也是我们最常用的集合,ArrayList是一个动态数组,而LinkedList是一个双向链表,Vector是线程安全的动态数组。它的操作与ArrayList几乎一样,查询修改用arraylist,增加删除用likedlist

Set Set是一个继承于Collection的接口,Set是一种不包括重复元素的Collection实现了Set接口的集合有:HashSetTreeSetLinkedHashSet

List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复

实现map的集合有:HashMapHashTableTreeMap

HashMap

以哈希表数据结构实现,HashMap的底层结构在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现

HashMap是线程不安全的,HashTable是线程安全的,其中的方法是Synchronize的,在多线程并发的情况下,可以直接使用HashTabl,但是使用HashMap时必须自己增加同步处理。

HashTable在不指定容量的情况下的默认容量为11,而HashMap16Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。

Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

 

HashTable

也是以哈希表数据结构实现的,

TreeMap

有序散列表,实现SortedMap接口,底层通过红黑树实现。

15.多线程的理解

班级准备大扫除,在大扫除之前,老师在纸上列了一个清单,每个同学都有不同的工作任务,分配好任务之后,每个同学都是有条不紊地完成自己的任务,扫地的同学去扫地,擦黑板的同学去擦黑板,清理桌子的同学清理桌子......在这个例子里,这个清单就是程序,而这个班级的全体同学是一个整体,也就是一个进程,最后,这个班级里面一个个同学就是一个个线程。

继承Thread

实现Runnable接口

实现Callable接口

 

16.@restcontroller和@controller的区别

@RestController注解相当于@ResponseBody @Controller合在一起的作用。

如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。

2) 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
    如果需要返回JSONXML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

17.@Resource@Autowire的区别

@Resource@Autowired都可以用来装配bean,都可以用于字段或setter方法。

@Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false

@Resource默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配。名称可以通过name属性指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,当注解写在setter方法上时,默认取属性名进行装配。

@Autowire@Qualifier配合使用效果和@Resource一样

myISAMInnoDB,锁从表锁到行锁。后者的出现从某种程度上是弥补前者的不足。比如:MyISAM不支持事务,InnoDB支持事务。表锁虽然开销小,锁表快,但高并发下性能低。行锁虽然开销大,锁表慢,但高并发下相比之下性能更高。事务和行锁都是在确保数据准确的基础上提高并发的处理能力。

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁

Innodb是行锁,myisam是表锁

表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低

行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

18.Springboot @SpringBootApplication注解详解

@SpringBootApplication

放置在Springboot启动类上,表明该类是开启Springboot容器的入口,它是一个复合注解。里面包含了包扫描,自动注入,配置注入的功能

@SpringBootApplication注解

 

里有三个注解@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

@SpringBootConfiguration 说明这是一个配置文件类,会被@ComponentScan扫描到。点进去会发现相当于@Configuration

@configuration@bean注解就可以创建一个简单的Spring配置类,用来代替xml配置文件

@Configuration的庄解类标识这个类可使用Spring loC容器作为bean定义的来源。@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象被注册为在Spring应用程序中上下文的bean

@ComponentScan

会自动扫描指定包下全部标有@Component的类,并注册成bean,当然包括@Component下的子注解:@Senvice@Repository,@Controller;默认会扫描当前包和所有子包。

l @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

l @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,

如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

l @ComponentScan:Spring组件扫描。

 

ComponentScan注解的默认扫描范围是:springboot目录及其下面的所有子包。

19.项目的事务问题用Mq解决分布式事务

·  A服务生产消息失败,回滚支付操作,业务回到最初状态,保证了一致性。

·  A服务生产消息成功,完成支付操作,MQ宕机,MQ重启后,消息还存在,订单服务消费消息,完成修改订单操作。保证了一致性。

·  A服务生产消息成功,MQ良好,订单服务消费消息失败。但是消息还存在,等订单服务重启后继续消费消息,保证了一致性。

20.为什么需要熔断降级

 它是系统负载过高,突发流量或者网络等各种异常情况介绍,常用的解决方案。

解决思路

 解决接口级故障的核心思想是优先保障核心业务和优先保障绝大部分用户。比如登录功能很重要,当访问量过高时,停掉注册功能,为登录腾出资源。

熔断是某个服务故障或者是异常引起的;降级是服务器压力剧增的时候,根据当前的业务情况及流量,对服务和页面进行有策略的降级

相同点:

   1)从可用性和可靠性触发,为了防止系统崩溃

   2)最终让用户体验到的是某些功能暂时不能用

不同点:

    1)服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制

   2)触发原因不同,上面颜色字体已解释

21.Rabbitmq的使用场景

解耦,异步处理,削峰

22.Mybatis的延时加载

通常用在多表联级查询。mybatis延时加载 把关联表的查询 拆分 一个个单表查询,关联的表需要时再去查询。提升性能

23.如何解决sql注入

1.采用PreparedStatement预编译语句集的set方法传值,代码的可读性和可维护性.

(2).PreparedStatement尽最大可能提高性能.

(3).最重要的一点是极大地提高了安全性.

2.使用正则表达式过滤传入的参数

3.字符串过滤

4.jsp中调用该函数检查是否包含非法字符

5.jsp页面判断代码

24.对于快速开发平台的理解

你不需要做后台代理,给你需求,给你表,你可以生成代码,生成页面

25.Mybatis的动态sql

mapper配置文件中,有时需要根据查询条件选择不同的SQL语句,或者将一些使用频率高的SQL语句单独配置,在需要使用的地方引用。Mybatis的一个特性:动态SQL,来解决这个问题。

mybatis动态sql语句是基于OGNL表达式的,主要有以下几类:

1. if 语句 (简单的条件判断)

2. choose (when,otherwize) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似

3. trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)

4. where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)、

5. set (主要用于更新时)

6. foreach (在实现 mybatis in 语句查询时特别有用)

26.怎么用mybatis逆向工程

MyBatis的逆向工程指利用MyBatis Generator,可以快速的根据表生成对应的映射文件,接口,以及bean类

导入jar包,配置好正确的pom文件,在pom文件中加入配置,编写MBG的配置文件,这是使用MBG快速生成映射文件,接口,以及bean类的主要操作,也是最重要的步骤。在工程目录下新建MBG的配置文件,命名为mbg.xml

接下来完成对MBG的配置文件的编写。在编写MBG的配置文件的时候,需要注意五处重要的配置,这五个配置也是编写MBG的配置文件最基本的五个步骤。

  1)jdbcConnection:配置数据库连接信息

  2)javaModelGenerator:配置javaBean的生成策略

  3)sqlMapGenerator :配置sql映射文件生成策略

  4)javaClientGenerator:配置Mapper接口的生成策略

  5)table :配置要逆向解析的数据表

      tableName:表名

      domainObjectName:对应的javaBean名

 在编写好了MBG的配置文件之后,就可以运行代码生成器生成代码了。

27.微服务与微服务调用除了feign还有什么

Spring cloud 中服务之间通过restful方式调用有两种方式

  • restTemplate+Ribbon
  • feign

从实践上看,采用feign的方式更优雅(feign内部也使用了ribbon做负载均衡)。

28.网关如何分发每个请求到对应微服务

gateway网关是怎么鉴权

29.数据库四种隔离级别

读未提交,不可重复读,可重复读,串行化,mysql默认的是可重复读

函数式编程是什么

Springboot的核心功能:起步依赖,自动配置

 

30.springboot自动配置原理

在启动器中有一个main方法 ,这个方法是一个标准的java应用的入口方法,一般在main方法中使用springapplicationRun()来启动应用。这个启动器要使用@springbootapplication注解声明,它是springboot的核心注解,这个注解里面,最主要的就是@enableautoconfiguration,点进去看@enableautofiguration的源码,会看到在@enableautofiguration注解里使用了@import注解来完成导入配置的功能,而enableautoconfigurationimportselector内部是使用了SpringFactoriesLoader.loadFactoryNames方法进行扫描具有META-INF/spring.factories文件的jar任何一个springboot应用,都会引入spring-boot-autoconfigure,而spring.factories文件就在该包下面。spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,上面的EnableAutoConfiguration配置了多个类,这些都是Spring Boot中的自动配置相关类;在启动过程中会解析对应类配置信息。每个Configuation类都定义了相关bean的实例化配置。都说明了哪些bean可以被自动配置,什么条件下可以自动配置,并把这些bean实例化出来。如果我们自定义了一个starter的话,也要在该starter的jar包中提供 spring.factories文件,并且为其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置类。所有框架的自动配置流程基本都是一样的,判断是否引入框架,获取配置参数,根据配置参数初始化框架相应组件。



在启动类中有一个main方法,这个方法是一个标准的java应用的入口方法,一般在main方法中使用springapplication。Run()来启动应用。这个启动器要使用@springbootapplication注解声明,它是springboot的核心注解,里面封装了spring注解的复合注解,最重要的有三个@componentscan包扫描,springbootconfiguration当前类具有配置类的作用,enableautoconfiguration注解自动开启配置。点进去enableautoconfiguration,这个注解里有一个import导入AutoConfigurationImportSelector 类,进入源码里面会有一个selectimports选择导入这个类,在这里面有一个getautoconfigurationentry比较重要(导入实体类),这个实体类里有很多的类,在meta-inf/spring。Factories中能够看到,通过getautoconfigurationentry这个方法就开启了自动配置,导入实体类,导入完之后,再调用getcandidateconfigurations获取,获取完之后放到list里。放到list里之后,就找这个配置文件。Springboot启动后,就会加载dispatchservlet

 

31.seata中有两种分布式事务实现方案

Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务

 

ATTCC

AT模式主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题 2PC-改进

TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题

Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样。

32.如何解决分布式事务

项目使用redisrabbitmq解决分布式事务,只能去解决最终一致性,不能解决强一致性,如果要发送的消息失败就会让他发起重试,这样能保证他的最终一致性,然后说一下发送方确认模式和接收方确认模式。强一致性我们用seata框架处理

加锁使用redissetnx命令,同时设置唯一id和过期时间。保证命令必须互斥,设置的key必须要有过期时间,防止崩溃时锁无法释放,value使用唯一id标志每个客户端,保证只有锁的持有者才可以释放锁

加锁直接使用set命令同时设置唯一id和过期时间;其中解锁稍微复杂些,加锁之后可以返回唯一id,标志此锁是该客户端锁拥有;释放锁时要先判断拥有者是否是自己,然后删除,这个需要redislua脚本保证两个命令的原子性执行。

确认生产者将信息投递到MQ服务器中(采用MQ确认机制)

    生产者向MQ发送消息失败,采用重试机制

确认消费者能正确的消费消息,采用手动ACK模式(注意幂等性问题)

    消费者消费消息失败,生产者无需回滚

生产者和消费者都成功,但是生产者后续步骤出现异常,数据库事务回滚

    生产者同时投递到两个队列,第二个队列判断生产者数据是否插入数据库,未插入则执行数据库插入逻辑

Base理论

基本可用,软状态,最终一致性

30.分布式事务解决方案:

XAXA是一个分布式事务协议。XA中大致分为两部分:事务管理器和本地资源管理器。

2PC:两阶段提交协议,将全局事务拆分成两个阶段来执行。阶段一:准备阶段,各个本地事务完成本地事务的准备工作。阶段二:执行阶段,各个本地事务根据上一阶段的执行结果,进行提交或者回滚,会产生单点故障问题和死锁

TCC:解决2pc中的资源锁定和阻塞问题,并且不会有太多的代码入侵。阶段一:准备阶段,资源的检测和预留;阶段二:执行阶段,根据上一步结果,判断下面的执行方法,如果上一步中所有事务参与者都成功,则执行confirm(提交),反之cancal(回滚)

​ XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。

TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。

Tcc优点效率高,缺点业务复杂开发成本高,代码侵入,需要人为的来写tryconfirmcancal代码侵入较多,安全性考虑,如果cancal执行失败,资源就无法释放,需要引入重试机制,而重试又可能导致重复执行,要考虑重试时的幂等性问题

Seata具有局限性,只能是关系型数据库,不支持非关系型数据库。seata中有两种分布式事务实现方案,ATTCC

  • AT模式主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题 2PC-改进
  • TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题

常用的mq产品:rabbitmqspring公司开发基于amqp协议(高级消息队列协议),erlang语言开发,稳定性好,kafka,分布式消息系统,高吞吐量,但是不能保证消息不丢失。Activemq,基于jms,年代较早;rocketmq,基于jms,原来时阿里巴巴的产品,后来交给了apache基金会管理,被孵化了

保证rabbitmq消息百分百不丢失,开启mq消息确认模式

 

Mq的事务模式企业中不使用,超级浪费性能

上下架,商品价格更新,通知搜索,更新索引库

通过mq消息队列解决事务问题,

如果用户下单超过2小时未支付,该怎么取消订单使用延迟队列

用户支付订单,如何保证更新订单状态和扣减库存,三个服务保持数据一致

Mq解决异步,并行,解耦,排队

经典案例,付款,整个流程需要:

1.支付宝付钱2.通知仓库3.成本计算4.财务更新5.会员积分6.送优惠券7.风控归档(记录用户是用什么方式支付,购买商品信息,下单时间等等)。但是为了用户体验感只让用户参与第一步支付宝付钱,解决用户体验,mq的异步功能。Mq的并行功能就是既可以通知仓库,又可以成本计算,又可以财务更新,以下内容都可以同时进行

用户只需要支付宝付钱,然后发消息给mq,由mq监控着其它步骤来完成

Mq的特点是,同一个队列被n多个监听器监听,他们之间是竞争关系,以队列为中心是竞争关系

31.Rabbitmq的工作模式

简单模式,工作模式(竞争的消费者模式,保证最终一致性),发布订阅模式(一次向许多消费者发送消息),路由模式(有选择地接收消息),主题模式(根据主题接收消息),rpc(远程调用),发布确认模式(基于发布定于、路由进行确认)

32.远程调用方式

dubbo(RPC) 远程调用 tcp协议 性能高;

feign远程调用 http协议 功能强大

33.如何保证RabbitMQ消息不丢失?

mq消息丢失会发生在三处位置,第一处:生产者与交换机之间的信道,如果交换机收到消息,交换机给生产者说明下收到,确认模式

第二处交换机与队列之间,队列给生产者说明没有收到,生产者重新发送,(不是给交换机说明),交换机不存消息,交换机发送消息丢失,让生产者重新发送,生产者的重发机制;

第三处队列与消费者之间,队列会持久化消息,直到消费者给队列发送应答确认消息消费掉,队列才会删除消息

设置消费者每次从队列获取原则,设置为公平分发公平分发的意思就是能者多劳,你性能强就你来干。轮询是不公平的

Rabbitmq

Broker消息服务器实体

Exchange 交换机

Queue队列

Binging绑定交换机与队列之间(指定routingkey

信道:消息的通道,一个连接有多个信道(多路复用)nio

交换机发送消息失败后对重发次数进行设定,到次数如果还没成功就取消发送,写入日志告诉管理员,由管理员来进行处理

队列发送消息失败后对重发消息,发消息的时候备份一下,在此将包含交换机、路由key、消息发送到redis中备份一下,选用hash

消费者的消息丢失需要手动应答解决,手动应答参数:消息标记;是否将消息放回队列

自动应答,抛遗产,消息会丢失

34.如何保证消费方消息不丢失

消费方接收到消息,需要手动应答,即使抛出异常消息还会存在队列中,出现异常,选择重发,设定重新发送次数,到达次数选择拒绝应答通过信道向队列应答消费方向队列要消息,如果连续接受几次还是不行,打印错误信息放到日志里,管理员来处理

 消息的ttl就是消息的存活时间

死信交换机:mq本身没有死信队列,是ttl(存活时间)队列加上死信交换机。死信交换机专门接收过期的队列消息

发普通消息:

  1. 创建queue
  2. 创建交换机
  3. 绑定队列到交换机(指定routingkey

发延迟消息

方式

  1. 基于死信 代码较多 交换机2routingkey2个 队列2
  2. 基于延迟插件(交换机 队列 绑定)

Jdk动态代理要求实现类有接口 注入的必须是接口

Cblib动态代理 既可以注入接口也可以注入实现类 性能跟jdk比是差的 无敌版

幂等性问题:不能重复提交;不能修改已支付订单

  1. 查询订单状态
  2. 订单状态为未支付
  3. 如果是未支付改为已关闭 否则不做任何操作

用户下了订单 不给钱 订单也不删除    逻辑删除 假删除 将删除状态由1改为0

35.如何保证消息不被重复消费或者说,如何保证消息消费时的幂等性?

在写入消息队列的数据做唯一标示,消费消息时,根据唯一标识判断是否消费过

36.RabbitMQ分布式事务是用什么实现的?

Rabbit mq分布式事务实现思路:生产者向Rabbit mq发送消息,消费者此时不消费消息生产者接收Rabbit mq返回的消息确认接收成功通知(ACK)生产者判断ACK,

执行本地事务。本地事务执行失败,往Redis缓存里面存入消息ID,消费者消费前查询是否存在该ID,如果存在则不消费,否则消费。本地事务执行成功,消费者消费消息

37.RabbitMQ接收到消息之后丢失了消息

丢失的原因:RabbitMQ接收到生产者发送过来的消息,是存在内存中的,如果没有被消费完,此时RabbitMQ宕机了,那么再次启动的时候,原来内存中的那些消息都丢失了。

解决办法:开启RabbitMQ的持久化。当生产者把消息成功写入RabbitMQ之后,RabbitMQ就把消息持久化到磁盘。结合上面的说到的confirm机制,只有当消息成功持久化磁盘之后,才会回调生产者的接口返回ack消息,否则都算失败,生产者会重新发送。存入磁盘的消息不会丢失,就算RabbitMQ挂掉了,重启之后,他会读取磁盘中的消息,不会导致消息的丢失。

38.如何保证RabbitMQ消息的顺序性?

拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

Rabbitmq7官网上7种工作模式,简单,工作,发布订阅,路由,主题,rpc,发布者确认。秒杀活动中使用rabbitmq的工作模式C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)

主题模式是路由模式的一种,就是路由查询的一种模糊匹配

 

39.如何确保消息正确地发送至 RabbitMQ?

将信道改成confirm模式(发送确认模式),所有在信道上发布的消息会被指派一个唯一id,如果消息进入到了队列,或者被写入磁盘持久化,信道会给生产者回复确认。如果mq内部发生内部错误导致消息丢失,会发送一条nack消息(未确认消息)。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

40.如何确保消息接收方消费了消息?

消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ 才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ 仅通过 Consumer 的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ 给了 Consumer 足够长的时间来处理消息。保证数据的最终一致性;

41.如何解决消息队列的延时以及过期失效问题?

先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。

42.什么是反射?

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

43.哪里用到反射机制?

1.JDBC中,利用反射动态加载了数据库驱动程序。

2.Web服务器中利用反射调用了Sevlet的服务方法。

3.Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。

4.很多框架都用到反射机制,注入属性,调用方法,如Spring

44.反射机制的优缺点?

优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。

缺点:对性能有影响,这类操作总是慢于直接执行java代码。

45.如何使用Java的反射?

1).通过一个全限类名创建一个对象

Class.forName(“全限类名”); 例如:com.mysql.jdbc.Driver Driver类已经被加载到 jvm中,并且完成了类的初始化工作就行了

类名.class; 获取Class<> clz 对象

对象.getClass();

2).获取构造器对象,通过构造器new出一个对象

Clazz.getConstructor([String.class]);

Con.newInstance([参数]);

通过class对象创建一个实例对象(就相当与new类名()无参构造器)

Cls.newInstance();

3).通过class对象获得一个属性对象

Field c=cls.getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。

Field c=cls.getDeclaredFields():获得某个类的所有声明的字段,即包括publicprivateproteced,但是 不包括父类的声明字段

4).通过class对象获得一个方法对象

Cls.getMethod(“方法名”,class……parameaType);(只能获取公共的)

Cls.getDeclareMethod(“方法名”);(获取任意修饰的方法,不能执行私有)

M.setAccessible(true);(让私有的方法可以执行)

让方法执行

1). Method.invoke(obj实例对象,obj可变参数);-----(是有返回值的)

46.redis常用命令

  set key1 value1 设置key

  get key1    获取key

  del key1   删除key

  exists key      判断是否存在key

  persist key     删除过期时间

  setnx key value           不存在就插入(not exists

  setex key time value      过期时间(expire

  String:

    set name cxx

       get name

     incr age        递增

       incrby age 10   递增

       decr age        递减

    decrby age 10   递减

  Hash

    hgetall myhash               获取所有的

      hexists myhash name          是否存在

       hsetnx myhash score 100      设置不存在的

       hincrby myhash id 1          递增

       hdel myhash name             删除

       hkeys myhash                 只取key

       hvals myhash                 只取value

       hlen myhash                  长度

  List

     lpush mylist a b c  左插入

       rpush mylist x y z  右插入

    lrem mylist count value  删除

    lpop mylist  弹出元素

       rpop mylist  弹出元素

 Set

   sadd myset redis

      smembers myset       数据集合

      srem myset set1         删除

      sismember myset set1 判断元素是否在集合中

47.为什么Redis是单线程的

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。

48.Redis内存满了怎么办

Redis安装目录下面的redis.conf配置文件中修改最大内存大小

Redis内存淘汰策略,默认对于写请求不再提供服务,直接返回错误。改为所有key中使用LRU算法进行淘汰,或者在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰

遵循LRU,清除掉最近最少使用的数据,如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉。 

或者TTL 设置过期时间,清除掉key过期的数据

或者LFU是redis4.0新加的一种淘汰策略。根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。避免了LRU的一种情况,LRU算法,一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。

49.Redis为什么很快

(一)纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度快;

(二)单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

(三)采用了非阻塞I/O多路复用机制

(四)灵活多样的数据结构,针对不同的场景使用对应的数据类型,减少内存使用的同时,节省网络流量传输。

redis内部使用一个redisObject对象来表示所有的key和value。redisObject主要的信息包括数据类型、编码方式、数据指针、虚拟内存等。它包含String,Hash,List,Set,Sorted Set五种数据类型,针对不同的场景使用对应的数据类型,减少内存使用的同时,节省网络流量传输。

(五)持久化

由于redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB持久化(原理是将redis在内存中的数据库记录定时 dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将redis的操作日志以追加的方式写入文件)。持久化似乎和redis的速度快并没有直接关系,但是这保证的redis数据的安全性和可靠性,也起到数据备份的作用。

50.Redis的其他数据类型

BloomFillterbloom过滤器

是通过一个大型位数组和几个不同的hash函数来实现的,把它理解为一个不精确的set,使用 set 可以帮我们判断集合中是否已经存在某些元素并且或者帮我们实现去重功能。

但是,set 提供精确的去重功能的同时也给我们带来了一个更大的问题——空间消耗。

网站去重,垃圾邮件过滤,缓存穿透 ,这三个只要使用 BloomFilter 就能完美解决。

符合了 BloomFilter 的一个特性 他说有的不一定有,他说没有的肯定没有,我说你这个 ID 在数据库不存在那就真的不存在,老子把你过滤了就是这么自信,怎么,你打我???

HyperLogLog

会存在误差的数据结构 HyperLogLog。

能提供不精确的去重计数方案(误差值在 0.81% 左右),不精确就不精确哇,UV 要你多精确?0.81%我们也能接受。最重要的是 HyperLogLog 只占用 12KB 的内存。

BitMap

让你统计一年内多个用户之间他们同时在线的天数,这个时候你怎么办?

,在 Redis 中 bitmap 底层就是 string,也可以说 string 底层就是 bitmap。

GeoHash

GeoHash 常用来计算 附近的人,附近的商店。

试想一下如果我们使用 关系数据库 来存储某个元素的地址 (id,经度,纬度) 。这个时候我们该如何计算附近的人?难道我们要遍历所有元素位置并做距离计算?这显然不可能。

当然你可以使用划分区域并使用 SQL 语句圈出区域,然后建立 双向复合索引 来提升性能,但是数据库的并发能力毕竟有限,我们能不能使用 Redis 来做呢?

答案是可以的,Redis 中使用了 GeoHash 提供了很好的解决方案。具体原理是将地球看成一个平面,并把二维坐标映射成一维(精度损失的原因)。

51.如何解决redis命中问题

命中:可以直接通过缓存获取到需要的数据。

不命中:无法直接通过缓存获取到想要的数据,需要再次查询数据库或者执行其它的操作。原因可能是由于缓存中根本不存在,或者缓存已经过期。

通常来讲,缓存的命中率越高则表示使用缓存的收益越高,应用的性能越好(响应时间越短、吞吐量越高),抗并发的能力越强。

需要在业务需求,缓存粒度,缓存策略,技术选型等各个方面去通盘考虑并做权衡。尽可能的聚焦在高频访问且时效性要求不高的热点业务上(如字典数据、session、token)通过缓存预加载(预热)、合理调整缓存有效期的时间 (避免同时失效)、增加存储容量、调整缓存粒度、更新缓存等手段来提高命中率。

52.redis分布式锁,上锁和解锁过程

命令必须保证互斥

设置的 key必须要有过期时间,防止崩溃时锁无法释放

value使用唯一id标志每个客户端,保证只有锁的持有者才可以释放锁

加锁直接使用set命令同时设置唯一id和过期时间;其中解锁稍微复杂些,加锁之后可以返回唯一id,标志此锁是该客户端锁拥有;释放锁时要先判断拥有者是否是自己,然后删除,这个需要redislua脚本保证两个命令的原子性执行。

53.Redis中两种持久化机制RDBAOF

 

使用setnx命令(key不存在时,创建并设置value 返回1,key存在时,会反回0)来获取锁,

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb
    量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。

确保分布式锁可用,至少要保证锁的实现能满足四个条件

互斥性。在任意时刻,只有一个客户端能持有锁。

不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

54.缓存最常见的3个问题:

1. 缓存穿透

2. 缓存雪崩

3. 缓存击穿

缓存穿透是指查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录,并且出于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决:空结果也进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存击穿是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

与缓存雪崩的区别:

1. 击穿是一个热点key失效

2. 雪崩是很多key集体失效

解决:锁

55.数据库锁

行锁

就是一锁锁一行或者多行记录,mysql的行锁是基于索引加载的,行锁是要加在索引响应的行上,即命中索引,当其他事务访问数据库同一张表时,被锁定的记录不能被访问。

行锁的特征:锁冲突概率低,并发性高,但是会有死锁的情况出现。

表锁

就是一锁锁一整张表,在表被锁定期间,其他事务不能对该表进行操作,必须等当前表的锁被释放后才能进行操作。表锁响应的是非索引字段,即全表扫描。表锁每次都是锁一整张表,所以表锁的锁冲突几率特别高,表锁不会出现死锁的情况。

记录锁

是在行锁上衍生的锁,记录锁锁的是表中的某一条记录,记录锁的出现条件必须是精准命中索引并且索引是唯一索引,

间隙锁

又称之为区间锁,每次锁定都是锁定一个区间,属于行锁,间隙锁的触发条件必然是命中索引的,当查询数据用范围查询而不是相等条件查询时,查询条件命中索引,并且没有查询到符合条件的记录,此时就会将查询条件中的范围数据进行锁定(即使是范围库中不存在的数据也会被锁定),

间隙锁只会出现在可重复读的事务隔离级别中

mysql的行锁默认就是使用的临键锁,临键锁是由记录锁和间隙锁共同实现的,临键锁的触发条件是查询条件命中索引,有匹配到数据库记录;

56.索引的优缺点

a.提高检索速度

b.会占用额外空间

c.更新表的时候速度会变慢

57.数据库使用了一段时间,系统查询速度变慢,怎么进行优化,是需要所有的sql都要分析吗

不用全部优化,使用慢查询日志定位,查询速度最慢的,查询次数最多的进行优化,找运维来做,我没有这个权限。

58.怎么进行系统的mysql数据的优化

先找运维人员在生产环境下开启慢查询日志,记录下慢查询日志,过个一两周的时间通过分析工具,我查到最慢的前五个sql,然后再查询查询次数最多的前五个sql,集中分析,用explain看看存在什么问题,有针对性的建索引优化。

59.数据库出现诡异问题

使用全局日志查看

Mysql主从复制

是从接入点开始复制

Binlog主从复制的格式分别有几种,他们各自的特点时什么

默认的statement,在binlog中会记录所有的写操作sql,但凡写操作sql中存在函数,有可能造成主从复制不一致

Row行模式,记录执行完sql后每一行的改变,但凡整表或者大部分数据进行更新,效率问题太低

Mixed,但凡sql有系统变量,也会出现主从复制不一致

60.怎么保证你的代码质量

首先我们会有成文的编码规范开发者手册,规范代码编写,保证代码的可读性,不编写大段的代码,编写注释

可变更性,提高代码的可复用性,利用一些设计模式来降低代码的耦合度

可维护性,代码不会写死(路径为相对路径),将某些条件设置为可配置的,需要必要的注解

61.Mysql常用的函数

聚合函数

COUNT(col)   统计查询结果的行数

MIN(col)   查询指定列的最小值

MAX(col)   查询指定列的最大值

SUM(col)   求和,返回指定列的总和

AVG(col)   求平均值,返回指定列数据的平均值

数值型函数

数值型函数主要是对数值型数据进行处理,

ABS(x)   返回x的绝对值

BIN(x)   返回x的二进制

CEILING(x)   返回大于x的最小整数值

EXP(x)   返回值e(自然对数的底)的x次方

FLOOR(x)   返回小于x的最大整数值

GREATEST(x1,x2,...,xn)   返回集合中最大的值

LEAST(x1,x2,...,xn)   返回集合中最小的值

LN(x)   返回x的自然对数

LOG(x,y)   返回x的以y为底的对数

MOD(x,y)   返回x/y的模(余数) 作者:程序猿诸葛 https://www.bilibili.com/read/cv3838568/ 出处:bilibili

字符串函数

字符串函数可以对字符串类型数据进行处理,在程序应用中用处还是比较大的,同样这里列举几个常用的如下:

 

LENGTH(s)   计算字符串长度函数,返回字符串的字节长度

CONCAT(s1,s2...,sn)   合并字符串函数,返回结果为连接参数产生的字符串,参数可以是一个或多个

INSERT(str,x,y,instr)   将字符串str从第x位置开始,y个字符长的子串替换为字符串instr,返回结果

LOWER(str)   将字符串中的字母转换为小写

UPPER(str)   将字符串中的字母转换为大写

LEFT(str,x)   返回字符串str中最左边的x个字符

RIGHT(str,x)   返回字符串str中最右边的x个字符

TRIM(str)   删除字符串左右两侧的空格

REPLACE   字符串替换函数,返回替换后的新字符串

SUBSTRING   截取字符串,返回从指定位置开始的指定长度的字符换

REVERSE(str)   返回颠倒字符串str的结果

日期和时间函数

CURDATE CURRENT_DATE   两个函数作用相同,返回当前系统的日期值

CURTIME CURRENT_TIME   两个函数作用相同,返回当前系统的时间值

NOW SYSDATE   两个函数作用相同,返回当前系统的日期和时间值

UNIX_TIMESTAMP   获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数

FROMUNIXTIME   UNIX 时间戳转换为时间格式,与UNIXTIMESTAMP互为反函数

MONTH   获取指定日期中的月份

MONTHNAME   获取指定日期中的月份英文名称

DAYNAME   获取指定曰期对应的星期几的英文名称

DAYOFWEEK   获取指定日期对应的一周的索引位置值

WEEK   获取指定日期是一年中的第几周,返回值的范围是否为 052 153

DAYOFYEAR   获取指定曰期是一年中的第几天,返回值范围是1~366

DAYOFMONTH   获取指定日期是一个月中是第几天,返回值范围是1~31

YEAR   获取年份,返回值范围是 19702069

TIMETOSEC   将时间参数转换为秒数

SECTOTIME   将秒数转换为时间,与TIMETOSEC 互为反函数

DATE_ADD ADDDATE   两个函数功能相同,都是向日期添加指定的时间间隔

DATE_SUB SUBDATE   两个函数功能相同,都是向日期减去指定的时间间隔

ADDTIME   时间加法运算,在原始时间上添加指定的时间

SUBTIME   时间减法运算,在原始时间上减去指定的时间

DATEDIFF   获取两个日期之间间隔,返回参数 1 减去参数 2 的值

DATE_FORMAT   格式化指定的日期,根据参数返回指定格式的值

WEEKDAY   获取指定日期在一周内的对应的工作日索引 作者:程序猿诸葛 https://www.bilibili.com/read/cv3838568/ 出处:bilibili

流程控制函数

流程控制类函数可以进行条件操作,用来实现SQL的条件逻辑,允许开发者将一些应用程序业务逻辑转换到数据库后台,列举如下:

 

IF(test,t,f)   如果test是真,返回t;否则返回f

IFNULL(arg1,arg2)   如果arg1不是空,返回arg1,否则返回arg2

NULLIF(arg1,arg2)   如果arg1=arg2返回NULL;否则返回arg1

CASE WHEN[test1] THEN [result1]...ELSE [default] END   如果testN是真,则返回resultN,否则返回default

CASE [test] WHEN[val1] THEN [result]...ELSE [default]END   如果testvalN相等,则返回resultN,否则返回default 作者:程序猿诸葛 https://www.bilibili.com/read/cv3838568/ 出处:bilibili

62.数据库三范式

第一范式列不可再分

1.每一列属性都是不可再分的属性值,确保每一列的原子性

2.两列的属性相近或相似或一样,尽量合并属性一样的列,确保不产生冗余数据

第二范式属性完全依赖于主键

第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主键

每一行数据只能 与其中一列相关,即一行数据只做一件事。只要数据列中出现数据重复,就要把表拆分开来

第三范式(3NF)属性不依赖于其它非主属性    属性直接依赖于主键

数据不能存在传递关系,即每个属性都跟主键有直接关系而不是间接关系。像:a-->b-->c  属性之间含有这样的关系,是不符合第三范式的。

63.Mysql的事务隔离级别及对应并发问题

脏读:事务a查询到别的事务还没有提交的数据

幻读:同一个事务中多次执行同一条语句,查询结果出现前后不一致的情况。指并发新增删除这种会产生数量变化的操作时,另一个事务前后查询相同数据时的不符合预期

不可重复读:同一次事务中前后查询的结果不一致。指并发更新时,另一个事务前后查询相同数据时的数据不符合预期

读未提交 会出现脏读,一个事务可以读取到另一个事务还未提交的数据

读已提交 不可重复读,只能读取到其他事务已经提交的数据 那些没有提交的数据是读不出来的

可重复读 幻读,让事务在自己的会话中重复读取数据 并且不会出现结果不一样的状况

串行化 都能解决

64.Mysql为什么使用innodb

支持事务,性能稳定,但是相较于myisam就差很多

65.mysql索引类型

1.普通索引

2.唯一索引

3.主键索引

4.组合索引

5.全文索引

66.复合索引怎么用

创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

67.主键索引的特点

 主键索引在内存中是可以存放信息的,而其它的索引是指向主键的,查其它索引的时候是先查询的主键找到的数据

68.有索引但是还是还是查询很慢是什么原因

是否使用索引和是否进入慢查询之间并没有必然的联系。使用索引只是表示了一个SQL语句的执行过程,而是否进入到慢查询是由它的执行时间决定的,而这个执行时间,可能会受各种外部因素的影响。换句话来说,使用了索引你的语句可能依然会很慢。

69.索引没有失效,但是没有走到索引的原因

mysql 通过索引扫描的行记录数超过全表的10%~30% 左右,优化器也可能不会走索引,自动变成全表扫描。

两张表关联查询,关联字段的数据类型不一致会出现没有用到索引的情况

70.复合索引怎么匹配的

根据最佳做前缀匹配

 

覆盖索引

SQL只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。

71.Sql的优化

首先是数据库的设计要合理可以考虑适当添加冗余字段,其次是sql语句要合理,尽量少使用innot in,不等于,这些导致索引失效的东西,然后在常查询的字短添加索引,还有合理运用索引覆盖,避免回表等技巧。

索引优化规则

a.全值匹配我最爱

where条件后面有多少个查询条件 就创建多少个字段的复合索引

b.最佳左前缀法则

带头大哥不能死 中间兄弟不能断,指的是查询从索引的最左前列开始并且不跳过索引中的列

c.索引失效的情况

索引列不要做任何操作

使用了不等于

使用了IS NOT NULL

like通配符以%开头

类型转换

一般性建议

a.对于单键索引,尽量选择针对当前query过滤性更好的索引(身份证号,手机号)

b.在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。这样过滤后面数据就会更少

c.在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引----全值匹配我最爱

d.在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面

e.书写sql语句时,尽量避免造成索引失效的情况

三 关联表查询优化

1.当我们有left join的时候

驱动表(left join左边)没有办法避免全表扫描,被驱动表是可以的(找相应字段建立索引)

2.当使用inner join的时候

MySQL可以找到有索引的表作为被驱动表

3.优化总结

a、保证被驱动表的join字段已经被索引

bleft join 时,选择小表作为驱动表,大表作为被驱动表(可以创建索引)

3inner join 时,mysql会自己帮你把小结果集的表选为驱动表。

4、子查询(临时表)尽量不要放在被驱动表,有可能使用不到索引。

5、能够直接多表关联的尽量直接关联,不用子查询,没有办法使用索引。

72.在什么情况下会导致索引失效

没有在索引列上做任何操作;使用了不等于;使用了is not null;like通配符以%开头;类型转换

73.B-TREEB+TREE的区别

a.B+TREE的结构特点

非叶子节点存放主键 没有数据

叶子节点有存放数据

存在子节点地址

b.优点

由于非叶子节点没有存储数据 相同大小的磁盘块可以存储更多的索引节点

相同数据B+TREE比B-TREE深度要小 读取磁盘次数也要少 性能更高

74.哪些情况需要创建索引

主键自动建立唯一索引

频繁作为查询条件的字段应该创建索引

两个表之间存在外键关联,那这个外键也应该建立索引

单键索引与组合索引相对比,组合索引性价比更高

查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

查询中统计或者分组字段

75.哪些情况不要创建索引

表记录太少

经常增删改的表或者字段

Where条件里用不到的字段不创建索引

过滤性不好的不适合建索引

性别 就男 女 未知

76.MyISAMInnoDB

a.外键 InnoDB支持外键

MyISAM不支持外键

b.事务

InnoDB支持事务

MyISAM不支持事务

c.索引

InnoDB会缓存索引还有数据   聚簇索引

MyISAM会缓存索引           非聚簇索引

d.锁

InnoDB采用的是行锁,操作时只锁某一行,不对其它行有影响,适合高并发操作

MyISAM采用的是表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作

e.缓存 InnoDB不仅缓存索引还要缓存真实数据,堆内存要求较高,而且内存大小对性能有绝对性影响

MyISAM只缓存索引,不缓存真实数据

d.如何选择

InnoDB频繁修改 有事务 大量对表的操作

MyISAM经常做查询的情况 没有事务

聚簇索引,就是指主索引文件和数据文件为同一份文件,聚簇索引主要用在Innodb存储引擎中。如主键。B+Tree的叶子节点上的data就是数据本身。
非聚簇索引就是指B+Tree的叶子节点上的data,并不是数据本身,而是数据存放的地址

77.索引种类

  • 普通索引:仅加速查询
  • 唯一索引:加速查询 + 列值唯一(可以有null)
  • 主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个
  • 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
  • 全文索引:对文本的内容进行分词,进行搜索

Eureka注册中心,负责服务的注册与发现。Eureka client负责将服务的信息注册到eurkea server中。Eureka server,注册中心,里面有个注册表,保存各个服务所在机器和端口号。

seata将一个本地事务作为一个分布式事务的分支,若干个分支分布在不同的微服务上,组成一个全局事务.seata包含三个结构:

Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态 ,负责协调驱动全局事务的提交或者回滚.

Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

78.注册中心用的是什么?

Nacos

Nacos 服务注册与订阅的完整流程

Nacos 客户端进行服务注册有两个部分组成,一个是将服务信息注册到服务端,另一个是像服务端发送心跳包,

这两个操作都是通过 NamingProxy 和服务端进行数据交互的。

Nacos 客户端进行服务订阅时也有两部分组成,一个是不断从服务端查询可用服务实例的定时任务,另一个是不断从已变服务队列中取出服务并通知

EventListener 持有者的定时任务。

79.Nacos服务注册方法

服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。

Nacos心跳机制就是客户端通过schedule计划表定时向服务端发送一个数据包 ,然后启动-个线程不断检测服务端的回应,如果在设定时间内没有收到服务端的回应,则认为服务器出现了故障。Nacos服务端会根据客户端的心跳包不断更新服务的状态。

同时支持AP和CP模式,他根据服务注册选择临时和永久来决定走AP模式还是CP模式,

    他这里支持CP模式对于我的理解来说,应该是为了配置中心集群,因为nacos可以同时作为注册中心和配置中心,

    因为他的配置中心信息是保存在nacos里面的,假如因为nacos其中一台挂掉后,还没有同步配置信息,

    就可能发生配置不一致的情况., 配置中心的配置变更是服务端有监听器,配置中心发生配置变化

    然后服务端会监听到配置发生变化,从而做出改变

80.分布式与微服务的区别

将一个很大的系统划分为多个业务模块,部署到不同的服务器上,各个业务模块通过接口进行数据交换。区别分布式的方式是根据不同机器的不同业务。差异是微服务的应用不一定是分撒在多个服务器上的,也可以是一个服务器。只是部署方式不一样。分布式属于微服务

RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。

1.8默认的是 UseParallelGC
ParallelGC 默认的是 Parallel Scavenge(新生代)+ Parallel Old(老年代)

81.sleep和wait的区别

线程调用sleep会进入TIMED_WAITTING状态 不会释放锁;线程调用wait会进入waitting状态,释放锁。

当一个线程进入另一个线程拿到synchronized锁的时候会进入blocked状态,拿到lock锁的时候会进入waitting状态

82.synchronized的用法

1.同步代码块

          synchronized (对象){   // synchronized (对象) 可以理解得到对象的监视权

             // 需要被同步(上锁)的代码;

          }

2.synchronized还可以放在方法声明中,表示整个方法-为同步方法

          public synchronized void show (String name){

               //需要被同步(上锁)的代码

          }

同步方法如果没有使用static修饰:默认锁对象为this

如果方法使用static修饰,默认锁对象:当前类.class

83.volatile

被设计用来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

volatile的作用是确保不会因编译器的优化而省略某些指令,volatile的变量是说这变量可能会被意想不到地改变,每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份,这样,编译器就不会去假设这个变量的值了。

wait()

释放占有的对象锁,线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。

sleep()不同的是,线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,线程重新获得cpu,执行代码。

wait()sleep()最大的不同在于wait()进入waitting状态,释放对象锁,而sleep()会进入timed_waitting状态,不释放锁

notify()

该方法会唤醒因为调用对象的wait()而等待的线程,其实就是对对象锁的唤醒,从而使得wait()的线程可以有机会获取对象锁。调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。需要注意的是,wait()notify()必须在synchronized代码块中调用。

notifyAll()则是唤醒所有等待的线程。

84.synchronized原理

底层如果要加锁,会得到一个监视器:

进入加锁代码会有一个 monitor enterexist

如果一个线程已经拿到了监视器monitor 另外一个线程去拿会失败 失败后会进入到一个阻塞(同步)队列

85.Synchronized和lock的区别

  1. lock锁更加面向对象
  2. Synchronized锁进入blocked阻塞状态是被动的,还没有进入同步代码块
  3. Lock锁进入waitting状态是主动的,进入到了同步代码块
  4. synchronized Java内置的关键字,使用后会自动释放锁.Lockjava.util.concurrent.Locks 包下的一个接口,必须要手动释放
  5. Lock可响应中断,而synchronized 不能响应中断,并且Lock提供了更丰富的方法实现
  6. synchronized是一个悲观锁,Lock是一个乐观锁(底层基于volatilecas实现),

syncjdk1.6后做了优化分 偏向锁 轻量锁和 重量级锁

synchronized加锁对象

a.普通方法synchronized,加锁对象 this,方法锁

b.同步静态方法synchronized加锁对象 当前类的.class,类锁

c.同步代码块加锁对象 括号里面指定的对象,对象锁

Synchronized加锁原理

synchronized底层如果要加锁,会得到一个监视器moniter

进入加锁代码会有一个 monitor entermoniter exist

如果一个线程已经拿到了监视器monitor ,它会处于锁定状态,另外一个线程去拿会失败 失败后会进入到一个阻塞(同步)队列

若是对象锁,则每个对象都持有一把自己的独一无二的锁,且对象之间的锁互不影响 。若是类锁,所有该类的对象共用这把锁。

一个线程获取一把锁,没有得到锁的线程只能排队等待;

synchronized 是可重入锁,避免很多情况下的死锁发生。

synchronized 方法若发生异常,则JVM会自动释放锁。

锁对象不能为空,否则抛出NPE(NullPointerException)

同步本身是不具备继承性的:即父类的synchronized 方法,子类重写该方法,分情况讨论: 没有synchonized修饰,则该子类方法不是线程同步的。(PS :涉及同步继承性的问题要分情况)

synchronized本身修饰的范围越小越好。毕竟是同步阻塞。跑不快还占着超车道

synchronized锁与Lock锁区别

lock是一个接口,而synchronizedjava的一个关键字

a.Lock锁更加面向对象

b.synchronized

进入BLOCKED状态是被动 还没有进入到同步代码块,

发生异常时会自动释放占有的锁,因此不会出现死锁;

c.Lock

进入WAITING状态是主动 进入到同步代码块,

lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。

Synchronized是悲观锁,lock是乐观锁

什么是双亲委派机制

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

双亲委派机制的作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

Java栈内存

8中基本数据类型+对象的引用变量+实例方法

(最重要)

创建对象的时候 堆负责分配空间

新生代:老年代=1:2

新生代=eden:from:to 811

eden:创建对象

方法区

静态变量+常量+类信息

谈谈finalize()的理解?

当对象被GC确定为要被回收的垃圾,回收前有GC帮你调用方法

每个对象的finalize方法只会被调用一次

垃圾回收算法

标记压缩算法,先标记后清除。标记的过程其实就是遍历所有的GC Roots 然后将所有GC Roots可达的对象标记为存活的对象。标记被引用的对象,删除未被标记的对象。然后再次扫描,将存活的放在一端,空中完整的区域

老年代使用标记压缩算法

新生代使用复制算法。第一次只对eden区进行回收;第二次对edenfrom进行回收;把原先fromto的名称进行互换,保证to区域为空;如果对象还在存货,则加1

当没有清除够15次时,但是from区已经满了,对象也会被移动到old

复制算法原理

对象产生的时候在eden区new,当eden空间用完时,程序又需要创建对象,这个时候触发JVM垃圾回收,不再被其他对象所引用的对象就会被销毁,然后将存活对象移动到from,下次再触发垃圾回收的时候eden+from作为主战场,存活对象移动到to区(这个时候to变为from) ,原先form变为to,谁空谁为to,有个交换的过程从from到to的过程每次复制一次对象年龄增长一岁,当年龄达到一定年龄(默认15岁)若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”

分代回收算法就是标记压缩算法和复制算法结合使用

如何判断一个对象是否存活?(或者GC对象的判定方法)

可达性分析算法,从GC Roots的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

内存泄漏与溢出的区别

1) 内存泄漏是指分配出去的内存无法回收了。

2) 内存溢出是指程序要求的内存,超出了系统所能分配的范围,从而发生溢出。比如用byte类型的变量存储10000这个数据,就属于内存溢出。

3) 内存溢出是提供的内存不够;内存泄漏是无法再提供内存资源。

GCroots有哪些

GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。

  • Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,.
  • Thread - 活着的线程
  • Stack Local - Java方法的local变量或参数
  • JNI Local - JNI方法的local变量或参数
  • JNI Global - 全局JNI引用
  • Monitor Used - 用于同步的监控对象
  • Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于"JVM持有"的了。

创建线程池的方式

  1. 创建固定数量线程的线程池 去银行柜台办理业务 适用于长期任务,性能更好
  2. 创建单个线程的线程池 任何时候都只有一个线程在执行 保证任务具有一定的顺序性
  3. 创建一个可扩展的线程池 适用于短时异步任务
  4. NewThreadPoolExecutor

OOM:内存溢出,要么是内存分配的少了,要么是应用使用的太多并且使用完后没有释放。

创建线程池的方式

  • 通过Executors工厂方法创建
  • 通过new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定义创建

RunnableCallable的区别

1.是否有返回值,runnable没有返回值

2.是否抛出异常,,runnable的异常是在内部处理,抛不出来

3.实现方法名称不一样 一个是call 一个是run

4.特点

内存泄露

申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。

内存溢出

申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

实际工作的时候用哪一个

线程池不允许使用Executors创建。而是使用ThreadPoolExecutor因为

Executors.newFixedThreadPool();

Executors.newSingleThreadExecutor();

主要是堆积的任务放在队列里面 就会耗费很大的内存,甚至OOM

Executors.newCachedThreadPool();

线程的最大个数 Integer.MAX_VALUE,创建太多的线程也会引起OOM

4.实际生活中用

线程池不允许使用Executors创建。而是使用ThreadPoolExecutor

线程池参数:核心线程数,线程池最大线程数,线程存活时间,存活时间单位,任务队列:存放已经提交但是还未执行的任务,生成线程池线程的工厂,拒绝策略

线程池原理

首先创建线程池的时候默认里面存活0个线程。当提交的任务数小于核心线程数的时候,马上去创建对应的线程数。提交的任务大于核心线程数时,没有被执行的会进入等待队列中等待。

如果等待队列满了之后,提交的数小于最大线程数时,会创建非核心线程执行。提交的数大于最大线程数,线程池会抛出拒绝策略。

拒绝策略

AbortPolicy1是直接比较粗暴的抛出异常,默认方式。CallerRunsPolicy:2是让线程池不能处理的任务交给调用线程池的线程来处理,能够最大程度的处理任务,这种是比较有用的策略。DiscardOldestPolicy:3是抛弃掉等待时间最长的任务,关注最新的任务,时效性强。DiscardPolicy:4是丢弃最新的任务,关注最老的任务

Lock

1.ReentrantLock重入锁

更加面向对象,具有多路通知

2.ReentrantReadWriteLock

用在读多写少的场景

读读共享 读写互斥 写写互斥:一个读写锁只能同时存在一个写锁但是可以存在多个读锁,不能同时存在读锁和写锁

volatile

1.防止JVM重排序

2.程序执行的happens-before原则

实现多线程的四种方式:继承thread类,实现runnable接口,使用callable创建有返回值的线程,使用线程池

谈谈对集合的理解

 Collection是集合最基本的接口,下面有两个接口listsetlist有序可重复,set无序不可重复。List分为arraylistlinkedlistarraylist底层是个可变大小的数组,随机访问效率比较高,linkedlist底层是链表结构,在首尾插入的时候效率比较高,经常用于新增删除。Sethashset无序的和treeset需要排序。Map接口分为hashmap无序,treemap有序。hashtableConcurrenthashmapHashmap的原理是hashmap根据hashcode分配元素存放的位置,如果两个元素相同,分配的hashcode的值相同,就去重写equals方法。

ConcurrentModificationException并发修改异常

Arraylist线程不安全,底层没有加synchronized锁,使用工具类collectionsSynchronizedlist把它封装成线程安全的listvector线程是安全的,效率低

Map必须要保证键值不同,value值可以相同

Hashset无序不可重复,线程不安全的。底层是hashmap,每个map元素的value都相同,通过保证mapkey不同来保证hashset的不可重复,唯一性

CopyOnWriteArrayset是效率高而且安全,使用的是reentrantlock重入锁,在finally中释放锁

Hashmap线程不安全,底层没有加锁。在高并发情况下,hashmap容易引起自旋死锁Hashtable安全效率低,底层加的是synchronized

HashMap在JDK1.8之后:底层实现是数组+链表/红黑树,扩容机制(1)是当table中元素的个数已经达到阈值(table.length*0.75)时并且新添加[index]桶已经是非空,那么table需要扩容为2倍。(2)当添加到[index]下时,发现[index]下的链表结点个数已经达到8个,而table的长度未达到64,此时table.length也会扩容为2倍

Hash表扩容是2倍扩容

hashmap创建对象的时候并没有对哈希表进行初始化。仅对加载因子进行了复制。加载因子的初始值是0.75,

第一次执行put方法存储的shih,初始化哈希表,初始长度是16,

Hashmap底层的哈希表并不是在数组装满后扩容,而是在数组中的元素数>加载因子*数组长度的时候扩容

加载因子是一个控制数组扩容的关键因素,0.75的时候可以让效率和空间取得最好的平衡

JDK8之后,如果链表的元素超过8个并且整个数组的长度达到64位及以上的情况下,链表结构会变成红黑树结构。

红黑树是二叉树的升级版,只会遍历一半的元素,所以查的快

Hashmap添加元素

会调用元素的hashcode方法计算出元素的hash值,然后用hash值计算出索引,这个索引就是元素在数组中的位置。然后判断索引处是否有元素,没有就直接插入数组,如果有会用元素的equals判断两个元素是否相等,不相等,就会插入到老元素的后面,形成链表,相等就会覆盖

ConcurrentHashMapjdk1.81.7中的区别

HashEntry 用来封装散列映射表中的键值对。

jdk1.7中,ConcurrentHashMap是由Segment数组和多个HashEntry数组组成,Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样 。

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,

Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据.Node数据结构很简单,就是一个链表,但是只允许对数据进行查找,不允许进行修改

PUT过程:

如果没有初始化就先调用initTable()方法来进行初始化过程

如果没有hash冲突就直接CAS插入

如果还在进行扩容操作就先进行扩容

如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,

最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环

如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容

我们项目对于购物车的设计主要分为两个部分:用户未登录和已登录两种状态。在添加购物车时需要进行判断用户是否登录,如果未登录,则将购物车的信息放在redis中,采用hash结构存储,key为uuid(用户的临时id)随机生成,field为skuId,value是商品购物项信息,同时在数据库进行持久化操作,将reids的Key(临时用户id)存放到cookie中。用户已登录状态下,cookie中会存放token,根据token我们能获取到用户的id,所以采用userId作为hash结构的key,field为skuId,value是商品购物项信息。在进入购物列表时,会判断用户的登录状态,如果是未登录的话直接返回未登录购物车数据,如果是已登录,就判断是否存在未登录购物车,存在就进行购物车合并,然后再删除未登录购物车数据。

当用户发起支付请求时,我们采用支付宝或者微信支付,以支付宝为例,我们要制作调用支付宝统一下单接口(支付接口)需要的签名和参数,我们先封装了一个AlipayClient配置类,把商户appid,支付宝公钥,开发者私钥,统一下单接口url以及签名方式等默认参数进行传入,从而生成AlipayClient对象交于spring管理。当调用支付时,我们会把订单交易标号、商品码,交易金额以及同步回调地址和异步回调地址等参数传给支付宝,从而生成支付页面,在这个过程中我们会记录支付信息存储到数据库中,当用户扫码支付后,支付宝回给用户跳转页面,主要是我们设定的同步回调地址。平台这块是根据支付宝的异步回调来判断用户是否支付成功,接收到异步回调时我们会验证签名,以校验合法性,同时会使用rabbitmq给订单模块发送一个校验成功的消息,以便于订单模块的后续处理。

支付宝还提供了退款、对账、关闭交易等接口,在项目中也都使用了

商品搜索是根据关键字进行拼配,从已有的数据库中摘录出相关的记录反馈给用户,在我们项目中有两个搜索入口,一个是商品的三级分类还有个就是用户在搜索框中输入关键字。我们主要是采用ES来实现业务,在ES中我们主要存入了分类id与名称、skuId、价格、名称、图片、品牌的id、名称、logo的url、平台属性数据以及热度排名字段等内容,我们把商品名称使用ik分词器进行了中文分词,同时还可以通过平台属性值、分类、价格区间以及品牌等作为搜索过滤条件,给用户展示商品名称、价格和默认图片等信息。

Springmvc运行流程

  1. 前端请求会被dispacherservlet进行拦截
  2. Dispatcherservlet会通过handelermapping找到当前请求响应的处理器
  3. 会通过适配器进行处理器的调用,并拿到返回值
  4. view交给视图解析器进行解析
  5. model设置到request域中进行返回

synchronized当一个线程Block状态 切换到Runnable状态,涉及到系统内核模式的用户模式切换,性能代价比较高

使用juc.atomic包下的atomic系列保证原子性操作,atomic底层机制就是cas

Compare And Swap:cas比较交换

CAS原理

a.cas里面有三个操作数 内存地址 旧的预期值 ,要修改的值

b.当要更新一个值的时候 需要先进行比较 ,如果值相同才进行更换

c.基于硬件级别的原子操作

3.CAS的缺点

a.CPU开销较大,cas的自旋

b.不能保证代码块的原子性

c.ABA问题

CAS容易造成ABA问题。一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。

4.for(;;)和while之间的区别

for循环效率比while高

https://blog.csdn.net/a22222259/article/details/96009106

for(;;)经过编译之后只有一个指令 所以效率较高

ABA问题

a.概念

一个变量从A->B,B->A的过程

synchronized和cas用哪个

a.在并发量确实非常大的情况 用synchronized性能更高

b.synchronized属于悲观锁 cas属于乐观锁

阻塞队列:队列为空 想去取数据 操作会被阻塞

队列为满 想去插入数据 操作依然会被阻塞

阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器。

常见队列有哪些

ArrayBlockingQueue

数组实现的有界阻塞队列,需要指定长度

LinkedBlockingQueue

由链表构成的一个有界队列,长度是Integer.MAX_VALUE

SynchronousQueue

不存储元素的阻塞队列

每一个put操作必须等待take取走,否则不能添加元素

适用于数据传递场景,把数据从一个线程传递给另外一个线程

4.队列之间的区别

ArrayBlockingQueue在put,take操作使用了同一个锁,两者操作不能同时进行,

LinkedBlockingQueue使用了不同的锁,put操作和take操作可同时进行

怎样线上排查oom内存用完了

先查看服务器的大概情况,使用top命令,显示系统中各个进程的资源占用情况,监控linux系统状况,看下内存cpu使用情况。查下java进程突然不服务的原因,看下与业务日志与关系紧密的中间件,组件(比如HSF,Mtop等)的日志,dmesg命令,用来查看开机之后的系统日志,看下一些系统资源与进程的变化信息,能够看出来内存溢出的信息。

用常用的jdk自带工具查看下jvm的状态,再用java dump分析问题

过滤器是在java web中将你传入的request、response提前过滤掉一些信息,然后再传入servlet进行业务逻辑的处理比如过滤掉非法url,

拦截器,是面向切面编程的,就是在service或者一个方法前调用一个方法,或者方法后调用一个方法。动态代理就是拦截器的简单实现

通俗理解:

1)过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)

2)拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预它,通过验证的少点,顺便干点别的东西)

nacos配置中心原理:

Nacos端口号8848

Nacos 服务端保存了配置信息,客户端连接到服务端之后,根据 dataID,group可以获取到具体的配置信息,当服务端的配置发生变更时,客户端会收到通知。当客户端拿到变更后的最新配置信息后,就可以做自己的处理

Nacos 服务端创建了相关的配置项后,客户端就可以进行监听了。

客户端是通过一个定时任务来检查自己监听的配置项的数据的,一旦服务端的数据发生变化时,客户端将会获取到最新的数据,并将最新的数据保存在一个 CacheData 对象中,然后会重新计算 CacheData 的 md5 属性的值,此时就会对该 CacheData 所绑定的 Listener 触发 receiveConfigInfo 回调。

考虑到服务端故障的问题,客户端将最新数据获取后会保存在本地的 snapshot 文件中,以后会优先从文件中获取配置信息的值。

为什么用nacos配置中心

如果微服务架构中没有使用统一配置中心时,所存在的问题:

- 配置文件分散在各个项目里,不方便维护

- 配置内容安全与权限

- 更新配置后,项目需要重启

nacos配置中心工作流程:

nacos的服务端管理页面添加配置文件,在新建配置页面进行配置。再导入nacos配置中心的的依赖,创建yml配置文件,配置服务名称和nacos地址。再创建nacosconfigcontroller进行配置信息的读取,添加value注解。利用@refreshscope注解实现自动刷新。此注解主要用来让这个类下的配置内容支持动态刷新,当应用启动后,修改nacos中的配置内容,这里也会马上生效。

从配置中心读取配置,分以下3步:

1,引入依赖

在生产者中引入依赖:

2 bootstrap.properties 中配置 Nacos server 的地址和应用名

@RefreshScope

refreshscope实现动态刷新底层使用的是scope注解,

@Scope 代表了Bean的作用域,通过底层代码可以看到两个主要属性value 和 proxyMode。proxyMode 这个就是@RefreshScope 实现的本质了。

@RefreshScope 使用就是 @Scope ,其内部就一个属性默认 ScopedProxyMode.TARGET_CLASS。知道了是通过Spring Scope 来实现的那就简单了,我们来看下Scope 这个接口。通过Object get(String name, ObjectFactory<?> objectFactory); 这个方法帮助我们来创建一个新的bean ,也就是说,@RefreshScope 在调用 刷新的时候会使用此方法来给我们创建新的对象,这样就可以通过spring 的装配机制将属性重新注入了,也就实现了所谓的动态刷新。

总结:

1.需要动态刷新的类标注@RefreshScope 注解

2.@RefreshScope 注解标注了@Scope 注解,并默认了ScopedProxyMode.TARGET_CLASS; 属性,此属性的功能就是在创建一个代理,在每次调用的时候都用它来调用GenericScope get 方法来获取对象

3.如属性发生变更会调用 ContextRefresher refresh() -》RefreshScope refreshAll() 进行缓存清理方法调用,并发送刷新事件通知 -》 GenericScope 真正的 清理方法destroy() 实现清理缓存

4.在下一次使用对象的时候,会调用GenericScope get(String name, ObjectFactory<?> objectFactory) 方法创建一个新的对象,并存入缓存中,此时新对象因为Spring 的装配机制就是新的属性了。

在服务提供者、消费者类上使用

注册中心的原理

服务实例在启动时注册到服务注册表,并在关闭时注销

服务消费者查询服务注册表,获得可用实例

服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求

spring过滤器和拦截器的区别

过滤器:是在javaweb中,你传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符.。

拦截器:在面向切面编程中用于在某个方法或字段被访问前,进行拦截在之前或之后加入某些操作。是aop的一种实现策略。

过滤器是servlet的,拦截器是aop的

Nacos注册中心原理:

服务通过nacos server内部的open api进行服务注册,nacos server内部有一个sevice服务的概念,里面有多个instance实例的概念,同时对不同的service服务可以划归到不同的namespace命名空间下去

注册的时候就是在注册表里维护好每个服务的每个实例的服务器地址,包括ip地址和端口号

一旦注册成功之后,服务就会跟nacos server进行定时的心跳,保持心跳是很关键的,nacos server会定时检查服务各个实例的心跳,如果一定时间没心跳,就认为这个服务实例宕机了,就从注册表里摘除了

其他服务会从nacos server通过open api查询要调用的服务实例列表,而且nacos客户端会启动一个定时任务,每隔10s就重新拉取一次服务实例列表,这样如果调用的服务有上线或者下线,就能很快感知到了

最常用的nacos注解

@EnableNacosConfig 注解启用 Nacos Spring 的配置管理服务

@EnableNacosDiscovery 注解开启 Nacos Spring 的服务发现功能

@NacosPropertySource 加载 dataId 为 example 的配置源,并开启自动更新

@NacosValue 注解设置属性值

Nacos注册中心工作流程:

Nacos客户端携带信息向服务端进行注册。客户端定时向服务端发送心跳,告知服务端处于可用状态。服务端会定时检查客户端是否有心跳。超过15s没心跳,会将客户端实例设置为不健康状态。超过30s会剔除改客户端实例。客户端向服务端查询所有注册的服务列表。客户端获取到之后会放入自己的本地缓存,需要使用的时候,先从本地缓存拿,没有再去服务获取。同时还有定时任务,去服务端拉取注册列表,更新本地缓存,如果失败拉取的间隔时间会根据失败次数增加。

eureka不同的是,nacos还有主动推送功能,eureka的注册列表发生改变的时候,eureka是失效读写缓存,等待下一次只读缓存(如果使用了)来更新时,重建读写缓存,或者客户端拉取数据时重建读写缓存。nacos并没有使用多级缓存。当注册列表发生改变时,会发送ServiceChangeEvent事件,然后主动向客户端推送信息

Nacos注册中心步骤:

生产者注册到nacos注册中心,步骤:

1,添加依赖:spring-cloud-starter-alibaba-nacos-discovery及springCloud

2, application.properties 中配置nacos服务地址和应用名

3,通过Spring Cloud原生注解 @EnableDiscoveryClient 开启服务注册发现功能

消费者注册到nacos跟生产者差不多,也分3步:

1. 添加依赖:同生产者

2. 在application.properties中配置nacos的服务名及服务地址:同生产者

3. 在引导类(NacosConsumerApplication.java)中添加@EnableDiscoveryClient注解:同生产者

Hystrix熔断降级

Hystrix 会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。

sentinelhystrix的区别

Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制

sentinel 的侧重点在于:

多样化的流量控制

熔断降级

系统负载保护

实时监控和控制台

隔离设计上的对比

隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离(Bulkhead Pattern)和信号量隔离,其中最推荐也是最常用的是线程池隔离。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead(开销) 比较大,特别是对低延时的调用有比较大的影响。

但是线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。在 Tomcat 之类的 Servlet 容器使用 Hystrix,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。

Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。

熔断降级的对比

Sentinel 和 Hystrix 的熔断降级功能本质上都是基于熔断器模式(Circuit Breaker Pattern)。Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。

Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。

Sentinel 还提供以下的特色功能

轻量级和高性能:

Sentinel 作为一个功能完备的高可用流量管控组件,其核心 sentinel-core 没有任何多余依赖,打包后只有不到 200 KB,非常轻量级。开发者可以放心地引入 sentinel-core 而不需担心依赖问题。同时,Sentinel 提供了多种扩展点,用户可以很方便地根据需求去进行扩展,并且无缝地切合到 Sentinel 中。引入 Sentinel 带来的性能损耗非常小。

流量控制:

Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS、并发调用数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。

Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有:

直接拒绝模式:即超出的请求直接拒绝。
慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

匀速器模式:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。

系统负载保护

Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

实时监控和控制面板

Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。

Sentinel控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。

生态

Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。

Feignribbon的区别

feign  ribbon  Spring Cloud  Netflix 中提供的两个实现软负载均衡的组件,Ribbon Feign 都是用于调用其他服务的,方式不同。Feign 则是在 Ribbon 的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 http 请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。

1.启动类使用的注解不同,Ribbon 用的是@RibbonClientFeign 用的是@EnableFeignClients

2.服务的指定位置不同,Ribbon 是在@RibbonClient 注解上声明,Feign 则是在定义抽象方法的接口中使用@FeignClient 声明。

3.调用方式不同,Ribbon 需要自己构建 http 请求,模拟 http 请求然后使用 RestTemplate 发送给其他服务,步骤相当繁琐。

使用feign调用服务

使用feign来实现服务提供者和服务消费者之间的远程调用

注解定义一个 FeignClient接口,然后调用那个接口就可以了。人家Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,Feign的一个关键机制就是使用了动态代理

首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理

接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心

Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址

最后针对这个地址,发起请求、解析响应

基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求

 

Feign远程调用,使用注解定义一个feign client的接口,直接调用就可以。Feign client会在底层根据注解,跟指定的服务建立连接。

对某个接口定义了 @FeignClient 注解,Feign 就会针对这个接口创建一个动态代理。调用那个接口,本质就是会调用 Feign 创建的动态代理,Feign的动态代理会根据你在接口上的 @RequestMapping 等注解,来动态构造出你要请求的服务的地址。

最后针对这个地址,发起请求、解析响应。

在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果

1)Proxy代理实例,实现了一个加 @FeignClient 注解的远程调用接口;

2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP 请求;

3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者。

Ribbon负载均衡,默认使用round robin随机轮询算法,ribbon的底层封装了loadbalance

Zuul微服务网关

 

Ribbon

Ribbon是和Feign以及nacos紧密协作,完成工作的

首先Ribbon会从 nacos Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。

然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器

Feign就会针对这台机器,构造并发起请求。

服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台

Gateway网关

有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。做统一的降级、限流、认证授权、安全。如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

线上cpu100%怎么查:

查消耗cpu最高的进程PID,执行top-c,显示列表信息,按下p,按cpu使用率进行排序

根据PID查出消耗cpu最高的线程号,执行top -hp 进程最高的pid,显示一个进程的线程运行信息列表。按下P,进程按照cpu使用率排序

根据线程号查出对应的java线程,进行处理。执行命令,导出进程快照,然后执行grep命令,看线程在做了什么,然后输出,定位问题

a.水平拆分

一张表里面的数据分散到多个表里面,按照年/月/日分表(分表)

b.垂直拆分

一个数据库里面的多个表分散到多个数据库里面(分库)

a.B+TREE的结构特点

非叶子节点存放主键 没有数据

叶子节点有存放数据

存在子节点地址

b.优点

由于非叶子节点没有存储数据 相同大小的磁盘块可以存储更多的索引节点

相同数据B+TREE比B-TREE深度要小 读取磁盘次数也要少 性能更高

B+树是B树的变种树,有n棵子树的节点中含有n个关键字,每个关键字不保存数据,只用来索引,数据都保存在叶子节点。是为文件系统而生的。

B+Tree相对于B-Tree有几点不同:

非叶子节点只存储键值信息。

所有叶子节点之间都有一个链指针。

数据记录都存放在叶子节点中。

 

主键用的是聚簇索引,非主键用的是非聚簇索引,之缓存索引不缓存数据。聚簇索引相当于查字典用拼音查,非聚簇用偏旁部首查

 

mysqlES对比

Hashmap初始长度为什么是16,阈值为什么是0.75

Hashmap怎么利用hash值计算出index

为什么要重写hashcode方法和equals方法

1.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率

2.保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。

Jdk8 stream流怎么实现的说一下

函数式的解决方案解开了代码细节和业务逻辑的耦合,类似于sql语句,表达的是"要做什么"而不是"如何去做",使程序员可以更加专注于业务逻辑,写出易于理解和维护的代码。

stream流操作的话,相当于过滤网,筛选

Synch锁的底层

1.基本加锁对象

a.普通方法synchronized,加锁对象 this,方法锁

b.同步静态方法synchronized加锁对象 当前类的.class,类锁

c.同步代码块加锁对象 括号里面指定的对象,对象锁

synchronized不能用在类级别的(静态)代码块

这个要在加载顺序上考虑,因为类级别的代码块在加载顺序上是要优于任何方法的,其执行顺序只跟代码位置先后有关。没人跟你抢,自然不需要同步

进入加锁代码会有一个 monitor enter和exist

如果一个线程已经拿到了监视器monitor 另外一个线程去拿会失败 失败后会进入到一个阻塞(同步)队列

 

synchronized方法的字节码中方法访问标识中有ACC_SYNCHRONIZED,JVM也是根据该标志直接判断是否需要同步的。

 

synchronized代码块的字节码中是用**监视器(monitor)**来实现同步的,且方法中含有多次出栈入栈操作。

 

显然,synchronized方法 比 synchronized代码块 的VM执行效率要高。根据官方的数据,大概是快13%左右。

对于synchronized方法被编译后,其methods的实现和普通方法一样,在VM的字节码层面并没有任何特别的指令来实现synchronized方法,仅仅是在methods的access_flags中多了一个ACC_SYNCHRONIZED,告知VM这是个同步方法.

 

而对于synchronized代码块将会在同步块的入口位置和出口位置分别插入monitorenter和monitorexit字节码指令.

 

同时访问synchronized的静态和非静态方法,能保证线程安全吗?

结论:不能,两者的锁对象不一样。前者是类锁(XXX.class),后者是this

 

2.2.2 同时访问synchronized方法和非同步方法,能保证线程安全吗?

结论:不能,因为synchronized只会对被修饰的方法起作用。

 

2.2.3 两个线程同时访问两个对象的非静态同步方法能保证线程安全吗?

结论:不能,每个对象都拥有一把锁。两个对象相当于有两把锁,导致锁对象不一致。(PS:如果是类锁,则所有对象共用一把锁)

 

2.2.4 若synchronized方法抛出异常,会导致死锁吗?

JVM会自动释放锁,不会导致死锁问题

 

2.2.5 若synchronized的锁对象能为空吗?会出现什么情况?

锁对象不能为空,否则抛出NPE(NullPointerException)

 

2.2.6 若synchronized的锁对象能为空吗?会出现什么情况?

锁对象不能为空,否则抛出NPE(NullPointerException)

 

2.3.1 synchronized涉及的继承性问题

重写父类的synchronized的方法,主要分为两种情况:

 

子类的方法没有被synchronized修饰:

synchronized的不具备继承性。所以子类方法是线程不安全的。

 

子类的方法被synchronized修饰(这里面试点主要考察锁对象的归属问题):

两个锁对象其实是一把锁,而且是子类对象作为锁。这也证明了: synchronized的锁是可重入锁。否则将出现死锁问题。

在开发过程中,你经常使用synchronized方法多还是synchronized代码块?and why?

synchronized同步的范围是越小越好。因为若该方法耗时很久,那其它线程必须等到该持锁线程执行完才能运行。(黄花菜都凉了都…)
synchronized代码块部分只有这一部分是同步的,其它的照样可以异步执行,提高运行效率。

有没有遇到过synchronized失效的问题?

synchronized 虽然用法简单,但是如果锁对象不一致,就会失效。排查问题的时候一定要着重从锁对象是否一致上去判断.

Aop用了哪些东西

动态代理跟java本事自带的代理有什么区别

动态代理使用场景

实际场景应用

2.1 校验用户权限,每一个菜单请求,都要判断一下请求的用户是否有该菜单权限。菜单多了,代码冗余,且容易遗漏。

通过动态代理就可以实现为:每一个用户,每一个菜单的请求,都经过代理(proxy),由他判断是否有权限,调用者只需要调用,实现自己的逻辑,不关心权限问题。

 

使用静态代理会产生什么问题,为什么要使用动态代理

静态代理每次都要重写接口种的方法,态代理使我们免于去重写接口中的方法,着重于去扩展响应的功能和方法的增强,在实际开发环境下,接口中方法很多的时候,就可以发现动态代理的便捷,会大大减少项目中的业务量。

 

cglibInvocationHandler有什么区别

invocationhandler实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,其实现就需要才cglib.cglib采用了非常底层的字节码技术。原理是:通过字节码技术为一个类创建子类。并且在子类中采用方法拦截的技术拦截所有父类的方法的调用。

  cglib创建的动态代理对象性能比JDK创建性能高很多,但是cglib在创建代理对象的时候所花费时间却是比jdk多的多,所以对于单例的对象,其不需要频繁的创建对象,用cglib更合适,反之,用jdk更好。同时,由于cglib是采用动态创建子类的方法,对于final方法,无法进行代理。

 

Ioc怎么解决循环依赖,依赖有多少种,哪些能解决,哪些不能解决

只能解决setter注入的循环依赖

Ioc通过三级缓存来解决循环依赖,IOC无法解决两种循环依赖,一种是非单例对象的,因为非单例对象不会放入缓存的。每次都是需要创建。二是通过构造器注入,也无法解决。从上面的流程可以看出,调用构造器创建实例是在createBeanInstance方法,而解决循环依赖是在populateBean这个方法中,执行顺序也决定了无法解决该种循环依赖。

Spring如何通过三级缓存解决循环依赖

Spring中有三级缓存,分别如下

 

singletonObjects:完成初始化的单例对象的cache(一级缓存)

earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)

singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)

Spring获取一个Bean的流程就说从一级到三级依次去寻找这个Bean

如果一二三级均没有这个Bean,就会走新建方法,Spring创建一个Bean的时候也会有3个步骤,如下

 

createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象.0

populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

initializeBean:调用spring xml中的init 方法。

我们再来说上面的循环依赖的例子Spring的解决方案:

 

A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行第二步填充属性

发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A)

尝试从一级缓存singletonObjects中获取(肯定没有,因为A还没初始化完全)

尝试从二级缓存earlySingletonObjects中获取(也没有)

尝试三级缓存,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过从三级缓存中拿到A的工厂对象,通过这个工厂对象构建出了A的Bean对象,B拿到A对象后顺利完成了初始化阶段1、2、3。B对象完全初始化之后将自己放入到一级缓存singletonObjects中

此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。

为什么构造器循环依赖和多例循环依赖Spring无法解决

构造器循环依赖

循环依赖的解决是实例化后通过三级缓存来解决的,但构造器是在实例化时调用的,此时bean还没有实例化完成。如果此时出现了循环依赖,一二三级缓存并没有Bean实例的任何相关信息,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。总结:spring的三级缓存解决的是实例化之后属性赋值的循环依赖,构造器被调用是在实例化之前,所以无法解决构造器的循环依赖!

 

多例循环依赖

对于prototype作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓存prototype作用域的bean ,因此无法提前暴露一个创建中的bean。

 

为什么不能只用一二级缓存来解决循环依赖?

这里面涉及到的问题很复杂,可以看这篇文章。

总结起来就是几点:一级缓存是放完全初始化好的对象的,如果只需要IOC功能其实一级缓存就能解决。但涉及到循环依赖,我们就需要暴露出一个没有初始化好的对象,所以这个时候就需要引入二级缓存,把初始化好的放到一级缓存里面去,没初始化完全的放到二级缓存里面去。看起来可以完美解决问题了,但如果有代理对象的话,实际流程就会变成下面这样。我们假设上诉例子中A为代理对象

 

A初始化的时候把能够生成A代理对象的一个A工厂对象放到三级缓存中

A发现自己依赖B对象就去生成B对象,B对象发现自己需要A对象,就会去三级缓存中把这个A工厂对象取出来并且生成了A对象的代理对象,然后把代理对象放入到二级缓存,同时赋值B对象中的A,到此B对象构建完毕并放入一级缓存中。

A对象使用构建好的B对象构建自己,A对象构建完毕。

Mybatis的缓存策略有多少种,有哪些缓存策略

一级缓存是SqlSession级别的缓存,默认开启。

二级缓存是NameSpace级别(Mapper)的缓存,多个SqlSession可以共享,使用时需要进行配置开启。

先查二级再查一级最后查数据库

 

为什么要用feign,不用feign还可以用什么

Feign 则是在 Ribbon 的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 http 请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。

1.启动类使用的注解不同,Ribbon 用的是@RibbonClientFeign 用的是@EnableFeignClients

2.服务的指定位置不同,Ribbon 是在@RibbonClient 注解上声明,Feign 则是在定义抽象方法的接口中使用@FeignClient 声明。

3.调用方式不同,Ribbon 需要自己构建 http 请求,模拟 http 请求然后使用 RestTemplate 发送给其他服务,步骤相当繁琐。

 

Spring启动过程大致如下:

1.创建beanFactory,加载配置文件

2.解析配置文件转化beanDefination,获取到bean的所有属性、依赖及初始化用到的各类处理器等

3.刷新beanFactory容器,初始化所有单例bean

4.注册所有的单例bean并返回可用的容器,一般为扩展的applicationContext

 

redis的value是如何序列化的

jackson2JsonRedisSerializer将value序列化写到xml配置文件里

 

一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

Elasticsearch 也是会对数据进行切分,同时每一个分片会保存多个副本,为了保证分布式环境下的高可用。

Elasticsearch 中,节点是对等的,节点间会通过自己的一些规则选取集群的 Master,Master 会负责集群状态信息的改变,并同步给其他节点。

只有建立索引和类型需要经过 Master,数据的写入有一个简单的 Routing 规则,可以 Route 到集群中的任意节点,所以数据写入压力是分散在整个集群的。

Elasticsearch 搭建 ELK 系统,也就是日志分析系统。如果日志接入了 ELK 系统就不一样。比如系统运行过程中,突然出现了异常,在日志中就能及时反馈,日志进入 ELK 系统中,我们直接在 Kibana 就能看到日志情况。如果再接入一些实时计算模块,还能做实时报警功能。

反向索引又叫倒排索引,是根据文章内容中的关键字建立索引。

Elasticsearch 中的索引、类型和文档的概念比较重要,类似于 MySQL 中的数据库、表和行。

 

集合去重的方法:

循环list中的所有元素然后删除重复的;通过hashset剔除重复元素;删除ArrayList中重复元素,保持顺序;把list里的对象遍历一遍,用list.contains(),如果不存在就放入到另外一个list集合中;retainAll和retainAll用法;用JDK1.8 Stream中对List进行去重:list.stream().distinct();

数组转集合:

  1. 遍历,最常用的方法2.使用数组工具类的asList()方法
  2. 遍历2.使用集合的toArray()方法

集合转数组:

什么是微服务?

单个轻量级服务一般为一个单独微服务,微服务讲究的是 专注某个功能的实现,微服务架构系统是一个分布式的系统,按照业务进行划分服务单元模块,解决单个系统的不足,满足越来越复杂的业务需求。

根据业务拆分成一个一个的服务,彻底去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事

微服务的优缺点?

优点:松耦合,聚焦单一业务功能,在开发中,专注于当前功能,便利集中,功能小而精。一个功能受损,对其他功能影响并不是太大,可以快速定位问题

缺点:随着服务数量增加,管理复杂,部署复杂,服务器需要增多,服务通信和调用压力增大,运维工程师压力增大,人力资源增多,系统依赖增强,数据一致性,性能监控。

什么是分布式?

专业的事情交给专业的人去做,尽量降低耦合度(就是说每个模块之间是不受影响的)一个模块你只做一件小事情

微服务与分布式的细微差别是,微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。

什么是熔断?什么是服务降级?

服务熔断的作用类似于家用的服务丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。

服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。

相同点:

目标一致 都是从可用性和可靠性出发,为了防止系统崩溃;

用户体验类似 最终都让用户体验到的是某些功能暂时不可用;

不同点:

触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;

Springclouddubbo的区别

Dubbo 是RPC通信springcloud是基于HTTP的REST方式。

Gateway工作流程

一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。

Spring cloud Gateway发出请求。然后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。

SpringBoot是什么?

spring boot来简化spring应用开发,约定大于配置,去繁从简,快速创建独立运行的spring项目与主流框架集成 

独立运行,springboot内嵌servlet容器,不需要打war包部署到容器中,只需要打一个可以执行的jar包就能运行,所有依赖都在一个jar包里

简化配置,spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。除此之外,还提供了各种启动器,开发者能快速上手。

大量的自动配置,spring Boot能根据当前类路径下的类、jar包来自动配置bean,简化开发,也可修改默认值 
无代码生成和XML配置,Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。

应用监控,Spring Boot提供一系列端点可以监控服务及应用,做健康检测。

SpringCloud是什么?

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理。

举个例子 : 一所医院  boot 是 一个一个科室    cloud 是把一个一个科室 组合起来 对外称之为 医院

存在依赖关系  cloud   离不开boot 

查看端口活跃的命令

Netstat-p

计算未完成 get方法会进行阻塞 ,一旦计算完成 如果还想得到结果 就可以结果复用

canal会把数据发送到MQ的topic中,然后通过消息队列的消费者进行处理.

数组和arraylist的区别

数组和ArrayList的本质区别在于前者是类型安全的,而后者是类型不安全的.

ArrayList为了兼容所有类型对象,使用了Object数组,在使用元素的时候会有装箱和拆箱的操作,降低了程序的性能.

ArrayList会动态扩充容量,容量为原来的2倍.

ArrayList只有把元素添加进去之后才可以通过下标访问相应的元素.

数组在创建的时候就已经确定了数据类型,并且它的长度是固定的,只能通过下标改变各个元素的值和访问.

两者应用场景:

如果已经知道数据的长度并且不需要频繁的做插入和删除操作,建议使用数组,反之亦然.

Springmvc运行流程中处理器映射器的作用handlermapping

作用:根据【请求】找到【处理器Handler】,但并不是简单的返回处理器,而是
将处理器和拦截器封装,形成一个处理器执行链(HandlerExecuteChain)

4.DispatcherServlet 拿着执行链去寻找对应的处理器适配器(HandlerAdapter)

Jdk1.7jdk1.8hashmap的区别

1.底层结构不一样,1.7是数组+单链表,1.8则是数组+单链表+红黑树结构;

2.jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;

3. 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;

4.扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;

5.扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。

1.(单)链表的目的:解决了Key的hash冲突问题;
2. 红黑树的目的:加快查询速度;

hash()

key的哈希值就是自身的hashCode的高16位和低16位进行异或运算得到的。

哈希表的容量被设计为2的N次方,则当某个key的hashCode()值 > table.length,则高位就不会参与到hash的计算。通过hashCode的高16位异或低16位,这样让高位也能参与到hash的计算当中,从而降低hash冲突的风险。

key 为null的hash值?

通过hash()方法,我们看到当key == null 时,其hash值为0。从而保证了key为null只能有一个。

如何确定桶下标?

HashMap中的桶下标是通过 (n-1)& hash 来计算的

位运算如何保证下标不越界呢?

HashMap在设计上其容量必须是2的n次幂,用心良苦啊。

n 是 2的次幂时, n -1 的二进制表示法的尾部都是以连续1的形式来表示的。这样当(n-1)与hash进行 与运算 时,会保留hash中后x 位的1,这样就保证了索引值不会超过数组长度。

==equals的区别

对于==,比较的是值是否相等

            如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;

    如果作用于引用类型的变量,则比较的是所指向的对象的地址

  2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量,equals继承Object类,比较的是是否是同一个对象

    如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

    诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

Deletedrop的区别

DDL:数据定义语言,定义库,表结构等,包括create,drop,alter等

DML:数据操作语言,增删改查数据,包括insert,delete,update,select等

DCL:数据控制语言,权限,事务等管理。

delete

1deleteDML,执行delete操作时,每次从表中删除一行,并且同时将该行的的删除操作记录在redoundo表空间中以便进行回滚(rollback)和重做操作,但要注意表空间要足够大,需要手动提交(commit)操作才能生效,可以通过rollback撤消操作。

2delete可根据条件删除表中满足条件的数据,如果不指定where子句,那么删除表中所有记录。

3delete语句不影响表所占用的extent,高水线(high watermark)保持原位置不变。

drop

1dropDDL,会隐式提交,所以,不能回滚,不会触发触发器。

2drop语句删除表结构及所有数据,并将表所占用的空间全部释放。

3drop语句将删除表的结构所依赖的约束,触发器,索引,依赖于该表的存储过程/函数将保留,但是变为invalid状态。

发生网络波动,支付中重复支付问题

同一个支付平台针对同一笔业务订单生成的流水号是唯一的,第三方支付平台对同一个流水号只进行一次支付,不会重复支付。

在第三方支付中心异步回调的时候,会查支付中心的流水表,按订单号查询支付状态是否为申请支付,是的话,将此订单支付状态改为支付完成,记录到支付中心流水表里。如果不是申请支付状态,对比支付中心流水表中现有记录的第三方平台交易流水号和第三方平台返回的第三方平台交易流水号。如果两者都相同则不进行任何操作,表示是第三方支付的重复通知,直接返回结果即可;如果两者不同,则记录到异常支付单表中。需要运营人员进行线下退款处理或者直接调用退款接口退款。

支付订单增加一个中间状态“支付中”,当同一个订单去支付的时候,先检查有没有状态为“支付中”的支付流水,支付完成以后更新支付流水状态的时候再讲其改成“支付成功”状态。

支付中心这边要自己定义一个超时时间(比如:30秒),在此时间范围内如果没有收到支付成功回调,则应调用接口主动去第三方支付平台查询支付结果,比如10s、20s、30s查一次,如果在最大查询次数内没有查到结果,应做异常处理

支付中心收到支付结果以后,将结果同步给业务系统,可以发MQ,也可以直接调用,直接调用的话要加重试(比如:SpringBoot Retry)

4、无论是支付中心,还是业务应用,在接收支付结果通知时都要考虑接口幂等性,消息只处理一次,其余的忽略

5、业务应用也应做超时主动查询支付结果

前后端分离如何校验接口调用的合法性

在前后台数据交互的时候,通过使用给Json添加token验证的方式来保证自己的接口不会被其他人调用并通过在返回字符串中添加token信息和时间戳的方式来保证接口的安全.

token是存放在服务器中的一个以 < K, V > 形式存放的字符串,在用户登录成功后,后台通过以唯一Id为基础(如userId)自动生成随机数的方式生成一个key(如RedisTokenManager.createToken(user.getId()))存放到K中,V则是存放了后台唯一字符串(当前为userId),这样后台就自动生成了一个Token对象,登录成功后每次数据验证都把Token对象放进去以进行校验,此时,还要对token对象进行设置超时时间,当一段时间客户端无响应的时候,服务器会自动清除服务器的token对象,让用户重新登录.

如何保证信息不被泄露:

在传输的过程中通过对数据进行加密验证 , 生成唯一的sign值,当任何人非法修改传递的参数的时候都会导致sign值不匹配从而造成验证失败.因为sign值是MD5加密而且是唯一的,参数修改会导致验证不通过.

后台验证流程:

  1. 判断返回参数是否有token,Timestamp,sign 这些参数,如果没有直接返回错误.
  2. 判断此URL是否过期,如果过期返回错误.
  3. 把除了sign的参数进行排序并大写,然后把秘钥拼接再排序之后的请求参数之前进行MD5和传过来的sign值进行比对 如果不一致返回错误.
  4. 根据token 取出来userID,如果取不到,说明token 过期,返回数据,让用户重新登录.
  5. 如果验证通过,则表示返回的数据是安全的,继续进行下一步操作.
  6. 客户端提交请求之前,先对自己请求的参数全部进行拼接加密得到一个加密字符串sign
  7. 请求参数加上sign,然后再发送给服务器
  8. 服务器将参数获取后也进行相同的拼接加密得到自己的sign
  9. 比较与客户端发来的sign是否相同
  10. 不相同则是被第三方修改过的,拒绝执行

接口服务数据被劫包,如何防止数据恶意提交

签名:对客户端发来的请求进行签名

防篡改

防重放

1)客户端的请求参数上加一个请求时间timestamp

原理:服务器获取请求的timestamp,然后比较自身系统时间,如果相差超过设定时间就是超时,该请求无效

作用:就算第三方截取了该请求,它也只能在设定时间内进行重放攻击

 关键:

  1. 第三方不知道加密方式和请求参数拼接规则,而客户端与服务器是知道的,因此第三方不知道修改参数后如何生成与服务器生成相同的sign
  2. 只要请求修改了一点点加密得到的就是不同的签名

2)客户端的请求参数上加一个随机字符串string

原理:服务端获取请求的随机字符串string,然后查看是否在设定时间内别的请求使用过该string,被使用过就判定无效

作用:加上timestamp,就造成短时间内一个请求只能使用一次,因为就算请求被拦截,它请求成功一次后,第二次复制重放时就因为随机字符串被使用而被拒绝

Mybatis一对多映射标签

collection

Mybatis一对一映射标签

association

SpringBoot中的Bean初始化方法

@PostConstruct

子类单继承父类,接口可以实现多接口

lambda表达式的用法

lambda表达式,允许我们将行为传到函数中,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。

1.替代匿名内部类

2.使用lambda表达式对集合进行迭代

3.用lambda表达式实现map

4.用lambda表达式实现map与reduce

5.filter操作

6.与函数式接口Predicate配合

优点
使用Lambda表达式可以简化接口匿名内部类的使用,可以减少类文件的生成,可能是未来编程的一种趋势。
缺点
使用Lambda表达式会减弱代码的可读性,而且Lambda表达式的使用局限性比较强,只能适用于接口只有一个抽象方法时使用,不宜调试。

使用Lambda表达式的前提就是:对应的接口有且只有一个抽象方法

Threadlocal

ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

 ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题

Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面。ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。

 同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

死锁

两个线程 相互拿着对方要使用的锁 双方处于一种BLOCKED状态

谈谈iocaop的理解

控制反转(IOC),IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。

最直观的感受是传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 或者 getInstance 等直接或者间接调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用spring 提供的对象就可以了。使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法,这是控制反转的思想。

AOP作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

面向切面编程指的是:程序在运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用CGLIB 方式实现动态代理。

动态代理是什么?有哪些应用?

动态代理是运行时动态生成代理类。

动态代理的应用有 Spring AOP数据查询、测试框架的后端 mockrpcJava注解对象获取等。

怎么实现动态代理?

JDK 原生动态代理和 cglib 动态代理。

JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。

java动态代理机制中有两个重要的,类和接口InvocationHandler(接口)和Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心;

1.InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,

2.Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

9.5 、JDK动态代理(Dynamic Proxy)

在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。

jdk动态代理方式

 ①:Proxy类是Jdk中自带的一个工具类(反射包下,属于反射的功能),它可以帮我们创建代理类或实例

②:方法newProxyInstance()---用于创建代理对象实例

③:Proxy.newProxyInstance()方法中参数介绍:

 参数一:目标对象的类加载器

参数二:目标对象实现的所有接口

参数三:InvocationHandler接口(重写invoke方法后)的实例

注意:InvocationHandler 接口的实现类中--->对代理的目标对象内的方法进行增强操作

给目标对象方法前面,后面、返回处、异常处添加额外的功能代码--增强 invoke()方法内的参数介绍: Object proxy:代理对象实例 Method method:代理对象调用的方法的反射--Method对象实例 Object[] args:调用代理方法时传递进来的参数

9.6 、CGLib动态代理(CGLib Proxy)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

面向对象的基本特征

1)封装:封装的好处就是安全,方便。封装隐藏了对象的具体实现,当要操纵对象时,只需调用其中的方法,而不用管方法的具体实现。属性的封装就是属性私有化并提供get/set方法,这样外界只能通过get/set方法来操作属性,行为变得可控。

2)继承:继承的好处就是代码的复用和扩展。继承可以保留父类的属性和方法,同时子类又可以扩展自己的属性和方法。

3)多态:目的是实现代码的灵活性,多态体现在重载和重写方法,更多的时候指的是对象的多态性,即当父类的变量指向子类的对象时,那么调用子类重写的方法时,运行的是子类重写过的代码,从而实现同一个父类的变量,因为赋值的子类对象不同而体现出不同的功能。应用主要体现在多态参数和多态数组中。

多态是在继承的基础上实现的。多态的三个要素:继承、重写和父类引用指向子类对象。父类引用指向不同的子类对象时,调用相同的方法,呈现出不同的行为;就是类多态特性。多态可以分成编译时多态和运行时多态。

数据库连接池参数

连接池支持的最大连接数;这里取值为20,表示同时最多有20个数据库连接。一般把maxActive设置成可能的并发量就行了设 0 为没有限制。

接池中最多可空闲maxIdle个连接 ,这里取值为20,表示即使没有数据库连接时依然可以保持20空闲的连接,而不被清除,随时处于待命状态。设 0 为没有限制。

连接池中最小空闲连接数,当连接数少于此值时,连接池会创建连接来补充到该值的数量

初始化连接数目 

连接池中连接用完时,新的请求等待时间,毫秒,这里取值-1,表示无限等待,直到超时为止,也可取值9000,表示9秒后超时。超过时间会出错误信息

是否清除已经超过“removeAbandonedTimout”设置的无效连接。如果值为“true”则超过“removeAbandonedTimout”设置的无效连接将会被清除。设置此属性可以从那些没有合适关闭连接的程序中恢复数据库的连接。

活动连接的最大空闲时间,单位为秒 超过此时间的连接会被释放到连接池中,针对未被close的活动连接

连接池中连接可空闲的时间,单位为毫秒 针对连接池中的连接对象

timeBetweenEvictionRunsMillis毫秒秒检查一次连接池中空闲的连接,把空闲时间超过minEvictableIdleTimeMillis毫秒的连接断开,直到连接池中的连接数到minIdle为止.

Nginx的负载均衡策略

  • 默认为轮询每个请求会按时间顺序逐一分配到不同的后端服务器。
  • 在轮询中,如果服务器down掉了,会自动剔除该服务器。
  • 缺省配置就是轮询策略。
  • 此策略适合服务器配置相当,无状态且短平快的服务使用。

权重方式,在轮询策略的基础上指定轮询的几率。

  • 权重越高分配到需要处理的请求越多。
  • 此策略可以与least_conn和ip_hash结合使用。
  • 此策略比较适合服务器的硬件配置差别比较大的情况, 你啊后 你好呀

ip_hash

  指定负载均衡器按照基于客户端IP的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证session会话。这样每个访客都固定访问一个后端服务器,可以解决session不能跨服务器的问题。

least_conn

  把请求转发给连接数较少的后端服务器。轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同;但是,有些请求占用的时间很长,会导致其所在的后端负载较高。这种情况下,least_conn这种方式就可以达到更好的负载均衡效果。

  • 此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况。

第三方策略

  第三方的负载均衡策略的实现需要安装第三方插件。

①fair

  按照服务器端的响应时间来分配请求,响应时间短的优先分配。

url_hash

  按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,要配合缓存命中来使用。同一个资源多次请求,可能会到达不同的服务器上,导致不必要的多次下载,缓存命中率不高,以及一些资源时间的浪费。而使用url_hash,可以使得同一个url(也就是同一个资源请求)会到达同一台服务器,一旦缓存住了资源,再此收到请求,就可以从缓存中读取。

Spring事务隔离级别

Spring事务隔离级别比数据库事务隔离级别多一个default。其他还是读未提交,读已提交,可重复读,串行化

Spring支持编程式事务管理以及声明式事务管理两种方式。

1. 编程式事务管理

编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。

2. 声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

Spring事务传播属性

1) required(默认属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

2) 2) Mandatory
支持当前事务,如果当前没有事务,就抛出异常。

3) Never
以非事务方式执行,如果当前存在事务,则抛出异常。

4) Not_supports
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

5) requires_new
新建事务,如果当前存在事务,把当前事务挂起。

6) Supports
支持当前事务,如果当前没有事务,就以非事务方式执行。

7) Nested
支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

spring事务什么情况下会失效

  1. 不带事务的方法调用这个类里带事务的方法,不会回滚,因为spring的回滚是用代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接用this调用,而没有生成代理事务
  2. 方法不是公共的,没有用public。@transactional注解只能用在public上。如果非要在不是public上用,可以开启AspectJ代理模式
  3. 当前数据库不支持事务
  4. 没有被spring管理
  5. 没有使用@EnableTransactionManagement注解来启用spring的自动事务管理功能
  6. 异常被try catch捕获,吞了
  7. 在业务代码中如果抛出RuntimeException异常,事务回滚;但是抛出Exception,事务不回滚;
  8. 业务和spring事务代码没有在同一个线程里

接口服务数据被劫包,如何防止数据恶意提交

解决方案--签名:对客户端发来的请求进行签名

1.防篡改

  1. 客户端提交请求之前,先对自己请求的参数全部进行拼接加密得到一个加密字符串sign
  2. 请求参数加上sign,然后再发送给服务器
  3. 服务器将参数获取后也进行相同的拼接加密得到自己的sign
  4. 比较与客户端发来的sign是否相同
  5. 不相同则是被第三方修改过的,拒绝执行

关键:

  1. 第三方不知道加密方式和请求参数拼接规则,而客户端与服务器是知道的,因此第三方不知道修改参数后如何生成与服务器生成相同的sign
  2. 只要请求修改了一点点加密得到的就是不同的签名

 

2.防重放

1)客户端的请求参数上加一个请求时间timestamp

原理:服务器获取请求的timestamp,然后比较自身系统时间,如果相差超过设定时间就是超时,该请求无效

作用:就算第三方截取了该请求,它也只能在设定时间内进行重放攻击

2)客户端的请求参数上加一个随机字符串string

原理:服务端获取请求的随机字符串string,然后查看是否在设定时间内别的请求使用过该string,被使用过就判定无效

作用:加上timestamp,就造成短时间内一个请求只能使用一次,因为就算请求被拦截,它请求成功一次后,第二次复制重放时就因为随机字符串被使用而被拒绝

集群环境中session如何实现共享

会话保持,使用一个固定的服务器专门保持session,其他服务器共享,通过会话保持,负载均衡进行请求分发的时候保证每个客户端固定的访问到后端的同一台应用服务器。会话保持方案在所有的负载均衡都有对应的实现。而且这是在负载均衡这一层就可以解决Session问题。

会话复制,会话复制在tomcat上,基于ip组播来完成session的复制,tomcat会话复制分为两种:

全局会话复制:利用Delta Manager复制会话中的变更信息到集群中的所有其他节点和非全局复制:使用Backup Manager进行复制,它会把Session复制给一个指定的备份节点。在集群超过6个节点之后就会出现各种问题,不推荐生产使用。

会话共享:session存放在哪,因为频繁使用,在生产环境中存放在性能更快的分布式KV数据中,redis

Sessioncookie的区别

Cookie的工作原理

1)浏览器端第一次发送请求到服务器端

2)服务器端创建Cookie,该Cookie中包含用户的信息,然后将该Cookie发送到浏览器端

3)浏览器端再次访问服务器端时会携带服务器端创建的Cookie

4)服务器端通过Cookie中携带的数据区分不同的用户

Session实现原理

当服务器创建完session对象后,会把session对象的id以cookie形式返回给客户端。这样,当用户保持当前浏览器的情况下再去访问服务器时,会把session的id传给服务器,服务器根据session的id来为用户提供相应的服务

区别:

cookie数据存放在客户的浏览器上,session数据放在服务器上

(2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session

(3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE

(4)单个cookie在客户端的限制是4K,就是说一个站点在客户端存放的COOKIE不能4K。

(5)所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中

 数组和链表的优缺点

数组随机访问性强(通过下标进行快速定位),查找速度快,但插入和删除效率低(插入和删除需要移动数据);可能浪费内存(因为是连续的,所以每次申请数组之前必须规定数组的大小,如果大小不合理,则可能会浪费内存),内存空间要求高,必须有足够的连续内存空间;数组大小固定,不能动态拓展。

链表插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素);内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间),大小没有固定,拓展很灵活。不能随机查找,必须从第一个开始遍历,查找效率低。

高并发系统中如果突然一个应用或服务变得很慢怎么处理

在一个高并发系统中 如果突然出现一个应用或者说一个服务突然变得很慢,应该怎么排查?

1、首先就是想看日志,后来想想看日志确实不太可行,并发量太大的情况下,查日志会很慢,(看日志,pstack strace gdb)。

2、应该从上到下看。----网络,系统,应用。任何一个环节都有可能有问题,首先看网络监控情况,然后看系统(内存,cpu,负载 )情况。

3、把线程dump出来,用jstack dump 线程调用链,看线程卡到哪里了。然后定位代码,看看赌在哪个函数,是不是调用了重函数。当然先定位到具体应用,再dump。

4、看接口故障率,然后看数据库有没有锁。

5、先看日志,排除了磁盘满了,网络慢,然后看进程,具体到当时几个线程,堵在哪儿。

6、分布式服务一般都是服务治理的,可以看整个调用链的时间图。

7、看监控,监控只是能找到问题  但是不能找到问题原因,什么请求、返回、错误、慢速,这些都是要监控的,告警也要配置好,有问题第一时间知道。

8、假如是网络问题那就找临近的防火墙看对应服务器的并发连接数是否新建连接/半开连接超高,或者有突发流量挤占了资源。同时查对应服务器的磁盘I/O情况和对应那个应用的进程内存占用曲线是否有突发。高并发应用我理解上日志肯定是没法去看的,因为日志量太巨大了。

9、集群或多主机的系统,需要根据监控 看各个主机的CPU 内存 接口IO等,这些能分析一些,如果能得出具体的主机 再分析哪个应用的CPU 内存 异常dump 等。如果涉及到接口机 可以看接口的失败率,应用的话需要排查log了,有时候主机空间不足 内存不足、硬件故障也会导致问题。

10、例子:在银行做安全运维时候,那帮服务器的和网银的一天到晚就说这是网络问题,这绝对网络瘫了。。然后查一圈发现他们一个什么java的问题,说是有个印度小哥把java runtime装了最高版本。但是系统不支持,必须重新装回低版本,就好了。

11、如果是分布式部署多台机器,别的机器没问题,但这台机器有问题。那么更有可能是因为网络或者磁盘导致的,一般这些资源都有zabbox这样的监控。看日志是肯定要的。

12、如果这些机器都是虚拟机的话也有可能是控制器系统的问题。

BeanFactoryApplicationContext有什么区别?

 BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器

BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。

ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 

ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。

不足之处是与beanfactory相比applicationcontext占用内存空间,应用程序配置bean较多的时候,启动较慢

都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,BeanFactory需要手动注册,而ApplicationContext则是自动注册。

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建

面向对象

封装继承多态

封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。

继承机制允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。继承机制提高了代码复用率,在Java中的Object类是所有类的超类,常称作上帝类。

多态同一个行为具有多个不同表现形式或形态的能力。是指一个类实例(对象)的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。多态存在的三个必要条件:继承;重写(子类继承父类后对父类方法进行重新定义);父类引用指向子类对象

后端怎么防止重复提交

访问请求到达服务器,服务器端生成token,分别保存在客户端和服务器。提交请求到达服务器,服务器端校验客户端带来的token与

此时保存在服务器的token是否一致,如果一致,就继续操作,删除服务器的token。如果不一致,就不能继续操作,即这个请求是重

复请求。

缓存数据库双写一致性问题解决

  • 先更新数据库,再删除缓存。
  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

但是会出现脏数据怎么办?

数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此读的耗时比写的耗时更短,这一情形很难出现。

使用canalmq的方式来实现mysqlredis的数据准试时同步

canal的工作原理就是把自己伪装成MySQL slave,模拟MySQL slave的交互协议向MySQL Mater发送 dump协议,MySQL mater收到canal发送过来的dump请求,开始推送binary log给canal,然后canal解析binary log,再发送到存储目的地,比如MySQL,Kafka,Elastic Search等等。

canal的好处在于对业务代码没有侵入,因为是基于监听binlog日志去进行同步数据的。实时性也能做到准实时,其实是很多企业一种比较常见的数据同步的方案。

MySQL主备复制原理

  • MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
  • MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
  • MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)

canal 工作原理

Canal是一个伪装成slave从机订阅mysql的binlog,实现数据同步的中间件,但是canal有很大的局限性,只支持mysql

数据库缓存最终一致性的四种方案

方案一

通过key的过期时间,mysql更新时,redis不更新。 这种方式实现简单,但不一致的时间会很长。如果读请求非常频繁,且过期时间比较长,则会产生很多长期的脏数据。

优点:

开发成本低,易于实现;

管理成本低,出问题的概率会比较小。

不足:

完全依赖过期时间,时间太短容易缓存频繁失效,太长容易有长时间更新延迟(不一致)

方案二

在方案一的基础上扩展,通过key的过期时间兜底,并且,在更新mysql时,同时更新redis。

优点:

  • 相对方案一,更新延迟更小。

不足:

  • 如果更新mysql成功,更新redis却失败,就退化到了方案一;
  • 在高并发场景,业务server需要和mysql,redis同时进行连接。这样是损耗双倍的连接资源,容易造成连接数过多的问题。

方案三

针对方案二的同步写redis进行优化,增加消息队列,将redis更新操作交给kafka,由消息队列保证可靠性,再搭建一个消费服务,来异步更新redis。

优点:

消息队列可以用一个句柄,很多消息队列客户端还支持本地缓存发送,有效解决了方案二连接数过多的问题;

使用消息队列,实现了逻辑上的解耦;

消息队列本身具有可靠性,通过手动提交等手段,可以至少一次消费到redis。

不足:

依旧解决不了时序性问题,如果多台业务服务器分别处理针对同一行数据的两条请求,举个栗子,a = 1; a = 5;,如果mysql中是第一条先执行,而进入kafka的顺序是第二条先执行,那么数据就会产生不一致。

引入了消息队列,同时要增加服务消费消息,成本较高。

方案四

通过订阅binlog来更新redis,把我们搭建的消费服务,作为mysql的一个slave,订阅binlog,解析出更新内容,再更新到redis。

优点

  • mysql压力不大情况下,延迟较低;
  • 和业务完全解耦;
  • 解决了时序性问题。

缺点

  • 要单独搭建一个同步服务,并且引入binlog同步机制,成本较大。

方案选型

首先确认产品上对延迟性的要求,如果要求极高,且数据有可能变化,别用缓存。

通常来说,方案1就够了,笔者咨询过4,5个团队,基本都是用方案1,因为能用缓存方案,通常是读多写少场景,同时业务上对延迟具有一定的包容性。方案1没有开发成本,其实比较实用。

如果想增加更新时的即时性,就选择方案2,不过没必要做重试保证之类的。

方案3,方案4针对于对延时要求比较高业务,一个是推模式,一个是拉模式,而方案4具备更强的可靠性,既然都愿意花功夫做处理消息的逻辑,不如一步到位,用方案4。

面试题持续更新中...

欢迎加入互联网技术交流群共同学习共同进步

  

-----------------------------------------------------------------------------------------------------------------------------------

版权声明:本文为博客园博主「LENGXUAN」的原创文章,转载请附上原文出处链接及本声明。

原文链接:https://www.cnblogs.com/lengxuanwl/p/12699602.html

原文地址:https://www.cnblogs.com/lengxuanwl/p/15565740.html