Java面试资料——持续更新

基础篇

1、HashMap的数据结构

HashMap由数组和链表组成,每个key经过hash计算之后得到某个值,然后将节点对象存储到数组中相应的位置,如果有多个值经过hash计算之后得到相同的值,在数组对象中存储多个节点对象的链表,每个对象除了key,value还有指向下一节点地址

2、HashMap和HashTable的区别

1、HashTable是线程安全的,所有的方法都被synchronized修饰,而hashMap是线程不安全的,在多线程下使用hashMap时,需要先将hashMap转换成线程安全的集合
2、hashMap最多允许一个空的key存在,允许多个空值存在,但hashTable不允许空的key,value存在
3、hashTable中hash数组的默认大小是11,而hashMap是16
4、hashTable继承自Dictionary类,而hashMap继承自AbstractMap类

3、线程安全的集合

线程安全的集合:Vector,HashTable
线程不安全的集合:ArrayList,TreeSet,HashSet,LinkedHashSet,HashMap,LinkedHashMap,TreeMap
使用Arrays和Conllecttions工具类可以将线程不安全的集合转为线程安全的集合,否则高并发下使用线程不安全的集合会抛出ConcurrentModificationException异常

4、接口和抽象类的区别

1、抽象类允许包含某些方法的实现,而接口不允许
2、抽象方法只能被public,abstract修饰
3、抽象类中的成员变量可以被public/private/protected final等修饰,而接口中只能被public static final修饰
4、抽象类只能被继承,一个类只能继承一个抽象类,而接口只能被实现,一个类可以实现多个接口

5、StringBuffer和StringBuilder区别

StringBuffer是线程安全的,StringBuilder线程不安全,多线程下使用StringBuffer,StringBuilder是单线程,所以效率较高

6、多线程

6.1、sleep和wait的区别

sleep方法会主动让出cup给其他线程使用,达到指定时间之后,cup使用权重新回到该线程并执行,如果线程进入到同步锁中,sleep并不会释放锁,即便已经让出了cpu,其他线程也无法执行
wait是指一个已经进入同步锁的线程,让自己暂时释放锁,给其他线程竞争锁的机会并执行,只有其他线程调用了notify方法,wait状态的线程就会接触该状态并参与竞争锁的队列中,直到获取锁并执行

6.2、synchronized和java.uti.concurrent.locks.Lock的区别

Lock能实现synchronized的所有功能,synchronized能自动释放锁,而Lock必须手动释放,并且在finally从句中释放,Lock的tryLock可以非阻塞式获得锁

6.3、run()和start()方法的区别

run()方法只是普通的方法被调用运行
start()方法是启动线程,由jvm运行run方法,执行start方法并不一定立马启动线程执行,只是意味着该线程代表的虚拟机处于可运行(就绪)状态,当分配到时间片之后,由jvm调用run方法启动线程执行。

6.4、synchronized的原理

synchronized是jvm虚拟机实现的一种同步互斥的一种方式,被synchronized修饰的代码块经过编译之后会生成monitorenter和monitorexit两个字节码指令,当虚拟机运行到monitorenter指令时,会先去获取对象的锁,若对象没有被锁定或者当前线程已经获取到对象的锁,会把锁的计数器+1,当执行到monitorexit指令时,锁的计数器-1,当锁的计数器为0时,释放对象锁。如果对象锁获取失败,当前线程就需要阻塞等待,直到对象锁被另一个线程释放。

6.5、线程的创建方式

1、继承Thread类:重写该类的run方法,调用start方法启动
2、实现runnable接口:定义一个类来实现Runnable接口,然后实例化该类,并将其作为构造参数传递给Thread类的实例对象,调用start方法启动线程,需重写run方法,没有返回值
3、实现callable接口:重写call方法,并且call执行后有返回值(返回一个Future对象,表示异步计算的结果,通过该对象能取消任务的执行和获取执行的结果)
4、创建线程池
	newCachedThreadPool()		-> 没有核心线程,且最大线程无限大
	newFixedThreadPool(Long)	-> 最小最大线程数相同,需要自己传入,即核心线程和非核心线程数都由自己指定
	newSingleExecutor()			-> 最小最大线程数都为1,即核心线程和非核心线程均为1
	
以上几种线程创建方式的对比:
实现runnable和callable接口的线程适用于多线程进行资源共享(目标对象可以复用),线程池中只能放入runnable和callable的实现类中,不能直接放入继承Thread的类

6.6、线程池

1、newCachedThreadPool方式创建的线程池没有核心线程,最大线程数无限大,执行效率最高,但可能会出现内存溢出的异常
ExecutorService executorService = Executors.newCachedThreadPool();//快

2、newFixedThreadPool方式创建的线程池最小最大线程数相同,需要自己传入,即核心线程和非核心线程数都由自己指定
ExecutorService executorService = Executors.newFixedThreadPool(10);//慢

3、newSingleThreadExecutor方式创建的线程池最小最大线程数都为1,即核心线程和非核心线程均为1,执行效率最慢
ExecutorService executorService = Executors.newSingleThreadExecutor();//最慢

