Java八股文—helloxf

目录

算法

排序算法

一交换排序-1 冒泡排序

一交换排序-2. 快速排序

二插入排序-1. 直接插入排序

二插入排序-2. 希尔排序

三选择排序-1. 简单选择排序

三选择排序-2. 堆排序

四归并排序

五、基数排序

六、八种排序算法的总结

JAVA

基础

软件设计原则

JAVA8特性

web

过滤器

网络

TCP/IP

粘包和拆包

加密

JVM

垃圾回收机制

垃圾收集器

何时OOM

组成

参数调优

常用命令

双亲委派机制

问题排查

IO

NIO

三大组件

select、poll、epoll

Netty

内存

内存模型

线程

线程模型

线程状态

线程池

volatile

AQS

CAP理论

设计模式

单例

抽象工厂

责任链

命令模式

模板

观察者

策略

装饰器

代理

Spring中的应用

数据结构

hashmap

B+树

数据库

redis

数据结构

特点

高可用方案

缓存击穿方案

缓存雪崩方案

缓存穿透方案

Mysql

索引

类型

行锁

执行流程

事务

JOIN原理

sql执行顺序

日志

undo log

redolog

MVCC机制

主从同步原理

线上问题排查

数据库开发规范

基础规范

命名规范

字段设计规范

MQ

种类

优势

应用场景

协议

保证消息不丢失

组成

Tomcat

组成

总结

调优

Tomcat启动时的类加载器

框架

Spring全家桶

框架优点

缺点:

IOC

概念

作用域

容器的初始化过程

Bean加载过程

Bean的生命周期

如何解决循环依赖

AOP

使用场景

动态代理

事务

传播行为

隔离级别

Spring Security安全框架

spring ORM

Spring DAO

Spring Context

SpringMVC

原理

Servlet容器和Spring容器关系

常用注解

SpringBoot

优点:

启动原理

启动过程

常用注解

Spring Cloud Alibaba微服务

主要功能

Ribbon

Sentinel

nacos

RocketMq

Dubbo

Seata

三大模块

执行流程

Alibaba Cloud ACM一款在分布式架构环境中对应配置进行集中管理和推送的应用配置中心产品

Alibaba Cloud OSS 阿里云对象存储服务

Alibaba Cloud SchedulerX 分布式任务调度产品

Alibaba Cloud SMS:覆盖全球的短信服务

gateway

链路追踪

Mybaties

优点

缺点

和hibernate区别

一二级缓存

分布式方案

分布式任务调度

elastic job

三种作业方式

分片机制

高可用

分布式锁

Redis实现:SETNX + EXPIRE

zk实现

分布式会话

分布式事务

分布式数据库

消息中间件

什么情况下需要用到分布式

微服务如何拆分

接口限流方案

常见限流算法

ELK

解决方案

大数据高并发

如何防止库存超卖

雪花算法:snowflake

如何保证服务幂等性

单点登录

分库分表

TOP key海量搜索

持续集成、持续发布、jenkins

分布式全局唯一发号器

带过期策略的LRU缓存

设计统一配置中心

现场问题处理

Linux常用命令

性能优化

常见优化手段

存储性能优化

代码优化


算法

排序算法

一交换排序-1 冒泡排序

冒泡排序是一种简单的交换排序算法,以升序排序为例,其核心思想是:

  1. 从第一个元素开始,比较相邻的两个元素。如果第一个比第二个大,则进行交换。

  2. 轮到下一组相邻元素,执行同样的比较操作,再找下一组,直到没有相邻元素可比较为止,此时最后的元素应是最大的数。

  3. 除了每次排序得到的最后一个元素,对剩余元素重复以上步骤,直到没有任何一对元素需要比较为止。

