查漏补缺 面试

  |   0 评论   |   0 浏览

开放性问题

  1. java有什么优点,有什么缺点?
    1. Java是纯面向对象的语言 Java吸取了C++面向对象的概念,将数据封装于类中,利用类的优点,实现了程序的简洁性和便于维护性。类的封装性、继承性等有关对象的特性,使程序代码只需一次编译,然后通过上述特性反复利用。
    2. 平台无关性 Java语言可以“一次编译,到处执行”。由于Java是解释性语言,编译器会将Java代码变成“中间代码”,然后在Java虚拟机(Java Virtual Machine,JVM)上解释执行。由于中间代码与平台无关,因此Java语言可以很好的跨平台执行,具有很好的可移植性。
    3. Java提供了很多内置的类库,通过这些类库,简化了开发人员的程序设计工作,同时缩短了项目的开发时间。例如,Java语言提供了对多线程的支持,提供了对网络通信的支持,最主要的是提供了垃圾回收器,这使得开发人员从内存的管理中解脱出来。
    4. 具有较好的安全性和健壮性。Java语言经常被用在网络环境中,为了增强程序的安全性,Java语言提供了一个防止恶意代码攻击的安全机制(数组边界检测和Bytecode校验等)。Java的强类型机制、垃圾回收器、异常处理和安全检查机制使得用Java语言编写的程序具有很好的健壮性。
    5. 分布式 Java建立在扩展TCP/IP网络平台上。库函数提供了用HTTP和FTP协议传送和接受信息的方法。这使得程序员使用网络上的文件和使用本机文件一样容易。

缺点:

第一: 运行速度慢,众所周知,Java程序的运行依赖于Java虚拟机,所以相对于其他语言(汇编,C,C++)编写的程序慢,因为它不是直接执行机器码。

第二: 因为Java考虑到了跨平台性。所以他不能像语言(例如:汇编,C) 那样更接近操作系统。也就不能和操作系统的底层打交道了。但可以通过Java的JNI(即Java本地接口)。

优化

方舟编译器 解决安卓程序“边解释边执行”的低效率问题

安卓系统使用Java作为编程语言,易于开发,但是不会将代码直接编译成机器语言,程序运行时有相当一部分代码还需要通过手机上的虚拟机临时同步编译,影响程序执行的效率。华为方舟编译器采取了静态编译的方式,是首个取代了安卓虚拟机模式的静态编译器。

方舟编译器采用全程执行机器码高效运行程序,架构进一步得到优化,可供开发者在开发环境一次性的将高级语言编译为机器码,手机安装应用程序后可全速运行程序,带来效率上的极大提升。

  1. 你遇到的难题 以及你是怎么解决的

多线程问题 protoBuffer 接口优化时 签名问题 签名偶发性出现不一致,排查出MD5线程不安全。

java.lang.OutOfMemoryError,java heap space

线上出现OOM异常 发生问题的时间点 发版记录 连接线上服务器

JVM启动参数配置了-XX:+HeapDumpOnOutOfMemoryError

下载dump文件 使用 Eclipse Memory Analyzer 分析堆内存里存的对象 byte数组占用了接近JVM配置的最大堆的大小也就是6GB

第二步看一下究竟是哪些byte数组,数组是啥内容:

深入的看看占用内存过多的对象是被谁引用的,哪个线程引用的,他们里面都是什么东西

展开这个线程,到底他创建了哪些对象

查看 返回对象是一个业务查询数据时没有传入条件一下查询出来上百万条数据引发了系统的OOM。

  1. jvm都能运行什么语言 与C++比为什么C++这么快 你有什么想法优化它?

Kotlin Scala

运行速度慢,众所周知,Java程序的运行依赖于Java虚拟机,所以相对于其他语言(汇编,C,C++)编写的程序慢,因为它不是直接执行机器码。

因为Java考虑到了跨平台性。所以他不能像语言(例如:汇编,C) 那样更接近操作系统。也就不能和操作系统的底层打交道了。但可以通过Java的JNI(即Java本地接口)。

JIT编译器运行占用的是用户程序运行时间,具有很大的时间压力,它能提供的优化手段也严重受制于编译成本。

