天天向上
2023-06-28 12:21:42 0 举报
AI智能生成
知识点
作者其他创作
大纲/内容
项目
自我介绍
面试官你好,我是XXX,来自于XXX。之前就职于XX科技有限公司,担任软件开发工程师一职。在职期间主要负责XX公司XX项目与研发。对线上问题处理,项目与数据库性能调优等问题都有自己的理解。对行业相关的研发设计流程也十分熟悉。因此决定面试咱们公司Java开发岗位。希望能获此机会,谢谢。
1.项目架构
2.项目中的难点
3.介绍下项目中比较复杂的实现
上线计划
发布计划:先进行DDL(增加字段时不要加非空约束,否则会对之前有影响),再发布代码,先发布服务提供方,再发布服务调用方
线上问题
生产环境cpu飙升
这类资源问题,第一时间要做的是两件事:
1. 问下其他人有无在这段时间做什么骚操作
2. 根据监控,快速排查各指标有无异常,比如某个http接口qps异常飙升。
当所有方便的手段用完之后,还是无法解决问题,就应该上机器用各种命令去查看,甚至是dump下jvm堆栈信息排查
1. 问下其他人有无在这段时间做什么骚操作
2. 根据监控,快速排查各指标有无异常,比如某个http接口qps异常飙升。
当所有方便的手段用完之后,还是无法解决问题,就应该上机器用各种命令去查看,甚至是dump下jvm堆栈信息排查
线下问题
启动耗时长
观察方法
1.利用BeanPostProcessor接口中postProcessBeforeInitialization与postProcessAfterInitialization方法在Bean初始化前后,统计初始化耗时
2.利用底层探针,例如arthas的trace命令统计每个方法耗时
3.手动debug
耗时原因
1.数据库初始化耗时->a.数据库连接池初始化;b.获取数据库元数据初始化
2.因为分库分表都需要将以上初始化
解决方案
a.数据库连接池初始化(设置Druid中asyncInit为true,进行懒加载)
b.获取数据库元数据初始化(将ColumnMetaData等数据变为JSON.存储在本地,直接读取文件;在工程中定义同样的包名类名,就可以把里面覆盖掉)
延伸
1.如果本地启动容器不想注册到注册中心,可以实现BeanPostProcessor接口,重写postProcessBeforeInitialization方法,将ProviderConfig中register字段set为false;
2.以上代码还应该进行环境隔离.利用@Conditional(TestEnvirChangCondition.class)指定环境;在TestEnvirChangCondition中实现Condition接口,重写matches方法.conditionContext.getEnvironment().getProperty(keyName,Boolean.class,defaultValue);
2.1建议使用 Spring 原生 Profiles 来区分集成测试环境
a. 在 DataSourceDefinitionProcessor 和 NotRegister 标注 @Profile("unittest")
b. 在具体测试类中标注 @ActiveProfiles("unittest")
2.以上代码还应该进行环境隔离.利用@Conditional(TestEnvirChangCondition.class)指定环境;在TestEnvirChangCondition中实现Condition接口,重写matches方法.conditionContext.getEnvironment().getProperty(keyName,Boolean.class,defaultValue);
2.1建议使用 Spring 原生 Profiles 来区分集成测试环境
a. 在 DataSourceDefinitionProcessor 和 NotRegister 标注 @Profile("unittest")
b. 在具体测试类中标注 @ActiveProfiles("unittest")
补充关于 Spring Bean 生命周期 SPI BeanPostProcessor 接口为什么不是一个很好的统计 Bean 耗时时间,主要原因在于一个 Bean 可能被多个 BeanPostProcessor 处理,所以需确保统计 BeanPostProcessor 的postProcessBeforeInitialization 必须是第一个调用,
同时,确保它的 postProcessAfterInitialization 是最后一个执行,显然这是矛盾的。
Spring 5.3 提供了 StartupStep API,它能够统计部分 Bean 和 IoC 容器耗时,但是还是不完整,估计作者也感受到了难度。其他类似需求相关实现开源代码
核心实现代码:https://github.com/microsphere-projects/microsphere-spring-projects/blob/main/microsphere-spring/microsphere-spring-context/src/main/java/io/github/microsphere/spring/context/event/
Bean 生命周期核心:BeanEventListener.java
Bean 耗时统计:BeanTimeStatistics.java
单元测试:BeanTimeStatisticsTest.java
同时,确保它的 postProcessAfterInitialization 是最后一个执行,显然这是矛盾的。
Spring 5.3 提供了 StartupStep API,它能够统计部分 Bean 和 IoC 容器耗时,但是还是不完整,估计作者也感受到了难度。其他类似需求相关实现开源代码
核心实现代码:https://github.com/microsphere-projects/microsphere-spring-projects/blob/main/microsphere-spring/microsphere-spring-context/src/main/java/io/github/microsphere/spring/context/event/
Bean 生命周期核心:BeanEventListener.java
Bean 耗时统计:BeanTimeStatistics.java
单元测试:BeanTimeStatisticsTest.java
Java基础
数据结构
List
ArrayList
初始化与扩容
ArrayList在创建时,其初始容量为0,即底层是一个空数组,而第一次往其中存入元素的时候,会进行第一次扩容,在这第一个扩容中,扩容为10,而之后的第二次,第三次,第N次扩容,均为前一次容量的1.5倍
痛点
扩容(创建时确定好大小,后面就避免扩容)。内存允许可以放21亿多的数据,调整源码还可以40亿。
数组赋值
查询是否存在是通过遍历
LinkedList
linkedList双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好.
可以代替数组结构,但是没有数组效率高。如果频繁的增删,也不应该用链表(要在链表中删除某个元素,首先得找到它啊,链表的查找耗时)
比ArrayList更占内存(存储前后两个引用)
可以代替数组结构,但是没有数组效率高。如果频繁的增删,也不应该用链表(要在链表中删除某个元素,首先得找到它啊,链表的查找耗时)
比ArrayList更占内存(存储前后两个引用)
线程安全的List:Collections.synchronizedList(List< T> list)
HashMap
初始化与扩容
HashMap在创建时,其初始容量为0,而第一次往其中存入元素的时候,会进行第一次扩容(初始化),在这第一个扩容中,扩容为16,负载因子为0.75.
红黑树阈值为8,树化另一条件是map数组长度为64.须同时满足.解除树形化阈值6.之后扩容 2 * n .
数组容量可以再构造方法中指定.但必须是2的幂次方(涉及位运算)
红黑树阈值为8,树化另一条件是map数组长度为64.须同时满足.解除树形化阈值6.之后扩容 2 * n .
数组容量可以再构造方法中指定.但必须是2的幂次方(涉及位运算)
为什么数组扩容后,链表长度会减半?
因为我们确定桶位置是数组长度与hash进行&操作,长度扩为2倍,桶位置正好会改为2倍
因为我们确定桶位置是数组长度与hash进行&操作,长度扩为2倍,桶位置正好会改为2倍
HashSet
底层是HashMap
优势
查询是否存在不是遍历,高效
ConcurrentHashMap
1.8中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile去修饰,保证了可见性
把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile去修饰,保证了可见性
volatile的特性
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的(实现可见性)
2.禁止进行指令重排序(实现有序性)
3.volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性
HashTable
HashTable的初始值是11,扩容 2 * n + 1。不允许null的Key和Value。直接使用给的初始值。HashTable是线程安全的(方法上都有synchronized),同步机制决定了它无法追求运行速度上的极致。在取余法的时候,使用位运算来提升效率已经意义不大了,更多的精力用在考虑解决hash冲突上。使用质数和奇数的取模运算,可以将Hash冲突的概率降低到最小。基本被淘汰
LinkedHashMap
Key和Value都允许空,
Key重复会覆盖、Value允许重复
有序
线程不安全
按照插入顺序进行访问
Key重复会覆盖、Value允许重复
有序
线程不安全
按照插入顺序进行访问
TreeMap
只允许空的 value,key 不能为空
key不可以重复,value允许重复
有序
线程不安全
传入的key进行了大小排序
key不可以重复,value允许重复
有序
线程不安全
传入的key进行了大小排序
vector
线程安全
低效
低效
Stack
线程安全
面试题
set集合和map集合的区别
同
1.存储的数据都是无序
异
1.Set为单列,Map为双列或者说键值对
2.Map的Key不能重复,value可以重复;Set值不允许重复
String优化
1.在实际开发中,对于需要多次或大量拼接的操作,在不考虑线程安全问题时,我们就应该尽可能使用 StringBuilder 进行 append 操作.
如果提前知道需要拼接 String 的个数,就应该直接使用带参构造器指定 capacity,以减少扩容的次数
如果提前知道需要拼接 String 的个数,就应该直接使用带参构造器指定 capacity,以减少扩容的次数
2.对于程序中大量使用存在的字符串时,尤其存在很多已经重复的字符串时,使用 intern()方法能够节省内存空间
MySQL
索引
EXPLAIN
key
type☆
system,const, eq_ref,ref ,fulltext,ref_or_null,index_merge, unique_subquery, index_subquery,range,index,ALL
结果值从最好到最坏
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge >unique_subquery > index_subquery > range >index > ALL
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。
key_len☆
key_len:实际使用到的索引长度(即:字节数)
key_len越小,索引效果越好 这是前面学到的只是,短一点效率更高
但是在联合索引里面,命中一次key_len加一次长度。越长代表精度越高,效果越好
key_len越小,索引效果越好 这是前面学到的只是,短一点效率更高
但是在联合索引里面,命中一次key_len加一次长度。越长代表精度越高,效果越好
rows☆
rows:预估的需要读取的记录条数
rows 值越小,代表数据越有可能在一个页里面,这样io就会更小。
rows 值越小,代表数据越有可能在一个页里面,这样io就会更小。
filtered
filtered 的值指返回结果的行占需要读到的行(rows 列的值)的百分比。
自己的理解: 比如读了100 rows. filtered 是10% 那么就说明还要对着100条进行过滤。
对于单表查询来说,这个filtered列的值没什么意义,更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即:rows * filtered)
自己的理解: 比如读了100 rows. filtered 是10% 那么就说明还要对着100条进行过滤。
对于单表查询来说,这个filtered列的值没什么意义,更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即:rows * filtered)
extra☆
是用来说明一些额外信息,包含不适合在其他列中显示但十分重要的额外信息。通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询.
Using where
当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息
当条件除了索引,还有其他条件,也会是这个提示
Using index
当查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,Extra列将会提示
Using index condition
有些搜索条件中虽然出现了索引列,但却不能使用到索引(索引条件下推)
Using filesort(重点优化)
有一些情况下对结果集中的记录进行排序是可以使用到索引的,在内存中或者磁盘上进行排序的方式统称为文件排序
优化:从业务层面看是否真的需要排序等操作,不用则去掉
Using temporary(重点优化)
借助临时表来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询.但是建立与维护临时表要付出很大成本
优化:从业务层面看是否真的需要临时表,需要的话则将临时表体量降到最低
索引失效
失效场景
1、使用like后面紧跟着%,如‘%XXX’
2、表中数据量较少时,不会走索引,而是全表查询
3、有类型转换时索引失效(如:字符串不加单引号)
4、where中索引列使用了函数
5、where中索引列有运算
6、is null可以走索引,is not null无法使用索引
7、复合索引没有用到左列字段(左前缀法则)
8、条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
9、索引范围条件右边的列
10、使用不等于(!=、<>;为了充分利用索引,有时候可以将>、<等价转为>=、<=的形式,或者将可能会有<、>的条件的字段尽量放在关联索引靠后位置)
对于单列索引,尽量选择针对当前query过滤性更好的索引
在选择组合索引时,query过滤性最好的字段应该越靠前越好
在选择组合索引时,尽量选择能包含当前query中where子句中更多字段的索引
在选择组合索引时,如果某个字段可能出现范围查询,尽量将它往后放
在选择组合索引时,query过滤性最好的字段应该越靠前越好
在选择组合索引时,尽量选择能包含当前query中where子句中更多字段的索引
在选择组合索引时,如果某个字段可能出现范围查询,尽量将它往后放
设计索引原则
适合创建索引
1.字段的数值有唯一性的限制
2.频繁作为 WHERE 查询条件的字段
3.经常 GROUP BY 和 ORDER BY 的列
4.UPDATE、DELETE 的 WHERE 条件列
5.DISTINCT 字段需要创建索引
6.多表 JOIN 连接操作时,尽量不要超过 3 张,对WHERE 条件创建索引,对用于连接的字段创建索引,并且类型必须一致
7.使用列的类型小的创建索引
8.使用字符串前缀创建索引
9.区分度高(散列性高)的列适合作为索引
10.使用最频繁的列放到联合索引的左侧
11.在多个字段都要创建索引的情况下,联合索引优于单值索引
2.频繁作为 WHERE 查询条件的字段
3.经常 GROUP BY 和 ORDER BY 的列
4.UPDATE、DELETE 的 WHERE 条件列
5.DISTINCT 字段需要创建索引
6.多表 JOIN 连接操作时,尽量不要超过 3 张,对WHERE 条件创建索引,对用于连接的字段创建索引,并且类型必须一致
7.使用列的类型小的创建索引
8.使用字符串前缀创建索引
9.区分度高(散列性高)的列适合作为索引
10.使用最频繁的列放到联合索引的左侧
11.在多个字段都要创建索引的情况下,联合索引优于单值索引
不适合创建索引
1. 在where中使用不到的字段,不要设置索引
2. 数据量小的表最好不要使用索引
3. 有大量重复数据的列上不要建立索引
4.避免对经常更新的表创建过多的索引
5.不建议用无序的值作为索引
6.删除不再使用或者很少使用的索引
7.不要定义冗余或重复的索引
2. 数据量小的表最好不要使用索引
3. 有大量重复数据的列上不要建立索引
4.避免对经常更新的表创建过多的索引
5.不建议用无序的值作为索引
6.删除不再使用或者很少使用的索引
7.不要定义冗余或重复的索引
结构
B+树
组成
段(逻辑概念)
完整的区(1MB)
若干零散的数据页(16KB)
存储空间被划分为七个部分,分别是:
文件头(File Header)[38字节]
页头(Page Header)[56字节]
最大最小记录(Infimum+supremum)[26字节]
用户记录(User Records)[不确定]
空闲空间(Free Space)[不确定]
页目录(Page Directory)[不确定]
文件尾(File Tailer)[8字节]
文件头(File Header)[38字节]
页头(Page Header)[56字节]
最大最小记录(Infimum+supremum)[26字节]
用户记录(User Records)[不确定]
空闲空间(Free Space)[不确定]
页目录(Page Directory)[不确定]
文件尾(File Tailer)[8字节]
注意事项
1.根页面位置万年不动(根节点页号会被记录到某个地方,固定读取)
2.. 内节点中目录项记录的唯一性(会把主键值,添加到二级索引内节点的目录项记录)
3.一个页面最少存储2条记录
面试题
B+树与B树区别,为什么不用B
B+Tree:非叶子节点只存key,大大减少了非叶子节点的大小,那么每个节点就可以存放更多的记录,树更矮了,I/O操作更少了。所以B+Tree拥有更好的性能。
索引种类,结构(覆盖索引,索引下推,回表)
调优
调优(刷盘调优、exists与in、limit深分页优化、io优化:只查询需要的字段,减少数据传输量、索引优化:查询字段全走索引,减少回表查询 )
降低锁粒度、降低锁持有时间(insert与update)
普通索引,联合索引(B+树,数据页,explain)
批量操作,单线程变多线程,分页查询
数据量大:分库分表,冷热分离
内连接,左外连接,右外连接,全连接(union 与union all)
union all效率高于union
三大范式
列的原子性
行的唯一性
非主键值直接依赖主键列,不能间接依赖
分库分表
分库分表标准:
单表数据超过1000W(阿里推荐行数超过500W)
OR
单表数据文件(.ibd)超过20G(阿里推荐单表容量超过2GB)
OR
单表数据文件(.ibd)超过20G(阿里推荐单表容量超过2GB)
单库单表:原始方案
单库多表:有效缩小磁盘扫描范围
多库多表:提供数据库并行处理能力
单库多表:有效缩小磁盘扫描范围
多库多表:提供数据库并行处理能力
面试题
1.怎么分?
2.为什么这么分?
3.是否有必要?
4.有考虑哪些地方?分布式事务,跨库关联,数据库成本
2.为什么这么分?
3.是否有必要?
4.有考虑哪些地方?分布式事务,跨库关联,数据库成本
分库分表问题:
1.分布式事务
2.跨库Join问题(a.程序先查A表,再循环查B表;b.MyCat与Sharding支持两表跨库Join)
3.跨节点分页查询(单节点取n条,在程序中合并运算取Top N,或者走ES)
4.全局主键Id问题(雪花算法)
5.扩容问题(范围分表容易扩容,但存在尾部热点问题;Hash分表极难扩容,建议改为一致性Hash,但迁移难度较大)
2.跨库Join问题(a.程序先查A表,再循环查B表;b.MyCat与Sharding支持两表跨库Join)
3.跨节点分页查询(单节点取n条,在程序中合并运算取Top N,或者走ES)
4.全局主键Id问题(雪花算法)
5.扩容问题(范围分表容易扩容,但存在尾部热点问题;Hash分表极难扩容,建议改为一致性Hash,但迁移难度较大)
分库方案
范围分表(1~10、10~20、20~30)
适合场景
适合日志(只关心最近产生的)
问题
尾部热点、数据偏斜、资源浪费问题
Hash分表(id % 3 =X)
适合场景
总量基本一致,分散相对均匀;适合档案系统(根据档案编号提取档案)
问题
存在范围查询跨库问题
架构
主主(双向同步)
主从
目标
热备份、多活、故障切换、负载均衡、读写分离
问题
主从延迟
从库性能差、从库压力大、从库过多
解决方案
选择主从一样的机器、一主多从、从库3~5为宜
大事务、慢SQL
解决方案
1.减少批量处理
2.优化慢SQL语句
3.实时性要求高的业务强制走主库(或者加上事务注解,也可以强制走主库)
网络延迟
解决方案
升级带宽
低版本MySQL(低版本是单线程复制)
解决方案
换高版本MySQL
主从同步数据不一致(主从数据复制方式)
1.异步复制(客户端commit后不等从库返回就将结果返回客户端)
2.半同步复制(设置应答从库数量)
3.组复制(基于Paxos协议状态机复制,组内大多数同意)
主备(故障切换)
日志
redo log(重做日志、事务持久性)
undo log(回滚日志、事务原子性、保存相反的操作,进行insert会存delete)
bin log(二进制日志、备份数据)
relay log(中继日志)
底层实现
WAL(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化
隔离级别
读未提交、读已提交、可重复读、串行化
问题
脏写、脏读、不可重复读、幻读
MVCC(多版本并发控制)
底层实现
隐藏字段(row_id、trx_id、roll_pointer)
Undo Log
Read View
实现过程
1.获取事务ID
2.获取ReadView
3.查询得到是数据,然后与ReadView事务版本进行比较
4.如果不匹配,就从undo Log中获取历史快照
5.最后返回符合规则的数据
2.获取ReadView
3.查询得到是数据,然后与ReadView事务版本进行比较
4.如果不匹配,就从undo Log中获取历史快照
5.最后返回符合规则的数据
延伸
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。
在可重复读的情况下InnoDB很大程度上避免幻读现象,但并不是完全解决。
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。
在可重复读的情况下InnoDB很大程度上避免幻读现象,但并不是完全解决。
未解决场景
在MVCC情况下,事务A先开启事务,然后普通 select 一个不存在的值,比如id = 3;这时候是查不到的,然后事务b开启事务insert一个id等于3的记录。接着事务A去update id = 3的记录,再去select id = 3,就可以查出那条记录
另外一种场景是事务A先普通select,然后事务B插入一条数据并提交,这时事务A再来个当前读 select for update就也能查出刚才插入的记录
锁
按照对数据操作类型
1.读锁/共享锁(S)
2.写锁/排他锁(X)
锁粒度划分
表级锁
1.表级S锁、X锁
2.意向锁
3.自增锁
4.MDL锁(元数据锁)
行级锁
1.记录锁
2.间隙锁
3.临键锁
临键锁(Next-Key锁) = 记录锁 + 间隙锁,大致可以这样理解
事务的 update 语句中 where 是等值查询,并且 id 是唯一索引,所以只会对 id = 1 这条记录加锁,因此,事务 B 的更新操作并不会阻塞。
但是,在 update 语句的 where 条件没有使用索引,就会全表扫描,于是就会对所有记录加上 next-key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。
事务的 update 语句中 where 是等值查询,并且 id 是唯一索引,所以只会对 id = 1 这条记录加锁,因此,事务 B 的更新操作并不会阻塞。
但是,在 update 语句的 where 条件没有使用索引,就会全表扫描,于是就会对所有记录加上 next-key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。
4.插入意向锁
页级锁
对待锁态度
悲观锁
乐观锁
按照加锁方式
1.隐式锁
2.显式锁
其他
1.全局锁
2.死锁
元数据
指关于数据库对象(如表、列、索引、视图、存储过程等)的信息(名字、数据类型、长度、精度等),这些信息描述了这些对象的结构和属性
面试题
exists与in
如查询的两表大小相当,那么用in和exists效率差别不大。
如两表中一小,一大,则子查询表大的用exists,子查询表小的用in。
not in 和not exists,如查询语句使用了not in 那么内外表都进行全表扫描,无法用到索引;而not exists的子查询依然能用到表上的索引。所以无论哪个表大。用not exists都比not in要快。
如两表中一小,一大,则子查询表大的用exists,子查询表小的用in。
not in 和not exists,如查询语句使用了not in 那么内外表都进行全表扫描,无法用到索引;而not exists的子查询依然能用到表上的索引。所以无论哪个表大。用not exists都比not in要快。
MySQL自增ID用完了该怎么办?
查看ID类型,如是int,把int改为bigint(-2的63次方~2的63次方-1);应该就不会用完了
数据库Hash索引与B+数索引区别
1.Hash索引不能进行范围查询
2.Hash索引不支持联合索引的最左索引
3.Hash索引不支持Order By排序
4.InnoDB不支持Hash索引
Spring
IOC
DI
基于注解的3种常规注入方式
基于属性注入(常用)
@Service
public class UserService {
@Autowired
private Wolf1Bean wolf1Bean;//通过属性注入
}
public class UserService {
@Autowired
private Wolf1Bean wolf1Bean;//通过属性注入
}
基于 setter 方法注入
@Service
public class UserService {
private Wolf3Bean wolf3Bean;
@Autowired //通过setter方法实现注入
public void setWolf3Bean(Wolf3Bean wolf3Bean) {
this.wolf3Bean = wolf3Bean;
}
}
public class UserService {
private Wolf3Bean wolf3Bean;
@Autowired //通过setter方法实现注入
public void setWolf3Bean(Wolf3Bean wolf3Bean) {
this.wolf3Bean = wolf3Bean;
}
}
基于构造器注入
@Service
public class UserService {
private Wolf2Bean wolf2Bean;
@Autowired //通过构造器注入
public UserService(Wolf2Bean wolf2Bean) {
this.wolf2Bean = wolf2Bean;
}
}
public class UserService {
private Wolf2Bean wolf2Bean;
@Autowired //通过构造器注入
public UserService(Wolf2Bean wolf2Bean) {
this.wolf2Bean = wolf2Bean;
}
}
AOP
切面执行顺序
执行顺序
环绕-->before/after-->afterThrow/afterReturn(afterThrow和afterReturn只会执行一个)
无异常
aroudBefore...
before...
add....
aroudAfter...
after...
afterReturn...
before...
add....
aroudAfter...
after...
afterReturn...
有异常无环绕
before...
after...
afterThrow...
after...
afterThrow...
有异常有环绕(与无异常情况执行顺序一致)
aroudBefore...
before...
aroudAfter...
after...
afterReturn...
before...
aroudAfter...
after...
afterReturn...
面试题
SpringBoot启动流程
SpringBoot的starter加载
autowire和resource区别
a是spring提供,先根据类型匹配类型;r是Java提供,先根据bean名称匹配,可解耦
BeanFactory与ApplicationContext
①BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了
③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢
controller和service实在IOC同一个容器中吗?
Spring同一个
SpringBoot同一个
SpringMVC不一定(父子容器)
SpringBoot同一个
SpringMVC不一定(父子容器)
Bean
Bean生命周期
1.Bean容器找到Spring配置文件中Bean的定义;
2.构造方法推断(执行无参构造方法,创建Bean)
3.Bean容器利用java 反射机制实例化Bean;
4.Bean容器为实例化的Bean设置属性值;
5.如果Bean实现了BeanNameAware接口,则执行setBeanName方法;
6.如果Bean实现了BeanClassLoaderAware接口,则执行setBeanClassLoader方法;
7.如果Bean实现了BeanFactoryAware接口,则执行setBeanFactory方法;
8.如果Bean实现了ApplicationContextAware接口,则执行setApplicationContext方法;
9.如果加载了BeanPostProcessor相关实现类,则执行postProcessBeforeInitialization方法;
10.如果Bean定义初始化方法(PostConstruct注解、配置init-method、实现了InitializingBean接口),则执行定义的初始化方法;
11.如果加载了BeanPostProcessor相关实现类,则执行postProcessAfterInitialization方法;
12.当要销毁这个Bean时,如果自定义了销毁方法(PreDestroy注解、配置destroy-method、实现了DisposableBean接口),则执行定义的销毁方法。
2.构造方法推断(执行无参构造方法,创建Bean)
3.Bean容器利用java 反射机制实例化Bean;
4.Bean容器为实例化的Bean设置属性值;
5.如果Bean实现了BeanNameAware接口,则执行setBeanName方法;
6.如果Bean实现了BeanClassLoaderAware接口,则执行setBeanClassLoader方法;
7.如果Bean实现了BeanFactoryAware接口,则执行setBeanFactory方法;
8.如果Bean实现了ApplicationContextAware接口,则执行setApplicationContext方法;
9.如果加载了BeanPostProcessor相关实现类,则执行postProcessBeforeInitialization方法;
10.如果Bean定义初始化方法(PostConstruct注解、配置init-method、实现了InitializingBean接口),则执行定义的初始化方法;
11.如果加载了BeanPostProcessor相关实现类,则执行postProcessAfterInitialization方法;
12.当要销毁这个Bean时,如果自定义了销毁方法(PreDestroy注解、配置destroy-method、实现了DisposableBean接口),则执行定义的销毁方法。
1.创建前准备
2.创建实例
3.依赖注入
4.容器缓存
5.销毁实例
2.创建实例
3.依赖注入
4.容器缓存
5.销毁实例
Bean作用域
拦截器和过滤器执行顺序
监听器 > 过滤器 > 拦截器 > servlet执行 > 拦截器 > 过滤器 > 监听器
循环依赖
通过三级缓存解决
三级:存储半成品Bean,未被引用的对象
二级:存储半成品,被其他Bean引用的Bean
一级:存储完整的Bean对象
二级:存储半成品,被其他Bean引用的Bean
一级:存储完整的Bean对象
过程
过程:
A实例化,未初始化。放入三级缓存中
A属性注入过程中,依赖B。发现B没有
B实例化,未初始化。放入三级缓存。
B属性注入过程中,发现依赖A。会依次从一级到三级找;从三级缓存中取出A注入,并且将A放入二级缓存,删掉三级中的A。
B初始化完成执行完其他流程得到一个完整的Bean,并且将B直接放入一级缓存中。
A继续完成B的属性注入,执行完其他流程形成一个完整的Bean,放入一级缓存
依靠引入"中间态(已经实例化还没初始化的半成品)"的概念
A实例化,未初始化。放入三级缓存中
A属性注入过程中,依赖B。发现B没有
B实例化,未初始化。放入三级缓存。
B属性注入过程中,发现依赖A。会依次从一级到三级找;从三级缓存中取出A注入,并且将A放入二级缓存,删掉三级中的A。
B初始化完成执行完其他流程得到一个完整的Bean,并且将B直接放入一级缓存中。
A继续完成B的属性注入,执行完其他流程形成一个完整的Bean,放入一级缓存
依靠引入"中间态(已经实例化还没初始化的半成品)"的概念
异常
1.scope=prototype 类型的循环依赖(原型多例导致)
2.无法解决构造函数注入(添加@Lazy解决)
3.被 @Async 增强的 Bean 的循环依赖(普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,其实现了 SmartInstantiationAwareBeanPostProcessor。而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,其没有实现 )
1.scope=prototype 类型的循环依赖(原型多例导致)
2.无法解决构造函数注入(添加@Lazy解决)
3.被 @Async 增强的 Bean 的循环依赖(普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,其实现了 SmartInstantiationAwareBeanPostProcessor。而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,其没有实现 )
应用到设计模式
工厂、模板、代理、单例
注解
启动注解
@SpringBootApplication(标记当前类为引导启动类(加载很多启动配置))
和三个子注解:
@SpringBootConfiguration(包含configuration注解,表示当前类为配置类)
@EnableAutoConfiguration
@ComponentScan(扫描文件同级包以及子包中的Bean)
和三个子注解:
@SpringBootConfiguration(包含configuration注解,表示当前类为配置类)
@EnableAutoConfiguration
@ComponentScan(扫描文件同级包以及子包中的Bean)
@EnableAutoConfiguration:
@AutoConfigurationPackage --> 作用是将主配置类所在的包下面所有的组件都扫描到Spring容器中。
@Import(EnableAutoConfigurationImportSelector.class) -->自动配置:通过源码查看其会加载源码中的META-INF/spring.facotries文件,这个文件中包含了很多配置类。通过加载这个文件里面的类信息,
然后这些类会去自动加载配置。
@AutoConfigurationPackage --> 作用是将主配置类所在的包下面所有的组件都扫描到Spring容器中。
@Import(EnableAutoConfigurationImportSelector.class) -->自动配置:通过源码查看其会加载源码中的META-INF/spring.facotries文件,这个文件中包含了很多配置类。通过加载这个文件里面的类信息,
然后这些类会去自动加载配置。
缓存注解
@Cacheable
Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法
keyGenerator:key 生成器。 key 和 keyGenerator 二选一使用
keyGenerator:key 生成器。 key 和 keyGenerator 二选一使用
@CachePut
与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CacheEvict
标注在需要清除缓存元素的方法或类上。当标记在一个类上时表示其中所有的方法执行都会触发缓存的清除操作;allEntries是否需要清除缓存中的所有元素。当指定allEntries为true时,Spring Cache将忽略指定的key。需要清除所有的元素,这比单独清除元素更高效
@Caching
可让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict
常用注解
引用类
@Autowired
@Inject
@Reference
@Resource
@Inject
@Reference
@Resource
声明类
@Bean
@Component
@Qualifier
@Primary
@Controller
@RestController
@Service
@Repository
@Component
@Qualifier
@Primary
@Controller
@RestController
@Service
@Repository
功能参数类
@RequestMaping
@RequstParam
@RequestBody
@PathVariable
@RequstParam
@RequestBody
@PathVariable
配置类
@Transactional
@Configuration
@Aspect
@Scope(作用域,如:单例,原型,请求,会话等)
@ScanScope
@Value
@Configuration
@Aspect
@Scope(作用域,如:单例,原型,请求,会话等)
@ScanScope
@Value
事务
事务失效
失效
1.访问权限问题(需要public)
2.方法用final、static修饰(AOP无法重写)
3.方法内部调用(走this.绕过了代理对象,而不是走的重写方法,可以新写个service或自己注入自己,或使用AopContext)
4.未被spring管理(如忘了加@Service注解)
5.多线程调用(不同数据库连接)
6.表不支持事务(执行引擎是MyISAM)
7.未开启事务
2.方法用final、static修饰(AOP无法重写)
3.方法内部调用(走this.绕过了代理对象,而不是走的重写方法,可以新写个service或自己注入自己,或使用AopContext)
4.未被spring管理(如忘了加@Service注解)
5.多线程调用(不同数据库连接)
6.表不支持事务(执行引擎是MyISAM)
7.未开启事务
事务不回滚
1.错误的传播特性
2.自己吞了异常(手动try...catch)
3.手动抛了别的异常(默认只回滚RuntimeException(运行时异常)和Error(错误))
4.自定义了回滚异常
5.嵌套事务回滚多了
2.自己吞了异常(手动try...catch)
3.手动抛了别的异常(默认只回滚RuntimeException(运行时异常)和Error(错误))
4.自定义了回滚异常
5.嵌套事务回滚多了
事务传播特性
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(默认)。[前后调用都坐在同一辆车上,要炸全都完了]
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。[前后调用坐两辆车,前车炸不影响]
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。[别人有作业就抄,没有就算了]
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。[不写作业,如果别人写了,就给他锁起来]
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。[别人有作业就抄,没有就举报给老师]
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。[不写作业,如果别人写了,就举报给老师]
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。[写了部分作业,当能抄的时候做个标记,如果别人的作业有问题,退回到标记点]
区别
NESTED和REQUIRED_NEW的区别:
REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED情况下,父事务回滚时,子事务也会回滚,而REQUIRED_NEW情况下,原有事务回滚,不会影响新开启的事务
REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED情况下,父事务回滚时,子事务也会回滚,而REQUIRED_NEW情况下,原有事务回滚,不会影响新开启的事务
NESTED和REQUIRED的区别:
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚
事务隔离级别
ISOLATION_DEFAULT(默认同数据库):默认值,表示使用底层数据库的默认隔离级别。大部分数据库通常是ISOLATION_READ_COMMITTED
ISOLATION_READ_UNCOMMITTED(读未提交):该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别
ISOLATION_READ_COMMITTED(读已提交):该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
ISOLATION_REPEATABLE_READ(可重复读):该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读
ISOLATION_SERIALIZABLE(串行化):所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
长事务
声明式事务
@Transactional
编程式事务
TransactionTemplate的execute方法
transactionTemplate.execute((status) => {
doSameThing...
return Boolean.TRUE;
})
doSameThing...
return Boolean.TRUE;
})
优点
1.避免由于Spring aop问题,导致事务失效的问题
2.能够更小粒度的控制事务的范围,更直观
分布式事务
CAP原则
含义
1.一致性(Consistency):每次读操作都能保证返回的是最新数据;
2.可用性(Availablity):任何一个没有发生故障的节点,会在合理的时间内返回一个正常的结果;
3.分区容忍性(Partition-torlerance):当节点间出现网络分区,照样可以提供服务。
2.可用性(Availablity):任何一个没有发生故障的节点,会在合理的时间内返回一个正常的结果;
3.分区容忍性(Partition-torlerance):当节点间出现网络分区,照样可以提供服务。
实践
CP是强一致性,AP是可用性。
zk是要求强一致性,也就是主从机器数据需要一致,如果主机崩了,此时就需要停下来进行选举,选举时间需要至少30s以上,在这期间系统是不可用的,服务不能注册与查询。这对于大中厂来说是不可接受的
相比之下,ap就没有主从之分,一台机器可以复制另外一台机器的数据,即使是其中一台机器宕机,其他也可以继续提供服务
zk是要求强一致性,也就是主从机器数据需要一致,如果主机崩了,此时就需要停下来进行选举,选举时间需要至少30s以上,在这期间系统是不可用的,服务不能注册与查询。这对于大中厂来说是不可接受的
相比之下,ap就没有主从之分,一台机器可以复制另外一台机器的数据,即使是其中一台机器宕机,其他也可以继续提供服务
延伸出
BASE理论
即使无法做到强一致性,但是可以采用适当的采取弱一致性,即最终一致性
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
Raft算法
节点状态
1.随从(启动都是随从)
2.候选者(如果节点没有监听到领导发消息,则就会变成候选者)
3.领导(候选者会给其他所有节点发送投票,当该节点收到大多数投票,则会成为领导."领导选举过程"->系统中所有改变都需要通过领导)
2.候选者(如果节点没有监听到领导发消息,则就会变成候选者)
3.领导(候选者会给其他所有节点发送投票,当该节点收到大多数投票,则会成为领导."领导选举过程"->系统中所有改变都需要通过领导)
领导选举
选举超时时间(控制选举过程)
1.选举超时(150~300ms)是随从想要成为候选者中间的时间(节点自旋时间)
2.在自旋时间过后,还没有领导发命令,可能是没领导.就可以变成候选者,让其他节点选自己当领导
3.自旋时间结束,成为候选人.发起一轮新选举.先投自己1票.
4.然后给别人发投票请求,让别人投票
5.如果接受请求的节点还没有投票.则可以投给发起者(投票后会重置自旋时间)
6.当发起者成为领导后,会给随从发送追加日志的消息
2.在自旋时间过后,还没有领导发命令,可能是没领导.就可以变成候选者,让其他节点选自己当领导
3.自旋时间结束,成为候选人.发起一轮新选举.先投自己1票.
4.然后给别人发投票请求,让别人投票
5.如果接受请求的节点还没有投票.则可以投给发起者(投票后会重置自旋时间)
6.当发起者成为领导后,会给随从发送追加日志的消息
心跳超时时间
1.追加日志消息会以指定间隔时间发送出去(维护心跳连接)
2.其他节点收到消息以后,又会重置自旋时间
3.这一轮选举会一直维持到有一个随从停止接收心跳,成为候选者(如领导宕机)
4.如果两个节点都成为候选者,将会出现投票分离的情况
5.进行多次选举,直到选出领导
2.其他节点收到消息以后,又会重置自旋时间
3.这一轮选举会一直维持到有一个随从停止接收心跳,成为候选者(如领导宕机)
4.如果两个节点都成为候选者,将会出现投票分离的情况
5.进行多次选举,直到选出领导
日志复制
案例
客户端想让分布式系统中SET 5
1.首先客户端给领导发送SET 5的命令,领导收到后不提交
2.每一个改变的命令都会被添加成"节点日志".并在下一个心跳时发送追加日志给其他所有节点.
3.复制日志到其他节点,当其他节点收到并写好后会回复收到日志
4.当领导收到大多数节点回复后,就会提交SET 5,并在下一个心跳时间响应给客户端,接着下一个心跳时间再通知其他节点提交
5.这样系统状态达到一致性
1.首先客户端给领导发送SET 5的命令,领导收到后不提交
2.每一个改变的命令都会被添加成"节点日志".并在下一个心跳时发送追加日志给其他所有节点.
3.复制日志到其他节点,当其他节点收到并写好后会回复收到日志
4.当领导收到大多数节点回复后,就会提交SET 5,并在下一个心跳时间响应给客户端,接着下一个心跳时间再通知其他节点提交
5.这样系统状态达到一致性
Raft在面临网络分区的时候保持一致性
1.如ABC、DE,分别处于两个机房.并有网络连接
2.在两机房出现网络故障后,会分别选领导.
3.当客户端分别要改变两个机房数据时,老领导所在的机房可能会出现没办法超过大多数的情况,所以一直保存不成功.
4.另一个机房,新选出的领导在选举过后如果超过大多数,则可以保存更改请求
5.在网络恢复以后没超过半数的机房,会回退所有未提交的数据.复制多数领导的日志
1.如ABC、DE,分别处于两个机房.并有网络连接
2.在两机房出现网络故障后,会分别选领导.
3.当客户端分别要改变两个机房数据时,老领导所在的机房可能会出现没办法超过大多数的情况,所以一直保存不成功.
4.另一个机房,新选出的领导在选举过后如果超过大多数,则可以保存更改请求
5.在网络恢复以后没超过半数的机房,会回退所有未提交的数据.复制多数领导的日志
具体解决方案
本地消息表
尽量让本地事务去保证,然后利用手动任务+定时任务;后续再通过本地消息表进行补偿性操作;达到最终一致的效果
1.①insert本地消息表;②定时任务OR人工;③调远程commit(成功后delete消息)->性能与可靠性高,弊端为实时性
2[乐观锁思想].①先去远程调commit;②失败后insert本地消息表(需要注意远程调之前还是之后,调之前开启事务,基本上没办法去做回滚)
3[悲观锁思想].①事务开启之前先insert一行数据;②调用远程;③如果远程调用成功,删除之前那行记录
外网或不可靠网络建议:3
2[乐观锁思想].①先去远程调commit;②失败后insert本地消息表(需要注意远程调之前还是之后,调之前开启事务,基本上没办法去做回滚)
3[悲观锁思想].①事务开启之前先insert一行数据;②调用远程;③如果远程调用成功,删除之前那行记录
外网或不可靠网络建议:3
业界主流分布式思维解决方案,都不保证它不重新投递
第二种解决方案在投递消息没有成功,未写入信息.机器重启或宕机时,消息丢失的情况
第三张可靠性更高,但是弊端是事务会更大(本地事务,中间嵌套了远程调用,事务会更大,对数据库资源消耗也会更大,不太适合高并发)
第三张可靠性更高,但是弊端是事务会更大(本地事务,中间嵌套了远程调用,事务会更大,对数据库资源消耗也会更大,不太适合高并发)
2PC(XA)、3PC
1.XA协议较简单,数据库原生支持,使用成本低
2.XA协议性能不理想
2.XA协议性能不理想
实现
TCC事务补偿型(柔性事务)
实现
Seata
使用
@GlobalTransactional
角色
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚
维护全局和分支事务的状态,驱动全局事务提交或回滚
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务
定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
写好Try、Confirm、Cancel相关逻辑,框架会在适当的时候调用:
一阶段prepare:调用Try逻辑;
二阶段commit:调用Confirm逻辑;
二阶段rollback:调用Cancel逻辑;
一阶段prepare:调用Try逻辑;
二阶段commit:调用Confirm逻辑;
二阶段rollback:调用Cancel逻辑;
最大努力通知型(柔性事务、可大并发)
可靠消息+最终一致性(柔性事务、可大并发)
问题
幂等性
同一个请求请求了多次,但结果要保证与只请求一次的结果相同
悬挂
补偿(反)操作已完成,业务逻辑(正)请求到了(此时应该忽略)
空补偿
补偿(反)操作比业务逻辑(正)早到
Redis
数据类型
string (字符串)
list (列表)
hash (字典)
set (集合) -> 用户标签,生成随机数抽奖
zset (有序集合) ->排行榜,社交需求,比如用户点赞
list (列表)
hash (字典)
set (集合) -> 用户标签,生成随机数抽奖
zset (有序集合) ->排行榜,社交需求,比如用户点赞
面试题
项目中如何用,用过哪些数据类型
主从同步
单线程为什么快
1.完全基于内存操作,读写效率高(持久化是fork子进程与Linux系统,页缓存技术)
2.单线程操作,避免了频繁的上下文切换.频繁切换会影响系统性能
3.合理高效的数据结构
4.采用了非阻塞IO多路复用机制.采用epoll模型
缓存雪崩,缓存穿透,缓存击穿
雪崩:缓存Key同一时间大量失效,大量请求直接达到数据库,DB瘫痪(1.随机失效时间;2.平均分布在集群不同节点;3.不设置失效时间;4.跑定时任务,定时刷新)
穿透:利用缓存与DB都没有的数据,如id=-1,直接穿透缓存打到数据库,一般属于恶意攻击(1.参数合法性校验;2.无论查出什么都缓存;3.使用布隆过滤器;4.拉黑相关IP;)
击穿:热点Key突然失效,大量该Key请求打到数据库(1.分布式锁,单体用互斥锁;2.永不失效)
穿透:利用缓存与DB都没有的数据,如id=-1,直接穿透缓存打到数据库,一般属于恶意攻击(1.参数合法性校验;2.无论查出什么都缓存;3.使用布隆过滤器;4.拉黑相关IP;)
击穿:热点Key突然失效,大量该Key请求打到数据库(1.分布式锁,单体用互斥锁;2.永不失效)
缓存预热,缓存降级
部署方式
单机
高可用
主从复制
哨兵模式
集群模式
Redis Cluster采用的是类一致性哈希算法实现节点选择的
Cluster会分成了16384(2的14次方) 个Slot(槽位),哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中,具体执行过程分为两大步。
1.根据键值对的 key,按照 CRC16 算法计算一个 16 bit 的值。
2.再用 16bit 值对 16384(2的14次方) 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
每个Redis节点负责处理一部分槽位
Cluster会分成了16384(2的14次方) 个Slot(槽位),哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中,具体执行过程分为两大步。
1.根据键值对的 key,按照 CRC16 算法计算一个 16 bit 的值。
2.再用 16bit 值对 16384(2的14次方) 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
每个Redis节点负责处理一部分槽位
分布式锁
1.setnx + expire(分开执行)
2.setnx + value值是过期时间
3.set的扩展命令(set ex px nx)
4.set ex px nx + 校验唯一随机值,再删除(避免误删)
5.Redisson
6.Redisson + RedLock
淘汰策略
1. volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失
2. volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰
3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key
4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合
5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰
6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略
策略选择
在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的
如果所有数据访问概率大致相等时,可以选择allkeys-random
如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略
如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的
由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了
如果所有数据访问概率大致相等时,可以选择allkeys-random
如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略
如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的
由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了
写后日志
1.由于Redis在写入日志之前,不对命令进行语法检查,所以只记录执行成功的命令,避免出现记录错误命令的情况
2.而且在命令执行后再写日志不会阻塞当前的写操作。
持久化
AOF
AOF 日志(Append Only File,文件追加方式):记录所有的操作命令,并以文本的形式追加到文件中。逻辑
RDB
RDB 快照(Redis DataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘。物理
混合
混合持久化方式:Redis 4.0 新增了混合持久化的方式,集成了 RDB 和 AOF 的优点。
在重写AOF文件的时候生成快照RDB,把当前数据以RDB的方式进行存储,替代原本AOF文件。新增加的指令还是使用AOF追加到文件后面。
在重写AOF文件的时候生成快照RDB,把当前数据以RDB的方式进行存储,替代原本AOF文件。新增加的指令还是使用AOF追加到文件后面。
双写一致性
含义
什么是双写?
同一份数据,需要写数据库、写缓存。
双写很难保证强一致性,可以保证最终一致性。
要做到强一致性就需要将所有读写请求用队列串行化,但是性能非常差,降低系统的QPS。
没有完美的方案,用到缓存就会存在不一致的情况,需要根据具体业务权衡得失,选择合适业务的方案。
同一份数据,需要写数据库、写缓存。
双写很难保证强一致性,可以保证最终一致性。
要做到强一致性就需要将所有读写请求用队列串行化,但是性能非常差,降低系统的QPS。
没有完美的方案,用到缓存就会存在不一致的情况,需要根据具体业务权衡得失,选择合适业务的方案。
解决方案
先更新数据库,再删除缓存(推荐方案)
此方案数据不一致的几率比较低,并且实现简单。
造成数据不一致的情况:在缓存刚好失效时,有线程查询数据库得到旧值,另外一个线程更新数据库并删除缓存后,前面持有旧值的线程将数据存入缓存,造成数据不一致。
造成数据不一致的情况:在缓存刚好失效时,有线程查询数据库得到旧值,另外一个线程更新数据库并删除缓存后,前面持有旧值的线程将数据存入缓存,造成数据不一致。
先删除缓存,再更新数据库
造成数据不一致的情况:删除缓存后,有线程将旧的数据重写回缓存,造成数据不一致。
先更新数据库,再更新缓存
造成数据不一致的情况:多个线程更新缓存时,由于网络问题导致更新顺序错乱,造成数据不一致。
延时双删
先删除缓存,再更新数据库,休眠一段时间,再删除缓存。
在高并发场景会影响性能,也极大降低了数据不一致的可能性。
在高并发场景会影响性能,也极大降低了数据不一致的可能性。
异步更新
使用Canal中间件订阅数据库的binlog,来对缓存更新。
造成数据不一致的情况:消费binlog也有一定时间的延迟,还是可能出现数据不一致。
造成数据不一致的情况:消费binlog也有一定时间的延迟,还是可能出现数据不一致。
写缓存失败的情况
上述方案都可能出现更新或删除缓存失败的情况,可以另起线程重试,或加入消息队列重试。
消息队列
应用场景
解耦
削峰
异步
如何不重复消费
幂等性
生产者
生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可
消费者
状态判断法
消费者消费数据后把消费数据记录在 redis 中,下次消费时先到 redis 中查看是否存在该消息,存在则表示消息已经消费过,直接丢弃消息。
业务判断法
通常数据消费后都需要插入到数据库中,使用数据库的唯一性约束防止重复消费。每次消费直接尝试插入数据,如果提示唯一性字段重复,则直接丢失消息。一般都是通过这个业务判断的方法就可以简单高效地避免消息的重复处理了。
如何不丢失消息
生产者
Confirm机制
一旦消息投递到队列,队列则会向生产者发送一个通知,如果设置了消息持久化到磁盘,则会等待消息持久化到磁盘之后再发送通知。生产者在发送完消息后不会等待回应,所以confirm机制性能相对比事务机制高。还会伴有超时机制。
队列
消息持久化
RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外死掉,消息就会丢失。
要想做到消息持久化,必须满足以下三个条件,缺一不可。
要想做到消息持久化,必须满足以下三个条件,缺一不可。
Exchange 设置持久化
Queue 设置持久化
Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
消费者
ACK确认机制
消费端消费完成后通知服务端,服务端才把消息删除。不自动,改手动
消息积压问题
消息有序性
RabbitMQ
保证同一组消息发送到同一队列,消费者固定消费一个队列
RabbitMQ同一个队列中是有序的(根据消息发送的顺序)只要保证是同一个消费者,消费就没问题,接收到的顺序也是按照发送的顺序
RabbitMQ同一个队列中是有序的(根据消息发送的顺序)只要保证是同一个消费者,消费就没问题,接收到的顺序也是按照发送的顺序
kafka
保证严格的有序性:只能使用一个分区来接收消息,类似于MQ中的队列。
也使用局部有序的方式,因为kafka主要是使用多线程进行消费。
在线程消费之前,创建几个本地队列缓存,将需要按照顺序处理的消息放在同一个队列里,再由线程消费。(比如同一个订单号的消息)
也使用局部有序的方式,因为kafka主要是使用多线程进行消费。
在线程消费之前,创建几个本地队列缓存,将需要按照顺序处理的消息放在同一个队列里,再由线程消费。(比如同一个订单号的消息)
rocketMQ
同一个订单的 binlog 进入到同一个 MessageQueue 中就可以了。
因为同一个 MessageQueue 内的消息是一定有序的,
一个 MessageQueue 中的消息只能交给一个消费者
来进行处理,所以消费者消费的时候就一定会是有序的。(和RabbitMQ差不多)
因为同一个 MessageQueue 内的消息是一定有序的,
一个 MessageQueue 中的消息只能交给一个消费者
来进行处理,所以消费者消费的时候就一定会是有序的。(和RabbitMQ差不多)
其他
无中间件的情况下可以使用数据库对消息进行存储,然后利用时间或者其他id等特性排序,然后再依次处理
多线程
锁
Synchronized
与Reentrantlock
与Reentrantlock
实现
Sync的实现就是基于monitor 管程,monitor 对象中有owner,WaitSet,EntryList。线程会进入monitor 对象中,查看owner 是否有值,如果没有,则会写入自己的线程id。如果有,则证明已经有线程拿到了锁。就会去EntryList中等待,进入阻塞状态。持有锁的线程如果调用了wait 方法,则会进入WaitSet中,进入等待状态。等出了WaitSet,又会进入EntryList中阻塞
区别
同
Sync和Reentrantlock都是可重入锁
异
Reentrantlock可中断
Reentrantlock可锁超时
Reentrantlock可公平
Reentrantlock可多条件变量(避免虚假唤醒)
volatile
内存屏障
读屏障:
保证在该屏障之后,对共享变量读取,加载的是主存中最新数据
保证在该屏障之后,对共享变量读取,加载的是主存中最新数据
写屏障:
保证了在该屏障之前,对共享变量的改动都同步到主存中.不修改顺序
保证了在该屏障之前,对共享变量的改动都同步到主存中.不修改顺序
保证变量的内存可见性
禁止指令重排序
线程池
参数
核心线程数
任务队列
最大线程数
拒绝策略
AbortPolicy:丢弃任务并抛异常
DiscardPolicy:丢弃任务,但是不抛异常
DiscardOldestPolicy:丢弃队列最老的任务,然后把当前任务加入队列
CallerRunsPolicy:由调用线程处理该任务
线程工厂
超时时间
超时单位
痛点
1.设置核心参数不知道多少合适
2.上线后改配置,重启麻烦,不能动态调整
3.不能实时监控,类似黑盒
JMM
JMM(Java内存模型)
分为主内存和工作内存,有时候JIT优化代码,以后就没办法保证可见性了.
所以可以通过volatile修饰成员变量和静态成员变量.使变量更改时会通过总线嗅探技术,让线程可感知.重新无主内存获取值.
因为线程在运行时会将变量值拷贝一份到工作内存.更改发生在主内存,线程不可感知,所以工作内存的变量依然是旧值.
不过synchronized也可以,只是实现相对更重.
在Java内存模型中,synchronized规定:线程在加锁时, 先清空工作内存→在主内存中拷贝最新变量的副本到工作内存 →执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
所以可以通过volatile修饰成员变量和静态成员变量.使变量更改时会通过总线嗅探技术,让线程可感知.重新无主内存获取值.
因为线程在运行时会将变量值拷贝一份到工作内存.更改发生在主内存,线程不可感知,所以工作内存的变量依然是旧值.
不过synchronized也可以,只是实现相对更重.
在Java内存模型中,synchronized规定:线程在加锁时, 先清空工作内存→在主内存中拷贝最新变量的副本到工作内存 →执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
threadlocal
ES
简单使用
1.获取client;
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//1,构建QueryBuilder请求对象
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");
//2,构建SearchRequest请求对象,指定索引库
SearchRequest searchRequest = new SearchRequest("huizi");
//3,构建SearchSourceBuilder查询对象
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//4,构建QueryBuilder对象指定查询方式和查询条件
//5,将QuseryBuilder对象设置到SearchSourceBuilder对象中
sourceBuilder.query(queryBuilder);
//字段过滤
sourceBuilder.fetchSource(new String[]{"title","price","band","category","id"},new String[]{"images"});
//排序
sourceBuilder.sort("price", SortOrder.DESC);
//分页
sourceBuilder.from(0);
sourceBuilder.size(2);
//5,将SearchSourceBuilder设置到SearchRequest中
searchRequest.source(sourceBuilder);
try {
//6,调用方法查询数据
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//7,解析返回结果
SearchHit[] hits = searchResponse.getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//1,构建QueryBuilder请求对象
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");
//2,构建SearchRequest请求对象,指定索引库
SearchRequest searchRequest = new SearchRequest("huizi");
//3,构建SearchSourceBuilder查询对象
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//4,构建QueryBuilder对象指定查询方式和查询条件
//5,将QuseryBuilder对象设置到SearchSourceBuilder对象中
sourceBuilder.query(queryBuilder);
//字段过滤
sourceBuilder.fetchSource(new String[]{"title","price","band","category","id"},new String[]{"images"});
//排序
sourceBuilder.sort("price", SortOrder.DESC);
//分页
sourceBuilder.from(0);
sourceBuilder.size(2);
//5,将SearchSourceBuilder设置到SearchRequest中
searchRequest.source(sourceBuilder);
try {
//6,调用方法查询数据
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//7,解析返回结果
SearchHit[] hits = searchResponse.getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
1.获取client;
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//多索引查询
SearchRequest searchRequest = new SearchRequest(new String[]{"huizi","huizi2"});
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机");
matchQueryBuilder.minimumShouldMatch("80%");
sourceBuilder.query(matchQueryBuilder);
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//7,解析返回结果
SearchHit[] hits = searchResponse.getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//多索引查询
SearchRequest searchRequest = new SearchRequest(new String[]{"huizi","huizi2"});
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机");
matchQueryBuilder.minimumShouldMatch("80%");
sourceBuilder.query(matchQueryBuilder);
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//7,解析返回结果
SearchHit[] hits = searchResponse.getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
1.获取client;
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//批量查询
MultiSearchRequest request = new MultiSearchRequest();
SearchRequest firstSearchRequest = new SearchRequest(new String[]{"huizi","huizi2"});
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("title", "小米"));
firstSearchRequest.source(searchSourceBuilder);
request.add(firstSearchRequest);
SearchRequest secondSearchRequest = new SearchRequest(new String[]{"huizi","huizi2"});
searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("band", "大米"));
secondSearchRequest.source(searchSourceBuilder);
request.add(secondSearchRequest);
try {
MultiSearchResponse multiSearchResponse = client.msearch(request, RequestOptions.DEFAULT);
MultiSearchResponse.Item[] responses = multiSearchResponse.getResponses();
for (MultiSearchResponse.Item respons : responses) {
SearchHit[] hits = respons.getResponse().getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
}
} catch (IOException e) {
e.printStackTrace();
}
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//批量查询
MultiSearchRequest request = new MultiSearchRequest();
SearchRequest firstSearchRequest = new SearchRequest(new String[]{"huizi","huizi2"});
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("title", "小米"));
firstSearchRequest.source(searchSourceBuilder);
request.add(firstSearchRequest);
SearchRequest secondSearchRequest = new SearchRequest(new String[]{"huizi","huizi2"});
searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("band", "大米"));
secondSearchRequest.source(searchSourceBuilder);
request.add(secondSearchRequest);
try {
MultiSearchResponse multiSearchResponse = client.msearch(request, RequestOptions.DEFAULT);
MultiSearchResponse.Item[] responses = multiSearchResponse.getResponses();
for (MultiSearchResponse.Item respons : responses) {
SearchHit[] hits = respons.getResponse().getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
}
} catch (IOException e) {
e.printStackTrace();
}
1.获取client;
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//布尔组合
SearchRequest searchRequest = new SearchRequest("huizi");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//名字-小米
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "小米").minimumShouldMatch("80%");
//品牌-小米
MatchQueryBuilder matchQueryBuilder2 = QueryBuilders.matchQuery("band", "小米").operator(Operator.AND);
//分类-化妆品
MatchQueryBuilder matchQueryBuilder3 = QueryBuilders.matchQuery("category", "化妆品");
//图片路径- jjj模糊
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("title", "小米");
//价格 必须 2699
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("price", "2699");
//must必须
boolQueryBuilder.must(matchQueryBuilder);
//must必须
boolQueryBuilder.must(termQueryBuilder);
//mustNot不能
// boolQueryBuilder.mustNot(matchQueryBuilder2);
//should可以
boolQueryBuilder.should(matchQueryBuilder3);
//结果中过滤
// boolQueryBuilder.filter(fuzzyQueryBuilder);
sourceBuilder.query(boolQueryBuilder);
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = searchResponse.getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)
//布尔组合
SearchRequest searchRequest = new SearchRequest("huizi");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//名字-小米
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "小米").minimumShouldMatch("80%");
//品牌-小米
MatchQueryBuilder matchQueryBuilder2 = QueryBuilders.matchQuery("band", "小米").operator(Operator.AND);
//分类-化妆品
MatchQueryBuilder matchQueryBuilder3 = QueryBuilders.matchQuery("category", "化妆品");
//图片路径- jjj模糊
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("title", "小米");
//价格 必须 2699
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("price", "2699");
//must必须
boolQueryBuilder.must(matchQueryBuilder);
//must必须
boolQueryBuilder.must(termQueryBuilder);
//mustNot不能
// boolQueryBuilder.mustNot(matchQueryBuilder2);
//should可以
boolQueryBuilder.should(matchQueryBuilder3);
//结果中过滤
// boolQueryBuilder.filter(fuzzyQueryBuilder);
sourceBuilder.query(boolQueryBuilder);
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = searchResponse.getHits().getHits();
for (int i = 0; i < hits.length; i++) {
System.out.println("返回的结果: " + hits[i].getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
(1)统计某个字段的数量
ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");
(2)去重统计某个字段的数量(有少量误差)
CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");
(3)聚合过滤
FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));
(4)按某个字段分组,相当于sql中的group by
TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");
(5)求和
SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");
(6)求平均
AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");
(7)求最大值
MaxBuilder mb= AggregationBuilders.max("max_price").field("price");
(8)求最小值
MinBuilder min= AggregationBuilders.min("min_price").field("price");
(9)按日期间隔分组
DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");
(10)获取聚合里面的结果
TopHitsBuilder thb= AggregationBuilders.topHits("top_result");
(11)嵌套的聚合
NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");
(12)反转嵌套
AggregationBuilders.reverseNested("res_negsted").path("kps ");
(13)拼接
.script 在后面拼接参数
searchSourceBuilder.aggregation(aggregationBuilder);
request.source(searchSourceBuilder);
ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");
(2)去重统计某个字段的数量(有少量误差)
CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");
(3)聚合过滤
FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));
(4)按某个字段分组,相当于sql中的group by
TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");
(5)求和
SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");
(6)求平均
AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");
(7)求最大值
MaxBuilder mb= AggregationBuilders.max("max_price").field("price");
(8)求最小值
MinBuilder min= AggregationBuilders.min("min_price").field("price");
(9)按日期间隔分组
DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");
(10)获取聚合里面的结果
TopHitsBuilder thb= AggregationBuilders.topHits("top_result");
(11)嵌套的聚合
NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");
(12)反转嵌套
AggregationBuilders.reverseNested("res_negsted").path("kps ");
(13)拼接
.script 在后面拼接参数
searchSourceBuilder.aggregation(aggregationBuilder);
request.source(searchSourceBuilder);
底层结构
倒排索引
通过分词策略,形成了词和文章的映射关系表,也称倒排表,这种词典 + 映射表即为倒排索引。有了倒排索引,就能实现 O(1) 时间复杂度的效率
在进行倒排索引的时候和该字段的type有关系
字段
query
代表查询,搜索 类似于SQL的select关键字
叶子查询
match
全局查询,如果是多个词语,会进行分词查询。字母字母默认转换成全小写,进行匹配。
match_phrase
查询短语,会对短语进行分词,match_phrase的分词结果必须在text字段分词中都包含,而且顺序必须相同,而且必须都是连续的
term
单个词语查询,会精确匹配词语,会根据输入字段精确匹配。
terms
多个词语查询,精确匹配,满足多个词语中的任何一个都会返回
exists
类似于SQL的ISNULL,字段不为空的会返回出来。
range
类似于SQL的between and关键字,返回查询
ids
一次查询多个id,批量返回。
fuzzy
模糊查询
复合查询
must
返回的文档必须满足must子句的条件,并且参与计算分值.
must_ not
返回的文档必须不满足must_not定义的条件
should
返回的文档可能满足should子句的条件。在一个Bool查询中,如果没有must或者filter,有一个或者多个should子句,那么只要满足一个就可以返回。minimum_should_match参数定义了至少满足几个子句
filter
返回的文档必须满足filter子句的条件。不会参与计算分值,如果一个查询既有filter又有should,那么至少包含一个should子句,Filter过滤的结果会进行缓存,查询效率更高,建议使用filter。
aggs
代表聚合,类似于SQL的group by 关键字,对查询出来的数据进行聚合 求平均值最大值等
highlight
对搜索出来的结果中的指定字段进行高亮显示
sort
指定字段对查询结果进行排序显示,类比SQL的order by关键字
from和size
对查询结果分页,类似于SQL的limit关键字
post_filter
后置过滤器,在聚合查询结果之后,再对查询结果进行过滤
搜索结果
hits
total
匹配到的文档总数
hits数组
_index
_type
_id
_source
_score
衡量文档与查询的匹配程度。默认情况下,首先返回最相关的文档结果,就是说,返回的文档是按照 _score 降序排列的。如果没有指定任何查询,那么所有的文档具有相同的相关性,因此对所有的结果而言 1 是中性的 _score
max_score
与查询所匹配文档的 _score 的最大值。
took
执行整个搜索请求耗费了多少毫秒
shard
部分告诉我们在查询中参与分片的总数,以及这些分片成功了多少个失败了多少个。正常情况下我们不希望分片失败,但是分片失败是可能发生的。如果我们遭遇到一种灾难级别的故障,在这个故障中丢失了相同分片的原始数据和副本,那么对这个分片将没有可用副本来对搜索请求作出响应。假若这样,Elasticsearch 将报告这个分片是失败的,但是会继续返回剩余分片的结果。
timeout
查询是否超时。默认情况下,搜索请求不会超时。如果低响应时间比完成结果更重要,你可以指定 timeout 为 10 或者 10ms(10毫秒),或者 1s(1秒)。在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。
应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。在后台,其他的分片可能仍在执行查询即使是结果已经被发送了。
应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。在后台,其他的分片可能仍在执行查询即使是结果已经被发送了。
JVM
JIT(即时编译)
有时,循环太频繁,判断条件一直为false,JIT会激进优化从if(obj!=null)优化为if(false).这就属于:循环表达式外提(Loop Expression Hoisting)
-Djava.compiler=NONE 关闭JIT
JVM结构
GC算法
标记一清除算法(Mark-Sweep)老年代
缺点
- 标记清除算法的效率不算高
- 在进行 GC 的时候,需要停止整个应用程序,用户体验较差
- 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表
- 在进行 GC 的时候,需要停止整个应用程序,用户体验较差
- 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表
复制算法(copying)新生代
优点
- 没有标记和清除过程,实现简单,运行高效
- 复制过去以后保证空间的连续性,不会出现“碎片”问题。
- 复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点
- 此算法的缺点也是很明显的,就是需要两倍的内存空间。
- 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销也不小
- 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销也不小
标记-压缩算法(Mark-Compact)老年代
优点
- 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可。
- 消除了复制算法当中,内存减半的高额代价。
- 消除了复制算法当中,内存减半的高额代价。
缺点
- 从效率上来说,标记-整理算法要低于复制算法。
- 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
- 移动过程中,需要全程暂停用户应用程序。即:STW
- 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
- 移动过程中,需要全程暂停用户应用程序。即:STW
内存分配方式
空闲列表
指针碰撞
并发问题解决方案
TLAB
为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用CAS进行内存分配。
CAS
CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
调优思路
C2 编译器优化
逃逸分析
标量替换:用标量值代替聚合对象的属性值
栈上分配:对于未逃逸的对象分配对象在栈而不是堆
但栈上分配还不成熟,Hotspot虚拟机没有实现栈上分配.现在主要还是通过逃逸分析来实现锁消除和标量替换.来提升性能,降低内存占比
同步消除:清除同步操作,通常指 synchronized
新生代,老年代大小比例
进入老年代年龄
调整Eden 和幸存者区比例
禁用偏向锁
设计模式
六大设计原则
1.开闭原则:对拓展开放,对修改关闭
2.单一职责原则:只做一件事
3.里氏替换原则:父类与子类的行为要保持与预期相符
4.迪米特法则:与外界耦合越少越好
5.接口隔离原则:将大接口拆分成多个以某个事务为边界的小接口
6.依赖倒置原则:面向接口编程
享元模式
应用
包装类、缓存、池化技术
装饰器模式
应用
各种IO流
策略模式
工厂模式
计算机网络
持久连接
持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
Connection: keep-alive
1.客户端与服务端的连接保活技术(连接复用),可以减少 TCP 连接的重复建立和断开所造成的额外开销,减轻服务器端的负载,减少响应时间
2.Connection是个逐跳首部,不会被最终传递给目标,因此,如果中间有代理服务器,最终客户端会与代理服务器建立长连接而不是目标服务器
2.Connection是个逐跳首部,不会被最终传递给目标,因此,如果中间有代理服务器,最终客户端会与代理服务器建立长连接而不是目标服务器
三次握手
稳定通信,三次是最低保证
同时,为了保证数据有序,服务器与客户端要交换序列
同时,为了保证数据有序,服务器与客户端要交换序列
粘包
Http(类似公共协议)不会有粘包问题(没规定好数据与数据之间字符串分隔符,如前4个字节是一个包,结果获取了前7个字节数据);
自定义协议规定好分隔符或者固定长度,就可以避免
自定义协议规定好分隔符或者固定长度,就可以避免
TCP、IP、ARP
TCP(传输层):端口
IP(网络层):IP地址
ARP(网络层):地址解析协议
frame:MAC地址(通过ARP协议得到)
IP(网络层):IP地址
ARP(网络层):地址解析协议
frame:MAC地址(通过ARP协议得到)
理解NIO
不知道大家有没有用过2010年左右(或许更早)2G时代的手机,可以运行那种基于J2ME的QQ,能聊天,看个空间,偷个菜(文字版)什么的。这种手机一般都有个缺点就是不能后台运行,一旦去做其他事情(玩游戏,看小说等),QQ就掉线了,就不能收到QQ消息了。如果想要实时接收到女神消息,就要一直保持打开着QQ,不能去做其他事情。这就类似于BIO,阻塞的。
后来QQ出了一个手机业务,叫超级QQ(每月10块呢),可以伪实时在线,同时更快的升级。之所以叫他伪实时在线,是因为它的实现方式是:当QQ收到消息时,腾讯会以短信的形式发到手机上,告诉你某某给你发消息了,请及时处理之类的(也可以直接回复短信,QQ上也会自动转发过去,不太相关暂时忽略)。此时再去登录QQ,就能立刻收到消息了。虽然手机同一时刻依然只能做一件事情,但是在没有QQ消息的时候也无需一直等待了,从而从容不铺去做别的事情。也就是非阻塞的了。
这个超级QQ的业务就像是NIO:人就是Selector,监听事件。短信就像是一个事件。QQ就像是Channel,建立沟通通道。人看到短信,根据短信内容,从而决定要不要打开QQ,处理消息
后来QQ出了一个手机业务,叫超级QQ(每月10块呢),可以伪实时在线,同时更快的升级。之所以叫他伪实时在线,是因为它的实现方式是:当QQ收到消息时,腾讯会以短信的形式发到手机上,告诉你某某给你发消息了,请及时处理之类的(也可以直接回复短信,QQ上也会自动转发过去,不太相关暂时忽略)。此时再去登录QQ,就能立刻收到消息了。虽然手机同一时刻依然只能做一件事情,但是在没有QQ消息的时候也无需一直等待了,从而从容不铺去做别的事情。也就是非阻塞的了。
这个超级QQ的业务就像是NIO:人就是Selector,监听事件。短信就像是一个事件。QQ就像是Channel,建立沟通通道。人看到短信,根据短信内容,从而决定要不要打开QQ,处理消息
Mac使用
窗口相关
切换窗口:按下⌘+Tab
最大化窗口(⌃⌘+F):本窗口视觉上占满全部屏幕,存在感最大
正常窗口:本窗口视觉上和其他本程序或者其他程序的窗口共用桌面
最小化窗口(⌘+M):有两种设置:一种是本窗口在视觉上能见,但是最小,不占用桌面,挪动到Dock的右边,一种是“本窗口”视觉上不可见,最小化(隐藏)到Dock的程序图标中(这个隐藏和下面的⌘+H的主要区别就是⌘+M针对单独的一个窗口,⌘+H是隐藏程序的所有窗口)
隐藏程序的所有窗口(⌘+H):整个程序从视觉上消失,不显示在屏幕的任何地方,但只是看不见而已,其他一切照旧
关闭窗口(⌘+W):本窗口实际上被关闭,所有和本窗口相关的资源释放,如果文件有编辑会提示保存,但和本程序的其他窗口无关
关闭程序(⌘+Q):程序实际上被关闭,本程序所有的窗口关闭,所有资源释放
⌘(command)、⎇(option)、⌃(control)、⎋(esc)、⇧(shift)、⌅(enter)
⇪(caps lock)、↩(return)、↖(home)、↘(end)、⇟(pagedown)、⇞(pageup)
最大化窗口(⌃⌘+F):本窗口视觉上占满全部屏幕,存在感最大
正常窗口:本窗口视觉上和其他本程序或者其他程序的窗口共用桌面
最小化窗口(⌘+M):有两种设置:一种是本窗口在视觉上能见,但是最小,不占用桌面,挪动到Dock的右边,一种是“本窗口”视觉上不可见,最小化(隐藏)到Dock的程序图标中(这个隐藏和下面的⌘+H的主要区别就是⌘+M针对单独的一个窗口,⌘+H是隐藏程序的所有窗口)
隐藏程序的所有窗口(⌘+H):整个程序从视觉上消失,不显示在屏幕的任何地方,但只是看不见而已,其他一切照旧
关闭窗口(⌘+W):本窗口实际上被关闭,所有和本窗口相关的资源释放,如果文件有编辑会提示保存,但和本程序的其他窗口无关
关闭程序(⌘+Q):程序实际上被关闭,本程序所有的窗口关闭,所有资源释放
⌘(command)、⎇(option)、⌃(control)、⎋(esc)、⇧(shift)、⌅(enter)
⇪(caps lock)、↩(return)、↖(home)、↘(end)、⇟(pagedown)、⇞(pageup)
IDEA
编辑代码查看了调用类实现逻辑,然后可以使用后退快捷键,快速回到刚才待编辑的代码处:⌘ + ⌥ + ← / →(方向键)
查看历史文件,并且在弹出窗口内可以使用关键键快速查找:⌘ +E
统一命名:⇧ + F6
查看历史文件,并且在弹出窗口内可以使用关键键快速查找:⌘ +E
统一命名:⇧ + F6
0 条评论
下一页