Java后端面试知识点整理

目录

系统架构

一、nginx

二、sentinel

三、JVM

1.Java堆(Heap)

2.方法区(Method Area)JVM规范的叫法

3.程序计数器(Program Counter Register)

4.JVM栈

5.本地方法栈(Native Method Stacks)

四、并发多线程[了解]

1.相关容器与相关类

        1)同步类容器

        2)并发类容器

2.阻塞队列

3.Copy-On-Write容器

4.其他类

5.Synchornized与Lock

Synchronized(适用于写比较多,粒度大)

Lock(粒度更细)

5.线程池

Executors创建线程池

自定义线程池

五、缓存

1.redis

 数据类型

redis架构:

Redis哨兵机制(Sentinel):

Redis Cluster集群:

2.缓存问题

2.1什么是缓存雪崩

2.2缓存穿透

2.3缓存击穿

2.4redis为什么快

六、mq

1.架构模式

生产者端的负载均衡

集群消费

2.消息队列有什么优点和缺点?

3.消息不丢失

4.如何保证消息不被重复消费

七、数据库

mysql 索引

2.事务并发处理可能引起的问题

3.隔离级别

4.sql优化

mysql索引为什么不用二叉树

八、k8s

1.Master节点

2.Node节点

3.网络模式

三种外部访问方式

Ingerss

问题

其他问题


系统架构

访问流程:

1、用户在浏览器访问系统地址,通过域名解析获取系统的ip。访问到底前端nginx,nginx发送请求到后端网关。

2、后端网关从nacos获取后端服务的地址,根据负载策略选择一台后端服务,发送请求。

3、后端访问处理业务逻辑、会读取redis、操作mysql、发送mq消息,通过feign client 调用其他的后端服务,完成业务操作,返回。

一、nginx

前端部署到nginx集群。

网关

使用spring getway,使用nacos作为配置中心和注册中心。使用sentinel 进行流量控制,对系统进行保护。

网关作用:

1、实现限流(sentinel),保护后端应用不被流量压垮。

2、负载均衡:按照负载策略(轮询、权重、iphash)访问后端服务接口。

3、权限控制:拦截未授权的访问。

4、访问日志记录。

二、sentinel

优点:

流量控制:直接限流、冷启动、排队等

熔断降级:通过并发线程数、响应时间进行限制降级

系统负载保护:提供系统维度的自适应保护能力

使用简单:与spring cloud 集成简单。

使用方式:

1、运行sentinel控制台应用,注册到nacos。

2、网关 引入sentinel依赖,注册到同样的nacos,配置sentinel地址。

3、在 sentinel控制台 中配置网关限流的配置,配置保存到nacos。网关读取nacos配置,使限流配置生效。

后端服务

服务使用nacos作为配置中心 和 注册中心。通过feign client 实现服务之间的调用。

三、JVM

1.Java堆(Heap)

一般是Java虚拟机所管理的内存中最大的一块,此内存区域唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆中还可以细分为:新生代老年代,新生代与老年代的默认比例为 1:2 ;新生代还分为Eden空间、From Survivor空间、To Survivor空间默认情况下代按照8:1:1Survivor只能使用一块(GC使用复制算法),所以新生代实际可用空间为 9/10(新生代)堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

2.方法区(Method Area)JVM规范的叫法

在HotSpot虚拟机也被称为永久代,也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量(jdk7移动到了堆)、即时编译器变异后的代码等数据。无法满足内存分配需求时,将抛出OutOfMemoryError异常。很少进行垃圾回收.

方法区和“PermGen space”(永久代)又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。

3.程序计数器(Program Counter Register)

记录正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)

是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

4.JVM栈

线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。抛出StackOverflowError和OutOfMemoryError异常。

5.本地方法栈(Native Method Stacks)

为虚拟机使用到的Native方法服务(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。抛出StackOverflowError和OutOfMemoryError异常。

四、并发多线程[了解]

1.相关容器与相关类

        1)同步类容器

                Vertor、HashTable

        2)并发类容器

                ConcurrentHasMap 代替 HashTable

2.阻塞队列

空的时候,take获取被阻塞;;满的时候,put插入被阻塞。