Java语言是动态的类型安全语言,这意味着需要由虚拟机来确保程序不会违反语言语义或访问非结构化内存。在实现层面上看,这就意味着虚拟机必须频繁进行动态检查,如对象实例访问时检查空指针、数组元素访问时检查上下界范围、类型转换时检查继承关系等等。对于这类程序代码没有明确写出的检查行为,尽管编译器会努力进行优化,但是总体上仍然要消耗着不少的运行时间。

Java语言中的对象内存分配都是堆上进行,只有方法中的局部变量才在栈上分配。而C/C++的对象则有多种内存分配方式,既可能在堆上分配,也可能在栈上分配,如果可以把线程私有的对象在栈上分配,将可以减轻内存回收的压力,也不需要考虑内存屏障方面的问题。另外,C/C++中主要由用户程序代码来回收分配的内存,这就不存在无用对象筛选的过程,因此效率上(仅指运行效率,排除了开发效率)也垃圾收集机制要高。

  1. 题库,每次答的题不一样 如何设计

时间戳

  1. 线上慢接口如何优化的

埋点追踪分析,找出真凶 对方法加日志,此时去找log就可以找到每一步跑的时间。根据实际可以一眼看出是哪一步跑慢了。那么这一步就是主要优化的方向了。。

首先需要分析为何跑慢了?

1.是不是资源层面的瓶颈?

2.是不是缓存没添加,如果加了,是不是热点数据导致负载不均衡?

3.是不是有依赖于第三方接口?

4.是不是接口涉及业务太多,导致程序跑很久?

5.是不是sql层面的问题导致的等待时机加长,进而拖慢接口?

6.网络层面的原因?带宽?DNS解析?

7.代码确实差???

8.未知?

对症下药

1.资源紧张,加机器,干上去,负载均衡搞起来

2.加缓存可以解决的问题都不是什么大问题,存在热点数据可以将某几个热点单独出来用专门的机器进行处理,不要因为局部影响整体

3.一方面与第三方沟通接口响应问题,另一方面超时时间注意把控,如果可以非核心业务能异步久异步掉。

4.把非核心的业务进行异步化操作。记住如果代码层面是非核心业务,但是会影响用户感知,需要慎重决定是否异步。

5.如果是代码不良导致加锁了,尽量优化索引或sql语句,让锁的级别最小(到行),一般来说到行差不多了。如果是单个sql跑慢了,需要分析是不是索引没加或者sql选的索引错了,索引该加的就加了,该force index也加了。

6.网路原因,需要联系运营商一起商量下怎么解决,单方面比较难有大的优化。

7.代码确实差,那也无药可救了。

  1. 100亿数据找出最大的1000个数字(top K问题)

top K问题

1、最容易想到的方法是将数据全部排序。该方法并不高效,因为题目的目的是寻找出最大的1000个数即可,而排序却是将所有的元素都排序了,做了很多的无用功。

2、局部淘汰法。用一个容器保存前10000个数,然后将剩余的所有数字一一与容器内的最小数字相比,如果所有后续的元素都比容器内的10000个数还小,那么容器内这个10000个数就是最大10000个数。如果某一后续元素比容器内最小数字大,则删掉容器内最小元素,并将该元素插入容器,最后遍历完这1亿个数,得到的结果容器中保存的数即为最终结果了。此时的时间复杂度为O(n+m^2),其中m为容器的大小。

3、第三种方法是分治法,即大数据里最常用的MapReduce。

a、将100亿个数据分为1000个大分区,每个区1000万个数据

b、每个大分区再细分成100个小分区。总共就有1000*100=10万个分区

c、计算每个小分区上最大的1000个数。

d、合并每个大分区细分出来的小分区。每个大分区有100个小分区,我们已经找出了每个小分区的前1000个数。将这100个分区的1000*100个数合并,找出每个大分区的前1000个数。

e、合并大分区。我们有1000个大分区,上一步已找出每个大分区的前1000个数。我们将这1000*1000个数合并,找出前1000.这1000个数就是所有数据中最大的1000个数。

4.Hash法。如果这1亿个书里面有很多重复的数,先通过Hash法,把这1亿个数字去重复,这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间,然后通过分治法或最小堆法查找最大的10000个数。

  1. 海量数据找到出现次数最多的100个(内存不足的时候可以先做hash分片,最后多路merge,每次操作可以用hashMap计数,也可以自己做hash函数计数)
  2. 一个进程最多申请多大空间