4、自定义创建线程池
new ThreadPoolExecutor(
                核心线程数, 允许最大线程数, 非核心线程空闲存活时间(0永久存),
                存活时间的单位(TimeUnit.MILLISECONDS...),
                队列,
                创建线程的工厂,
                拒绝策略);

线程池的参数补充:

队列:
	1、有界队列:ArrayBlockingQueue,需要自己指定队列长度
	2、无界缓冲队列:LinkedBlockingQueue、SynchronousQueue
拒绝策略:
	1、终止策略:AbortPolicy,当队列放满时,如果当前线程数等于最大线程数,新进来的任务就直接被拒绝,并丢弃任务抛出异常
	2、丢弃策略:DiscardPolicy,直接将任务丢弃,且不会抛出异常,用户不会感知到,可能会造成数据丢失
	3、丢弃最旧未处理的策略:DiscardOldestPolicy,丢弃队列中的头节点的任务,将被拒绝的新任务添加到队列中
	4、CallerRunsPolicy:当线程和队列都满了的时候,此时的新任务是谁提交谁执行

案例:

// 核心线程数1,最大线程数5,队列只能存放94,此时有100个任务进来,势必会有一个任务放不下而被拒绝 
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1, 5, 0L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(94),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i=0; i<=100;i++) {
            try {
                threadPoolExecutor.execute(new MyTask(i));
            } catch (Exception e) {

            }
        }
        threadPoolExecutor.shutdown();
        //threadPoolExecutor.shutdownNow();

6.7、多线程下如何保证线程执行顺序

使用join方法
如有T1,T2,T3三个线程,不确定三个线程的执行顺序,需要保证这三个线程按顺序执行
在T2线程的run方法内调用T1线程的join方法(表示想要执行T2线程,必须等T1线程执行完),同理在T3线程的run方法内调用T2线程的join方法即可实现线程按指定顺序执行

6.8、线程死锁

死锁是只两个或两个以上的线程在执行过程中,由于互相竞争资源或彼此通信而造成的一种阻塞现象,被称为死锁
如:线程1拥有资源1的锁,线程2拥有资源2的锁,当两个线程都试图去获取对方的资源时,就会形成死锁状态

6.9、避免线程死锁

1、避免一个线程同时获取多个锁
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
3、使用定时锁,用lock.tryLock(timeout)非阻塞式获得锁

7、TCP协议与socket通讯

tcp属于传输层协议,而socket是应用层和传输层之间的一个抽象层
tcp建立连接需要三次握手,而断开连接需要四次握手,socket可以保持长连接
tcp服务端和客户端使用socket通讯

tcp连接过程:首先客户端向服务端发送连接请求的信号(第一次握手),服务端接收到来自客户端的信号之后返回一个信号(第二次握手),并在此基础上额外增加一个信号,客户端接收到服务端的返回的信号后给出回应(第三次握手)

tcp关闭连接过程:客户端发起断开连接请求(第一次握手),服务端接收到请求后给客户端返回(第二次握手),这时客户端到服务端的单项流动被关闭,而服务端还是可以向客户端发送消息,直到服务端向客户端发送挥手信号(第三次握手),客户端接收信号后给服务端回应(第四次握手),至此,双向通道才会关闭

8、JVM虚拟机

Java虚拟机,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的

8.1 GC垃圾回收

当程序创建了对象之后,GC就会自动监测该对象的地址 、大小以及使用情况,一旦确定对象超过作用区域没人使用的时候,就会对其进行回收内存空间,jvm一般会分为堆、栈和方法区,堆存储的是对象的实例和数组等,栈存储的是基本数据类型及对象的引用,方法区存储的是被虚拟机加载的类(class)、静态变量、常量等数据,而GC的主要回收目标是堆,堆又分为新生代和老年代,方法区成为持久代,新生代是新创建的对象,这个是GC回收的主要目标,这里面的对象存活时间较短,只有少部分对象能够活下来

Minor GC:新生代GC
Major GC:老年代GC,执行老年代GC会连带执行新生代GC
Full GC:清理整个堆空间,包括年轻代和老年代

Spring框架篇

1、aop和ioc的区别

ioc控制反转,也称DI依赖注入,简单的说就是把原先需要程序员手动创建的对象和关系依赖,通过ioc容器来创建并管理(通过xml配置文件配置bean对象及依赖关系,或者使用注解@annotation),利用java反射功能实例化bean并且建立依赖关系。
	针对ioc补充bean的生命周期:实例化bean->属性赋值->初始化->销毁
aop动态代理,是整个ioc流程中新增的一个扩展出来的功能,在不改变原有代码的前提下,实现额外的功能行为的过程,如:记录日志,权限控制,监控方法运行的时间,事务管理(调用方法前开启事务,执行方法之后关闭事务)

ioc实现机制:工厂模式+反射机制

aop动态代理的流程:
1、先获取需要代理的原始对象,然后创建代理工厂
2、设置代理对象
3、选择代理策略(这里选择advice类)并生成代理对象(jdk或者cglib方式生成代理对象)
4、获取代理对象并执行代理方法