阻塞队列的优点:我们不需要关心什么时候阻塞线程,什么时候唤醒线程,BlockingQueue帮我们控制

使用场景:生产者消费者模式、线程池、中间件。

1)ConcurrentLinkedQueue

一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue。它是一个基于链表无界线程安全队列。该队列先进先出,头部是最先加入的,尾部是最近加入的,不允许null元素。

2)BlockingQueue接口

  • ArrayBlockingQueue 基于数组实现的阻塞队列,内部维护了一个定长的数组。没有实现读写分离。长度是需要定义的,可以指定先进先出或者先进后出,也叫有界队列。使用ReentrantLock、notEmpty(Condition)、notFull进行加锁。

  • LinkedBlockingQueue 基于链表实现的。内部实现读写分离(读写锁两个分离),可读写并行。是一个无界队列。使用ReentrantLock、notEmpty、notFull进行加锁。

  • SynchronousQueue 一种没有缓冲的队列,生产者生产的数据直接会被消费者获取并消费。手把手交换。
  • PriorityBlockingQueue 基于优先级的阻塞队列(优先级的判断通过构造方法传入的Compator对象决定,也就是传入的对象必须实现Comparable接口)。在实现PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁,它是一个无界队列。使用ReentrantLock、notEmpty进行加锁。

  • DelayQueue 带有延迟时间的Queue,里面的元素只有当其指定的延迟时间到了,才能够从队列里中获取到该元素。DelayQueue 元素必须实现Delayed接口,DelayQueue 是一个没有大小限制的队列,可用于缓存超时的数据进行移除、任务超市处理、空闲连接的关闭等。

3)LinkedTransferQueue

无边界的队列, 阻塞队列。担保必须被消费。没有消费者的时候,无法将元素放进去。

Transfer(obj)方法会一直阻塞,直到有消费者拿出去消费。

4)Deque 双端队列

允许队列的头部和尾部进行出队和入队操作

LinkedBlockingDeque 是一个线程安全双端队列。在内部维护了前端和后端节点,但是没有实现读写分离,一次同一时间只能有一个线程对其进行操作。在高并发中性能要远低于BlockingQueue。更要低于ConcurrentLinkedQueue。在jdk早期有一个非线程安全的ArrayDeque,java6里面新增了LinkedBlockingDeque 来弥补多线程场景下的安全的问题。

3.Copy-On-Write容器

添加元素时,先当前容器复制一份,然后往新的容器添加元素,添加完成后原容器的引用指向新容器

好处:可以对容器进行并发读。是一种读写分离的容器。

缺点:不适用多写少读的场景。浪费空间。

CopyOnWriteArrayList

底层使用Object[]实现。写数据的候,使用ReentrantLock加锁。

CopyOnWriteArraySet

底层使用CopyOnWriteArrayList实现。

4.其他类

1)CyclicBarrier(人到齐才开会,加法)

场景:每个线程代表一个跑步运动员,当运动员都准备好后,才一起出发,只有有一个人没有准备好,大家都等待。多个线程等待所有的线程准备好,然后一起执行。原理:使用ReentrantLock和Condition。通过count保存需要等待的线程数量。调用await()时count--,再判断count是否为0如果不为零则执行Condition.await(),如果为零执行condition.signalAll()唤醒所有等待的线程。

2)CountDownLatch (发令枪减法)

用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作。主线程等待其他线程执行完毕后,再继续执行下一步的动作实施者是主线程,具有不可重复性。

调用await()的线程会阻塞,等待指定数量的线程调用countDown(),调用完毕后,等待的线程继续往下执行。原理:内部类Syn继承AQS类,创建的时候,设置状态数state,调用countDown() state减1,await()当state为0时,跳出循环往下执行。

区别:

1、CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次

2、CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、isBroken(用来知道阻塞的线程是否被中断)等方法。

3、CountDownLatch会阻塞主线程,CyclicBarrier只会阻塞子线程,不会阻塞主线程

Semaphore信号灯

Semaphore用于多个共享资源互斥使用并发线程数控制,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1再访问共享资源

如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

非常适合高并发访问量。用于限流,允许同一时间只能运行指定的线程数。

原理:通过继承AQS实现,并支持公平非公平。

原子类

AtomicInteger

