Java技术栈
2020-04-26 17:41:49 0 举报
AI智能生成
Java核心技术栈
作者其他创作
大纲/内容
10.分布式
CAP理论
C(一致性)A(可用性)P(分区容错性)
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项
BASE理论
分布式锁
当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数
实现方式
基于数据库
主键
版本号
排他锁(for update)
基于Redis
setnx()+expire()
setnx()+get()+getset()
1. setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2
2. get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 3
3. 计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime
4. 判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试
5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理
基于Redlock
基于 ZooKeeper 做分布式锁
ZK基本锁
利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁。
临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁
缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大
ZK优化锁
上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)
步骤:
1.在 /lock 节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)
2.判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后 watch 序号比本身小的前一个节点
3.当取锁失败,设置 watch 后则等待 watch 事件到来后,再次判断是否序号最小
4.取锁成功则执行代码,最后释放锁(删除该节点)
集群模式的 Redis 分布式锁,它基于 N 个完全独立的 Redis 节点(通常情况下 N 可以设置成 5)
优点:性能高
缺点:
失效时间设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间
基于redisson
分布式事务
Redis事务
两阶段提交(2PC)
补偿事务(TCC)
本地消息表(异步确保)
MQ 事务消息
消息队列
防止消息重复消费
消息的可靠性投递
19.构建工具
maven
Gradle
18.代码管理
git
svn
17.大数据
HBase
Hive
Hadoop(HDFS、MapReduce)
Storm
16.容器
Tomcat
Jetty
Nginx
K8S+Docker
15.Linux
sed
awk
shell
14.搜索
Elasticsearch
定位
搜索引擎
nosql数据库
数据分析工具
数据结构
字符串
String
6.x以后不再支持
text
6.x以后用来代替string,用于吉检索
keyword
可用于排序
整数
浮点数
日期
布尔类型
binary
二进制不支持索引
数组
对象
特性
高性能
近实时
分布式,自集群
支持TCP和REST API
概念
index(索引)
数据库
type(类型)
表,不过一个索引 只有一个类型这里比不太合适
document(文档)
行
字段(field)
列
mapping(映射)
表结构
创建之后不能改
分片
路由
默认按_id 进行哈希路由到不同分片,保证所有分片数据均衡
hash(routing)%主分片数
主分片
主分片数不可变
副分片
相同分片的副分片不在同一个节点内
深度分页的劣势
版本
每次文档更新都会更新版本号
原理
倒排索引
ES集群
master节点
只有一个
脑裂
节点发现
ES自研的ZenDiscovery
负责集群管理
索引管理
不服务用户请求
data节点
存储数据
client节点
负载均衡
分布式
P2P类型的分布式系统(gossip协议)
相同cluster.name名称属于同一个集群
13.架构
架构设计
系统重构
中台能力
DDD领域建模
12.微服务
dubbo
架构
注册中心
Zookeeper
集群
脑裂问题
树存储
失效剔除 基于临时节点
simple
redis
multicast
提供者
消费者
核心配置
dubbo:service/dubbo:reference/dubbo:protocol/dubbo:registry/dubbo:application/dubbo:provider/dubbo:consumer/dubbo: method
通信框架(默认)
netty(默认)
mina
grizzly
远程调用(RPC)协议
dubbo协议(默认)
采用单一长链接和NIO异步通讯,适合于小数据量 大并发的服务调用,以及服务消费者机器远大于服务提供者机器的情况,不适合传输较大数据
Hession协议
底层采用Http通讯,使用多连接 短连接,同步传输,序列化成hessian二进制,适合传入 传出 参数数据包较大,提供者 比消费者个数多,提供者压力较大,可传文件。
Http协议
WebService协议
Redis协议
RMI协议
Thrift协议
Memcache协议
序列化
Hessian(默认)
dubbo
fastjson
java 自带序列化
负载均衡
RandomLoadBalance(默认)
随机,按权重设置随机概率
RoundRobinLoadBalance
轮循,按公约后的权重设置轮循比率
LeastActiveLoadBalance
最少活跃调用数,相同活跃数的随机。活跃数 指 调用前后计数差
ConsistentHashLoadBalance
一致性Hash,相同参数的请求 总是发到同一提供者
集群容错
SPI
实现原理
常见问题
spring cloud
注册中心
Eureka(Netflix 开源)
内部使用ConcurrentHashMap实现
Consul (Google 开源)
Nacos (阿里开源)
zookeeper(待验证)
服务网关
Zuul
网络路由
核心过滤器
pre
post
route
统一操作
降级
限流
认证
安全
异常处理
调用链监控
指标监控
日志监控
ELK
认证和授权
熔断和降级
Hystrix
隔离
线程隔离
在用户请求和服务之间加入了线程池
信号量的大小可以动态调整,线程池大小 不可以
信号隔离
熔断
熔断器在线程池 之间
状态
关闭状态
打开状态
半开(半熔断) 状态
降级
快速失败
不做处理,直接抛异常
无声失败
返回null ,空map ,空list
静态返回
返回默认值或自己组装一个值
远程缓存
在失败的情况下 再发起一次remote请求,不过这次请求的是一个缓存,比如redis 由于是又发起一起远程调用,所以会重新封装一次Command,这个时候要注意,执行fallback的线程 一定要和主线程分开,也就是重新命名一个ThreadPoolKey
主次方式退回
我们日常开发中需要上线一个新功能,为了防止 新功能上线失败,可以再新功能失败时 回退到老的代码
负载均衡
Ribbon
Feign
原理是动态代理
注解
配置中心
Apollo(携程开源)
Spring Cloud Config(基于Git)
容器化部署
docker
k8s
微服务领域,为什么选SpringCloud而不是Dubbo?
从开发难易度:dubbo需要依赖jar包,开发阶段难度极大;springcloud比较自由,但带来的问题是无法“强力约束接口规范”
从系统结构简易程序:springcloud的系统结构更简单、“注册+springmvc=springcloud”,而dubbo各种复杂的Url,protocol,register,invocation,dubbofilter,dubboSPI,dubbo序列化....炫技的成分更多一些
从整个大的平台架构来讲,dubbo框架只是专注于服务之间的治理,如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成,这样无形中使用dubbo的难度就会增加。Spring Cloud几乎考虑了服务治理的方方面面,更有Spring Boot这个大将的支持,开发起来非常的便利和简单。
从社区活跃度这个角度来对比,Dubbo虽然也是一个非常优秀的服务治理框架,并且在服务治理、灰度发布、流量分发这方面做的比Spring Cloud还好,除过当当网在基础上增加了rest支持外,已有两年多的时间几乎都没有任何更新了。在使用过程中出现问题,提交到github的Issue也少有回复
Spring Cloud来源于Spring,质量、稳定性、持续性都可以得到保证
SOA和微服务的区别
APM工具
Pinpoint
11.框架
Spring
IOC/DI
控制反转
对象A所依赖的对象B,由new 的方式转变为由容器进行注入的方式,
控制权由A反转到容器,DI是IOC的一种实现
原理
Xml解析
工厂模式
单例模式
反射
Bean
生命周期(总共11步)
1.初始化Bean,调用Bean构造器new 一个bean的实例
2.给Bean注入属性
3.如果Bean实现了BeanNameAware接口,调用setBeanName方法
4.如果Bean实现了BeanFactoryAware接口,调用setBeanFactory方法
5.如果有关联的BeanPostProcessor ,调用processBeforeInitialization (预初始化)方法
6.如果Bean实现了InitializatingBean,调用afterPropertiesSet方法
7.调用配置或注解Init-method方法
8.如果有关联的BeanPostProcessor,调用processPostInitialzation(后初始化)方法,跟6对比着看
9.Bean创建成功-执行业务逻辑,直到销毁
10.调用DispostbleBean 的destory方法
11.调用配置或注解的destory-method方法
加载过程
BeanFactory
获取时实例化Bean
ApplicationContext
继承BeanFactory
加载时实例化Bean
有参构造和无参构造,初始化
AOP
动态代理
https://baijiahao.baidu.com/s?id=1615034709376787673&wfr=spider&for=pc
jdk
原理:接口 实现接口:InvocationHandler
优点:
Jdk代理生成的代理类只有一个,因而其编译速度是非常快的;
缺点:
1.目标类被代理的方法必须实现一个接口
2.由于被代理的目标类是动态传入代理类中的,执行效率相对来说低一点,这也是Jdk代理被称为动态代理的原因
Jdk代理与目标类都会实现同一个接口,并且在代理类中会调用目标类中被代理的方法,调用者实际调用的则是代理类的方法
cglib(字节码)
原理:继承
优点:
代理类的方法是已经静态编译生成了的,因而Cglib代理的执行效率相对来说高一些
缺点:
Cglib代理需要为每个目标类生成相应的子类,因而在实际运行过程中,其可能会生成非常多的子类,影响了虚拟机编译类的效率
Javassist字节码
JBoss使用字节码实现AOP
字节码
ASM
事务(声明式事务)
事务有四个特性:ACID
Spring中七种事务传播行为
常用注解
@Controller
@Service
@Component
@Autowired、@Qualifier、@Resource
MyBatis
连接池
druid
密码加密
慢SQL
监控
动态SQL
crud
sql片段
foreach
接口、实体映射
分页
Spring Boot
简单
编码
配置
只需要很少的配置就能跑起来
部署
内置容器
监控
各种actuator
依赖管理
各种startor
parent,减少整个引用的依赖冲突
缺点
封装的太严重,很多问题暴露不出来
用好比较难。后面隐藏了整个Spring 的技术体系
Spring Security
OAuth2.0
JWT
JSON Web Token
三部分
头部Header
负载Payload
签名verify Signature
过滤器
授权
方法级
Web安全验证
01.Java
多线程
3.内存与JVM
内存
堆(heap)
新生代
一块Eden
两块Survivor(From 和 To)
老年代
栈(Stack)
本地方法栈(Native Method Stack)
方法区(Method Area)
运行时常量池(Runtime Constant Pool)
`
程序计数器(Program Counter Register)
垃圾回收(GC)
垃圾收集算法
引用计数算法
缺点:无法处理循环引用的问题
根搜索算法(常用)
设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的
GC roots
虚拟机栈中的引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI的引用的对象
垃圾回收算法
标记-清除算法(Mark-Sweep)
复制算法
标记-整理算法
分代收集算法
java sun hotspot虚拟机将内存分为新生代(堆)、老年代(堆)、永久代(方法区、常量池、即时编译代码)几个区域,
新生代主要使用基于复制算法的垃圾回收,老年代和永久代主要使用标记-整理算法进行垃圾回收。具体每个区域使用哪种垃圾回收算法还要视收集器的实现制约。
各区域回收规则(GC)
新生代(Minor-GC),默认占比8:1:1
Eden(伊甸区)
满了才GC
复制到其中一个Survivor中
Survivor(幸存者区)
满了才GC(GC 对象是 Eden+活动的Survivor)
将满足晋升阈值的对象复制到老年代
不满足晋升的复制到另一个Survivor
老年代
fullgc
收集器
https://www.cnblogs.com/zuoxiaolong/p/jvm7.html
串行搜集器(serial collector)
概念:它只有一条GC线程,且就像前面说的,它在运行的时候需要暂停用户程序(stop the world)
serial(用于新生代,采用复制算法)
serial old(用于年老代,采用标记/整理算法)
并行搜集器(parallel collector)
概念:它有多条GC线程,且它也需要暂停用户程序(stop the world)
ParNew(用于新生代,采用复制算法)
Parallel Scavenge(用于新生代,采用复制算法)
Parallel old(用于年老代,采用标记/整理算法)
并发搜集器(concurrent collector)
概念:它有一条或多条GC线程,且它需要在部分阶段暂停用户程序(stop the world),部分阶段与用户程序并发执行。
concurrent mark sweep[CMS](用于年老代,采用标记/清除算法)
G1
面向服务器的收集器
GC
Minor GC
从年轻代空间(包括Eden 和Survivor 区域)回收内存被称为Minor GC
Major GC
清理老年代
Full GC
清理整个堆空间-包括年轻代和老年代
类
生命周期
加载
把class字节码文件从各个来源通过类加载器装载入内存中
链接
验证
保证加载进来的字节流符合虚拟机规范,不会造成安全错误
准备
为类变量(注意,不是实例变量)分配内存,并且赋予初值
(注:不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。)
解析
将常量池内的符号引用替换为直接引用的过程。
符号引用
即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息
直接引用
可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针
初始化
主要是对类变量初始化,是执行类构造器的过程
使用
卸载
加载机制
类加载器
启动类加载器(BootStrap Classloader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(App ClassLoader)
自定义类加载器(Custom ClassLoader)
双亲委派模型
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载
如何破坏?线程上下文类加载器:如 热部署
调优
大对象fllgc导致的stop the world 时间过长
降低进入老年代的对象数
增大年轻代空间
增加对象晋升的年龄阈值
缩短fullgc时间
减少 老年代空间
可能会导致更频繁的oom,所以需要调整成一个“合适”的值
oom
永久代
调大permsize
JVM参数
-Xmx256m(设置堆最大值)
-Xms128(设置初始化堆大小)
-xss128k(设置线程栈大小)
-Xmn2g(设置年轻代大小)
-XX:NewSize=1024m(设置年轻代 初始值)
-XX:MaxNewSize=1024(设置年轻代最大值)
-XX:PermSize=256m 设置永久代初始值
-XX:MaxpermSize=256m 设置永久代最大值
-XX:NewRatio=4(设置年轻代和年老代的比值)
-XX:SurvivorRatio=4设置Suvivor区和Eden区的比值
-XX:MaxTenuringThreshold=7 (表示一个对象从年轻代移入年老代的年龄)
内存分配机制
内存泄露与监控
程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。
大量的内存泄露会导致内存溢出
引用状态
强引用
代码中普遍存在的类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
软引用
描述 有些还有用但并非必须的对象。再系统将要发生溢出异常之前,将会把这些对象列进回收范围 进行二次回收。如果这次回收 还没有足够的内存,才会抛出内存溢出异常。Java 中的类SoftReference 表示软引用。
弱引用
描述 非必需对象。被弱引用关联的对象 只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前是否足够,都会回收掉 只被弱引用 关联的对象。
Java 中的类 WeakReferance 表示弱引用
虚引用
这个引用存在的唯一目的就是 在这个对象被收集器回收时 收到一个系统通知,被虚引用 关联的对象,和生存时间完全没关系。
Java中的类 PhantomReference表述虚引用
内存模型
重排序
volatile重排序规则
1.当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序
2.当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
3.当第一个操作是volatile写,第二个是volatile读时,不能重排序
final重排序规则
JMM禁止将final域内的写重排序到构造函数之外
顺序一致性
Happens-before
JVM常见面试题
https://www.cnblogs.com/nov5026/p/11013492.html
2.JAVA基础
数据类型
基本类型
整形
byte short int long cahr
浮点型
double float
boolean
引用类型
类 接口 数组
字符串
面向对象
类 和 对象
枚举 enum
enum 与接口常量
集合类
集成与多态
泛型
内部类
接口
注解
异常与错误
正则表达式
Pattern 和 Matcher
Java7与Java8差异
集合
Collection
List
ArrayList
基于数组
多读场景
LinkList
基于链表
多读场景
Vector
线程安全
Set
HashSet
TreeSet
LinkedHashSet
Queue
不阻塞队列
ConcurrentLinkedQueue
阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue(双端队列)
知识点:原理、结构、性能、排序、线程安全、性能、大小、扩容机制
Map
AbstractMap
HashMap
允许null
线程不全
ConcurrentHashMap
线程安全
get方式不加锁,使用getObjectVolatile保证获取到最新值
分段锁(1.8 使用红黑树,时间复杂度O(log(n)))
Hashtable
不允许null
线程安全
LinkedHashMap
有序(插入顺序)
TreeMap
有序(自然排序、实现SortedMap)
使用红黑树
高效操作几个的方式
并行流
异步流CompletableFuture
常用类
String
new String("ABC") 当常量池中存在对象ABC时,创建一个堆对象;否则创建两个对象,堆对象+常量池对象
Date
基本语法
运算符
自增 自减
访问控制
public private protecated
循环条件
if while for break continue
异常
反射
02.算法
搜索算法
排序算法
稳定
冒泡排序
O(n2)
插入排序
新插入元素与 前面的队列比较
O(n*log2n)
快速排序
中轴、分治排序
O(n*log2n)
归并排序
分解、求解、合并
O(n*log2n)
TimSort
结合了合并排序和插入排序;块排序
O(n log n)
比较类排序算法
交换排序
冒泡排序
快速排序
插入排序
简单插入排序
希尔排序
选择排序
简单选择排序
堆排序
归并排序
二路归并排序
多路归并排序
TimSort
非比较类排序算法
计数排序
通排序
基数排序
03.数据结构
线性结构
链表 hash
树形结构
树 二叉树
图
图的搜索遍历
广度优先
深度优先
04.设计模式
原则
单例模式
懒汉模式
线程不安全
可以使用同步变成线程安全
懒加载
饿汉模式
线程安全
提前初始化,非懒加载
静态内部类
线程安全
双重校验锁
枚举
工厂模式
简单工厂模式
switch 或者if else 实现
工厂模式
抽象工厂模式
模板模式
策略模式
适配器模式
源-被转换的角色
适配器
目标-将要转换成的角色
更好的复用性
更好的拓展性
内部实现复制凌乱,要进去才知道具体的调用实现
观察者模式
观察着模式
被观察者维护观察者列表
发布-订阅
发布者和订阅者 通过委托处理
发布者和订阅者完全解耦
命令模式
建造者模式
外观模式
桥接模式
代理模式
动态代理
JDK
基于接口
InvocationHader
CGLB
基于继承
MethodInterceptor
静态代理
装饰着模式
顶级抽象接口
被装饰者
装饰者
装饰子类
行为模式
结构模式
创建模式
05.数据库
Oracle
TableStore
MongoDB
MySql
基于B+树
B树:有序数组+平衡多叉树;B+树:有序数组链表+平衡多叉树;
数据库引擎
InnoDB
支持事务
支持行级锁
不支持全文检索
MyISAM
不支持事务
表锁
全文检索
优化
降低范式标准,增加冗余
分库分表(先垂直,后水平:简单、符合处理现实问题的方式)
https://blog.csdn.net/qq_39940205/article/details/80536666
垂直切分
垂直分库
根据业务拆分:用户、商品、订单 等(分库后用不同服务器:磁盘、内存、tps)
垂直分表
大表拆小表(基于字段,将不常用、数据较大、字段较长的字段拆分到扩展表)
水平切分
水平分表
针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去。
但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用
水平分库分表
将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈
水平分库分表切分规则
RANGE
HASH取模
地理区域
时间
问题
分布式ID
UUID
数据库自增长序列
snowflake
redis incr命令实现id自增
跨表操作
分布式事务
扩容
mvcc
为什么用B+树做索引?
锁
级别
类型
什么叫意向锁
mvcc
事务
四大特性ACID
原子性(atomicity)
一致性(consistency)
隔离性(isolation)
持久性(durability)
事务会产生的问题
脏读
事务A读到了事务B还没有提交的旧数据
不可重复读
在一个事务里面读取了两次某个数据,读出来的数据不一致
幻读
在一个事务里面的操作中发现了未被操作的数据
事务隔离级别
读未提交(READ_UNCOMMITTED)->脏读、幻读
即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
读已提交(READ_COMMITED)->不可重复度、幻读
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
重复读取(REPEATABLE_READ)->幻读
即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
串行化(SERLALIZABLE)
最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
索引
聚合索引
(每个表只能有一个聚簇索引)聚簇索引的数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。
非聚合索引
每个表你最多可以建立249个非聚簇索引。非聚簇索引需要大量的硬盘空间和内存。另外,虽然非聚簇索引可以提高从表中取数据的速度,它也会降低向表中插入和更新数据的速度。每当你改变了一个建立了非聚簇索引的表中的数据时,必须同时更新索引。因此你对一个表建立非聚簇索引时要慎重考虑。如果你预计一个表需要频繁地更新数据,那么不要对它建立太多非聚簇索引。
索引类型
普通索引
唯一索引
主键索引
组合索引
全文索引
EXPLAIN解释器
type
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
SQL性能优化的目标,至少要达到range 级别,要求是ref级别,如果可以是const最好
06.IO与网络编程
基本IO
文件IO
序列化
网络IO
socket
线程与并发
线程
锁
线程安全
原子操作性
Synchronized
同步与异步
共享与可见性
NIO
并发容器
Executor框架
线程池
newStringThreadExecutor
newFixdTheadPool
newCachedThreadPool
newScheduledThreadPool
延迟与周期任务
ScheduledThreadPoolExecutor
Callable 与 Future
BlockingQueue
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue
PriorityBlockingQueue
SynchronousQueue
并发
同步、异步、阻塞、非阻塞
Fork/Join
消息队列
ActiveMQ
IO设计模式
Reactor
Proactor
NIO和IO的主要区别
IO是面向流的,NIO是面向缓冲区的
Java IO的各种流是阻塞的,Java NIO的非阻塞模式
Java NIO的选择器允许一个单独的线程来监视多个输入通道
网络并发
数据结构
XML
DOM
SAX
JSON
fastjson
gjson
通信协议
HTTP/HTTPS
https原理:https://www.cnblogs.com/zery/p/5164795.html
XMPP
socket
通信框架
MINA
Netty
网络基础
TCP/IP
链路层
主要是网卡、操作系统、驱动 等软硬件设备
网络层
IP 协议,路由和寻址
传输层
TCP
三次握手
过程
SYN +客户端 seq序列号(ISN)
SYN+ACK+ACKnum+服务端seq序列号
ACK+ACKnum
为什么两次不行
四次挥手
有连接
可靠传输
重发
排序
UDP
无连接
丢包
速度快,占资源少
适用于能忍受丢包的情景,如视频、语音等
应用层
DNS
HTTP
FTP
IO多路复用
epoll
事件
select
轮询
Socket
Netty
07.消息队列
常见队列比较
kafka
领英开源
高吞吐、高并发、高性能
常用与超高吞吐量的日志采集、实时数据同步、实时数据计算等场景
RabbitMQ
Erlang开发
高吞吐、高并发、高性能
集群部署、高可用
消息可靠
后台管理界面完善
社区活跃度高
原理
Erlang实现的高级消息队列协议(AMQP)
AMQP
模型层(Model Layer)
会话层(Sessionn Layer)
传输层(Transport Layer)
RocketMQ
阿里开源
Java开发
高吞吐、高并发、高性能
支持分布式事务
场景和选型
削峰
秒杀场景
解耦
A系统生成数据,B C进行订阅
异步调用
下单和派送时两个过程
缺点
数据一致性
一般是最终一致性
系统稳定性降低
重复数据和数据丢失
系统可用性降低
依赖MQ的高可用
08.中间件
8.高并发
锁
happens-before
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系
原则定义
如果一个操作happens-before 另一个操作,那么第一个操作的执行结果 将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
两个操作之间存在happens-before 关系,并不意味着 一定要按照happens-before 原则制定的顺序来执行。如果重排序之后的执行结果 与按照happens-before关系来执行的结果一致,那么这种重排序并不非法
规则
1.程序顺序规则:一个线程中的每个操作,happens-before于该线程的任意后续操作
2.监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3.Volatile变量规则:对一个volatile的写,happens-before于任意后续对这个volatile的读
4.传递性
5.start()规则:如果线程A执行操作ThreadB.start(),那么线程A的ThreadB.start 操作happens-before于线程B中的任意操作
6.join()规则:如果线程A执行操作ThreadB.join(),,并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回
Synchronized
尽量减少同步块
官方提示相对Lock 优先使用Synchronized
非公平锁
1.6优化之后 性能跟重入锁几乎一样
偏向锁
轻量级锁
自选锁
AQS实现原理
Lock
ReentrantLock
实现原理
ReentrantReadWriteLock
对锁可以多线程同时获取
上写锁之后,其他线程读锁和写锁都获取不到
读锁共享,写锁独占
Volatile
内存可见性
指令重排
concurrent包中的工具类大多使用volatile 和CAS配合实现
重排序规则
1.当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序
2.当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
3.当第一个操作是volatile写,第二个是volatile读时,不能重排序
悲观锁
悲观的认为每次获取数据之后,别人都会把原始数据改掉,所以每次操作都会加锁,防止期间 数据被别人改掉
使用写频繁的场景
乐观锁
乐观的认为读取数据之后,没有人会修改原始数据,但是更新数据时 需要判断原始数据有没有人修改富哦,没有才写入,改过则丢弃
使用读频繁的场景
一般使用CAS或者版本号来实现
缺点
ABA问题
CAS
概念
compare and swap 比较并交换(是乐观锁)
过程
V内存原值
A 预期原值
B新值
用预期原值A与内存值V比较,如果相等 使用新值B替换V,不相等 就无操作
死锁
避免一个线程 在锁内同时占用多个资源,尽量保证每个锁 只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
线程池
Executor
Executors
ThreadPoolExecutor
参数
corePoolSize(核心线程数,一直存活,默认1)
核心线程会一直存活,及时没有任务需要执行
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
queueCapacity(阻塞队列数量,默认Integer.MAX_VALUE)
当核心线程数达到最大时,新任务会放在队列中排队等待执行
maxPoolSize(最大线程数量,Integer.MAX_VALUE)
当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
keepAliceTime(线程空闲时间,默认60s)
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
allowCoreThreadTimeOut(允许核心线程超时,默认 false)
为true 时核心线程空闲时间超过keepAliveTime也会被回收
rejectedExecutionHandler(任务拒绝处理器,默认AbortPolicy())
当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务.
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
如何来设置参数
需要根据几个值来决定
tasks :每秒的任务数,假设为500~1000
taskcost:每个任务花费时间,假设为0.1s
responsetime:系统允许容忍的最大响应时间,假设为1s
做几个计算
corePoolSize = 每秒需要多少个线程处理?
threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
queueCapacity = (coreSizePool/taskcost)*responsetime
计算可得 queueCapacity = 80/0.1*1 = 800。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
调优
Runnable
入口run方法,无返回值
Callable
入口call方法,有返回值Future泛型可以用来获取异步执行结果
Thread
FutureTask
上下文切换
频繁切换会降低性能
CPU控制权由一个正在运行的线程切换到另一个已经就绪等待获取CPU控制权的线程的
配置规则
IO密集
指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理,即在这个时间范围内,可以由其他线程来使用CPU
多配置一些线程
CPU密集
系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成
配置CPU处理器个数+/-1个线程
混合型
两者都占有一定的时间
配置规则
https://segmentfault.com/a/1190000004249911(待整理)
多线程
程间通信协作
现实中,需要线程之间的协作。
比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作
生产-消费者 案例:https://www.cnblogs.com/xdyixia/p/9386133.html
线程通信协作的最常见的两种方式
syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
通过管道进行线程间通信:1)字节流;2)字符流
实现多线程编程的方式有两种
继承 Thread 类
实现 Runnable 接口
进程间的通讯方式
管道
有名管道
信号量
消息队列
信号
共享内存
套接字
ThreadLocal
ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险
每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal
ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置(加1或减1)
Key是弱引用类型的,Value并非弱引用
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收
队列
阻塞队列
有界
ArrayBlockingQueue
LinkedBlockingQueue
无界
PriorityBlockingQueue
支持优先级
DelayQueue
使用优先队列实现
LinkedTransferQueue
LinkedBlockingDeque
SynchronousQueue
并发队列
ConcurrentLinkedQueue
使用CAS,poll性能提升较多
concurrent
ConcurrentHashMap
JDK7
分段锁-Segment(默认16个Segment)
自旋锁
每个Segment都是一个数组,hash碰撞之后 使用链表
JDK8
大数组
hash碰撞之后使用链表(链表长度大于8时,使用红黑树存储)
CAS+Synchronized
CountDownLatch
不能重用,只能指定一次 计数次数
主要方法
await
调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断
countDown
调用此方法则计数减1
底层原理
AQS(AbstractQueuedSynchronizer)里面的共享锁来实现
使用场景
开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段
有任务A和任务B,任务B必须在任务A完成之后再做。而任务A还能被分为n部分,并且这n部分之间的任务互不影响。为了加快任务完成进度,把这n部分任务分给不同的线程,当A任务完成了,然后通知做B任务的线程接着完成任务,至于完成B任务的线程,可以是一个,也可以是多个。
CyclicBarrier
可重用
CountDownLatch 和CyclicBarrier的不同之处?
Semaphore
acquire
获取许可
release
释放许可
原子工具类
使用CAS和Volatile实现
解决方案
扩容
垂直扩容
水平扩容
应用拆分
集群化
分布式
限流
数据库
分库分表
读写分离
消息队列
异步化
解耦
削峰
流量控制
缓存
分类和场景
分布式缓存
redis
本地缓存
ehcache
map
集中式缓存
memcache
常见问题
缓存击穿
热点数据
缓存雪崩
缓存击穿
一致性
过期策略
LRU
Least Recently Userd 最近最少使用
实现
可以使用双向链表实现,最近使用的数据移动到尾部,超过长度删除头部数据
LinkedHashMap 中可以按照访问顺序排序,原理也是双向链表,每次访问都会数据移动到尾部,然后重写removeEldestEntry方法即可,removeEldestEntry是否移除老数据的方法,默认false
IO
创建和系统核数相同的线程
volatile、synchronized、lock的区别
synchronized和volatile的区别
volatile 不会发生线程阻塞,而 synchronized 会发生线程阻塞
volatile 只能修饰变量,而 synchronized 可以修饰方法、代码块等。
volatile 不能保证原子性(不能保证线程安全),而 synchronized 可以保证原子性。
volatile 解决的是变量在多线程之间的可见性,而 synchronized 解决的是多线程之间访问资源的同步性
synchronized和lock的区别
synchronized是隐式锁,在需要同步的对象中加入此控制,而lock是显示锁,需要显示指定起始位置和终止位置
使用lock时在finally中必须释放锁,不然容易造成线程死锁;而使用synchronized时,获取锁的线程会在执行完同步代码后释放锁(或者JVM会在线程执行发生异常时释放锁)
使用lock时线程不会一直等待;而使用synchronized时,假设A线程获得锁后阻塞,其他线程会一直等待
lock可重入、可中断、可公平也可不公平;而synchronized可重入但不可中断、非公平
JDK1.8新特性
CompletableFuture
Stream
09.缓存
Redis
定位
内存型数据库,nosql 数据库
缓存
分布式锁
加锁
SET
支持NX、PX命令、可以在Set的时候传入
SETNX
set if not exist
key 和 expire需要同时设置,保证原子操作,expir 保证不会出现死锁
解锁
脚本
要保证只能释放自己的锁
get 和 del 要有原子性
String script=“if redis.ca.ll('get',KEYS[1])==ARG[1] then return redis.call('del'),KEYS[1] else return 0 end”
常用命令
set
get
setnx
SET if Not Exists
getset
将给定 key 的值设为 value ,并返回 key 的旧值(old value)
incr
Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作
Redis原子计数器
Watch/UNWATCH
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。
监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)
MULTI/EXEC/DISCARD
开启一个事务;执行事务;放弃事务
scan 搜索
特性
原子性
单线程
持久化
基于语句追加(AOF)
定时快照(RDB)
支持订阅-发布
支持过期
基本数据结构
String (字符串)
List(队列)
Hash(哈希)
Sets (集合)
Sorted Sets(有序集合)
Redis原理
线程IO模型
多并发
非异步IO+事件轮询API(多路复用,相当于JAVA里面的NIO )
指令队列
定时队列
持久化
RDB持久化
原理是将Reids在内存中的数据库记录定时dump到磁盘上
定时执行(fork子进程)
AOF持久化(append only file)
原理是将Reids的操作(写、删除)日志以追加的方式写入文件
Redis集群
3.0版本以上
数据分片(sharding)
使用分片(sharding)实现,总共有16384个哈希槽(hash slot)
key 按照算法分配到不同的哈希槽
每个节点包含不同的哈希槽
主从复制模型
每个节点都有一主多从
数据一致性
不保证数据的强一致性
异步复制
回收策略
volatile-lru
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl
从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random
从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random
从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐)
禁止驱逐数据
使用策略规则
如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
Redis与Memcached的区别
Redis支持多种数据结构:如 list,set,zset,hash
Redis支持数据的备份
Redis支持数据的持久化
常见问题
缓存击穿问题
是请求很多缓存中不存在的数据(热点key),导致这些请求短时间内直接落在了数据库上,致使数据库异常
解决方案
接口限流与熔断、降级
缓存穿透
是指查询一个数据库一定不存在的数据
解决方案
布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从避免了对底层存储系统的查询压力
即使查询返回的数据为空,也会进行缓存。(不过 缓存时间较短,最长不超过5分钟)
缓存雪崩问题
是指在某一个时间段,缓存集中过期失效
解决方案
备用缓存(Redis集群)
使用互斥锁
在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量
不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布
永不过期(简单高效)
高可用
主从模式
原理
https://www.jianshu.com/p/40212051ccc9
同步方式
全量(SYNC、BGSAVE)
增量
脑裂问题
只要问题:数据不一致,数据丢失
哨兵模式
选举master方式
集群模式
0 条评论
下一页