jdk方式代理代码详解:

		// 获取原始对象
        AopDemoService aopDemoService = new AopDemoServiceImpl();
        // 创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 设置代理目标
        proxyFactory.setTarget(aopDemoService);
        // 选择代理策略
        proxyFactory.addAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation methodInvocation) throws Throwable {
                //代理对象方法执行之前做的逻辑处理
                System.out.println("before");
                Object proceed = methodInvocation.proceed();
                //代理对象方法执行之后做的逻辑处理
                System.out.println("after");
                return proceed;
            }
        });
        // 获取代理之后的对象
        AopDemoService proxy = (AopDemoService) proxyFactory.getProxy();
        proxy.getUserInfo("张静");

2、Spring事务的特性

原子性:要么一起成功,要么一起失败,并且回滚
一致性:事务执行的前后要保持数据的一致
隔离性:多个事务之间是相互隔离互不影响的
持久性:事务一旦提交,数据的改变是持久的

补充事务回滚的步骤:

当需要开启事务的时候,会获取数据库连接,然后关闭自动提交,开启事务,当事务执行中抛出了异常,事务会感知并执行回滚操作,如果执行过程中没有出现异常,就会调用commit提交事务

3、分布式事务

分布式事务保证分布式系统的数据一致性,在分布式系统上,一次大的操作可能由多个小的操作完成,而这些小操作分布在各个不同的系统中,分布式事务就是要保证这些操作要么一起成功,要么失败回滚
分布式事务组件:Spring Cloud Alibaba中的seata(使用注解@GlobalTransactional)

4、设计模式

单例模式:
	Bean默认的都是单例模式,读取配置文件也是单例模式
	饿汉式:不管用不用的上,一开始就创建出这个bean,线程安全
	懒汉式:只在使用的时候创建bean,线程不安全,需要使用synchronized修饰
工厂模式:
	BeanFactory就是简单的工厂模式的体现,用来创建对象的实例
代理模式:
	Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
模板方法:
	用来解决代码重复的问题,如:RestTemplate,JmsTemplate,JpaTemplate
观察者模式:
	定义对象建一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖他的对象都会得到通知和更新,如Spring中的listener监听器的实现-ApplicationLisener

5、Spring中Bean的生命周期

1、实例化 -> 无参构造,创建bean实例
2、属性赋值 -> 调用set方法设置属性值
3、初始化
4、生存期 -> 获取bean的实例
5、销毁 -> 使用完调用destory方法销毁

6、Spring使用@Autowired自动装配的过程

Spring用@Autowired注解来自动装配指定的bean,在使用注解之前,需要在Spring配置文件中配置<context:annotation-config/>,在启动SpringIOC容器时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowired,@Resource,@Inject等注解的时候,就会在容器中查找需要的bean,并装配给该对象的属性。

补充:在使用@Autowired注解时,首先在容器中查找对应类型的bean,如果查询的结果只有一个,就直接将该bean装配给指定的数据,如果查询出来多个,那么@Autowired就会根据名称来查找,如果查询结果为空,就会抛出异常,使用required=false来解决

7、事务失效的原因

1、bean对象没有被spring容器管理,导致ioc容器无法实现代理,从而导致事务失效
2、方法的修饰符不是public
3、自身调用问题,如同一个类中有A,B两个方法,其中A没有添加事务注解,B添加了事务注解,当A方法中调用了B方法,也会导致B方法事务失效
4、数据源没有配置事务管理器
5、数据库不支持事务,如:MySQL的MyISAM存储引擎
6、异常被捕获
7、配置了错误的异常类型,如:定义回滚异常类型为空指针异常,当抛出了其他类型异常时会导致事务失效

8、SpringMVC的请求流程

1、用户发起请求到前端控制器,前端控制器根据请求信息分配给页面控制器进行处理
2、页面控制器接收到请求后,进行功能处理,并解析请求的参数,得到一个命令对象,将该对象给业务对象处理并返回一个ModelAndView
3、前端根据返回的ModelAndView选择相应的视图渲染
4、最后将响应返回给用户

MySql篇

1、mysql的隔离级别

读取未提交:
	最低级别,一般不会使用,因为不安全,所有事务都能看到未提交的数据,会出现脏读现象
读取已提交:
	大多数数据库默认的级别,但不是mysql的默认级别,不可重复读(一个事务内,多次读同一数据),因为可能会出现同一条sql语句返回的结果不同,即同一事务的其他实例在该实例处理期间可能会有新的提交
可重读:
	MySQL的默认级别,确保同一事务在并发读取时,会看到同样的数据行,会导致幻读现象,当用户读取某个范围内的数据行时,另一个事务在该范围内插入了新的数据,当用户再次读取这个范围内的数据行时,就会出现幻影行
可串行化:
	最高的隔离级别,强制事务进行排序,使之不可能互相冲突,解决幻读问题,是在每个读的数据行加上共享锁,会导致大量的超时现象和锁竞争,从而造成数据库的性能下降,这种隔离级别一般也不会使用

