线程
继承Thread类创建线程 只能继承一次
2:实现Runnable(run 无返回值 不可抛异常 接口创建线程 编程复杂
3:使用Callable(call 有返回值 可抛异常 和FutureTask创建线程
4:使用线程池,例如用Executor框架创建线程
ThreadLocal作用于线程内独立的数据,线程访问同一个threadlocal内的不同map,key就是threadlocal val是各自线程存的值,get和set会调用currentThread()获取本线程的对象,底层用的ThreadLocalMap内的Entry,根据hash判断位置和解决hash冲突,entry的key就是TheadLocal继承了WeakReference(弱引用)所以用完要remove掉 弱引用gc发现就会回收map的key为nullvalue还存在导致内存泄漏
悲观锁 就是很悲观,每次去拿数据的时候都认为别人会修改,当前读:每次获取数据前都要被加锁(select …for update)。synchronized 和 ReentrantLock 等独占锁
乐观锁 就是很乐观,每次去拿数据的时候都认为别人不会修改,不会加锁,但是每次更新时都要判断是否被修改过,可以使用版本号判断。Mysql 提供了 MVCC,支持乐观锁
懒加载 就是我用到才去加载 用不到就不加载 第一次访问接口要加载bean 可能慢一点 应为spring的bean默认是单例的,加载一次就保存了,第二次就直接拿来用了
MVCC:在读正在写的数据时不加锁来提高读取效率和并发性
当前读:select lock in share mode (共享锁),select for update,增删改等悲观锁 读最新数据
快照读:不加锁的读 会读到历史数据
具体实现有3 个隐式字段DB_TRX_ID最后操作数据行的事务ID;DB_ROLL_PTR上条修改日志id;DB_ROW_ID隐含的自增ID还有一个隐藏的删除flag用于逻辑删除。undo log日志事务记录
RC的隔离级别下每个快照读都会生成并获取最新的readview
RR的隔离级别下只有在同一个事务的第一个快照读才会创建readview,之后的每次快照读都使用的同一个readview,所以每次的查询结果都是一样的
AQS是⼀个JAVA线程同步的核心组件。锁是先尝试CAS乐观锁去获取锁 获取不到转换为悲观锁。在AQS中,维护了⼀个state信号量state和双向链表队列。线程队列,就是⽤来给线程排队的。加锁线程变量就是当前加锁的线程。在可重⼊锁这个场景下,state就⽤来表示加锁的次数。0⽆锁,每加⼀次锁,state就加1。释放锁state就减1。
CAS乐观锁:compare and swap比较并转换,底层如何实现?利用unsafe提供的原子性操作方法。
更新一个变量时 只有原变量A的内存地址=V才将内存地址更新成B。用例Atomic(AtomicInteger
AtomicLong和AtomicBoolean)和1.6之后Synchronized转变为重量级锁前,缺点CPU开销大 不能保证全部代码的原子性和ABA:当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。利用版本号比较或查询的结果设为where条件。
线程锁
synchronized(sin捆莱茵斯特)锁和Lock对比:S包裹代码加非公平锁 lock.lock()/unlock()随时加锁解锁,tryLock()可以尝试申请锁很公平,lockInterruptibly()被阻塞的线程可以终止等待。依赖AQS
S锁修饰代码块锁定的是传入的对象,普通方法(非静态方法)时,锁的是方法的调用者 静态方法锁定的是类,不是对象,所以不管多少个对象锁的都是一个类
lock 锁住的是mLock.lock()和mLock.unlock()之间的代码块
Lock()方法:直接去获取锁。失败了进入阻塞等待 是可重入锁且不会死锁 但要手动释放锁。
tryLock()方法尝试获取锁 只有资源没被持有才可以取到,成功获取到锁资源之后,会立即返回true;失败即返回false 线程不用阻塞等待 加上timeout参数等待超时后返回false
ReentrantLock中的公平锁和⾮公平锁的底层实现
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
偏向锁:在锁对象的对象头中记录当前获取到该锁的线程ID,线程下次来就直接获取。只有一个线程操作锁的时候,一旦有竞争就会升级到轻量锁
轻量级锁:第⼆个线程来竞争偏向锁就会升级为轻量级锁,底层是通过⾃旋实现,不会阻塞线程,用CAS机制来竞争锁
重量级锁: 如果⾃旋次数过多仍然没有获取到锁或者一个线程在持有锁,一个在自旋,又有第三个来访时,则会升级为重量级锁会导致线程阻塞
1.互斥条件:共享资源x和y只能被一个线程占用
2.占有且等待:线程t1已经取得共享资源x在等待共享资源x在等待共享资源y的时候,不释放共享资源x
3.不可抢占:其他线程不能强行抢占线程t1占有的资源
4.循环等待:线程t1等待线程t2占有的资源,线程t2等待线程t1占有的资源
解决:1可一次性申请所有的资源。2占有部分资源的线程如果申请不到其他资源可以主动释放它占有的资源。3按顺序申请,资源是有线性顺序的。4tryLock可以设置超时时间。5只锁需要的部分
分布式事务模式:
2PC(强一致性事务数据库层面)同步阻塞协议,二阶段提交分别指的是准备(投票)和提交两个阶段,1给参与者发送准备命令同步等待所有资源的响应后就进入提交阶段完成事务,失败选择回滚或不断重.等参与者响应所以可能会阻塞整个协议,重复提交会导致数据重复,解决:时间超时回滚,加入心跳机制,数据重复用幂等性处理
3PC(强一致性事务数据库层面) 加了参与者超时机制并通过引入预提交阶段来使得参与者之间的状态得到统一,协调者向参与者发送准备请求,等待参与者的响应.也不能保证数据一致,性能有所下降,等待时也会阻塞
TCC(补偿性事务,业务层面可以跨数据库Try - Confirm - Cancel)尝试提交,提交和回滚,撤销和确认失败会重试需要保证操作的幂等,重试失败则需要人工处理
Saga长事务模型(补偿性事务)只允许两层嵌套,无ACID保证:原子性和隔离性不能满足,通过saga log可以保证一致性和持久性。一个分布式事务拆分为多个本地事务,本地都有执行和补偿模块(TCC中的Confirm和Cancel)。
本地消息表(最终一致性)就是用系统本地的事务来实现分布式事务调用失败后台任务定时去读取本地消息表,异步投递,增大了消息重复投递的可能性,重试要保证对应服务的方法是幂等的,
消息事务RocketMQ(最终一致性)第一步先给 Broker 发送事务消息即半消息这个消息对消费者来说不可见, RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令,RollBack 那么订阅方收不到这条消息
最大努力通知本地消息表有后台任务定时去查看未完成的消息,然后去调用对应的服务,一个消息多次调用都失败的时候记录下引入人工,或者直接舍弃。算是最大努力了。
事务消息也一样,如果订阅者一直不消费或者消费不了则一直重试,最后进入死信队列。其实这也算最大努力。最大努力通知表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。适用于对时间不敏感的业务,例如短信通知。
区别:对于关系型数据库,要求更新的数据能被后访问看到,这是强一致性,如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据则是最终一致性。
Seata是支持分布式事务的框架,提供全局事务协调和本地事务管理功能
AT模式(自动提交)通过拦截数据库操作来实现事务的管理,在全局事务开始时生成全局事务ID传递给各参与者,在参与者执行数据库操作时,会将该ID作为参数传递给Seata的事务管理器。事务管理器会根据全局事务的状态,决定是提交还是回滚该事务。
TCC模式每个参与者都需要提供三个操作:尝试try确认Confirm和取消Cancel.开始时依次触发每个参与者的尝试操作,如果所有参与者的尝试操作都成功触发每个参与者的确认操作如果失败,依次触发每个参与者的取消操作以回滚事务
当使用SAGA模式时,Seata会在全局事务开始时创建一个事务日志并在执行每个参与者的操作时保存该上下文。失败时通过执行补偿操作来恢复到一致的状态
分布式锁
基于MySql的InnoDB引擎在查询语句后面增加for update
基于Zookeeper
基于redis
Redis分布式锁
setnx lock “key”在key不存在时创建,EXPIRE key 100 为 key 设置生存时间
set命令同时设置过期时间set key value EX “100” NX或lua脚本
java:jedis.set(String key, String value, “NX”, “PX”, 100)NX即key不存在进行set操作否则不操作 PX为时间
或者将Lua代码传到jedis.eval()
Redis实现消息队列lpush往list左边推元素然后自旋调用llen mq获取list长度或rpop读数据消费消息,brpop可以阻塞读取消息
String List Hash Set Zset
type key 获取key数据类型
set Stringkey “helloword” ; get Stringkey
hset Hashkey key1 “hello” key2 “world” ; hget Hashkey key1
lpush Listkey 11 ; lrange Listkey 0 3 (下标
无序set:sadd Setkey 111 ;smembers Setkey
有序set:zadd zsetkey 1 val;zrangebyscore zsetkey 0 10
Redis清空服务器的数据:flushall 清空当前库中的所有 key:flushdb
缓存雪崩:如果缓存中某一时刻大批热点数据同时过期,那么就可能导致大量请求直接访问Mysq了,解决办法就是在过期时间上增加一点随机值,另外如果搭建一个高可用的Redis集群也是防止缓存雪崩的有效手段
缓存击穿:和缓存雪崩类似,缓存雪崩是大批热点数据失效,而缓存击穿是指某一个热点ky突然失效,也导致了大量请求直接访问Mysq数据库,这就是缓存击穿,解决方案就是考虑这个热点ky不设过期时间
缓存穿透:假如某一时刻访问redis的大量key都在redis中不存在(比如黑客故意伪造一些乱七八糟的key),那么也会给数据造成压力,这就是缓存穿透,在缓存之前加一层布隆过滤器来拦截不存在的ky
Redisi和Mysql如何保证数据一致
延时双删,步骤是:先删除Rdis缓存数据,再更新Mysql,延迟几百毫秒再删除Redis缓存数据,这样就算在更新Mysq时,有其他线程读了mysql,把老数据读到了Redis中,那么也会被删除掉,从而把数据保持一致
Redis持久化AOF(append only file执行命令后记录写操作日志追加到AOF文件末尾,风险:执行命令没记录日志宕机数据丢失,阻塞下一个操作
三种写回策略always同步写回,子命令执行完立即将日志写磁盘
everysec命令执行完日志写到AOF内存缓冲区一秒同步一次到磁盘(优
no只写到AOF内存缓冲区,由操作系统决定何时写入磁盘
AOF重写机制文件太大为提高性能和恢复速度,建新的AOF文件包含数据库重建需最小指令集合 异步执行过程非阻塞,可手动/64MB自动触发
默认RDB快照的形式存时刻的数据在磁盘上dump.rdb二进制文件数据恢复快 会丢失数据 自动触发m秒内n次修改调用bgsave异步,手动save同步会阻塞
如果数据不能丢失两者混用
Dubbo核心组件
Provider:服务的提供方
Consumer:调用远程服务的服务消费方
Registry:服务注册和发现的注册中心
Monitor:统计服务调用次数和调用时间的监控中心
Container:服务运行容器
Dubbo负载均衡策略,配置的形式不仅支持代码配置,还支持Dubbo-admin控制台灵活动态配置。负载均衡的算法可以精准到某个服务接口的某个方法。
1,随机:从多个服务提供者随机选择一个来处理本次请求,调用量越大则分布越均匀,并支特按权重设置随机概率
2,轮询:依次选择服务提供者来处理请求,并支持按权重进行轮询,底层采用的是平滑加权轮询算法
3,最小活跃调用数:统计服务提供者当前正在处理的请求,下次请求过来则交给活跃数最小的服务器来处理
4,一致性哈希:相同参数的请求总是发到同一个服务提供者
Feign只支持N种策略:轮询\随机\ResponseTime加权。负载均衡算法是Client级别的。
Dubbo是如何完成服务导出的?
1.首先Dubbo会将程序员所使用的@DubboService注解或@Service注解进行解析得到程序员所定义的服务参数,包括定义的服务名、服务接口、服务超时时间、服务协议
等等,得到一个ServiceBean。
2.然后调用ServiceBean的export方法进行服务导出
3,然后将服务信息注册到注册中心,如果有多个协议,多个注册中心,那就将服务按单个协议,单个注册中心进行注册
4,将服务信息注册到注册中心后,还会绑定一些监听器,监听动态配置中心的变更
5,还会根据服务协议启动对应的Web服务器或网络框架,比如Tomcat、.Netty等
Dubbo支持多传输协议(Dubbo、Rmi、http、redis等等)非常灵活。默认的Dubbo协议利用Netty,TCP传输,单一、异步、长连接,适合数据量小、高并发和服务提供者远远少于消费者的场景。
Feign基于Http传输协议,短连接,不适合高并发的访问。
dubbo分布式事务解决:Seata为用户提供了 AT、TCC 和SAGA事务模式。消费者端方法上@GlobalTransactional
Elasticsearch是一个基于 Lucene 的搜索引擎,Lucene就是一个jar包,里面包含了各种建立倒排索引的方法。
它提供了具有 HTTP Web 界面和无架构 JSON 文档的分布式,分片式的全文搜索引擎。
es为每个term(字段)都建立了倒排索引,而[1,2]就是Posting List就是一个int的数组,相当于分组 这个字段属于那一组里,在mysql里一条数据里的所有字段都属于这一组。
es为了能快速找到某个term,将所有的term排个序,二分法logN的效率查找term,就像通过字典查找一样,这就是Term Dictionary
Term Index就像字典里的索引页一样,A开头的有哪些term,可以理解term index是一颗树先定位A开头的 再找AA开头的。这棵树不会包含所有的term,它包含的是term的一些前缀.
数据写入:数据会优先写入到内存缓存中,此时新写入的数据还不可以被检索,达到默认时间1s或者内存的数据达到一定量时会触发一次刷新Refresh将内存中的数据生成到新的段上并缓存到文件缓存系统上,稍后再被刷新到磁盘中生成提交点,这时新写的数据就可以被检索了。这也是为什么ES被称为近实时检索的原因。索引文件被拆分为多个子文件,每个子文件叫作段, 每一个段本身都是一个倒排索引,并且段一旦被写入硬盘,就不可再修改。在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。新增新数据只需要对当前文档新增一个段就可以了。删除:由于不可修改,所以对于删除操作,不会把文档从旧的段中移除而是通过新增一个.del文件,文件中会列出这些被删除文档的段信息。这个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。
更新:不能修改旧的段来反映文档的更新,更新相当于是删除和新增这两个动作组成。会将旧的文档在 .del文件中标记删除,然后文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就会被移除。
冲刷flush:将内存中的分段提交到磁盘在可被检索的过程中