AtomicRefrerence (给对象加原子)

原子类,给对象加上原子特性。compareAndSet(expect, update)

AtomicStampedReference (带版本的原子类)

带有版本的原子类.解决ABA问题。底层使用内部类Pair存储版本号。Key为对象,value问版本号。比较的时候传入版本号。

private static class Pair<T> {

final T reference;

final int stamp;

AtomicIntegerFieldUpdater (对类中的成员加原子)

AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater和

类中的某个成员进行原子性操作。该类的作用如下:

1、让对属性具备原子性,需要满足如下条件。

1、Volatile

2、非private、protected(如果是非当前类)

3、类型必须一致。

2、不想使用锁(synchronized等)

3、大量需要原子类型修饰的对象,相对比较耗内存。

适用于集合中的很多对象,要对该对象中的属性进行原子操作。使用AtomicIntegerFieldUpdater来提高性能,对所有的对象使用AtomicStampedReference会降低性能。AtomicIntegerFieldUpdater属于按需使用。

5.Synchornized与Lock

Synchronized(适用于写比较多,粒度大)

1)synchronized的获取和释放锁由JVM实现,用户不需要显示的释放锁,非常方便。

2)在某个对象的所有synchronized方法中,在某一时刻,只能有一个唯一的一个线程去访问这些synchronized方法。

3)如果一个方法是synchronized方法,那么该synchronized关键字表示给当前对象上锁(this)

4)如果一个synchornized方法是静态的(static的),那么该synchronized关键字表示给当前对象所对应的Class对象上锁。(每个类,不管生成多少对象,其对应的Class对象只有一个)

保证变量修改的可见性和原子性,会造成线程阻塞。

Lock(粒度更细)

重入锁

ReentrantLock(默认非公平)在进行同步的代码部分加上锁,但不要忘记最后(finally里面)一定要释放锁,不然会造成所永远无法释放,其他线程永远进不来了的结果。Synchronize也是重入锁。一个线程可以锁多次,但是释放的时候就得释放加锁的次数。

CAS+CLH队列来实现。它支持公平锁和非公平锁,两者的实现类似。

  • CLH队列:带头结点的双向非循环链表

队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。如图

先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入CLH队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:

非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取吞吐量公平锁大。可能导致优先级反转饥饿现象。Synchronize、ReentrantLock默认也是非公平。

公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁

Lock方法底层调用的是Unsafe的park(),unlock调用的是unpark()。

2、读写锁

实现读写分离,在高并发下,读多写少的情况下,性能要远高于重入锁。读写锁本质是读锁、写锁。在读锁下,多个线程可以并进行访问,但是在写锁的时候,只能一个一个的顺序访问。ReentrantReadWriteLock.

读读共享,写写互斥,读写互斥。

3、自旋锁

自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起

4、偏向锁

如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则退出偏向模式

5.线程池

Executors创建线程池

newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该线程数始终不变。当有一个任务提交时,如果线程池中有空间,则立即执行;如果没有,则会被暂缓在一个任务队列中等待有空闲的线程去执行。

newSingleThreadPool()方法,创建一个线程的线程池。若有空闲,则执行;没有,则暂缓在任务队列中。

newCachedThreadPool()方法,返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量。若有空闲的线程则执行任务,若无任务,则不创建线程。并且每一个线程在60秒内没有使用,则被回收。

newScheduledThreadPool()方法,该方法返回一个ScheduledExecutorService对象,但该线程可以指定线程的数量。带有时间调度执行点,是否延迟执行,间隔执行时间

自定义线程池

ThreadPoolExecutor(int corePoolSize, 数量

int maximumPoolSize, 最大数

long keepAliveTime, 存活时间

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

corePoolSize 线程池中保持的线程数,即使设置了最大空闲时间,也会保持这个线程数

maximumPoolSize 线程池中允许的最大线程数

keepAliveTime 当线程池中线程数大于corePoolSize的时候,当线程的等待执行任务的时候超过keepAliveTime 的时候就会被回收。否则将会永远地等待新的任务执行。

unit keepAliveTime 的单位。

workQueue 该队列保存未被执行的任务。该队列仅仅会持有实现了Runnable接口,并且是被execute方法提交的任务。

threadFactory 创建executor创建线程的工厂。

handler 当队列满了而且是线程都在工作时,被阻塞时,所采取的拒绝策略。

对于需求,可以自定义拒绝策略,将任务写到文件/数据库中,等待后续线程池负载降低后,再处理。

五、缓存

1.redis

 数据类型

        1)string,

        2)hash(key-value),

        3)list(插入顺序排序,双向链表实现),

        4)set(无序、唯一,hash表实现)

        5)zset(sorted set:每个元素都会关联一个double类型的分数,根据分数从小到大排序)。

        跳表存放所有的数据,HashMap存放成员到Score的映射(排序)。