2、mysql中有哪几种锁

1、表级锁:开销小,加锁快,不会出现死锁;锁定粒度大(每次操作都是对整张表加锁),发生锁冲突的概率最高,并发度低
2、行级锁:开销大,加锁慢,会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度高
3、页面锁:开销和加锁的时间介于表级锁和行级锁之间,会出现死锁;锁定粒度也位于两者之间,并发度一般

3、MyISAM和InnoDB存储引擎的区别

MyISAM:不支持事务,每次查询都是原子的,支持表级锁,即每次操作是对整个表加锁,存储表的总行数,该存储引擎模式下,每张表都由三个文件构成(.frm -> 表结构文件,.MYD -> 表数据文件,.MYI -> 数据索引文件),采用非聚集索引(索引和数据是分离的,需要先从索引文件中找到数据地址的行记录,然后去数据文件中查到数据)

InnoDB:支持事务,支持行级锁及外键约束,可支持并发写,不存储总行数,该存储引擎模式下,每张表由(.frm和.idb两种文件组成),采用聚集索引(叶子节点包含完整的数据记录,在.idb文件中找到索引就找到了完整的数据记录)

使用场景:
MyISAM:适合读多写少的场景
InnoDB:适合写多的场景,能处理多重并发的更新请求,因为支持事务,可回滚
		例如:
			需要支持事务的场景:转账,付款等
			数据读写及更新都比较频繁:BBS、SNS、微博、微信等
			数据一致性要求较高:转账、充值

4、锁的优化策略

1、读写分离
2、分段加锁
3、减少锁的持有时间,多个线程尽量以相同的顺序去获取资源,不能将锁的粒度过于细化,不然可能会出现线程的加锁和释放次数过多降低效率

5、索引的底层实现原理和优化

底层实现原理是用的B+树,主要在所有的叶子节点中增加了指向下一个叶子节点的指针
索引的优化:
1、尽量全值匹配
2、最佳左前缀法则
	如:索引列 name,age,pos
	select * from user where name = 'zhangsan' -> 会用到索引name
	select * from user where name = 'zhangsan' and age = 25 -> 会用到索引name和age
	select * from user where name = 'zhangsan' and pos = 'dev' -> 只会用到索引name,因为跳过了age
	select * from user where age = 25 and pos = 'dev' -> 不会用到索引,因为最左前列没有name
3、不在索引列上做任何操作
4、范围条件放在最后
5、尽量使用覆盖索引
6、尽量不用不等于,会导致索引失效
7、尽量不用null和not null
	1、在字段为not null的情况下,如果使用is null和is not null会导致索引失效
	如:select * from user where name is not null; -> 索引失效
	解决方法:使用覆盖索引,如:select name,age,pos from user where name is not null;
	2、在字段可以为null的情况下,使用is null,索引正常,使用is not null索引失效,解决方法同上
8、使用like查询时,最左侧不能出现通配符,否则会导致索引失效
	如:select * from user where name like 'july%'; -> 索引正常使用
9、字符类型要加引号,否则索引会失效
10、OR改成UNION
	如:select * from user name = 'zhangsan' or name = 'lisi'; -> 索引失效
	解决方法:
	1、覆盖索引:select name,age from user where name = 'zhangsan' or name = 'lisi';
	2、UNION:select * from user where name = 'zhangsan' UNION select * from user where name = 'lisi'

6、常用索引

覆盖索引:要查询的数据只从索引中就能取得,即查询的列要被索引覆盖
组合索引
前缀索引
唯一索引
默认使用B+树结构的组合索引

7、索引失效的原因

1、非覆盖索引使用了select *
2、不满足最左前缀原则
3、索引列上有计算
4、索引列上用了函数
5、字符类型没有加引号
6、like查询时,左边出现了通配符%
7、非覆盖索引使用了or查询
8、使用了不等于查询
9、范围索引列没有放在最后
10、非覆盖索引使用了is not null

8、悲观锁和乐观锁

悲观锁:每次拿数据的时候都会认为数据会被修改,所以每次想拿数据的时候都会上锁,直到拿到锁。
乐观锁:每次拿数据的时候都会认为数据不会被修改,所以不会上锁,但是在更新的时候会判断在此期间是否有人去更新这个数据。
总结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景

Redis篇

1、redis数据结构

字符串(String)、数组(list)、集合(set)、有序集合(zset)、哈希(hash)

2、redis实现分布式锁

使用setnx命令,格式:setnx key value 如:setnx lock_key 1
setnx命令是指如果key不存在就创建,如果存在就不做任何操作,当线程拿到同步锁,即setnx命令设置键成功,其他没有拿到同步锁的线程,即setnx命令设置失败了
删除分布式锁的命令:del key,如:del lock_key

3、redis持久化机制

