当前位置:   article > 正文

Java中级面试常见题目+答案_java中级面试题大汇总

java中级面试题大汇总

目录

一.基础知识

1.集合类:List和Set比较,各自的子类比较(ArrayList,Vector,LinkedList;HashSet,TreeSet);

ArrayList,LinkedList,Vector都属于List
List:元素是有顺序的,元素可以重复因为每个元素有自己的角标(索引)
  |-- ArrayList:底层的数据结构是数组结构,特点是:查询很快,增 删 稍微慢点,线程不同步
  |-- LinkedList:底层使用的是链表数据结构,特点是:增 删很快,查询慢。
  |--Vector:底层是数组数据结构,线程同步,被ArrayList代替了,现在用的只有他的枚举。
Set:元素是无序的,且不可以重复(存入和取出的顺序不一定一致),线程不同步。
  |--HashSet:底层是哈希表数据结构。根据hashCode和equals方法来确定元素的唯一性
  |--TreeSet:可以对Set集合中的元素进行排序(自然循序),底层的数据结构是二叉树,
    也可以自己写个类实现Comparable 或者 Comparator 接口,定义自己的比较器,将其作为参数传递给TreeSet的构造函数。
Map:这个集合是存储键值对的,一对一对往里存,而且要确保键的唯一性(01,张三)这样的形式打印出来就是  01=张三
   |--HashTable:底层是哈希表数据结构,不可以存入null键和null值,该集合线程是同步的,效率比较低。出现于JDK1.0
   |--HashMap:底层是哈希表数据结构,可以存入null键和null值,线程不同步,效率较高,代替了HashTable,出现于JDK 1.2
   |--TreeMap:底层是二叉树数据结构,线程不同步,可以用于个map集合中的键进行排序
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.HashMap的底层实现,之后会问ConcurrentHashMap的底层实现;

HashMap由数组和链表来实现对数据的存储
HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题。
数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;
链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

JDK 1.8的 改变:HashMap采用数组+链表+红黑树实现。
改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
Hashtable的同步会锁住整个数组。在高并发的情况下,性能会非常差,Java5中引入了java.util.concurrent.ConcurrentHashMap作为高吞吐量的线程安全HashMap实现,它采用了锁分离的技术允许多个修改操作并发进行

ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
Segment的get操作实现非常简单和高效。先经过一次再哈希,然后使用这个哈希值通过哈希运算定位到segment,再通过哈希算法定位到元素。get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读,我们知道HashTable容器的get方法是需要加锁的,那么ConcurrentHashMap的get操作是如何做到不加锁的呢?原因是它的get方法里将要使用的共享变量都定义成volatile。
由于put方法里需要对共享变量进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。Put方法首先定位到Segment,然后在Segment里进行插入操作。

是否需要扩容。在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阀值,数组进行扩容。值得一提的是,Segment的扩容判断比HashMap更恰当,因为HashMap是在插入元素后判断元素是否已经到达容量的,如果到达了就进行扩容,但是很有可能扩容之后没有新元素插入,这时HashMap就进行了一次无效的扩容


缺点:ConcurrentHashMap的size操作


https://blog.csdn.net/lin20044140410/article/details/79320587
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.如何实现HashMap顺序存储:可以参考LinkedHashMap的底层实现;

方法一:维护一张表,存储数据插入的顺序,可以使用vector。但是如果删除数据呢
首先得在vector里面找到那个数据,再删除,而删除又要移动大量数据,性能效率很低
使用list,移动问题可以解决,但是查找数据O的时间消耗,如果删除m次,那查找数据的性能就是0
那总体性能也是0.性能还是没法接受。
方法二:
可以在hashmap里维护插入顺序的id,在value建一个存储id值,在维护一张表vector,并且id对应vector里面的值。
插入的时候,id+ = 1,hashmao.insert, vector.push_back
删除的时候,先hashmao.find(key),得到value,并且从value中得到id,通过id把对应vector值设置为无效。
更新: 删除+ 插入。
维护工作OK了,输出的时候直接输出vector里面的值就可以了,无效的就continue.
算法福再度为O
方法三:
Java里面有个容器LinkedListHashMap,它能实现按照插入的顺序输出结果。
他的原理也是维护一张表,但他是链表,并且hashmap中维护指向链表的指针,这样可以快速定位链表中的元素
进行删除。
他的时间复杂度也是0,空间上比上面少些。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.HashTable和ConcurrentHashMap的区别;

Hashtable所有的方法都是同步的,因此,它是线程安全的

Synchronized容器和Concurrent容器有什么区别?

在Java语言中,多线程安全的容器主要分为两种:Synchronized和Concurrent,虽然它们都是线程安全的,但是它们在性能方面差距比较大。

Synchronized容器(同步容器)主要通过synchronized关键字来实现线程安全,在使用的时候会对所有的数据加锁。需要注意的是,由于同步容器将所有对容器状态的访问都串行化了,这样虽然保证了线程的安全性,但是这种方法的代价就是严重降低了并发性,当多个线程竞争容器时,吞吐量会严重降低。于是引入了Concurrent容器(并发容器),Concurrent容器采用了更加智能的方案,该方案不是对整个数据加锁,而是采取了更加细粒度的锁机制,因此,在大并发量的情况下,拥有更高的效率。
————————————————
原文链接:https://blog.csdn.net/weixin_39651041/article/details/79953811
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5.String,StringBuffer和StringBuilder的区别;

String类是不可变类
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
  • 1
  • 2
  • 3
  • 4

6.Object的方法有哪些:比如有wait方法,为什么会有;

锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。
  • 1

7.wait和sleep的区别,必须理解;

wait():释放资源,释放锁
sleep():释放资源,不释放锁
  • 1
  • 2

8.JVM的内存结构,JVM的算法;

1.方法区(Method Area)
2.堆区(Heap)
3.虚拟机栈(VM Stack)
4.本地方法栈(Native Method Stack)
5.程序计数器(Program Counter Register)

方法区存放了要加载的类的信息(如类名、修饰符等)、静态变量、构造函数、final定义的常量、类中的字段和方法等信息。方法区是全局共享的,在一定条件下也会被GC。

堆区是GC最频繁的,也是理解GC机制最重要的区域。堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例及数组,所有new出来的对象都存储在该区域。

