自己辛苦整理,相对简化,适用于面试突击。
希望对初中级java开发的面试有所帮助。
毕竟现在的就业环境太差了。
有问题、有补充欢迎评论指出,虚心求教,有错麻溜改。
对你有帮助的话,记得点赞收藏。
朋友要找工作的话,记得转发给他哦~
JDK:java开发工具;JRE:java运行时环境;JVM:java虚拟机。
面向对象相较于面向过程而言是两种不同的处理问题的角度。
面向过程注重步骤,面向对象更注重完成这些任务的参与者(对象)。
面向过程比较直接高效,而面向对象更易于复用、扩展和维护。
修饰的类不可被继承,修饰的方法不能被子类重写,修饰的变量不能被修改(引用类型不可修改地址)。
如果final修饰的是类变量(static),只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,
即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码
中对final变量赋初值(仅一次)。
局部内部类和匿名内部类只能访问局部final变量。(内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着 方法的执行完毕就被销毁。当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解 决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以 访问它,实际访问的是局部变量的"copy"。这样就好像延长了局部变量的生命周期 final变量 )
String是final修饰的,不可变,底层用char数组实现的。每次操作都会产生新的String对象 。
StringBuffer和StringBuilder都是在原对象上操作 。
StringBuffer是线程安全的,StringBuilder线程不安全的 。
StringBuffer方法都是synchronized修饰的 。
性能:StringBuilder > StringBuffer > String 。
场景:经常需要改变字符串内容时使用后面两个 。
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer。
修饰符可以不同,发生在编译时。
等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
接口的设计目的,是对类的行为进行约束;而抽象类的设计目的,是代码复用。
静态关键字,用法包括静态变量和静态方法。
//创建一个Date日期对象:代表了系统当前此刻日期时间信息
Date d = new Date();
//获取时间毫秒值的形式:从19700101 0:0:0开始走到此刻的总毫秒值
long time = d.getTime(); // long time = System.currentTimeMillis();
time += (60 * 60 + 123) * 1000;
//把时间毫秒值转换成日期对象
Date d2 = new Date(time);
// 与上述代码逻辑一样,只是写法不同
Date d2 = new Date();
d2.setTime(time); // 修改日期对象成为time这个时间
//日期对象
Date d = new Date();
//开始格式化:创建一个简单日期格式化对象
// 注意:参数是格式化之后的时间形式,必须申明!
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
//开始格式化日期对象成为字符串形式
String result = sdf.format(d);
//格式化时间毫秒值----------
long time = d.getTime() + 60 * 1000;
sdf.format(time)
//SimpleDateFormat解析字符串时间成为日期对象
String timeStr = "2022年05月27日 12:12:12";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date d = sdf.parse(timeStr); // 解析
// 拿到系统此刻日历对象
Calendar rightNow = Calendar.getInstance();
// 获取日历的信息:public int get(int field):取日期中的某个字段信息。
int year = rightNow.get(Calendar.YEAR);
int mm = rightNow.get(Calendar.MONTH);
int days = rightNow.get(Calendar.DAY_OF_YEAR);
//public void add(int field,int amount):为某个字段增加/减少指定的值
// 请问64天后是什么时间
rightNow.add(Calendar.DAY_OF_YEAR , 64);
//拿到此刻时间毫秒值
long time = rightNow.getTimeInMillis();
System.out.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(time));
//获取本地日期对象。
LocalDate nowDate = LocalDate.now();
int year = nowDate.getYear();
int month = nowDate.getMonthValue();
int day = nowDate.getDayOfMonth();
//当年的第几天
int dayOfYear = nowDate.getDayOfYear();
//星期
System.out.println(nowDate.getDayOfWeek());
System.out.println(nowDate.getDayOfWeek().getValue());
//月份
System.out.println(nowDate.getMonth());
System.out.println(nowDate.getMonth().getValue());
//直接传入对应的年月日
LocalDate bt = LocalDate.of(2025, 5, 20);
//相对上面只是把月换成了枚举
System.out.println(LocalDate.of(2025, Month.MAY, 20));
//获取本地时间对象。
LocalTime nowTime = LocalTime.now();
int hour = nowTime.getHour();//时
int minute = nowTime.getMinute();//分
int second = nowTime.getSecond();//秒
int nano = nowTime.getNano();//纳秒
LocalTime time = LocalTime.of(8, 30);
System.out.println(time);//时分
System.out.println(LocalTime.of(8, 20, 30));//时分秒
// 日期 时间
LocalDateTime nowDateTime = LocalDateTime.now();
//今天是:
System.out.println("今天是:" + nowDateTime);
System.out.println(nowDateTime.getYear());//年
System.out.println(nowDateTime.getMonthValue());//月
System.out.println(nowDateTime.getDayOfMonth());//日
System.out.println(nowDateTime.getHour());//时
System.out.println(nowDateTime.getMinute());//分
System.out.println(nowDateTime.getSecond());//秒
//日:当年的第几天
System.out.println(nowDateTime.getDayOfYear());
//星期
System.out.println(nowDateTime.getDayOfWeek());//枚举
System.out.println(nowDateTime.getDayOfWeek().getValue());//数组
//月份
System.out.println(nowDateTime.getMonth());//枚举
System.out.println(nowDateTime.getMonth().getValue());//数组
//转日期
LocalDate ld = nowDateTime.toLocalDate();
//转时间
LocalTime lt = nowDateTime.toLocalTime();
LocalDateTime ldt = LocalDateTime.now();
//格式化器
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String ldtStr1 = dtf.format(ldt);
//解析
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 解析当前字符串时间成为本地日期时间对象
LocalDateTime ldt1 = LocalDateTime.parse("2022-11-11 11:11:11" , dtf1);
System.out.println(ldt1.getDayOfYear());
for (int i = 0; i <arr.length-1; i++) {
//标志位
boolean flag = true;
for (int j = 0; j <arr.length-1-i ; j++) {
if(arr[j] > arr[j+1]){
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
flag = false;
}
}
//当不再发生交换时,则结束比较
if(flag){
break;
}
}
所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素。
素,在逐一遍历各个元素。
思路:介绍list的特点–>简单介绍Arraylist,LinkedList 的底层实现–>说说Arraylist,LinkedList 的区别–>最后可以说他们不是线程安全的,引入写时复制思想。
List是一个有序,可重复的集合。它的实现类包括ArrayList,LinkedList,Vector。
ArrayList底层是动态数组实现的。动态数组就是长度不固定,随着数据的增多而变长。实例化Arraylist的时候,如果不指定长度,默认就是10。添加元素时,是按照顺序从头部开始往后添加。
使用无参构造ArrayList()创建ArrayList对象时,不会定义底层数组的长度,当第一次调用add(E e) 方法时,初始化定义底层数组的长度为10,之后调用add(E e)时,如果需要扩容,则调用grow(int minCapacity) 进行扩容,长度为原来的1.5倍。
因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会 涉及到元素的移动,所以增删效率一般。
但是由于每个元素占用的内存相同且是连续排列的,因此在查找的时候,根据元素的下标可以迅速访问数组中的任意元素,查询效率非常高。
LinkedList底层是双向链表的数据结构实现,每个节点包括:上一节点和下一节点的引用地址和data用来存储数据,双向链表不是连续排列的,是可以占用一段不连续的内存空间的。
当有新元素插入时,只需要修改所要插入位置的前一个元素的引用和后一个元素的引用。
删除也只需要修改两个引用,当前元素就没有指向,就成了垃圾对象,被回收。效率高。但是查询的时候需要从第一个元素开始查找,直到找到需要的数据,所以查询的效率比较低。
ArrayList底层是数组实现,LinkedList底层是链表实现
Arraylist适合随机查找,LinkedList适合删除和添加
都实现了List接口,但是LinkedList同时还实现了Deque接口,还可以作为双端队列。
ArrayList通过下标查询快,LinkedList通过下标查询需要遍历所有,但是查第一个和最后一个很快
ArrayList添加需要扩容,指定位置添加,需要数组移动元素。LinkedList添加不需要扩容,指定位置添加,需要遍历找到位置
ArrayList实现了Random Access接口,LinkedList没有。实现Random Access接口可以使用普通for循环遍历,没有实现的使用foreach和迭代器,ArrayList用for循环快,LinkedList用迭代器快。
ArrayList和LinkedList都是线程不安全的。(在添加操作时,可能是分成两步完成的: 1、在items[size]的位置存放此元素,2、增大size的值,这个时候就会引发线程安全问题。)如果想要解决当前的这个问题,可以用写时复制的CopyOnWriteArrayList。
无序,元素不能重复。
键值对 key value的集合,可以使用任何引用类型的数据,key不能重复,通过指定的key就可以获取对应的value。
先说HashMap的Put⽅法的⼤体流程:
是Node对象)并放⼊该位置
1.7版本
1.8版本
采用 Fail-Fast 机制,底层通过一个 modCount 值记录修改的次数,对 HashMap 的修改操作都会增加这个值。迭代器在初始过程中会将这个值赋给 exceptedModCount ,在迭代的过程中,如果发现 modCount 和 exceptedModCount 的值不一致,代表有其他线程修改了Map,就立刻抛出异常。
怎么解决:
loadFactor加载因子0.75f。
在 JDK7 中,ConcurrentHashMap 使用“分段锁”机制实现线程安全,数据结构可以看成是"Segment数组+HashEntry数组+链表",一个 ConcurrentHashMap 实例中包含若干个 Segment 实例组成的数组,每个 Segment 实例又包含由若干个桶,每个桶中都是由若干个 HashEntry 对象链接起来的链表。.
因为Segment 类继承 ReentrantLock 类,所以能充当锁的角色,通过 segment 段将 ConcurrentHashMap 划分为不同的部分,就可以使用不同的锁来控制对哈希表不同部分的修改,从而允许多个写操作并发进行,默认支持 16 个线程执行并发写操作,及任意数量线程的读操作。
在 JDK8 及以上的版本中,ConcurrentHashMap 的底层数据结构依然采用“数组+链表+红黑树”,但是在实现线程安全性方面,抛弃了 JDK7 版本的 Segment分段锁的概念,而是采用了 synchronized + CAS 算法来保证线程安全。在ConcurrentHashMap中,大量使用 Unsafe.compareAndSwapXXX 的方法,这类方法是利用一个CAS算法实现无锁化的修改值操作,可以大大减少使用加锁造成的性能消耗。这个算法的基本思想就是不断比较当前内存中的变量值和你预期变量值是否相等,如果相等,则接受修改的值,否则拒绝你的而操作。
异常相当于⼀种提示,如果我们抛出异常,就相当于告诉上层⽅法,我抛了⼀个异常,我处理不了这个异常,交给你来处理,⽽对于上层⽅法来说,它也需要决定⾃⼰能不能处理这个异常,是否也需要交给它的上层。
我们在工作当中,异常的处理应该说是因业务需求而定,比如我们在判断非法参数时就可以抛出异常提示,同时定义统一的异常处理器,对我们的异常进行统一的处理和响应。而我们在一个接口中开启了事务这个时候是不能够捕获异常的,这样会导致管理器无法通过异常信息进行回滚操作。
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
源码中定义的线程的状态:
public enum State {
NEW, //新生
RUNNABLE, //就绪状态
BLOCKED, //阻塞状态
WAITING, //等待
TIMED_WAITING, //限时等待
TERMINATED; //终结状态
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
第一种,在配置类上添加@EnableAsync来开启异步调用,在指定方法上添加@Async注解异步执行方法
第二种
@Configuration
//开启异步调用
@EnableAsync
public class AysncConfig {
//配置线程池, 不同的异步方法, 可以交给特定的线程池来完成
@Bean("myThreadPool")
public ExecutorService myThreadPool() {
//这个类则是spring包下的, 是spring为我们提供的线程池类
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
threadPoolTaskExecutor.setCorePoolSize(10);
//最大线程数
threadPoolTaskExecutor.setMaxPoolSize(50);
//阻塞队列大小
threadPoolTaskExecutor.setQueueCapacity(50);
//超时时长
threadPoolTaskExecutor.setKeepAliveSeconds(30);
//线程名前缀
threadPoolTaskExecutor.setThreadNamePrefix("lz-thread-");
//拒绝策略
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//初始化
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor.getThreadPoolExecutor();
}
}
@Async("myThreadPool")
public CompletableFuture<String> getString1(Integer msg) {
//方法体
return CompletableFuture.completedFuture(String.valueOf(msg));
}
每一个 Thread 对象均含有一个 ThreadLocalMap 类型的成员变量 threadLocals ,它存储本线程中所有ThreadLocal对象及其对应的值。
ThreadLocalMap 由一个个 Entry 对象构成,Entry 继承自 WeakReference
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对
象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap
对象。再以当前ThreadLocal对象为key,获取对应的value。由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景:
(Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的 connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离)
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal中的Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。
由于ThreadLocal对象是弱引用,如果外部没有强引用指向它,它就会被GC回收,导致Entry的Key为null,如果这时value外部也没有强引用指向它,那么value就永远也访问不到了,按理也应该被GC回收,但是由于Entry对象还在强引用value,导致value无法被回收,这时「内存泄漏」就发生了,value成了一个永远也无法被访问,但是又无法被回收的对象。
Entry对象属于ThreadLocalMap,ThreadLocalMap属于Thread,如果线程本身的生命周期很短,短时间内就会被销毁,那么「内存泄漏」立刻就会得到解决,只要线程被销毁,value也会随之被回收。问题是,线程本身是非常珍贵的计算机资源,很少会去频繁的创建和销毁,一般都是通过线程池来使用,这就将线程的生命周期大大拉长,「内存泄漏」的影响也会越来越大。
ThreadLocal正确的使用方法
volatile是java提供的一种轻量级的同步机制。volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
可以保证数据的可见性和有序性,无法保证原子性。
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。unsafe类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用。Atomic就是自旋调用原子操作。
乐观锁:每次拿数据的时候认为别人不会修改数据,所以不会上锁,更新的时候会判断一下这期间有没有人去更新过这个数据。适合多读的应用类型,提高吞吐量。
悲观锁:每个去拿数据的时候认为别人会修改数据,所以每次都会上锁,别人想拿的时候就会阻塞直到它拿到锁。
悲观锁适合写多的场景,乐观锁适合读多的场景(不加锁提高了性能)
悲观锁在java中就是利用各种锁;乐观锁在java中使用无锁编程,采用CAS算法
CAS (Compare and Swap 比较并交换),当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS操作中包含三个操作数一一需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,否则处理器不做任何操作。
独享锁(独占锁)是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁。其写锁是独享锁,读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。对于Synchronized而言,当然是独享锁。
ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentrantLock。
读写锁在Java中的具体实现就是ReadWriteLock.
以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取该对象上的锁,而其他线程是不可以的。可重入锁的意义在于防止死锁。
实现原理:
为每个锁关联一个请求计数器和一个占有他的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM会记录锁的占有者,并将请求计数器置为1。
同一个线程再次请求这个锁,计数器递增;
每次占用线程退出同步代码块,计数器递减。
直到计数器为0,锁被释放。
公平锁:在锁上等待时间最长的线程将获得锁的使用权。
非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。
在Java 中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循坏的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU。
JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。
继承ClassLoader可以自定义类加载器。
向上委派,向下加载。
双亲委派模型的好处:
上图为类的生命周期。
类加载的过程包括:加载、链接、初始化。
GC Roots的对象有:
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
对于还在正常运⾏的系统:
对于已经发⽣了OOM的系统:
三⾊标记:是⼀种逻辑上的抽象。将每个内存对象分成三种颜⾊:
http://www.cnblogs.com/qixuejia/p/3637735.html
limit,对于小的偏移量,直接使用limit来查询没有什么问题,但是数据量增大后,limit语句的偏移量增大,查询效率会降低可通过子查询的分页方式或JOIN分页方式解决。
索引:帮助MySQL高效检索数据的排好序的数据结构。
主键索引:设定为主键后数据库会自动创建索引,InnoDB为聚簇索引。
单值索引:即一个索引只包含单个列,一个表可以有多个单值索引。
唯一索引:索引列的值必须唯一,但允许有空值。
复合索引:即一个索引包含多个列。
全文索引(Full Text):在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。
特殊:
聚簇索引:将数据存储与索引放到了一块,索引结构的子节点保存了行数据。
非聚簇索引:将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置。
B+ 树非叶子节点上是不存储数据的,仅存储键值。而且数据是按照顺序排列的通过双向链表进行连接。三层存储大约2000W+条数据。
查询更快、占⽤空间更⼩
索引覆盖就是⼀个SQL在执⾏时,可以利⽤索引来快速查找,并且此SQL所要查询的字段在当前索引对应的字段中都包含了,那么就表示此SQL⾛完索引后不⽤回表了,所需要的字段都在当前索引的叶⼦节点上存在,可以直接作为结果返回了 。
当⼀个SQL想要利⽤索引是,就⼀定要提供该索引所对应的字段中最左边的字段,也就是排在最前⾯的字段,⽐如针对a,b,c三个字段建⽴了⼀个联合索引,那么在写⼀个sql时就⼀定要提供a字段的条件,这样才能⽤到联合索引,这是由于在建⽴a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段 从左往右去⽐较⼤⼩进⾏排序的,所以如果想要利⽤B+树进⾏快速查找也得符合这个规则。
列名 | 描述 |
---|---|
id | id相同由上而下的执行;id不同,id越大优先级越高。 |
select_type | 查询的类型,主要是用于区别:普通查询、联合查询、子查询等复杂查询。 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 针对单表的查询⽅式,其参数提供了判断查询是否高效的重要依据。效率从好到差依次是:system>const>eq_ref>ref>range>index>ALL。 |
possible_keys | 可能⽤到的索引 |
key | 实际上使⽤的索引 |
key_len | 实际使⽤到的索引⻓度 |
ref | 当使⽤索引列等值查询时,与索引列进⾏等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分⽐ |
Extra | ⼀些额外的信息,⽐如Using filesort、Using index、Using where |
事务是并发控制的基本单位。一组操作要么都执行要么都不执行。
维护数据库数据的一致性,每个事务结束时,保证数据的一致性。
ACID
Spring是一种轻量级的面向切面和控制反转开源框架,提高开发效率与维护性优点。
Spring能帮我们解耦合,帮助管理对象之间的依赖关系。
Spring 中 AOP帮助我们提高代码复用性、扩展性。(安全、事务、权限等)
Spring 不用关心对象的创建,只需要配置好配置文件即可,简化了开发。
Spring是一个简单并强大的声明式事务管理。
IOC:控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转
简单来说,在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
IOC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(依赖注入)来实现的,即应用程序在运行时依赖 IOC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性。
例子: 当某个 Java实例需要另一个 Java 实例时,传统的方法是由调用者创建被调用者的实例(new ),而使用 Spring框架后,被调用者的实例不再由调用者创建,而是由 Spring 容器创建,这称为控制反转。
Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过 Spring 容器获得被调用者实例,这称为依赖注入。
OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
IOC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
AOP实现的关键在于代理模式,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能(他会在编译阶段将切面织入到Java字节码中,运行的时候就是增强之后的对象),但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
切面(Aspect):被抽取出来的公共模块。Aspect切面可以看成 Pointcut切点和 Advice通知的结合,一个切面可以由多个切点和通知组成。在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
切点(Pointcut):切点用于定义要对哪些Join point进行拦截。
通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
代理(Proxy):将通知织入到目标对象之后形成的代理对象
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:资源文件访问(ClassPathXmlApplicationContext),提供在监听器中注册bean的事件。它是在容器启动时,一次性创建了所有的Bean。在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。
从对象的创建到销毁的过程。而Spring中的一个Bean从开始到结束经历很多过程,但总体可以分为六个阶段Bean定义、实例化、属性赋值、初始化、生存期、销毁。
首先进行实例化bean对象,然后进入对bean的属性进行设置,然后对BeanNameAware(让Spring容器获取bean的名称)
设置对象属性(依赖注入)
然后处理Aware接口
实现BeanNameAware接口,让Spring容器获取bean的名称
实现BeanFactoryAware接口,让bean的BeanFactory调用容器的服务
实现ApplicationContextAware接口,让bean当前的applicationContext可以调用Spring容器的服务
实现了BeanPostProcessor接口
配置init-method属性就会自动调用初始化方法
清理-销毁,调用destory()方法结束生命周期
Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。
对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
xml配置的自动装配
注解的自动装配
使用@Autowired、@Resource注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
spring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
mvc:是一种设计模式
m:model模型层,主要用于数据封装
v:view视图层,用于数据的显示
c:controller控制层,用于逻辑控制操作
优点:有利于开发中的分工,有利于组件的重用,解耦合,在系统中并行开发,提升开发的效率。
自定义的Interceptor类要实现了Spring的HandlerInterceptor接口。
HandlerInterceptor接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。
preHandle:Controller方法处理请求前执行,根据拦截器定义的顺序,正向执行。
postHandle:Controller方法处理请求后执行,根据拦截器定义的顺序,逆向执行。需要所有的preHandle方法都返回true时才会调用。
afterCompletion:View视图渲染后处理方法:根据拦截器定义的顺序,逆向执行。preHandle返回true就会调用。
编写完拦截器后,我们还需要编写MVC配置类。继承WebMvcConfigurer,重写addInterceptors,添加自定义拦截器,设置拦截路径及不拦截路径。
Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。
#{}是预编译处理,${}是字符串替换。
mybatis在处理#{}时,会将sql中的#{}代替为?号,再调用preparedStatement的set方法赋值;
mybatis在处理 时,会把 {}时,会把 时,会把{}替换成变量值。
使用#{}可以防止sql注入,提高系统安全性。
<insert id="方法名" parameterType="实体类路径" keyProperty="uuid" useGeneratedKeys="true">insert>
keyProperty表示返回的id要保存到对象的哪个属性中;
useGeneratedKeys表示主键id为自动增长模式;
insert 方法总是返回一个int值 ,这个值代表的是插入的行数。 如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
<association>association>
<collection>collection>
除了常见的select|insert|updae|delete标签外,还、、、、,加上动态sql的9个标签 trim | where | set | foreach | if | choose | when | otherwise | bind 等,其中 为sql片段标签,通过标签引入sql片段,为不支持自增的主键生成策略标签。
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用
Spring 的难度,简省了繁重的配置,提供了各种启动器,使开发者能快速上手。
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含
了以下 3 个注解:
主要是Spring Boot的启动类上的核心注解SpringBootApplication注解主配置类,有了这个主配置
类启动时就会为SpringBoot开启一个@EnableAutoConfiguration注解自动配置功能。
有了这个EnableAutoConfiguration的话就会:
直接使用:@CrossOrigin
或者@Configuration
实现WebMvcConfigurer重写 addCorsMapperings方法
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")//项目中的所有接口都支持跨域
.allowedOrigins("*")//所有地址都可以访问,也可以配置具体地址
.allowCredentials(true) //是否允许请求带有验证信息
.allowedMethods("*")//"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
.allowedHeaders("*").maxAge(3600);// 跨域允许时间
}
}
首先使用注解EnableTransactionManagement开启事物之后,然后在Service方法上添加注解Transactional便可。
配置文件配置:spring.profiles.active
可以在项目中配置多个application配置文件,根据应用场景不同,通过application-中的来决定启用某个配置文件。
命令 | 作用 |
---|---|
pwd | 查看当前目录的路径 |
ls | 查看目录下的文件 |
cd | 切换目录 |
mkdir | 创建目录 -p级联创建 |
rmdir | 删除目录 |
touch | 创建文件 |
rm | 删除命令 -f 强制删除 -r递归删除 |
echo | 输出命令,可以输入变量,字符串的值 |
>和>> | 输出符号,将内容输出到文件中,>表示覆盖(会删除原文件内容) >>表示追加 |
cat | 查看文件的所有内容 |
more | 分页查看文件内容 |
tail | 从文件的末尾查看文件内容。-n n是一个正整数,表示查看文件的后n行数据。-f 动态的查看文件的最后几行内容 |
cp | cp [参数] 原文件路径 目标文件路径。拷贝命令 |
mv | 移动命令,它可以移动文件,也可以给文件改名 |
free | 查看系统内存的命令 |
chmod | 用3个数字来设置文件或目录的权限,第1个数字表示用户权限,第2数字表示用户组权限,第3个数字表示其 他用户权限 |
remote dictionary service:是用C语言开发的一个开源的基于内存的高性能键值对(key-value)数据库,它通过提供多种键值数据类型来适应不同场景下的存储需求。
RDB(Redis DataBase)持久化方式能够在指定的时间间隔能对你的数据进行快照存储。在默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。
当Redis需要保存dump.rdb文件时,服务器执行以下操作:
优点:二进制紧凑文件,省空间。可保存不同时间的数据集。回复速度快,适用于灾难恢复。
缺点:耗性能、不可精确控制、会丢失数据。
AOF(Append Only File),打开AOF后, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
在不打断服务客户端的情况下, 对 AOF 文件进行重建(rebuild)。执行 bgrewriteaof 命令, Redis 将生成一个新的 AOF 文件, 这个文件包含重建当前数据集所需的最少命令。Redis 2.4 则可以通过配置自动触发 AOF 重写。
需要配置以下参数:
优点:让redis更加耐久,使用默认策略出现故障也最多丢失1秒数据,且文件可读性强。
缺点:根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
由于 AOF 持久化方式在重启加载数据时效率远远不如 RDB 方式,所以 Redis 在4.0版本后引入了混合持久化方式,配置项为 aof-use-rdb-preamble,yes开启,策略为在生成或写入 AOF 文件时,将 RDB 数据写在前面,AOF 数据追加到后面,在每次启动时先加载 RDB,再加载 AOF。
Redis执行指令过程中,多条连续执行的指令不得被干扰,打断,插队。一个队列中,一次性、顺序性、排他性的执行一系列命令。
开启事务:multi
执行事务:exec
取消事务:discard
注意:
如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。
定义事务的过程中,命令执行出现错误能够正确运行的命令会执行,运行错误的命令不会被执行。已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
多个客户端有可能同时操作同一组数据,并且该数据一旦被操作修改后,将不适用于继续操作。在操作之前锁定要操作的数据,一旦发生变化,终止当前操作。
对已经过期数据进行删除的策略。
到达Redis的最大内存使用量的时候,这个时候就需要对Redis存储的数据进行清理,释放内存,将Redis内存使用量保持在容量限制以下。(也叫逐出算法)
主从模式中,Redis部署了多台机器,有主节点,负责读写操作,有从节点,只负责读操作。从节点的数据来自主节点,实现原理就是主从复制机制。
主从复制包括全量复制,增量复制两种。一般当slave第一次启动连接master,或者认为是第一次连接,就采用全量复制,反之就是增量复制
redis2.8版本之后,已经使用psync来替代sync,因为sync命令非常消耗系统资源,psync的效率更高。
slave与master全量同步之后,master上的数据,如果再次发生更新,就会触发增量复制。
当master节点发生数据增减时,就会触发replicationFeedSalves()函数,接下来在 Master节点上调用的每一个命令会使用replicationFeedSlaves()来同步到Slave节点。执行此函数之前呢,master节点会判断用户执行的命令是否有数据更新,如果有数据更新的话,并且slave节点不为空,就会执行此函数。这个函数作用就是:把用户执行的命令发送到所有的slave节点,让slave节点执行。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。
简单来说,哨兵模式就三个作用:
哨兵的工作模式如下:
一个Master不能正常工作时,哨兵会开始一次自动故障迁移。
哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。Cluster集群就解决了这个问题,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的功能。
分布式存储采用的分布式算法是Hash Slot插槽算法。
插槽算法把整个数据库被分为16384个slot(槽),每个进入Redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。
请求查询的数据在数据库压根儿就不存在,出现非正常URL访问,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到DB数据库上面。大量的无意义的查询落在DB上,明显会增加数据库的压力,严重的可能会引起数据库宕机。
解决方案:
在平常高并发的系统中,大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到DB数据库上面去。某一时刻数据库请求查询量过大,压力剧增,严重可能会引起数据库宕机。
解决方案:
当某一时刻发生大规模的缓存失效的情况(较短的时间内,缓存中较多的key集中过期),比如缓存服务器宕机了,会有大量的请求进来直接打到DB上面,结果就是DB扛不住,直接宕掉。
解决方案:
Redis6.0之前,Redis在处理客户端的请求时,包括读socket、解析、执行、写socket等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。
6.0后redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。
存在的问题:假设线程获取了锁之后,在执行任务的过程中挂掉,来不及显示地执行del命令释放锁,那么竞争该锁的线程都会执行不了,产生死锁的情况。
解决方案:设置锁超时时间
存在问题:setnx 和 expire 不是原子性的操作,假设某个线程执行setnx 命令,成功获得了锁,但是还没来得及执行expire 命令,服务器就挂掉了,这样一来,这把锁就没有设置过期时间了,变成了死锁,别的线程再也没有办法获得锁了。
解决方案:redis的set命令支持在获取锁的同时设置key的过期时间
存在问题:
① 假如线程A成功得到了锁,并且设置的超时时间是 30 秒。如果某些原因导致线程 A 执行的很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B 得到了锁。
② 随后,线程A执行完任务,接着执行del指令来释放锁。但这时候线程 B 还没执行完,线程A实际上删除的是线程B加的锁。
解决方案:
可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁。在加锁的时候把当前的线程 ID 当做value,并在删除之前验证 key 对应的 value 是不是自己线程的 ID。但是,这样做其实隐含了一个新的问题,get操作、判断和释放锁是两个独立操作,不是原子性。对于非原子性的问题,我们可以使用Lua脚本来确保操作的原子性(if redis.call(‘get’,KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end;)
if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){
//加锁
try {
do something //业务处理
} catch(){ }
finally {
//判断是不是当前线程加的锁,是才释放
if (uni_request_id.equals(jedis.get(key))) {
jedis.del(key); //释放锁
}
}
}
以上方式加上lua脚本后比较不错了,一般情况下,已经可以使用这种实现方式。但是存在锁过期释放了,业务还没执行完的问题(实际上,估算个业务处理的时间,一般没啥问题了)。
redisson分布式锁的关键点:
- 对key不设置过期时间,由Redisson在加锁成功后给维护一个watchdog看门狗,watchdog负责定时监听并处理,在锁没有被释放且快要过期的时候自动对锁进行续期,保证解锁前锁不会自动失效
- 通过Lua脚本实现了加锁和解锁的原子操作
- 通过记录获取锁的客户端id,每次加锁时判断是否是当前客户端已经获得锁,实现了可重入锁。
MQ全称为Message Queue,”是在消息的传输过程中保存消息的容器。它是典型的:生产者、消费者模型。适用业务场景:解耦、异步、流量削峰。
RabbitMQ特点:基于AMQP协议、通过插件还支持JMS标准。高并发、高性能、高可用强大的社区支持。支持开发语言众多。
在业务服务模块中解耦、异步通信、高并发限流、超时业务、数据延迟处理等都可以使用RabbitMQ。
简单队列模式、工作队列模式、订阅模式(fanout)、路由模式(direct)、主题模式(topic)、RPC模式、发布确认模式。
public static final String DIRECT_EXCHANGE_NAME = "direct.exchange";
public static final String DIRECT_ROUTING_KEY_NAME = "direct.routing.key";
@ApiOperation("路由模式(direct)发送消息")
@GetMapping("/sendDirectMessage/{msg}")
public String sendDirectMessage(@PathVariable String msg) {
//发送消息到交换机
//参数: 交换机、路由键、消息体
rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME, DirectConfig.DIRECT_ROUTING_KEY_NAME, msg);
return "success";
}
/**
* 步骤:
* 1.配置交换机
* 2.配置队列
* 3.绑定队列到交换机
*/
@Configuration
public class DirectConfig {
public static final String DIRECT_EXCHANGE_NAME = "direct.exchange";
public static final String DIRECT_QUEUE_NAME = "direct.queue";
public static final String DIRECT_ROUTING_KEY_NAME = "direct.routing.key";
//配置交换机
@Bean
DirectExchange directExchange() {
//参数: 交换机名、持久化、自动删除、参数
//return new DirectExchange(DIRECT_EXCHANGE_NAME,true,false,null);
return new DirectExchange(DIRECT_EXCHANGE_NAME);
}
//配置队列
@Bean
Queue directQueue() {
//参数: 队列名、持久化、是否独占、自动删除、参数
//return new Queue(DIRECT_QUEUE_NAME,true,false,false,null);
return new Queue(DIRECT_QUEUE_NAME);
}
//绑定队列到交换机
@Bean
Binding directBinding() {
//参数:队列名、目的类型(使用默认)、交换机名、路右键名、参数
//return new Binding(DIRECT_QUEUE_NAME, Binding.DestinationType.QUEUE, DIRECT_EXCHANGE_NAME,DIRECT_ROUTING_KEY_NAME,null);
return BindingBuilder.bind(directQueue()).to(directExchange()).with(DIRECT_ROUTING_KEY_NAME);
}
}
@Component
//也可应用到方法上
@RabbitListener(queues = DirectConfig.DIRECT_QUEUE_NAME)
public class DircetRecive {
private static final Logger log = LoggerFactory.getLogger(DircetRecive.class);
@RabbitHandler
public void recive(String msg){
log.info("我收到的消息是:{}",msg);
}
}
配置文件配置:
spring:
# 添加配置
rabbitmq:
# 发布方return回调确认开启
publisher-returns: true
# 开启是否达到交换机的回调(相关联的)
publisher-confirm-type: correlated
启动MQ的回调ConfirmCallback和ReturnCallback。
ConfirmCallback是消息无论有没有到达交换机都会触发,到达交换机ack为true、未到达为false。
ReturnCallback是exchange到queue成功则不触发,不成功则触发。(该回调多出现在开发阶段)
在默认情况下,消息消费时出现异常情况,消息会重新入队进行再次消费。如果异常不能及时得以解决,就会出现消息会无限制的投递,占用服务器资源。此时可以开启消费端,消息重试机制,对消息重试进行控制。
spring:
rabbitmq:
listener:
simple:
retry:
#开启消息重发控制
enabled: true
#重发次数
max-attempts: 3
#间隔时间
initial-interval: 3000
根据不同的业务场景,也可以通过编码的方式对消费端消息进行手动应答,来处理异常问题。
spring:
rabbitmq:
listener:
simple:
# 开启手动ack
acknowledge-mode: manual
@Component
public class DirectReliableRecive {
private static final Logger log = LoggerFactory.getLogger(DirectReliableRecive.class);
@RabbitListener(queues = DirectReliableConfig.DIRECT_RELIABLE_QUEUE_NAME)
public void revice(Message message,Channel channel) {
try {
log.info("我收到的消息是:{}",new String(message.getBody()));
int i = 1/0;
//响应成功 第一个参数:消息标识 第二个参数: 是否多个应答
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
e.printStackTrace();
try {
//响应不成功 第一个参数:消息标识 第二个参数: 是否多个应答 第三个参数:是否重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
//消息拒绝 第一个参数:消息标识 第二个参数:是否重新入队
//channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
死信队列:DLX,dead-letter-exchange,利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。
死信的来源:
死信的处理方式
根据消息TTL过期会变为死信的特性,可在延时任务中进行使用(取消未付款订单),在消息发送是指定TTL,不做正常消费。等其变为死信,在数据库中,查询该消息的状态,根据业务进行状态变更。
RabbitMQ中,安装延时队列插件,也可以直接使用延时队列。
在消息的可靠投递中,一个消息发送成功但还没有来的及进行回调处理。定时任务此时查询到该消息状态为-1,则进行了重新发送。那么这个正常的消息,就被发送了两次。出现了幂等性问题。
为了解决幂等性问题,在消息发送时,在Message对象中存储一个消息唯一标识符。
在消费端消费的时候,首先取出标识符,查看在redis中有没有,如果没有就存入到redis中,再进行业务处理。如果有则代表该消息已经被消费过,直接return。
spring:
rabbitmq:
listener:
simple:
# 公平分发
prefetch: 1
# 开启手动ack
acknowledge-mode: manual
max-concurrency: 1 #每次最多拿一条消息
Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发。
nginx接收一个请求后,首先由listen和server_name指令匹配server模块,再匹配server模块里的location,location就是实际地址。
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
}
}
正向代理:一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
正向代理总结就一句话:代理端代理的是客户端。用户是有感知的。
反向代理:是指以代理服务器来接收网络上的连接请求,然后将请求,发给内部网络上的服务器并将从服务器上得到的结果返回给网络上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
反向代理总结就一句话:代理端代理的是服务端。用户是无感知的。
#当客户端访问www.xxxx.com,监听端口号为80直接跳转到真实ip服务器地址 127.0.0.1:8080
server {
listen 80;
server_name www.xxxx.com;
location / {
proxy_pass http://127.0.0.1:8080;
index index.html index.htm;
}
}
Spring cloud 是一个基于 Spring Boot 实现的服务治理工具包,用于微服务架构中管理和协调服务的。Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。有了 Spring Cloud 之后,让微服务架构的落地变得更简单。
其实和SOA 架构类似,微服务是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。服务做到:单一职责、面向服务,对外暴露RestAPI、服务自治。
eureka就是服务注册中心(可以是一个集群),对外暴露自己的地址。服务提供者启动后向eureka注册自己的信息(ip、地址、服务名等)。消费者从eureka拉取注册中心维护的服务列表的副本,通过列表上的服务信息,完成服务调用。服务提供者会定期向eureka发送心跳续约。
注解:
//标明该服务为Eureka的服务端
@EnableEurekaServer
//标明该服务为Eureka的客户端
@EnableEurekaClient
//标明该服务为Eureka的客户端,替代上
@EnableDiscoveryClient
自我保护机制:默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制。
Ribbon是SpringCloud的负载均衡组件。
我们通过服务名配置好负载均衡算法,就可以直接请求到对应的实例上。底层是通过LoadBalancerInterceptor这个类会在对请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务名。
IRule:这是所有负载均衡策略的父接口,里边的核心方法就是choose方法,用来选择一个服务实例。
策略 | 说明 |
---|---|
com.netflix.loadbalancer.RoundRobinRule 轮询策略 | 启动的服务被循环访问 |
com.netflix.loadbalancer.RandomRule 随机选择 | 随机从服务器列表中选择一个访问 |
com.netflix.loadbalancer.RetryRule 重试选择 | 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务 |
BestAvailableRule 最大可用策略 | 先过滤出故障服务器,再选择一个当前并发请求数最小的服务(nacos是NacosRule(com.alibaba.cloud.nacos.ribbon.NacosRule)) |
WeightedResponseTimeRule 带有加权的轮询策略 | 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择 |
AvailabilityFilteringRule 可用过滤策略 | 先过滤出故障的或并发请求大于阈值的服务实例,再选择并发较小的实例 |
ZoneAvoidanceRule 区域感知策略 | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 |
# 这里使用服务提供者的instanceName
# 通过服务名,来进行不同服务的个性化配置
nacos-producer:
ribbon:
# 代表Ribbon使用的负载均衡策略 (nacos的权重负载策略)
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #通过配置文件,配置负载均衡策略
# 同一台服务器上的最大重试次数(第一次尝试除外)
MaxAutoRetries: 1
# 重试的下一个服务器的最大数量(不包括第一个服务器)
MaxAutoRetriesNextServer: 1
# 是否可以为此客户端重试所有操作
OkToRetryOnAllOperations: true
# 从源刷新服务器列表的时间间隔
ServerListRefreshInterval: 2000
# Apache HttpClient使用的连接超时
ConnectTimeout: 3000
# Apache HttpClient使用的读取超时
ReadTimeout: 3000
# 另一个服务名
nacos-producer1:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #通过配置文件,配置负载均衡策略
# 预加载配置,默认为懒加载。我们在这里开启预加载。
# 一般在服务多的情况下,懒加载有可能在第一次访问时造成短暂的拥堵,有可能造成生产故障。
ribbon:
eager-load:
enabled: true
clients: nacos-producer #这里添加的是预加载的服务名
Spring Cloud OpenFeign对Feign进行了增强,是声明式、模板化的HTTP客户端。用于远程服务调用。
OpenFeign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给OpenFeign去做。
//开启feign客户端
@EnableFeignClients
//指定接口代理nacos-producer客户端,path为当前服务接口前缀。
@FeignClient(value = "nacos-producer", path = "/product")
public interface ProductFeignService {
}
OpenFeign中本身已经集成了Ribbon依赖和自动配置,因此不需要额外引入依赖,也不需要再注入 RestTemplate 对象。
hystrix是Netlifx开源的一款容错框架,防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)等功能。
// 添加断路器支持
@EnableCircuitBreaker
@RestController
@RequestMapping("h1")
@DefaultProperties(defaultFallback = "classFallBack")
public class HystrixController01 {
@RequestMapping("/method01/{id}")
//添加到方法上,表明该方法开启服务降级、熔断
@HystrixCommand(fallbackMethod = "methodFallBack")
//@HystrixCommand
public String method(@PathVariable String id) {
try {
TimeUnit.SECONDS.sleep(5);//出现sleep interrupted睡眠中断异常
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello world!";
}
public String methodFallBack(String id) {
return "系统异常,请及时联系客服人员。";
}
/**
* com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException:
* fallback method wasn't found: classFallBack([])
* 这里要注意,我们在class的回调方法中,形参列表必须为空。
* 不然会出现以上错误。
* @return
*/
public String classFallBack(/*String id*/) {
return "系统异常,请及时联系客服人员。";
}
}
服务雪崩效应产生与服务堆积在同一个线程池中有关,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,这时候可能会导致其他服务没有线程进行接受请求,这就是服务雪崩效应。
@RestController
@RequestMapping("h2")
public class HystrixController02 {
@GetMapping("/method01/{id}")
@HystrixCommand(fallbackMethod = "methodFallBack",
//测试thread和semaphore 两种隔离策略的异同
// execution.isolation.strategy 默认为thread
commandProperties = {
//@HystrixProperty(name= HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "THREAD")}
@HystrixProperty(name= HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE")}
)
public String method01(@PathVariable String id){
//xxxx
}
@GetMapping("/method02/{id}")
@HystrixCommand(fallbackMethod = "methodFallBack",
commandProperties = {
设置超时的时候不中断线程,默认为true
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT,value="false")}
)
public String method02(@PathVariable String id){
//xxxx
}
@GetMapping("/method03/{id}")
@HystrixCommand(fallbackMethod = "methodFallBack",
commandProperties = {
//设置熔断策略为semaphore,并且最大连接为1 (可以通过该思路来实现,限流)
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="1")}
)
public String method03(@PathVariable String id){
//xxxx
}
@GetMapping("/method04/{id}")
@HystrixCommand(fallbackMethod = "methodFallBack",
commandProperties = {
//设置是否超时,默认为true
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_TIMEOUT_ENABLED,value="false")}
)
public String method04(@PathVariable String id){
//xxxx
}
@GetMapping("/method05/{id}")
@HystrixCommand(fallbackMethod = "methodFallBack",
commandProperties = {
//设置过期时间,单位:毫秒,默认:1000
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value="6000")}
)
public String method05(@PathVariable("id")String id){
//xxxx
}
public String methodFallBack(String id) {
return "系统异常,请及时联系客服人员。";
}
}
服务在高并发的情况下出现进程阻塞,导致当前线程不可用,慢慢的全部线程阻塞 ,导致服务器雪崩。这时直接熔断整个服务,而不是一直等到服务器超时。
/**
* circuitBreaker.enabled :true 打开熔断 默认开启
* circuitBreaker.requestVolumeThreshold: 当在配置时间窗口内达到此数量的失败后,进行短路。默认20个
* circuitBreaker.sleepWindowInMilliseconds:短路多久以后开始尝试是否恢复,默认5s
* circuitBreaker.errorThresholdPercentage:出错百分比阈值,当达到此阈值后,开始短路。默认50%
*/
@GetMapping("/method06/{id}")
@HystrixCommand(fallbackMethod = "methodFallBack",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")}
)
public String method06(@PathVariable String id){
//xxxx
}
@EnableFeignClients
//@SpringBootApplication
//@EnableCircuitBreaker //开启熔断器
//@EnableDiscoveryClient
@SpringCloudApplication //以上三个注解的组合注解
feign:
hystrix:
#feign是否开启熔断
enabled: true
Fallback类,相比于FallbackFactory,工厂可以用来获取到触发断路器的异常信息。建议使用FallbackFactory。
FallbackFactory
/**
* fallbackFactory 指定一个fallback工厂,与指定fallback实现类不同,
* 此工厂可以用来获取到触发断路器的异常信息,
* ProductFeignFallBack需要实现FallbackFactory类
* 指定服务的serviceName
*/
@FeignClient(value = "rest-template-user",fallbackFactory = ProductFeignFallBack.class)
@Component
public interface Product02Feign {
@GetMapping("/product/getString")
public String getString();
}
@Component
@Slf4j
/**
* 必须实现FallBackFactory,且它的泛型必须是你要指定地点feign的接口。
*/
public class ProductFeignFallBack implements FallbackFactory<ProductFeign> {
@Override
public ProductFeign create(Throwable throwable) {
return new ProductFeign() {
@Override
public String getString() {
log.error("fallback reason:{}",throwable.getMessage());
return "我犯错了,我知道。";
}
};
}
}
Spring Cloud Gateway是建立在Spring生态系统之上的API网关,Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到api。所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
gateway相当于所有服务的门户,将客户端请求与服务端应用相分离,客户端请求通过gateway后由定义的路由和断言进行转发,路由代表需要转发请求的地址,断言相当于请求这些地址时所满足的条件,只有同时符合路由和断言才给予转发。最后过滤器是对请求的增强。
spring:
cloud:
gateway:
# 默认全局过滤器,对所有路由生效
default-filters:
# 响应头过滤器,对输出的响应设置其头部属性名称为
# X-Response-Default-MyName,值为 lz
# 如果有多个参数多则重写一行设置不同的参数
- AddResponseHeader=X-Response-Default-MyName, lz
# 路由
routes:
- id: feign-consummer-route
uri: lb://feign-consummer
# 谓词
predicates:
- Path=/**
# 过滤器
filters:
- PrefixPath=/feign
@Component
public class AuthMyFilter implements GlobalFilter, Ordered {
/**
* @description 过滤器执行方法
* @param exchange 前后端交互信息,包括request与response
* @param chain 过滤器链
* @return 下个过滤器直到过滤器结束或者校验不通过返回结果
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//开始执行鉴权方法
//获取请求参数
String token = exchange.getRequest().getHeaders().getFirst("token");
if (StringUtils.isEmpty(token)) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
String msg = "token is null!";
DataBuffer wrap = response.bufferFactory().wrap(msg.getBytes());
return response.writeWith(Mono.just(wrap));
}
// 调用下个过滤器(过滤器是基于函数回调)
return chain.filter(exchange);
}
/**
* @description 定义过滤器优先级,数字越小优先级约高,可以为负数
*/
@Override
public int getOrder() {
return 0;
}
}
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
#允许跨域的请求路径
'[/**]':
#允许的来源
allowedOrigins: "*"
#允许的方法
allowedMethods:
- GET
- POST
#是否允许携带cookie
allowCredentials: true
#允许http请求携带的header
allowedHeaders:
- Content-Type
#response应答可以暴露的header
exposedHeaders:
- Content-Type
配置中心。
分布式系统中,由于服务数量非常多,配置文件分散在不同的微服务项目中,config支持配置文件放在远程Git仓库(GitHub、码云),对配置文件集中管理。
Bus是消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。 Spring Cloud Bus可选的消息代理有RabbitMQ和Kafka。对配置文件进行监控和通知。
致力于服务发现、配置和管理微服务。
相比于eureka来说,nacos除了可以做服务注册中心。其实它也集成了服务配置的功能,我们可以直接使用它作为服务配置中心。
分布式容错机制。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。相比于hystrix功能更加丰富,还有可视化配置的控制台。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
使用时先定义好资源埋点。只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。
注解实现流控
可配置的参数:
#对Feign的支持
feign:
sentinel:
enabled: true # 添加feign对sentinel的支持
@FeignClient(value = "nacos-producer", path = "/product", fallback = ProductFeignFallback.class)
public interface ProductFeignService {
//xxxxx
}
@Component
public class ProductFeignFallback implements ProductFeignService {
//xxxx
}
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
在分布式系统中,系统间的网络不能100%保证健康,一定会有故障的时候,而服务有必须对外保证服务。因此Partition Tolerance不可避免。
如果此时要保证一致性,就必须等待网络恢复,完成数据同步后,整个集群才对外提供服务,服务处于阻塞状态,不可用。
如果此时要保证可用性,就不能等待网络恢复,那节点之间就会出现数据不一致。
也就是说,在P一定会出现的情况下,A和C之间只能实现一个。
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论,有两种解决思路:
BASE理论是对CAP的一种解决思路,包含三个思想:
**TC (Transaction Coordinator) - 事务协调者:**维护全局和分支事务的状态,驱动全局事务提交或回滚。这个就是我们的Seata服务器,用于全局控制。
**TM (Transaction Manager) - 事务管理器:**定义全局事务的范围:开始全局事务、提交或回滚全局事务。
**RM (Resource Manager) - 资源管理器:**管理分支(本地)事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
AT模式的核心是对业务无侵入,是一种改进后的两阶段提交。
简单总结:
Dubbo是一款高性能、轻量级的开源RPC框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和Spring框架无缝集成。
节点角色说明:
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方。 |
Consumer | 调用远程服务的服务消费方。 |
Registry | 服务注册与发现的注册中心。 |
Monitor | 统计服务的调用次数和调用时间的监控中心。 |
调用关系说明:
可以通讯。启动Dubbo 时,消费者会从Zookeeper拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。
Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀。
RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题。
LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求。
ConstantHash LoadBalance: 一致性Hash策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动。
默认为Random随机调用
默认的容错方案是Failover Cluster。
Dubbo超时设置有两种方式:
服务提供者端设置超时时间,在Dubbo的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。
服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。
因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。
dubbo在调用服务不成功时,默认是会重试两次。
ZooKeeper 是一个开源的分布式协调服务。它是一个为分布式应用提供一致性服务的软件,分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构。
这里面的每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息。
节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下。
节点可以分为四大类:
有了 zookeeper 的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
我们可以基于zookeeper的两种特性来实现分布式锁,首先我们来看第一种:
我们可以基于唯一节点特性来实现分布式锁的操作。多个应用程序去抢占锁资源时,只需要在指定节点上创建一个 /Lock 节点,由于Zookeeper中节点的唯一性特性,使得只会有一个用户成功创建 /Lock 节点,剩下没有创建成功的用户表示竞争锁失败。
这种方法虽然能达到目的,但是会有一个问题,假设有非常多的节点需要等待获得锁,那么等待的方式自然是使用watcher机制来监听/lock节点的删除事件,一旦发现该节点被删除说明之前获得锁的节点已经释放了锁,那么此时剩下的B、C、D节点会同时收到删除事件从而去竞争锁,这个过程会产生惊群效应。
什么是“惊群效应”呢?简单来说就是如果存在许多的客户端在等待获取锁,当成功获取到锁的进程释放该节点后,所有处于等待状态的客户端都会被唤醒,这个时候zookeeper会在短时间内发送大量子节点变更事件给所有待获取锁的客户端,然后实际情况是只会有一个客户端获得锁。如果在集群规模比较大的情况下,会对zookeeper服务器的性能产生比较的影响。
为了解决惊群效应,我们可以采用Zookeeper的有序节点特性来实现分布式锁。
每个客户端都往指定的节点下注册一个临时有序节点,越早创建的节点,节点的顺序编号就越小,那么我们可以判断子节点中最小的节点设置为获得锁。如果自己的节点不是所有子节点中最小的,意味着还没有获得锁。这个的实现和前面单节点实现的差异性在于,每个节点只需要监听比自己小的节点,当比自己小的节点删除以后,客户端会收到watcher事件,此时再次判断自己的节点是不是所有子节点中最小的,如果是则获得锁,否则就不断重复这个过程,这样就不会导致惊群效应,因为每个客户端只需要监控一个节点。
Docker 是一个开源的应用容器引擎。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
Dockerfile 是一个文本文件,其中包含我们需要运行以构建 Docker 映像的所有命令。Docker 使用 Dockerfile 中的指令自动构建镜像。我们可以docker build用来创建按顺序执行多个命令行指令的自动构建。
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。
vue的生命周期通常有8个,分别是创建前后,挂载前后,更新前后,销毁前后,分别对应的钩子函数有beforeCreate创建前,created创建后,beforeMount挂载前,moubted挂载后,beforeUpdate更新前,updated更新后,beforeDestory销毁前,destoyed销毁后。
此元素进入页面后,此元素只会显示或隐藏不会被再次改变显示状态,此时用v-if更加合适。当用v-if来隐藏元素时,初次加载时就不用渲染此dom节点,提升页面加载速度
此元素进入页面后,此元素会频繁的改变显示状态,此时用v-show更加合适。当用v-show来隐藏元素时,只会在初次加载时渲染此dom节点,之后都是通用display来控制显隐。