32位意味着4G的寻址空间,linux把它分为两部分:最高的1G(虚拟地址从0xC0000000到0xffffffff)用做内核本身,成为“系统空间”,而较低的3G字节(从0x00000000到0xbffffff)用作各进程的“用户空间”。这样,理论上每个进程可以使用的用户空间都是3G。当然,实际的空间大小收到物理存储器大小的限制。虽然各个进程拥有其自己的3G用户空间,系统空间却由所有的进程共享。从具体进程的角度看,则每个进程都拥有4G的虚拟空间,较低的3G为自己的用户空间,最高的1G为所有进程以及内核共享的系统空间。

现有服务的配置

  1. 线上服务器技术架构

Spring cloud lvs nginx redis mysql mongo kafka 数据中台

  1. 线上服务器配置

逻辑cpu 40个 10核 内存64G

CPU使用率是 40%--80%

-Xms6g -Xmx6g

-Xms3g -Xmx3g

  1. kafka集群线上配置 集群同步数据

由于kafka底层IO的实现是基于Java的selector,selector在Linux上实现的机制是epoll(异步),在Windows上实现的是(select),因此它在Linux上能实现更高效的IO性能,而且Linux可以实现零拷贝机制。

我们来计算一下:每天 1 亿条 1KB 大小的消息,保存两份且留存两周的时间,那么总的空间大小就等于 1 亿 * 1KB * 2 / 1000 / 1000 = 200GB。一般情况下 Kafka 集群除了消息数据还有其他类型的数据,比如索引数据等,故我们再为这些数据预留出 10% 的磁盘空间,因此总的存储容量就是 220GB。既然要保存两周,那么整体容量即为 220GB * 14,大约 3TB 左右。Kafka 支持数据的压缩,假设压缩比是 0.75,那么最后你需要规划的存储空间就是 0.75 * 3 = 2.25TB。

  1. redis集群线上配置 集群同步数据

主从模式

哨兵模式

集群模式

  1. mysql集群线上配置
  2. PV 日活 QPS TPS

1、吞吐量(TPS):
吞吐量是指系统在单位时间内处理请求的数量;也就是事务数/秒。它是软件测试结果的测量单位。

2、每秒查询率QPS(TPS):
每秒钟request/事务 数量;是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
(一次事务查询T 可能进行了多次服务器请求Q)

3、并发数:
系统同时处理的request/事务数

4、并发用户数:
并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量;与吞吐量相比,并发用户数是一个更直观但也更笼统的性能指标。

5、响应时间(RT):
响应时间是指系统对请求作出响应的时间;一般取平均响应时间

1、PV(Page View):
访问量, 即页面浏览量或点击量,衡量网站用户访问的网页数量;在一定统计周期内用户每打开或刷新一个页面就记录1次,多次打开或刷新同一页面则浏览量累计。

3、IP(Internet Protocol):

独立IP数,是指1天内多少个独立的IP浏览了页面,即统计不同的IP浏览用户数量。同一IP不管访问了几个页面,独立IP数均为1;不同的IP浏览页面,计数会加1。 IP是基于用户广域网IP地址来区分不同的访问者的,所以,多个用户(多个局域网IP)在同一个路由器(同一个广域网IP)内上网,可能被记录为一个独立IP访问者。如果用户不断更换IP,则有可能被多次统计。

4、UIP(Unique IP):
独立IP,和UV类似,正常情况下,同一个IP可能会有很多个UV,同一个UV只能有一个IP.

5、VV(Visit View):
访问次数,是指统计时段内所有访客的PV总和。

  1. IO型CPU计算型区别 线上线程池配置 线上服务器配置
  2. CPU利用率 CPU使用率过高怎么排查

CPU使用率是 40%--80%

-Xms6g -Xmx6g

-Xms3g -Xmx3g

1、先通过top命令找到消耗cpu很高的进程id假设是123

2、执行top -p 123单独监控该进程

3、在第2步的监控界面输入H,获取当前进程下的所有线程信息

4、找到消耗cpu特别高的线程编号,假设是123

5、执行jstack 123456对当前的进程做dump,输出所有的线程信息

6 将第4步得到的线程编号11354转成16进制是0x7b