虚拟机栈占用的是操作系统内存,每个线程对应一个虚拟机栈,它是线程私有的,生命周期和线程一样,每个方法被执行时产生一个栈帧(Statck Frame),栈帧用于存储局部变量表、动态链接、操作数和方法出口等信息,当方法被调用时,栈帧入栈,当方法调用结束时,栈帧出栈。
       局部变量表中存储着方法相关的局部变量,包括各种基本数据类型及对象的引用地址等,因此他有个特点:内存空间可以在编译期间就确定,运行时不再改变。
       虚拟机栈定义了两种异常类型:StackOverFlowError(栈溢出)和OutOfMemoryError(内存溢出)。
本地方法栈用于支持native方法的执行,存储了每个native方法的执行状态。本地方法栈和虚拟机栈他们的运行机制一致,唯一的区别是,虚拟机栈执行Java方法,本地方法栈执行native方法。在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将虚拟机栈和本地方法栈一起使用。

程序计数器是一个很小的内存区域,不在RAM上,而是直接划分在CPU上,程序猿无法操作它,它的作用是:JVM在解释字节码(.class)文件时,存储当前线程执行的字节码行号,只是一种概念模型,各种JVM所采用的方式不一样。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

9.强引用,软引用和弱引用的区别;

强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。
       软引用:只有当JVM内存不足时才会被回收。
       弱引用:只要GC,就会立马回收,不管内存是否充足。
       虚引用:它唯一的作用就是做一些跟踪记录,辅助finalize函数的使用。
  • 1
  • 2
  • 3
  • 4

10.数组在内存中如何分配;

Java数组的初始化,有以下两种方式,
1.静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度,如:
String[] names = new String[]{"多啦A梦", "大雄", "静香"};
2.动态初始化:初始化时由程序员显示的指定数组的长度,由系统为数据每个元素分配初始值,如:
String[] cars = new String[4]; //系统会默认给数组元素分配初始值为null
  • 1
  • 2
  • 3
  • 4
  • 5

11.

12.springmvc的核心是什么,请求的流程是怎么处理的,控制反转怎么实现的;

1.核心:SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。

2.流程说明:
(1)客户端(浏览器)发送请求,直接请求到DispatcherServlet。
(2)DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。
(3)解析到对应的Handler后,开始由HandlerAdapter适配器处理。
(4)HandlerAdapter会根据Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
(5)处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。
(6)ViewResolver会根据逻辑View查找实际的View。
(7)DispaterServlet把返回的Model传给View。
(8)通过View返回给请求者(浏览器)

3.IOC如何实现
通过DI(Dependency Injection,依赖注入)来实现的。
所谓依赖注入,其实就是给对象里的属性赋值,因为对象里有其他对象,因此就形成了依赖。Spring有4种方式来给属性赋值:
1. 构造方法注入constructor
2. set方法注入
3. 自动装配:Spring提供了自动装配的功能,简化了我们的配置,自动装配默认是不打开的。byName/byType
4. 注解@Autowired
配置了bean的id和class。
Spring中默认的bean为单实例模式,通过bean的class引用反射机制可以创建这个实例。
因此,spring框架通过反射代替我们创建好了实例并且替我们维护他们。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

13.spring里面的aop的原理是什么;

概念
切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。
连接点(Joinpoint) :程序执行过程中的某一行为。
通知(Advice) :“切面”对于某个“连接点”所产生的动作。
切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。
目标对象(Target Object) :被一个或者多个切面所通知的对象。
AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。

通知(Advice)类型
前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。
后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。
返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。
抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。

Spring AOP的底层都是通过代理来实现的
一种是基于JDK的动态代理
一种是基于CgLIB的动态代理
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

17.说说http,https协议;

什么是HTTP?
超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。
什么是HTTPS?
HTTPS是身披SSL外壳的HTTP。HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。

HTTP特点:
1.无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作
2.无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。
3.基于请求和响应:基本的特性,由客户端发起请求,服务端响应
4.简单快速、灵活
5.通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性

HTTPS特点:
内容加密:采用混合加密技术,中间者无法直接查看明文内容
验证身份:通过证书认证客户端访问的是自己的服务器
保护数据完整性:防止传输的内容被中间人冒充或者篡改

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

19.osi五层网络协议;

五层体系结构包括:应用层、运输层、网络层、数据链路层和物理层
  • 1

20.tcp,udp区别;

TCP和UDP的区别      
  1、基于连接与无连接;UDP是无连接的,即发送数据之前不需要建立连接
  2、TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
  3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
  4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
  5、TCP对系统资源要求较多,UDP对系统资源要求较少。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

21.用过哪些加密算法:对称加密,非对称加密算法;

对称加密:双方使用的同一个密钥,既可以加密又可以解密,这种加密方法称为对称加密,也称为单密钥加密。
在对称加密算法中常用的算法有:DES、AES等。
非对称加密:一对密钥由公钥和私钥组成(可以使用很多对密钥)。私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥可以互相加密解密)。
RSA、Elgamal、背包算法、Rabin、Diffie-Hellman、ECC(椭圆曲线加密算法)。
使用最广泛的是RSA算法,Elgamal是另一种常用的非对称加密算法。
  • 1
  • 2
  • 3
  • 4
  • 5

22.说说tcp三次握手,四次挥手;

TCP三次握手和四次挥手
TCP有6种标示:SYN(建立联机) ACK(确认) PSH(传送) FIN(结束) RST(重置) URG(紧急) 

TCP三次握手
  第一次握手
      客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号 seq=x,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始。表示客户端想要和服务端建立连接。

  第二次握手
      TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端是否准备好。
  第三次握手
      TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。

思考:为什么要三次握手呢,有人说两次握手就好了
举例:已失效的连接请求报文段。
   client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效
的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个
请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的
很多资源就没白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。


TCP的四次挥手  

第一次挥手
    TCP发送一个FIN(结束),用来关闭客户到服务端的连接。
    客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

第二次挥手
    服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。
    服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

第三次挥手
      服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。
      服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手
     客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。
     客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

思考:那么为什么是4次挥手呢?
为了确保数据能够完成传输。
      关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢.因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。

思考:客户端突然挂掉了怎么办?
    正常连接时,客户端突然挂掉了,如果没有措施处理这种情况,那么就会出现客户端和服务器端出现长时期的空闲。解决办法是在服务器端设置保活计时器,每当服务器收到客户端的消息,就将计时器复位。超时时间通常设置为2小时。若服务器超过2小时没收到客户的信息,他就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,还没有响应就认为客户端出了故障,因而终止该连接。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