redis快的原因:

        1)单线程,减少线程的切换

        2)非阻塞io多路复印

        3)纯内存操作

        4)存储简单,value当成字符串

        5)对数据结构进行了设计:

string : 使用到了SDS(动态字符串),

list:使用到了 ziplist linkedlist

hash:使用到了 zipkist hashtable

set:使用到了 intset Hashtable

zset:使用到了 ziplist 、 skiplist

redis架构:

Redis哨兵机制(Sentinel):

通过哨兵完整主节点的故障切换。

缺点:单机内存限制。

解决:不同业务使用单独的哨兵集群。

Redis Cluster集群:

Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0 ~ 16383,计算公式:slot = CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。

下图展现一个五个节点构成的集群,每个节点平均大约负责5461个槽。

Redis 集群功能限制

  • key批量操作支持有限。如:MSET``MGET,目前只支持具有相同slot值的key执行批量操作。
  • key事务操作支持有限。支持多key在同一节点上的事务操作,不支持分布在多个节点的事务功能。
  • key作为数据分区的最小粒度,因此不能将一个大的键值对象映射不同节点。如:hash、list。
  • 不支持多数据库空间。单机下Redis支持16个数据库,集群模式下只能使用一个数据库空间,即db 0
  • 复制结构只支持一层,不支持嵌套树状复制结构。

2.缓存问题

2.1什么是缓存雪崩

指在某一个时间段,缓存集中过期失效。当这些缓存的过期时间一样,并且Redis巧合将这部分的数据都删光了。这就会导致这段时间内,全部数请求到数据库中。

解决方式:

1)过期时间使用随机值

2)多级缓存(nginx缓存、reids缓存,jvm缓存ehcache)

3)对redis cluster资源访问进行隔离(hytrix 避免所有资源都hang在redis上),对访问失败的时候,进行熔断、降级

2.2缓存穿透

请求的数据在缓存大量不命中(例如请求不存在的数据),导致请求走数据库

解决方式:

1)由于每次的参数是不合法的,可以提前过滤掉。

2)即使从数据库中查不到,也将这个空对象放入缓存,并设置较短的过期时间。

3)布隆过滤器(借助于bitmap实现,多个hash算法,算出索引下标记,将二进制向量对应的元素改成1。所有的都匹配时,表示可能存在,存在一个不匹配,则绝对不存在。)。

布隆过滤器

使用方式:

1、对布隆过滤器进行初始化,查询数据库数据并加入到布隆过滤器中。

2、请求发送过来布隆过滤器判断是否命中,命中就走缓存,之后接着看是否走数据库还是直接从缓存获取返回。

3、如果布隆过滤器miss,就直接返回,不走cache了。

作用:

布隆过滤器的优点是海量数据、快速判断,缺点是存在一定的误判率。

缺点:

1.会存在误判,因为当要查到的元素并没有在容器中,但是刚好hash之后得到的k个位置上值都是1,那就过滤器就会通过,所以使用它的场景需要允许一定非常小概率的误差。

2.删除困难,通过原理可知,在删除时,不能随便就修改byte数组的置1值,因为你无法确定那个位置的1是不是只有你这个值在使用,修改了就很可能会影响别的值。

随着增加的值越来越多,bit位被置为1的位数也越来越多。这样即使某个值,比如:“meituan”没有做过存储,但是它的哈希值对应的bit位正好被其他值置为1了,虽然出现这种情况的概率很低,但实际不能排除有这种可能性。所以说,一个值如果在布隆过滤器中存在,实际数据是不一定存在。

