熟练掌握 JAVASE 基础语法,熟悉集合框架底层、熟悉并发编程,熟悉理解锁 熟练掌握 Redis 的使用,熟悉 Redis 的持久化机制、哨兵原理、集群脑裂及双写一致性问题 、缓存穿透、缓存击穿、缓存雪崩、主从复制 熟练使用 Dubbo、Zookeeper,以及了解底层 RPC 原理,Zookeeper 分布式锁,Leader 选举,集群同步等 熟练使用各种框架,Spring、Mybatis,Mybatis-plus、SpringMvc、SpringBoot 等 熟练使用微服务框架 SpringCloud,了解各个组件 了解 JVM、垃圾回收机制 熟练使用 MySQL,Oracle 等关系数据库,掌握基本的 sql 调优及原理 熟悉消息中间件,Rabbitmq、Rocketmq、Kafka 熟悉消息丢失、消息重复消费、消息乱序、消息堆积解决方案 熟练掌握 Linux 系统的基本操作,熟悉 Nginx、Docker、ElasticSearch、FastDFS 等 熟练使用 java 开发工具,Ecplise 、IDEA,及代码管理管理 Git、SVN,及 MAVEN
Java 集合体系有什么?
集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue) 和 map(映射)
1. Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
2. Iterator:迭代器,可以通过迭代器遍历集合中的数据。
3. Map:是映射表的基础接口。
ArrayList 底层结构是数组,底层查询快,增删慢
LinkedList 底层结构是链表型的,增删快,查询慢
Voctor 底层结构是数组 线程安全的,增删慢,查询慢
List
ArrayList 线程不安全,查询速度快
l Vector 线程安全,但速度 慢,已被 ArrayList 替代
l LinkedList 链表结果,增删速度快
l TreeList 树型结构,保证增删复杂度都是O(log n),增删性能远高于 ArrayList和LinkedList,但是稍微占用内存
Set
Set:元素是无序(存入和取出的顺序不一定一致),元素不可以重复。
u HashSet:底层数据结构是哈希表, 是线程不安全的, 数据不同步。
HashSet 是如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode 和 equals 来完成。 如果元素的 HashCode 值相同,才会判断 equals 是否为 true。 如果元素的 hashcode 值不同,不会调用 equals。 注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的 hashcode 和 equals 方法
l TreeSet:底层数据结构是二叉树,存放有序:TreeSet 线程不安全可以对 Set 集合中的元素进行排序。通过 compareTo 或者 compare 方法来保证 元素的唯一性。
Map
Correction、Set、List 接口都属于单值的操作,而 Map 中的每个元素都使 用 key——>value 的形式存储在集合中。 Map 集合:该集合存储键值对, 是 key:value 一对一对往里存, 而且要保证 键的唯一性
Map 接口的常用子类
l HashMap:底层数据结构是哈希表,允许使用 null 值和 null 键,该集合 是数据不同步的,将 hashtable 替代,jdk1.2.效率高。
l TreeMap:底层数据结构是二叉树,线程不同步,可以用于给 map 集合中 的键进行排序
hashmap
结构:
1.7 数组+链表
1.8 数组+链表+红黑树
1.7 采用头插法
新的值会取代原有的值,原有的值就会顺推到链表中取,目的是为了提升查询的效率
1.8 采用尾插法
为了安全,防止环化
扩容:
1.7的扩容
将数组的长度变为原来的两倍,然后将原来的元素放到新数组上,即将旧数组先遍历数组,在遍历链表,将数据全部取出,然后通过hash算法,就能得到该数据在新数组的索引值
1.8的扩容
看hash和oldcap的&值是否为0,如果为0,则新旧数组不变,如果不是0,则新数组的索引应该是原索引+oldcap长度的索引
hash
将任意长度的输入,转变成固定长度的输出,该输出的值就是散列值,当前存储数据的结构就称为哈希表,所对应得关系得方法,称为散列函数
hash算法及hash冲突
算法:高低16位取异或的值^2的n次幂-1
高低16位取异或:
1.让整合的hash能够更多位参与到运算
2.采用异或,异或可以让0,1尽可能更平均
2 的n次幂-1 = 数组的长度-1
1.能够让数据能够更好的落在数组的奇数或者偶数位上===》更加均匀的排列
2 能够让数据都落在数组索引上
3 让hash值有意义,因为底层采用的是2进制,所以只有是1,才能保证hash值有作用,如果是0,不管hash值为多少,并过的值永远是0
HashMap发生hash冲突之后,他是如何解决的
如何解决 数学的角度上:链式地址法 hashMap的解决方案 : :线性探测法 hash冲突有什么后果:效率会变低,会形成链表,查询速度就会变慢 hash冲突能够解决吗? :不能,只能尽可能去避免
Hashtable
跟HashMap相比Hashtable是线程安全的,适合在多线程的情况下使用,但是效率可不太乐观。
Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。
Hashtable使用的是安全失败机制(fail-safe),这种机制会使你此次读到的数据不一定是最新的数据。
快速失败(fail—fast)是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
如果你使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。
初始化容量不同:HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
扩容机制不同:当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
在源码中,他在对数据操作的时候都会上锁,所以效率比较低下。
reentrantlock
ReentrantLock是可重入的互斥锁
在ReentrantLock中,它对AbstractQueuedSynchronizer(aqs)的state状态值定义为线程获取该锁的重入次数,state状态值为0表示当前没有被任何线程持有,state状态值为1表示被其他线程持有,因为支持可重入,如果是持有锁的线程,再次获取同一把锁,直接成功,并且state状态值+1,线程释放锁state状态值-1,同理重入多次锁的线程,需要释放相应的次数。
AQS使用一个volatile修饰的私有变量来表示同步状态,当state=0表示释放了锁,当sta-te>0表示获得锁。
currentHashMap
1.7 采用的是分段式锁
数据结构:reentrantlock+segment+hashEntry
每个段就是一个segment,这个segment就是对应的锁,在操作过程中,如果没有去操作同一个节点,就不存在阻塞问题
1.8
采用的是volatile+cas+syn
线程安全的map
1 初始化:
在源码中有一个while循环,所有的线程都会进入到while循环中,然后进行cas,如果进入到cas的线程,会进行map的初始化,而其他没有进入到cas的线程,会进行线程的礼让,释放cpu的执行权,这就保证在多个线程的情况下,只有一个线程在进行初始化
2 put
根据key计算出hash值
判断是否需要初始化
即为当前key定位出的Node,如果表示为空,则表示当前位置可以写入数据,利用cas尝试写入。失败则自旋保证成功
如果当前位置的hash值==moced==-1,则需要进行扩容
如果都不满足,则利用syn锁写入数据
(之所以这个要用 syn,而不单纯采用cas,是因为这个地方除了 插入逻辑以外,你还要遍历的逻辑也是线程安全)
如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。
gets
根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
如果是红黑树那就按照树的方式获取值。
就不满足那就按照链表的方式遍历获取值。
volatile
可见性 原子性 有序性
如果一个变量被volatile关键字修饰,那么所有线程都是可见的。
volatile是一个变量修饰符,只能用来修饰变量。
底层原理
当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CPU cache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。
可见性
对于volatile关键字修饰的变量:多线程下jvm会为每个线程分配一个独立的缓存来提高效率,当一个线程修改了该变量后,另外一个线程立即可以看到
禁止指令重排序
指令重排序是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
指令重排序可能会带来的问题
如果一个操作不是原子的,就会给JVM留下重排的机会。
volatile关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
CAS
内存值V、预期值A、要修改的新值B
CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。
线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。
ABA问题?
就是说来了一个线程把值改回了B,又来了一个线程把值又改回了A,对于这个时候判断的线程,就发现他的值还是A,所以他就不知道这个值到底有没有被人改过,其实很多场景如果只追求最后结果正确,这是没关系的。
但是实际过程中还是需要记录修改过程的,比如资金修改什么的,你每次修改的都应该有记录,方便回溯。 解决方法:
用版本号去保证就好了,就比如说,我在修改前去查询他原来的值的时候再带一个版本号,每次判断就连值和版本号一起判断,判断成功就给版本号加1。
时间戳也可以,查询的时候把时间戳一起查出来,对的上才修改并且更新值的时候一起修改更新时间,这样也能保证
面向过程与面向对象的区别
面向过程主要是针对功能,而面向对象主要是针对能够实现该功能的背后的实体
面向对象实质上就是面向实体,所以当我们使用面向对象进行编程时,一定要建立这样一个观念:万物皆对象!
都可以实现代码重用和模块化编程,但是面对对象的模块化更深,数据更封闭,也更安全!因为面向对象的封装性更强!面对对象的思维方式更加贴近于现实生活,更容易解决大型的复杂的业务逻辑从前期开发角度上来看,面对对象远比面向过程要复杂,但是从维护和扩展功能的角度上来看,面对对象远比面向过程要简单!
线程的实现方式
1.继承 thread 2.实现 runanble 3.实现 callAble 3.1 可以抛出异常 3.2 可以接受返回值 3.3 阻塞 callable 需要和FutureTask 配合使用 4.使用线程池
线程的生命周期
新建状态:指的就是线程被new 出来 就绪状态:你去调用start方法,但是他还没有得到cpu的分配 运行状态: 已经跑起来了,已经得到cpu的分配权利 阻塞状态: 就是遇见了wait,sleep 之类的东西我,就会被阻塞 ,sleep会自动变为就绪状态(而且sleep 不会释放锁),而wait(会释放锁资源)必要要等待notify唤醒 死亡状态:运行结束后,就进入到死亡状态
线程池的7大参数
长足线程数 corePoolSize
最大线程数 maximumPoolSize
阻塞队列 workQueue
拒绝策略 defaultHandler
默认工厂 Executors.defaultThreadFactory()
多余的空线程的存活时间 keepAliveTime
时间单位 unit
线程池在创建之后,并不会立刻创建线程,而是当任务过来后,我们线程池会去判断当前线程池中的线程是否小于常驻线程,如果小于则创建线程,直到线程池中的线程不再小于常驻线程,任务交给线程去处理,如果常驻线程处理不过来之后,把这个任务交给阻塞队列,如果阻塞队列都满了之后,此时会开辟最大线程,如果最大线程和阻塞队列都满了的话,此时就会拒绝策略。
拒绝策略
1.丢弃 2.抛出异常 3.交给方法调用者 4.丢弃队列中等待时间最久的那一个
工作中使用哪种线程池
在工作中咱们不会使用executors创建线程池,因为如果使用可伸缩的线程池,整体线程数无法控制,也不使用单个线程线程池,也不能使用 固定长度大小线程池 因为他的底层是一个 linkedBlockingQueue,而这个queue,他会开辟一个Integer.maxValue大小的阻塞队列,这样的太占用内容 不使用executors 线程池他是 阿里规范上边明确指出的 我们怎么使用: 1.使用第三方线程池 2.自定义线程池
工作中如何配置线程池
注意:这个地方有一个理论依据,但是你不要说是你配的 cpu型:进行了大量计算这种操作 cpu+1 io 型:除了cpu就是io 2 * cpu+1 具体地配置应该是由 压力测试工具 ,比如jmeter 这样的压测工具,在一定强度下压测出来的最终结果
这个是由 老大来配置的,只是去了解了一下
CountDownLatch
public void createMappingAndIndex() {
//创建索引
elasticsearchTemplate.createIndex(SkuInfo.class);
//创建映射
elasticsearchTemplate.putMapping(SkuInfo.class);
}
ExecutorService threadPool = Executors.newFixedThreadPool(5); //不建议使用
//导入全部sku集合进入到索引库
@Override
public void importAll() {
// pageHelper.startPage(1,1000) 第一页 , 1000条
// pageHelper.startPage(2,1000) 第二页 1001~ 2000条
// 首先确定他有多少页 总记录数 10001 / 每页显示条数 1000== 0? 10+1 countDolnlatch
int pageSize = 1000;
int allCount = skuFeign.selectCount();
int allPage = allCount % pageSize == 0 ? allCount / pageSize :allCount / pageSize + 1;
CountDownLatch cdl = new CountDownLatch(allPage);
for (int page= 1; page <= allPage; page++) {
threadPool.submit(new TaskThread(page,pageSize,cdl));
}
// 当所有分线程走完之后,他就应该不再阻塞
// 什么时候 countdown 完成一个线程 countdown 一次
// 变量是多少 一共有多少个线程要执行
try {
cdl.await();
} catch (IsnterruptedException e) {
e.printStackTrace();
}
为什么要使用分布式锁
在采用分布式架构后,程序会有多个实例,则就会有多个锁对象,但不管是lock或者syn都不能保证锁住的是同一个对象
redis的分布式锁
利用redis的setnx和setex
具体实现: 设定一个字符串,将该字符串写入到数据库中,如果redis中没有该条数据记录,则写入成功,返回1.当client拿到的返回值为1,则表明拿到了锁,那就会继续执行自己的业务逻辑,使用完后会将redis中的记录删除,如果写入失败,则返回0,当client拿到的值为0,则表明获取锁失败,那么将会进行自旋抢锁。
缺陷:不能防止锁 会删除其他的锁 无法进行续约逻辑 不能保证公平性
解决办法:使用redission
redission
是redis提供的对于分布式支持的lock锁,底层原理是看门狗原理
redission有两种模式
1 如果你没有传入参数,则lock以-1接受(底层源码执行的),如果传入了参数,则使用该参数
2当获得当前锁的线程,则会去判断leasTime是否!=-1,
3 如果不等于-1,则表明传入了过期时间
3.1 不会续期
3.2 锁以传入的过期时间作为锁失效的时间
3.3 到期后直接返回
4 如果等于-1,则表明没有传入过期时间,则会使用系统默认的过期时间,即看门狗原理
5 底层以一个taskTimer进行续约逻辑,即看门狗的默认时间为30s,那么每过1/3看门狗时间就进行预约,即每过10秒执行续约逻辑
zk的分布式锁
核心理论:当客户端获取锁,则创建临时节点,使用完锁,删除节点
具体实现:
1. 客户端获取锁时,在lock节点下创建临时顺序节点
2. 然后会获取到lock下的所有子节点,当客户端获取到节点后,会去验证该节点是否为最小节点
3. 如果发现是最小的节点,那么说明自己获取到锁,然后执行自己的业务逻辑,使用完锁后会删除该节点
4. 如果发现不是最小节点,说明未获取到锁,那么会去找到那个最小的节点,并给该节点绑定一个监听事件器,监听删除事件
5. 如果发现该节点被删除后,客户端的watcher会收到通知,然后客户端会再次判断自己的节点是否为最小节点
6. 如果为最小节点,则表明获取到锁,执行自己的业务逻辑,使用完后删除即可
7. 如果依旧不是最小节点,那么表明仍为获取到锁,继续执行之前的获取那个最小节点的步骤
syn
synchronized
jvm层面的锁
sny其实锁住的是一个对象,对象布局为
1 实例数据
2填充数据
3 object-header
里面包含了: mark—word :锁信息 hashcode gc标志位 gc的年龄
kclass—pointer :即一个指针压缩,它最终会指向处于方法类的模板信息
锁的升级
为什么会产生锁升级?
在jdk1.5以前,只要经过了syn,那么不管处于什么状态,都会进行用户态和内核态的切换,导致性能的极限下降
偏向锁
在锁对象的对象头中会记录当前获取到该锁的线程id,该线程下次如果又来获取该锁就可以直接获取
轻量级锁
由偏向锁升级而来,当一个线程获取到锁以后,此时这把锁是偏向锁,如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程
自旋锁
自旋锁就是线程在获取锁 的过程中,不会去阻塞线程,也就无所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较消耗时间,自旋锁是线程通过cas获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到锁,这个过程线程一直运行中,相对而言没有使用太多的操作系统资源,比较轻量。
重量级锁
如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
lock
lock是java层面的锁
他分为公平锁和非公平锁
Lock锁可重入、等待时可中断、可判断、可公平可非公平
原理
如果是一把非公平锁:先进行一次cas ,如果cas 失败,就会去执行自旋1.获得state 变量,判断state 是否是个0 ,如果不是0 表示有人正在持有锁,如果是0 ,则判断队列是否有元素,如果队列没有元素,则cas 抢锁,如果当前锁 被人持有,他就判断是不是自己持有这把锁,如果是,则表明现在是可重入状态-> 将state 加1 ,如果需要排队,则 lock 锁,将线程放置到 双向链表中进行排队,他会让我们线程中的元素 进行LockSupport.park(),阻塞,如果我们持有锁的线程此时执行完之后,他会将队列中的第一个元素唤醒 -> 他就和其他线程进行抢锁,如果抢成功,被唤醒的线程就会执行,抢锁失败,则继续阻塞
synchronized和lock的区别(8点)
1.来源及用法: lock是一个接口,是java写的控制锁的代码,而synchronized是java的一个内置关键字,synchronized是托管给JVM执行的; synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
2.异常是否释放锁: synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)
3.是否响应中断 lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;
4.是否阻塞 Lock可以尝试获取锁,synchronized获取不到锁只能一直阻塞 synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.锁特点 synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、等待时可中断、可判断、可公平可非公平
6.性能差别 1.在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。 如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。而且Lock可以提高多个线程进行读写操作的效率。(可以通过readwritelock实现读写分离)
7.等待通知 synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度 8.锁机制 1.synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。 2.Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。 现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
持久化机制
rdb
rdb会每隔一段时间如果发生了指定次数的写操作,那么会将内存中的数据写到磁盘当中,生成一个dump.rdb的文件,恢复数据时,直接=读取磁盘中的数据即可
原理:在底层fork出一条子进程,和主进程一摸一样,用来操作数据的持久化,在此过过程中主进程不会进行任何写操作,这也就保证了rdb的高效性,持久化操作的底层原理为bgsave,即异步进行持久化
aof
以日志的方式记录用户的每一次写操作指令,当数据需要恢复时,再次去执行日志记录的指令即可
aof默认是关闭的,需要手动打开
aof有一个重写机制:把命令进行压缩,对aof文件进行瘦身
重写的条件:
大小大于64mb
2.成为原来大小的1倍
aof的同步机制:
1 always:每执行一次操作就持久化一次
2.every;每过一秒就持久化一次
3 no :由操作系统决定
rdb的优势:恢复大批量的数据 劣势: 会丢失一部分数据
aof的优势:丢失数据是可控的,在数据量较少时,性能好 劣势:随著数据的增加,aof文件会越来越大,aof的效率就变低
实际应用: rdb+aof ---> rdb来恢复大面积数据 用aof 追
加rdb丢失的数据
哨兵原理
(cmd通道)
哨兵会和其他哨兵,master,salve建立cmd通道,然后哨兵会通过cmd通道发送hello指令给master,如果未收到响应,那么会哨兵会认为该master主观下线了,哨兵便通知其他哨兵也去给该master发送指令,如果超过半数的哨兵未收到响应,那么该master就会被认为客观下线,然后哨兵会进行互相投票(互相投票,谁先接收到对方的投票,就投递给谁),选举出一个哨兵来选出一个salve成为新的master,选举的机制为: 排除不在线的 响应时间久 断开时间长的
会有一个优先级策略:slave-priority : 参数越小,优先级越大 offset 偏移量(数据的更新量) runid(事先设置好的)
集群原理
redis的集群有16384个槽,当连接到数据库时,连上集群的某个节点后,会计算出当前key应当存放在哪个槽,然后会将数据存放到对应的槽点
集群脑裂
在master还未将数据同步到slaver中,发生网络波动,master和salve不在一个网段,而把哨兵和salve分在一个网段,导致该master被哨兵认为客观下线,会选举出salve成为新的master,当网络恢复后,会出现两个master,不管哪一个master重新变为salve,都会导致数据的丢失。
解决方案:配置文件中设置, 保证master至少有多少个salve 还有联系
异步数据丢失
在master将rdb数据发送给salve后,master将会接收新的操作数据,在接收到新数据后,将数据写到缓冲区,还没同步到salve,mater就宕机了,然后哨兵会选举salver为新的master,那么缓冲区的数据就会丢失,
解决方案:在配置文件中设置 min-slaves-max-lag 10 延迟最多多久
双写一致性问题
双写方案
先写数据库,再写缓存
产生问题:在高并发情况下,造成数据库的数据与缓存不一致
失效方案
写完数据库,删除缓存
产生问题:在高并发的情况下,在用户在写数据的时候,另外的用户也来操作数据库,在将数据写到缓存时,老用户把自己的数据更新到缓存,造成数据库与缓存不一致
解决:1) 加一把读写锁
2)给redis的key加一个过期时间 添加的时间--》表明可以忍受这一部分时间的脏数据
3)queue+自旋
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大
解决方案:
1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存 有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻 击用户反复用同一个id暴力攻击
缓存击穿
某个key过期,此时多个请求同时访问该条数据,再缓存中读取不到,请求会直接到数据去查询,造成数据库的压力过大
解决:1 设置热点数据缓存永不过期
2 加互斥锁
缓存雪崩
1 redis宕机后,大量的请求直接落到数据库
2 大量的key同时过期,所有的请求直接发送到数据库
解决方案:
1 事前:将redis配置成一个高可用的架构:哨兵 salve
2 事中:hystrix 实现限流+降级+本地缓存=从而保证数据库不被一瞬间击垮
3 事后:提前对redis进行持久化,然后在重启redis服务时,可以将持久化的数据加载出来供系统使用 --》缓存预热--》将热点数据加入到redis中
本地缓存:ehcache 查询时,先查询本地缓存,本地缓存没有在查询redis,redis没有,再查询数据库,这时候再去将数据库中的数据同步到ehcache和redis中
hystrix: 熔断器 到达设定的阈值,则熔断器打开,拒绝请求
限流:为了防止流量一下子过大而直接冲垮系统的一种技术
降级: 退而求其次的方案,对于多余的请求,进行降级处理,即告知友好提示 :请稍等,服务器正在升级,空白页面做出响应,而不是直接去访问数据库获取数据
redis实现延时队列
Redis的key过期回调事件,也能达到延迟队列的效果,简单来说我们开启监听key是否过期的事件,一旦key过期会触发一个callback事件。
redis的数据结构
string set zset hash list
redis的过期策略和淘汰机制
过期策略:三种
定时删除:给key设置过期时间,且到达过期时间后,由定时器任务立即执行对键的删除
用处理器性能换取存储空间(拿时间换空间)
惰性删除:当key到达过期时间后,不做处理,当下次访问该数据时,如果发现已过期,便会删除该数据,并返回一个nul
总结:用存储空间换取处理器性能(拿空间换时间)
定期删除:定期性的轮询redis库中的时效性数据,采用随机策略,利用过期数据的占比的方式控制删除频度
总结:周期性抽查存储空间(随机抽查,重点抽查)
淘汰机制
3类8种
1.易丢数据
最近最少使用
最近使用次数最少
过期数据
随机淘汰
2.全库数据
最近最少使用
最近使用次数最少
过期数据
3.放弃数据追逐
redis的主从复制
全量更新
简单说:在建立连接时,salve会将自己的数据进行清空后,master会给salve走一个全量更新,后续更新走增量更新
具体实现:1 salve与master建立连接
2 master会发起全量更新,生成一个rdb文件,将数据全部发送给salve,并且master还会发送以一个offset的偏移量和runid给salve
3 salver再接收数据前,会将自己的数据清空,然后接受master的数据
4 再此过程中,master再次接收到数据,会将数据写到缓存区,offset会记录写了多少数据
5 salve同步完成过后,master会将缓存区的数据发送给salve
6 如果master发送的offset和salve一致,那么就说明master同步过程中没有数据更新
7 如果master发送的runid与salver不一致,并且offset不在主机的offset的范围内,那么salve会走全量更新
8如果master发送的offset在主机的offset范围内,那么salve会走增量更新(即将多余的offset发送给salve)
Redis热点数据如何处理
一:凭借业务经验,预估某些热key
二:在客户端进行收集
三: 在proxy层做收集
四: 用redis自带命令:monitor命令 hotkeys参数
五: 自己抓包评估
保证数据库全部为热点数据
合理使用淘汰机制
具体: 限定redis的占用内存 ,redis会根据自身的淘汰策略,留下热点数据
使用策略规则:
1 如果数据一部分访问频率高,一部分低,则采用allkeys-lru 挑选最近最少使用的数据淘汰
2 如果数据的访问频率差不多一致,则采用allkeys—random 任意选择数据淘汰,相当于随机
redis的单线程为什么这么快
1)纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis将数据 存储在内存里面,读写数据时都不会受到磁盘i/o流的限制,所以速度快
2)单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多进程导致的切换而消耗cpu,不用考虑各种锁的问题,不存在加锁释放锁操作,没有因为出现死锁而导致的性能消耗
3)采用了非阻塞i/o多路复用机制
五 Dubbo
原理:dubbo是阿里巴巴公司开源的一个高性能,轻量级 的java RPC框架
dubbo(写一个接口)才能被调用
Dubbo作为一个RPC框架,其最核心的功能就是要实现跨网络的远程调用。
rpc原理
当发起请求时,
消费者会根据拉取的服务列表获取到多个生产者的信息
通过调用接口实现动态代理(proxy)
然后通过cluster的集群管理的负载均衡策略(loadbalance)插件从而得知应该调用哪台服务器
并且通过protocol会规定一个上层协议
将对应的数据进行封装一个request请求(exchange)
然后会将数据进行序列化后通过底层框架的netty、mina的底层网络通信协议进行数据的传递
然后生产者反向解析传递的数据,再调用上层协议,
然后获取到代理对象,就可以去调用就具体的生产者的接口
服务器容器负责启动,加载运行服务提供者
服务提供者早启动时,想注册中心注册自己提供的服务
服务消费者在启动时,向注册中心拉取自己所需的服务
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将 基于长连接推送变更数据给消费者。
消费者会从生产者提供的地址列表中,基于软负债均衡算法,选取一台生产者进行调用,如果调用失败,会再选取另外一台调用
消费者和生产者会将在内存中调用的次数和时间定时发送到监控中心
默认协议
连接个数: 单连接 连接方式:长连接 传输协议:tcp 传输方式:nio异步传输
序列化:hession 二进制序列化 适用范围:传入传出的参数数据包较小
使用场景: 常规远程服务方法调用
负载均衡
1 轮询:带权重
2 随机:带权重
3 一致性哈希
4 动态感知(最少活跃调用数)
注册中心挂了,服务是否可以正常访问?
可以,因为dubbo服务消费者在第一-次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。
当服务提供者地址发生变化时,注册中心会通知服务消费者。
dubbo支持注册中心有哪些
zookeeper redis
超时机制与重试
在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
解决:dubbo利用超时机制来解决这个问题,设置-个超时时间, 在这个时间段内,无法完成服务访问,则自动断开连接。
重试:设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。如果出现网络抖动,则这一-次请求就会失败。Dubbo提供重试机制来避免类似问题的发生。
六 Zookeeper
zookeeper是一个分布式的、开源的分布式应用程序的协调服务
zab协议:
一套规定zk如何进行数据同步,和数据恢复的协议
zookeeper原理
zk是分布式协调框架、注册中心
zk的本质:文件系统+通知机制
文件系统:
在zk的原生角度,其实就是一个树结构:znode
在java的层面:concurrenthashmap 服务列表
通知机制:使用观察者模式,即消费者去注册中心的服务列表到本地,如果zk发现生产者产生了任意改变,那么zk回去通知消费者重新到注册中心拉取最新的数据
数据同步2pc
1.客户端只能处理读请求,如果遇见写请求,会将请求转发给leader
2.当leader收到转发消息或者客户端写数据时,zk会将数据写到磁盘文件中,但不会立即提交
3.leader会向follower广播发起2pc提交中的提议请求,follower接收到消息后,会写入到自己的日志中,并回复ack
4.当leader收到ack的响应超过半数,那么zk会将磁盘中的数据写到leader上,并且向follower广播一个commit指令,然后follower会提交日志文件中的数据
leader选举
选举时期:
1 集群启动的时候
2 leader宕机时
3 leader的follower超过半数未跟随时
选举策略:
1 都会投递给自己
2 会和别人的leader进行对比,对比的原则为:
以谁的数据新 zxid(事务id) zserverid(服务器id) 在集群之前就指定好了
3 如果已经获得超过半数投票的支持,此时哪怕对应的其他服务器更强也没有用
分布式锁
核心思想:客户端获取锁,则创建子节点,使用完锁,删除节点
原理:当客户端获取锁时,在lock节点下创建临时顺序节点,然后获取到lock节点下的所有子节点,客户端获取到子节点后会去对比这些子节点,如果获取的子节点为最小,那么表示客户端获取到锁,然后执行他自己的业务逻辑,使用完锁后会删除节点,如果获取到的子节点不是最小,那么表明没有获取到锁,那么它会去找到最小的那个节点,给它注册一个事件监听器,监听删除事件;当客户端发现比自己小的那个节点被删除后,客户端的watcher会收到相应通知,此时客户端会再次判断自己的节点是否为最小节点,如果是,则获取到锁,如果不是继续重复上面步骤去监听那个最小节点,
羊群效应
由始至终都只有一个key=lock的临时节点,当一个线程释放锁之后,n个线程会同时被唤醒,尝试创建临时节点,这就是常说的羊群效应
熟练使用各种框架,Spring、Mybatis,Mybatis-plus、SpringMvc、SpringBoot 等 熟练使用微服务框架 SpringCloud,了解各个组件 了解 JVM、垃圾回收机制,具有一定的线上排错经验
七 spring
spring是一个轻量级的ioc和aop的容器框架。
ioc
ioc,控制反转,即将创建对象的权力交给spring
底层:xml+工厂+反射
优点 : 高内聚,低耦合
di:依赖注入,即资源已经反转给spring容器,所需资源找spring即可
aop
aop:一种面向切面的编程,将共性的代码抽取到独立的模块,在代码重新运行时,会将抽取的内容自动织入到切点的位置
作用:权限 统计 事务
实现原理:
一、jdk动态代理实现AOP拦截
底层其实就是静态代理;他会在底层写一套反射代码,这套代码会基于你写的内容,在底层生成一套和静态代理一摸一样的代码,在他的逻辑里面,会调用编辑器对这套生成的代码进行编译,在通过你的传入类加载器把该代码加载到内存当中,然后执行,销毁
二、cglib动态代理实现AOP拦截
cglib,是一个代码生成的类库,可以在运行时动态的生成指定类的一个子对象,并覆盖其方法增强代码,从而实现aop。
注入方式
三种方式
基于注解 基于setter 基于构造器
事务管理
声明式事务
xml 注解
核心原理:利用aop来完成对编程式事务的代码控制
编程式事务
利用一些spring来创建对象管理事务
比如 transactionmanager 事务管理器
transactionstate 事务的状态
transactionbeandafinition 事务定义自定义对现象
缺点: 代码侵入性太强,学习成本太高
事务的传播行为
比如: require 如果有事务,则加入事务,如果没有事务,则创建事务
support 如果有事务,则加入事务,如果没有事务,则以非事务的方式进行
require_new 不管有没有事务,都会创建新的事务
作用域
单例
多例
线程是否安全
单例可能会产生线程安全问题,因为单例意味着内存只有一个实例,当多个对象来访问时,如果创建的成员变量没有保证原子性,那么会造成线程安全
多例不会造成线程安全
常见注解
@service
@controller
@component
生命周期
。。。。
循环依赖
。。。
八 mybatis
mybatis是一个办orm(对象关系映射)框架,内部封装了jdbc
# 和 $ 的区别
#{}是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;使用#{}可以有效的防止 SQL 注入,提高系统安全性。
Mybatis 在处理${}时,就是把${}替换成变量的值。
resultType和resultMap的区别
①当提供的返回类型属性是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis对自动的给把对应的值赋给resultType所指定对象的属性。 ②当提供的返回类型是resultMap时,因为Map不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
常见的mybatis 标签有哪些
select |insert |updae|delete
Mybatis 是否支持延迟加载
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加 载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。 它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时, 进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。
一二级缓存
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作 用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存且不能关闭。 2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache, HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自 定义存储源,如 Ehcache。默认不打开二级缓存,要手动开启二级缓存,使用 二级缓存属性类需要实现 Serializable 序列化接口( 可用来保存对象的状态),可 在它的映射文件中配置
九 Mybatis和Mybatis-plus
Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC
(#)和($)的区别
#{}是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值; Mybatis 在处理${}时,就是把${}替换成变量的值。 使用#{}可以有效的防止 SQL 注入,提高系统安全性。
2.resultType和resultMap的区别
①当提供的返回类型属性是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis对自动的给把对应的值赋给resultType所指定对象的属性。 ②当提供的返回类型是resultMap时,因为Map不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
上面两种方法都实现了查询,那resultType和resultMap有什么区别那??
resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载
————————————————
原文链接:https://blog.csdn.net/wxr15732623310/article/details/76687646
3.常见的mybatis 标签
select |insert |updae|delete
4 MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、 提高效率而生。
使用
① 导入依赖,如果有注释掉之前mybatis的依赖
②如果MyBatis plus有自定义mapper文件,地址配置的由mybatis-mapper-locations改为mybatis-plus.mapper-locations
③ 给实体类加注解
@TableName(“user”) 加载类上 将该类和数据库中对应的表建立联系 注解值为表名
@TableId(value = “对应表列名”,type = IdType.AUTO) 建立主键属性和主键字段之间的联系。 value 字段名, type 定义主键自增类型 IdType.AUTO MySQL自动递增
常规属性@TableField(“name”) 将常规属性和常规字段名建立联系
连表查询 封装数据的属性 没有对应的字段 需要定义忽略
@TableField(exist = false) exist = false代表当前属性在表中没有字段名
创建实体类对应的dao接口 继承 BaseMapper 就会拥有MybatisPlus所有默认方法
十 SpringMvc
springmvc是一个基于java的实现了mvc设计模式的请求驱动类型的轻量级web框架
请求流程
1)用户发送请求前端控制器,然后前端控制器接收到请求后,调用处理器映射器请求获取handle,处理器映射器会根据请-求的url找到具体的处理器,然后生成处理器对象以及处理器拦截器(如果有的话),然后一一返回给前端控制器,前端控制器会调用处理器适配器,然后处理器适配器经过适配,调用具体的处理器,处理器执行完成后返回modelandview,处理器适配器将处理器返回的结果返回给前端控制器,然后前端控制器将结果传给视图解析器进行解析;
解析完后返回具体的view,然后前端控制器会对view进行渲染视图;最后前端控制器将数据响应给用户
springmvc的区别
1)springmvc的接口时servlet,即前端控制器
2)SpringMVC 是基于方法开发(一个 url 对应一个方法),请求参数传递
到方法的形参,可以设计为单例或多例(建议单例),struts2 是基于类开发,传
递参数是通过类的属性,只能设计为多例
十一 SpringBoot
springBoot提供了一种快速使用spring的方式,基于约定优于配置的思想,可以让开发人员不必再配置与逻辑业务之间进行思维的切换。
自动装配
核心理论: 不用配置bean,就可以使用当前这个bean AutoConfigurationImportSelector.class
核心注解为:springbootapplication里面包含了一个核心注解是enableautoConfiguration,在里面也包含了一个核心注解:import,
这个注解引入了一个autoconfigurationimportselector.class的类,因为导入这个类,会自动执行调用这个类的selectImports的方法,他就会去加载mete-inf下的spring.facotrys,该文件里配置了所有需要被创建 spring 容器中的 bean,然后他就会判断容器中是否有这个bean,如果容器中没有,那么springboot就会帮我们创建,如果容器中有,就意味着开发人员自定义了这个bean,springboot就不会帮我们创建这个bean。
在这个类中包含了多个类的全路径,然后返回一个数组,这个数组包含了所有的类的class文件,
十二 SpringCloud
微服务
微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间一般通过 HTTP 的 RESTfuLAPI 进行通信协作。
各种架构
单体:将所有的模块放到一个项目中,耦合性很强,不利于扩展
分布式:将项目进行拆分,拆成生产者和消费者,众多模块共同的组成分布式架构,比单体的耦合性低,利于扩展
微服务:将项目进一步拆分,一个模块就是一个项目,耦合性最低,也最利于扩展
组件
Eureka:
各个服务启动时,Eureka Client 都会将服务注册到 Eureka
注册中心:eureka
1.心跳机制/服务剔除
生产者向eureka进行注册后,生产者会向eureka进行30s心跳续约,如果说在90s时间之内,都没有接受到心跳,于是eureka就会判断自己是否有保护机制,如果有,该服务就不会被删除,如果没有,则剔除
自我保护机制:其实是为了防止网络抖动而产生的误删
2.底层原理
eureka底层有一个redonly和readwrite缓存,生产者向eureka进行服务注册,一旦注册完,eureka就立刻将数据同步到readwrite缓存,该缓存绑定了一个定时器,会定期将数据同步到readonly缓存,消费者会定期去拉取readonly中的数据,完成消费者对生产者的调用
3.eureka 和zk之间的区别
3.1 拉取数据的方式
zk 靠通知,通知你之后,再去拉取
eureka 定时去拿
3.2 集群方式不一样
zk ledader和follower
eureka 没有主从之分
3.3 设计的角度不一样
cap
c:一致性
a:可用行
p:分区容错性
如果zk的leader 挂掉之后,整个zk的集群就无法对外提供服务,我们其实是可以容忍有一段时间的脏数据,但是我们不容忍整个注册中心无法对外提供服务,所以在设计时,
zk强调的cp(c 官网上是顺序一致性,底层有一个queue,消息都会放到queue,排队执行),正是因为zk有这样的问题,
eureka 在设计的时候,采用 eureka 强调的就是 ap,你想拉数据,你随时来拉数据,不会对你进行阻塞的
ngnix 负载均衡
ngnix分为正向代理和反向代理
正向代理 直接向外界暴露正向代理服务器的地址
反向代理 用于屏蔽内网web服务器细节问题,比如:别人来访问我们的服务器,我们的项目都部署在内网服务器中,外界无法访问,我们可以通过配置一个ngnix 服务器,实现反向代理,让ngnix 对外暴露,别人可以访问nginx ,再由nginx 来反向路由到我们内部的服务器,同时当有多台及其时,再配置其负载均衡
feign
具体使用
修改引导类,添加注解@EnableFeignClients开启远程调用
基于 Feign 的动态代理机制,根据注解和选择的机器,拼接请求 URL 地址,发起请求
fegin(丢数据) :可以作为你在工作中遇见印象最深的问题的是
当我们在使用fegin进行远程调用的时候,由于feign的底层会new 出来一个restTeamplate,这个restTeamplate 里边啥信息都没有,这个时候被调用方就无法拿到数据
解决:fegin提供了一个特殊的拦截器,在fegin,new 完restTeamplate之后,他会先去判断你的代码中是否实现了这个拦截器,如果实现了这个拦截器,他就会走你的拦截器逻辑(你要做的事情就把 本来要丢掉的数据放入到 fegin new 出来的那个restTeamplate中)就解决了
Hystrix
(熔断器)发起请求是通过 Hystrix 的线程池来走的,不同的服务走不同的线
我们在进行服务间调用的时,很有可能某一个服务出现了很慢的情况,这个服务就会把tomcat 线程耗掉 ,以至上游链路全部耗死 ,一个服务宕掉或者是超时等等情况,就会导致雪崩
雪崩问题的解决
1.线程池隔离(比较损耗性能-> 线程上下文切换,但是他更灵活) 2.信号量隔离(更常用这个) 其实底层就是一个变量,几乎对于性能没有什么影响 熔断器本身的原理
在链路调用过程中,如果失败的次数达到了阈值,熔断器会打开,如果打开之后,就没有请求能够被放行,过了段时间,熔断器会处于半开状态,放一个请求,判断这个请求是否正常调用,如果正常调用,熔断器就会关闭,如果调用再次失败,又会处于打开状态
网关
网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控、路由转发等
在目前的网关解决方案里,有Nginx+ Lua、Netflix Zuul 、Spring Cloud Gateway等等
Spring Cloud config
Spring Cloud Config 解决了在分布式场景下多环境配置文件的管理和维护。 • 好处: 集中管理配置文件 不同环境不同配置,动态化的配置更新 配置信息改变时,不需要重启即可更新配置信息到服务
gitee搭建远程仓库
1.编写仓库名称、仓库路径、公开(公开的比较方便)
2.语言和模板可以不选,一般使用单分支模型,只创建master分支
3.使用小乌龟工具,将远程仓库clone到本地
4.使用小乌龟工具将配置文件提交到远程仓库
-config server搭建
config server:
使用gitee创建远程仓库,上传配置文件
搭建 config server 模块
导入 config-server 依赖
编写配置,设置 gitee 远程仓库地址
config client:
导入 starter-config 依赖
配置config server 地址,读取配置文件名称等信息
创建配置文件bootstrap.yml
获取配置值
@Value("${itheima}")
private String itheima;
config client刷新
在 config 客户端引入 actuator 依赖
获取配置信息类上,添加 @RefreshScope 注解
/**
* Goods Controller 服务提供方
*/
@RestController
@RequestMapping("/goods")
@RefreshScope // 开启刷新功能
public class GoodsController {
@Autowired
private GoodsService goodsService;
@Value("${server.port}")
private int port;
@Value("${itheima}")
private String itheima;
...
}
添加配置management.endpoints.web.exposure.include: refresh
使用curl工具发送post请求 curl -X POST http://localhost:8001/actuator/refresh
十三 MySQL
MySQL是一个关系型数据库管理系统
索引
本质是数据结构
优势:b+tree的数据结构,可以表示上百万的数据,所以他查询上百万的数据只需要三次i/o
原理:
btree的数据结构:
每个节点不仅包含key值,还包含了数据,会增加查询数据时 磁盘的io次数
b+tree的数据结构:
非叶子节点,只存储key值,所有数据存储在叶子节点上,所有叶子节点之间都有连接指针
索引越多越好吗
数据量小的表不需要建立索引,因为建立索引会增加额外的开销。
数据变更需要维护索引,因此更多的索引意味着更多的维护成本。
更多的索引意味着更多的存储空间。
使用主键自增的原因
自增的主键的值是顺序的,所以Innodb把每一条记录都存储在一条记录的后面。当达到页面的最大填充因子时候(innodb默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的 修改):
①下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费
②新插入的行一定会在原有的最大数据行下一行,mysql定位和寻址很快,不会为计算新行的位置而做出额外的消耗
③减少了页分裂和碎片的产生
索引失效情况
1 在索引列上进行运算操作,将会索引失效
2 字符串不加单引号,索引失效
3 用or分割开的条件,如果or前的条件中有索引,后面列没有索引,则会索引失效
4 以%开头的like模糊查询,会导致索引失效
5 当全表扫面的速度比走索引快,索引会失效(当查询条件中的数据值占整个数据比例特别大时,会判断全表扫描比索引快,所以使用全表扫描)
6 is null, not null
当需要查询的字段中null值占绝大部分时,is null不走索引
当需要查询的字段中not nul的数据占绝大部分时,is not null不走索引
7 in走索引,not in 索引失效
提高索引查询效率的方法
尽量使用覆盖索引(只访问索引的查询(索引列完全包含查询列)),避免select *
不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引
如果查询列中有没包含索引的,将会进行回表查询(去访问主键索引获取信息)
锁机制
操作分类
共享锁:也叫读锁。针对同一份数据,多个事务读取操作可以同时加锁而不互相影响,但是不能修改数据
排他锁:也叫写锁。当前的操作没有完成前,会阻断其他操作的读取和写入
粒度分类
表级锁:会锁定整个表。开销小,枷锁快。锁定力度大,发生锁冲突概率高,并发度低,不会出现死锁情况
行级锁:会锁定当前行。开销大,加锁慢。锁定粒度小,发生冲突概率小,并发度高,会出现死锁情况
使用方式分类
悲观锁:每次查询数据时都认为别人会修改,很悲观,所以查询时加锁
乐观锁:每次查询数据时都认为别人不会修改,很乐观,但是更新时会判断以下在此期间别人有没有去更新这个数据
数据库三大范式
第一范式:数据表中每个字段都必须是不可拆分的最小单元,也就是确保 每一列的原子性;
第二范式:满足一范式后,表中每一列必须有唯一性,都必须依赖 于主键;
第三范式:满足二范式后,表中的每一列只与主键直接相关而不是 间接相关(外键也是直接相关),字段没有冗余。
MySQL优化
1) SQL 以及索引的优化
2) 合理的数据库设计
满足三范式,合理即可
数据库五大约束: 主键约束 唯一索引约束 默认值约束 非空约束 外键约束
字段类型选择: A:尽量使用 TINYINT.SMALLINT.MEDIUM_INT 作为整数类型而非 INT,如果非负则加上 UNSIGNED B:VARCHAR 的长度只分配真正需要的空间 C:使用枚举或整数代替字符串类型 D:尽量使用 TIMESTAMP 而非DATETIME E:单表不要有太多字段,建议在 20 以内 F:避免使用 NULL 字段,很难查询优化且占用额外索引空间 3) 硬件优化 4) 系统配置的优化
A:尽量少用(或者不用)sqlserver 自带的函数 B:连续数值条件,用BETWEEN 不用 IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5 C:Update 语句,如果只更改 1.2 个字段,不要 Update 全部字段, 否则频繁调用会引起明显的性能消耗 D:尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型 E:不建议使用 select * from t ,用具体的字段列表代替“*”,不要返 回
SQL语句优化
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使 用索引而进行全表扫描。
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致 引擎放弃使用索引而进行全表扫描。
尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放 弃使用索引而进行全表扫描
in 和 not in 也要慎用,否则会导致全表扫描 、对于连续的数值,能用 between 就不要用 in 了
如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只 有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必 须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知 的,因而无法作为索引选择的输入项。
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放 弃使用索引而进行全表扫描。
很多时候用 exists 代替 in 是一个好的选择
少用子查询,多用多表查询
事务的四种隔离级别
读未提交
读已提交
可重复读
序列化
十四 消息中间件
rabbitmq
5种工作模式
1.简单工作模式: 相当于你在发送消息的时候,不用指定交换机,直接指定发送给哪个queue就OK,(虽然你没有指定,但是其实他是有一个默认的交换机)
2.工作队列模式 也没有交换机,他也有默认的,他和第一种方式的区别在于多了一个消费者,但是他是只能有一消费者拿到生产者生产的这条消息
3.订阅模型分类 /分裂模式:
当交换机绑定了queue了之后,然后消费者去对接这些queue,生产者生产的一条消息,就可以被多个人接受到 c1 ,c2都可以拿到
4.路由模式
他除了要绑定queue和交换机的关系以外,他还要绑定 交换机和routtingKey的关系,在生产者发送消息时除了带上交换机以外,还带上routtingKey,rabbitmq 就知道自动去匹配outtingKey ,把消息转换到对应queue上
5.通配符模式
比路由模式更加灵活的一种方式
mq的好处
异步
削峰
解耦
mq的消息丢失
rabbitmq的消息丢失分三个方向:
1)mq端
消息到达mq后,消费者还没来的及消费,码后mq就宕机了,由于mq把这个消息保存到内存当中,内存的数据没了,则消息就丢失了
解决办法:将mq持久化(配置)
2)消费者端
因为mq默认是ack自动答应,只要消息一旦到达消费者端,消费者会立刻给mq响应一个ack,此时mq就会删除这个消息,但是有可能,在消费在消费这个消息的过程中失败了,但由于是自动应答,这个消息已经被mq删除,导致消息丢了。
解决办法:将自动应答给位手动应答,只有当消息被成功消费后,才响应ack
3)生产者端
确认机制,默认为异步执行,基于回调来完成,在发送消息之后,有两个回调方法,来表示消息是否到达了mq 端
mq的消息重复消费
响应的这个ack,恰好被弄丢了,于是这个消息明明被消费过了,但是mq 里边还有,mq 就会自动的重试
解决办法:setNx 消息最终一致性 接口幂等性
消息的最终一致性
在操作过程中要保证在事务在操作完成后,发送一条mq消息,这条消息一定要被消费者接收到,并且要保证一定要消费到,才能保证消息的一致性
具体操作:在操作完本地事务后,添加一个本地日志列表,通过定时器去定期把信息加载出来, 发送mq消息,事务的参与方拿到消息之后,去进行自己的逻辑,成功后再将这个消息从本地日志列表删除
接口幂等性
1.什么是幂等性
接口幂等性是用户对于同一操作发起的一次或多次请求的结果是一致的,不会因为多次点击产生副作用,比如支付场景,用户购买了商品支付扣款成功,但网络异常,此时钱扣了,用户再次点击按钮,会进行第二次扣款,这样就没有保证幂等性
同时消息的重复消费也是幂等性问题 系统的多次点击 也是幂等性问题
解决办法
方法一
1.服务器提供了发送token的接口,我们在分析业务时,哪些接口存在幂等问题,就必须在执行业务前,先去获得token ,服务器会把token 保存到redis
2.调用业务接口请求时,把token 携带过去,一般放在头部
3.服务器判断token是否存在于redis 中,存在表示第一次请求,然后删除,执行业务
4.如果我token不存在,则表示重复
token 的风险:
1.删除令牌的时机
后删:如果在生成订单过程中又来请求,则无法操作
最终解决方案 : 先删:获得,比较,删除:原子性操作
二 各种锁机制
1.悲观锁(慎用 ,因为效率低,而且容易产生数据库的问题) for update 2.乐观锁机制 ( 添加版本号) 该操作更适合修改逻辑中 update tb1 set count = count -1 ,version = version +1 where goods_id =1 and version =1 逻辑:我们操作库存时,先获得当前商品version,version 是1 ,调用库存服务后,version =2 ,如果我此时重复提交,则无法修改第二次
三 分布式锁
如果多个机器可能再同一时间同时处理,获得到锁之前,需要判断其标志位
举例:
• 分布式库存 --> task(1,2,3 数据需要回滚 ) -----> 咱们先去把库存扣掉-> ,然后如果发现出现了问题,回滚 --> 就是把扣除的库存给他退回去
• 让多个相同的服务器 去抢锁- > 操作业务 (回滚) 此处应该有一个 状态 -> 是否回滚过 ->
消息最大努力通知
通常的应用场景在支付场景下 最大努力通知,当支付收到用户的付款后,他会向系统发起最大努力通知,这个通知是基于一定策略,每隔一段时间通知一次,而且间隔的时间会长,同时支付宝还会提供一个 供系统查询的结构,以便在消息丢失时,系统可以主动来查询用户的支付情况
RocketMQ主要解决了两个功能:
本地事务与消息发送的原子
性问题。
事务参与方接收消息的可靠性。可靠消息最终一致性事务适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消 息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。
分布式事务解决方案之可靠消息最终一致性、最大努力通知_壹醉方休的博客-CSDN博客
消息乱序
当RabbitMQ采用work Queue模式,一个Queue对应多个Consumer时,多个Consumer直接是竞争关系,此时就会出现MQ消息乱序的问题。
处理:
将一个商品的上下架指定到一个消费者中处理。
采用路由模式,一个队列绑定一个消费者
商品id相同的消息(上下架),指定发送到一个队列中
发送消息时,要先发上架消息再发下架消息
消息堆积处理
1. 增加消费者的处理能力(例如优化代码),或减少发布频率。
2. 考虑使用队列最大长度限制。
3. 给消息设置年龄,超时就丢弃。
4. 默认情况下,rabbitmq 消费者为单线程串行消费,设置并发消费两个关 键属性 concurrentConsumers 和 prefetchCountoncurrentConsumers 设置 的是对每个 listener 在初始化的时候设置的并发消费者的个数,prefetchCount是每次一次性从 broker 里面取的待消费的消息的个数
5. 建立新的 queue,消费者同时订阅新旧 queue,采用订阅模式。
6. 生产者端缓存数据,在 mq 被消费完后再发送到 mq,打破发送循环条 件,设置合适的 qos 值,当 qos 值被用光,而新的 ack 没有被 mq 接收时,就 可以跳出发送循环,去接收新的消息;消费者主动 block 接收进程,消费者感受 到接收消息过快时主动 block,利用 block 和 unblock 方法调节接收速率,当接 收线程被 block 时,跳出发送循环。
kafka
Kafka 是一个分布式流媒体平台
Kafka通常用于两大类应用:
构建可在系统或应用程序之间可靠获取数据的实时流数据管道
构建转换或响应数据流的实时流应用程序
kafka的消息丢失
消息丢失的原因
生产者向leader 去写了数据,然后leader 还没有来的及把这个消息同步给 follower ,此时消费者也还没得及去leader 中拉取数据,此时leader 挂掉了,zk就会让follower去进行leader 选举,但是这些follower无论谁成为leader,他们里边都没有 丢掉了那条消息,于是 这条就丢了
解决方案:
配置 acks= all 表示只有当所有节点都收到数据之后,才会给生产者响应 成功
配置一个重试 : retries = MAX 写成一个很大的数
保证至少当前leader 有一个小弟
kafka的消息重复消费
生产者生产消息后会产生offset,递增的整数,这个整数用来记录当前发送到第多少消息,他们正常的流程消费者再消费完消息后,基于offset的自动提交机制,他会把他消费到的offset提交给zk,zk会通知kafka拿到消费者的消费的情况,如果此时消费者挂了之后,kafka 会基于他手上offset 再发送下一条消息,但是因为他是自动提交,有可能在他触发向zk提交之前,就挂了,此时kafka就不知道消费者准确的消费消息,于是就把 消费者消费过的数据再发给消费者,此时就消息重复消费了
解决
1.改成手动提交offset
2.加上setnx 以防offset由于网络抖动抖掉了,从而造成的
消息中间件的高可用
rabbitmq
rabbitmq的高可用方案->他不是一个分布式消息中间件
1.普通集群模式 就是集群中有一个核心节点,核心节点里边就有mq中的所有数据,和所有配置信息数据(元数据),其他非核心节点里边他就只有元数据,所以当客户端来找mq集群时,无论找到的是谁,他都会来请求核心节点,这个方式就几乎没有人使用
2.镜像模式 每个节点都相当于核心节点,客户端去操作每个核心节点都ok 优点:提高了可用性 缺点:每个节点中都有一套完整的数据,如果数据量太大,对于mq的压力就很大了
kafka
kafka8.0以后出现的机制,对数据进行备份,划分成主备之分,主 : leader,从 follower,平时操作 leader,leader 要负责把数据同步给follower ,当leader挂掉之后,zk就会感知到,他就发起 kafka的leader 选举 ,把follower变成leaderkafka消息中间件:他是分布式消息中间件
延迟队列
延迟队列=ttl+死信队列
类似于做一个定时任务,但是没有设置具体的时间起点,只是设置了以时间段来保证消息的可用性
想要消息延迟多久被处理,ttl则刚好能让消息延迟多久后成为死信,然后成为死信的消息都会进入死信队列,
这样只需要消费者一直消费死信队列的消息就实现了消息的延迟消费,因为里面的消息都是希望被立即处理的消息
rabbitmq_delayed_message_exchange:
一个向 RabbitMQ 添加延迟消息(或预定消息)的插件。
DelayQueue
DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。
DelayQueue添加的元素,是需要实现Delayed类。
由Delayed定义可以得知,队列元素需要实现getDelay(TimeUnit unit)方法和compareTo(Delayed o)方法, getDelay定义了剩余到期时间,compareTo方法定义了元素排序规则,注意,元素的排序规则影响了元素的获取顺序,将在后面说明。
DelayQueue存放元素的容器是由PriorityQueue类来实现的。
PriorityQueue(优先级队列)的数据结构是二叉堆。
当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆。
DelayQueue是线程安全的,因为它的操作都需要先获取锁才能进行。
DelayQueue来实现延迟是这样的,里面用PriorityQueue来存放元素,并且呢,PriorityQueue维持一个最小堆,就是二叉堆的根节点是最小的。然后得到根节点,调用根节点对象存储的元素的getDelay方法,计算延迟时间,如果小于0,直接返回元素,如果大于0,那么就要有限等待,然后再返回。
原文链接:简单实现延迟队列_大碗稀饭的博客-CSDN博客
redis实现延迟队列
十五 jvm
JVM 的基本结构
虚拟机,一种能够运行 java 字节码的虚拟机
类加载子系统
加载 .class 文件到内存。
内存结构
运行时的数据区。
执行引擎
执行内存中的.class,输出执行结果(包含 GC:垃圾收集器)。
本地方法的接口。
本地方法库
JVM 的主要组成部分及其作用
两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
Execution engine(执行引擎):执行classes中的指令。
Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。
GC 是垃圾收集的意思
jdk1.8之前与之后
元数据区取代了永久代
元数据空间并不在虚拟机中,而是使用本地内存
JVM 内存结构
程序计数器
Java 虚拟机栈
本地方法栈
java堆
方法区
堆内存划分
Java堆 = 老年代 + 新生代 1:2
新生代 = Eden + S0 + S1 8:1:1
JVM 有哪些垃圾回收器
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
垃圾回收算法
引用计数算法
jdk1.2 以前试用: 为这个对象分配一个计数器,有引用指向它,计数器+1 ,如果一个引用不再指向它,计数器-1 ,最后判断是否有引用指向它,只需要判断计数器是否大于0
以上的方式不靠谱-> 因为他在一种叫做循环引用问题的场景下,会出现问题
可达性算法
选取一系列的(4种) 作为GC ROOTS ,通过GC ROOTS 去指向我们的对象,如果说,通过GC ROOTS 找下来的线能够到达对象,那么这个对象就不是一个垃圾,如果一个对象到GCRoots没有任何引用链相连时,则说明此对象不可用。
GC ROOTS
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的native方法)中引用的对象、
Java 中都有哪些引用类型?
强引用:发生 gc 的时候不会被回收。 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。 弱引用:有用但不是必须的对象,在下一次GC时会被回收。 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
哪些情况会触发Full GC
内存泄漏掉了
1 年龄
2 老年代不足
由于日积月累--》导致年老代的数据快满了,---》但是程序依旧再运行--》
老年代空间只有在新生代对象转入及创建大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
3 方法区空间不足
JVM规范中运行时数据区域中的方法区,又被称为永生代或者永生区(并不是所有的jvm都有永生代的概念),方法区中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,方法区可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息【java.lang.OutOfMemoryError: PermGen space】 为避免方法区占满造成Full GC现象,可采用的方法为增大方法区空间或转为使用CMS GC。
4 显示调用System.gc() ===> 不推荐使用
tomcat 调优的
禁止显示调用 system.gc() 的配置
jvm调优
1.预调优
2.线上cpu飙升到100% 如何处理
1.找出哪个进程cpu高(top)
2.该进程中那个线程cpu高(top -Hp)
3.确认该线程是业务线程还是GC 线程
如果是GC 线程 -> 那一定是频繁gc 导致 (应当尽量少的发生FGC)
案例一、通过jvm日志查看是否是CMS,如果可以,尽量换成G1
如果不让换:则 一定配置-XX:CMSInitiatingOccupancyFraction 92% ->60% 、通过jvm日志查看是否是CMS,如果可以,尽量换成G1
3.线上产生oom如何处理
1.拿到dump文件
生成dump文件这个事, **导出失败好几次**,最后我们加上了live 指令之后,才导出成功/ 或者让运维给你生成了一个dump文件
1.1 手动获得
1.2 当发生oom时 自动生成
2.利用mat 去分析dump文件
3.将dump文件放到mat 中,查看哪些对象占用内存比较高
4.我们直接通过查看调用树和引用树 我们就能锁定当前这个内存占有率比较高的对象是在哪个程序中出现
5.分析对应的程序
编一个场景
1.线程池
• 2.hashMap 生命周期长,忘记了remove
• 3. 突发的加载 太多数据,从而导致oom 异常
• jvm大神
1.线程池
• 核心思路:有大量的任务要处理,提交速度非常快,处理的呢,比较慢,于是大量的请求其实就会被线程池的阻塞队列持有
2. 有一个生命周期超级长的集合对象
生命周期超级长,然后,你平时又没有对他进行清空
利用mat 进行堆分析
如果mat 分析出来是某个线程造成的, 我们应该先去分析这个具体的线程
调用树
哪些对象占用的内存比较高 --->打印当前的调用树
生成了dump有两种形式
1 jvm参数 设置
配置
2 手动生成
操作步骤
1.导出该线程的堆栈
2.jmap指令
2.1 手动:jmap -dump:format=b,file=
eg:jmap -dump:format=b,file=d:\1.hprof pid
2.2 自动:-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:/101.hprof
jmap 指令执行是 非常消耗的内存的
如果面试官问你, 你如何去分析oom -> 在程序堆内存溢出自动生成dump 文件
但是还没有oom -> dump
务必注意: 我们实际的做法-> 我们都是配置了高可用的服务器,可以让 其中的一台下线,让他去执行 jmap 指令
常用的 JVM 调优的参数
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。
内存分配策略
1 对象优先在 Eden 区分配
Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快; Major GC/Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上
2 大对象直接进入老年代
3 长期存活对象将进入老年代
十六 分布式事务
分布式事务的产生原因
事务这个东西本质上来说是由数据库提供的,而我们现在由于采用了分库的思想,所以说,在一个service方法,操作了不同的库,于是就产生了分布式事务的问题
事务的四个特征 1.原子性 2.一致性 3.隔离性 4.持久性
分布式事务的理论
CAP c:一致性--> 数据是一致的 a:可用性-->只要一访问就要立刻响应 p:分区容错性-->只有有集群,然后就必须要满足 三者只能取其二,另一个只能弱一点
BASE:全称: Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性) 核心思想是:
既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency) 软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
Base描述的是:如果三者只能进其二,剩下那一个应该做到什么样子 1.ap c :最终一致性 2.cp a :基本可用
降级分成两大类(了解) 1.主动降级 2.被动降级 比如说A -> B 在调用过程中,出现了问题,他会走降级方法
setaet
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
XID 在微服务调用链路的上下文中传播。
RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
TM 向 TC 发起针对 XID 的全局提交或回滚决议。
TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
2pc
在第一阶段,tm会去让rm 进行事务方面的准备,将工作内容进行持久化,RM会根据他完成的情况给TM 进行一个ok或者是no的响应, 第二阶段,TM根据收到的响应做出反应,如果TM收到了所有的ok,就会发起commit提交,让RM 全部去提交事务,如果有人响应no,则让RM 把工作内容进行回滚
优点:尽可能地实现了强一致性(也不能100%成功) 缺点:牺牲了可用性,性能损耗较大,不适合于高并发场景下,没有合适的补偿机制
tcc
tcc补偿机制有三个接口 try 接口负责将资源进行锁定 confirm 处理我们的核心逻辑 cancel 是处理过程中失败了的话,就有回滚逻辑 优点:灵活性极强 缺点:1.一致性不如2pc 2.代码侵入性太强
十七 网路通讯部分
TCP 与 UDP 区别?
UDP:
a.是面向无连接, 将数据及源的封装成数据包中,不需要建立连接
b.每个数据报的大小在限制 64k 内
c.因无连接,是不可靠协议
d.不需要建立连接,速度快
TCP:
a.建立连接,形成传输数据的通道.
b.在连接中进行大数据量传输,以字节流方式
c.通过三次握手完成连接,是可靠协议
d 必须建立连接效率会稍低.聊天.网络视频会议就是 UDP
什么是 Http 协议?**
区别 1: get 重点在从服务器上获取资源,post 重点在向服务器发送数据;
区别 2:get 传输数据是通过 URL 请求,以 field(字段)= value 的形式,置于 URL 后,并用"?"连接,多个请求数据间用"&"连接,如 http://127.0.0.1/Test/LogI n.action?name=admin&password=admin,这个过程用户是可见的; post 传输数据通过 Http 的 post 机制,将字段与对应值封存在请求实体中 发送给服务器,这个过程对用户是不可见的;
区别 3:Get 传输的数据量小,因为受 URL 长度限制,但效率较高; Post 可以传输大量数据,所以上传文件时只能用 Post 方式;
区别 4:Get 是不安全的,因为 URL 是可见的,可能会泄露私密信息,如密码等; Post 较 get 安全性较高;
区别 5:get 方式只能支持 ASCII 字符,向服务器传的中文字符可能会乱码。 post 支持标准字符集,可以正确传递中文字符。
http 中重定向和请求转发的区别?
本质区别:转发是服务器行为,重定向是客户端行为。
重定向特点:两次请求,浏览器地址发生变化,可以访问自己 web 之外的 资源,传输的数据会丢失。
请求转发特点:一次请求,浏览器地址不变,访问的是自己本身的 web 资 源,传输的数据不会丢失。
Cookie 和 Session。
Cookie 是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个web服务器存储 cookie。以后浏览器再给特定的 web 服务器发 送请求时,同时会发送所有为该服务器存储的 cookie。
Session 是存储在 web 服务器端的一块信息。session 对象存储特定用 户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存 储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下 去。
Cookie 和 session 的不同点:
1.无论客户端做怎样的设置,session 都能够正常工作。当客户端禁用 cookie 时将无法使用 cookie。
2.在存储的数据量方面:session 能够存储任意的 java 对象,cookie 只 能存储 String 类型的对象。
Linux常见命令
top 查看当前linux中的进程,他会打印出来这些进程的信息,同时还会打印出来cpu的使用率 并且进行排序
ps -ef
kill 杀死
jps 查看进程,只不过他是查看java中进程
jstack 进程号 -->打印出来当前 进程中的所有线程的情况
jmap
restful
REST:Representational State Transfer(表象层状态转变)
REST (REpresentation State Transfer) 描述了一个架构样式的网络系统,指的是一组架构约束条件和原则。
RESTful 指的是满足这些约束条件和原则的应用程序或设计。
每个资源都使用 URI (Universal Resource Identifier) 得到一个惟一的地址。所有资源都共享统一的界面,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是应用程序状态的引擎,资源表示通过超链接互联
REST 描述了一个架构样式的互联系统(如 Web 应用程序)。REST 约束条件作为一个整体应用时,将生成一个简单、可扩展、有效、安全、可靠的架构。由于它简便、轻量级以及通过 HTTP 直接传输数据的特性,RESTful Web 服务成为基于 SOAP 服务的一个最有前途的替代方案。用于 web 服务和动态 Web 应用程序的多层架构可以实现可重用性、简单性、可扩展性和组件可响应性的清晰分离。
1. URL中不可出现动词
2.URL能够唯一地标识单个或一类资源
3.通过Http动词去操作URL标识的资源(get(获取),pst(添加),put(更新),delete(删除))
restFul是符合rest架构风格的网络API接口,完全承认Http是用于标识资源。restFul URL是面向资源的,可以唯一标识和定位资源。 对于该URL标识的资源做何种操作是由Http方法决定的。 rest请求方法有4种,包括get,post,put,delete.分别对应获取资源,添加资源,更新资源及删除资源.
MongoDB
MongoDB是一个基于分布式文件存储的数据库。
ongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
在MongoDB中,存储的文档结构是一种类似于json的结构,称之为bson(全称为:Binary JSON)。
参数说明:
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,抛出异常的级别。
使用mongodb?
评论内容的特点:
数据量的很大的,
目录
一 JAVASE 基础语法
二 集合框架底层 (多线程、线程池)
三 锁
四 redis
查询或新增的比较频繁
数据价值比较低,对事务要求不高。
所以最终采用的当前技术解决。
(1)高性能:
MongoDB提供高性能的数据持久性。特别是,
对嵌入式数据模型的支持减少了数据库系统上的I/O活动。
索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。
(2)高可用性:
MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。
(3)高扩展性:
MongoDB提供了水平可扩展性作为其核心功能的一部分。
分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)
(4)丰富的查询支持:
MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。
(5)其他特点:如无模式(动态模式)、灵活的文档模型
什么是跨域?
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
所谓同源是指,域名,协议,端口均相同,
http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
解决办法:
1、JSONP:
使用方式就不赘述了,但是要注意JSONP只支持GET请求,不支持POST请求。
2、代理:
例如www.123.com/index.html需要调用www.456.com/server.php,可以写一个接口www.123.com/server.php,由这个接口在后端去调用www.456.com/server.php并拿到返回值,然后再返回给index.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题。
3、PHP端修改header(XHR2方式)
在php接口脚本中加入以下两句即可:
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式4.nginx 反向代理
利用nginx反向代理把跨域为不跨域,支持各种请求方式
缺点:需要在nginx进行额外配置,语义不清晰
方法1:在nginx中配置地址重写(或者转发也行)
方法2:nginx中添加允许跨域请求头
在网关的application.yml的配置文件中
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
SLF4J 而不是 Log4J
每个Java开发人员都知道日志记录对Java应用的重要性,尤其是对服务端应用,而且其中许多人都已经熟悉了各种记录日志的库,比如java.util.logging,Apache的log4j,logback,然而如果你不知道SLF4J,java的简单记录日志的设计的话 ,那么到了学习并在你的项目中使用它的时候了。在这篇Java文档里,我们将学习为什么使用SLF4J比使用log4j或者java.util.logging更好。从我写 Java开发人员的10个记录日志的技巧 算起已经过去了很长一段时间了。我不记得我所写的有关日志记录的任何事情了。无论如何,让我们回归到这个主题上来,与所有提到的这些日志记录库相比,SLF4J与它们之间有一个主要的区别。SLF4J或者说是Java的简单记录日志设计没有真正地实现日志记录,相反它只是一个允许你使用任何处于后端的日志记录库的 抽象层 。如果你正在编写内部或者外部使用的API或者应用库的话,那么你真的不需要让使用你所编写的库的客户端还去选择日志库。假设项目已经使用了log4j,而且你包含一个名为Apache Active MQ的库,这个库还依赖于另一个日志记录库logback的话,那么你还需要包含它们,然而,如果Apache Active MQ使用了SLF4J的话,你可以继续使用你的日志记录库,而不需要痛苦地添加和维护新的日志记录框架。简短的说,SLF4J让你的代码独立于任何特定的日志记录API,这个好的想法尤其适合于公共的API开发人员。虽然日志记录库的抽象理念不是新的,而且Apache的commons logging日志记录库也是用了这个理念,不过现在SLF4J很快就会成为Java世界里标准的日志记录库。让我们看一些使用 SLF4J而不使用log4j,logback或者java.util.logging的理由。
https://blog.csdn.net/rengq126/article/details/17123489?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control
团队组成
团队人数:16人
产品经理:2人,负责产品推广与产品调研、确定客户需求。
UI: 2人,负责原型图设计
项目经理:1人
系统架构师:1人,主要负责需求调研、概要设计、详细设计。
资深开发工程师(后端):2人,主要负责项目核心功能开发。
程序员(后端):3人,主要负责项目后端代码开发。
前端开发工程师:2人,主要负责项目前端代码开发。
测试工程师:2人
,主要负责项目测试。
运维人员:1人,主要负责上线部署。
9.1 公司组织架构
产品部(2-3),UI部门(2-7),前端(3-4),服务端(后端18-24) (开发一组,开发二组...),移动端(2-3人),测试部(5-10),运维部(3-4),技术部门整体不超过50 人,三个开发小组,每个小组大致6-8 人左右
9.3 公司一般多少台服务器
两种极端情况
1.小公司
3 台 2台线上,1台线下 或者6 台 4台线上 2台测试
32 g 4核16 线程
2.几十上百台