23.cookie和session的区别,分布式环境怎么保存用户状态;

cookie和session的区别,分布式环境怎么保存用户状态
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

分布式环境下的session(举例两种):
服务器session复制
原理:任何一个服务器上的session发生改变(增删改),该节点会把这个 session的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要session,以此来保证Session同步。
优点:可容错,各个服务器间session能够实时响应。
缺点:会对网络负荷造成一定压力,如果session量大的话可能会造成网络堵塞,拖慢服务器性能。

session共享机制
使用分布式缓存方案比如memcached、redis,但是要求Memcached或Redis必须是集群。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

25.请写一段栈溢出、堆溢出的代码;

栈溢出(StackOverflowError)
堆溢出(OutOfMemoryError:Java heap space)
永久代溢出(OutOfMemoryError: PermGen space)
直接内存溢出


/**
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    int i=0;
    while(true){
        list.add(new byte[5*1024*1024]);
        System.out.println("分配次数:"+(++i));
    }
}

public class StackSOFTest {

    int depth = 0;

    public void sofMethod(){
        depth ++ ;
        sofMethod();
    }

    public static void main(String[] args) {
        StackSOFTest test = null;
        try {
            test = new StackSOFTest();
            test.sofMethod();
        } finally {
            System.out.println("递归次数:"+test.depth);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

26.ThreadLocal可以用来共享数据吗

ThreadLocal 解决多线程变量共享问题
ThreadLocal 不是一个线程,而是一个线程的本地化对象。当某个变量在使用 ThreadLocal 进行维护时,ThreadLocal 为使用该变量的每个线程分配了一个独立的变量副本,每个线程可以自行操作自己对应的变量副本,而不会影响其他线程的变量副本。


  • 1
  • 2
  • 3
  • 4

27.springboot的优点

①良好的基因
②简化编码
③简化配置
④简化部署
⑤简化监控
  • 1
  • 2
  • 3
  • 4
  • 5

28.Spring 如何解决循环依赖的问题

    29.spring项目中静态方法中使用注入的bean

    自己写一个工具类,通过spring上下文获取这个bean。转成静态的。
    /*
     * @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
     * 标记为组件后在启动服务时,会将此类实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
     */
    @Component
    public class SpringContextHelper implements ApplicationContextAware{
    
     
        /**
         * Spring应用上下文环境
         */
        private static ApplicationContext applicationContext;
     
        /**
         * 重写并初始化上下文
         * @param applicationContext 应用上下文
         * @throws BeansException bean异常
         */
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            // 初始化applicationContext
            SpringContextHelper.applicationContext = applicationContext;
        }
     
        /**
         * 通过类获取
         * @param clazz 注入的类
         * @param <T> 返回类型
         * @return 返回这个bean
         * @throws BeansException bean异常
         */
        @SuppressWarnings("unchecked")
        public static <T> T getBean(Class clazz) throws BeansException {
            return  (T)applicationContext.getBean(clazz);
        }
     
        /**
         * 通过名字获取
         * @param name 名字
         * @param <T> 返回类型
         * @return 返回这个bean
         * @throws BeansException bean异常
         */
        @SuppressWarnings("unchecked")
        public static <T> T getBean(String name) throws BeansException {
            return (T) applicationContext.getBean(name);
        }
    
    }
    在需要使用的类中注入你的bean使用即可
    private static EmployeeService employeeService = SpringContextHelper.getBean(EmployeeService.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    30.Java8新特性:1、Lambda表达式;2、方法引用;3、默认方法;4、新编译工具;5、Stream API;6、Date Time API;7、Option;8、Nashorn javascript引擎。

    1、Lambda 表达式
    Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
    2、方法引用
    方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
    3、默认方法
    默认方法就是一个在接口里面有了一个实现的方法。
    4、新工具
    新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
    5、Stream API
    新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
    6、Date Time API
    加强对日期与时间的处理。
    7、Optional 类
    Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
    8、Nashorn JavaScript 引擎
    Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    二.IO:

    1.bio,nio,aio的区别;

    首页  问题
    匿名
    BIO、NIO和AIO的区别
    
        Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
        Java NIO: 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
        Java AIO: 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
       NIO比BIO的改善之处是把一些无效的连接挡在了启动线程之前,减少了这部分资源的浪费(因为我们都知道每创建一个线程,就要为这个线程分配一定的内存空间)
       AIO比NIO的进一步改善之处是将一些暂时可能无效的请求挡在了启动线程之前,比如在NIO的处理方式中,当一个请求来的话,开启线程进行处理,但这个请求所需要的资源还没有就绪,此时必须等待后端的应用资源,这时线程就被阻塞了。
    适用场景分析:
       BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解,如之前在Apache中使用。
       NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持,如在 Nginx,Netty中使用。(dubbo)
       AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持,在成长中,Netty曾经使用过,后来放弃。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.nio框架:dubbo的实现原理;

    dubbo工作原理
    第一层:service层,接口层,给服务提供者和消费者来实现的
    第二层:config层,配置层,主要是对dubbo进行各种配置的
    第三层:proxy层,服务代理层,透明生成客户端的stub和服务单的skeleton
    第四层:registry层,服务注册层,负责服务的注册与发现
    第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
    第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控
    第七层:protocol层,远程调用层,封装rpc调用
    第八层:exchange层,信息交换层,封装请求响应模式,同步转异步
    第九层:transport层,网络传输层,抽象mina和netty为统一接口
    第十层:serialize层,数据序列化层
    
    工作流程:
    1)第一步,provider向注册中心去注册
    2)第二步,consumer从注册中心订阅服务,注册中心会通知consumer注册好的服务
    3)第三步,consumer调用provider
    4)第四步,consumer和provider都异步的通知监控中心
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三.算法:

    1.java中常说的堆和栈,分别是什么数据结构;另外,为什么要分为堆和栈来存储数据

    堆和栈,分别是什么数据结构
    栈是一种具有后进先出性质的数据结构
    堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆
    
    • 1
    • 2
    • 3

    2.TreeMap如何插入数据:二叉树的左旋,右旋,双旋;

    TreeMap的实现是红黑树算法的实现,红黑树,是一个自平衡的二叉排序树。(变色/旋转)
    特性:
    每个节点要么为红色,要么为黑色
    根节点为黑色
    每个叶子节点(NIL)是黑色 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
    不允许连续两个红色节点
    从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑色节点
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.一个排序之后的数组,插入数据,可以使用什么方法?答:二分法;问:时间复杂度是多少?

    log(N)
    
    • 1

    4.平衡二叉树的时间复杂度;

    O(logn)
    https://www.cnblogs.com/AndyAo/p/8191883.html
    
    • 1
    • 2

    7.快速排序 & 冒泡排序 & 堆排序 &排序复杂度

    
    https://blog.csdn.net/ly_xiamu/article/details/107637789
    
    • 1
    • 2

    四. 多线程相关:

    1.说说阻塞队列的实现:可以参考ArrayBlockingQueue的底层实现(锁和同步都行);

    LinkedBlockingQueue是一个基于链表实现的可选容量的阻塞队列。队头的元素是插入时间最长的,队尾的元素是最新插入的。新的元素将会被插入到队列的尾部。 
    LinkedBlockingQueue的容量限制是可选的,如果在初始化时没有指定容量,那么默认使用int的最大值作为队列容量。
    原理
    LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队,这也就意味着,同一时刻,只能有一个线程执行入队,其余执行入队的线程将会被阻塞;同时,可以有另一个线程执行出队,其余执行出队的线程将会被阻塞。换句话说,虽然入队和出队两个操作同时均只能有一个线程操作,但是可以一个入队线程和一个出队线程共同执行,也就意味着可能同时有两个线程在操作队列,那么为了维持线程安全,LinkedBlockingQueue使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的。
    
    • 1
    • 2
    • 3
    • 4

    2.进程通讯的方式:消息队列,共享内存,信号量,socket通讯等;

    常见的通信方式
    管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
    命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
    信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
    信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.Excutors可以产生哪些线程池;

    Excutors 可以产生哪些线程池?
    1、newCachedThreadPool:用来创建一个可缓存线程池,该线程池没有长度限制,对于新的任务,如果 有空闲的线程,则使用空闲的线程执行,如果没有,则新建一个线程来执行任务。如果线程池长度超过处理需要,可灵活回收空闲线程
    2、newFixedThreadPool :用来创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中 等待。定长线程池的大小通常根据系统资源进行设置: Runtime.getRuntime().availableProcessors()
    3、newScheduledThreadPool:用来创建一个定长线程池,并且支持定时和周期性的执行任务
    4、newSingleThreadExecutor:用来创建一个单线程化的线程池,它只用唯一的工作线程来执行任务, 一次只支持一个,所有任务按照指定的顺序执行
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.为什么要用线程池;

    为什么要用线程池
    当我们去创建每个线程的时候,都需要为它去分配内存,如虚拟机栈,程序计数器,本地方法栈等,所以创建的过程时间消耗是比较大的
    当线程使用结束,如run方法执行结束,结束的时候,又要进行垃圾回收,又是大的时间消耗
    当再执行到另一个单元的时候,又需要多个线程的时候,又重新经历从创建线程,到使用完销毁线程的过程,重复创建和销毁的这两个过程导致效率上的消耗
    所以,我们思考有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,所以就诞生了线程池
    
    降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。
    提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    8.线程池原理

    线程池的两个核心队列:
    线程等待池,即线程队列BlockingQueue。
    任务处理池(PoolWorker),即正在工作的Thread列表(HashSet<Worker>)。
    
    corePoolSize	核心线程数量,线程池维护线程的最少数量
    1.如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
    2.如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
    3.如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
    4.如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
    5.当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
    
    线程池需要管理线程的生命周期,需要在线程长时间不运行的时候进行回收。线程池使用一张Hash表去持有线程的引用,这样可以通过添加引用、移除引用这样的操作来控制线程的生命周期。这个时候重要的就是如何判断线程是否在运行。
    
    Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。
    
    使用规范:
    【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
    说明: 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资
    源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者
    “过度切换”的问题。
    
    
    【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
    的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    说明: Executors 返回的线程池对象的弊端如下:
    1) FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    2) CachedThreadPool 和 ScheduledThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    9.高并发下接口幂等性的解决方案

    幂等性概念:任意多次执行所产生的影响均与一次执行的影响相同
    幂等性解决方案
    1.防重表
    数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据(比如订单表对订单号进行唯一索引,所有重复提交可能产生同一个订单号的都会被拆除。当然,订单号要按你自己的设定走,一般订单号设计会是时间戳加迭代。那么如果是这样,创建仍然不能保证幂等,具体根据业务需求来判定建立的唯一索引位置)
    2.token令牌机制,
    分为两个阶段,获取token和使用token。每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token。
    先查询后判断,
    首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。
    3.支付缓冲区
    把订单的支付请求都快速地接下来,一个快速接单的缓冲管道。后续使用异步任务处理管道中的数据,过滤掉重复的待支付订单。优点是同步转异步,高吞吐。不足是不能及时地返回支付结果,需要后续监听支付结果的异步返回。(一般支付都是采用这种方式)
    4.悲观锁或者乐观锁。
    悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)
    乐观锁,一般通过version来做乐观锁,这样既能保证执行效率,又能保证幂等。例如: UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version# 不过,乐观锁存在失效的情况,就是常说的ABA问题,不过如果version版本一直是自增的就不会出现ABA的情况。(ABA可以查看这篇文章链接,关于CAS机制可以看这篇链接)
    5.分布式锁
    防重表可以使用分布式锁代替,比如Redis。订单发起支付请求,支付系统会去Redis缓存中查询是否存在该订单号的Key,如果不存在,则向Redis增加Key为订单号。查询订单支付已经支付,如果没有则进行支付,支付完成后删除该订单号的Key。通过Redis做到了分布式锁,只有这次订单订单支付请求完成,下次请求才能进来。相比去重表,将放并发做到了缓存中,较为高效。思路相同,同一时间只能完成一次支付请求。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    10. 获取子线程的返回值的方法:1.主线程等待。2.Join方法等待。3.实现Callable接口。

    五.数据库相关(mysql)

    1.

    msyql优化经验
    https://blog.csdn.net/qq_32332777/article/details/112617642
    
    • 1
    • 2

    2.mysql的语句优化,使用什么工具;

    Explain执行计划
          1、id:SELECT识别符。这是SELECT的查询序列号;
          2、select_type:查询类型,主要有PRIMARY(子查询中最外层查询)、SUBQUERY(子查询内层第一个SELECT)、UNION(UNION语句中第二个SELECT开始后面所有SELECT)、SIMPLE(除了子查询或者union之外的其他查询);
          3、table:所访问的数据库表名;
          4、type:对表的访问方式,包括以下类型all(全表扫描),index(全索引扫描),rang(索引范围扫描),ref(join语句中被驱动表索引引用查询),eq_ref(通过主键或唯一索引访问,最多只会有一条结果),const(读常量,只需读一次),system(系统表。表中只有一条数据),null(速度最快)。
          5、possible_keys:查询可能使用到的索引;
          6、key:最后选用的索引;
          7、key_len:使用索引的最大长度;
          8、ref:列出某个表的某个字段过滤;
          9、rows:估算出的结果行数;
          10、extra:查询细节信息,可能是以下值:distinct、using filesort(order by操作)、using index(所查数据只需要在index中即可获取)、using temporary(使用临时表)、using where(如果包含where,且不是仅通过索引即可获取内容,就会包含此信息)。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.mysql的索引分类:B+,hash;什么情况用什么索引;

    mysql的索引分类:B+,hash;什么情况用什么索引
    在MySQL的存储引擎中,MyISAM不支持哈希索引,而InnoDB中的hash索引是存储引擎根据B-Tree索引自建的
    
    B-Tree索引的特点
    1、B-tree索引可以加快数据的查询速度
      存储引擎不需要进行全表扫描来获得需要的数据,取而代之的是从索引的根节点开始进行搜索。然后根据指针逐层向下查找,通过比较节点页的值和有目标值就可以找到合适的指针进入下层节点,而这些指针实际上定义了子节点页中值的上限和下限。
    2、B-tree索引更适合进行范围查询
      因为前面说过,B-tree对索引是顺序组织存储的,所以就很适合进行查找范围数据。
    
    hash索引的特点
    1、hash索引是基于hash表实现的,只有查询条件精确匹配hash索引中的所有列的时候,才能用到hash索引。
    2、对于hash索引中的所有列,存储引擎都会为每一行计算一个hash码,hash索引中存储的就是hash码。
    3、hash索引包括键值、hash码和指针 。
      因为hash索引本身只需要存储对应的hash值,所以索引的结构十分紧凑,这也让hash索引查找的速度非常快。然而,hash索引也是存在其限制的:
    hash索引的限制
    1、Hash索引必须进行二次查找
      使用哈市索引两次查找,第一次找到相应的行,第二次读取数据,但是被频繁访问到的行一般会缓存在内存中,这点对数据库性能的影响不大。
    2、hash索引不能用于外排序
      hash索引存储的是hash码而不是键值,所以无法用于外排序
    3、hash索引不支持部分索引查找也不支持范围查找
      只能用到等值查询,不能范围和模糊查询
    4、hash索引中的hash码的计算可能存在hash冲突
      当出现hash冲突的时候,存储引擎必须遍历整个链表中的所有行指针,逐行比较,直到找到所有的符合条件的行,若hash冲突很多的话,一些索引的维护代价机会很高,所以说hash索引不适用于选择性很差的列上(重复值很多)。姓名、性别、身份证(合适)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.mysql的存储引擎有哪些,区别是什么;

    mysql的存储引擎有哪些,区别是什么
    
    MySQL常见的三种存储引擎为InnoDB、MyISAM和MEMORY。其区别体现在事务安全、存储限制、空间使用、内存使用、插入数据的速度和对外键的支持。
    1、事务安全:
    InnoDB支持事务安全,MyISAM和MEMORY两个不支持。
    2、存储限制:
    InnoDB有64TB的存储限制,MyISAM和MEMORY要是具体情况而定。
    3、空间使用:
    InnoDB对空间使用程度较高,MyISAM和MEMORY对空间使用程度较低。
    4、内存使用:
    InnoDB和MEMORY对内存使用程度较高,MyISAM对内存使用程度较低。
    5、插入数据的速度:
    InnoDB插入数据的速度较低,MyISAM和MEMORY插入数据的速度较高。
    6、对外键的支持:
    InnoDB对外键支持情况较好,MyISAM和MEMORY两个不支持外键。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.说说事务的特性和隔离级别;

    事务的特性和隔离级别
    ⑴ 原子性(Atomicity)
      原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
    ⑵ 一致性(Consistency)
      一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
      拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
    ⑶ 隔离性(Isolation)
      隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
      即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
      关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
    ⑷ 持久性(Durability)
      持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
      
    四种隔离级别:
      ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
      ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
      ③ Read committed (读已提交):可避免脏读的发生。
      ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    6.悲观锁和乐观锁的区别,怎么实现

    悲观锁和乐观锁的区别,怎么实现
    概念
    悲观锁:一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
    
    乐观锁:一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作。
    
    乐观锁适用于写比较少的情况下(多读场景)。乐观锁一般会使用版本号机制或CAS算法实现。
    悲观锁适用于写比较多的情况下(多写场景)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    7. 数据库on where having的区别

    on和where
    所有的查询都回产生一个中间临时报表,查询结果就是从返回临时报表中得到。on和where后面所跟限制条件的区别,主要与限制条件起作用的时机有关,on根据限制条件对数据库记录进行过滤,然后生产临时报表;而where是在临时报表生产之后,根据限制条件从临时报表中筛选结果。
    
    总结:在左外连接中,on会返回左表中的所有记录;而where中,此时相当于inner join,只会返回满足条件的记录。
    速度:因为on限制条件发生时间较早,产生的临时报表数据集要小,因此on的性能要优于where。
    
    having和where
    having和where的区别也是与限制条件起作用时机有关,having是在聚集函数计算结果出来之后筛选结果,查询结果只返回符合条件的分组,having不能单独出现,只能出现在group by子句中。而where是在计算之前筛选结果,如果聚集函数使用where,那么聚集函数只计算满足where子句限制条件的数据。
    
    总结:where即可以和select等其他子句搭配使用,也可以和group by子句搭配使用,where的优先级要高于having。
    速度:因为where在聚集函数之前筛选数据,having在计算之后筛选分组,因此where的性能要优于having。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    8.MySQL的索引类型有哪些

    存储方式区分
    根据存储方式的不同,MySQL 中常用的索引在物理上分为  B-树索引和 HASH 索引两类
    逻辑区分
    1) 普通索引    关键字是 INDEX 或 KEY
    2) 唯一索引    关键字UNIQUE 
    3) 主键索引    关键字PRIMARY KEY  
    4) 空间索引    关键字SPATIAL ,空间索引只能在存储引擎为 MyISAM 的表中创建  
    5) 全文索引    关键字FULLTEXT ,只能在 CHAR、VARCHAR 或 TEXT 类型的列上创建  
    实际使用区分
    1)单列索引
    2)多列索引
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    六.mq

    1.mq的原理是什么:

    MQ组成结构
       Broker:消息服务器,作为server提供消息核心服务
       Producer:消息生产者,业务的发起方,负责生产消息传输给broker,
       Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理
       Topic:主题,是一种消息的逻辑分类,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅 者,实现消息的广播
       Queue:队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收
       Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.mq如何保证实时性;

    mq如何保证实时性
    
    1.生产者将数据发送到 RabbitMQ 的时候,此时可以选择用 RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit。(缺点:吞吐量会下来,因为太耗性能)
    
    一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启 confirm 模式,在生产者那里设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
    事务机制和 cnofirm 机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是 confirm 机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。
    所以一般在生产者这块避免数据丢失,都是用 confirm 机制的。
    
    2.RabbitMQ 弄丢了数据
    就是 RabbitMQ 自己弄丢了数据,这个你必须开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,可能导致少量数据丢失,但是这个概率较小。
    
    设置持久化有两个步骤:
    创建 queue 的时候将其设置为持久化
    这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
    第二个是发送消息的时候将消息的 deliveryMode 设置为 2
    就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
    
    3.消费端弄丢了数据
    RabbitMQ 如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。
    这个时候得用 RabbitMQ 提供的 ack 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。这样的话,如果你还没处理完,不就没有 ack 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.mq的持久化是怎么做的;

    mq的持久化是怎么做的
    队列持久化需要在声明队列时添加参数 durable=True,这样在rabbitmq崩溃时也能保存队列
    仅仅使用durable=True ,只能持久化队列,不能持久化消息
    消息持久化需要在消息生成时,添加参数 properties=pika.BasicProperties(delivery_mode=2)
    
    • 1
    • 2
    • 3
    • 4

    七.nosql相关(主要是redis)

    1.redis和memcache的区别

    redis和memcache的区别;
    1、 Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。 
    2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。 
    3、虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘 
    4、过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10 
    5、分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从 
    6、存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化) 
    7、灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复 
    8、Redis支持数据的备份,即master-slave模式的数据备份。
    
    关于redis和memcache的不同,下面罗列了一些相关说法,供记录:
    
    redis和memecache的不同在于: 
    1、存储方式: 
    memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小 
    redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。 
    2、数据支持类型: 
    redis在数据支持上要比memecache多的多。 
    3、使用底层模型不同: 
    新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 
    4、运行环境不同: 
    redis目前官方只支持LINUX 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上
    
    个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis,其他简单的key/value存储,选择memcache。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3.redis是如何持久化的:rdb和aof;

    RDB持久化
    RDB持久化是将进程数据写入文件,RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。
    AOF持久化
    AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中。
    随着时间的流逝,你会发现这个AOF文件越来越大,于是redis有一套rewrite机制,来缩小AOF文件的体积。然而,在rewrite的过程中也是需要父进程来fork出一个子进程进行rewrite操作。因此AOF也是会影响redis的性能的。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.redis集群如何同步;

    
    1、从服务器向主服务器发送SYNC命令;
    2、收到SYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令;
    3、当主服务器的BGSAVE命令执行完毕时,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
    4、主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态。
    
    SYNC命令是非常消耗资源的,因为每次执行SYNC命令,主从服务器需要执行一下操作:
    1、主服务器需要执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘I/O资源;
    2、主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的时间产生影响;
    3、接收到RDB文件的从服务器需要载入主服务器发来的RDB文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求。
    SYNC是一个如此消耗资源的命令,所以Redis最好在真需要的时候才需要执行SYNC命令。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5.redis的数据添加过程是怎样的:哈希槽;

    哈希槽是用来决定这个key存在哪个节点,通过哈希计算,得到槽值为1,那么这个数据将存到节点1的Redis上
    一个 redis 集群包含 16384 个哈希槽(hash slot),数据库中的每个数据都属于这16384个哈希槽中的一个。集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽。集群中的每一个节点负责处理一部分哈希槽。
    
    • 1
    • 2

    6.redis的淘汰策略有哪些;

    redis内存数据数据集大小升到一定大的时候,就会实行数据淘汰策略(回收策略)。
    1,volatile-lru:从已设置过期时间的哈希表(server.db[i].expires)中随机挑选多个key,然后在选到的key中用lru算法淘汰最近最少使用的数据
    2,allkey-lru:从所有key的哈希表(server.db[i].dict)中随机挑选多个key,然后再选到的key中利用lru算法淘汰最近最少使用的数据
    3,volatile-ttl:从已设置过期时间的哈希表(server.db[i].expires)中随机挑选多个key,然后在选到的key中选择过期时间最小的数据淘汰掉。
    4,volatile-random:从已设置过期时间的哈希表(server.db[i].expires)中随机挑选key淘汰掉。
    5,allkey-random:从所有的key的哈希表(server.db[i].dict)中随机挑数据淘汰
    6,no-eviction(驱逐):内存达到上限,不淘汰数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    7.redis有哪些数据结构;

    Redis 有 5 种基础数据结构,它们分别是:string(字符串)、list(列表)、hash(字典)、set(集合) 和 zset(有序集合)。
    
    • 1

    八.zookeeper:

    1.zookeeper是什么

    zookeeper是什么
    ZooKeeper 分布式服务框架是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
    
    
    • 1
    • 2
    • 3

    2.zookeeper哪里用到;

    zookeeper可以用于搭建高可用服务框架,主要先看以下几个应用场景:
    1、 master的选举基本思路和编码实现
    2、 数据的发布和订阅
    3、 软负载均衡
    4、 分布式队列
    5、 分布式锁
    6、 命名服务
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.zookeeper的选主过程;

    选主机制
    Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。leader选举是保证分布式数据一致性的关键。
    
    
    • 1
    • 2
    • 3

    4.zookeeper集群之间如何通讯;

    Zookeeper的通信流程
    Session创建
    当启动一个Zookeeper client的时候,首先按照一定的算法查找出follower, 然后与Follower建立起NIO连接。当连接建立好后,发送create session的命令。当server收到create session命令,先从本地的session列表中查找看是否已经存在有相同sessionId,则关闭原session重新创建新的session。创建session的过程将需要发送到Leader,再由leader通知其他follower,大部分Follower都将此操作记录到本地日志再通知leader后,leader发送commit命令给所有Follower,连接客户端的Follower返回创建成功的session响应。
    
     Zookeeper查询命令
    Zookeeper查询命令主要用来查询服务器端的数据,不会更改服务器端的数据。所有的查询命令都可以即刻从client连接的server立即返回,不需要leader进行协调。查询命令包括以下这些命令:
    
    exists:判断指定path的node是否存在,如果存在则返回true,否则返回false.
    getData:从指定path获取该node的数据
    getACL:获取指定path的ACL。
    getChildren:获取指定path的node的所有孩子结点。
    所有的查询命令都可以设置watcher,通过它来跟踪指定path的数据变化。一旦指定的数据发生变化(create,delete,modified,children_changed,setData,setACL),Watcher监听器被触发服务器,服务端会数据变化通知给客户端,一次性失效。
    
    Zookeeper修改命令
    Zookeeper修改命令主要是用来修改节点数据或结构,或者权限信息。任何修改命令都需要提交到leader进行协调,协调完成后才返回。修改命令主要包括:
    createSession:请求server创建一个session
    create:创建一个节点
    delete:删除一个节点
    setData:修改一个节点的数据
    setACL:修改一个节点的ACL
    closeSession:请求server关闭session
    任何修改命令都需要leader协调。 在leader的协调过程中,需要3次leader与Follower之间的来回请求响应,并且在此过程中还会涉及事务日志的记录。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5.你们的zookeeper的节点加密是用的什么方式;

    CL全称为Access Control List(访问控制列表),用于控制资源的访问权限。ZooKeeper使用ACL来控制对其znode(ZooKeeper数据树的数据节点)的访问。ACL实现与UNIX文件访问权限非常相似:它使用权限位来允许/禁止针对节点的各种操作以及位应用的范围。与标准UNIX权限不同,ZooKeeper节点不受用户(文件所有者),组和world(其他)的三个标准范围的限制。
    zk利用ACL策略控制节点的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等。
    在传统的文件系统中,一个文件拥有某个组的权限即拥有了组里的所有权限,文件或子目录默认会继承自父目录的ACL。而在Zookeeper中,znode的ACL是没有继承关系的,每个znode的权限都是独立控制的,只有客户端满足znode设置的权限要求时,才能完成相应的操作。Zookeeper的ACL,分为三个维度:scheme、id、permission,通常表示为:scheme:id:permission,schema代表授权策略,id代表用户,permission代表权限。
    
    https://blog.csdn.net/qq_34021712/article/details/82871976
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.分布式锁的实现过程;

    常用的分布式锁有哪些?
    1.利用数据库实现排他锁
    2.基于redis实现分布式锁
    3.zookeeper做分布式锁
    4.基于consul实现分布式锁
    方式对比:
    
    1.利用数据库实现排他锁
    方案一: 利用表字段的唯一约束
    如果插入成功,则表示获取锁,插入失败则获取锁失败,可以进行重试。
    方案二: 基于表字段版本号
    在设计表的时候就为每张表设置一个版本号字段,在进行写操作的时候先查出一条数据的版本号,然后再写入的时候判断这个版本号是否被更改,如果更改则写入失败。
    
    2.基于redis实现分布式锁
    首先明白set()和setnx()命令的区别:set()当key存在时可以覆盖value,setnx()则不会覆盖value;
    
    方案一: 通过redis的setnx()和expire()命令实现
    Long flag = setnx(String key,String value)
    如果key已经存在则返回值flag=0,什么也不做,如果key不存在返回flag=1,添加key成功;
    expire(String key,int seconds);设置超时时间,避免死锁问题。
    del(String key); 业务处理完成后,删除setnx设置的键。
    
    方案二: 基于redis的setnx()、get()、getset()方法
    
    getset(key,newValue):该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么就返回null.
    实现步骤:
    setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2。
    get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 3。
    计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime。
    判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
    在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。
    
    3.zookeeper做分布式锁
    Watch 机制,client 可以监控每个节点的变化,当产生变化会给 client 产生一个事件。
    方案一: 基于zk的临时节点与watch机制
    原理: 创建一个普通节点/lock,当线程需要获取锁时,就在/lock节点下创建一个临时节点,创建成功则表示获取锁成功,失败则利用watch /lock节点,有删除节点时再去争取锁。临时节点的好处在于当进程挂掉后能自动删除上锁的节点,所以不会发生死锁现象。
    缺点: 所有获取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程都一起去创建节点,并发量很大。
    方案二: 基于zk的临时有效节点与watch机制
    原理:上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。
    
    方式对比:
    三种方案的比较从理解的难易程度角度(从低到高): 数据库 > 缓存 > Zookeeper
    从实现的复杂性角度(从低到高): Zookeeper >= 缓存 > 数据库
    从性能角度(从高到低): 缓存 > Zookeeper >= 数据库
    从可靠性角度(从高到低): Zookeeper > 缓存 > 数据库
    ————————————————
    https://blog.csdn.net/jiang18238032891/article/details/98031812
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    7.springcloud五大组件

          springcloud常用五大组件:
                1、服务发现——Netflix Eureka
                2、负载均衡——Netflix Ribbon
                3、断路器——Netflix Hystrix
                4、服务网关——Netflix Zuul
                5、分布式配置——Spring Cloud Config
                
        Eureka 是分为 server 和 service , server 通常只有一个(多个可以提高高可用性及容错),service 有一般多个。service 通常都是单个 springboot 程序,启动时向 server 注册自己,这样,在 RestTemplate 或者 feign 调用时,不需要根据 ip 及 端口 去查找,只需要知道你这个 springboot 程序的 applic.name 即可(当ip地址多的时候,每启动一下就配置一次会很麻烦)。
    
        Ribbon 一般结合 RestTemplate 去使用,RestTemplate 默认实现了 负载均衡,轮询机制,及多个相同 application.name 的程序,不同ip或端口,Ribbon 不需要知道你的ip和端口,只需要知道你在 Eureka server 注册的名字,便会 不断对具有相同名字的 程序进行轮询。
    
        Hystrix 断路器,一般 Ribbon 去访问某个 程序应用时,这个程序挂掉了,此时如果没任何措施,可能引起连锁反应,最后导致不可想象的后果,而 断路器 的作用就是,当这个程序不可用时,及时切断之间联系,当程序恢复时,在保持关联。
    
        Zuul 网关,他的作用类似 nginx 的反向代理,除此之外,还可以配置权限验证等。当后台有多个应用时,app或者前端程序想访问后端接口时,统一格式,否则程序一多便会出现混乱。
    
        Spring Cloud Config 分布式配置,他的作用见名知意,通过 连接 git 或 svn 等,实现统一的 文件配置,结合 spring cloud bus 可以实现动态的属性配置。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    8. 分布式系统数据一致性的解决方案

    分布式事务的目的是保障分库数据一致性,而跨库事务会遇到各种不可控制的问题
    
    1.基于XA协议的两阶段提交方案(2PC/3PC)
    交易中间件与数据库通过 XA 接口规范,使用两阶段提交来完成一个全局事务, XA 规范的基础是两阶段提交协议。
    第一阶段是表决阶段,所有参与者都将本事务能否成功的信息反馈发给协调者;第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交或者回滚。
    两阶段提交方案应用非常广泛,几乎所有商业OLTP数据库都支持XA协议。但是两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
    缺点:
         1.同步阻塞问题:执行过程中,所有参与节点都是事务阻塞型的。
         2.单点故障:由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
         3.数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
         4.太过保守
            如果协调者询问参与者事务提交的过程中,参与者出现故障导致无法获取所有参与者的响应信息的话,这时协调者只能依靠其自身的超市机制来判断是否中断事务,任何一个节点的失败都会导致整个事务的失败。
    
    优点:相较于二阶段提交协议,三阶段提交一些最大的优点就是降低了参与者的阻塞范围,并且能够在出现单点故障后继续达成一致。
       缺点:如果进入PreCommit后,协调者发出的是abort请求,假设只有一个参与者收到并进行了abort操作,而其他对于系统状态未知的参与者(网络分区)会根据3PC选择继续Commit,此时系统状态还是会发生不一致性。
       
    2.TCC方案
    TCC方案在电商、金融领域落地较多。TCC方案其实是两阶段提交的一种改进。其将整个业务逻辑的每个分支显式的分成了Try、Confirm、Cancel三个操作。Try部分完成业务的准备工作,confirm部分完成业务的提交,cancel部分完成事务的回滚。
    TCC方案让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。 当然TCC方案也有不足之处,集中表现在以下两个方面:
    对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。
    实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。
    
    3.本地消息表
    这种实现方式的思路,其实是源于ebay,后来通过支付宝等公司的布道,在业内广泛使用。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。
    基于消息的最终一致性方案对应用侵入性也很高,应用需要进行大量业务改造,成本较高。
    
    4.GTS--分布式事务解决方案
    GTS是一款分布式事务中间件,由阿里巴巴中间件部门研发,可以为微服务架构中的分布式事务提供一站式解决方案。
    
    更多GTS资料请访问研发团队微博。
    
    4.1 GTS的核心优势
    性能超强
    GTS通过大量创新,解决了事务ACID特性与高性能、高可用、低侵入不可兼得的问题。单事务分支的平均响应时间在2ms左右,3台服务器组成的集群可以支撑3万TPS以上的分布式事务请求。
    
    应用侵入性极低
    GTS对业务低侵入,业务代码最少只需要添加一行注解(@TxcTransaction)声明事务即可。业务与事务分离,将微服务从事务中解放出来,微服务关注于业务本身,不再需要考虑反向接口、幂等、回滚策略等复杂问题,极大降低了微服务开发的难度与工作量。
    
    完整解决方案
    GTS支持多种主流的服务框架,包括EDAS,Dubbo,Spring Cloud等。 
    有些情况下,应用需要调用第三方系统的接口,而第三方系统没有接入GTS。此时需要用到GTS的MT模式。GTS的MT模式可以等价于TCC模式,用户可以根据自身业务需求自定义每个事务阶段的具体行为。MT模式提供了更多的灵活性,可能性,以达到特殊场景下的自定义优化及特殊功能的实现。
    
    容错能力强
    GTS解决了XA事务协调器单点问题,实现真正的高可用,可以保证各种异常情况下的严格数据一致。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    9.分布式的缺点

    1、架构设计变得复杂(尤其是其中的分布式事务)
    
    2、部署单个服务会比较快,但是如果一次部署需要多个服务,部署会变得复杂
    
    3、系统的吞吐量会变大,但是响应时间会变长
    
    4、运维复杂度会因为服务变多而变得很复杂
    
    5、架构复杂导致学习曲线变大
    
    6、测试和查错的复杂度增大
    
    7、技术可以很多样,这会带来维护和运维的复杂度
    
    8、管理分布式系统中的服务和调度变得困难和复杂
    ————————————————
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    九.linux相关

    1.linux常用的命令有哪些

    linux常用的命令有哪些
    pwd    cd    ls    grep    cp    mv    rm    df -h(查看磁盘使用情况)
    
    • 1
    • 2

    2.如何获取java进程的pid;

    ps ps -ef|grep java
    
    • 1

    3.如何获取某个进程的网络端口号;

    lsof   –i:端口号
    
    • 1

    4.如何实时打印日志;

    tail -f catalina.out
    
    • 1

    5.如何统计某个字符串行数;

    Linux 统计某个字符串出现的次数
    grep -o objStr  filename|wc -l
    
    • 1
    • 2

    十.设计与思想

    2.一千万的用户实时排名如何实现;

    一千万的用户实时排名如何实现
    Redis sorted set 排名
    
    相同的分桶策略,MySQL排名速度会比Redis慢一些,MySQL节省内存,查询逻辑比Redis复杂,相比Redis可以实现复合多字段排名。Redis相比查询比MySQL高效,实现逻辑简单,相比MySQL耗费内存,以内存空间换速度。
    
    • 1
    • 2
    • 3
    • 4

    3.万人并发抢票怎么实现;

    微服务的设计思想,然后再用分布式的部署方式
    
    缓存雪崩,缓存击穿,缓存穿透
    缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
    缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
    缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
    
    五万人并发抢票怎么实现
    秒杀链接加盐
    Redis集群
    Nginx
    按钮控制
    前端限流+后端限流
    库存预热
    
    限流,顶不住就挡一部分出去但是不能说不行,降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就独立的,但是你会调用其他的系统嘛,你快不行了你别拖累兄弟们啊。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/427528
    推荐阅读
    相关标签
      

    闽ICP备14008679号