布隆过滤器会根据元素个数和误判率来自动跳转哈希函数个数和位数组的容量。

实现框架

实现布隆过滤器常用google guava框架。BloomFilter.

2.3缓存击穿

缓存击穿,是指一个热点key,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决:

1)设置热点数据不过期。

2)多级缓存,时间各不相同(本地缓存、分布式缓存)

3)加互斥锁。

4)从缓存读取,如果不存在(存在则直接读取缓存返回)。

获取锁,如果获取成功,从数据库中读取数据放到缓存中,释放锁。

获取锁失败,线程sleep(100),调用当前方法再次获取。

2.4redis为什么快

六、mq

1.架构模式

  • 单节点: 不可靠
  • 多Master模式:没有从节点。当单个节点宕机,在该节点恢复之前,该节点没有被消费的消息不可订阅。对实时性有影响。
  • 多Master多Slave异步复制)。推荐使用

每个主节点配置一个从节点。主从数据同步使用异步复制(Master写好了马上返回ACK,然后与从节点同步数据),主从消息延迟为毫秒级(可以忽略)。从节点只能读。

优点:主节点宕机,消费从节点,该过程不需要人工干预,性能与多master模式几乎一样。

缺点:主节点宕机,磁盘损坏时,会有少量消息丢失。

  • 多Master多Slave同步双写

主从写成功之后,返回成功标识。

优点:Master宕机,消息无延迟,服务可用性高、数据可用性高。

缺点:性能比异步复制模式低10%左右。

生产者端的负载均衡

生产者发送时,会自动轮询当前所有可发送的broker一条消息发送成功,下次换另外一个broker发送,使消息平均落到所有的broker上

集群消费

个topic可以由同一个组下所有消费者分担消费。具体例子:假如TopicA有6个队列,某个消费者ID起了2个消费者实例,那么每个消费者负责消费3个队列。如果再增加一个消费者ID相同消费者实例,即当前共有3个消费者同时消费6个队列,那每个消费者负责2个队列的消费。

消费者端的负载均衡,就是集群消费模式下,同一个group所有消费者实例平均消费Topic所有队列。 还可以在组里面进行负载均衡。

2.消息队列有什么优点和缺点?

  • 优点:
    • 解耦:生产者直接调用mq的api完成消息发送,不需要耦合业务代码。
    • 异步:生产者消息发送成功,就返回。不需要等消息消费。
    • 削峰:当生产消息过多时,通过mq缓存大量的消息,减轻下游服务的压力。
  • 缺点:
    • 系统可用性降低MQ崩溃了,系统无法正常运转。
    • 系统复杂性提高:加个MQ进来,保证消息重复消费?处理消息丢失的情况?保证消息的顺序性
    • 一致性问题:需要保证所有的消费者都正常处理(发短信)。

3.消息不丢失

以rockemq举例

1)、生产这发送消息到 mq服务时,可以根据接口返回判断发送成功或失败,如果失败进行重试,保证消息发送到mq服务。

2)、mq服务使用多主多从,异步复制。保证服务的高可用。

3)、消费者在消费消息时,消费成功则返回ack,如果消费失败,则重试。保证消息被消费。

4)、如果消息一直被消费失败,消息会被投递到死信队列。此时我们使用一个后端的服务,监听死信队列,对消息进行重试,如果一直不成功,因为死信队列默认3天后就会将消息删除。我们需要将消息导出保存,后续再处理。

4.如何保证消息不被重复消费

(1)消息中写入也标识,执行业务后,写入数据库。每次消费消息,根据业务标识查询,如果有则不执行。

(2) 将处理后的消息业务标识保存到redis,消费时到redis查询一下,如果有则不处理。

一般都是各个服务对自己核心的接口保证幂等性,每个服务根据自己的业务逻辑来实现,而且仅仅是对少数核心接口做幂等性保障。

七、数据库

mysql 索引

mysql索引类别

主键(聚集)索引,非聚集索引(普通索引,唯一索引,全文索引)

1、聚集索引(主键索引)

定义:索引中键值的逻辑顺序决定了表中相应行的物理顺序(类似字典目录abc对应的页号)

聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。