redis有两种持久化机制:
RDB:在指定时间将当前时刻内存中的数据生成一份快照文件(默认为dump.rdb),并且保存至在磁盘上,即便redis宕机了,在服务恢复的时候就会加载快照文件中的数据并恢复到redis缓存来达到持久化的目的。
AOF:这种持久化模式是指redis会将每一次写的操作记录在aof文件的末尾,当redis宕机或者重启的时候,就会加载aof文件来恢复数据,需要定期清理文件体积,因为会逐渐增大,而且加载慢

4、Redis高可用方案

4.1、主从架构

redis主从架构是实现高可用的一种模式,通过复制的方式,将主服务器上的redis数据同步复制到从服务器上,采用读写分离的方式,数据只能写进主节点,然后从主节点复制到从节点,可以根据实际情况配置从节点的个数,读数据的时候,主从节点都可以提供服务。
当主节点发生故障之后,可以手动选择其中一个从节点作为主节点继续服务

4.2、哨兵模式

由一个或多个sentinel实例组成的sentinel系统,可以监视所有的主节点和从节点,当被监视的主节点进入下线状态时,会自动将该主节点下的某个从节点升级为主节点(通过发布订阅模式通知其他从节点,修改配置文件让他们切换主机)

4.3、集群

一个redis集群由多个redis节点组成(至少需要6个节点才能保证高可用,三组主节点,每组主节点至少对应一个从节点),实现了redis的分布式存储,对数据进行分片,每台redis节点上存储不同的内容,来解决在线扩容的问题。节点组内部分主备两类节点,两者数据一致,通过异步化的主备复制实现,每个节点组有且只有一个主节点,当某个节点组的主节点发生故障,从节点会自动选举成为主节点提供服务,如果主节点下没有从节点,当主节点发生故障后,集群将不可用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDbuGpAN-1667463761092)(Java面试资料整理-最新.assets/image-20221018100222475.png)]

5、Redis缓存和数据库不一致

高并发场景下容易出现该问题,如果先删除了缓存,还没来得及删除数据库,而另一个线程就来读取数据,发现缓存为空,就会去查找数据库,设置缓存,上一个线程执行到删除数据库的操作之后就会造成缓存和数据库不一致的现象

如何优化?
1、采用延时双删:有个弊端,只是减少了概率的发生,且演长了请求时间
	redis.del(key);
	db.update(data);
	Thread.sleep(300);
	redis.del(key);
2、异步更新缓存

6、缓存击穿

是指redis中键过期了,导致大量的请求到数据库,可能导致数据库瞬间被压垮

如何优化?
1、设置热点数据永不过期
2、采用mutex互斥锁,当缓存失效的时候,不是立即去db查数据,而是先用setnx命令去设置一个mutex key锁住,其他线程拿不到锁就会等待,当某个线程拿到锁之后,去数据库查数据并回设到缓存中,然后释放锁

7、缓存穿透

是指请求redis中key对应的数据不存在,导致大量的请求到数据库,从而可能压垮数据,如:用一个不存在的id恶意发起攻击

如何优化?
1、设置并发锁,如果获取到锁了,才能查数据库,然后释放锁
2、设置拦截器,对于不存在的key进行拦截

8、缓存雪崩

缓存服务器重启或者大量的key在某个时间失效,导致请求到数据库

如何优化?
设置key的失效时间使用随机值,指定随机值产生的范围

9、Redis过期键的删除策略

1、定时删除:
	创建一个定时器,当某个key过期时,定时器立即将其删除并释放key所占的内存,如果有大量键过期,在执行删除时会占用相当一部分的cpu,从而对性能造成影响
2、惰性删除:
	当key到期之后先不做处理,等下次访问的时候去判断如果过期了就删除,如果未过期就直接返回数据,如果一直没获取就会造成内存的浪费
3、定期删除:
	对上面两种策略的折中处理,采用定期轮询删除的方式,每次轮询随机抽取过期键进行删除

10、Redis数据淘汰策略

noeviction:
	不进行数据淘汰,当缓存被写满时,再有写的请求进来,redis将不再提供服务,直接返回错误
allkeys-lru:
	缓存写满之后,使用LRU算法在所有的数据中进行筛选删除
volatile-lru:
	缓存写满之后,针对过期的键使用LRU算法进行淘汰删除
allkeys-random:
	缓存写满之后,对所有的键进行随机删除
volatile-random:
	缓存写满之后,针对设置了过期时间的键进行随机删除
volatile-ttl:
	缓存写满之后,针对设置了过期时间的键,根据过期时间的先后顺序进行删除

数据库框架篇

1、MyBatis

Mybatis是一个半ORM(对象关系映射)框架,内部封装了JDBC,开发时只需要关注sql本身,不需要花精力去加载驱动、创建连接、创建statemenet等过程,sql语句可以自己去控制并调优,且灵活度高,方便维护
hibernate只需要配置POJO与数据库表的关系映射即可,不需要自己写sql就能自动生成sql语句并调用jdbc接口来执行,数据库的移植性较高

2、Hibernate

hibernate是一个全自动的ORM框架,只需配置POJO与数据库表的关系映射,无需程序员写sql,可移植性强,提供了缓存机制

2.1 hibernate的工作流程

