java面试题整理(技术积累)每次找工作前必备八股文
2025-10-28 09:15:08 0 举报
AI智能生成
每次找工作前必备八股文
作者其他创作
大纲/内容
DTO/VO/POJO区别
DTO(Data Transfer Object)数据传输对象
entity
Entity是数据表对应到实体类的映射,也就是DAO层表与类的字段映射
VO或者说model
Model是MVC中一个概念,Model是一个高度优化组合或者精简后的一个用于在View层展示数据的对象
与Entity不一定进行一一对应,展示在View层中数据可能是一个Entity的精简,也可能是多个Entity的组合
与Entity不一定进行一一对应,展示在View层中数据可能是一个Entity的精简,也可能是多个Entity的组合
POJO
简单Java对象,普通的JavaBean
除了属性和get、set方法外不包含具体的业务逻辑方法
和Entity区别在于没有和数据表中字段一一对应
除了属性和get、set方法外不包含具体的业务逻辑方法
和Entity区别在于没有和数据表中字段一一对应
DP算法与数据结构
复杂度
时间复杂度
随着数据量的增加和时间花费的增加
需要花费的时间是多少
需要花费的时间是多少
O(1) hashMap
O(logN)二叉树
O(N) 线性的,如for循环
O(N²)for循环嵌套一个for循环
空间复杂度
当前算法需要占用多少内存空间
线性表
List 数组
链表
LinkedList
1、永远记住node的next节点位置
2、哨兵节点,当头节点被删除,哨兵节点可以指向第二个节点
ConcurrentHashMap
数组+链表+红黑树
hashmap-线程不安全,hashtable线程安全,但是性能很低
ConcurrentHashMap对数组初始化进行了CAS无锁化的机制
spring
springboot
springboot
spring的生命周期
Spring装配Bean的过程:
1. 实例化;
2. 设置属性值;
3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;
4. 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;
5. 如果实现ApplicationContextAware接口,调用setApplicationContext设置ApplicationContext
6. 调用BeanPostProcessor的预先初始化⽅法;
7. 调用InitializingBean的afterPropertiesSet()方法;
8. 调用定制init-method⽅法;
9. 调用BeanPostProcessor的后初始化方法;
2. 调用定制的destroy-method⽅方法
1. 实例化;
2. 设置属性值;
3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;
4. 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;
5. 如果实现ApplicationContextAware接口,调用setApplicationContext设置ApplicationContext
6. 调用BeanPostProcessor的预先初始化⽅法;
7. 调用InitializingBean的afterPropertiesSet()方法;
8. 调用定制init-method⽅法;
9. 调用BeanPostProcessor的后初始化方法;
Spring容器器关闭过程:
2. 调用定制的destroy-method⽅方法
springmvc请求处理流程
1、客户端发送请求,web服务器将其解析,如果匹配dispatcherServlet的请求映射路径,web容器将请求转发给前端控制器
2、根据handlermapping找到对应的处理请求的处理器handler
3、处理器去对 handler进行具体的调用
4、处理器返回逻辑视图ModerAndView对象给DispatcherServlet
5、DispatcherServler通过ViewResolver视图解析器转为正式视图
6、Dispatch通过model解析出modelandview中的参数最终展现出完整的view并返回给客户端
springboot的运行流程
1、准备阶段
1.1.判定web容器类型
由于spring-boot-starter-web依赖引入了tomcat(包括了javaee规范)和spring-web,所以会自动选择SERVLET模式
1.2.加载BootstrapRegistryInitializer
加载根文件spring.factories的注册器
加载根文件spring.factories的注册器
getSpringFactoriesInstances方法:加载BootStrapper类的实现类的逻辑,是使用SpringFactoriesLoader完成,
该类会将类路径下所有 resources/META-INF/spring.factories文件内key为BootStrapper接口的值都加载出来作为该接口的实现类列表
该类会将类路径下所有 resources/META-INF/spring.factories文件内key为BootStrapper接口的值都加载出来作为该接口的实现类列表
createSpringFactoriesInstances方法:通过反射完成
1.3.加载ApplicationContextInitializer
加载上下文初始化器
获取ApplicationContextInitializer接口实现类并创建实例对象,赋值给initializers成员变量
获取ApplicationContextInitializer接口实现类并创建实例对象,赋值给initializers成员变量
1.4.加载ApplicationListener
获取ApplicationListener接口接口实现类并创建实例对象,赋值给listeners成员变量
1.5.获取Main方法所在类
2.运行阶段
SpringApplication类的构造方法看完后,
我们就来看一下他的run方法
SpringApplication类的构造方法看完后,
我们就来看一下他的run方法
2.1.创建BootStrapContext
2.2.设置headless模式
通过配置java.awt.headless属性(这里默认为true)为true
可在没有外设(显示器,鼠标等)硬件支持时也可以通过系统模拟出这些相关功能
可在没有外设(显示器,鼠标等)硬件支持时也可以通过系统模拟出这些相关功能
2.3.获取所有监听器
从spring.facotries内找到所有SpringApplicationRunListener接口实现创建实例对象,并将这些对象封装到SpringApplicationRunListeners对象中
初始化事件派发器
后续调用starting方法,派发applcationStartingevent事件
2.4.发送springBoot开始事件,调用第3步
结合上文就可以了解,此方法的作用就是调用所有SpringApplicationRunListener的starting方法
2.5.环境配置整理
主要逻辑有6步:
创建environment对象-->配置属性源-->configurationProperties属性源attach-->触发属性配置事件-->将默认属性源移到末尾-->与sprintApplication绑定
创建environment对象-->配置属性源-->configurationProperties属性源attach-->触发属性配置事件-->将默认属性源移到末尾-->与sprintApplication绑定
2.6.打印banner
2.7.创建ApplicationContext
2.8.初始化配置ApplicationContext
1.准备context
1.将SpringApplication的environment转交给ApplicationContext
2.将SpringApplication的beanNameGenerator,resourceLoader,classLoader,ConversionService转交给ApplicationContext
2.将SpringApplication的beanNameGenerator,resourceLoader,classLoader,ConversionService转交给ApplicationContext
2.初始化context
1.调用所有的ApplicationContextInitializer接口实例(在SpringApplication构造方法中加载的)的initialize方法
2.调用Listeners的contextPrepared方法,发送ApplicationContextInitializedEvent事件
3.关闭boostrapContext
4.打印相关信息
2.调用Listeners的contextPrepared方法,发送ApplicationContextInitializedEvent事件
3.关闭boostrapContext
4.打印相关信息
3.动态注入组件
1.注入参数对象(封装了run方法传过来的参数)到sping工厂
2.注入banner打印器到spring工厂
3.配置工厂是否允许beanName重复(该配置点可以直接通过SpringApplication设置)
4.注入懒加载工厂处理器,如果开启该处理器会将工厂内几乎所有的bean都设置为懒加载(默认关闭)
2.注入banner打印器到spring工厂
3.配置工厂是否允许beanName重复(该配置点可以直接通过SpringApplication设置)
4.注入懒加载工厂处理器,如果开启该处理器会将工厂内几乎所有的bean都设置为懒加载(默认关闭)
4.加载资源
1.获取现有的资源配置类(一般这里只有一个main类)
2.加载资源,其实就是配置类扫描逻辑,扫描配置类上的注解信息(@Component,@ComponentScan,@Configuration等等)
3.调用Listeners的contextLoaded方法,发出ApplicationPreparedEvent事件
2.加载资源,其实就是配置类扫描逻辑,扫描配置类上的注解信息(@Component,@ComponentScan,@Configuration等等)
3.调用Listeners的contextLoaded方法,发出ApplicationPreparedEvent事件
2.9.refreshApplicationContext
我们可以一直跟一下该refresh方法,会发现最终还是会调用到AbstractApplicationContext的refresh方法
这样就和spring的核心流程打通了
这样就和spring的核心流程打通了
2.10.afterRefreshApplicationContext
老套路,留给子类扩展使用
2.11.context启动事件 & runners调用 & 运行事件
springboot源码分析
spring
容器(bean)
1、new->工厂的方式->IOC(生产对象,自动注入)
2、xml和注解的方式,读取到spring容器中
springboot准备工作-调用了spring(refresh方法)
在springboot准备阶段中,有多个监听器(listener)
ApplicationStrartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
ApplicationEnvironmentPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
监听每到一个阶段,下一步要干什么
springboot的运行流程
1、创建一个springApplication对象。调用构造方法
2、初始化操作
①、判断当前的系统类型是web,servlet项目
②、加载所有的初始化器
③、加载所有的监听器
④、设置程序运行的主类
3、执行第一步创建的对象的run方法
1、创建计时器对象并开始计时
2、设置handless的属性并设置到系统属性中
3、初始化监听器
从spring.factoy文件中得到class
getRunlisters()方法初始化
启动准备好的监听器
4、设置换命令行参数
5、准备环境对象
1、创建环境对象
2、加载系统参数
3、设置环境监听器集合
6、打印banner图
7、创建应用程序的上下文
8、准备异常报告器
9、prepareContext准备上下文环境
1、监听配置
2、设置环境对象
3、进行初始化操作
4、load对应的资源
10、refresh调用工程跟spring源码过程一样
很多springboot核心功能都是在此方法中完成
1、自动装配
2、tomcat配置
11、finshRefres留给用户添加扩展使用的
计时器结束
12、发布上下文启动时间
调用runner执行器
13、发布上下文就绪时间
14、springboot启动完成
springboot的启动过程
(精简版本)
(精简版本)
1、通过springFactoryLoader加载springboot包下的META-INF/spring.factories文件
获取并创建springapplicationRunLister对象
获取并创建springapplicationRunLister对象
2、然后通过springapplicationRunlistenr发出staring的消息
3、创建参数,并配置当前的springboot应用将要使用的env准备完毕的事件
4、创建ApplicationContext,初始化上下文,并设置env然后加载相关配置
5、然后再有监听器发出context准备完毕的事件,
6、将各种bean装在applicationcontext中,继续有springboot监听发出contextloader已经ok
7、refresh applicationContext,完成ioc容器可用最后一步
1、自动装配
2、tomcat配置
8、有监听器发出start事件,完成最终的启动
springCloud
SpringCloud和Dubbo的区别
1、服务调用得到方式
Dubbo是RPC
springcloud是Rest Api
2、注册中心
dubbo用的是zk
springcloud是eureka,也可以是zookeeper
3、服务网关
dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素
Eureka
Eureke的自我保护机制
如果当EurekaServer在一定时间内没有收到某个微服务实例的心跳,那么服务器就会注销改实例,默人为90S,其实这种行为是比较危险的
例如网络分区容错性这个问题,此时Eureka自我保护机制就会启动
当服务器的短时间内丢失过多的客户端时,那么这个节点就会进入自我保护
进入该模式。 eureka server会保护服务注册表中信息,不在删除服务注册表中的数据、当网络故障恢复 server会自动退出自我保护模式、
Eureka和zookeeper的区别
zk保证的是CP,Eureka保证对的是AP
zk服务器端一个挂了,就会不提供服务,需要选举leadder
eureka不会不提供服务,并且可以保证客户端节点是最新的,如果不是,就是因为自我保护机制等导致的
eureka不会不提供服务,并且可以保证客户端节点是最新的,如果不是,就是因为自我保护机制等导致的
Ribbon
Feign
Hystrix
Config
Zuul
Gateway
功能
授权
日志
限流
断言 (Predicate)
自定义断言
AuthRoutePredicateFactory
yml
过滤 Filter
路由层面的RouteFilter
全局过滤器
基于服务的发现LB--服务名
ratelimiter(限流的路由)
23种设计模式
创建型模式
1、单例模式
在类中写private的无参构造
枚举单例
工厂模式-> 一个可以产生对象的方法和类就是工厂
2、工厂方法,里面是生产对象的方法
产品纬度扩展方便
3、抽象工厂
(形容词用接口,名词用抽象类)
(形容词用接口,名词用抽象类)
扩展产品一族方便
4、建造者模式(Builder)
封装一个复杂对象的构建过程,并可以按步骤构造。
简单来说:就是把复杂对象拆分成多个对象,并分别构建并最后返回
同样的对象,可以用不同的构建生成器去构建
5、原型模式(prototype)
(Clone)通过复制现有的实例来创建新的实例。
实现cloneable接口
clone对象内部有其他对象,需要深度clone
结构性模式
6、适配器模式(Adapter)
转换器
电压的转换头等含义
创建一个抽象类,实现一个多个方法的接口,然后把所有方法都重写一个空的实现.别人就可以new这个抽象类,重写关注的接口的方法了
7、桥接模式(Bridge)
双维度扩展
用聚合代替继承
分离抽象与具体实现,让他们可以独自发展
8、组合模式(Composite)
树状结构专用模式
说白了就是跟文件-文件夹的递归遍历
9、装饰模式(Decorator)
动态的给对象添加新的功能。
10、外观模式(facade)-门面模式
由一个门面,去后面统一协调
11、享元模式(共享元数据)
FlyWeight
FlyWeight
重复利用对象
池子化的思想
String用的就是享元模式,常量池
12、代理模式
jdk动态代理,对实现接口的类产生代理
cglib的代理,生成子类的方式
ASM底层实现,直接修改二进制码
行为型模式
13、访问者模式(Visitor)
访问结构固定-然后改变内部结构的模式
应用面不多
14、模板方法模式(钩子函数)TemplateMethod
在父类中定义好抽象的方法,子类继承去实现,,调用父类(模板方法)钩子函数,就会按照父类模板的内容进行调用
15、策略模式(strategy)
一个方法会有不同的策略,就可以抽象出来接口,然后让其他子类去实现
16、状态模式(state)
一个类中的动作,根据类中状态的不同会作出不同的反应
当动作不变,状态可扩展就用state模式
17、观察者模式(Observer)
事件本身
1.被观察者源对象
2.其他参数
2.其他参数
观察者
实现观察者接口
接口中参数传入,事件本身
对象源
维护观察者列表,编写add观察者方法
创建event,并传入观察者中
18、备忘录模式(Memento)
记录状态,便于回滚
save And load
游戏的存盘和载入
19、中介者模式(Mediator)
20、迭代器模式(Iterrator)
一般都用在遍历集合,那个集合需要有返回Iterrator方法
Iterator
集合容器都需要实现Iterrater接口
boolean hasNext()
E next()
21、解释器模式(Intepreter)
解释脚本语言的
不用学,华为工程师估计用的上
22、命令模式(command)
封装命令
接口或者抽象类,含有do和undo方法
doit
undo
23、责任链模式
(Chain of Responsibility)
(Chain of Responsibility)
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推
6大设计原则
单一职责原则
开闭原则
里氏替换原则
子类可以透明的替换父类
面向接口编程
接口的职责要单一
高内聚 低耦合
集合容器
Collection
List
Vector自带锁
Stack
读写都加锁
LinkedList()
ArrayList
都不加锁
CopyOnWriteArryaList
写时候复制
因为一个容器读多写少,不上锁,写的时候重新复制了1个,然后上锁写,最后把引用重新指向新拷贝出来的容器
Set
不允许重复
不允许重复
HashSet
LinkedHashSet
SortedSet
TreeSet
EnumSet
CopyOnWriteArraySet
ConcurrentSkipListSet
Queue
多线程,单个元素优先考虑Queue
Queue和list的区别
queue添加了对线程友好的API,
offer,peek,poll
put,take 阻塞
多线程,单个元素优先考虑Queue
Queue和list的区别
queue添加了对线程友好的API,
offer,peek,poll
put,take 阻塞
offer 添加,返回布尔值
peek取。但不删除
poll 取,并删除
peek取。但不删除
poll 取,并删除
Deque
ArrayDeque
BlockingDeque
LinkedBlockingDeque
BlockingQueue
(线程阻塞)
(线程阻塞)
ArrayBlockQueue
有界的
有界的
可以指定容器的大小,满了,程序就阻塞了
PriorityBlockingQueue
LinkedBlockQueue
无界的
无界的
天生的生产者消费者模型
生产:put
消费:take
消费:take
TransferQueue
LinkedTransferQueue
多了个transfer方法,放进去以后,必须有人消费才会结束阻塞
SynchronousQueue
2个线程交换数据
2个线程交换数据
容量为0,传递东西的
DelayQueue
时间上排序
时间上排序
按时间任务调度
ConcurrentLinkedQueue(无锁化CAS)
PriorityQueue
内部会自己排序
比如abc..z
1,12,13,2,23,4,5,
Map
Vector 和HashTable
方法都加锁Sync,基本不用
HashMap
方法都没有加Sync
ConcurrentHashMap
效率:
插入的效率 不如 HashTable(线程安全,竞争激烈时候效率低下)和SyncHashMap
读的效率很高
插入的效率 不如 HashTable(线程安全,竞争激烈时候效率低下)和SyncHashMap
读的效率很高
并发集合ConcurrentHashMap,将Map划分成几个片段,只对几个相关的片段上锁,同时允许多线程访问其他未上锁的片段
Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。
应该根据具体的应用场景选择合适的HashMap。
ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。
应该根据具体的应用场景选择合适的HashMap。
TreeMap
(排序)红黑树
查询效率较高
没有出ConcurrentTreeMap
但是有ConcurrentSkipListMap
SyncHahsMap
可以让没有锁的HashMap变为有锁
WeakHashMap
identityHashMap
JVM
JVM调优
堆大小设置
-Xmx
jvm的最大可用内存
-Xms
JVM的初始内存,一般和xmx设置的相同,避免每次垃圾回收完成后,重新分配内存
-Xmn
设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss
-Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,以前每个线程栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m
持久代一般固定大小为64m
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
回收器选择
并行收集器(吞吐量优先)
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集
-XX:MaxGCPauseMillis=100: (毫秒)设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
并发收集器(响应时间优先)
并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:CMSFullGCsBeforeCompaction=5:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
垃圾回收统计信息
-XX:+PrintGC
输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
与上面几个配合使用,把相关日志信息记录到文件以便分析
调优总结
1、年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
2、老年代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
3、较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
2、老年代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
3、较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
GCl垃圾回收算法
分代算法
方案一
ParNew
CMS
初始标记-Stop-the-world
并发标记
重新标记-Stop-the-world
并发清理
方案二
Serial
SerialOld
方案三-1.8默认使用
Parallel Scavenge
Parallel Old
逻辑分代,物理不分
查看参数
java -XX:+PrintFlagsFinal -version
查看当前系统使用的GC等参数
java -XX:+PrintCommandLineFlags -version
jps
查看java的所有线程的线程号
jinfo 线程id
查看这个java程序的一些内容
jstas -gc 线程id 1000
1000毫秒刷新一次
jstatck 线程id |more
判断CPU过高,使用这个命令多
第一件事儿找到那个线程使用cpu过高
jmap
类似与 arthsa里面的heapdump命令,但是可以直接分析
jmap -histo 线程id |head 20
生产也不能用,stw 暂停
arthas-3.1.4
阿里开源的linux的
dashboard
可以看内存的使用情况,线程的cpu的使用情况
jvm
jvm启动的参数,用了哪些垃圾回收器
thread
线程使用情况,例如cpu的情况
thread 线程号 就可以看调用的那些方法
thread -b 就可以找到那些线程死锁
sc 包名
全名search class
搜索jvm加载的哪些类
sm 类名
全名search method
trace 类 方法
我们对某个类的某个方法有怀疑
heapdump
堆是停的,跟STW一样,不建议使用
会导出堆内存的使用情况的hprof文件
jad
在线反编译
看看代码是不是不对
多线程
CAS
Java线程具有五中基本状态
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 — 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 — 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 — 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 — 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
CAS的概念
严格来讲有2个参数
cas(expected,update)
cas(期望值,更新值)
ABA问题
加version版本号,每次操作版本号+1
int类型不担心ABA,但是对象引用的话,还是会出现问题
LongAdder
AtomicLong
long上sync
AtomicLong
long上sync
效率在很高很高的并发下,依次递增的算法,效率从上到下
什么是线程
一个程序中不同的执行路径就是一个线程
切记是调用start()方法而不是run
切记是调用start()方法而不是run
线程的几个方法
sleep()
睡眠一段时间让给别的进程去执行
到睡眠时间了,自动复活,进入到就绪状态
yield()
礼让一下,进入到等待队列中(返回就绪状态)
本质就是让出一下cpu。但是随时会拿回来
join()
只能join其他线程,让他先运行,等他运行完了。自己再运行
经常用来等待另一个线程的结束
锁
锁的种类
多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!
so,不使用线程锁, 可能导致错误
so,不使用线程锁, 可能导致错误
进程锁:也是为了控制同一操作系统中多个进程访问一个共享资源,只是因为程序的独立性,各个进程是无法控制其他进程对资源的访问的,但是可以使用本地系统的信号量控制
分布式锁:当多个进程不在同一个系统之中时,使用分布式锁控制多个进程对资源的访问。
JUC各种同步锁
(可重入锁)ReentrantLock
(完全互斥)-》互斥锁、排它锁
(完全互斥)-》互斥锁、排它锁
相当于Sync
try{
lock
}finally{
unlock
}
lock
}finally{
unlock
}
trylock(给时间尝试获取锁)
还有打断上锁-lockinterupptibly
公平和非公平概念,sync只有非公平
(倒数门栓)countDownLatch
new CountDownLatch(100次)
latch.await();//阻塞。门栓100次倒数没有了,发车
countDownLatch.countDown();// 减少等待的线程个数
latch.await();//阻塞。门栓100次倒数没有了,发车
countDownLatch.countDown();// 减少等待的线程个数
(栅栏)CyclicBarrier
人数满了 推到,然后在起来
人数满了 推到,然后在起来
创建栅栏,到10个线程就发车,并触发Runable方法
CyclicBarrier cb = new CyclicBarrier(10, new Runnable() {
CyclicBarrier cb = new CyclicBarrier(10, new Runnable() {
每个线程调用cb.await(),线程就阻塞等着,到10个线程才往后执行
(读写锁)ReadWriteLock
---读是共享锁,写是排它锁
---读是共享锁,写是排它锁
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLocklock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
static Lock readLocklock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
(信号量“信号灯”)Semaphore
--做限流使用
--做限流使用
灯亮着可以执行,没灯了不可以执行
new Semaphore(1);
semaphore.availablePermits(); 有几个可以用的灯
s.acquire()取得一盏灯,将数字-1
s.release()还回一盏灯,将数字+1
(两两交换)Exchanger
适合生产者消费者模式
适合生产者消费者模式
Exchanger.exchange(交换的数据);
Exchanger.exchange(data);---生产者
Exchanger.exchange(null);--消费者
Exchanger.exchange(null);--消费者
(阶段)phase
1、可以栅栏等待线程的个数
2、也可以控制等待线程阶段
1、可以栅栏等待线程的个数
2、也可以控制等待线程阶段
如果程序中遇到分好几个阶段执行,并且有的阶段需要多个线程共同参与的情况下可能会用到Phase。
自定义一个MarriagePhaser 继承 Phaser 重写Phaser的onAdvance方法定义了4个阶段(进入下一个阶段时该方法被自动调用)。
阶段必须从0开始。onAdvice的两个参数 phase是第几个阶段,registeredParties是目前有几个已注册线程参加。
(onAdvice)在栅栏推到以后自动调用
最后返回值为false表示流程未结束,继续执行下一阶段,返回true表示流程结束。
阶段必须从0开始。onAdvice的两个参数 phase是第几个阶段,registeredParties是目前有几个已注册线程参加。
(onAdvice)在栅栏推到以后自动调用
最后返回值为false表示流程未结束,继续执行下一阶段,返回true表示流程结束。
常用方法:
1、phaser.bulkRegister(num).定义注册线程的数量,比如婚礼的总参加人数
2、继承Phaser,重写onAdvance方法
3、phaser.arriveAndAwaitAdvance()完成某阶段 推到继续前进
3、取消注册,arriveAndDeregister()
1、phaser.bulkRegister(num).定义注册线程的数量,比如婚礼的总参加人数
2、继承Phaser,重写onAdvance方法
3、phaser.arriveAndAwaitAdvance()完成某阶段 推到继续前进
3、取消注册,arriveAndDeregister()
LockSupport
lockSupport.park()
想什么时候停就什么时候停,不用sync让对象进入wait
lockSupport.unpark(叫醒的线程)
unpark可以在park之前调用
AQS(JUC包下同步锁的实现)
模板方法。父类制定了模板,具体的实现都是子类去实现
callback function
callback function
关键:volatle int state--0解锁,大于0加锁
监控state的node双向链表,node里面装的是线程
varhandle
普通对象的成员变量也可以进行CAS操作
比反射快,直接修改二进制码
threadLocal
threadLocal的set方法,是获取当前线程,然后放到当前线程的map中
做个不恰当的比喻,从表面上看ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。
用途:比如spring的声明式事务,多个数据库连接都从ThreadLocal中拿
synchronized
可以加在一段代码上,也可以加在方法上
(锁的是对象,obj,不能string int)
如果对象上锁,对象属性改变不影响锁,但是如果对象被改变会影响,也就是对象头上的锁换位置了
所以对象给一个final
(锁的是对象,obj,不能string int)
如果对象上锁,对象属性改变不影响锁,但是如果对象被改变会影响,也就是对象头上的锁换位置了
所以对象给一个final
synchronized和ReentrantLock的区别
synchronized是关键字
ReentrankLock是类
(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
(2)ReentrantLock可以获取各种锁的信息
(3)ReentrantLock可以灵活地实现多路通知
lock.condition 指定线程的条件,本质就是不同的等待队列
(2)ReentrantLock可以获取各种锁的信息
(3)ReentrantLock可以灵活地实现多路通知
lock.condition 指定线程的条件,本质就是不同的等待队列
二者的锁机制其实也是不一样的。ReentrantLock底层是CAS调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word
锁的升级同步
自旋锁默认旋转10次,10次后就升级为重量级锁
执行时间短。线程少,用自旋锁
自旋锁占用cpu时间
执行时间长,线程多。用重量级锁
不占用cpu时间,去内核队列排队
程序之中如果出现异常,会释放锁,那其他线程拿到锁(乱入),会出现数据的不一致
一定要捕获异常,否则其他线程会拿到锁,继续执行
对象在内存中的存储布局
java对象头
markworld
类型指针
Class Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
实例数据
存储的是对象的属性信息,包括父类的属性信息,按照4字节对齐
字节对齐,要求是8的倍数
底层实现
每个对象都有监视器锁,当moniter被占用的时候,对象进入锁状态
线程通过monitorenter指令尝试获取monitor所有权
1、如果monitor的进入数为0,则该线程进入,进入数设置为1,线程占有monitor
2、如果该线程已经占有了monitor,只是重新进入,则进入数+1
3、如果已经有别的线程占有了monitor,则该线程进入阻塞状态,只到monitor的进入数为0,再重新尝试获取monitor
2、如果该线程已经占有了monitor,只是重新进入,则进入数+1
3、如果已经有别的线程占有了monitor,则该线程进入阻塞状态,只到monitor的进入数为0,再重新尝试获取monitor
monitorexit
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
锁的升级、降级:就是JVM优化synchronized运行的机制,当jvm检测到不同的竞争状况时,会自动的切换到合适的锁实现
偏向锁:
当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS操作,在对象头上的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销
--------其他线程想获取锁,那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
--------其他线程想获取锁,那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
偏向锁的取消:
偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;
如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;
----》用于常有竞争锁存在的场景
偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;
如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;
----》用于常有竞争锁存在的场景
自旋锁:
为什么要引入轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。
有竞争出现时,当有另外的线程试图锁定某个已经被偏斜锁锁定的对象,jvm就会撤销revoke偏斜锁,并切换到轻量级锁。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果成功,就使用轻量级锁,否则继续升级未重量级锁
重量级锁:(系统锁)
自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转
注意:一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态
PS:锁降级也是存在的,当JVM进入SafePoint安全点的时候,会检查是否有闲置的Monitor,然后试图进行降级。
synchronized方法内部可以掉用其他的synchronized方法,锁是可以重入的
volatile
(可变的异变的,容易产生改变你的)
(可变的异变的,容易产生改变你的)
保证线程可见性
(线程之间运行在不同的cpu上)
(线程之间运行在不同的cpu上)
用了cpu缓存一致性协议
防止指令重排序
双重检测锁,还是要加volatile
(单例模式,判断2次对象是否被初始化)
(单例模式,判断2次对象是否被初始化)
1、先申请内存
2、调用instans初始化
3、赋值
2、调用instans初始化
3、赋值
不加的话 可能会拿到第一步初始化的值而不是被赋值过的结果
四种线程池的创建
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
线程的生命周期不用管了
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数
newCachedThreadPool创建一个可缓存线程池
弹性的,来一个启动一个
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
阿里建议自定义线程池:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Future和callable
callbale
有返回值,但是需要另一个future去接
future
未来返回的结果,保存了callable的返回值
future.get()是阻塞的
可以等所有任务执行完毕后,主线程在继续执行,比如多线程计算任务
FutureTask
是任务又是future
CompltetableFuture
可以管理多个future
netty
Redis
计算机文件的查找
磁盘:
1、寻址 毫秒级
2、带宽,几百兆或者几G
1、寻址 毫秒级
2、带宽,几百兆或者几G
内存:
1、寻址,比磁盘快十万倍
1、寻址,比磁盘快十万倍
IO/buffer问题
1、磁盘的扇区,一个扇区是512byte,如果数据都是1个小扇区那么大,那么数据量大了以后,
索引也会很多,才能定位到这些小扇区(小格子),成本会变大
1、磁盘的扇区,一个扇区是512byte,如果数据都是1个小扇区那么大,那么数据量大了以后,
索引也会很多,才能定位到这些小扇区(小格子),成本会变大
磁盘格式:无论读多少,最少都是4k从磁盘读取
关系型数据库:
行级存储,创建数据表的时候,要指定schema
schema,指定数据的类型,和数据的宽度
宽度有了,就算不放数据,也会开辟这个空间,并且此行数据占位一行。
索引:为了查询快,会单独开辟一个空间去存储索引,然后根据查询索引去-》指向dataPage
B+树(在内存中)树干在内存中,所有的区间和偏移
索引和数据都在磁盘中
问题》:如果数据库行很多,很大,性能是否会下降
1、增删改会变慢,因为会动索引,移动位置
2、查询看情况
1、1个人或者简单的sql语句进来,并且很快命中索引,依然很快
2、高并发上完人进来,索引命中耗时久,并且带宽不够,需要排队一次将数据load到内存中
HANA数据库Sap公司,太贵买不起
才出现了缓存
memcache
key-value结构,但是value没有类型概念
当客户端要从value中拿到一个数组中的某一个数据就很不方便
而且网络iO了很多不必要的数据,然后客户端还要实现自己的取某一个下标数据的代码
redis-》》》计算向数据移动
IO的演变过程
1、BIO阻塞IO模型
每次连接都抛出一个线程,去连接内核,等着内核返回,阻塞,效率很低
假如1000个连接就有一个线程,1000个文件描述符
堆栈,
堆的空间共享
栈空间独立,一个线程在JVM中栈空间默认1M
1000个线程就占用了1g空间,并且CPU光去内核调度了,然后内存空间光线程都占了不少,堆都没地方NEW对象了
零拷贝
用户态和内核态共享空间,用的是操作系统内核mmap
零拷贝
0拷贝用的是系统内核调用的sendfile
0拷贝读写都在内核直接完成,不需要拷贝给用户态空间中
2、NIO(同步非阻塞)
一个线程持有了1000个线程,然后玩命轮训去询问内核1000次是否有我事儿了,这样内核很辛苦
3、多路复用,同步非阻塞
1个线程去调用内核的系统调用,select()
select属于内核态空间,内核自己去轮训后返回给你几个可用的,然后用户态拿到以后自己去进行读写拷贝
4、EPOLL(多路复用器)也还是NIO,需要自己去read和write
epool-create-》往epooll维护的共享空间的红黑树中放socket文件描述符
返回一个epool的文件描述符
epoll会创建一个内核和用户态的共享空间
epool-wait
epool去询问内核,然后返回可用的到共享空间的维护的链表队列中,
用户态调用wait去队列中获取可用的fd,然后自己进行读写
IO
BIO
BLockIO,一种阻塞IO,当IO量大,会造成阻塞,影响线程的效率,已经淘汰
kernel操作系统内核
客户端需要和内核tcp三次握手
socket()=6fd文件描述符
bind(6fd,9090)绑定这个文件描述符
accept(6fd)==》7fd打开监听,监听到客户端7号文件描述符
NIO
非阻塞的IO
操作系统kernel内核允许 accept不会阻塞
代码
问题:连接的Client多的时候 循环遍历的时候就更加费时间
如果每个客户端是一条路,那每条路就要自己去看一眼
多路复用器selecter
select(文件描述符。。。。。),监听这这么多个
R/W都是自己出发,读写是同步的
询问系统是不是有accept或者read事件
kernel系统内核
redis单节点,单线程
客户端访问redis——tcp握手
通过内核的EpoLL(非阻塞多路复用)
REDIS五个数据类型
String
String
SET KEY 过期时间 NX|XX
GET KEY
help @string
自己去查
msetnx
原子性,要不批量都成功,要不失败
int
有+1 -1等操作
bitmap
bitpos k1 1 1 1
BITPOS key bit [start] [end]
返回9
因为是查找1 在第1个字节中第一次出现的位置
bitcout
BITCOUNT key [start end]
summary: Count set bits in a string
summary: Count set bits in a string
统计字节偏移量中 1出现了几次
setbit 20190101 1 1
setbit 20190102 1 1
setbit 20190102 7 1
bitop or destkey 20190101 20190102
bitcount destkey 0 -1
setbit 20190102 1 1
setbit 20190102 7 1
bitop or destkey 20190101 20190102
bitcount destkey 0 -1
00
List
栈,同向命令
Lpush
Lpop
队列,先进先出,反向命令
Lpush
Rpop
BRpop 如果没有就阻塞
数组,有索引的操作
阻塞,单发布订阅队列FIFO
底层数据结构使用的是ziplist
双端队列,可以支持头插法和尾插发
Hash
有计算功能
点赞,详情页,收藏
有序,但是不去重
散列键——好处
1、key为表名 filed为id:字段 value为值
2、减少key的创建,减少key管理时候的io操作
1、key为表名 filed为id:字段 value为值
2、减少key的创建,减少key管理时候的io操作
购物车的实现
hincrby {userid}:shoppingCar
{goodsId}
{count}
Set
无序去重
集合操作-交集差集并集
SRANDMEMBER key count
count为正数:抽出来的数量一定不会重复,也不会大于集合容量
抽奖
count为负数,会重复,并一定满足这个负数的数量
家庭纷争
Spop
抽出就删除了
sorted_set
排序并且会去重
物理内存---分值左小右大
(不随着命令发生变化)
(不随着命令发生变化)
skipList牺牲空间换时间
"store:1:20220209"
"store:1:20220209"
"store:1:20220209"
用交集求此商店2天的营业额的交集,并保存到store:1:8to9中
ZUNIONSTORE store:1:8to9 2(key的个数) store:1:20220209 store:1:20220208
ZUNIONSTORE store:1:8to9 2(key的个数) store:1:20220209 store:1:20220208
布隆过滤器--根据bulong算法,它内部维护一个bitmap,一个产品会根据布隆算法,将bitmap对应算出来的位换为1
1、BF.add() 告诉布隆有什么产品
2、BF.exitx()
检查时候命中
client会先去查key,然后查布隆,命中后会查数据库,但是如果没有了,自己维护这个key返回null
内存不够了
回收策略
LRU
用的次数少的
LFU
好久没用的
volatile
在含有过期时间的key中回收
一般过期时间使用多的key的场景选这个
allkeys
所有key中满足LRU或者LFU的key被回收
过期时间
如果设置了TTL了,查询不会改变
如果set操作了 会移除过期时间功能
可以倒计时,且redis不可以延长
也可以倒计时
过期时间判定原理
主动访问这个过期key的时候返回nil。然后服务端去删除这个key
被动访问,随机访问20个key,超过5个超时,就再次轮序访问20个
reids常见问题汇总以及处理方案
1、缓存的击穿
2、缓存的穿透
3、缓存的雪崩
4、缓存的一致性(双写)
持久化策略
RDB
利用liunx的父子进程,父进程去export
AOF
增量日志
zookeeper
为分布式系统提供一致性服务,其一致性主要通过Paxos算法的ZAB协议
Paxos算法分2个阶段
准备阶段
发送提案编号
accept会拿着跟着记得maxN(最大提案编号)对比(如果没接受过提议就直接同意并保存这个提案编号)
选举阶段
会再次那所有人的提案号和maxN对比,如果半数同意,直接选为Leader,其他选举直接结束
ZAB协议
原子消息广播协议
K集群中事务处理是leader负责,follower会转发到leader来统一处理。简单理解就是ZK的写统一leader来做,读可以follower处理,这也就是CAP理论中ZK更适合读多写少的服务。
三类角色
Leader处理写请求
Follower处理读请求,并可以投票写请求
ObServer帮助Follower处理读请求
三种模式
恢复模式
当Leader崩溃的时候,进入恢复模式,要恢复到zk集群正常的工作状态
恢复模式的2个原则
1、已被处理过的消息不能丢
2、被丢弃的消息不能再次出现
同步模式
当Leader崩溃或者被选举出来以后,所有的flowwer会将主机的数据同步到自己的主机中,当完成同步,恢复模式也就结束了
ZID
为64为长度的long类型
高32位是纪元,epoch
即每一个leader选出来的时候,都会更新一个朝代,并通知给所有zkServer
低32为是xid
xid为事务id,也就是写id,所以叫xid,每一个写操作都需要leader发出一个提案,每个follower需要表决是否同意此次写操作
每个提案都有一个xid
消息广播算法
当集群中已经有过半的follower与leader完成了同步状态,zk集群就可以进入广播模式了
leader接收到写请求以后,会生产一个唯一的64位id,并放到fifo的队列中保证顺序
当follower接受提案会拿到zxid和本地的zxid值对比,如果大于,就将zxid记录到本地事务中并返回ack
当leader接收到过半的ack以后,会往follwer发送commit消息,批准这些follwer本地执行该消息
zk状态
Looking
选举状态-查找leader的状态
followering
跟随状态,同步leader状态,此状的的角色为follower
observing
观察状态,同步leader状态,此状态的角色为observer
leadering
此状态的服务器为leader
leader选举算法
集群启动中的leader选举(集群每个节点都没有数据,以SID的大小为准)
1台机器启动,无法投票选举
第2台机器启动,第二台服务器Server2启动,此时2台机器可以相互通信,进去leader选举过程
1、每个Server投票,因初始状态都会发送自己的myid和zxid,此时 S1投票为(1,0)S2投票为(2,0)然后将投票发送给其他机器
2、所有机器接收到投票后先判断改选票的有效性,例如检测是否是本轮投票,是否来自LOOKING状态的服务器
3、处理投票,服务都需要将别人的投票和自己的进行PK
优先检查ZXID,ZXID较大的服务器优先为leader(这个很重要:是数据最新原则,保证数据的完整性)
如过ZXID相同就比较myid,myid较大的就作为leader,myid就是服务器的标识
对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0。再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
4.统计投票
每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。
5、改变服务器状态
一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。
断连后的leader选举(集群的每个节点都有数据,或者leader宕机->以ZXID和SID的最大值为准)
在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致
假设正在运行的有Server1、Server2、Server3三台服务器,当前Leader是Server2,若某一时刻Leader挂了,此时便开始Leader选举
1、变更状态
leader挂后,余下非Observer的服务器都会将自己的服务器状态改为LOOKING,开始选举
2、每个Server会发出1个投票
在运行期间每台服务器的ZXID可能不同,此时假定S1的ZXID为123(1,,123),S3的ZXID为122(3,122)
在第一轮投票中,S1和S3都会投给自己(1,,123),(3,122)
3、接受来自各个服务器的投票
首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
4.处理投票
针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK
S1收到了其他服务器(3,122)和自己的(1,,123)对比,发现自己的123大,不改变投票
S3收到了(1,123)和自己的(3,122)对比,发现小,更新自己投票,发出新的投票为(1、123)
5、统计投票
每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server3而言,都统计出集群中已经有两台机器接受了(1, 123)的投票信息,此时便认为S1是leader,并且已经选出了Leader。
6、改变服务器的状态
一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。
HotSpot
引用类型
强
Obeject 0 = new Object();
o就会指向内存的new Object ,当o=null ,才能被GC
软
当内存不够用的时候,软引用会被垃圾回收
做缓存用
(如大图片,数据库)-》当内存不够用的时候,就把缓存干掉,下次拿就去数据库拿
(如大图片,数据库)-》当内存不够用的时候,就把缓存干掉,下次拿就去数据库拿
弱
weakRenfence
ThreadLocal
set的时候,Map的key是弱引用,防止内存泄漏
虚
当对象呗回收的时候,虚引用的队列会检测到,就可以去回收对外内存了
堆外内存就是Netty使用的,又叫直接内存
java在内存中的布局
class类通过类加载机器加载到内存中
线程共享的
Heap堆内存:也是 GC 垃圾回收的主站场,用于存放类的实例对象及 Arrays 实例等。
方法区
主要存放类结构、类成员定义,static 静态成员等。
Runtime Constant Pool:运行时常量池,比如:字符串,int -128~127 范围的值等,它是 Method Area 中的一部分。
线程独有的(程序计数器,JVM栈,本地方法栈)
程序计数器:记录每个线程当前执行的指令信。eg:当前执行到哪一条指令,下一条该取哪条指令。
JVM栈:记录每个栈帧(Frame)中的局部变量、方法返回地址等
线程中每次有方法调用时,会创建Frame,方法调用结束时Frame 销毁。
线程中每次有方法调用时,会创建Frame,方法调用结束时Frame 销毁。
Native Method Stack:本地 (原生) 方法栈,顾名思义就是调用操作系统原生本地方法时,所需要的内存区域。
堆外内存
还有一类不受 JVM 虚拟机管控的内存区,这里也提一下,即:堆外内存。
可以通过 Unsafe 和 NIO 包下的 DirectByteBuffer 来操作堆外内存。如上图,虽然堆外内存不受 JVM 管控,但是堆内存中会持有对它的引用,以便进行 GC。
GC垃圾回收
1、什么是垃圾
原子计数算法
被引用+1,对象引用失效-1
无法解决循环调用问题
可达性分析算法
判断对象的引用链条向下遍历,是否可达GCROOT
虚拟机栈中引用对象
方法区静态属性引用对象
方法区中静态常量引用对象
本地方法栈中JNI引用对象
2、那些内存需要回收
无需GC
(随着线程的创建和销毁)
(随着线程的创建和销毁)
线程独享的JVM栈
本地方法栈
程序计数器
需要GC
静态区---常量池
堆内存中 全局可见的临时对象
垃圾回收算法
1、标记清除法
直接清除要清除的内存,碎片化严重
2、标记复制法
内存左右各一半
将左侧存活的对象(浅灰色区域)复制到右侧,然后左侧全部清空。避免了内存碎片问题,
但是内存浪费很严重,相当于只能使用 50% 的内存。
但是内存浪费很严重,相当于只能使用 50% 的内存。
3、标记整理或标记压缩算法
将垃圾对象清理掉后,同时将剩下的存活对象进行整理挪动(类似于 windows 的磁盘碎片整理),保证它们占用的空间连续,这样就避免了内存碎片问题,但是整理过程也会降低 GC 的效率。
收集器
CMS收集器
1、初始标记
2、并发标记
2.5 并发预清理,minerGC减少年轻代
这样第三步重新表标记是扫描所有代,年轻代被清除过一次,就会减少remark的时间
3、重新标记Remark
4、并发清理
案例
对象生命周期的分布情况:如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。
小结
通过案例分析了解到,由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象,我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。
通过案例分析了解到,由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象,我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。
高并发(流量有多大?)
1、考虑数据一致性要求?
最终一致性
高。极高的一致性,带来的是高性能消耗
TPS和QPS区别
TPS就是单个事务的响应速度-也就是一个请求的响应
每秒钟完成的技术交易的数量
QPS就是数据库单个查询请求的响应速率
2、适当的削峰
系统估算:
单节点tomcat 不会超过200QPS
nginx 官方5w以下
单节点tomcat 不会超过200QPS
nginx 官方5w以下
域名的划分和分组
当单个域名的这一组服务扛不住
CDN缓存静态资源
?问题:怎么更新动态缓存
-一致性要求不高,5分更新一次
如何更新?-JOB定时任务去跑
?问题:怎么更新动态缓存
-一致性要求不高,5分更新一次
如何更新?-JOB定时任务去跑
LVS(keepalived)
指向N多个nginx(只做反向代理)
应用层nginx
1、c语言开发nginx模块
1、c语言开发nginx模块
2、Lua语音内嵌开发
ngixn+lua+redis
nginx+Lua+Kafka
nginx+Lua+Kafka
Lua类似与胶水,把2个不能相连的模块去捏合在一起
Lua语言
语言的特点
1、减少网络开销。本来需要多次请求的,可以一次请求完毕
2、原子操作:Redis会将整个脚本作为一次整体执行,中间不会执行其他命令,类似存储过程
3、复用:客户端发送的脚本会存储在Redis中,从而实现脚本的复用
2、原子操作:Redis会将整个脚本作为一次整体执行,中间不会执行其他命令,类似存储过程
3、复用:客户端发送的脚本会存储在Redis中,从而实现脚本的复用
1、运行lua脚本
/usr/local/install/redis/bin/redis-cli --eval test.lua
Lua+nginx
(openresty)
(openresty)
1、路由转发
2、web防火墙
3、限流
DNS
1、创建一个域名。这个域名提供商会有自己的DNS服务器
2、运营商的DNS服务器发送广播,广播到公网的DNS服务器,所以一般配置好域名映射需要一定的时间,一般不超过24小时
2、运营商的DNS服务器发送广播,广播到公网的DNS服务器,所以一般配置好域名映射需要一定的时间,一般不超过24小时
原理:
1、当用户输入zhangning.java域名的时候
2、先去本地的HOSTS文件中查
3、然后DNS缓存中查,
4、然后往配置的DNS服务器去查
2、先去本地的HOSTS文件中查
3、然后DNS缓存中查,
4、然后往配置的DNS服务器去查
Mysql调优
往mysql写东西
先写日志文件保证了AD(原子性,持久性)
CI(一致性隔离性)用的锁保证的
在写数据文件时候,事务提交就做redo操作
如果事务没有提交就做undo操作
事务具有4个特征ACID
A:atomicity
原子性: 事务要不全部成功,要不全部回滚
C:consistency
一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态
I:isolation
隔离性
四个隔离级别
1、读未提交 read uncommit
2、读已提交 read commit
3、可重复读取 repeatable read --默认隔离级别
4、串行化
https://www.cnblogs.com/wyc1994666/p/11367051.html
1、读未提交 read uncommit
2、读已提交 read commit
3、可重复读取 repeatable read --默认隔离级别
4、串行化
https://www.cnblogs.com/wyc1994666/p/11367051.html
读写锁
mvcc
事务之间相互不影响
D:durability 持久性
持久性:事务一旦提交,数据库就会永久保存,如果出现宕机,那么只要数据库可以启动没要回复到事务成功结束的状态,
事务提交就做redo操作
如果事务没有提交就做undo操作
索引的优点
1.大大减少了服务需要扫描的数据量
2、帮助服务器避免排序和临时表
3.将随机IO变成顺序io
索引的用处
1、快速查找匹配where字句的行
2、从consideration中消除行,如何可以在多个索引之间进行选择,mysql通常会使用找到最少行的索引
3、如何表具有多列索引,则优化端可以使用索引的任何最左前缀来查找行
4、当有表连接的时候,从其他表检索行数据
5、查找特定索引列的min或max的值
6、如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组
7、在某些情况下,可以优化查询以检索值而无需查询数据行
索引的分类
主键索引
唯一索引
普通索引(给除了主键和唯一键的索引)
辅助索引
二级索引
全文索引
组合索引
面试技术名词
回表
InnoDB聚集索引
1、如果定义了主键,则主键为聚集索引
2、如果表中没有主键,则第一个不为空unique列则是聚集索引
3、否则,innodb会创建一个隐藏的row-id作为聚集索引
innodb普通索引
t(id PK, name KEY, sex, flag);
id是聚集索引,name是普通索引。
id是聚集索引,name是普通索引。
查询的时候先根据普通索引查到id,然后在根据id查到记录
也就是-扫描两遍索引树
缺点
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
覆盖索引
如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。即只需扫描索引而无须回表。
根据explain解析结果可以看出Extra的值为Using index ,表示已经使用了索引覆盖
最左匹配
适用于组合索引,优先匹配最左边的,如果没有不采用索引
mysql有个优化器,会自动优化左右的顺序
总之就是火车头都没找到,这个索引不会去使用的
组合索引设计原则====最左前置原则(最常用列>离散度高的列>占用空间少)
索引下推(ICP)
1、在不使用ICP的情况下,在使用非主键索引(又叫普通索引或者二级索引)进行查询时,存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件 。
2、在使用ICP的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。
索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
2、在使用ICP的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。
索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
根据explain解析结果可以看出Extra的值为Using index condition,表示已经使用了索引下推。
索引匹配方式(组合索引是name,age,pos的组合)
全值匹配
是指和索引中所有列进行匹配
explain select * from staffs where name =‘July’ and age='23' and pos='dev'
匹配最左前缀
只匹配前面的几列
explain select * from staffs where name ='july' and age='23'
匹配列前缀
可以匹配某一列的值的开头部分
explain select * from staffs where name like 'J%'
explain select * from staffs where name like '%y'
索引失效(前后都有%)
explain select * from staffs where name like '%J%'
匹配范围值
可以查找某一个范围的数据
explain select * from staffs where name >'Mary'
精确匹配某一列并范围匹配另一一列
可以查询第一列的全部和第二列的部分
explain select * from staffs where name =‘July’ and age>'23'
只访问索引的查询
查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
explain select name,age,pos from staffs where name =‘July’ and age='23' and pos='dev'
索引采用的数据结构
哈希表
数组接口,hash算出下标,然后同一下标往下树形
等值匹配非常快,但是不支持范围查询
等值匹配非常快,但是不支持范围查询
二叉树
左小右大规则,递归的二分查找
左小右大规则,递归的二分查找
缺点就是id自增会一直在右边放置节点
平衡二叉树
左右子节点的高度差不会大于1
缺点:树会太高了
缺点:树会太高了
B树:多路平衡查找树
根节点=支节点数量-1
每个节点都存储了数据区,io利用率不高
每个节点都存储了数据区,io利用率不高
B+树
1、采用左闭合的比较方式
2、根节点和支节点不保存数据区
3、叶子节点保存数据区
4、叶子节点关键字采用双向链表
2、根节点和支节点不保存数据区
3、叶子节点保存数据区
4、叶子节点关键字采用双向链表
mysiam
数据和索引是分开存储的。索引存储在MYI文件中,数据存储在MYD文件中
一条sql语句有可能使用多个索引
innodb
聚集索引clustered index
叶子节点含有行记录
叶子节点含有行记录
行记录数据直接存储在B+树叶子节点的上面,所以只有一个聚集索引,也就是主键索引
离散度低的列不适合建立索引,如性别男女,只有1和2,还不如做全表扫描
查询表空间和索引空间
SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name',
CONCAT(ROUND(table_rows/1000000,2),'M') AS 'Number of Rows',
CONCAT(ROUND(data_length/(1024*1024*1024),2),'G') AS 'Data Size',
CONCAT(ROUND(index_length/(1024*1024*1024),2),'G') AS 'Index Size' ,
CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),2),'G') AS'Total'
FROM information_schema.TABLES
WHERE table_schema = 'mall'
and TABLE_NAME = 't_settle_order'
CONCAT(ROUND(table_rows/1000000,2),'M') AS 'Number of Rows',
CONCAT(ROUND(data_length/(1024*1024*1024),2),'G') AS 'Data Size',
CONCAT(ROUND(index_length/(1024*1024*1024),2),'G') AS 'Index Size' ,
CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),2),'G') AS'Total'
FROM information_schema.TABLES
WHERE table_schema = 'mall'
and TABLE_NAME = 't_settle_order'
事务的隔离级别
读未提交
读已提交
会出现不可重复度
不可重复度是因为update和delete导致的
可重复度
(innodb默认)
(innodb默认)
一次事务中,读取多次的结果是一样的,但是无法解决幻读问题
幻读:insert才会导致的
串行化
事务隔离级别的解决方案
1、读取数据前进行加锁,让别人无法修改
2、读取以后生成快照,并利用这个快照提供一定级别的事务读取,就是MVCC
Multi Version Concurrency Control
锁的实现
悲观锁
共享锁
开启事务
select * from t1 lock in share mode;
select * from t1 lock in share mode;
总结:
MySQL 共享锁(lock in share mode)
允许其他事务也增加共享锁读取
不允许其他事务增加排他锁(for update)
当事务同时增加共享锁时,事务的更新操作必须等待先执行的事务commit后才能执行,如果同时并发太大的时候很容易造成死锁。
MySQL 共享锁(lock in share mode)
允许其他事务也增加共享锁读取
不允许其他事务增加排他锁(for update)
当事务同时增加共享锁时,事务的更新操作必须等待先执行的事务commit后才能执行,如果同时并发太大的时候很容易造成死锁。
排它锁
for update 行锁
表锁死操作:
LOCK TABLES table_name read local; 将当前表设置为只读,不能进行插入或更新操作。
UNLOCK TABLES;锁住表了,使用UNLOCK进行释放。
LOCK TABLES table_name read local; 将当前表设置为只读,不能进行插入或更新操作。
UNLOCK TABLES;锁住表了,使用UNLOCK进行释放。
记录锁
表中的数据id为1 4 7 10
间隙锁
临键锁
调优方案
1、连接数
1、调大服务器端最大的连接数。默认151 最多10w
2、加快客户端释放连接的速度
2、架构优化
redis缓存
3、分片
垂直拆分,按照业务主题拆分,如每个业务放一个数据库
水平分库分表
就是一个表拆为DB1、DB2、DB*
4、explan
1、tyep
const
主键索引或者唯一索引查到的数据
eq-ref
join
被驱动表,也就是右边。被关联表中where条件用到了唯一索引
ref
用到了非唯一性索引
rang
索引的范围查询
in < > between
index
full index scan
ALL
没有用到索引
full table scan
rows
预计扫描的行数
filtered
过滤百分比
看是在搜索引擎上过滤还是返回到服务器端过滤
Extra
1、useing index 索引覆盖
2、useing where
存储引擎没办法利用索引过滤数据(也就是没用上索引),需要返回给服务器端过滤
3、using index Condition 索引下推
4、using filesort
不能直接用索引排序
5、using temporay
放到临时表
一般是有distinct和group by的时候用
RPC
dubbo
在provider端配置consumer的属性
@Service(version = "1.0.0",timeout = 10000,interfaceClass = DemoService.class)
负载均衡策略:默认随机加权重 。改为轮训loadbalance=“roundrobin”
timeout 和retries 重试次数(N+1次),如果请求响应时间大于timeout,会新开线程去重试请求,要预防重复操作
executes 可以并发的最大线程数,跟上面第二条有相互关系,正常生产至少也得至少300并发吧
消息队列
Kafka
rocketMQ
组件
nameServer
mq的路由中心,可以做高可用部署
nameServer每隔10秒检查一次。
如果超过120秒未收到broker的心跳,就认为挂掉了
如果超过120秒未收到broker的心跳,就认为挂掉了
?nameServer集群如何保持一致
1、borker的新增,broker新增时候会往所有的NS发送注册
2、broker下线。netty会有监听,发送剔除请求,如果是宕机,NS会120秒后删除
3、路由发现(生产者和消费者客户端会定期去刷新broker列表)
broker
1、和nameServer简历TCP长连接,30秒发送一次心跳
produce
根据messageQueue
3个参数
topic :确定我是那个topic上的队列
brokerName:我存在那个broker上面(带路,路由)
QueueId:我在这个topic中的队列编号
topic :确定我是那个topic上的队列
brokerName:我存在那个broker上面(带路,路由)
QueueId:我在这个topic中的队列编号
读写队列的配置数最好相等
或者读队列大于写队列,要不会出现消息消费者不消费那个QueueId中的消息
consumer
消费方式
广播消费
所有消费者都消费一次
集群消费
也就是负载消费
一个消息只会被一个消费者消费
消费模型
1、pull
kafuka只支持pull模式
RMQ底层都是pull模式
2、push
只有rabbitMQ是2个消费模型都支持的
consumerQueue
key value
commitLog
如何做到每秒200万数据的磁盘写入呢?
pageCache内核态从磁盘一次读一页一般是4k大小
读取到内核态缓冲区,用户态需要拷贝过去才能操作
读取到内核态缓冲区,用户态需要拷贝过去才能操作
0拷贝
用了MMAP
内存映射
内存映射
用户态和内存缓存区建立了映射关系,在用户态操作就相当于直接操作内核态数据
文件清理策略
每天凌晨4点清理过期的文件
如果写入过快,到达存储空间的85%会清理一部分,如果到达90%会拒绝写入
消费者负载与rebalance
1、连续分配(默认)
2、轮询分配,每人轮流一次
3、指定broker中的topic去消费
消费的重试与死信队列
消费者如果出现异常,可以return 重试
如果重试几波以后还不行,就放入死信队列,需要人工干预
主从同步和刷盘类型
1.主从同步
主从异步
主从同步双写(推荐)不然丢消息概率很大
刷盘类型分2中
同步刷盘,效率低,但是不会丢失数据
异步刷盘,会将数据放入缓冲区,然后就ask。提升性能,但是可能丢失数据(默认)
topic
发送到那个topic中
tags
用来过滤消息
生产者将消息封装到message中
消费者可以设置消费的sub表达式
*
tagA
tagA||tagB
keys
可以理解为消息的唯一id
不能重复,RMQ页面端可以根据key直接查询消息
顺序消费原理:
1、生产者发送消息时候,到达broker是有序的,所以生产者不能多线程异步发送,而是单线程顺序发送
2、写入broker的时候,应该是顺序写入,也就是相同主题的消息应该集中写入同一个messageQueue,此时发送消息传入同一个hashKey 就能保证消息不是分散写入
3、消费者消费的时候只能由一个线程,否则会因为消费速率不同,有可能出现记录到数据库的时候无序
RMQ事务流程
1、生产者发送RMQ消息,是一个半消息,会放到一个half队列中
2、然后生产者执行本地事务,成功后告诉broker 提交还是回滚这半个消息
消息集中存储
所有消息都写入到commitLog文件中
Dledger
故障转移
需要多配置一个故障转移的Dledger节点
k8s
master节点
api-server
k8s网关,所有的指令都必须通过apiServer
schduler
调度器,使用调度算法,把请求资源调度到某一个node节点
controller
控制器,维护k8s资源对象
ReplicaSet副本控制器
副本控制器的基本概念:控制pod(服务集群)的数量,永远与预期设定的数量保持一致即可
replicas: 2
Deployment 资源部署对象
滚动更新
新创建一个RS,然后重新创建新版本的pod,就删除一个老版本的pod
StatefulSet
为了解决有状态服务使用容器化部署的一个问题
k8s来说,不能使用deployment部署模型部署有状态服务,通常情况下,deployment被用于部署无状态服务
那么对于有状态服务的部署,使用statefulSet进行有状态服务的部署
那么对于有状态服务的部署,使用statefulSet进行有状态服务的部署
service
POD如何对外提供服务
通过物理宿主机的ip+port进行访问
然后内部进行数据包的转发,来达到pod对外提供服务
内部是如何访问pod的
访问pod是如何实现负载均衡的
POD IP
pod的ip地址
NODE IP
物理机的ip地址
cluster IP
虚拟ip,是有k8s抽象出的service对象,这个service就是一个虚拟ip的进程(VIP资源对象)
etcd
存储资源对象
node节点
docker
运行容器的基础环境,容器引擎
kubelet
在每一个node节点都存储一份,在node节点上的资源操作指令有kubelet来执行
kube-proxy
代理服务,负载均衡
fluentd
日志收集服务
pod
是k8s管理的基本单位。pod内部是容器,k8s不直接管理容器,而是管理pod
状态服务的解释
1、有状态服务
有实时数据需要存储
有状态服务集群中,把某一个服务抽离出去,一段时间后再加入网络,会导致集群网络无法使用
2、无状态服务
没有实时数据需要存储
抽离出去以后,对集群服务没有影响
springcloud
Nacos
Feign声明式restful调用(在消费端)
@FeignClient(name="服务名",url="http://localhost:2100")
这样可以脱离eureka使用
自定义feign配置
自定义配置类
增加拦截器
好处是一次配置所有接口都生效
feign的日志
zuul
http请求
ZuulServlet
(本质就是httpservlet)
(本质就是httpservlet)
初始化
RequestContext
RequestContext
一系列过滤器
本质就是过滤器。可以做很多工作
灰度发布
限流
鉴权
新老url的处理
服务不可达的failBack
zuul过滤器限流
1、网关限流
令牌桶
2、微服务限流
在被调用的服务方,写filter。然后同样的令牌桶逻辑
token的透传
就是服务通过网关去调用服务。token无法往后传递
分布式事务
阿里分布式事务框架Seata
接口安全设计
1、在网络传输中防止被抓包
对称加密
服务器和客户端有一样的秘钥
非对称加密
客户端都有公钥,但是私钥只有服务器有
如果证书不是CA机构颁发的证书,会发生中间人攻击
https
1、假如客户端发送请求到服务器段,被劫持,劫持者拿到的请求是密文,劫持者听不懂就行了
算法(变量/盐)
参数变了,算法就变了
server服务器端的公钥去ca机构拿证书,然后client端https请求时候先访问443.使用公钥加密发送密文
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的证书之后,会对证书进行检查,验证其合法性,如果发现发现证书有问题,那么HTTPS传输就无法继续。严格的说,这里应该是验证服务器发送的数字证书的合法性,关于客户端如何验证数字证书的合法性,下文会进行说明。如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
5.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
6.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
7.然后服务器将加密后的密文发送给客户端。
8.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的证书之后,会对证书进行检查,验证其合法性,如果发现发现证书有问题,那么HTTPS传输就无法继续。严格的说,这里应该是验证服务器发送的数字证书的合法性,关于客户端如何验证数字证书的合法性,下文会进行说明。如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
5.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
6.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
7.然后服务器将加密后的密文发送给客户端。
8.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
定时任务
任务调度之Quartz
1、任务调度工厂
2、任务调度器
SimpleTrigger,固定时刻固定时间间隔
CalendarIntervalTrigger
基于日历的调度,会自动识别一年一个月
DailyTimeIntervalTrigger
每天的6点到次日8点,每隔10分钟运行一次
CronTrigger:基于表达式执行
linux的cronTab
每天定时备份数据库文件
spring Task
@Scheduled(“corn表达式”)
缺点:不支持集群
缺点:不支持集群
0 条评论
下一页