索引的叶子节点就是对应的数据节点(MySQL的MyISAM除外,此存储引擎的聚集索引和非聚集索引只多了个唯一约束,其他没什么区别),可以直接获取到对应的全部列的数据。因此查询数据,聚集索引的速度更快。

2、非聚集索引

该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。

非聚集索引叶子节点存储的是主键的信息,查询到叶子节点后,再根据主键信息进行二次查询

mysql不同引擎BTree的实现

mysql中每个表都有一个聚簇索引(clustered index ),除此之外的表上的每个非聚簇索引都是二级索引,又叫辅助索引(secondary indexes)。

1、MyISAM实现(B+Tree)

MyISAM索引文件和数据文件是分离的,索引文件仅保存数据行记录的地址(行指针)。MyISAM引擎使用B+Tree作为索引结构叶节点的data域仅仅存放的是指向数据记录的地址(也叫行指针),在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。

MyISAM(B+Tree)叶子节点存储的内容:

主键索引:行指针

二级索引:行指针

2、InnoDB实现(B+Tree)

实现方式却与MyISAM不同。

InnoDB表数据文件本身B+Tree组织的一个索引文件,这个索引的key是数据表的主键,这棵树的叶节点data保存了完整的数据记录(整行数据)。

innodb的二级索引,保存的是索引列值以及指向主键的指针,所以我们使用覆盖索引的做优化处理就是针对mysql的innodb的索引而言的。

叶子节点保存的内容。

主键索引 :完整的数据(整行数据)[建议使用自增主键、不用uuid 太长,导致性能差]

二级索引:索引列值+主键信息(指针)

遍历数据如下

使用B+Tree的好处

1)全值匹配

全值匹配指的是和索引中的所有列进行匹配,即可用于查找姓名和出生日期

2)匹配最左前缀

如:只查找姓,即只使用索引的第一列

3)匹配列前缀

也可以只匹配某一列值的开头部分,如:匹配以J开头的姓的人(like 'J%'),这里也只是使用了索引的第一列,且是第一列的一部分

4)匹配范围值

如查找姓在allen和barrymore之间的人,这里也只使用了索引的第一列

5)精确匹配某一列并范围匹配另外一列

2.事务并发处理可能引起的问题

脏读:读了另外一个事务没有提交的数据。

不可重复读:在同一个事务中,读两回的值不一致。(一般指的是同一行的数据,某些字段改变了,并提交了)

幻读:读的时候另外一个事务往里面插入一条新数据,影响了读的结果。(A查询数据,B删除或增加数据,A再次查询,发现原来的记录消失了或出现原来不存在的记录,指的是多条数据)

3.隔离级别

read-uncommitted 最低级别,任何情况都无法保证。

read-committed避免脏读

Repeatable避免脏读、不可重复读

serializable避免脏读、不可重复读、幻读

考虑效率问题,一般把级别设置为read-committed,这样还无法解决不可重复读问题,这个时候,可以悲观锁和乐观锁解决这个问题。悲观锁使用数据库的锁乐观锁使用版本解决这个问题。

4.sql优化

4.1 避免全表扫描

考虑在where 及order by 涉及的列上建立索引

4.2 避免判断null 值

避免在where 子句中对字段进行null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

可以在num 上设置默认值0,确保表中num 列没有null 值,然后这样查询:

select id from t where num=0

4.3 避免不等值判断

避免在where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描

4.4 避免使用or 逻辑,使用union代替

避免在where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10

union all

select id from t where num=20

4.5 注意模糊查询(使用右like)

4.6 避免查询条件中字段计算

4.7 避免查询条件中对字段进行函数操作

4.8 WHERE 子句“=”左边注意点

不要在where 子句中的“=”左边进行函数、算术运算或其他表达式运算

4.9 慎用in 和not in 逻辑

select id from t1 where num in(select id from t2 where id > 10)

此时外层查询会全表扫描,不使用索引。可以修改为:

select id from t1, (select id from t1 where id > 10)t2 where t1.id = t2.id

此时索引被使用,可以明显提升查询效率。

mysql索引为什么不用二叉树

因为使用二叉树会使树的高度过高,导致查询效率变低。

八、k8s

k8s 由master节点(进行调度)和node节点(运行容器)组成。