1、读取并解析配置文件
2、读取并解析映射文件,创建SessionFactory
3、打开Session
4、创建事务
5、进行持久化操作(调用session的save/saveOrupdate/list/load/get等方法)
6、提交事务
7、关闭session
8、关闭SessionFactory

2.2 缓存机制

hibernate有一级缓存和二级缓存

一级缓存:也叫session缓存,只在session作用范围内有效,无需用户干涉,有hibernate自身维护,调用evict(Object)清除object缓存,clear()清除一级缓存中的所有缓存,flush()刷出缓存。

二级缓存:应用级别的缓存,在所有session中都有效,支持配置第三方的缓存,如EhCache

2.3 hibernate的几种状态

临时/瞬时状态:直接new出来的对象,还没被持久化(没保存在数据库中),不受session管理
持久化状态:当调用session的save/saveOrupdate/get/list/load等方法的时候,进入持久化状态
游离状态:session关闭之后就是游离状态

中间件篇

消息中间件

1、rocket MQ与Kafka的区别

1.1 数据可靠性
rocket MQ支持异步刷盘、同步刷盘两种方式,同步复制和异步复制
Kafka使用异步刷盘的方式,同步复制和异步复制

总结:rocket MQ支持Kafka所不具备的同步刷盘功能,在单机可靠性上比Kafka更高,Kafka的数据以partition分区为单位,一个Kafka实例可能会有多个分区,而rocket MQ只有一个分区,而且rocket MQ可以充分利用IO组的commit机制批量传输数据,所以rocket MQ的复制性能比Kafka高
1.2 性能
Kafka每秒的单机写入在百万条/秒,消息大小为10个字节,而rocketMQ为7万条/秒,若单机部署3个broker可达到12万条/秒,消息大小为10字节,Kafka主要应用于日志场景,而rocketMQ应用在业务场景

为什么Kafka会这么快?
	是因为producer(消息生产者)端将多个小消息合并,批量向broker发送,如果缓存过多消息,GC是个很严重的问题,其次如果producer端发送了消息,还没有到达broker就通知业务消息发送成功,而此时broker宕机,就会造成消息丢失,而rocketMQ没有这么做是为了确保消息必达牺牲了性能,没有在rocketMQ层做消息合并,主张在业务层自己做
1.3 单机支持的队列数
Kafka单机若超过64个partition/队列,cpu使用率会明显飙升,随着partition的增多,cpu使用率越高,导致发消息的响应时间越长
rocket MQ单机最高支持5万个队列,而且cpu使用率不会有明显的变化
由于rocketMQ支持的队列数远高于Kafka,所以支持的consumer(消息消费者)集群也更多
1.4 消息投递的实时性
在这一点上Kafka采用的短轮询,实时性取决于轮询的时间间隔,后面的版本也支持长轮询,rocket MQ采用长轮询,同push实时性一致,且两者消息投递的延迟在几毫秒内
1.5 消费失败重试
Kafka不支持消费失败重试,而rocketMQ支持,使用定时重试,每次重试间隔时间顺延
消费失败重试主要用于第一次消费由于服务器压力过大导致失败,之后可能会调用成功的场景
1.6 消息有序
Kafka可以保证同一个partition上的消息有序,一旦broker宕机之后,消息就会乱序
rocketMQ支持严格的消息顺序,broker宕机之后,仅会造成发送消息失败但不会乱序
1.7 定时消息
Kafka不支持定时消息,rocketMQ支持定时级别,定时级别用户可定制
1.8 分布式事务消息
Kafka不支持分布式事务消息,而rocketMQ支持
1.9 消息查询
Kafka不支持消息查询,rocket MQ支持根据消息标识查询消息,也支持根据消息内容查询消息,用于排查消息丢失问题。
1.10 消息回溯
Kafka可按照消息的offset回溯消息,rocketMQ支持按时间来回溯,精确到毫秒
1.11 消息并行度
Kafka的消息并行度依赖于topic中配置的partition个数,最多用几台机器来消费取决于partition的数量
rocketMQ的并行度等于消费的线程数,不受队列数的限制,顺序消费的并行度与Kafka一致,乱序消费取决于consumer的线程数,如topic配置10个队列,10台机器消费,每台机器100个线程,那么并行度为1000
1.12 消息堆积能力
这一点上Kafka的消息堆积能力比rocketMQ要好,rocketMQ单机也能支持亿级的消息积压能力,完全能满足日常的需求了

2、应用场景

2.1 异步处理
如异步发送短信,在业务处理完不用等短信发送成功就可结束流程,发送短信使用异步的方式处理
2.2 服务解耦
如分布式系统中的下单流程,主流程结束后,之后的业务由消息队列异步执行,如:通过消息队列完成短信通知、积分服务、营销服务等,如果后续有其他功能需要实现,只需要订阅响应的topic即可
2.3 削峰填谷
高并发的请求存入消息队列,后端服务通过消费消息来处理请求

3、rocket MQ发送消息模式

1、同步消息
	不会丢失消息,且有返回值
	应用场景:重要的消息通知、短信通知