public void bubbleSortOpt(int[] arr) {
 
    if(arr == null) {
        throw new NullPoniterException();
    }
    if(arr.length < 2) {
        return;
    }
    int temp = 0;
    for(int i = 0; i < arr.length - 1; i++) {
        for(int j = 0; j < arr.length - i - 1; j++) {
            if(arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

一交换排序-2. 快速排序

快速排序的思想很简单,就是先把待排序的数组拆成左右两个区间,左边都比中间的基准数小,右边都比基准数大。接着左右两边各自再做同样的操作,完成后再拆分再继续,一直到各区间只有一个数为止。

public void quicksort(int[] arr, int start, int end) {
 
    if(start < end) {
        // 把数组中的首位数字作为基准数
        int stard = arr[start];
        // 记录需要排序的下标
        int low = start;
        int high = end;
        // 循环找到比基准数大的数和比基准数小的数
        while(low < high) {
            // 右边的数字比基准数大
            while(low < high && arr[high] >= stard) {
                high--;
            }
            // 使用右边的数替换左边的数
            arr[low] = arr[high];
            // 左边的数字比基准数小
            while(low < high && arr[low] <= stard) {
                low++;
            }
            // 使用左边的数替换右边的数
            arr[high] = arr[low];
        }
        // 把标准值赋给下标重合的位置
        arr[low] = stard;
        // 处理所有小的数字
        quickSort(arr, start, low);
        // 处理所有大的数字
        quickSort(arr, low + 1, end);
    }
}

二插入排序-1. 直接插入排序

直接插入排序就是插入排序的粗暴实现。对于一个序列,选定一个下标,认为在这个下标之前的元素都是有序的。将下标所在的元素插入到其之前的序列中。接着再选取这个下标的后一个元素,继续重复操作。直到最后一个元素完成插入为止。我们一般从序列的第二个元素开始操作。

public void insertSort(int[] arr) {
    // 遍历所有数字
    for(int i = 1; i < arr.length - 1; i++) {
        // 当前数字比前一个数字小
        if(arr[i] < arr[i - 1]) {
            int j;
            // 把当前遍历的数字保存起来
            int temp = arr[i];
            for(j = i - 1; j >= 0 && arr[j] > temp; j--) {
                // 前一个数字赋给后一个数字
                arr[j + 1] = arr[j];
            }
            // 把临时变量赋给不满足条件的后一个元素
            arr[j + 1] = temp;
        }
    }
}

二插入排序-2. 希尔排序

某些情况下直接插入排序的效率极低。比如一个已经有序的升序数组,这时再插入一个比最小值还要小的数,也就意味着被插入的数要和数组所有元素比较一次。我们需要对直接插入排序进行改进。

希尔排序能实现这个目的。希尔排序把序列按下标的一定增量(步长)分组,对每组分别使用插入排序。随着增量(步长)减少,一直到一,算法结束,整个序列变为有序。因此希尔排序又称缩小增量排序。

一般来说,初次取序列的一半为增量,以后每次减半,直到增量为一。

public void shellSort(int[] arr) {
    // gap 为步长,每次减为原来的一半。
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        // 对每一组都执行直接插入排序
        for (int i = 0 ;i < gap; i++) {
            // 对本组数据执行直接插入排序
            for (int j = i + gap; j < arr.length; j += gap) {
                // 如果 a[j] < a[j-gap],则寻找 a[j] 位置,并将后面数据的位置都后移
                if (arr[j] < arr[j - gap]) {
                    int k;
                    int temp = arr[j];
                    for (k = j - gap; k >= 0 && arr[k] > temp; k -= gap) {
                        arr[k + gap] = arr[k];
                    }
                    arr[k + gap] = temp;
                }
            }
        }
    }
}

三选择排序-1. 简单选择排序

选择排序思想的暴力实现,每一趟从未排序的区间找到一个最小元素,并放到第一位,直到全部区间有序为止。

public void selectSort(int[] arr) {
    // 遍历所有的数
    for (int i = 0; i < arr.length; i++) {
        int minIndex = i;
        // 把当前遍历的数和后面所有的数进行比较,并记录下最小的数的下标
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                // 记录最小的数的下标
                minIndex = j;
            }
        }
        // 如果最小的数和当前遍历的下标不一致,则交换
        if (i != minIndex) {
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

三选择排序-2. 堆排序

我们知道,对于任何一个数组都可以看成一颗完全二叉树。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

像上图的大顶堆,映射为数组,就是 [50, 45, 40, 20, 25, 35, 30, 10, 15]。可以发现第一个下标的元素就是最大值,将其与末尾元素交换,则末尾元素就是最大值。所以堆排序的思想可以归纳为以下两步:

  1. 根据初始数组构造堆

  2. 每次交换第一个和最后一个元素,然后将除最后一个元素以外的其他元素重新调整为大顶堆

/**
 * 转化为大顶堆
 * @param arr 待转化的数组
 * @param size 待调整的区间长度
 * @param index 结点下标
 */
public void maxHeap(int[] arr, int size, int index) {
    // 左子结点
    int leftNode = 2 * index + 1;
    // 右子结点
    int rightNode = 2 * index + 2;
    int max = index;
    // 和两个子结点分别对比,找出最大的结点
    if (leftNode < size && arr[leftNode] > arr[max]) {
        max = leftNode;
    }
    if (rightNode < size && arr[rightNode] > arr[max]) {
        max = rightNode;
    }
    // 交换位置
    if (max != index) {
        int temp = arr[index];
        arr[index] = arr[max];
        arr[max] = temp;
        // 因为交换位置后有可能使子树不满足大顶堆条件,所以要对子树进行调整
        maxHeap(arr, size, max);
    }
}
 
/**
 * 堆排序
 * @param arr 待排序的整型数组
 */
public static void heapSort(int[] arr) {
    // 开始位置是最后一个非叶子结点,即最后一个结点的父结点
    int start = (arr.length - 1) / 2;
    // 调整为大顶堆
    for (int i = start; i >= 0; i--) {
        SortTools.maxHeap(arr, arr.length, i);
    }
    // 先把数组中第 0 个位置的数和堆中最后一个数交换位置,再把前面的处理为大顶堆
    for (int i = arr.length - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        maxHeap(arr, i, 0);
    }
}

四归并排序

归并排序是建立在归并操作上的一种有效,稳定的排序算法。该算法采用分治法的思想,是一个非常典型的应用。归并排序的思路如下:

  1. 将 n 个元素分成两个各含 n/2 个元素的子序列

  2. 借助递归,两个子序列分别继续进行第一步操作,直到不可再分为止

  3. 此时每一层递归都有两个子序列,再将其合并,作为一个有序的子序列返回上一层,再继续合并,全部完成之后得到的就是一个有序的序列

关键在于两个子序列应该如何合并。假设两个子序列各自都是有序的,那么合并步骤就是:

  1. 创建一个用于存放结果的临时数组,其长度是两个子序列合并后的长度

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

  3. 比较两个指针所指向的元素,选择相对小的元素放入临时数组,并移动指针到下一位置

  4. 重复步骤 3 直到某一指针达到序列尾

  5. 将另一序列剩下的所有元素直接复制到合并序列尾

/**
 * 合并数组
 */
public static void merge(int[] arr, int low, int middle, int high) {
    // 用于存储归并后的临时数组
    int[] temp = new int[high - low + 1];
    // 记录第一个数组中需要遍历的下标
    int i = low;
    // 记录第二个数组中需要遍历的下标
    int j = middle + 1;
    // 记录在临时数组中存放的下标
    int index = 0;
    // 遍历两个数组,取出小的数字,放入临时数组中
    while (i <= middle && j <= high) {
        // 第一个数组的数据更小
        if (arr[i] <= arr[j]) {
            // 把更小的数据放入临时数组中
            temp[index] = arr[i];
            // 下标向后移动一位
            i++;
        } else {
            temp[index] = arr[j];
            j++;
        }
        index++;
    }
    // 处理剩余未比较的数据
    while (i <= middle) {
        temp[index] = arr[i];
        i++;
        index++;
    }
    while (j <= high) {
        temp[index] = arr[j];
        j++;
        index++;
    }
    // 把临时数组中的数据重新放入原数组
    for (int k = 0; k < temp.length; k++) {
        arr[k + low] = temp[k];
    }
}
 
/**
 * 归并排序
 */
public static void mergeSort(int[] arr, int low, int high) {
    int middle = (high + low) / 2;
    if (low < high) {
        // 处理左边数组
        mergeSort(arr, low, middle);
        // 处理右边数组
        mergeSort(arr, middle + 1, high);
        // 归并
        merge(arr, low, middle, high);
    }
}

五、基数排序

基数排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。为此需要将所有待比较的数值统一为同样的数位长度,数位不足的数在高位补零。

/**
 * 基数排序
 */
public static void radixSort(int[] arr) {
    // 存放数组中的最大数字
    int max = Integer.MIN_VALUE;
    for (int value : arr) {
        if (value > max) {
            max = value;
        }
    }
    // 计算最大数字是几位数
    int maxLength = (max + "").length();
    // 用于临时存储数据
    int[][] temp = new int[10][arr.length];
    // 用于记录在 temp 中相应的下标存放数字的数量
    int[] counts = new int[10];
    // 根据最大长度的数决定比较次数
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        // 每一个数字分别计算余数
        for (int j = 0; j < arr.length; j++) {
            // 计算余数
            int remainder = arr[j] / n % 10;
            // 把当前遍历的数据放到指定的数组中
            temp[remainder][counts[remainder]] = arr[j];
            // 记录数量
            counts[remainder]++;
        }
        // 记录取的元素需要放的位置
        int index = 0;
        // 把数字取出来
        for (int k = 0; k < counts.length; k++) {
            // 记录数量的数组中当前余数记录的数量不为 0
            if (counts[k] != 0) {
                // 循环取出元素
                for (int l = 0; l < counts[k]; l++) {
                    arr[index] = temp[k][l];
                    // 记录下一个位置
                    index++;
                }
                // 把数量置空
                counts[k] = 0;
            }
        }
    }
}

六、八种排序算法的总结

排序法

最好情形

平均时间

最差情形

稳定度

空间复杂度

冒泡排序

O(n)

O(n^2)

O(n^2)

稳定

O(1)

快速排序

O(nlogn)

O(nlogn)

O(n^2)

不稳定

O(nlogn)

直接插入排序

O(n)

O(n^2)

O(n^2)

稳定

O(1)

希尔排序

不稳定

O(1)

直接选择排序

O(n^2)

O(n^2)

O(n^2)

不稳定

O(1)

堆排序

O(nlogn)

O(nlogn)

O(nlogn)

不稳定

O(nlogn)

归并排序

O(nlogn)

O(nlogn)

O(nlogn)

稳定

O(n)

JAVA

基础

软件设计原则

1. 单一职责原则

一个类只干一类事情,实际应用会增加类的数量

2. 里氏替换原则

实现一个接口就需要实现所有方法

3. 依赖倒置原则

解耦,模块间的依赖通过抽象发生

4. 接口隔离原则

接口职能单一,高内聚

5. 迪米特法则

最少知识原则,只与接口协定交互,其他不关心

6. 开闭原则

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭

接口定义需要考虑后续需求,避免抽象的修改,以及约束相关实现,增强扩展性

JAVA8特性

  • Lambda表达式

  • FunctionInterface函数式接口

  • default关键字

  • Date APi

  • Stream的分治并行处理 依赖JAVA7 Fork/Jion框架

web

过滤器

过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射

网络

TCP/IP

三次握手

客户端->服务器发送: 同部位SYN=1,初始序列号seq=x

服务器->客户端: SYN=1,ACK=1,seq=y,确认号ack=x+1

客户端->服务器: ACK=1,seg=x+1,ack=y+1

四次挥手

客户端->服务器: FIN=1,seq=u

服务器->客户端: ACK=1,seq=v,ack=u+1

服务器->客户端: FIN=1,ACK=1,seg=w,ack=u+1

客户端->服务器: ACK=1,seq=u+1,ack=w+1

为什么要四次: 客户端请求FIN关闭连接时,服务端可能还在数据传输,所以先返回已接收到FIN请求,等传输完再给客户端发送FIN正式关闭

不足

使用明文,内容可能被窃听

不验证通信方身份,可能被伪装

无法证明报文完整性,可能被篡改

粘包和拆包

原因:tcp是数据流的协议,会根据TCP缓冲区实际情况进行包拆分,一个完整的包可能被拆分几个包发,也可能几个小包合为一个包发

粘包解决方法

  • 消息定长,不足补空格

  • 包尾加换行符分割,如FTP协议

  • 消息分为消息头个消息体,消息头包含消息长度等信息(HTTP)

  • 更复杂的应用层协议 (Netty做了解码器处理粘包问题

加密

MD5、DES、AES、RSA

1.BASE64加密/解密

2.MD5(Message Digest Algorithm)加密

3.DES(Data Encryption Standard)对称加密

4.AES(Advanced Encryption Standard)加密/解密

5.HMAC(Hash Message Authentication Code)散列消息鉴别码

 6.恺撒加密

7.SHA(Secure Hash Algorithm)安全散列算法

8.RSA加密/解密

9.PBE加密/解密

RSA 加密/解密RSA算法是一种非对称加密算法,所谓非对称就是该算法需要一对密钥,若使用其中一个加密,则需要用另一个才能解密。目前它是最有影响力和最常用的公钥加密算法,能够抵抗已知的绝大多数密码攻击。从提出到现今的三十多年里,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一。

非对称加密应用

  1. 先生成摘要在对摘要加密,因为使用非对称加密是非常耗时的。

  2. 数字签名

  3. 身份验证

  4. Https SSL/TLS 通信

JVM

垃圾回收机制

回收策略

引用计数,可达性分析(应用较多)

引用类型区分

强引用,对象没有被引用则会被GC;软引用,用于缓存,内存不够时会被GC;弱引用,会被GC;虚引用,用于得知是否被GC

垃圾回收算法

标记-清除算法,复制算法,标记-整理算法,分代回收算法

垃圾收集器

串行垃圾回收器(Serial收集器):适合小型、短时间运行程序

吞吐量优先垃圾回收器Parallel(JDK8默认、并行的)

响应时间优先的垃圾回收器(JDK1.5开始)(CMS)

G1(Garbage First)收集器(JDK9默认的回收器)

分类

新生代: Serial(复制算法)、ParNew(复制)、Parallel Scavenge

老年代: serial Old(标记整理算法)、parallel Old(标记整理算法)、CMS(标记清除)

整个java堆: G1

GC流程

对象诞生即新生代(eden),在进行minor gc过程中,如果依旧存活,移动到from,变成Survivor,进行标记代数,检查一定次数(默认15)后,晋升为老年代

何时OOM

  • 可能出现OOM的区域: 虚拟机栈、堆、MetaSpace

  • 当一直创建class类信息,MetaSpace内存不足进行full GC后还是不够,就会导致OOM溢出

  • 递归调用导致虚拟机栈OOM溢出

  • 内存泄漏、并发大不断创建对象触发GC后还是存不下新对象就会OOM

组成

堆、虚拟机栈、程序计数器、方法区(java8后:堆外内存-元空间、直接内存)、本地方法栈

参数调优

-Xms7168m 堆内存7G

-Xmx7168m 最大堆内存7G

-XX: SurvivorRatio Eden和S比例

-XX: NewRatio新生代和老年代比例

调优步骤

分析GC及dump文件,判断是否需要优化,确定瓶颈问题点

确定JVM调优量化目标

确定调优参数

对比调优后变化

常用命令

jps、jmap、jstack、jstate、gump

双亲委派机制

子类加载器收到加载请求,不会先处理,而是先把请求委派给父类加载器处理,父类处理不了再返回子类

设计原因:

安全。类加载有了层次,保证基础核心的类被根加载器加载,而不是去加载用户自定义的和基础类同名的类,保证有序,安全

如何打破(不同数据库驱动接口) :

1、自定义类加载器重写loadclass万法

2.使用线程上下文Thread的 setContextClassLoad

问题排查

IO

NIO

三大组件

selector多路复用选择器

针对有用的请求进行选择:accept、read、write

channel通道

用于进行数据传输,面向缓冲区操作,双向;文件传输FileChannel、UDP DataGramChannel、TCP SocketChannel ServerSocketChannel

Buffer缓冲区

用于存储数据、只能存储基本类型

优势:用select/poll,同时处理上万连接,与一条线程维护一个连接比,系统不必创建维护线程,大大减少系统开销

缺点:本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都需要读写事件就绪后,自己负责读写,这个过程是阻塞的

select、poll、epoll

区别:

  1. select/poll指只支持LT。epoll监控多个文件描述符的IO事件,支持边缘触发(ET edage trigger)和水平触发(LT level trigger),通过epoll_wait等待IO事件,如果当前没有可用事件则阻塞调用线程

  2. epoll是一种IO事件通知机制,是linux内核实现IO多路复用的一个实现

  3. 消息传递方式:select和poll:内核需要将消息传递到用户空间,需要内核拷贝动作。epoll是通过内核和用户空间共享一块内存来实现,性能较高

  4. 文件句柄剧增后带来的IO效率问题:select、poll因为每次调用都会对接进行线性遍历,所以随着FD剧增后遍历速度会线性下降。epoll基于每个FD的callable函数实现,只有活跃的socket才会主动调用callback,所以在活跃socket少时不会有性能线性下降问题

  5. 支持一个进程打开最大连接数:select 由FD_size自定义,32位机器最大3264.poll没有限制。epoll有上线但是很大,1G内存可开10W

  6. 虽然epoll性能好,但是当连接数少且都十分活跃下,可能select、poll都比epoll性能好,毕竟epoll通知机制需要函数调用

Netty

高性能特点

  • 异步非阻塞

  • 零拷贝

  • 内存池byteBuf

  • 多种内存管理策略

  • 高效的Reactor线程模型

  • 采用串行无锁化设计

线程模型

Netty使用EventLoop来处理连接上的读写事件,一个连接上的所有请求都保证在一个EventLoop中被处理。一个EventLoop中只有一个Thread,所以也就实现了所有连接上的事件只会在一个线程中被执行,一个EventLoopGroup包含多个EventLoop,可以把一个EventLoop当做是Reactor线程模型中的一个线程,而EventLoopGroup类似于ExecutorService

单线程(千级)

多线程(万级)

主从多线程:

特点:服务器用于接收客户端连接不再是一个单独的NIO线程,而是一个独立的NIO线程池,Acceptor接收到请求,将创建的SocketChannel注册到sub Reactor线程池中的IO线程。

有两个线程池(自定义):bossGroup(监听处理请求)和workGroup(处理IO业务事件)【都是EventLoopGroup实例】

Reactor:基于NIO技术,可读可写时通知用户。也叫dispatcher模式

内存

内存模型

线程

线程模型

线程状态

NEW、RUNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED

线程池

参数:

  • 最大线程数maximumPoolSize: 当等待队列不是无界队列而是ArrayBlockingQueue时,如果满了,且大于最大线程数,则会走拒绝策略RejectExcutionHandler处理满了的任务,默认是AbortPolicy

  • corePoolSize: 核心线程数

  • keepaliveTime 空闲线程存活时间

  • unit空闲线程存活时间单位

  • workqueue工作队列

  • 线程工厂threadFactory

  • handler拒绝策略

组成

  • 线程管理器ThreadPool

  • 工作线程 PoolWorker

  • 任务接口Task

  • 任务队列taskqueue

原理

一个线程集合workset和一个阻塞队列workqueue,当用户向线程池提交一个任务,线程池会先将任务放入workqueue,workset中线程会不断向workqueue获取线程执行,当workqueue没有任务,work就会阻塞,直到队列有任务再继续

线程池优点

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建销毁造成的消耗

  2. 提高响应速度,任务到达不需要创建线程就可以执行

  3. 线程是稀缺资源。线程池可以进行统提高线程的管理性。管理、调优和监控

线程数考虑3因素

资源预算、服务器配置、任务自身特性。具体就是服务器CPU核心数量、多少内存,IO支持的最大QPS,任务主要是执行计算还是IO,任务是否含数据库连接等稀缺资源

最佳线程数(具体情况具体分析)为((线程等待时间+线程CPU时间)/线程CPU时间)*CPU核心数

共享锁、排它锁、死锁、乐观锁、悲观锁、可重入锁、独占锁、重量级锁、轻量级锁、公平锁、行锁、表锁、自旋锁、读写锁

synchronized关键字

基于字节码由JVM底层 monitor对象监视器实现

Lock接口

Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。

volatile

特性:可见性、禁止指令重排、无法保证原子性

AQS

AbustactQueuedSynchronizer简称,是java底层同步的工具类,用一个int类型变量表示同步状态,并提供一系列CAS操作来管理这个状态

CAP理论

一个分布式系统最多只能同时满足一致性(consistency)、可用性(Available)、分区容错性(Partition tolerance)中的2项

设计模式

单例

private volatile static 懒汉单例 instance = null; // 构造方法私有化,外部不能new private 懒汉单例() {} //3.提供一个公有的静态方法,返回实例对象-实现线程安全的懒汉式(双重检查加锁):public static 懒汉单例 getInstance() {   if (instance == null) {     synchronized (懒汉单例.class) {         if (instance == null) {instance = new 懒汉单例();}     }   }   return instance; }

简单工厂

做个通讯功能: 新增一个通讯接口或抽象类,小米、华为手机实现通讯。简单T厂根据传入公司名称来直接实例化小米、华为手机实例

工厂方法

新增两个接口,一个造手机 (手机工厂)一个打电话(手机抽象接口)。华为、小米手机实现打电话。小米工厂、华为工厂实现生产手机(返回的是打电话接口 (抽象产即。

抽象工厂

工厂方法基础上,手机工厂改为抽象工厂,既可以生产手机也可以生产空调

责任链

责任链模式:Chain of Responsibility Patten 。就是将链中的每一个结点看做是一个对象,每个结点处理请求均不同,且内部自动维护一个下一个结点对象。当请求从链条的首端出发时,会沿着链的路径依次传递给每一个结点的对象,直到有对象处理这个请求为止。

一般比如说一个请假需要多个负责任审批,过五关斩六将等这些,都是责任链模式。

应用:springmvc流程、mybatis的执行流程、spring的过滤器和拦截器、限流熔断sentinel

命令模式

命令模式涉及的角色及其职责如下: 

抽象命令(Command)角色:一般定义为接口,用来定义执行命令的接口。 

具体命令(ConcreteCommand)角色:通常会持有接收者对象,并调用接收者对象的相应功能来完成命令要执行的操作。 

接收者(Receiver)角色:真正执行命令的对象。任何类都可能成为接收者,只要它能够实现命令要求实现的相应功能。 

调用者(Invoker)角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。 

客户端(Client)角色:创建具体的命令对象,并且设置命令对象的接收者。

模板

Abstract Class叫做抽象模板,它的方法分为两类:模板方法和基本方法。基本方法:基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法中。被调用模板方法:可以有一个或者几个,一般是具体的方法,也就是一个框架,,完成固定的逻辑。实现对基本方法的调度

应用:组装电脑,乐高。CPU、内存就是基本方法,整体拼装是模板方法

观察者

需要监听的类加入监听者成员变量,在需要监听的方法后判断监听者有的话增加后置事件

策略

策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口和  具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化

应用:

当我们给人支付工资的时候可以使用不同的支付方式、定义支付接口、新增策略上下文 (看成是某个业务的实例)、定义具体策略的实现 (传参上下文)

装饰器

应用:

增加奶茶计费功能

新增奶茶(计费)抽象类,奶茶类继承奶茶抽象类

继承奶茶(计费)抽象类加料抽象类(装饰器)

布丁、奶绿1继承加料类2增加奶茶抽象类成员变量3通过构造方法初始化,4并重写计费方法,返回自身价值+成员变量奶茶抽象类的计费结果

代理

JDK动态代理,基于反射,使用于有接口的类

CGLIB基于ASM字节码操纵,适用于没有接口的类

优点: 可以在方法前后增加统一的前后置事件

Spring中的应用

代理模式AOP

spring listener 观察者模式

代理模式:AOP和remoting(远程)

单例:bean默认

模板方法:解决代码重复问题:RestTemple、JmsTemplate、JpaTemplate

前端控制器:DispatchServlet对请求进行分发

依赖注入:贯穿BeanFactory和ApplicationContext接口核心理念

工厂模式:BeanFactory来创建对象

数据结构

hashmap

底层数据结构

Entry数组。数组+链表+红黑树

Hash算法本质就三步:取Key的hashcode值、高位运算、取模运算

处理hash冲突

通过单向链表+红黑树解决冲突,出现hash冲突时,比较新老key是否相等,相等,新值覆盖旧值,不等,新值存入新的Node,指向老节点,形成链表

ConcurrentHashMap:1.8取消了reentrantlock(可重入锁)+segment(分段锁)+hashEntry,改为synchronized+CAS+hashEntry+红黑树

B+树

优点

  • 比B树更加矮胖,查找更快,存储更高效

  • 单一节点存储更多元素,使得查询次数更少

  • 所有查询都要查找到叶子节点(覆盖索引),查询性能稳定

  • 所有叶子节点形成有序链表,便于范围查询

数据库

redis

数据结构

  • strings

  • hashes 可以只修改一个属性

  • lists 双端链表

  • sets

  • sorted sets

特点

快:单线程(避免线程切换消耗)、内存、多路复用IO

持久化方式:RDB+AOF,RDB快照,可能丢失。AOF类似binlog

主从同步原理:从初次接入master后先将主的RDB文件下载到本地然后加载进缓存进行全量初始化。后面主会将写命令同步给从slaver

高可用方案

哨兵+分片(redis cluster)+多【主-从】

缓存击穿方案

缓存击穿: key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

  • 场景:热点Key的失效瞬间导致直接请求数据库。处理:不更新的缓存设置永久有效。分布式锁方案,在失效后可以阻塞等待缓存从数据库查回来

  • 分布式部署redis

方案:使用互斥锁(mutex key) 这种思路比较简单,就是让一个线程回写缓存,其他线程等待回写缓存线程执行完,重新读缓存即可。 同一时间只有一个线程读数据库然后回写缓存,其他线程都处于阻塞状态。 如果是分布式应用就需要使用分布式锁。

缓存雪崩方案

缓存雪崩:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉.

1事前 高可用部署方案 2 事中hystrix限流降级避免打死mysql 3事后 备份,重启快速恢复

方案:均匀过期、加互斥锁、缓存永不过期、双层缓存策略(1)过期 设置不同的过期时间,让缓存失效的时间点尽量均匀。通常可以为有效期增加随机值或者统一规划有效期 (2)加锁 跟缓存击穿解决思路一致,同一时间只让一个线程构建缓存,其他线程阻塞排队。 (3)不过期 跟缓存击穿解决思路一致,缓存在物理上永远不过期,用一个异步的线程更新缓存 (4)双层存策略 使用主备两层缓存: 主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值. 备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存.

缓存穿透方案

缓存穿透: key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

数据库查不到就在缓存存一个有效时间空值,下次就会直接读取缓存

方案:布隆过滤器(错误率换取空间)

Mysql

索引

原理

生产环境的 B+树索引有多少层 般是2~3层,可以存放约 两千万行的数据。(2层大概几万条)一层叶子 两层非聚簇索引

B-tree利用了磁盘块的特性进行构建的树。每个磁盘块一个节点,每个节点包含了很关键字。把树的节点关键字增多后树的层级比原来的二叉树少了,减少数据查找的次数和复杂度。B-tree巧5妙利用了磁盘预读原理,将一个节点的大小设为等于一个页(每页为4K),这样每个节点只需要一次IO就可以完全载入。B-tree的数据可以存在任何节点中。

B+tree:B+tree是B-tree的变种,B+tree数据只存储在叶子节点中。这样在B树的基础上每个节点存储的关键字数更多树的层级更少所以查询数据更快,所有指关键字指针都存在叶子节点,所以每次查找的次数都相同所以查询速度更稳定

类型

  • 聚簇索引:一般指主键索引,可以通过索引物理顺序直接找到数据页,而非聚簇索引一般指存了聚簇索引的索引页,不能直接指向数据页可能需要回表

  • 主键索引

  • 非唯一索引

  • 唯一索引

  • B+索引

行锁

mysql行锁基于索引实现,只有用到索引才能触发

什么时候用表锁:更新全表或多表关联更新防止死锁

执行流程

1、查询缓存

2、解析器生成解析树

3、预处理再次生成解析树

4、查询优化器

5、查询执行计划

6、查询执行引擎

7、查询数据返回结果

事务

特性:原子性、一致性、 持久性、隔离性

隔离级别:read uncommit、read commited、repeatable read、serializable

JOIN原理

  • JOIN的原理: 在mysql中使用Nested Loop Join来实现join; A JOIN B:通过A表的结果集作为循环基础,一条一条的通过结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果;

  • JOIN的优化原则:

    • 尽可能减少Join 语句中的Nested Loop 的循环总次数,用小结果集驱动大结果集;

    • 优先优化Nested Loop 的内层循环;

    • 保证Join 语句中被驱动表上Join 条件字段已经被索引;

    • 扩大join buffer的大小;

sql执行顺序

from,join,on,where,group by,sum,having,select,distinct,order by

日志

数据库事务特性的实现原理 在说明原子性原理之前,首先介绍一下 MySQL 的事务日志。MySQL 的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB 存还提供了两种事务日志: redo log (重做日志)和undo log (回滚日志)。其中

redo log 用于保证事务持久性;

undo log 则是事务原子性和隔离性实现的基础

在 MySQL 中还存在 bin log (二进制志也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的

作用不同: redo log 是用于事故恢复的,保证 MySQL 宕机也不会影响持久性; bin log是用于时间点恢复的,保证服务器可以基于时间点恢复数据,此外 bin log 还用于主从复制

层次不同: redo log 是lnnoDB 索引实现的,而 bin log 是 MySQL 的服务器层实现的,同时支持 nnoDB 和 其他存储引擎

写入时机不同: bin log 在事务提交时写入; redo log 的写入时机相对多元 MVCC

undo log

undo log 是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的 SQL 语句。lnnoDB 实现回滚,靠的就是

undo log 属于逻辑日志,它记录的是 SQL 执行的相关信息。当发生回滚时,lnnoDB 会根据undolog 的内容做与之前相反的工作

redolog

持久性 redolog 存在的背景:lnnoDB 作为 MySQL 的存储引,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘10效率会很低,为此 nnoDB 提供了缓存 Bufer Pool,Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从 Buffer Pool 中读取,如果 Buffer Pool 中没有,则从磁盘读取后放入 BufferPool;当向数据库写入数据时,会首先写入 Buffer Pool,Buffer Pool 中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)

MVCC机制

MVCC 全称 Multi-Version Concurrency Control,即多版本的并发控制协议。下面的例子很好的体现了 MVCC 的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)--在 T5 时刻,事务 和事务C 可以读取到不同版本的数据

主从同步原理

从库启线程同步主binlog日志到本地后执行。另外同步方式有半同步复制和并行复制方式,半同步是复制后回传同步标识,解决数据丢失问题。并行是多从一起复制

线上问题排查

show full processlist查看目前在运行的sql

explain 执计划

  • type,查询类型

  • key,优化器采用的索引

  • possible key

  • rows查询需要检索的数量

  • key len: 使用的索引左前缀的长度

show命令

  • show global status like ‘Slow_queries’ 查询慢sql的个数

  • show variables like 'long_query_time' 查看慢查询相关配置

  • show variables like 'innodb_buffer_pool_size' 查看innodb缓存

  • show status like 'Innodb_buffer_pool%' 查看缓存使用状态

    • 缓存命中率= (1-Innodb_buffer_pool_reads/Innodb_buffer_pool_read_requests);

    • 缓存率=(Innodb_buffer_pool_pages_data/Innodb_buffer_pool_pages_total)

  • show profiles 执行过程中各资源消耗情况(会话级)

  • show engine9 innodb status 分析死锁 ,需要super权限

数据库开发规范

基础规范

  • 必须使用InnoDb:支持事务、行级锁、并发性能好,cpu以及内存缓存页使得资源利用率高

  • 必须使用UTF-8字符集:万国码,无需转码、无乱码风险、节省空间

  • 数据库表字段必须加中文注释

  • 禁止使用存储过程、视图、触发器、Event,高并发大数据互联网业务,架构设计思想是解放数据库CPU,将计算移至服务层,并发大时这些功能可能拖死数据库,业务逻辑放到服务层具备更好扩展性,能轻易实现加机器就能加性能。数据库擅长存储和索引,计算上移

  • 禁止存储大文件,附件应该用url形式

命名规范

  • 只允许使用内网域名而不是ip访问数据库

  • 域名区分线上环境、开发环境和测试环境,从库加-s标识,备库加-ss标识

  • 库名、表名、字段名:小写,下划线风格,不超过32字符,必须见名知意,禁止拼音英文混用

  • 表名t,非唯一索引idx,唯一索引uniq

  • 单实例表数小于500

  • 单表列数小于30M

  • 表必须有主键,例如自增

    • 主键递增,数据写入可以提高性能。避免page分页,减少表碎片,提升空间和内存使用

    • 索引较短,减少磁盘空间,提高索引缓存效率

    • 无主键的表删除,在ow模式的主从架构,会导致备库夯住

  • 禁止使用外建:关联应该用业务进行过解读,外建会导致表之间耦合,update和delete会涉及相关表,十分影响性能

字段设计规范

  • 必须把字段定义为not null。并且提供默认值

    • null的列使索引、索引统计、值比较都更加复杂,对于mysql更难优化

    • null类型mysql内部需要特殊处理,增加数据库记录复杂性;同等条件下,表中有较多空字段的时候,数据库处理性能会降低很多

    • nul值需要更多的存储空间,无论表还是索引每行的nul列都需要额外空间标识

    • 对null处理的时候,只能用is null 或 is not null,不能用=、> 、!=等操作符。如 where name !=“jiang”如果name有空值,结果不会包含name为null的记录

  • 禁止使用TEXT、BLOB:浪费更多磁盘空间和内存,大字段查询会淘汰热数据,降低内存命中率,影响性能

  • 禁止用小数存货币,容易对不上

  • 使用varchar(20)存手机号:区号或国家代号。手机号不需计算。支持模糊查询

  • 禁止用ENUM,增加需要DDL操作,实质还是存的整数

  • 应用程序需要捕获异常,并处理

  • 索引

    • 单表索引控制在5个内

    • 禁止在更新频繁、区分度不高的列建立索引:更新会变更B+树,降低数据库性能

    • 单索引字段数控制在5个内

    • 组合索引,区分度高的放前面:最左匹配原则,有效过滤

    • 禁止使用select *,只获取必要字段,显示说明列属性:读取不需要的列会增加IO、CPU、NET消耗。不能有效利用覆盖索引。容易在增加或删除出现BUG

    • 禁止使用insert 表名,需显示指定插入的列属性

    • 禁止使用jion查询,禁止大表使用子查询:产生临时表,消耗cpu内存

    • 索引失效

      • 禁止使用or需要改为in:索引失效

      • 禁止在where条件属性上加函数或表达式

      • 禁止负向查询(not in,!=等)或%开头模糊查询

      • 禁止使用隐式转换:索引失效

    • 应用程序需要捕获异常,并处理

MQ

种类

rabbitmq

特点:万级,us级,主从架构

ActiveMq

特点:主从架构,万级,ms级时效,java开发

RocketMq Alibaba

特点:分布式、10W单机吞吐,ms级时效 java语言

kafka

1.中小型公司首选RabbitMQ: 管理界面简单,高并发。

2.大型公司可以选择RocketMQ:更高并发,可对rocketmq进行定制化开发

3.日志采集功能,首选kafka,专为大数据准备。

优势

  • 系统解耦

  • 提高系统响应时间

  • 为大数据处理架构提供服务

  • java消息服务-JMS

应用场景

异步通信、解耦、冗余、扩展性、过载保护、可恢复、顺序保证、缓冲-削峰、数据流处理

协议

AMQP、MQTT、STOMP、XMPP、基于TCPIP自定义协议(redis、kafka)

保证消息不丢失

组成

消息中间件的组成

2.1 Broker 消息服务器,作为server提供消息核心服务

2.2 Producer 消息生产者,业务的发起方,负责生产消息传输给broker,

2.3 Consumer 消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理

2.4 Topic 主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播

2.5 Queue 队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收

2.6 Message 消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输

Tomcat

组成

  • service (对外提供web服务)包含核心功能:负责接收和反馈外部请求连接器Connector

    • 连接器的核心功能:监听网络端口,接收和响应网络请求。 网络字节流处理,将接受到的字节流转换为Tomcat request,再转换为servletRequest给容器,同时将容器传来的ServletResponse转为Tomact Response在转为网络字节流

    • 组件:Endpoint

      • 端点 Acceptor:监听请求

      • handler:处理数据

    • 处理器processor:负责构建Tomcat Request和Response对象

    • 适配器Adapter:实现Tomcat Request、Response和Servlet Request、response的转换

  • 映射器Mapper:根据请求URL地址匹配由哪个容器来处理

  • 负责对内处理业务的容器Container

    • 介绍:每个service包含一个容器。容器由一个引擎管理多虚拟主机,每个虚拟主机可以管理多个web应用,每个web应用多个servlet包装器

      • 组件:Engine:引擎,管理多虚拟主机

      • Host:虚拟主机,负责web应用的部署

      • Context:web应用,包含多servlet封装器

      • wrapper:封装器,对servlet进行封装,负责实例生命周期

      • 总结:容器的请求处理过程就是在engine、host、context、wrapper这四个容器之间层层调用,最后在servlet中执行业务逻辑。各个容器都有通道pipeline,每个通道上都有basic value,类似一个闸门处理request和response

    • Container用于封装和管理Servlet,以及具体处理Request请求,在Connector内部包含了4个子容器,

  • 一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Htps链接,也可以提供向相同协议不同端口的连接示意图如下(Engine、Host、Context下边会说到):

总结

一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Htps链接,也可以提供向相同协议不同端口的连接示意图如下(Engine、Host、Context下边会说到):

调优

1 connectionTimeout 该参数指定了Tomcat的超时时间,单位为毫秒。

2、maxThreads 该参数指定了Tomcat的最大线程数,默认为200,一般服务器可以设置为5000。 3minSpareThreads 该参数指定了Tomcat的最小空闲线程数。

4、acceptCount 该参数指定了当Tomcat启动进程的数量达到maxThreads参数规定的最大值时,Tomcat队列中允许存储的请求个数,该参数默认为100,一般服务器可以设置为10000。

5enableLooksup 该参数指定Tomcat是否进行DNS查询,一般设置为false,不进行DNS查询以加快处理响应速度

Tomcat启动时的类加载器

1.Bootstrap引导类加载器:加载JVM启动所需类,以及扩展类(jre/lib/ext下)

2.System系统类加载器:加载tomcat启动的类,比如bootstrap.jar,通常在cataline.bat或cataline.sh指定。位于CATALINE_HOME/lib下

3.Common通用类加载器:CATALINE_HOME/lib下,如servlet-api.jar

4.webapp应用类加载器:每个应用在部署后,都会创建一个唯一类加载器。该类加载器会加载位于WEB-INF/lib下的jar和WEB-INF/classes下的class

当应用需要到某个类时,按照下面顺序加载:

1.bootstrap

2.syatem

3、应用类加载器加载WEB-INF/classes

4、应用类加载器加载WEB-INF/lib

5.common类加载器在CATALINE_HOME/lib中加载

框架

Spring全家桶

框架优点

  1. 轻量:基本的版本只有2M

  2. 控制反转,实现了松耦合

  3. AOP面向切面编程,可以把业务逻辑和系统服务分开

  4. IOC容器:管理应用中对象的生命周期和配置

  5. MVC框架式web框架替代品

  6. 事务管理:提供了一个事务管理接口,可扩展到上至本地事务下至全局事务(JTA)

  7. 异常处理:提供方便的API把具体技术相关异常(如JDBC、hibernate、JDO异常)转换为一致的unchecked异常

缺点:

Spring缺点 缺少一个公用控制器 没有SpringBoot好用,spring把好的框架都粘连在一起使用(hibernate、struts2、JPA)

IOC

概念

  1. IOC即为控制反转,把对象的创建和对象之间的调用过程,交给Spring管理。

  2. 使用IOC的目的:为了降低耦合度。提高代码可维护性和易扩展性

  3. 原理是基于反射

  4. Spring中常见的IOC容器有BeanFactory(负责管理bean)和其子接口ApplicationContext(提供AOP、事务管理、国际化等支持)

作用域

  • singleton

  • prototype

  • request

  • session

  • global session

容器的初始化过程

1.)Resource定位;指对BeanDefinition的资源定位过程。通俗地讲,就是找到定义Javabean信息的XML文件,并将其封装成Resource对象。

2.)BeanDefinition的载入;把用户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。

3.)向IoC容器注册这些BeanDefinition。

注意:loC容器初始化过程,一般不包含Bean依赖注入的实现。在Spring loC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。

Bean加载过程

  1. 获取beanName:对传入的name进行解析,转化为可以从Map中获取到BeanDefinition的beanName

  2. 合并Bean定义:对父类进行定义,如果还有就递归合并。以获取完整的bean定义信息

  3. 实例化:使用构造或者工厂方法创建bean

  4. 属性填充:寻找并注入依赖,依赖的bean会递归调用getBean方法

  5. 初始化:调用定义的初始化方法

  6. 获取最终的bean

Bean的生命周期

1、实例化

@开始填充属性

@开始初始化

3.调用BeanNameAware的setBeanName方法

4.调用BeanFactoryAware的setBeanFactory方法

5.调用ApplicationContextAware的setApplicationContext方法

6、调用initiizingBean的afterPropertiesSet方法

7、调用定制的初始化方法

8、调用BeanPostProcess的postProcessAfterIiniiZation方法

删除正在创建的BeanName

返回Bean

9、Bean准备就绪,调用DispostbleBean的destory方法

10.调用定制化销毁方法

如何解决循环依赖

提前暴露

map缓存实例化对象,递归实例化参数

spring内部有三级缓存: singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例

earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例

singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象

循环依赖

构造器方式无法解决,只能抛出异常 多例方式无法解决,只能抛出异常 因为Spring容器不缓存"prototype"作用域的bean,因此无法提前暴露一个创建中的bean。  单例模式可以解决 通过提前暴露一个单例工厂方法,从而使其他bean能够引用到该bean/提前暴露一个正在创建中的bean

AOP

AOP实现的关键就在于AOP框架自动创建的AOP代理,AOP代理则可分为静态代理(例如:原生AspectJ)和动态代理(例如:spring aop)两大类,其中静态代理是指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于JDK动态代理、CGLIB等在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强。 概念

  1. 面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度。

  2. Spring的AOP实现是通过动态代理实现的,分别是JDK的动态代理,以及CGLib的动态代理。

使用场景

  • 权限

  • 缓存

  • 内容传递

  • 错误处理

  • 全局操作日志

  • 懒加载

  • 调试

  • 事务

动态代理

两者的区别

  1. jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。

  2. 当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。

为什么jdk动态代理必须是接口?

jdk动态代理会生成一个字节码文件(class)反编译后看到,生成的代理对象默认继承了Proxy对象,因为java是单继承,所以代理对象必须是接口

事务

传播行为

PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATIONMANDATORY使用当前的事务,如果当前没有事务,就抛出异常

PROPAGATION REQUIRES NEW新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION NOT SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

PROPAGATION NEVER以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与

PROPAGATION REQUIRED类似的操作。

默认使用PROPAGATION_REQUIRED 如果当前没有事务就新建一个,如果已经存在事务则加入

隔离级别

1SOLATION DEFAULT这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别

2.另外四个与JDBC的隔离级别相对应:

3.ISOLATIONREAD UNCOMMITTED这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。

4.ISOLATIONREAD COMMITTED保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻读。

5.ISOLATION REPEATABLE READ这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免不可重复读。

6SOLATION SERIALZABLE这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防上脏读,不可重复读外,还避免了幻读。

默认使用数据库默认的隔离级别

Spring Security安全框架

底层是Filter过滤器

spring ORM

提供了与第三方持久层框架的良好整合

Spring DAO

spring进一步简化dao开发步骤,能以一致的方式使用数据库访问技术,用统一的方法调用事务管理,避免具体实现渗入业务层

Spring Context

上下文,提供了框架式的对象访问方法。提供了国际化(i18n)、电子邮件、校验和调度功能

#BeanFactory 和 ApplicationContext 有什么区别ApplicationContext 在 BeanFactory基础上加了很多高级特性国际化、获取资源、获取环境信息(通过Application context获jdk、操作系统环境)以及发布事件(自定义事件、发布事件、监听事件。观察者模式)

类型:

FileSystemXmlApplicationContext

ClassPathXmlApplicationContext

WebXmlApplicationContext

SpringMVC

原理

  1. Dispatcher调度员:客户端所有请求交给前端控制器DispatcherServlet处理,它负责调用系统其他模块处理用户请求

  2. dispatcherServlet收到请求后,根据请求信息(URL、请求参数)以及HandlerMapping的配置,找到处理该请求的Handler

  3. 在这里spring会通过HandlerAdapter对该处理器进行封装

  4. HandlerAdapter是一个适配器,他用统一的接口对各种handler中的方法进行调用

  5. handler完成请求返回ModelAndViewer对象给dispatcherServlet

  6. 客户端得到响应,可能是html、xml、json、图片或pdf文件

Servlet容器和Spring容器关系

Tomcat&Jetty在启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring容器提供宿主环境。 Tomcat&Jetty在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextlnitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的loC容器,o(容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。 Tomcat&Jetty在启动过程中还会扫描Servlet,一个Web应用中的Servlet可以有多个,以SpringMVC中的DispatcherServlet为例,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求. Servet一般会延迟加载,当第一个请求达到时,Tomcat&Jetty发现DispatcherServlet还没有被实例化,就调用DispatcherServet的init方法,DispatcherServet在初始化的时候会建立自己的容器,叫做SpringMVC 容器,用来持有Spring MVC相关的Bean,同时,Spring MVC还会通过ServetContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,Spring MVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean.也就是说Spring根容器不能访河SpringMVC容器里的Bean,说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。

常用注解

@Controller

  • 用于标注控制层组件

  • 标注在类上,是他成为spring mvc的一个Controller对象

  • 分发处理器将会扫描该类的方法,并检测是否使用了@RequestMapping注解

  • 可以吧Request请求的Header部分的值绑定到方法的参数上

@RestController

相当于@Controller和@responseBody的结合

@RequestMapping

一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径

@ResponseBody

  • 异步请求

  • 该注解用于将Controller的方法返回的对象,通过适当的HTTPMessageConverter转换为指定格式后,写入到Response对象的Body数据区

  • 返回的数据不是html标签的页面,而是其他某种格式的数据时(JSON,XML)时使用

@RequestParam

主要用于在SpringMVC后台控制层获取参数,类似一种是request.getParameter(name)

@PathVariable

用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数

@Component泛指组件,当组件不好归类就用这个

@Service

业务层组件

@Repository 用于注解dao层,在daoimpl类上

@Autowired

可以对类的成员变量、方法及构造函数进行标注,完成自动装配的工作后。通过@Autowired来消除set get方法的使用

SpringBoot

优点:

  1. 配置更简单,约定大于配置

  2. 直接使用性能指标。健康指标和接口应用信息

  3. pom引入包更方便:无需spring、spring MVC、redis、mybaties、log4j、mysql-connect-java,只要start-web依赖即可

  4. 用jar运行

  5. 嵌入式的servlet容器,无需打war包,内嵌tomcat直接运行

  6. 无需xml配置即可实现spring配置

启动原理

1、初始化SpingApplication对象

1、设置应用类型,后面会根据类型初始化对应的环境,常用的一般都是servlet环境

2、加载系统中引导器Bootstrapper (从META-INF/spring.factories中加载)

3、初始化classpath下 META-NF/spring.factories 中已配置的ApplicationContextlnitalizer

4、初始化classpath下所以已配置的 ApplicationListener

5、根据调用栈,设置main 方法的类名

2、执行run()方法

1、获取并启动监听器 2、构造上下文环境 3始化应用上下文 4、刷新应用上下文前的准备阶段 5刷上下文 6、刷新应用上下文后的扩展接口

启动过程

1、通过springFactoriesLoader加载META-INFO/spring.factories文件,获取并创建SpringApplicationRunListener对象

2、然后由SpringApplictionRunListener来发出starting消息

3、创建参数、并配置当前SpringBoot应用要使用的Environment

4、完成之后、依然由SpringApplicationRunListeneer来发出environmentPrepared消息

5、创建ApplicitonContext

初始化ApplicationContext,并设置Environment加载相关配置

7、由SpringApplicationRunListener来发出contextPrepared消息,告知springBoot应用使用的ApplicationContext已准备完毕

8、将各种Beans装载如ApplicationContext,继续由SpringApplicationRunListener来发出contextLoanded的消息,告知Spring boot应用ApplicationContext已装填

refresh Application完成Ioc容器最后一步

10、由SprinApplicationRunListener发出started消息

完成最终的程序启动

12、由SpringApplicationRunlistnener发出running消息,告知程序已运行

常用注解

@Bean:默认使用方法名作为ID

@Scope作用域,默认单例Singleton

@Value 给变量赋值,可以是基本数字字符串或${}从环境变量里取值

@Autowired:自动装配,默认优先从容器中寻找组件

@Profile 环境标识:spring提供了根据当前环境动态激活和切换的一系列组件功能。只有指定的环境才能被注册到容器,默认default

@RestController: @ResponseBody和@Controller的合集

@EnableAutoConfiguration: 尝试根据你添加的jar依赖自动配置你的Spring应用。

@ComponentScan: 表示将该类自动发现(扫描)并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类

@ImportResource: 用来加载xml配置文件。

@Configuration: 相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Confiquration类作为项目的配置主类一可以使用@lmportResource注解加载xml配置文件。

@SpringBootApplication: 相当于@EnableAutoConfiguration、@ComponentScan和@Configuration的合集.

Spring Cloud Alibaba微服务

主要功能

服务限流降级:默认支持WebServlet、WebFlux,OpenFeign、RestTemplate、SpringCloudGateway,Zuul,Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控。

服务注册与发现: 适配Spring Cloud服务注册与发现标准,默认集成了Ribbon的支持.

分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。

消息驱动能力:基于Spring Cloud Stream为微服务应用构建消息驱动能力。

分布式事务:使用@GlobalTransactional注解,高效并且对业务零侵入地解决分布式事务问题。

阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用任何时间、任何地点存储和访问任意类型的数据。

分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有Worker (schedulerx-client)上执行

阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

Ribbon

基于客户端的负载均衡,Feign已默认集成Ribbon

从注册中心服务器获取服务列表缓存到本地,轮询负载均衡策略

Sentinel

  • Sentinel是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。

  • 其他容错组件:Hystrix、Resilience4j

    • Hystrix采用的是线程池隔离的方式,优点是做到了资源之间的隔离,缺点是增加了线程切换 的 成本。 Sentinel采用的是通过并发线程的数量和响应时间来对资源做限制

  • 通过轻量级控制台实现单机资源实时监控、规则管理

nacos

  • 易于构建云原生应用的动态服务发现、配置管理、服务管理平台

  • Discovery服务发现(治理)和Config服务配置

Config

命名空间(Namespace)命名空间可用于进行不同环境的配置隔离。一般一个环境划分到一个命名空间

配置分组(Group)配置分组用于将不同的服务可以归类到同一分组。一般将一个项目的配置分到一组

配置集(Data ID)在系统中,一个配置文件通常就是一个配置集。一般微服务的配置就是一个配置集

RocketMq

开源分布式消息系统,基于高可用的分布式集群技术,提供低延时,高可靠的消息发布与订阅服务

发送方式 :可靠同步发送、可靠异步发送和单向发送可靠同步发送

  1. 同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方式。 此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等可靠异步发送

  2. 异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式发送方通过回调接口接收服务器响应,并对响应结果进行处理。异步发送一般用于链路耗时较长,对RT响应时间较为敏感的业务场景,例如用户视频上传后通知启动转码服务,转码完成后通知推送转码结果等。

  3. 单向发送单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集

Dubbo

高性能RPC框架

Seata

阿里开源产品。易于使用的高性能微服务分布式事务解决方案

四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适用场景: 。AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本。 。TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景 Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。 XA模式是分布式强一致性的解决方案,但性能低而使用较少。

XA协议-两阶段提交(2PC)

XA协议-三阶段提交(3PC)

3.AT(Auto Transaction)模式 AT 模式的一阶段、二阶段提交和回滚均由 Seata 架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务AT 模式是一种对业务无任何侵入的分布式事务解决方案。但AT模式存在的不足就是 当操作的数据 是共享型数据,会存在 脏写的问题,所以如果是 用户独有数据可以使用AT模式

4.TCC(Try、Confirm、Cancel)模式TCC方案其实是两阶段提交的一种改进。分成了Try、Cnfirm、Cancel三个操作。事务发起方在一阶段执行 Try 方式在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法

5 SAGA模式 Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。 事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。

默认AT

三大模块

分别是 TM、RM 和 TC

TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

执行流程

Seata的执行流程如下:

1.A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID

2.A服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖

3.A服务执行分支事务,向数据库做操作

4.A服务开始远程调用B服务,此时XID会在微服务的调用链上传播

5.B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖

6.B服务执行分支事务,向数据库做操作

7.全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚8.TC协调其管辖之下的所有分支事务,决定是否回滚

Alibaba Cloud ACM一款在分布式架构环境中对应配置进行集中管理和推送的应用配置中心产品

Alibaba Cloud OSS 阿里云对象存储服务

Alibaba Cloud SchedulerX 分布式任务调度产品

Alibaba Cloud SMS:覆盖全球的短信服务

gateway

  • 性能强大,是第一代产品zuul的1.6倍

  • 执行流程

    • 1Gateway Client向Gateway Server发送请求

    • 2请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文

    • 3.然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping

    • 4.RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用

    • 5.如果过断言成功,由FilteringWebHandler创建过滤器链并调用

    • 6请求会一次经过PreFilter--微服务-PostFilter的方法,最终返回响应

  • 核心框架 基本概念

    • 路由(Route)是gateway中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个 信息:

    • id路由标识符,区别于其他Route。

    • uri,路由指向的目的地uri,即客户端请求最终被转发到的微服务。

    • order,用于多个Route之间的排序,数值越小排序越靠前,匹配优先级越高。

    • predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。

    • fifilter,过滤器用于修改请求和响应信息。

  • 网关限流

基于Sentinel的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter实例以及SentinelGatewayBlockExceptionHandler实例即可从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeld自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组

链路追踪

Mybaties

定义:一个半ORM(对象关系映射)框架,内部封装JDBC,开发者只关注sql语句本身,不需要处理驱动、链接、事务等。可使用xml或注解配置和映射原生信息

优点

(1)基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sq与程序代码的耦合,便于统一管理,提供XML标签,支持编写动态SQL语句,并可重用。

(2)与JDBC相比,减少了50%以上的代码量,消除了JDBC大量几余的代码,不需要手动开关连接

(3)很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。

(4)能够与Spring很好的集成;

(5)提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护。

缺点

(1)SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求

(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

和hibernate区别

(1)Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句

(2)Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sgl映射文件,工作量大。

(3)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。

一二级缓存

1)一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存

2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置; 3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

分布式方案

分布式任务调度

elastic job

elastic-job使用了quartz的调度机制,内部原理一致,他可以看作是quartz的一个扩展实现,使用注册中心(zookeeper)替换了quartz的jdbc数据存储方式,此外,elastic-job又支持分片等特殊功能

三种作业方式

Simple类型作业Simple类型即为简单实现,未经任何封装的类型,需实现SimpleJob接口,该接口仅提供单一方法用于覆盖,此方法将定时执行,与Quartz原生接口相似,但提供了弹性扩缩容和分片等功能;

DataFlow类型作业 Dataflow类型用于处理数据流,需实现DataflowJob接口,该接口提供2个方法可供覆盖,分别用于抓取(fetchData)和处理(processData)数据; 可通过DataflowJobConfiguration配置是否流式处理。 流式处理数据只有fetchData方法的返回值为null或集合长度为空时,作业才停止抓取,否则作业将一直运行下去; 非流式处理数据则只会在每次作业执行过程中执行一次fetchData方法和processData方法,随即完成本次作业。 如果采用流式作业处理方式,建议processData处理数据后更新其状态,避免fetchData再次抓取到,从而使得作业永不停止,适用于不间歇的数据处理。 Script类型作业Script类型作业意为脚本类型作业,支持shell,python,perl等所有类型脚本。只需通过控制台或代码配置scriptCommandLine即可,无需编码。执行脚本路径可包含参数,参数传递完毕后,作业框架会自动追加最后一个参数为作业运行时信息;

分片机制

  • 分片是指将一个任务拆分成多个任务执行,比如:现在要对一些数据进行处理,为了快速的执行任务,我们用2台服务器,让每台服务器执行任务的50%

  • 这样任务执行就比较快,可以充分利用服务器资源,你可以部署很多台机器来执行这些分片任务;Elastic-job提供的分片功能,能够让任务通过分片进行水平扩展,可以通过多部署几台服务器,支撑海量数据量的任务调度,当任务减少,可以缩减服务器,实现了动态伸缩(扩容和缩容);

  • 分片策略

    • 轮询分片策略

    • 奇偶分片策略

    • 平均分片策略

高可用

  1. 当任务服务器在运行中宕机时,注册中心同样会通过临时节点感知到,并将在下次运行时将分片转移至仍存活的服务器,以达到任务高可用的效果,本次由于服务器宕机而未执行完的任务,则可以通过失效转移的方式继续执行;

  2. 失效转移:Elastic-Job不会在本次执行过程中进行重新分片,而是等待下次调度之前才开启重新分片流程,当任务执行过程中服务器宕机,失效转移允许将该次未完成的任务在另一任务节点上补偿执行;

  3. 线程池策略

  4. 错误处理策略

分布式锁

Redis实现:SETNX + EXPIRE

zk实现

优点锁

安全性高,zk可持久化,且能实时监听获取锁的客户端状态。一旦客户端宕机,则瞬时节点随之消失,zk因而能第一时间释放锁。这也省去了用分布式缓存实现锁的过程中需要加入超时时间判断的这一逻辑。

5适用场 对可靠性要求非常高,且并发程度不高的场景下使用。如核心数据的定时全量/增量同步等。

3缺点性能开销比较高。因为其需要动态产生、销毁瞬时节点来实现锁功能。所以不太适合直接提供给高并发的场景使用。

4实现可以直接采用zookeeper第三方库curator即可方便地实现分布式锁。

分布式会话

tomcat session共享

spring 基于redis的共享

分布式事务

seata

用于在分布式系统中保证不同节点之间数据一致性

分布式数据库

一致性hash

分布式RAFT算法,一致性协议选举

消息中间件

activeMq

rabbit mq

什么情况下需要用到分布式

a.优点:

i.模块解耦,把模块拆分,使用接口通信,降低模块之间的耦合度i.项目拆分,不同团队负责不同的子项目: 把项目拆分成若干个子项目,不同的团队负责不同的子项目.ii.提高项目扩展性: 增加功能时只需要再增加一个子项目,调用其他系统的接口就可以。

iv.分布式部署:可以灵活的进行分布式部署.v提高代码的复用性:比如service层 如果不采用分布式rest服务方式架构就会在手机wap商城,微信商城pcandroid,ios每个端都要写一个service层逻辑,开发量大,难以维护一起升级,这时候就可以采用分布式rest服务方式公用一个service层。

b.缺点: i.系统之间的交互要使用远程通信,接口开发增大工作量ii.网络请求有延时; ii.事务处理比较麻烦,需要使用分布式事务

微服务如何拆分

1、业务方面拆分:所有技术方面的考虑,包括架构设计和解拆分都要考虑业务的需要。在服务拆分时,先从业务角度确定拆分的方案。拆分的边界要充分考虑业务的独立性和专业性,比如搜索类服务、支付类服务、购物车类服务,按服务的业务功能合理地划出拆分边界。

2、减少维护成本:拆分前的维护成本-拆分后的维护成本>0

3、服务独立:确保拆分后的服务由相对独立的团队负责维护,尽量不要出现在不同服务之间的交叉调用。

4、系统扩展:拆分的一个重要理由也是最有价值的结果是提高了系统的扩展性。用户对不同的服务有不同的并发和性能方面的要求,因此服务具有不同的扩展性。把具有不同扩展性要求的服务拆分出来分别进行部署,可以降低成本,提高效率.

接口限流方案

1.限制 总并发数(比如 数据库连接池、线程池)

2.限制 瞬时并发数(如nginx的limit conn模块,用来限制 瞬时并发连接数)

3.限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit req模块,限制每秒的平均速率)

4.限制远程接口调用速率

5.限制MQ的消费速率

6可以根据网络连接数、网络流量、CPU或内存负载等来限流

常见限流算法

计数器法

一分钟最多100个请求,用一个计数器记录,每过一分钟清零,大于100则无法通过

滑动窗口

计数器算法的改进,防止瞬间并高发

漏桶算法

请求滴在桶里,桶地下有洞,均匀的流出(成功请求),当桶满了,多余请求就会失败

令牌桶算法

桶里最多放100个钥匙,以一个固定速率放入token,一个请求拿走一个钥匙,没有钥匙则无法通过

ELK

1. Elasticsearch -->存储数据是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。它是一个建立在全文搜索引擎 Apache Lucene 基础上的搜索引擎,使用 Java 语言编写,能对大容量的数据进行接近实时的存储、搜索和分析操作。

2. Logstash --> 收集数据数据收集引擎。它支持动态的从各种数据源搜集数据,并对数据进行过滤、分析、丰富、统一格式等操作,然后存储到用户指定的位置。

3. Kibana --> 展示数据数据分析和可视化平台。通常与 Elasticsearch 配合使用,对其中数据进行搜索、分析和以统计图表的方式展示。EFK是ELK日志分析系统的一个变种,加入了filebeat 可以更好的收集到资源日志 来为我们的日志分析做好准备工作。

ELK 是现阶段众多企业单位都在使用的一种系统,它能够方便的为我们收集你想要的日志并且展示出来日志分析ELK是Elasticsearch、Logstash、Kibana的简称,这三者都是开源软件,通常配合使用。

Elasticsearch 是一个实时的、分布式的可扩展的搜索引擎,允许进行全文、结构化搜索,它通常用于索引和搜索大量日志数据,也可用于搜索许多不同类型的文档。Beats 是的得力工具。将 Beats 和您的容器一起置于上,或者将 Beats 作为函数加以部署,然后便可在 Elastisearch 中集中处理数据。如果需要更加强大的处理性能,Beats 还能将数据输送到 Logstash 进行转换和解析。数据采集服务器Kibana 核心产品搭载了一批经典功能:柱状图、线状图、饼图、旭日图,等等。不仅如此,您还可以使用 Vega 语法来设计独属于您自己的可视化图形。所有这些都利用 Elasticsearch 的完整聚合功能。Elasticsearch 通常与 Kibana 一起部署,Kibana 是 Elasticsearch 的一个功能强大的数据可视化 Dashboard,Kibana 允许你通过 web 界面来浏览 Elasticsearch 日志数据。

解决方案

大数据高并发

优化思路

  • 限流:只有少部分可以秒杀成功。限制大部分流量

  • 削峰:利用缓存和消息中间件

  • 异步处理

  • 内存缓存:高并发下数据库读写一般是最大的瓶颈,磁盘IO性能低

如何防止库存超卖

  • 乐观锁

  • 数据库锁( for update)

  • 分布式锁

  • 悲观锁

  • 消息队列,使修改库存操作串行化

  • redis watch 监视键值对,如果事务提交时发现监视的键值对发生变化,事务将被取消

雪花算法:snowflake

Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是∶使用41bit作为毫秒数,10bit作为机器的ID( 5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!

如何保证服务幂等性

  • 接口的幂等性实际就是接口的可重复调用,在调用多次的情况下,结果一致有些接口天然幂等,如查询

  • 全局唯一ID:雪花算法,再执行操作前判断id是否存在

  • 多版本控制

  • 状态机控制:比如订单创建0,付款成功100,付款失败99,做状态机更新时,可以 where id = #{id} and status< #{status}

单点登录

OAuth2

OAuth2.0的授权模式

授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner passwordcredentials) 客户端模式(client credentials)

应用:微信三方登录OAuth2.0获取access_token流程微信 OAuth2.0 授权登录让微信用户使用微信身份安全登录第三方应用或网站,在微信用户授权登录已接入微信 OAuth2.0 的第三方应用后,第三方可以获取到用户的接口调用凭证(access_token),通过 access_token 可以进行微信开放平台授权关系接口调用,从而可实现获取微信用户基本开放信息和帮助用户实现基础开放功能等。

三种实现

  1. 应用服务器集群模式:基于tomcat等应用服务器的session同步机制

  2. 微服务模式:cookie+redis,将session用户信息放入redis中,各个模块可以共享redis中用户信息

  3. token:按照规则生产字符串,字符串中可以包含用户信息,开发人员可以自定义生成规则,也可以使用提供好的生成规则(如JWT自动生成含用户信息的字符串)

疑问

如果第三方应用直接通过服务提供方的账户密码访问,存在信息泄露给了第三方,比如微信QQ单点登录第三方App,不可能把账户密码给第三方

分库分表

实现方案

  1. Mycat中间件服务,无需改代码就可兼容

  2. Spring Boot集成ShardingJdbc实现分库分表

    1. ShardingJdbc本质上是一个轻量级的JDBC驱动代理,在使用的过程中只需要依赖相关Jar包即可,并不需要额外部署任何服务。通过系统配置文件就可以实现分库分表逻辑的定义,并实现应用透明代理访问。

  3. JDBC直接实现水平分表

考虑问题点

业务关联,存储规划、未来数据增长模式评估、怎么拆、一致性问题、分布式事务、跨节点JION、分页排序统计问题、分库分表细节如何对应用透明,如何用代理层屏蔽分库分表对程序带来侵入

TOP key海量搜索

局部淘汰

分治

hash去重

最小堆

持续集成、持续发布、jenkins

分布式全局唯一发号器

redis生产ID INCR原子操作

Twitter的snowflake雪花算法

带过期策略的LRU缓存

利用链表和hashMap,当插入时在链表中命中,则把节点移到最前,不存在则新增插入头部,如数据已满,则移除最后节点

设计统一配置中心

特点

  • 配置的CURD

  • 不同环境配置隔离

  • 高性能 高可用

  • 高并发

现场问题处理

Linux常用命令

vi、rm、cfconfig、mv

Top命令

常用的Linux基本操作命令 tail -n 行数值 filename 查看文件最后5行 在Linux下查看所有java进程命令:1。ps -ef grep java 停止特定java进程命令: ill-9 java进程序号停止所有java进程命令: pkill- 9java 2、jps -l-v -m 输出传递给main 方法的参数,在入式jvm上可能是null-v 输出传递给JVM的参数 ls-用来显示目标列表 cd -用来切换工作目录 pwd-以绝对路径的方式显示用户当前工作目录cat-文件内容查看 grep-是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来tail-输出文件中的尾部内容 ps-用于报告当前系统的进程状态kill -命令用来删除执行中的程序或工作top -可以实时动态地查看系统的整体运行情况ifconfig

ping

telnet

性能优化

常见优化手段

* 进行系统性能测试> 所有的性能调优,都有应该建立在性能测试的基础上,直觉很重要,但是要用数据说话,可以推测,但是要通过测试求证。* 寻找系统瓶颈,分而治之,逐步优化 > 性能测试后,对整个请求经历的各个环节进行分析,排查出现性能瓶颈的地方,定位问题,分析影响性能的的主要因素是什么?内存、磁盘IO、网络、CPU,还是代码问题?架构设计不足?或者确实是系统资源不足?

存储性能优化

* 选择更优的算法

* 选择合适的数据结构

* 编写更少的代码

代码优化

  • 不创建不必要的对象

  • 使类和成员的可访问性最小化

  • s使可变性最小化

  • 优先使用复合

  • 接口优于抽象类

  • 可变参数谨慎使用

  • 返回零长度的数组或集合,不要返回null

  • 优先使用标准的异常

  • 用枚举代替int常量

  • 将局部变量的作用域最小化

  • 当心字符串连接的性能

  • 精确计算,避免使用float和double

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HELLO XF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值