1.Master节点

  • kube-apiserver:资源操作的入口,并提供认证、授权、访问控制、API 注册和发现等机制。
  • kube-scheduler:负责资源的调度(将待调度的pod,调度到node上去。具体执行由kubelet执行。),按照预定的调度策略将 Pod 调度到相应的机器上。
  • kube-controller-manager:一个控制循环,维护集群的状态(决定运行几个pod),比如故障检测自动扩展滚动更新等。
  • etcd:基于 Raft 开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)

2.Node节点

  • kube-proxy:(网络代理,使用iptable实现为 Service 提供 cluster 内部的服务发现负载均衡
  • kubelet:维持容器的生命周期(对node和pod的管理. 它与主节点 API 服务器通信并运行为其节点安排的容器。,同时也负责 Volume(CVI,Container Volume Interface)和网络(CNI,Container Network Interface)的管理。
  • runtime:负责镜像管理以及 Pod容器真正运行(CRI,Container Runtime Interface),默认的容器运行时为 Docker,还支持 RKT 容器。

3.网络模式

无网络模式:单机

Host模式:共享宿主机网络

桥接模式(DockerNat模式):二层网络,无法网络虚拟化。Docker的网络模式。

SDN:三层虚拟网络calico、flannel

三种外部访问方式

  • ClusterIp:只有集群内部才能访问,外部无法访问。使用Ingress来提供对外访问接口。

  • NodePort: 会在所有node节点暴露端口(所有node上的服务,外部都能访问)

- 每个端口只能是一种服务

- 端口范围只能是 30000-32767(可调)

- 不在 YAML 配置文件中指定则会分配一个默认端口

不要在生产环境中使用这种方式暴露服务,大多数时候我们应该让 Kubernetes 来选择端口

  • Loadbalance:在NodePod基础之上,添加了负载均衡。(并不是所有的k8s集群都会支持,大多是在公有云托管集群中会支持该类型。)

Ingerss

7层反向代理,可以把客户端的请求转发到相对应的service上。

组成:ingress资源对象(设置转发规则的),ingress-controller(实际用于转发请求的组件),【负载均衡】nginx traefik

实例,创建nginx Deployment、Service(Cluster IP)

问题

1、如何构建镜像,镜像如何管理。

通过dockerfile文件构建镜像。构建成功后,将镜像推送到hurb(远程的镜像管理仓库)中,进行管理。

2、dockerfile如何编写

dockerfile文件命令一般有

FROM: 第1行,表示使用哪个系统。

MAINTAINER:标识作者

RUN:表示执行某些语句(例如安装软件)

ENV:设置镜像里面的环境(比如jdk环境)

COPY:拷贝文件

CMD:执行命令

FROM centos:7
MAINTAINER tyn <tanye.tyn@alibaba-inc.com>
# 安装打包必备软件
RUN yum install -y wget unzip telnet lsof net-tools bind-utils

# 准备 JDK/Tomcat 系统变量
ENV JAVA_HOME /usr/lib/jvm/java
ENV PATH $PATH:$JAVA_HOME/bin
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV ADMIN_HOME /home/admin

# 下载安装 OpenJDK
RUN yum -y install java-1.8.0-openjdk-devel
RUN mkdir -p /home/admin/app/
RUN mkdir -p /home/admin/arms/

# 拷贝jar,修改成自己项目的jar包,路径相对根目录
ARG JAR_LOCAL_PATH=cb-data-portal-boot/target/cb-data-portal-boot.jar
COPY ${JAR_LOCAL_PATH}  /home/admin/app/cb-data-portal-boot.jar
ARG ARMS_JAR_LOCAL_PATH=docker/ArmsAgent.zip
COPY ${ARMS_JAR_LOCAL_PATH}  /home/admin/arms/ArmsAgent.zip
RUN unzip /home/admin/arms/ArmsAgent.zip -d /home/admin/arms/

# 增加容器内中⽂支持
ENV LANG="en_US.UTF-8"
# 增强 Webshell 使⽤体验
ENV TERM=xterm
 
# 将启动命令写入启动脚本 start.sh
RUN mkdir -p ${ADMIN_HOME}

RUN echo 'java  -javaagent:/home/admin/arms/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar -Darms.licenseKey=1801840892792677@6a06048e165aacd -Darms.appName=hj-zsxt-k8s-cb-data-portal -jar  ${CATALINA_OPTS} /home/admin/app/cb-data-portal-boot.jar'> /home/admin/start.sh && chmod +x /home/admin/start.sh
WORKDIR ${ADMIN_HOME}
CMD ["/bin/bash", "/home/admin/start.sh"]

3、k8s如何运行镜像,用户如何访问k8s运行的应用。

k8s部署我们的应用,一般是无状态状态应用。

1)我们使用Deployment 来部署应用。本质上是一个yml文件,里面指定使用了哪个镜像,需要部署几个pod节点等。

示例

2)、通过Service的ClusterIP提供集群内的访问入口,再通过Ingress提供集群外的访问。