2、异步消息
	不会丢失消息,消息发送出去之后有一个监听器去异步监听MQ的响应
	应用场景:用户视频上传后通知启动解码服务,转码完成后推送结果
3、单向发送
	只管发送,不关心结果,没有返回值
	应用场景:日志收集

4、rocket MQ消息的丢失分析

1、生产者发送时丢失
	解决方案:发送确认 + 重试
2、rocket MQ自身丢失
	解决方案:搭集群,使用主从架构 + 持久化,如多主多从的异步刷盘、同步复制的高可用模式
	生产者发送一条消息是先存入主节点,主节点会将消息同步复制到从节点备份,MQ的消息先是存到内存,如果使用异步刷盘的方式,会把消息刷到磁盘上进行持久化,消费者读取消息优先从主节点上获取进行消费,只有当主节点繁忙或者宕机的时候,才会去从节点消费消息
3、消费者消费时丢失
	解决方案:重试 + 死信消息

5、消息重复消费

1、幂等性处理,使用MVCC(版本控制器)处理,需要引入版本号
幂等性概念:每次运行的结果都一样,称之为幂等性,如:update user set age = 12 where id = 1
非幂等性如:update user set age = 12 + 1 where id = 1
解决方案:
update user set age = 12 + 1, version = version + 1 where id = 1 and version = 1
经过如上解决之后,每次运行的结果都不一样

2、去重表方案
添加一张去重表,给指定id设置唯一索引,当消费一条消息之后,先往这张表添加一条数据,添加成功之后在去做其他操作,如果添加失败,说明这条消息已经消费过,这时只需捕获异常return出去即可,多线程使用synchronized加锁,若数据库压力大可使用缓存。

Dubbo

dubbo是一款高性能的RPC分布式服务框架,提供服务自动注册、自动发现等高效的服务治理方案,可以和spring框架无缝集成

1、服务发现与注册

使用zookeeper的服务注册与发现,服务提供方与服务消费方的application.yml配置如下

dubbo:
  application:
    name: 自定义服务名
  protocol:
    name: dubbo
    port: -1
  registry:
    id: zk-registry
    # 此处出现的ip端口与服务提供方启动时配置的一致
    address: zookeeper://127.0.0.1:3333
  config-center:
    address: zookeeper://127.0.0.1:3333
  metadata-report:
    address: zookeeper://127.0.0.1:3333

服务提供方启动zookeeper流程

1、实例化解析配置文件的对象QuorumPeerConfig
2、实例化zookeeper服务端启动类ZooKeeperServerMain
3、实例化解析配置类ServerConfig
4、将第一步创建出来的配置文件解析对象设置进解析配置类中
5、启动ZooKeeperServerMain

代码如下:

// 1、实例化解析配置文件的对象QuorumPeerConfig
Properties properties = new Properties();
File file = new File(System.getProperty("java.io.tmpdir")
+ File.separator + UUID.randomUUID());
file.deleteOnExit();
properties.setProperty("dataDir", file.getAbsolutePath());
properties.setProperty("clientPort", String.valueOf(clientPort));
QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig();
quorumPeerConfig.parseProperties(properties);
// 2、实例化zookeeper服务端启动类ZooKeeperServerMain
zkServer = new ZooKeeperServerMain();
// 3、实例化解析配置类ServerConfig
ServerConfig configuration = new ServerConfig();
// 4、将配置文件解析对象设置进解析配置类中
configuration.readFrom(quorumPeerConfig);
// 5、启动ZooKeeperServerMain
zkServer.runFromConfig(configuration);

2、服务提供方实现接口

将实现的接口用@DubboService注解

代码如下:

@DubboService
public class UserServiceImpl implements UserService {
    @Override
    public String getUserInfo(String username) {
        System.out.println("你好:" + username + ", 来自客户端消费者的请求:" + RpcContext.getContext().getRemoteAddress());
        return "你好 " + username;
    }
}

3、服务消费方调用远程接口

使用@DubboReference注解注入远程接口,即可在本地直接调用远程方法

代码如下:

@Service
public class CustomerUserService {

    @DubboReference
    private UserService userService;

    public String getUserInfo(String username) {
        return userService.getUserInfo(username);
    }
}

4、服务注册与发现的流程

1、服务容器负责启动,加载,运行服务提供者
2、服务提供者在启动时,会向注册中心注册自己提供的服务
3、服务消费者在启动时,会向注册中心订阅自己需要的服务
4、注册中心返回服务提供者提供的地址列表给服务消费者,如有变更,注册中心将基于长连接推送变更数据给消费者
5、服务消费者从服务列表中基于软负载均衡算法,选一台服务进行调用,如果调用失败再换一台
6、服务提供者和消费者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计到监控中心

5、Dubbo和Spring Cloud的区别

两者并没有直接的联系,Dubbo主要关注的是服务调用、流量分发、流量监控和熔断,而SpringCloud考虑的是服务治理的方方面面,更注重打造的是一个生态。
Dubbo底层是弄netty框架,基于TCP协议传输,配合以Hession序列化完成RPC通信
Spring Cloud是基于http协议Rest接口调用远程服务的过程,相对来说,Http请求会有更大的报文,占的带宽也更高,但rest比rpc更为灵活,没有代码级别的依赖

