目录
数据库MySQL
数据库的优化
- 少用select *
- 数据库中尽量不要留有空(null)值,尽量用 not null 填充。
select * from user where age is null 总不会比 select * from user where age = 0 好用吧
- 使用join得时候,以小的一方驱动大的一方,比如left join 左边尽量放结果小的,小的先处理
- 多表联查尽量拆分为多个query,多表联查效率低,容易锁表和堵塞
select * from user left join admin on admin.user_id = user.user_id and user.user_Id > 100 优化为: select * from (select * from user where user_id > 100) u left join admin on admin.user_id = u.user_id
- limit 基数大时使用between,例:limit 100000,100010 改为where id between 100000 and100010
- 尽量避免在列上做运算,会导致索引失效
select * from user where year(time) > '2020' 改为: select * from user where time > '2020-01-01'
数据库安全
- 服务端对sql预编译,防止sql注入,例如mybatis的 #{ }
- 不使用明文密码
- 服务器开启防火墙.
- 除了root用户其他用户不允许访问MySQL主数据库的user表。
10亿个手机号查询
- 先考虑是否为空,是否是国内,是否唯一等等
- 分区分表
- 对于查询某个手机号是否存在,可以在数据库上层加一层布隆过滤器,提高效率。
分区、分表、分库、分片
- 分区:就是把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的,水平分区(行数),垂直分区(列)主要可以提升查询效率
- 分表:就是把一张表按一定的规则分解成N个具有独立存储空间的实体表。系统读写时需要根据定义好的规则得到对应的字表明,然后操作它单表的并发能力提高了,磁盘I/O性能也提高了,写操作效率提高了
- 分库:一旦分表,一个库中的表会越来越多,为突破单节点数据库服务器的 I/O 能力限制,解决数据库扩展性问题。
Mysql的存储引擎
存储引擎有十几个,默认支持的是InnoDB,一个数据库多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能
- MyISAM:大文件,高的插入、查询速度,但不支持事物,适用主要用来插入和查询记录
- InnoDB:事务型数据库的首选,支持事务行锁外键和非锁定读,提供具有提交,回滚,崩溃恢复能力的事务安全,处理巨大数据量,用在需要高性能的大型数据库站点,并发控制
- Memory:数据存储到内存,临时存放数据
- Archive:适合存储归档数据,如记录日志信息可以使用Archive
Mysql索引
MySQL数据库支持多种索引类型,如BTree索引,B+Tree索引,哈希索引,全文索引等等,InnoDB 的数据文件本身就是索引文件
普通索引:最基本的索引,它没有任何限制,用于加速查询
唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一
主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
组合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较
虽然索引可以增加查询速度,但对于更新、创建或者删除的时候,需要去维护索引,导致性能会受影响。
索引原理
索引是一种数据结构,可以快速访问数据库表提取信息
索引最终选用B+树数据结构,从二叉排序树到二叉平衡树,再到B树,最终演化成了B+树
B+树的非叶子节点并不直接存储可以从磁盘中取出关键值的指针,而是存储关键值的索引,关键值只存储在叶子节点中,并且叶子节点组成了一个有序链表。比较的次数均衡,减少了IO次数,提高了查找速度,查找也更稳定。
查找id为9的关键值数据,id一次比较,查找成功;由于节点并不存储关键值的数据指针,所以本次比较不需要IO操作,顺着指针找到叶子节点,并根据叶子节点的数据指针进行一次IO操作,取出完整数据,查找完成。
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
InnoDB的数据文件本身就是索引文件,所有辅助索引都引用主索引,InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
你每次的个数,注意该索引并不存储全部数据。创建表,系统会为你自动创建一个基于ID的聚集索引(上述B+树),存储全部数据;你每次增加索引,数据库就会为你创建一个附加索引(上述B+树),索引选取的字段个数就是每个节点存储数据索引
回表
比如你创建了name, age索引 ng_index,查询数据时使用了
select * from table where name ='bill' and age = 21;
由于附加索引中只有name 和 age,因此命中索引后,数据库还必须回去聚集索引中查找其他数据,这就是回表,这也是你背的那条:少用select * 的原因。
索引覆盖
结合回表会更好理解,比如上述ng_index索引,有查询
select name, age from table where name ='bill' and age = 21;
此时select的字段name,age在索引ng_index中都能获取到,所以不需要回表,满足索引覆盖,效率较高。
隔离级别
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
- READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
- READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
- REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。(mysql默认)
- SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。
Java知识点
面向对象
封装,数据与操作数据的方法绑定,对数据的访问只能通过已定义的接口
继承,从已有类继承信息得到新类的过程
多态,不同子类型的对象可以对同一消息做出不同的反应
抽象,将一类对象的信息总结出来构造类的过程,只关心对象有哪些属性和行为
常用的设计模式
Singleton,单例模式:
保证一个类只有一个实例,并提供一个访问它的全局访问点
1,饿汉式,直接创建对象返回,定义类的静态私有变量同时进行实例化
2,懒汉式,调用时创建对象返回
Abstract Factory,抽象工厂:
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。
Factory Method,工厂方法:
定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类。
Iterator,迭代器模式:
提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
Observer,观察者模式:
定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
多线程
什么是线程和进程
进程:在操作系统中能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从创建、运行到消亡的过程。
线程:是一个比进程更小的执行单位,能够完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程中可以产生多个线程。
为什么要用多线程
- 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;
- 进程之间不能共享数据,线程可以;
- 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;
- Java语言内置了多线程功能支持,简化了java多线程编程。
线程的生命周期
新建(New)- 就绪(start() )- 运行(CPU开始调度)-等待/阻塞/睡眠 - 终止 (退出了run())
创建线程的方法
单继承多实现
- 继承Thread类 ,重写run()方法
- 实现Runnable接口, 重写run()方法
- 实现Callable接口
- 使用线程池创建
线程同步与锁
当多个线程同时操作一个可共享资源变量时(如对其进行增删改查操作),会导致数据不准确,而且相互之间产生冲突。所以加入同步锁以避免该线程在没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性
synchronized (this) { //同步代码块 }
死锁
进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁
多线程锁优化
一、尽量不要锁住方法
二、缩小同步代码块,只锁数据
三、锁中尽量不要再包含锁
四、将锁私有化,在内部管理锁
五、进行适当的锁分解高并发,线程锁
多线程场景
web服务器本身
后台任务,例如:定时向大量(100w以上)的用户发送邮件;
异步处理,例如:发微博、记录日志等;
分布式计算
IO与NIO
IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区,ava IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。
NIO(New IO)是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个
JDK自带4种的线程池
固定线程数的线程池(newFixedThreadPool)
因此永远不可能拒绝任务,在入队列和出队列时使用的是不同的Lock,意味着他们之间不存在互斥关系,在多CPU情况下,他们能正在在同一时刻既消费,又生产,真正做到并行。因此这种线程池不会拒绝任务,而且不会开辟新的线程,也不会因为线程的长时间不使用而销毁线程。这是典型的生产者----消费者问题,这种线程池适合用在稳定且固定的并发场景
缓存的线程池(newCachedThreadPool)
核心池大小为0,线程池最大线程数目为最大整型,这意味着所有的任务一提交就会加入到阻塞队列中当目前线程处理忙碌状态时,所以开辟新的线程来处理请求),线程进入wait set。总结下来:①这是一个可以无限扩大的线程池;②适合处理执行时间比较小的任务;③线程空闲时间超过60s就会被杀死,所以长时间处于空闲状态的时候,这种线程池几乎不占用资源;④阻塞队列没有存储空间,只要请求到来,就必须找到一条空闲线程去处理这个请求,找不到则在线程池新开辟一条线程。如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。
单个线程的线程池(newSingleThreadExecutor)
当线程运行时抛出异常的时候会有新的线程加入线程池替他完成接下来的任务。创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,所以这个比较适合那些需要按序执行任务的场景。比如:一些不太重要的收尾,日志等工作可以放到单线程的线程中去执行
固定个数的线程池(newScheduledThreadPool)
相比于第一个固定个数的线程池强大在 ①可以执行延时任务,②也可以执行带有返回值的任务
高并发
就是在某个时间点,有大量访问同时到来
前端优化:减少HTTP请求,合并css或js,添加异步请求,启用浏览器缓存和文件压缩,CDN加速,建立独立图片服务器,
服务端优化:页面静态化,并发处理,队列处理
数据库优化:数据库缓存,分库分表,分区操作,读写分离,负载均衡
web服务器优化:负载均衡,nginx反向代理Java中的动态代理
JDK的动态代理,就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程
1、静态代理代理的一个类,事先知道代理的是什么,动态代理代理的是一个接口下多个实现类,事先不知道代理的是什么,运行的时候才知道
2、jdk动态代理代理的是接口,需要业务类去实现接口,CGLIB动态代理代理的是类,不需要业务类继承接口。通过派生的子类实现代理,动态修改字节码来修改类
3,常用列子,AOP,Spring框架,Hibernate框架
HashMap如何解决hash冲突
分离链表法
开放定址法
当hash表的单一链表长度超过 8 个的时候,链表结构就会转为红黑树结构
HashMap的数据结构
在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个链表,所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
HashMap底层是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。哈希表结构:结合数组结构和链表结构的优点,从而实现了查询和修改效率高,插入和删除效率也高的一种数据结构
为何效率高:增删是在链表上完成的,而查询只需扫描部分,则效率高。
map.put(k,v)实现原理
- 首先将k,v封装到Node对象当中(节点)。
- 然后它的底层会调用K的hashCode()方法得出hash值。
- 通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
map.get(k)实现原理
- 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
- 通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。
链表变得很长很长效率会非常慢
jdk1.8最重要的就是引入了红黑树的设计,当hash表的单一链表长度超过 8 个的时候,链表结构就会转为红黑树结构。
- 红黑树查询:其访问性能近似于折半查找,时间复杂度 O(logn);
- 链表查询:这种情况下,需要遍历全部元素才行,时间复杂度 O(n);
简单的说,红黑树是一种近似平衡的二叉查找树,其主要的优点就是“平衡“,即左右子树高度几乎一致,以此来防止树退化为链表,通过这种方式来保障查找的时间复杂度为 log(n)。
HashMap扩容
什么时候扩容:当前容器的元素个数,如果大于等于当前数组的长度乘以加载因子的值
resize()方法扩容:使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里
单点登陆Session跨域工程的解决方案
1、Cookie技术是客户端的解决方案
2、session复制,每一台服务器上都保持一份相同的session
3、session集中存储:存储db redis
4、session sticky:会话保存在单机上,保证会话请求落在一台服务器上
http socket
- HTTP是应用层协议,主要解决如何包装数据。即超文本传送协议
- socket则是对TCP/IP协议的封装和应用(程序员层面上)。
- TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,
JVM内存结构
Java虚拟机运行时数据区域主要包含了PC寄存器(程序计数器)、Java虚拟机栈、本地方法栈、Java堆、方法区以及运行时常量池
堆和栈的区别是什么
堆和栈(虚拟机栈)是完全不同的两块内存区域,一个是线程独享的,一个是线程共享的,二者之间最大的区别就是存储的内容不同:
堆中主要存放对象实例。
栈(局部变量表)中主要存放各种基本数据类型、对象的引用。反射
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制
中间件
Redis
持久化策略
1、无持久化:数据只在服务器运行时存在
2、快照,在指定时间间隔内生成数据集的时间快照,断电可能会数据丢失
3、AOF文件,记录服务器执行的所有写操作命令,并在服务器启动后重新执行这些命令还原数据
五种类型
String,List,Hash,Set,Sorted Set
Sorted Set排序:和集合一样也是 string 类型元素的集合,且不允许重复的成员。但每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间
缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁参考代码如下:
- 获取缓存-无>加锁>获取数据库...>获取缓存-无>有锁>等待一段时间重试
缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同得缓存数据库中。
- 设置热点数据永远不过期。
redis分布式锁
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
确保分布式锁可用可靠性
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
加锁就一行代码:
jedis.set(String key, String value, String nxxx, String expx, int time)
,这个set()方法一共有五个形参:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用
UUID.randomUUID().toString()
方法生成。第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。
在redis里放一个具有锁标识,服务器标识,过期时间的数据
rabbitmq
- 1.解耦,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!
- 2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
- 3.削峰,并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
缺点:降低了系统的稳定性:本来系统运行好好的,现在你非要加入个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性会降低;
异步处理:把耗时操作,单独交给独立的业务系统,通过消息队列作为中间件,达到应用解耦的目的,并且消耗的资源很低,单台服务器能承受更大的并发请求
应用解耦:假设下单成功,库存失败,发货成功,当我们修复库存的时候,不需要任何管数据的不一致性,因为库存队列未被处理的消息,会直接发送到库存系统,库存系统会进行处理。实现了应用的大幅度解耦
流量削峰:秒杀活动中,控制队列长度,当请求来了,往队列里写入,超过队列的长度,就返回失败
日志处理:实时性要求不高,用队列处理再好
消息通讯
Kafka
开源的消息发布订阅系统,它主要用于处理活跃的流式数据,大数据量的数据处理上。
- 日志收集:
- 消息系统:解耦和生产者和消费者、缓存消息等。
- 用户活动跟踪:
- 运营指标:Kafka也经常用来记录运营监控数据。
- 流式处理:比如spark streaming和storm
- 事件源
框架
SpringMVC流程
- 用户发送请求至前端控制器
- 前端控制器收到请求调用处理器映射器,处理器映射器根据请求url找到具体的处理器,返回给前端控制器。
- 前端控制器根据处理器请求处理器适配器,
- 处理器适配器向前端控制器返回ModelAndView
- 前端控制器请求视图解析器去进行视图解析
- 视图解析器向前端控制器返回View
- 前端控制器进行视图渲染
- 前端控制器向用户响应结果
Mybatis
Mybatis是一个半自动 ORM 框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,直接编写原生态sql,可以严格控制sql执行性能,灵活度高
- 基于 SQL 语句编程,相当灵活
- SQL 写在 XML 里,解除 sql 与程序代码的耦合
- 与 JDBC 相比,减少了 50%以上的代码量
- 不需要手动开关连接
- 很好的与各种数据库兼容
- 能够与 Spring 很好的集成
Spring
Spring是一个轻量级的IoC和AOP容器框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。
IOC是一种编程思想,利用这种思想可以很好地实现模块之间的解耦。控制反转(对象的创建由spring容器控制管理),依赖注入(通过set和构造方法自动设置对象需要的值)
Spring Bean的生命周期
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
主要逻辑都在doCreate()方法中,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应。
- createBeanInstance() -> 实例化
- populateBean() -> 属性赋值
- initializeBean() -> 初始化
Struts
MVC模式,工作流程
- 1、客户端浏览器发出HTTP请求.
- 2、根据web.xml配置,该请求被FilterDispatcher接收
- 3、根据struts.xml配置,找到需要调用的Action类和方法, 并通过IoC方式,将值注入给Aciton
- 4、Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。
- 5、Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面
- 6、返回HTTP响应到客户端浏览器
hibernate
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的
- get和load都是利用主键策略查询数据,
- get默认不使用懒加载机制,load默认要使用懒加载机制,所谓的懒加载就是我们这个数据如果不使用,hibernate就不发送SQL到数据库查询数据。
- 当查询数据库不存在的数据的时候,get方法返回null,load方法抛出空指针异常,
hibernate把他所管理的数据划分为三种状态
瞬时的(刚new出来的数据–内存有,数据库没有)
持久的 (从数据查询的,或者刚保存到数据库,session没关闭的, 数据库有,内存也有)
游离的 、脱管的(数据库有,内存没有)hibernate分为2级缓存
一级缓存又叫session缓存,又叫事务级缓存,生命周期从事务开始到事务结束,一级缓存是hibernate自带的,暴力使用,当我们一创建session就已有这个缓存了。数据库就会自动往缓存存放,
二级缓存是hibernate提供的一组开放的接口方式实现的,都是通过整合第三方的缓存框架来实现的,二级缓存又叫sessionFactory的缓存,可以跨session访问。常用的EHcache、OScache,这个需要一些配置。当我们每次 查询数据的时候,首先是到一级缓存查看是否存在该对象,如果有直接返回,如果没有就去二级缓存进行查看,如果有直接返回,如果没有在发送SQL到数据库查询数据,
当SQL发送查询回该数据的时候,hibernate会把该对象以主键为标记的形式存储到二级缓存和一级缓存,如果返回的是集合,会把集合打散然后以主键的形式存储到缓存。一级缓存和二级缓存只针对以ID查询的方式生效,get、load方法。安全框架shiro
是Java的一个安全框架,可以帮我们完成认证、授权、加密、会话管理、web集成、缓存等
权限控制的四个级别:
url级别权限控制,方法注解权限控制,代码级别权限控制,页面标签级别权限控制
SpringCloud简介与5大常用组件
springcloud是微服务架构的集大成者,将一系列优秀的组件进行了整合。基于springboot构建
springcloud项目是由多个独立项目集合而成的,每个项目都是独立的,各自进行自己的迭代和版本发布。所以springcloud不方便使用版本号来管理,而是使用版本名。以避免和子项目版本号的冲突
SpringCloud核心组件——Eureka—服务启动时,Eureka Client都会将服务注册到Eureka Server同时Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里
Eureka是服务架构的注册中心专门负责服务的注册和发现;
由两个组件组成:Eureka服务端和Eureka客户端,服务端用作服务注册中心。支持集群部署,客户端是一个java客户端,用来处理服务注册与发现
在应用启动时,Eureka客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息
SpringCloud核心组件——Feign—基于动态代理机制,根据注解,拼接请求URL地址,选择机器,发起请求
Feign,通过对一个接口打上注解,他会基于这个注解标注的接口生成动态代理,然后针对Feign的动态代理去调用他的方法时,会在底层生成http协议格式的请求
客服端负载均衡——Ribbon—服务间发起请求时,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
主要提供客户侧的软件负载均衡算法,服务部署在多台服务器上,Ribbon根据 负载均衡算法 选择一台机器出来
断路器——Hystrix—服务间的请求走不同的Hystrix线程池,避免了服务雪崩
断路器,保护系统,控制故障范围。是一个隔离,熔断,降级的框架
隔离:当服务A请求调用服务B,C时,请求服务B时一个线程池,请求服务C是一个线程池。相互之间互不影响
熔断:当服务A完成一项逻辑业务,需要调用服务B, 如果服务B挂掉了,每次调用到会被卡住几秒钟,会影响用户的操作体验,我们一般直接对服务B熔断,不走网络请求的那几秒钟,这个过程,就是熔断。
降级:服务B熔断了,此时服务B不能啥都不干啊,此时,就来个降级,此时需要往数据库表或日志文件中记录一条消息,说某个用户因为服务B挂掉,进行的操作没成功,并且带上参数,等到 服务B服务再次回复,你可以根据这些记录手动补上记录。
服务网关——Zuul—提供统一的请求入口,Zuul会将这些请求转发给相对应的服务
api网关,主要负责 网络路由,负载均衡等多种作用,类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。
如果前端、移动端要调用后端系统,不用管后台有多少服务实例,所有的请求只需直接集中走网关,网关会将请求转发给后台的所有微服务。
SpringCloud工作流程
Docker
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口
kubernetes
kubernetes,简称K8s,是用8代替8个字符“ubernete”而成的缩写。是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制
Jenkins
Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。
教程:https://www.jianshu.com/p/5f671aca2b5a
jetty
Jetty比较简单,和Tomcat大致相同,Jetty较于Tomcat属于轻量级,而且在处理高并发细粒度请求的场景下显得更快速高效。所以使其也拥有众多使用场景,合适的选择应该为:云平台本身的门户网站放在Tomcat内,而云台托管的Java Web应该是部署在Jetty内的
教程:https://blog.csdn.net/weixin_38978094/article/details/87917711
高并发下的事务处理
- 分布式事务:本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性
消息事务+最终一致性:开源的RocketMQ就支持这一特性
TCC编程:TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。TCC就是通过代码人为实现了两阶段提交不能很好地被复用。
- 分库分表:当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表
- 应用SOA化:电商网站,对整个网站进行拆解,分离出了订单中心、用户中心、库存中心,为了保证数据一致性,就需要用到分布式事务。
分布式事务
- 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
- TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。
- 本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。
事务实现应该是具备原子性、一致性、隔离性和持久性,简称 ACID。
- 原子性(Atomicity),可以理解为一个事务内的所有操作要么都执行,要么都不执行。
- 一致性(Consistency),可以理解为数据是满足完整性约束的,也就是不会存在中间状态的数据,比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态。
- 隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。
- 持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。
2PC二阶段提交
同步阻塞协议,协调者有超时机制
准备阶段协调者会给各参与者发送准备命令,同步等待所有资源的响应之后就进入第二阶段即提交阶段(注意提交阶段不一定是提交事务,也可能是回滚事务)。
3PC
3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。
TCC
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等
CC 指的是
Try - Confirm - Cancel
。
- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
TCC 对业务的侵入较大和业务紧耦合,用的范围更大,但是开发量也更大,毕竟都在业务上实现,所以TCC可以跨数据库、跨不同的业务系统来实现事务。
本地消息表
实现的是最终一致性,容忍了数据暂时不一致的情况。
- 本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
- 然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
- 有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理
RocketMQ 消息事务
RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。
ps:作者其实见题凉
Java最新面试题整理与记录,持续更新
最新推荐文章于 2024-09-04 02:23:05 发布