锦鲤日续订活动:
1、网关转发请求后,对入参RSA验签
2、在业务处理器初始化后,把中移动传过来的业务类型参数注入到处理器的属性,然后实现处理器做业务逻辑处理,远程调用用户模块、协议模块暴露的查询服务RPC接口、对用户的uid进行续订资格校验查询,将第三方通知处理结果返回给网关
3、跳转续订页面,小程序后端调用核心模块,如果是续订场景,做业务逻辑处理,续订同步任务记录先初始化入库,更新任务状态为代执行,选择对应的topic,给自己发MQ消息,消费成功时,将同步消息记录表入库,同时调用渠道模块,组装同步信息,走出口网关,同步给中移动发送请求,信息同步给中移
付款模块:
4.1 钱包模块接收到扣款消息,先发送扣款成功半消息 4.2 钱包模块执行本地事务,模拟支付,更新钱包信息,插入扣款流水 4.3 回查逻辑为:查询本地是否存在扣款流水,不存在则回滚事务消息 4.4 订单模块接收到事务消息后,开始更新本地订单状态。此处通过乐观锁保证幂等算法
- 冒泡;一次比较2个 n2 稳定
- 归并:分成n/2 nlogn2 稳定
- 插入:从后往前插入 n2 稳定
- 希尔:n^ 1.3 不稳定
- 快排:nlog2n
- 选择:从未排序中选最大的 n2
- 堆排序:nlogn2
实例化:在第一次访问或启动tomcat时,tomcat会调用此无参构造方法实例化servlet。(一次)
初始化:tomcat在实例化此 servlet 后,会立即调用 init() 方法初始化servlet。(一次)
就绪:容器收到请求后调用 servlet 的 service() 方法来处理请求。(多次)
销毁:容器删除 servlet 对象,删除前会调用 destory() 方法。(一次)
请求转发:forward(); 重定向:sendRedirect()
重定向
1、使用 response 对象的 sendRedirect() 方法
2、setStatus() 和 setHeader() 方法一起使用
String site = “https://www.baidu.com/” ;
response.setStatus(response.SC_MOVED_TEMPORARILY);
response.setHeader(“Location”, site);
spring boot通过FilterRegistrationBean实例注册,实现Filter类重写doFilter方法,对参数进行校验。创建一个FilterConfiguration类,允许的网站域名,限制 HEADER 或 METHOD,设置顺序
监听器利用Listener实现Redis的缓存预热,实现ServletContextListener接口,重写contextInitialized()方法,获取service对象bean,获取mySql数据库中所有的User,将User缓存到Redis数据库中
AOPSpring框架根据bean是否实现接口决定使用何种AOP实现:
如果bean实现了接口则使用JDK动态代理对bean进行增强, 实现impl
如果bean没有实现接口则使用CGLib对bean进行增强。继承extends
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
TCP和UDP 三次握手1、客户端给服务器发送一个 SYN 报文。 发送正常 2、服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。 接收正常 3、客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。 4、ACK 报文之后,三次握手建立完成。四次挥手
1、客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。 2、服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。 3、如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。 4、客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态 5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。fail-fast和fail-safe
用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
BIO/NIO/AIO1、BIO/阻塞IO,从系统内核拷贝数据到内存空间时,有线程阻塞等待数据传输完成,然后处理
2、NIO:将BIO等待时间做一个轮询机制,如果数据处理好,就调度线程处理
3、AIO:异步通信,数据加载完后,push发事件通知
反射 Instanceof、 JVM 类加载机制1、LOAD加载:加载到内存,生成二进制字节流,转化为方法区数据结果,生成对象。
2、link验证:对文件格式、元数据、字节码、引用验证
准备:给类分配内存设置初始值
解析:常量池的符号引用改为直接引用
3、INIT初始化:执行代码
4、使用:使用阶段包括主动引用和被动引用,主动饮用会引起类的初始化,而被动引用不会引起类的初始化
5、卸载:类所有的实例都已经被回收、ClassLoader被回收、无反射(Major GC)
虚拟机公有线程
程序计数器:线程独立,改变计数器来选取指令
Native方法栈:Java 通过 JNI 直接调用本地 C/C++ 库
Java堆:储存实例对象、1.8存字符串常量 Java堆是垃圾收集器管理的主要区域。线程共享。-Xmx4000m -Xms4000m LargePageSizeInBytes(大分页内存)=128m,使用70%后开始CMS收集
虚拟机栈(java栈):线程私有,存储局部变量表、操作数栈、动态链接、方法出口。
方法区:所有的①类(class)②静态变量(static变量)③静态方法④常量 ⑤成员方法
元空间:Object的System.out、int型常量
字符串放在常量池,jdk7放在堆内存永久代,jdk8放在元空间
双亲委派模型两种类加载器:
-
启动类加载器(Bootstrap ClassLoader):加载jr的核心库
-
其它类的加载器:
扩展类加载器(Extension ClassLoader) 加载ext目录的扩展包
应用程序类加载器(Application ClassLoader)java代码
破坏双亲委派模型
1、继承ClassLoader覆盖loadClass() 2、线程上下文类加载器 3、热替换、模块热部署
Tomcat类加载:Catalina类加载器(加载Tomcat专用的类)和Shared类加载器(Web应用程序都复用的类)是兄弟关系
为什么委托机制:递归的向父类查找,防止内存中出现多份同样的字节码
垃圾标记算法原理:
引用计数算法:有一个地方引用它时,计数器值就加1。缺点:互相引用
根可达算法:GC Roots 的对象作为根,走过的路径称为引用链,没有引用链对象叫垃圾
垃圾收集算法标记-清除算法、标记-整理算法、复制算法、分代收集算法
垃圾回收机制GCRoots:
GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。
Major GC:方法区GC,类加载机制的卸载
Minor GC:新生代“消失”的过程
新生代空间的构成:比例8:1:1 一个伊甸园空间(Eden)标记清除、两个幸存者空间(Survivor0、Survivor1)复制
Eden满了,执行YoungGC,移动到Survivor0空间,Survivor0满了,存活对象移动到Survivor1,重复15次,剩下移动到老年代
老年代:对象比较大放老年代,老年代存不下引发Full GC,调优主要是减少 Full GC 的触发次数
Java中四种引用fullGc system.gc finalize 垃圾对象
三色标记法:白:所有对象 灰:全局变量和函数栈的对象 黑:?,1、GCRoots指向的对象置为灰色。2、把灰对象全部置为黑,第一步的灰对象指向的为灰 3、继续灰变黑,灰指向灰色,等灰色都变成黑色,剩下的白色需要清除
CMS使用 标记-清理 算法,用两次短暂的暂停来代替串行或并行标记整理算法时候的长暂停。
1、初始标记:停顿线程STW,标记对象
2、并发标记:用户线程可以和GC线程一起并发执行
3、并发预清理:重新扫描,找到2并发标记过程需要被回收的
4、重新标记:停顿线程STW
5、并发清理:并发执行
G1并行与并发的标记-复制垃圾回收器。G1将内存划分一个个固定大小的region,每个region可以是年轻代、老年代的一个。
整个堆被划分成2048左右个Region。每个Region的大小在1-32MB之间
Region的状态有三种:young(新生代)old(老年代)Humongous(半满)
把一个对象分配到Region内,只需要简单增加top的值,撞点到top值,就从新变老,维护一个空间Region的链表,回收之后的Region都会被加入到这个链表中。
ZGCZGC关键技术:着色指针和读屏障技术
JVM监控命令jps : 虚拟机进程状况工具,列出正在运行的虚拟机进程
jinfo : Java配置信息工具
jmap : 生成堆转储快照
jstack :生成虚拟机当前时刻的线程快照
JConsole:常用工具
设计模式1、创建型模式
抽象工厂模式一个超级工厂创建其他工厂。
抽象工厂创建 查流量 接口,定义继承抽象类 FlowFactory工厂类,创建一个工厂创造器 FactoryProducer。 业务实体类使用FactoryProducer获取FlowFactory的对象,像AbstractFactory传递要查询过期流量的数据,获取所需对象的类型
单例模式商检测、校验
装饰器模式 模版模式 组合模式3、行为型模式
责任链设计模式为请求创建了一个接收者对象的链。
创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
策略模式 spring注入方式
循环依赖 提前暴露 三级缓存
事务失效的原因1、数据库引擎不支持事务,MyISAM 引擎是不支持事务操作的
2、没有被 Spring 管理,去掉注解@Service,不会被 Spring 管理
3、@Transactional 注解只能应用到 public 可见度的方法上,用在其他也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
4、自身调用问题,方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的方法不生效
IOC控制反转IOC的初始化流程:
1、根据 xml 文件内容来构建 ApplicationContext
2、Resource定位,访问XML配置文件
3、将Resource定位好的资源载入到BeanDefinition
4、将BeanDefiniton注册到容器中
AOP面向切面编程实现方式两种:
静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
CGlib 静态代理:在编译期实现
JDK 动态代理:带有接口的对象,在运行期实现
1、创建接口及实现类
2、实现代理处理器:实现 InvokationHandler ,实现 invoke(Proxy proxy,Method method,Object[] args) 方法
3、通过 Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h) 获得代理类
4、通过代理类调用方法
普通 IO ,面向流,同步阻塞线程
NIO,面向缓冲区,同步非阻塞
bean生命周期beanfactory生命周期:去掉ApplicationContextAware接口步骤
1、实例化Bean
调用createBean进行实例化,实例化对象被包装在BeanWrapper对象中
2、 设置对象属性(依赖注入)
3、 注入Aware接口,实现BeanNameAware接口,BeanFactoryAware接口,ApplicationContextAware接口
4、前置处理——》检查配置自定义的init方法——〉后置处理
5、使用
6、Bean是否实现DisposableBean接口,调用destroy方法
7、是否配置destroy-method属性自动销毁
springboot@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了
并发 多线程CPU密集型和IO密集型
线程池提交方式有两种:execute()和submit()
创建线程的方法1、继承Thread类,重写run()方法
2、实现Runnable接口,重写run()接口。
4、实现Callable接口,重写call(),带返回值,将FutureTask作为返回的承接对象
5、线程池的实现
Executor接口定义execute方法,接收一个Runable实例,执行任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,比如,ExecutorService提供关闭方法shutdown,以及跟踪任务执行状况的Future 方法。
//流打印 list.parallelStream().forEach(System.out::println); //流计算求和 return list.parallelStream().mapToInt(i -> i).sum();线程池 三大方法
Executors.newSingleThreadExecutor();–创建一个线程的线程池
Executors.newFixedThreadPool(int n);–创建指定线程的线程池
Executors.newCachedThreadPool();–创建可缓存的线程池
int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了 无调用会释放
TimeUnit unit, //超时单位
BlockingQueue workQueue, //阻塞队列
ThreadFactory threadFactory, //用于创建线程的线程工厂
RejectedExecutionHandler handler //拒绝策略
AbortPolicy 队列满了,抛异常
CallerRunsPolicy 哪来的回哪去(一般回主线程)
DiscardPolicy 队列满了,丢掉任务,不抛异常
DiscardOldestPolicy 队列满了,尝试和最早线程竞争,不会抛出异常
1、新建状态(new):使用 new 创建一个线程,仅仅只是在堆中分配了内存空间
2、可运行状态(runnable):新建状态调用 start() 方法,进入可运行状态。而这个又分成两种状态,ready和 running
就绪状态(runnable):线程对象调用了 start() 方法,等待 JVM 的调度,(此时该线程并没有运行)
运行状态(running):线程对象获得 JVM 调度,如果存在多个 CPU,那么运行多个线程并行运行
注意:线程对象只能调用一次 start() 方法,否则报错:illegaThreadStateExecptiong
3、阻塞状态(blocked):正在运行的线程因为某种原因放弃 CPU,暂时停止运行,就会进入blocked。此时 JVM 不会给线程分配 CPU, 直到线程重新进入ready,才有机会转到running。
4、等待状态(waiting):等待状态只能被其他线程唤醒,此时使用的是无参数的 wait() 方法
5、计时等待(timed waiting):调用了带参数的 wait(long time)或 sleep(long time) 方法
6、终止状态(terminated):通常称为死亡状态,表示线程终止
线程的方法1、start():开启一个新的线程来执行用户定义的子任务
2、run():继承Thread类重写run方法,执行任务
3、sleep(long millis):线程休眠,不会释放同步锁。
4、wait():执行到wait(),就释放当前的锁,必须在同步代码块或同步方法中
5、notify()/notifyAll():唤醒wait的一个或所有的线程,会放弃同步锁,需和wait()成对使用,必须在同步代码块或同步方法中
6、join():联合线程:表示这个线程等待另一个线程完成后(死亡)才执行,join 方法被调用之后,线程对象处于阻塞状态。写在哪个线程中,哪个线程阻塞
7、yield():礼让线程,让出 CPU 资源,线程对象进入就绪状态,会优先给更高优先级的线程运行机会
线程的同步1、同步方法:有synchronized关键字修饰的方法。
2、同步代码块:锁
3、特殊域变量(volatile)
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
4、使用重入锁实现线程同步
ReentrantLock类是可重入、互斥、实现了Lock接口的锁。ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
5、使用局部变量实现线程同步
使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
ThreadLocal与同步机制区别
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
6、使用阻塞队列实现线程同步
linkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。队列是先进先出的顺序(FIFO)
linkedBlockingQueue 类常用方法
linkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的linkedBlockingQueue
put(E e) : 在队尾添加一个元素,如果队列满则阻塞
size() : 返回队列中的元素个数
take() : 移除并返回队头元素,如果队列空则阻塞
hashCode是jdk根据对象的地址算出来的一个int数字,代表对象在内存中的存储位置。重写equals方法的同时必须也要重写hashCode方法,否则就会降低 Map 等集合的索引速度
hash冲突:两个对象对应一个hashCode,但equals却并不相等,链表数据结构来解决hash冲突的情况
==和equals()的区别:
==:判断两个字符串在内存中首地址是否相同,即判断两者是否是同一个字符串对象
equles():如果没有重写equals()方法比较的是对象的地址,因为对Object来说对象没有什么属性可以比较,只能比较最底层的地址.
ArrayList一个默认容量=10用数组实现的集合,支持随机访问,元素有序且可以重复。
第 11 次添加元素,扩容1.5倍将原数组元素引用拷贝到新数组,数组可以有 null 值存在
linkedList是链表实现的线性表(双链表),元素有序且可以重复。集合里每一个元素就代表一个 Node 类对象。
public Object pop() {
//堆栈
return this.list.removeLast();
//队列
return this.list.removeFirst();
}
普通for循环的用时远大于迭代器的遍历,因为每次要头开始遍历
普通:for (int i = 0; i < size; i++) { list.get(i); }
增强for:for (Integer i : list) { }
Iterator迭代器:for(Iterator iter = list.iterator(); iter.hasNext() { iter.next(); }
ArrayList和linkedList区别:
linkedList查找时间复杂度是O(n),插入删除的时间复杂度是O(1)。ArrayList相反
linkedList 和 ArrayList 都是线程不安全的,可以使用 Collections 中的方法在外部封装一下。
HashMap1、哈希函数
- 对key对象的hashcode进行扰动:低16位是和高16位进行异或
- 通过取模求得数组下标
2、哈希冲突
出现冲突后采用的是链地址法,
-
当链表的长度>=8且数组长度>=64时,会把链表转化成红黑树。
-
当链表长度>=8,但数组长度<64时,会优先进行扩容,而不是转化成红黑树。
-
当红黑树节点数<=6,自动转化成链表。
3、扩容方案
1.7:遍历所有的节点,把所有的节点依次通过hash函数计算新的下标,再头插法插入到新数组的链表中
1.8:2倍长度,一定是原来位置+原数组长度。
4、线程安全
HashMap并不是线程安全的,在多线程的情况下导致数据丢失,死循环问题:线程新建节点采用头插法找不到链表尾部形成死循环
线程A将节点插入下标2的位置,在判null之后,线程被挂起;线程A将节点插入下标2的位置;恢复线程A,节点会覆盖,导致数据丢失
jdk1.8采用了尾插法,解决死循环问题,未解决多线程数据丢失
5、解决数据一致性问题
-
synchronize锁对象方法
采用Hashtable
调用Collections.synchronizeMap()方法来让HashMap具有多线程能力
在每个方法中,对整个对象加synchronize关键字进行上锁。重量级锁,单线程进行读写,限制了并发
ConcurrentHashMap在1.7中,AQS+ReentrantLock
ConcurrentHashMap由Segment+HashEntry,Segment实际继承自可重入锁(ReentrantLock),一个ConcurrentHashMap里包含一个Segment数组,Segment包含一个HashEntry数组,是一个链表结构的table
在1.8中,CAS+Synchronized(CAS )
用Node数组+链表+红黑树的数据结构,插入位置没有数据,就直接CAS无锁插入,存在hash冲突,锁住链表或者红黑树的头结点
HashTableHashMap和HashTable的区别
1、父类不同:HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。
2、对外提供的接口不同:Hashtable比HashMap多提供了elments() 和contains() 两个方法。elments()返回value的枚举。contains()判断传入的value
3、对null的支持不同:Hashtable:key和value都不能为null;HashMap:key仅一个可以为null,多个value为null
4、安全性不同:HashMap是线程不安全的,会产生死锁;Hashtable是线程安全的,每个方法上都有synchronized 关键字。需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。分段锁,并不对整个数据进行锁定
HashTable:初始容量是11,扩容策略是翻倍加1,即当前容量 capacity * 2 + 1
put:哈希取模,头插法插入重复元素 get:做Hash映射,得到对应的index,重复元素尾出
HashSetHashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
用于全局限流使用
ThreadLocal类似HashMap的数据结构,保存一对key/value的Entry单元,各个线程的数据互不干扰。
key:ThreadLock对象
value:修改的变量
每个线程中都有一个ThreadLocalMap数据结构,ThreadLocalMap的Entry继承了WeakReference
内存泄露
原因:
使用ThreadLocal的set方法之后,插入Entry对象在ThreadLocalMap中是弱引用,得不到回收,没有显示的调用remove方法,就有可能发生内存泄露
ReentrantLockSynchronized和ReentrantLock的区别
1、ReentrantLock 和 syncronized ,都是可重入锁,RetrantLock 利用 AQS 的 state 状态来判断资源是否已锁,加锁+1,解锁-1,state=0,获取资源
2、ReentrantLock,需要 lock() 和unlock()
3、锁的超时时间,syncronized可能阻塞,ReentrantLock 有 tryLock方法,超时跳过
4、synchronized 是非公平锁,先抢到锁先执行。ReentrantLock能设置ture/fasle来设置公平/非公平锁
5、ReentrantLock可以中断锁
Synchronized同步代码块:monitorenter插入到同步代码块开始位置,monitorexit指令插入到同步代码块结束的位置
CountDownLatchCountDownLatch称之为闭锁;countDown(),一个是await();countDown方法用来给计数器减一
volatile- 保证变量的内存可见性 - 禁止指令重排序
java虚拟机提供的轻量级的同步机制
1、volatile 写前插入 StoreStore屏障
2)volatile 写后插入 StoreLoad屏障
3)volatile 读前插入 LoadLoad屏障
4)volatile 读后插入 LoadStore屏障
数据库 数据库三范式1.第一范式(1NF):列不可再分。
2.第二范式(2NF):属性完全依赖于主键。
3.第三范式(3NF):属性不依赖于其它非主属性 属性直接依赖于主键。
B树:节点存储key和data,叶子结点指针为空。时间复杂度基本
B+树:只有叶子结点存储data
Mysql的B+树:增加了顺序访问指针,优化范围查找
SQL优化EXPLAIN命令:
MYSQL会在查询上设置一个标记,当执行查询时,显示出执行计划中的每一部分和执行的次序。
Explain Select * From db;
- 哪些索引可以使用
explain查询计划优化章节,即explain的输出结果Extra字段为Using index时,能够触发索引覆盖
案例:
设计一张退订原因记录表,索引设为主键id,手机号+退订原因+创建时间的联合索引,查询某月或者某几个月时间段数据效率太低。通过EXPLAIN,发现ref中创建时间段没用上,了解到最左前缀原理,新增范围索引年份+月份的parttime列,来进行数据分片查询。
回表
从普通索引无法直接定位行记录,扫码两遍索引树,先通过普通索引定位到主键id,在通过聚集索引定位到具体行记录。出现using index condition就是二级索引回表
查询优化:
slow_query_log打开用慢查询日志,用日志分析工具mysqldumpslow,查看慢查询的次数和sql语句。
InnoDB mysql隐藏列MySQL使用的B+Tree结构都在经典B+Tree的基础上进行了优化,在B+Tree的每个叶子节点增加一个指向相邻叶子节点的顺序访问指针,优化区间访问性能。
data存的是数据本身。索引也是数据。数据和索引存在一个文件中。
行级锁(row-level):开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
事务的隔离级别- 读未提交(READ UNCOMMITTED) 脏读,读到未提交的数据
- 读已提交(READ COMMITTED) 不可重复读 事务未提交,可能多次读取数据不一致
- 可重复读(REPEATABLE READ) 发生幻读 读到别人提交的记录
- 串行化(SERIALIZABLE) 事务串行
隔离级别导致的可能异常:
脏写:事务A更新NULL为a,B读取到a更新为b,A用 undo log 日志执行回滚,将b改为NULL
不可重复读:事务A读取到a,事务B将a改为b,事务C将a改为c,事务A查询到三次的结果为,a,b,c。update、delete
幻读:事务A范条件索引得到,a、b,事务B insert 后在相同条件中新增c条,事务A再次读取发现a,b,c insert
MySQL的MVCCACID,分别为隔离性、一致性、原子性、持久性。在InnoDB中,利用日志恢复技术保证了事务的原子性和持久性,利用并发控制技术,保证了事务之间的隔离性。
多版本并发控制
MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read)。
MVCC是通过保存数据在某个时间点的快照来进行控制的。使用MVCC就是允许同一个数据记录拥有多个不同的版本。然后在查询时通过添加相对应的约束条件,就可以获取用户想要的对应版本的数据。
MVCC过程:
InnoDB的MVCC,是通过在每行纪录后面保存两个隐藏的列来实现的。一个保存了行的创建时间,一个保存了行的过期时间
在可重复读隔离级别下,MVCC具体的操作如下:
1、开始事务
2、记录数据行数据快照到undo log
3、更新数据
4、将undo log写到磁盘
5、将数据写到磁盘
6、提交事务
日志缓存
redo log(重做日志):包括重做日志缓冲(redo log buffer),日志易失;重做日志文件(redo log file),是持久的
记录的是每一个数据页中的更改
undo log(回滚日志):保存了事务发生之前的数据的一个版本,当事务提交之后,undo log 并不能立马被删除,而是放入待清理的链表,由 purge 线程判断是否有其它事务在使用 undo 段中表的上一个事务之前的版本信息。
binlog (二进制日志):存储着每条变更的SQL语句
分库分表垂直拆分:通过用户的唯一uid做关联。将用户的相关数据拆分成为用户表、账户表、记录表、流水表等相关表
水平拆分:同一张表中的数据拆分到不同的数据库中进行存储、把一张表拆分成 n 多张小表。例如32库1024表格
读写分离:更新数据时,应用将数据写入master主库,主库将数据同步给多个slave从库。查询数据时,选择某个slave节点读取
分片策略:常用的方法有:HASH取模、范围分片、地理位置分片、时间分片等
分布式全局ID选择方案:
公司方案:根据手机号作为路由规则值计算字符串,通过zal分库分表工具类,计算序列值。再用手机号计算的字符串和分库数量分表数量进行运算,最后拼接三个字符串得到全局唯一id
1、支付宝uid作为索引,查询慢
1、redis自增
2、雪花算法 :0+41bit时间戳+机器id+序列号组成
3、Mysql自增主键:与业务方案不符,影响用户id搜索效率
推荐连续自增的主键id,推荐不要使用uuid或者不连续不重复的雪花id
Redishttps://www.cnblogs.com/gtblog/p/13536391.html
单线程的 Redis 为什么这么快:
纯内存操作,不用上下文切换、单线程操作、非阻塞 I/O 多路复用机制
跳表:
String:一个 key 对应一个 value,string 类型的值最大能存储 512MB。做一些复杂的计数功能
Hash:Redis hash 是一个键值(key=>value)对集合适合用于存储对象。做单点登录的时候,存储用户信息
List:简单的字符串列表,按照顺序插入。简单的消息队列、基于 Redis 的分页功能
Set:是 string 类型的无序集合,集合是通过哈希表实现的 做全局去重
zset:也是string类型元素的集合,且不允许重复的成员,每个元素都会关联一个double类型的分数,通过分数来为集合中的成员进行从小到大的排序 排行榜功能
Stream:主要用于消息队列,缺点就是消息无法持久化。
Bitmap位图:基于string定义了一组bit位操作,一串很长的二进制向量,初始默认值都是0,利用hash函数置为1
内存淘汰机制**allkeys-lru:**当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key
**volatile-lru:**当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key
Redis 和数据库双写一致性问题首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
并发竞争单节点:setnx+lua:
set key value px milliseconds nx:
- set命令要用set key value px milliseconds nx;
- value要具有唯一性;
- 释放锁时要验证value值,不能误解锁;
//获取锁(unique_value可以是UUID等)
SET resource_name unique_value NX PX 30000
// 释放锁(lua脚本中,一定要比较value,防止误解锁)
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
多节点:RedLock算法
假设有5个完全独立的redis主服务器:
1、获取当前时间戳
2、按照顺序使用相同的key,value获取所有redis服务的锁
3、client通过获取所有能获取锁的时间减去时间戳,小与TTL。至少有3个redis实例成功获取锁
4、锁的有效时间是TTL—获取锁事件5
5、获取锁失败,解锁所有redis实例,在随机时间后重试获取锁,设定次数限制
持久化机制RDB机制(异步):save, BGSAVE 命令会触发一个操作,父进程执行fork操作创建子进程,遍历hash table,子进程创建RDB数据文件,fork() 可能会非常耗时,会丢失数据
AOF机制:持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,记录数据库状态
集群策略主从+哨兵模式、Cluster集群
内存淘汰算法LRU(最近最少使用)、LFU(最不经常使用页置换算法)
缓存穿透请求穿过了缓存层,直接打到了数据库
缓存穿透:缓存和数据库中都没有
鉴权校验,将key-value对写为key-null
缓存击穿:数据库有,缓存没有
热点key永不失效;加互斥锁,缓存没有数据,就获取锁,读库更新缓存
缓存雪崩:大量key同时过期
过期时间随机;热点key分布在不同数据库中,
BLoom过滤器bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。穷举法
实现:
1、bitmap:一串很长的二进制向量,初始默认值都是0,利用hash函数置为1
2、Redisson工具
缓存雪崩给缓存的失效时间、使用互斥锁、双缓存(从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B)
数据不一致问题采用rehash自动漂移策略,在节点多次上下线之后,也会产生脏数据
缓存降级可以砍掉某个功能,也可以砍掉某些模块。
缓存限流(热点key)限流算法有:计数器、漏桶和令牌桶算法。
计数器:使用linkedList来记录滑动窗口的10个格子,每次移动都需要记录当前服务请求的次数。当前访问次数和linkedList中最后一个相差是否超过100,如果超过就需要限流了。
漏桶算法:使用队列来实现
令牌桶算法:存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌
系统设计缓存的技术方案分类:
1)实时性比较高的那块数据,比如说库存,销量之类的这种数据,我们采取的实时的缓存+数据库双写的技术方案,双写一致性保障的方案。
2)实时性要求不高的数据,比如说商品的基本信息,等等,我们采取的是三级缓存架构的技术方案,就是说由一个专门的数据生产的服务,去获取整个商品详情页需要的各种数据,经过处理后,将数据放入各级缓存中。
使用MQ将客户创建订单时,将后面的轨迹、库存、状态等信息的更新全都放到MQ里面然后去异步操作,这样就可加快系统的访问速度,提供更好的客户体验。
Message结构* @param topic 消息主题, 最长不超过255个字符; 由a-z, A-Z, 0-9, 以及中划线"-"和下划线"_"构成. * @param tag 消息标签, 请使用合法标识符, 尽量简短且见名知意 * @param key 业务主键 * @param body 消息体, 消息体长度默认不超过4M, 具体请参阅集群部署文档描述.MQ高可用
Producer:发送有三种方式:同步,异步,单向(Oneway)
双主双从架构,NameServer多节点,同步双写,异步刷盘,改同步刷盘可靠性更高,消息持续化到磁盘
消息重复消费
RocketMq不解决消息重复消费问题,影响消息系统的吞吐量和高可用,靠业务逻辑来解决
1、消费端处理消息的业务逻辑保持幂等性,例如数据库记录消息的id确保唯一,没有就新增,有就修改
2、在发送消息时,给每条消息指定一个全局唯一的 ID。消费时,先根据这个 ID 检查这条消息是否有被消费过,如果没有消费过,才更新数据,然后将消费状态置为已消费,如果消费过,那么就不处理
消息丢失问题1、开启分布式事务,保证producer消息发送给MQ,成功 commit 半消息,就可以消费
2、开启同步刷盘,mq消息持久化时,是先将消息写到内存,如果消息没异步刷盘,Broker宕机,也会丢失
3、消费者自带监听器,消费完消息后,返回CONSUME_SUCCESS,就消费成功,没返回其他消费组处理
消息顺序消费实际场景:放在同一个topic,和同一个tag下消费
改进:保证投递到同一queue
消息积压修复consumer的问题,新建临时的topic,扩容节点
消息顺序发送RocketMQ通过轮询随机/hash的MessageQueueSelector的所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。比如订单号相同的消息会通过MessageQueueSelector被先后发送到同一个队列中,然后通过订单号确认是同一个队列
MQ事务消息com.alibaba.rocketmq.example.transaction.TransactionProducer
直接基于 MQ 来实现事务,不再用本地的消息表。RocketMQ相比Kafka支持事务消息
1、发送方给 Broker 发送Prepared消息(半消息),发送成功后发送方再执行本地事务
2、根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令,
3、RocketMQ 的发送方会提供一个反查事务状态接口,半消息没有收到任何操作请求,我们需要定义好事务反查接口,根据业务状态确定事务COMMIT, ROLLBACK, 还是继续 UNKNOW。如果是待确认就定时轮询重试
- 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
- 生成订单成功,发消息成功,但是本地订单创建失败
改进:
1、利用本地事务,在创建订单时,也新增事务表一条记录。此时不发消息。
2、同进程内有一单线程的线程池,不断查询事务表,查到事务记录,将事务记录,发送到mq中,传递给下游消息。
或者:用本地事务监听器检查mq消息,入库查询订单记录,判断是否回滚。
3、监听mq的回执,收到回执后删除本地事务表记录。
4、避免重复提交
TCC解决分布式事务业务解析:
1、Try:生成订单,给订单一个初始状态:pending;预操作,库存状态改为冻结。其中夹杂其他业务逻辑,如库存校验等
2、/confirm/i:更新订单状态,扣减库存,发放积分
3、Cancel:更新订单状态取消,还原库存,移除预发积分
- 本地消息表:有后台任务定时去查看未完成的消息,然后调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。
- 事务消息:当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了会一直重试,最后进入死信队列。
#{parameterName}引用参数,Mybatis会**把这个参数认为是一个字符串,并自动加上引号。
简单说**#{}是经过预编译的,是安全的**。可以将恶意参数当作占位符,防止sql注入
而**${}**是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。
模糊查询Like,在sql语句中拼接通配符
mci_gy_id LIKE concat('%',#mciGyId#,'%')
shardingjdbc
1、配置datasource、连接池、双主库8从库
2、分片策略:标准分片策略、复合分片策略、行表达式分片策略、Hint分片策略、不分片策略
3、分片算法:精确分片算法(分片键 =与in)、范围分片算法(分片键 between and)、复合分片算法、Hint分片算法
分库分表策略、分片规则、精确分片算法
配置分片策略、分表规则
3、指定自增主键为user_id,不指定默认为雪花算法
负载均衡策略(两种):1.round_robin(默认)循环 2.RANDOM 随机
注意:数据分片+多主多从负载均衡方式由于涉及到数据分片,而逻辑SQ在改写过程中会生成多个物理SQL,shardingSphere采用异步执行得方式并行处理,故负载均衡维度在表级别,而不是数据库级别。
Kafkakafka的一个基本架构:多个broker组成,一个broker是一个节点;你创建一个topic,这个topic可以划分成多个partition,每个partition可以存在于不同的broker上面,每个partition存放一部分数据。kafka在0.8之后,每个partition的数据都会同步到其他机器上,所有的副本会选举一个leader出来
kafka性能高的原因
1、PageCache文件,页缓存优化I/O
2、零拷技术减少拷贝次数
3、批量量处理。合并小的请求,然后以流的方式进行交互
4、Pull 拉模式。使用拉模式进行消息的获取消费
kafka有一个叫做offset的概念,就是每个消息写进去,都有一个offset代表他的序号,然后consumer消费了数据之后,每隔一段时间,会把自己消费过的消息的offset提交一下
dubbo使用zookeeper注册中心暴露服务地址,生成远程服务代理,可以像使用本地bean一样使用demoServicezk
zk在分布式系统中,对应用提供一致性保证,分布式选主,通过各种机制,对应用进行协调,从而使分布式系统对外提供某种特定的服务。
对于较低版本的zookeeper服务端,如3.4.x,则需要依赖curator2.x版本,如:2.12.0。如果使用高版本的curator,需要将curator自身依赖的ZooKeeper在maven中exclude掉。 并引入对应的低版本zookeeper客户端。
zk是通过使用curator实现的mutex锁(互斥锁)进行leader竞争。如果获取到的锁就是leader。
如果竞争leader的时候竞争锁失败,则会阻塞,并为上个节点添加watcher。
Curator提供两种方式进行集群选主,分别为:
- LeaderLatch方式
- LeaderElection方式
**LeaderLatch **通过 leaderLatch.start() 开启leader选举之后,我们需要调用 leaderLatch.await() 。如果当前的客户端未成为leader,则会进行等待,(内部源码实现是通过Object.wait()进行阻塞) 直到成为leader后,当前客户端线程会被唤醒,成为主节点就会以主节点的身份对外提供文件相关服务,其他非主节点阻塞。
LeaderElection需要建立一个LeaderSelector实例,它接收Curatorframework实例、选举节点、以及一个LeaderSelectorListener Leader选举监听器。然后实现LeaderSelectorListener的回调方法:
- takeLeadership回调中需要开发者实现当成为leader之后的业务逻辑。当一个客户端成为leader之后,便会回调takeLeadership方法,执行leader角色的业务逻辑
- stateChanged方法需要开发者实现当连接状态发生变化之后的业务逻辑。比如:我们可以直接抛出异常,阻止leader业务逻辑继续进行。待另外的节点成为leader后执行takeLeadership方法
``
LeaderSelector leaderSelector = new LeaderSelector(
client,
election,
new LeaderSelectorListener() {
@Override
public void takeLeadership(Curatorframework curatorframework) throws Exception {
System.out.println("你已经成为leader");
// 在 这里干leader的所有事情,此时方法不能退出
Thread.sleep(Integer.MAX_VALUE);
}
@Override
public void stateChanged(Curatorframework curatorframework, ConnectionState connectionState) {
System.out.println("你已经不是leader,链接状态发生变化,connectionState" + connectionState);
if (connectionState.equals(ConnectionState.LOST)) {
throw new CancelLeadershipException();
}
}
});
zk节点类型
zk实现分布式锁
zk实现服务发现
与dubbo搭配使用,
zk节点类型
临时和持久
springcloud eureka服务注册实现
ribbonHTTP和TCP的客户端负载均衡工具
调用负载均衡:
1、服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
2、服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
hytrix feignfeign+hystrix 在服务的消费方实现服务熔断
nacoszuul 网关 token token粒度