6、Dubbo注册中心集群挂掉,发布者和订阅者之间能否通信

消费者在启动时,会从zookeeper注册中心拉取提供者提供的服务列表,然后缓存到本地,每次调用的时候,会按照本地存储的地址进行调用,所以就算集群挂掉,依然可以与发布者通信

7、Dubbo集群的负载均衡策略

1、Random LoadBalance:	——	默认使用的策略
	随机选取提供者策略:利于动态调整提供者权重,调用越多,分布越均匀。
2、RoundRobin LoadBalance:
	轮询选取提供者策略:平均分布,存在请求累积的问题
3、LeastActive LoadBalance:
	最少活跃调用策略:解决慢提供者接收更少的请求
4、ConstantHash LoadBalance:
	一致性hash策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者。

8、Dubbo集群的容错方案

1、Failover Cluster —— 默认的容错方案
	失败自动切换:当出现失败的时候,自动切换到其他服务器,用于读操作,重试会带来更长的延迟
2、Failfast Cluster
	快速失败:只发起一次调用,失败立马报错,用于非幂等性操作,如新增记录
3、Failsafe Cluster
	失败安全:忽略出现的异常,如写入日志
4、Failback Cluster
	失败自动恢复:后台记录失败请求,定时重发,如消息通知
5、Forking Cluster
	并行调用多个服务器,只要一个成功即返回:用于实时性较高的读操作,会浪费较多的服务器资源,可设置forks的值控制最大并行数
6、Broadcast Cluster
	广播调用所有提供者,任意一台报错则报错,用于通知所有提供者更新缓存或日志等本地资源信息

微服务篇

1、Spring Cloud Alibaba

学习地址:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md

服务注册与发现:Nacos,默认集成了Ribbon负载均衡
声明式HTTP请求:Feign,通过定义Feign接口,实现如Dubbo一样本地调用远程接口方法,通过Nacos注册中心的服务名加接口地址调用,会自动使用Ribbon负载均衡的对服务器发起调用。
分布式事务:Seata,使用@GlobalTransactional
服务熔断降级:Sentinel,流量防卫兵
消息系统:Rocket MQ
网关:Gateway

1.1 架构流程

1、nacos的搭建与配置
	//搭建和配置省略,另有文档说明
	搭建好Nacos服务端之后,只需在项目中引入相关依赖并且配置文件加入配置即可将服务注册到nacos注册中心
2、客户端连接nacos注册中心订阅相应服务
	引入相关依赖包,配置文件加入nacos与feign相关配置,启动类添加@EnableDiscoveryClient与@EnableFeignClients注解
3、使用Feign声明式Http请求客户端调用注册中心的服务地址进行消费
4、其他组件,如Rocket MQ、Gateway、sentinel、seata则按实际需求进行添加

2、Feign的原理

启动的时候会进行包扫描,扫描包下所有@FeignClient注解的类,并将这些类注入到IOC容器中,当定义Feign中的接口被调用时,通过JDK动态代理来生成RequestTemplate,RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。通过RequestTemplate生成Request交给client处理,client默认是JDK的HTTPUrlConnection,也可以是OKhttp和Apache的HttpClient,最后client封装成LoadBalanceClient,结合Ribbon负载均衡的发起调用。	

ttps://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md

服务注册与发现:Nacos,默认集成了Ribbon负载均衡
声明式HTTP请求:Feign,通过定义Feign接口,实现如Dubbo一样本地调用远程接口方法,通过Nacos注册中心的服务名加接口地址调用,会自动使用Ribbon负载均衡的对服务器发起调用。
分布式事务:Seata,使用@GlobalTransactional
服务熔断降级:Sentinel,流量防卫兵
消息系统:Rocket MQ
网关:Gateway

1.1 架构流程

1、nacos的搭建与配置
	//搭建和配置省略,另有文档说明
	搭建好Nacos服务端之后,只需在项目中引入相关依赖并且配置文件加入配置即可将服务注册到nacos注册中心
2、客户端连接nacos注册中心订阅相应服务
	引入相关依赖包,配置文件加入nacos与feign相关配置,启动类添加@EnableDiscoveryClient与@EnableFeignClients注解
3、使用Feign声明式Http请求客户端调用注册中心的服务地址进行消费
4、其他组件,如Rocket MQ、Gateway、sentinel、seata则按实际需求进行添加

2、Feign的原理

启动的时候会进行包扫描,扫描包下所有@FeignClient注解的类,并将这些类注入到IOC容器中,当定义Feign中的接口被调用时,通过JDK动态代理来生成RequestTemplate,RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。通过RequestTemplate生成Request交给client处理,client默认是JDK的HTTPUrlConnection,也可以是OKhttp和Apache的HttpClient,最后client封装成LoadBalanceClient,结合Ribbon负载均衡的发起调用。	
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值