Ingress -> Service(ClusterIP) ->POD(应用)

其他问题

1、i++ 是否为原子操作。

不是,两步操作。第1步获取i的值,并且加1。第2步把i的值写回。

2、一张表有8千万的数据,如何加快查询速度

1)、搞两张表,分冷数据和热数据,冷数据存放6个月以前的,热数据存放6个月以后的。

2)、分库分表,将数据存放到多个表里面。

如何分:

需要考虑到数据的均衡,比如分成16张表,每个表的结构是一模一样。保存时,根据数据的id,获取hashcode,取16的余数(0-15),保存到对于编号的表里。

id生成策略:

雪花算法

雪花算法(SnowFlake)_文丑颜不良啊的博客-CSDN博客_雪花算法

3、对于高并发,系统服务应该如何处理

1)、系统设计层面,需要对系统进行的拆分。拆分热点服务(秒杀)。避免热点服务的对普通服务的影响。服务使用自己单独的数据库,提高数据并发写的能力。每个服务可以按需部署。

2)、前端:

动静分离,静态资源放到cdn。

页面按钮控制(一秒只允许点击一次,图片验证码,问题验证码)

使用Nginx 对相同账号 (head中 UId )的请求频率进行限制。

使用NGINX 对同一个商品的请求频率进行限制

3)、网关:

限流,(Sentinel),卖1000件商品,请求有10W,我们不需要把十万都放进来,你可以放1W请求进来。(服务级别、接口级别、IP级别)

风控: 过滤机器人账号(买商品很少),根据真实用户的概率,将概率小的拦截。

4)、后端服务:

Tomcat只有几百的并发,就多部署一点。临时扩容,多租点流量机。

降级: 限制或关闭非核心功能。

限流:(服务级别、接口级别、IP级别)

5)、缓存

将一些业务数据放到缓存,比如商品库存,抢购开始前预热到缓存( 库存信息可以分为多份,放入不同key的缓存中,如库存为10万可以分为10份分别放入不同key的缓存中,这样将数据分散操作可以达到更高的读写性能。)。

(有些场景可以提供多级缓存、nginx、服务本地、分布式缓存)

6)、MQ,对业务使用异步的方式进行处理。

【对于高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改 ,大量的写请求灌入MQ里,

后边系统消费后慢慢写,控制在mysql承载范围之内。

所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用MQ来异步写,提升并发性。

MQ单机抗几万并发也是ok的。

a、使用Redis扣库存,扣减成功则发mq ,写标记。

b、再次请求时,查询Redis 判断是否下单成功、是否发送消息到mq。

判断是否下单成功,成功就返回下单成功。

判断是否发送了mq,发送了,返回排队中。

没有发送,判断是否还有库存,如果没有,返回无库存。

c、消费者,消费消息,生成订单,修改库存(需要加锁)。下单成功,写到Redis。

通过后端服务同步库存到Redis(下单30分钟不付款,回收库存。)

7)、DB层优化

读写分离:主从架构,主库写入,从库读取,搞一个读写分离。读流量太多的时候,还可以加更多的从库。

分表分库: 每个表的数据量保持少一点,提高sql跑的性能。

数据库集群

8)、nosql、es

对于开发层面,

异步(日志的记录)

池化技术(线程池、连接池)

并发,尽量使用无锁(cas、 有些场景使用空间换时间 copyonwriter 思想来解决)、

零拷贝。

io多路复用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值