7 根据第6步得到的0x7b在第5步的线程信息里面去找对应线程内容

8 解读线程信息,定位具体代码位置

原因

1.程序计算比较密集

2.程序死循环

3.程序逻请求堵塞

4.IO读写太高

  1. 线上使用的是哪种垃圾回收 G1

基础部分

  1. 什么是对象
  2. hashcode 和 equals 方法常用地方
  3. Set 和 List 区别?
  4. ArrayList 和 LinkedList 区别
  5. 如何删除List存在的某些元素 一次遍历 for循环遍历会报什么异常
  6. hashmap put 方法存放的时候怎么判断是否是重复的
  7. HashTable 你了解过吗
  8. HashMap的实现原理

HashMap的put()和get()的实现

map.put(k,v)实现原理

第一步首先将k,v封装到Node对象当中(节点)。

第二步它的底层会调用K的hashCode()方法得出hash值。

第三步通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

map.get(k)实现原理

第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。

第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

  1. hashmap底层结构画一下,手写代码做一个url解析器,用正则方式和hashMap的数据结构。
  2. HashMap 的扩容过程

java1.7 如果原有table长度已经达到了上限,就不再扩容了。 如果还未达到上限,则创建一个新的table,并调用transfer方法

transfer方法的作用是把原table的Node放到新的table中,使用的是头插法,也就是说,新table中链表的顺序和旧列表中是相反的,在HashMap线程不安全的情况下,这种头插法可能会导致环状节点。

jdk1.7扩容是重新计算hash;jdk1.8是要看看原来的hash值新增的那个bit是1还是0好了,如果是0则索引没变,如果是1则索引变成"原索引+oldCap".这是jdk1.8的亮点,设计的确实非常的巧妙,即省去了重新计算hash值得时间,又均匀的把之前的冲突的节点分散到新的数组bucket上

jdk1.7在rehash的时候,旧链表迁移到新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是jdk1.8不会倒置

  1. arraylist.sort怎么实现的
  2. Concurrenthashmap 是怎么做到线程安全的?ConcurrentHashMap和HashTable的区别
  3. CopyOnWriteList底层是什么,适用的情况,vector的特点,实现的是List接口吗。
  4. 什么是线程安全,如何保证线程安全问题?
  5. 并发注意什么,线程实现同步的方式,通信
  6. volatile的实现机制
  7. lock 和 synchronized 的区别
  8. java的优先级队列,如果让你设计一个数据结构实现优先级队列如何做?
  9. AQS AbstractQueuedSynchronizer

抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。

这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态

初始状态下,这个state的值是0。

另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。

接着线程跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。

如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。

一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。所以大家看下面的图,就是线程1跑过来加锁的一个过程。

其实看到这儿,大家应该对所谓的AQS有感觉了。说白了,就是并发包里的一个核心组件,里面有state变量、加锁线程变量等核心的东西,维护了加锁状态。

你会发现,ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的。

这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁。

可重入锁的意思,就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。

其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。

接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?

我们来看看锁的互斥是如何实现的?

线程2跑过来一下看到,哎呀!state的值不是0啊?所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!

接着线程2会看一下,是不是自己之前加的锁啊?当然不是了,“加锁线程”这个变量明确记录了是线程1占用了这个锁,所以线程2此时就是加锁失败。

接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了

所以大家可以看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!

同样,给大家来一张图,一起感受一下:

接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁!他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null!

接下来,会从等待队列的队头唤醒线程2重新尝试加锁。

好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。

此外,还要把“加锁线程”设置为线程2自己,同时线程2自己就从等待队列中出队了。

Condition队列

AQS中还有另一个非常重要的内部类ConditionObject,它实现了Condition接口,主要用于实现条件锁。

ConditionObject中也维护了一个队列,这个队列主要用于等待条件的成立,当条件成立时,其它线程将signal这个队列中的元素,将其移动到AQS的队列中,等待占有锁的线程释放锁后被唤醒。

Condition典型的运用场景是在BlockingQueue中的实现,当队列为空时,获取元素的线程阻塞在notEmpty条件上,一旦队列中添加了一个元素,将通知notEmpty条件,将其队列中的元素移动到AQS队列中等待被唤醒。

  1. CAS原理 AtomicInteger >>> Unsafe >>> cas >>> aba
  2. 悲观锁乐观锁,底层怎么实现的,越详细越好

乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

乐观锁一般会使用版本号机制或CAS算法实现

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

需要读写的内存值 V

进行比较的值 A

拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

乐观锁的缺点

ABA 问题是乐观锁一个常见的问题

1 ABA 问题

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2 循环时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3 只能保证一个共享变量的原子操作

CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

  1. 公平锁和非公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死
  1. 锁的膨胀过程,Synchronized和Lock的区别,底层的monitor实现和unsafe类的CAS函数,参数表示什么,寄存器cpu如何做)

偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。

一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个

线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将

对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,( 偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。

轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

可以发现synchronized同步代码块是通过加monitorenter和monitorexit指令实现的。

每个对象都有个**监视器锁(monitor) **,当monitor被占用的时候就代表对象处于锁定状态,而monitorenter指令的作用就是获取monitor的所有权,monitorexit的作用是释放monitor的所有权,这两者的工作流程如下:

monitorenter:

如果monitor的进入数为0,则线程进入到monitor,然后将进入数设置为1,该线程称为monitor的所有者。

如果是线程已经拥有此monitor(即monitor进入数不为0),然后该线程又重新进入monitor,则将monitor的进入数+1,这个即为锁的重入。

如果其他线程已经占用了monitor,则该线程进入到阻塞状态,知道monitor的进入数为0,该线程再去重新尝试获取monitor的所有权。

monitorexit:执行该指令的线程必须是monitor的所有者,指令执行时,monitor进入数-1,如果-1后进入数为0,那么线程退出monitor,不再是这个monitor的所有者。这个时候其它阻塞的线程可以尝试获取monitor的所有权。

  1. 分布式锁
  2. redisson 分布式锁原理 自动延期
  3. 线程池核心参数

corePoolSize:

线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。

maximumPoolSize:

线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。

1、corePoolSize:核心线程数

  • 核心线程会一直存活,及时没有任务需要执行
  • 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
  • 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

2、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行

3、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

4、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0

5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
44. 阻塞队列

BlockingQueue的核心方法

放入数据

  • offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
  • offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败
  • put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.

获取数据

  • poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
  • poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间 超时还没有数据可取,返回失败。
  • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
  • drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

常见BlockingQueue

ArrayBlockingQueue

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

LinkedBlockingQueue

基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

DelayQueue

DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

SynchronousQueue

一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。

  声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:

  如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;

  但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

 BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待于唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。

  1. 类加载机制
  2. jvm里 内容
  3. FullGc频率

jstat -gc java进程ID

S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT

0.0 12288.0 0.0 12288.0 2629632.0 1951744.0 1552384.0 843280.7 101912.0 96793.7 9932.0 8981.8 25192 769.371 0 0.000 769.371

S0C:第一个幸存区的大小 0.0

S1C:第二个幸存区的大小 12288.0

S0U:第一个幸存区的使用大小 0.0

S1U:第二个幸存区的使用大小 12288.0

EC:伊甸园区的大小 2629632.0

EU:伊甸园区的使用大小 1951744.0

OC:老年代大小 1552384.0

OU:老年代使用大小 843280.7

MC:方法区大小 101912.0

MU:方法区使用大小 96793.7

CCSC:压缩类空间大小 9932.0

CCSU:压缩类空间使用大小 8981.8

YGC:年轻代垃圾回收次数 25192

YGCT:年轻代垃圾回收消耗时间 769.371

FGC:老年代垃圾回收次数 0

FGCT:老年代垃圾回收消耗时间 0.000

GCT:垃圾回收消耗总时间 769.371

ps -eo pid,tty,user,comm,lstart,etime | grep 73098

73098 ? lbs java Wed Apr 21 19:34:32 2021 6-15:39:32

  1. 2核2G使用那个垃圾回收器比较好 每种垃圾回收器的优缺点和用途
  2. Minor GC与Full GC分别在什么时候发生?
  3. jvm调优如何检查内存泄露,如何优化gc参数
  4. GC收集器有哪些?CMS收集器与G1收集器的特点。
  5. 垃圾回收算法
  6. 线上问题排查
  7. java8函数式编程
  8. Tomcat为什么要重写类加载器?
  9. happens before 原理
  10. 几种常用的内存调试工具:jmap、jstack、jconsole。
  11. 类加载的五个过程:加载、验证、准备、解析、初始化。
  12. 双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。
  13. 做到线程安全的手段

1、synchronized锁(偏向锁,轻量级锁,重量级锁)

2、volatile锁,只能保证线程之间的可见性,但不能保证数据的原子性

3、jdk1.5并发包中提供的Atomic原子类

4、Lock锁

  1. 各种序列化技术的比较
  2. Executor,Executors,ExecutorService的区别
  3. 类加载器的加载层级:

【引导类加载器】(bootstrap class loader)

用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的(C实现的),并不继承自java.lang.ClassLoader。

加载扩展类和应用程序类加载器,并指定它们的父类加载器。

【扩展类加载器】(extensions class loader)

用来加载java的扩展库(JAVA_HOME/jre/lib/ext/*.jar,或java.ext.dirs路径下的内容)java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载java类。

有sun.miscLauncher$ExtClassLoader实现,继承自java.lang.ClassLoader

【应用程序类加载器】(application class loader)

它根据java应用的类路径(classpath,java.class.path路径)来加载指定路径的类,一般来说,java应用的类都是由它来完成加载的

由sun.misc.Launcher$AppClassLoader实现,继承自java.lang.ClassLoader

【自定义类加载器】

开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

说明:在java中由于类的加载采用的是双亲委托机制,上面几种类加载器是父子关系,其中引导类加载器为基础。

  1. 是否自定义过注解

  2. OOM异常产生及如何追踪定位

  3. Netty与普通NIO的比较;粘包/拆包

  4. HashMap、TreeMap、LinkedHashMap的使用场景:

    1.HashMap中k的值没有顺序,常用来做统计。

    2.LinkedHashMap吧。它内部有一个链表,保持Key插入的顺序。迭代的时候,也是按照插入顺序迭代,而且迭代比HashMap快。

    3.TreeMap的顺序是Key的自然顺序(如整数从小到大),也可以指定比较函数。但不是插入的顺序。

    4.Hashtable与 HashMap类似,它继承自Dictionary类、

    不同的是:它不允许记录的键或者值为空;它支持线程的同步、即任一时刻只有一个线程能写Hashtable,

    因此也导致了 Hashtable在写入时会比较慢

框架部分

  1. Bean与对象 的区别
  2. Spring Bean 的生命周期
  3. 实例化bean 反射的方式生成对象

2.填充bean的属性 populateBean,循环依赖问题 (三级缓存)

3.调用aware接口相关的方法,invokeAwareMethod方法 完成beanName beanFactory beanClassLoad对象的属性设置

4.调用BeanPostProcessor中的前置方法:使用比较多的有(ApplicationContentxPostProcessor,设置ApplicationContent,Environment,ResourceLoader,EmbeddValueResolver等对象)

5.调用initmethod方法:invokeInitmethod()判断是否实现了initaizingBean接口,如果有 调用a

6.调用BeanPostProcessor的后置处理方法:SpringAop就是在此处实现的,AbstractAutoProxyCreator

7.获取到完整对的对象,可以通过getBean的方式来进行对象的获取

8.销毁过程:1.判断是否实现了DispoableBean接口 2. 调用destroMethod方法

  1. 三级缓存 每级存什么 eurka三级缓存都存啥 三级缓存都有什么好处
  2. bean有哪些初始化方法
  3. Spring MVC 原理
  4. spring boot 启动流程 自动配置 与spring的区别
  5. Spring AOP IOC的原理
  6. spirng 事务传播特性
  7. Java种的代理有几种实现方式?
  8. 反射机制
  9. spring cloud eurka robin
  10. CAP理论
  11. 服务注册发现组件Eureka工作原理
  12. 断路器
  13. 服务降级
  14. SpringBoot自动配置工作原理
  15. 微服务划分力度 如何划分
  16. 微服务高可用怎么保证
  17. 分布式事务有哪些类型,如何使用;二段式和三段式的区别
  18. mybatis 一级缓存二级缓存
  19. 讲讲Spring中怎么对初始化的bean做其他操作。(这里有三种方式,@PostConstruct注解方式,init-method的XML配置方式,InitializingBean接口方式)
  20. 分布式事务
  21. git版本控制机制
  22. @Component、@Controller、@Service、@Repository区别

1、@controller 控制器(注入服务)用于标注控制层,相当于struts中的action层

2、@service 服务(注入dao)用于标注服务层,主要用来进行业务的逻辑处理

3、@repository(实现dao访问)用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件.

4、@component (把普通pojo实例化到spring容器中,相当于配置文件中的

泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。

  1. Spring解决循环依赖的思路
  2. 后台服务器对于一个请求是如何做负载均衡的,有哪些策略,会出现什么样的问题,怎么解决。(说了一致性hash算法,分布式hash的特性,具体的应用场景
  3. nginx 如何做动静分离,如何做反向代理

循环依赖也就是循环引用,指两个或多个对象互相持有对方的引用。

通俗地说,假设在Spring中有3个Service Bean,分别为ServiceA、ServiceB和ServiceC,

如果ServiceA引用了ServiceB,ServiceB引用了ServiceC,

而ServiceC又引用了ServiceA,最终形成可一个环,这样就出现了循环依赖。

对Spring来说循环依赖,有以下几种:

1、Prototype类型Bean的循环依赖

2、构造器循环依赖

3、setter循环依赖

Spring的循环依赖解决思路基本描述完成。可以看到,整体的处理方式还是很巧妙的。主要流程总结如下:

1、创建Bean过程中,将beanName放入singletonsCurrentlyInCreation正在创建池中,并创建对应的ObjectFactory放入对象工厂缓存池。

2、解析Bean依赖过程中,如果发现存在了循环依赖,则直接引用ObjectFactory所创建的提前暴露的Bean。

3、Bean的创建结束后,将其从singletonsCurrentlyInCreation中移除,并删除对应的ObjectFactory。

数据库部分

  1. mysql优化

数据库表连接问题 数据库表设计问题 sql执行计划 sql索引设计问题 sql语句设计问题 参数配置问题

  1. 主从同步机制
  2. myisam innodb引擎的区别

  1. 如何做读写分离,分库分表后全局id怎么设计
  2. 索引(包括分类及优化方式,失效条件,底层结构)
  3. 主键索引和普通索引的区别,组合索引怎么用会失效。
  4. 索引的前缀匹配的原理,从B树的结构上具体分析一下。
  5. 聚集索引在底层怎么实现的,数据和关键字是怎么存的。
  6. 组合索引和唯一性索引在底层实现上的区别
  7. sql的优化策略,慢查询日志怎么操作,参数含义。
  8. explain 每个列代表什么含义(关于优化级别 ref 和 all,什么时候应该用到index却没用到,关于extra列出现了usetempory 和 filesort分别的原因和如何着手优化等)

expain出来的信息有10列,分别是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra

id 我的理解是SQL执行的顺序的标识,SQL从大到小的执行 id相同时,执行顺序由上至下 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行 .id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

select_type 示查询中每个select子句的类型

  • SIMPLE(简单SELECT,不使用UNION或子查询等)
  • PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
  • UNION(UNION中的第二个或后面的SELECT语句)
  • DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
  • UNION RESULT(UNION的结果)
  • SUBQUERY(子查询中的第一个SELECT)
  • DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
  • DERIVED(派生表的SELECT, FROM子句的子查询)
  • UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

table 显示这一行的数据是关于哪张表的,有时不是真实的表名字,看到的是derivedx(x是个数字,我的理解是第几步执行的结果)

type 表示MySQL在表中找到所需行的方式,又称“访问类型”。

常用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)

possible_keys 指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用

key列显示MySQL实际决定使用的键(索引)

key_len 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)

不损失精确性的情况下,长度越短越好

ref 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

rows 表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数

Extra 该列包含MySQL解决查询的详细信息

Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤

Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询

Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”

Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。

Impossible where:这个值强调了where语句会导致没有符合条件的行。

Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行

  1. MySQL 事务特性和隔离级别

ACID

原子性 undo log mvcc 要么成功要么失败 失败回滚 成功提交 一个事务多条语句

一致性 最核心 最本质的要求

隔离性 锁 mvcc(多版本并发控制) 隐藏字段 undolog review

持久性 redo log

数据库的事务隔离级别有4种,分别是

读未提交,

读已提交,

可重复读,

序列化,

不同的隔离级别解决了脏读幻读不可重复读等相关问题,因此在选择隔离级别的时候要根据应用场景来觉得,使用适合的隔离级别

脏读 超过2个以上的事务操作 无论第二个事务有没有提交 第一个事务都能读到相应的数据 没有隔离性

不可重复读 同一事物两次查询 查询的结果不一样

幻读 当一个事务A读取某一个数据范围时,另一个事务B在这个范围插入行,A事务再次读取这个范围的数据时,会产生幻读 mvcc+间隙锁 某一个范围

当前读 快照读 当前读 与快照读 一起使用的时候 会出现幻读 临建锁+mvcc 解决

死锁 锁冲突

开启一个锁的属性

show innoDB_

死锁 排他锁 临建锁

  1. oracle分页语句

SELECT *

FROM (SELECT tt.*, ROWNUM AS rowno

  FROM (  SELECT t.*

            FROM emp t

           WHERE hire_date BETWEEN TO_DATE ('20060501', 'yyyymmdd')

                               AND TO_DATE ('20060731', 'yyyymmdd')

        ORDER BY create_time DESC, emp_no) tt

 WHERE ROWNUM <= 20) table_alias

WHERE table_alias.rowno >= 10;

  1. sql查询优化
  • 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
  • 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
  • 应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描 可以改用 union all
  • 下面的查询也将导致全表扫描:select id from t where name like ‘%李%’若要提高效率,可以考虑全文检索。
  • 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段
  • 尽量避免大事务操作,提高系统并发能力
  • 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
  1. 数据库表迁移方案
  2. 分库分表

主从复制

读写分离

高可用集群

主备

MGR 多主

水平分库 水平分表 垂直分库 垂直分表

按照某个数据范围分表

能不切分尽量不要切分

如果要切面 一定要选择合适的切分规则 提前规划好

数据切分尽量通过数据沉余或者表分组来降低夸库join的可能

由于数据库中间件对数据join实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表join

  1. sql join的本质

嵌套循环连接 并不支持哈希连接和合并连接

Simple Nested-Loop join

一条条记录比较

Index Nested-Loop Join

利用索引进行范围查找 当在索引上找到了符合的值,再回表进行查询

Block Nested-Loop join

有索引时走索引 没索引时 利用内存区域(缓存) join buffer 多次比较合并到一次 只加载到内存一次

join buffer size 连接缓存大小

  1. 分表 订单id,商户id,用户id
  2. 索引失效情况?

1.组合索引不遵从最左匹配原则

2.组合索引的前面索引列使用范围查询(< > like) 会导致后续索引失效

  1. 不要再索引上做任何操作 计算 函数 类型转换

4.isnull 和 is not null无法使用索引

5.尽量少使用or操作符,否则连接时索引会失效

6.字符串不添加引号会导致索引失效

7.两表关联使用的条件字段长中的长度不一致编码不一致会导致索引失效

8.like语句中以%开头的模糊查询

9.如果mysql中使用权标扫描比使用索引快也会导致索引失效

in 不可预估

优化表 optimize

修复表

  1. 说明一下数据库索引原理

IO 所有的数据都是存在磁盘里的 IO是瓶颈 每次从磁盘中取数据的时候 尽可能少的取数据 1 读取次数少 2 量少 不能把所有数据加到内存中 所以我们需要 分块读取 局部性原理 CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中

磁盘预读 将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入

数据结构 B+树 二叉树 AVL树 红黑树 B树

Msql B+树 hash表 存储引擎不同 提高查询效率 保证读取的数据有效 需要分块读取 存储系统里 磁盘 与内存是以页为单位的 读取时 一般读的都是页的整数倍 innoDB 默认读取16kb的数据

最终 树的分支 有且仅有2个 次数变多 把二叉树 变成多叉树 有序 多叉有序树 B树 检索时 数据和K值是放到一起的 每次读取数据时 每一块16KB里 会存着 索引数据+实际数据 存储空间占用大 树的分支范围变小

导致树想插入更多数据时 深度增加 考虑 B树中的 非叶子数据放到叶子节点中 非叶子节点存储key的值 叶子节点存储数据 依托这样的变化 有了这样的结构

B树

B+树