java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库
Java Virtual Machine是Java虚拟机
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),
但是长整型(long)在目前所有的版本中都是不可以的。
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
面向对象:
优点:易维护、易复用、易扩展
在Object类中,equals方法比较的是hashcode码,而hashcode码为默认的内存地址(Object的hashcode方法是本地方法,也就是用c语言或c++实现的)
而在某些类中,比如String,HashMap类中,equals方法比较的数值是否相同或者key值是否相同,地址就会不一样
而有个规定,就是两个对象equals相同,则该两个对象hashcode也相同。
hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同
对于对象 比较的就是两对象的引用类型
对于基本数据类型,比较数值是否相等
补码
~数字 代表其数字反码的补码的反码减一
基本数据类型:***
引用数据类型:数组,类,接口
调用的是父类的static方法或者字段
调用的是父类的final方法或者字段
通过数组来引用
基本类型也具有对象的特征
它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作
区别:声明方式不同(后者需要new),存储方式及位置不同(前栈后堆),初始值不同(后者null),使用方式不同(集合容器中用包装类)
String对象在创建后不是不可变的,后两者就可以是可以修改的
StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高
StringBuffer类中的方法都添加了synchronized关键字,也就是给这个方法添加了一个锁,用来保证线程安全
string方法使用了final进行修饰
字符串池如果没有使字符串不可变,那就不可能,你定义两个数字他的值是相同的,如果可变,那么修改其中一个值的时候,字符串池中的值发生变化,那其他指向他的值也会变化,就会带来很多风险
字符串已被广泛用作许多 Java 类的参数,比如说hashMap的键
由于 String 是不可变的,它可以安全地共享许多线程,这对于多线程编程非常重要. 并且避免了 Java 中的同步问题,不变性也使得String 实例在 Java 中是线程安全的,这意味着你不需要从外部同步 String 操作
String 在 Java 中是不可变的另一个原因是允许 String 缓存其哈希码,使得它在 Java 中的 HashMap 中使用的 HashMap 键非常快
Java 7以及以前的版本,那么接口中可以包含的内容有:1. 常量;2. 抽象方法
如果是Java 8,还可以额外包含有:3. 默认方法;4. 静态方法
如果是Java 9,还可以额外包含有:5. 私有方法
抽象类由普通类与抽象方法构成,可以使用任意权限,使用extends关键字,且一个子类只能继承一个抽象类,一个抽象类能实现若干个接口
接口由全局常量与抽象方法构成,只能使用public修饰,使用implement关键字,一个类可以实现多个接口,接口不等继承抽象类,但是可以继承多个父接口
单一性原则、开闭原则、里氏替换原则、依赖倒转原则、接口隔离原则、迪米特法则、合成聚合复用
可以将一个类的定义放在另外一个类的定义内部,这就是内部类
分为成员内部类、局部内部类、匿名内部类和静态内部类
从流向分为输入流与输出流
从流操作的单元分为字节流与字符流
从流的角色划分为节点流和处理流
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
多用于单线程,会一直监听一个ServerSocket 等待read方法调用,但是无法很好的解决C10k 以及C10M问题
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
在jdk1.4版本引入,提供了Channel,Selector,Buffer等抽象
在一些简单的nio使用时,先创建Socket处理通道,然后将其设置为非阻塞通道
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
在一个类中会定义一些不希望被外界访问到的属性和方法,所有用正常方法难以访问它的属性与方法,但是可以使用反射机制来获取
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
序列化就是一种用来处理对象流的机制,序列华江对象转化为容易传输的格式的过程
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理
1.通过new对象实现反射机制
2.通过路径实现反射机制
3.通过类名实现反射机制
通过getDeclaredConstructors可以返回类的所有构造方法
通过getConstructors方法获取类中 所有的public类型的构造方法
getParameterTypes可以得到构造方法的所有参数
得到类的实例,我们主要借助于newInstance方法
Constructor Method Field AccessibleObject (左三父类)
setAccessible 爆破
优点:能动态的获取类的实例,提高系统的灵活与扩展,实现无比强大的功能
缺点:性能较低,相对不安全,破坏了类的封装性
Private修饰符不是为了绝对安全设计的,是对用户常规使用的约束
当然Private可以清晰看出来清晰的类结构
java中定义map为一个接口,其主要的实现类有四个,HashMap,Hashtable,LinkedHashMap,TreeMap
map主要是用于存储键值对
使用最多的还是HashMap,HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null,HashMap也不支持线程的同步
Hashtable与HashMap相似,但是他支持线程同步;
LinkedHashMap是HashMap的一个子类,它保存了记录的插入顺序,使用迭代器遍历时,得到的记录肯定是先插入的
TreeMap实现了SortMap接口,所以它能保存它的键值并按升序排序,遍历的时候,记录也是排序的
Collection是JDK中集合层次结构中的最根本的接口
Collections是一个包装类。它包含有各种有关集合操作的静态多态方法,不能实例化,像一个Collection集合框架中的工具类
数组+链表
hashmap创建的时候可以设定容量与负载因子,默认大小一般为16,0.75
初始化大小为 大于k的 2的整数次方
在代码实现上是使用异或和位运算的的出需要扩容大小的一半
判断数组为空,如果是空的就要进行初始化
不为空,计算 k 的 hash 值,通过长度 & hash计算应当存放在数组中的下标 index;
查看是否有该key的值,存在就覆盖
不存在 ,存储在数组下标的链表链中(头插法)
在链表插入的时候,先插入在链表头结点(及堆中链表的头结点),再将数组位置引用替换为刚插入的值
插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍
扩容过程中需要用一个链表来重新计算各个点对于不同长度数组所应该存储的位置
每一次的操作也会增加modCount 属于是fast-fail机制 为了处理多线程同时处理该map的一种异常机制
concurrentHashMap与HashTable
HashTable是在方法前添加了synchronized 给对象加锁实现了线程安全
阻塞队列是在队列的基础上增加了两个附加操作,
由于阻塞对内的这样一个特性,可以非常容易的去实现生产者和消费者这样一个模型,说生产者需要关心数据的一个生产,而消费者只需要关心数据的一个消费,如果队列满了生产者就等待,同样队列空了消费者也需要等待
实现这样一个阻塞队列,需要用到两个非常关键的技术
队列元素的一个存储
线程的阻塞和唤醒而ArrayBlockingQueue,它是基于数组结构的组成队列,也就是说队列元素是存储在数组结构里面,并且由于数组的长度有限制的,为了达到循环生产和循环消费,ArrayBlockingQueue里用到了一个循环数组,而线程的阻塞和唤醒,线程的阻塞和唤醒用到了JUC包里面的一个ReentrantLock和condition,condition的相当于wait/notify在JUC里面的一个实现
1、char的长度是不可变的,而varchar的长度是可变的
2、超出长度自动截取
3、char(10)和char(10),都表示可存10个字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放10个
4、char最多可以存放255个字符, varchar的最大长度为65535个字节,varchar可存放的字符数跟编码有关
5、char和varchar的最大长度限制是mysql规定的
第一范式 每个列都不可以拆分
第二范式 在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
MYSQL的客服端
MYSQL的服务端
内存结构
Buffer Pool 缓冲池 free page/clean page /dirty page
Change Pool 更改缓冲池
自适应哈希
Log Buffer 日志缓冲区
原子性:undo log:回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)
undo log是逻辑日志,事务插入,日志则删除反向操作
一致性
持久性:redo log 用于刷新脏页到磁盘时,发生错误,进行数据恢复
为什么不直接从缓冲区直接写到磁盘而是用redo log来进行磁盘操作?
直接缓存是随机操作,redo log是顺序操作 性能更高(redo log的两个空间也会时间性删除更新)
1.当前读:当开启事务时,InnoDB事务隔离级别为可重复读,但是使用行共享锁或者排它锁都是在你事务还没结束时读取别人提交的事务
2.快照读 读取的记录可能是历史数据,不加锁,是非阻塞读
3.MVCC 多版本控制
RC隔离级别下,在事务中每一次执行快照度时生成ReadView
RR隔离级别下,仅在十五中第一次执行快照读时生成ReadView 后续复用该ReadView
Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。增删改InnoDB更优,支持哈希索引不支持全文索引
MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。Select MyISAM更优,支持全文索引不支持哈希索引
MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。
如果你不建主键,那么数据库会自动选择一个没有重复值的列,或者用一个你看不到的列作为索引进行存储值,那就建议创建表的时候也建主键,主键可以当做索引使用
b+树中对数据存储为了方便范围查找会对进行排序,整形自增更易于存储
索引是帮助MYSQL高效获取数据排好序的数据结构
优点:它可以加快数据的检索速度,提高数据检索效率,降低数据库IO的成本
缺点:但是创建维护(增删改更新表的操作)索引需要时间,而且需要占物理空间
BTREE索引
Hash索引:只有Memory引擎支持
R-TREE索引(空间索引):是MYISAM引擎的特殊索引类型
Full-text索引(全文索引):是MYISAM引擎的特殊索引类型,主要用于全文索引,InnoDB从版本5.6开始也支持
BTREE 又称为多路平衡搜索树
mysql中的B+Tree进行了优化,在叶子节点上添加了前后指针
hash 结构为链表加数组,不支持范围查找
B+Tree
非叶子节点不存储data,只存储索引冗余,可以放更多的索引
叶子节点用指针连接,提高区间访问的性能
B-Tree
叶子节点的指针为空
索引元素不重复
数据索引递增排序
单值索引:一个索引只包含单个列,一个表可以有多个单列索引
唯一索引:索引列的值必须唯一,允许有空值(可以多个null)
复合索引:即一个索引包含多个列
设定主键的时候Mysql会默认创建一个主键索引
创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] ##索引类型
INDEX index_name
[USING index_type] ##索引结构
on table_name(index_col_name,..)
查看索引
show index from table_name
删除索引
DROP INDEX index_name ON table_name
ALTER索引
alter table table_name add [primary|unique|index|fulltext]##主键/唯一/普通/全文
index_name(colum_list);
视图是一种虚拟存在的表,数据并不在数据库中实际存在
视图就是一条SELECT语句执行后返回的结果集
创建
CREATE VIEW view_name AS select_statement##Select语句
查看
show tables
修改
ALTER VIEW view_name AS select_statement##Select语句
删除
DROP VIEW view_name
存储过程和函数就是预先编辑并存储在数据库中的一段SQL语句集合,可以减少数据在数据库和应用服务器之间的传输
存储过程和函数的区别在于函数必须有返回值,前者没有
DELIMITER 用来声明SQL语句的分隔符,默认为;
CREATE PROCEDURE procedure_name
begin
---sql语句
end;
call procedure_name
-- 查看指定数据库中的所有储存过程
SELECT name from mysql.proc where db='db_name'
-- 查看存储过程的状态信息
show procedure status
-- 查询某个存储过程的定义
show create procedure procedure_name
DROP PROCEDURE procedure_name
Declare 、set、SELECT… into…、if判断、输入参数,输出参数,case结构、while循环,repeat循环,loop循环、退出条件leave
游标是用来储存查询结果集的数据结构
声明游标declare、开启游标open、获取游标fetch、关闭游标close
触发器是与表有关的数据库对象,在增删改之前或者之后,触发并执行触发器中定义的SQL语句集合
使用别名OLD与NEW来用用触发器中发生变化的记录内容
create trigger trigger_name
before/after insert/update/delete
on table_name
begin
triggrt_statement;
end;
drop trigger trigger_name
show trigger;
全局锁
经典使用场景为对数据的全库逻辑备份
flush tables with read lock
表级锁
表锁
元数据锁 (MDL)
意向锁a
行级锁
行锁
间隙锁
临键锁
间隙锁唯一目的是防止其然事务插入间隙,间隙锁可以共存
硬件和操作系统层面的优化
从硬件层面来书,影响MYSQL性能的主要是CPU,可用内存大小,磁盘读写速度,网络带宽
从操作系统层面来说,应用文件句柄数,操作系统的网络配置
以上部分的优化主要由DBA和运维工程师去完成
在硬件方面的优化中,重点应该是服务本身多承载的体量,然后提出合理的要求,避免出现资源浪费的一个现象
架构设计层面的优化
MySQL程序酒配置优化
MYSQL又是一个互联网大厂检验过的生产级别的成熟数数据库,对于MYSQL数据库本身的优化,一般可以通过MYSQL配置文件my.cnf来完成,比如说MYSQL5.7版本,默认的最大连接数是151个,这个值可以在my.cnf中去修改
binlog日志默认是不开启,我们也可以在这个文件中去修改开启
缓存BufferPool默认大小配置,这些配置啊一般是和用户的安装环境以及使用场景有关系,这些配置官方只会提供一个默认的配置,具体的情况呢还是得由使用者去根据实际情况去修改
关于配置项的修改,需要关注两个层面
第一个是配置的作用域,它可以分为会话级别和全局范围
第二个是是否支持热加载,
针对这两个点啊,我们需要注意的是的是全局参数的设定,对于已经存在的会话是无法生效的,会话参数的设计,随着会话的销毁而失效
第三个是全局类的统一配置建议配置在默认配置文件中,否则重启服务会导致配置失效
SQL执行优化
SQL语句上的优化
慢SQL的定位和排查,我们可以通过慢查询日志和慢查询日志工具分析得到有问题的SQL列表
执行计划分析,针对慢SQL,我们可以使用关键字explain来,去查看当前SQL的执行计划,所以重点关注Type,key,Rows,Filterd从而去定位MYSQL执行慢的根本原因再去有目的去进行优化
使用的show profile工具,show profile是MYSQL提供的,可以用来分析当前会话中SQL资源消耗情况的工具,可以用于SQL调优的测量,默认情况下show profile是关闭状态,打开之后会保存最近15次的运行结果,针对运行慢的SQL,通过播放工具进行详细分析,可以得到SQL执行过程中所有资源的开销情况以及IO开销,CPU开销,性能开销等
Redis是一个开源的非关系型数据库
默认接口为6379
Redis可以存储物五种不同类型的值之间的映射,键类型只能为字符串,值支持五种数据类型:字符串,列表,集合,散列表,有序集合
具有
五个大优点
缺点:但是容量受物理内存限制
而且不具备自动容错和恢复功能
较难支持在线扩容
Redis提供了AOF与RDB两种持久化机制
RDB是默认的持久化方式,那一定时间将内存的数据以快照的方式保存到硬盘,对应产生的数据为dump.rdb
RDB的优点:
但是 数据安全性较低,因为是一段时间内的持久化,可能会发生数据丢失
AOF持久化是将Redis每次写命令记录到单独的日志中
AOF的优点:
但是AOF文件比RDB文件大,恢复速度慢,数据量大的时候,启动效率比RDB低
两种方式同时开启时,数据恢复Redis优先选择AOF
redis事务就是一次性,顺序性,排他性的执行一个队列的一系列命令
事务开始Multi
命令入队
事务执行Exec
取消事务Discard
watch key 监视一个或者多个key ,事务执行前key被其他命令所改动,事务将会被打断
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
如果在一个事务中出现运行错误,那么正确的命令会被执行。
WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
UNWATCH命令可以取消watch对所有key的监控。
在6.x以前的模型,工作线程只有一条,对于多条请求,内核使用多路复用器(epoll)进行管理,然后进行内核的数据读取
在6.x之后的模型,有一条工作线程,若干个io线程,io线程并行,工作线程串行
网卡缓存之后,会在内核中产生队列,然后io线程进行io操作,提高了吞吐量
提出redis单线程串行的点
redis可以保证内部串行,但是不能保证外部业务的顺序
一般情况下,Redis是用来实现应用和数据库之间的一个读操作的缓存层的,主要目的是去减少数据库的iO,还可以提升数据的IO性能
当应用程序需要去读取某个数据的时候,首先会先尝试Redis里面去加载,如果命中了就直接反回,如果没有命中就直接从数据库里面查询,查询到数据之后再把数据缓存到Redis里面,这种架构里面呢,会出现一个问题,就是一份数据同时保存在数据库和Redis里面,当数据发生变化的时候,需要同时去更新Redis和MYSQL,由于更新操做的是有先后顺序的,并且它并不像Mysql中的多表事物操作,可以满足acid的特性,所以就会出现叫数据一致性问题,在这个情况下,能够选择的方法只有几种
先更新数据库再更新缓存
如果先根据数据库再更新缓存,再更新缓存,那么如果缓存更新失败就会导致数据库和Redis的数据是不一致的,
先删除缓存再更新数据库,
如果是先删除缓存再更新数据库,理想情况下是应用下次访问Redis的时候,发现Redis里面的数据是空的,那么就会从数据库加载保存到Redis里面,数据理论上是一致的,但是在极端情况下由于删除Redis和更新数据库,这两个操作并不是原子操作,所以在这个过程中,如果出现其他线程来访问,还是会存在数据不一致的问题
如果需要在极端情况下仍然去保证 Redis和Mysql的数据一致性,就只能采用最终一致性的方案,比如基于RocketMQ的可靠性消息通信来实现数据的最终一致性,还可以直接通过Canal组件监控Mysql里面的binlog日志,把更新后的数据同步到Redis里面,因为这里是基于最终一致性来实现的,如果业务场景不能去接受数据的短期不一致性,那么就不能使用这样的方案
缓存雪崩
现象:存储在缓存里面,大量数据在同一个时刻全部过期,原本缓存组件能够扛住了大部分流量全部请求到了数据库,从而呢导致数据库压力增加,造成数据库服务器的崩溃的一种现象
导致缓存雪崩的原因有几个方面
一个是缓存中间件宕机,当然可以对缓存中间件做高可用集群来避免,其实就是缓存中里面大部分key可以都设置的相同的过期时间,导致同一时刻这些key都过期了,所以在这样一个情况下呢,可以在失效时间里面去增加1~5分钟的随机值,从而去避免同时失效的一个问题,
缓存穿透
现象:表示的是短时间内有大量的不存在的key请求到应用程序里面,而这些不存在的key呢在缓存里面又找不到,从而去导致全部的请求,全部穿透到的数据库,造成数据库的压力增加,我认为这个长期的核心问题是针对于缓存的一种攻击行为,因为正常的业务里面即便是出现这样一个不存在key的情况,由于缓存的不断预热,影响也不会很大,而攻击行为呢就需要去具备时间的一个持续性,而只有key确定在数据库里面不存在的情况下,才能达到这样一个目的,
所以在这个问题下有两个方法可以去解决
第1个是把无效的key保存到Redis里面,并且设置一个特殊的值,比如说像“null”字符串,这样的话下次再来访问的时候就不会去查数据库了,(但是如果攻击者不断的用随机的不存在的key来访问也还是会存在相同的问题)
使用布隆过滤器来实现,在系统启动的时候,我们可以把目标数据全部缓存到布隆过滤器里面,当攻击者去使用不存在的key来请求的时候先到布隆过滤器里面去进行查询,如果不存在就意味着这个key,肯定在数据库里面也不存在,所以这个时候就不会去访问数据库
布隆过滤器呢还有个好处
它采用的是bitmap,位图来进行数据存储,所以它的占用内存空间是很小的
不过呢,在我看来啊,这个问题真的有点过于放大他所带来的影响,当然也是要考虑,
首先我认为为什么放大呢,是因为
第一个在一个成熟的系统里面,对于比较重要的热点数据必然会有一个专门的缓释系统来维护,同时啊他的过期时间的维护,必然和其他的业务的key会有一定的区别,
而且对于非常重要的场景,我们还会设计多级缓存的一个实现
其次啊,即便是触发了缓存雪崩,那么数据库本身的容灾能力也并没有那么脆弱,数据库的“主从”“双组”“读写分离”这些策略都可以很好的去缓解并发流量,
最后呢数据库本身也有最大连接数的限制,超过限制的请求会被拒绝,
再结合熔断机制也能够很好的去保护数据库系统,最多就是造成部分用户体验不好而已,
另外在程序设计上,为了避免缓存未命中导致大量请求穿透的数据库的问题,我们还可以在访问数据库这个环节里面去加锁,虽然影响了性能,但是对整个系统是安全的
总而言之办法有很多,具体选择哪种方式还要看具体的业务场景
过期的策略有三种;定时过期,惰性过期,定期过期
可以使用expire设置key的过期时间,对于处理过期一般就两种策略
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则:
(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
Redis里面的内存淘汰策略呢是指,当内存的使用率达到了max memory的上限的时候,他的一种内存释放的一个行为
Redis里面提供了很多种内存的淘汰算法,归纳起来呢主要有4种,
第1种是随机:随机移除某个key
第2种是TTL算法:就是在设置的过期时间的键里面呢,去找到更早过期时间的key进行有限移除
第3个是LRU算法去移除最近很少使用的key
- LRU是一种比较常见的内存淘汰算法,在Redis里面它会维护一个大小为16的候候选池,这个候选池里面的数据会根据时间进行排序,每一次随机抽取5个key放到这个候选池里面,当候选词满了以后,访问的时间间隔最大的key,就会从候选词里面取出来淘汰掉,通过这一个设计,就可以把真实的最少访问的key从内存里面淘汰内存里面淘汰
- 但是这样还是会存在一个问题,假如一个key很长时间没有访问,但是最近一次偶然被访问到那么,LRU就会认为这是一个热点key,不会被淘汰,所以在Redis4里面增加了一个LFU的算法
- LUF增加了访问频率这样一个维度来统计数据的热点情况
第4个是LFU算法,那么他跟LRU算法是类似的
- LFU的主要设计是使用了两个双向列表去形成一个二维的双向列表,一个是用来保存访问频率,另一个是用来保存访问频率相同的所有的元素,当添加元素的时候啊,访问频次默认为1,于是找到相同频次的节点,然后添加到相同的频率节点对应的双向列表的头部,当元素被访问的时候呢,就会增加对应T的访问频率,并且把当前访问的节点移动到下一个频次的节点
- 有可能会出现某个数据前期的访问次数很多,然后后续就一直不使用了,如果单纯按照这样的一个访问频次来进行淘汰的话,那么这个T就很难被淘汰掉,所以啊,在LFU的算法里面去通过了使用频率和上次访问时间来标记数据的这样一个热度,如果某个数据有如何写,那么就增加访问的频率,如果一段时间内这个数据没有读写,那么就减少访问频率,
- 通过LFU算法改进之后,就可以真正达到非热点数据的淘汰,
- LFU也有缺点,相比LRU算法呢,LFU增加了访问频次的一个维护以及实现的复杂度,要比LRU更高
redis是弱一致性的,异步的同步
锁不能使用主从
在配置中提供必须要有多少个client链接能同步,配置同步一直,趋向一致性
Servlet
一种服务器端的Java应用程序,由 Web 容器加载和载管理,用于生成动态 Web 内容,负责处理客户端请求
Jsp
是 Servlet 的扩展,本质上还是 Servlet,每个 Jsp 页面就是一个 Servlet 实例,Jsp 页面会被 Web 容器编译成 Servlet,Servlet 再负责响应用户请求
jsp共有以下9个内置对象:
1.request 客户端请求,此请求会包含GET/POST请求的参数
2.response 网页传回客户端的回应
3.pageContext 网页的属性是在这里管理
4.session 请求有关的会话期
5.application servlet正在执行的内容
6.out 用来传送回应的输出
7.config servlet的架构部分
8.page jsp页面网页本身
9.exception 针对错误网页,未捕捉的例外
名称 | 作用域 |
---|---|
application | 在所有应用程序中有效 |
session | 在当前会话中有效 |
request | 在当前请求中有效 |
page | 在当前页面有效 |
存储位置不同,Cookie在浏览器端存储,Session在服务器端存储;
存储容量不同,Cookie存储容量很小,Session存储容量可以很大;
安全性不同,Cookie安全性较低,Session安全性很高;
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用cookie
一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。
如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session失效。
但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用session。
严格限制 Web 应用的数据库的操作权限,给连接数据库的用户提供满足需要的最低权限,最大限度的减少注入攻击对数据库的危害
校验参数的数据格式是否合法(可以使用正则或特殊字符的判断)
对进入数据库的特殊字符进行转义处理,或编码转换
预编译 SQL(Java 中使用 PreparedStatement),参数化查询方式,避免 SQL 拼接
发布前,利用工具进行 SQL 注入检测
报错信息不要包含 SQL 信息输出到 Web 页面JVM
这个词实际上是CSS(Cross Site Scripting),但它与CSS同名。所以名字是XSS。跨站点脚本攻击类似于上面提到的CSRF。实际上,原则是将一段JavaScript代码注入网页
XSS主要分为两大类:非持久型攻击、持久型攻击。
不管是用户端从任何的输入到任何输出都进行过滤,转义,让攻击者的代码注入不能识别,就可以避免攻击了
CSRF(Cross-site request forgery)跨站请求伪造
目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。
进程:
线程:
区别:
直接使用 Thread类创建
使用 Runnable 类配合 Thread类创建
把【线程】和【任务】(要执行的代码)分开
Thread 代表线程Runnable 可运行的任务(线程要执行的代码)
使用FutureTask类(Thread类的任务为管理器) 配合 Thread类创建//实现一个Callable接口(可以返回返回值)
相同点
1、两者都是接口;(废话)
2、两者都可用来编写多线程程序;
3、两者都需要调用Thread.start()启动线程;
不同点
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
注意点
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池
volatile关键字有两个作用
在我的理解中,可见性是指当一个线程对于共享变量的修改,其他线程可以立刻看到线程修改之后的值
其实这个可见性,本质上是有几个方面造成的
第一个是CPU层面的高速缓存
总线锁:他锁定的是CPU的前端总线,从而导致只能有一个线程和内存通信,这样就避免了多线程并发造成的可见性问题
缓存锁:缓存锁是对总线锁的一个优化,因为总线锁的CPU使用效率大幅度下降,所以缓存锁只针对CPU三级缓存中目标数据去加锁,而缓存锁是使用MESI缓存一致性协议来实现的
第二个是指令重排序
第三个是编译器层面优化
当然除了volatile关键字以外,从JDK5开始,JMM就使用了一种Happens-Before的模型去描述多线程的可见性的一个关系,也就是如果两个操作之间具备Happens-Before关系那么就意味着这两个操作具备可见性的一个关系,不需要再额外考虑增加volatile关键字来提供可见性的一个保障
定长线程池:newFixedThreadPool 每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程
可缓存的线程池:newCatchThreadPool 如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制
定长线程池:newScheduledThreadPool 可执行周期性的任务
单线程的线程池:newSingleThreadExecutor 线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
单线程可执行周期性任务的线程池:newSingleThreadScheduledExecutor
任务窃取线程池:newWorkStealingPool 不保证执行顺序,适合任务耗时差异较大
最原始的线程池:ThreadPoolExecutor()
execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
execute() 没有返回值;而 submit() 有返回值
submit() 的返回值 Future 调用get方法时,可以捕获处理异常
首先ThreadLocal是一种线程隔离机制,它提供了多线程环境下,对于共享变量访问的一个安全性
在多线程访问共享变量这个场景里,一般情况下的我们的解决办法是对于共享变量去加锁,所以保证在同一个时刻只有一个线程能够对共享变量进行更新,并且基于happens -before规则里面的锁监视器规则,又能够保证数据修改之后对其他线程是可见的,但是加锁会带来性能上的下降,所以ThreadLocal用了一种空间换时间的一个设计思想,也就是说在每个变量里面都有一个容器来存储共享变量的一个副本,然后每个线程只对自己的变量副本来做更新操作,这样的话既解决了现成的安全问题,又避免了多线程竞争所的一个开销
ThreadLocal的一个具体实现原理是在Thread类里面有一个成员变量叫ThreadLocalMap,他专门用来存储当前限量的共享变量的一个副本,后续这个,去这个线程对共享边的一个操作呢,都是从这样一个ThreadLocalMap里面去进行变更的,不会影响全局共享变量的一个值,从而去实现数据的一个隔离
产生死锁的必要条件:互斥条件、请求和保持条件、不剥夺条件、环路等待条件
死锁简单来说就是两个或者两个以上的线程在执行的过程中去争夺同样一个共享资源造成的相互等待的一个现象,如果没有外部的干预,线程会一直阻塞,无法往下去执行,这样一直处于相互等待资源的线程,我们称为死锁线程。
导致死锁的条件有4个,也就是说这4个条件,同时满足就会产生死锁。
导致死锁之后呢,只能通过人工干预来解决,比如说重启服务或者“kill”掉这这个线程,所以我们只能在写代码的时候去规避可能出现的死锁问题,而按照死锁发生的4个条件我们只需要破坏其中的任何一种就可以去解决它,但是
类加载子系统:类加载器
运行时数据区域:方法区、虚拟机栈、本地方法栈、堆、程序计数器
执行引擎:JIT即时编译器、垃圾回收器
本地库接口
本地方法库
首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能
虚拟机遇到一条new 指令时,先检查常量池是否以及加载相应的类,如果没有,需要立刻去加载目标类,然后去调用目标类的构造器去完成初始化,目标类的加载是通过类加载器来实现的,主要就是把一个类加载到内存里面,初始化的过程,这个步骤主要是对目标类里面的静态变量,成员变量,静态代码块进行初始化,当目标内被初始化以后就可以从常量池里面去找到对应的类源信息
类加载初始化通过后,接下来分配内存。若java堆内存是绝对的规整,使用“指针碰撞”方法分配内存;如果不规整,就从空心列表中分配,叫“空闲列表”方式。
指针碰撞:分配内存是将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离
空闲列表:需要有虚拟机维护一个列表来记录那些内存是可用的,在分配到时候可以从列表中查询到足够大堆内存分配给对象,并在分配后更新列表记录
划分内存时需要考虑一个问题-并发,也有两种方式:CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。
然后内存空间初始化操作,JVM会把目标对象里面的普通成员变量初始化为零,至比如说int类型初始化为零,String类型初始化为“null”,这一步操作主要是要保证对象里面的实例字段,不用初始化就可以直接使用,程序能够直接获取这些字段对应的数据类型的0值
之后JVM还需要对目标对象的对象头做一些设置,比如对象所属的内源信息,对象的GC分代年龄,HashCode,锁标记等,完成这些步骤以后呢,对于JVM来说新对象的创建工作已经完成了,但是对于Java语言来说,对象创建才算刚刚开始
接下来要做的啊,就是执行目标对象内部生成的方法初始化成员变量的值,执行构造块,最后调用目标对象的构造方法去完成对象创建。其中啊,方法是Java文件编译之后,在字节码文件里生成的,它是一个实例构造器,这个构造器里面会把构造块,变量初始化,调用父类构造器等这样一些操作组织在一起,调用方法能完成这一系列动作
java程序需要通过JVM栈上的引用访问堆中具体的对象。对象的访问方式取决于JVM虚拟机实现,主流访问方式有句柄和直接指针
指针: 指向对象,代表一个对象在内存中的起始地址。
句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。
java是有GC回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除
但是,即使这样,java还是存在内存泄露的情况:长生命周期的对象持有短生命周期的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收;
JVM运行时数据区可划分为,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈,方法区和堆。
其中方法区和堆属于线程之间共享的,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈属于线程私有的。
栈使用的是数据结构中的栈,先进后出的远侧,物理地址分配是连续的所以性能快
栈内存放的是局部变量,操作树栈,返回结果。更关注的是程序方法的执行
栈是连续的,所以分配内存的大小要在编译期就确认,大小固定
栈只对于线程是可见的,所以也是线程私有的。他的生命周期和线程相同
堆物理地址分配对象是不连续的,可能性能慢一些
堆存放的是对象的实例和数组,该区更关注的是数据的存储
堆是不连续的,所以分配内存是在运行期就确定的,大小不固定,一般堆大小远远大于栈
堆对于整个应用程序的都是可见的
静态变量存在方法区
静态的对象还是放在堆
加载、验证、准备、解析,初始化,使用,卸载
在类加载过程中,对于类文件的加载顺序
总共有四种类加载器
启动类加载器、扩展类加载器、应用类加载器、自定义加载器
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器(注意,上图已经指出,这里的父子关系是组合关系而非继承关系)去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
新生代收集器:
吞吐量 = 用户线程时间/(用户线程时间+GC线程时间)
老年代收集器:
堆内存垃圾收集器:G1(Java堆并行收集器)
CMS 处理过程有七个步骤:
1.标记-清除算法
2.复制算法
3.标记-整理算法
4.分代收集算法
#{}是预编译处理
$ 符号一般用来当作占位符
spring是一个轻量级java开发框架致力于让程序员开发更加高效 它
但是spring是一个轻量级框架却给人大而全的感觉,而且非常依赖反射,反射影响性能
IOC与AOP
控制反转:原来的对象是由使用者来进行控制,有了spring之后,可以把整个对象交给spring来帮我们进行管理
DI:依赖注入,把对应的属性的值注入到具体的对象中,@Autowired,populateBean完成属性值的注入
容器:存储对象,使用map结构来存储,在spring中一般存在三级缓存,singletonObjects存放完整的bean对象,整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理(bean的生命周期)
接口注入
Setter方法注入
构造器注入
1.纯配置文件
2.配置文件加注解@Component+
3.纯注解@Configuration +@Bean
(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
(2)prototype:为每一个bean请求提供一个实例。
(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
(5)global-session:全局作用域,global-session和Portlet应用相关。
(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(2)基于 TransactionProxyFactoryBean的声明式事务管理
(3)基于 @Transactional 的声明式事务管理
(4)基于Aspectj AOP配置事务
如果在代码中把两个或者多个Bean相互之间去持有对方的引用,就会发生循环依赖,循环依赖的会导致注入出现死循环,这是spring发生循环依赖的一个原因,循环依赖有三种形态
Spring设计了三级缓存去解决情况应该的问题,当我们去通过getBean去获得一个对象实例的时候,Spring会先从一级缓存去找,如果发现一级缓存中没有找到,就去二级缓存的去找,如果一二级缓存都没有找到,意味着这个Bean还没有实际化,于是容器会去实例化这个Bean,而这个初始化Bean,我们认为它叫早期Bean,于是会把这个目标Bean人放入到二级缓存,同时啊加入一个标记是表示它是否存在循环依赖,如果不存在就把这个Bean放入二级缓存,否则会标记这个Bean的存在循环依赖,然后在等待下一次轮循的时候去赋值,就是解析Autowired注解,Autowired注解对赋值完后,会将目标Bean存一键缓存。
这个可以做一个总结。Spring一级缓存存放所有成熟的Bean,二级缓存能存放所有的早期Bean,先取一级缓存再取二期缓存。
第三级缓存的作用
三级缓存呢是用来存储代理Bean啊,当调用getBean方法的时候,发现目标Bean需要通过代理工厂来创建,这个时候,会把创建好的实例保存到三级缓存,最终也会把复制好的Bean同步到一级缓存
Spring在哪些情况下是无法去解决循环依赖的问题
有4种情况下的情况应该是无法被解决
Spring Bean生命周期,大致可以分为5个阶段
分别是创建前准备,准备实例化,依赖注入,容器缓存和销毁实例阶段,
BeanFactory
ApplicationContext
过滤器(Filter)
过滤器,是在java web中将你传入的request、response提前过滤掉一些信息,或者提前设置一些参数。然后再传入Servlet或Struts2的 action进行业务逻辑处理。比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入Servlet或Struts2的action前统一设置字符集,或者去除掉一些非法字符。
拦截器(Interceptor)
拦截器,是面向切面编程(AOP,Aspect Oriented Program)的。就是在你的Service或者一个方法前调用一个方法,或者在方法后调用一个方法。比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
通俗理解:
使用场景
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
静态变量不属于对象的属性,而是属于类的属性,Spring是基于对象的属性进行依赖注入的,所以使用静态变量后对静态变量进行注入,会报空指针异常
解决方式有三种:
xml方式 在bean中添加init-method关键字为init
使用getBean方法 static修饰对象的时候,使用LocalContextFactory.getInstance().getBean方法赋值
使用注解@PostConstruct方法实现
编写一个方法init()
,并标注注解@PostConstruct
,意思就是在完成构造函数实例化后就调用该方法,该方法会对对象实例化。
set方法上添加@Autowired注解,类定义上添加@Component注解;
事务管理、安全检查、权限控制、数据校验、缓存、对象池管理等
aop是ioc的一个扩展功能,先有的ioc,再有的aop,只是在ioc的整个流程中新增的一个扩展点而已:BeanPostProcessor总: aop概念,应用场景,动态代理
bean的创建过程中有一个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在BeanPostProcessor的后置处理方法中来进行实现
1.实现的接口不一样
JDK动态代理实现InvocationHandler接口
CGLIB实现MethodInterceptor接口
2.使用的方法不一样
JDK的方法:Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this)
method.invoke(target,args)
利用所代理方法所实现的接口来进行获取所需代理类的方法与变量
CGLIB的方法 Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
methodProxy.invokeSuper(o,objects)
利用继承所需代理的方法来进行获取所需代理类的方法与变量
3.要求不一样
JDK被代理的对象必须要实现接口
CGLIB被代理的对象最好不要声明成final
4.虽然方法不一样,但是两者的最后结果一样
spring的事务管理是如何实现的?
传播特性有几种? 7种
Required,Requires_new,nested,Support,Not_Support,Never,Mandatory
某一个事务嵌套另一个事务的时候怎么办?
A方法调用B方法,AB方法都有事务,并且传播特性不同,那么A如果有异常,B怎么办,B如果有异常,A怎么办
在回答两种方式区别的时候,最大的问题在于保存点的设置,很多会认为内部设置REQURED和NESTED效果是一样的,其实在外层方法对内层方法的异常情况在进行楠获的时候区别都不同
使用REQURED的时候,会报Transcionrolled back becauseit hasbeen marked as allackonly信息,因为内部异常了,设置了回滚标记,外部捕获之后,要进行事务的提交意味着要回滚,所以会报异常
NESTED不会发证这种情况,因为在回滚的时候把回滚标记清除了,外部捕获异常后去提交,没发现回滚标记.就可以正常提交了。
这两种方式产生的效果是一样的
但是REQUREDJ NEw会有新的连接生成,而NESTED使用的是当前事务的连接,
而且NESTED还可以回滚到保存点、REQURED NEW每次都是一个新的务的回滚
但NESTED其实是一个事务,外层事务可以控制内层事务的回滚,内层就算没有异常.外层出现异常,也可以全部回滚。
!
前端控制器(DispatcherServlet):其作用是接收用户请求,然后给用户反馈结果。它的作用相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
处理器映射器(HandlerMapping):其作用是根据请求的URL路径,通过注解或者XML配置,寻找匹配的处理器信息。
处理器适配器(HandlerAdapter):其作用是根据映射器处理器找到的处理器信息,按照特定规则执行相关的处理器(Handler)。
处理器(Hander):其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至ModelAndView对象中。
视图解析器(ViewResolver):其作用是进行解析操作,通过ModelAndView对象中的View信息将逻辑视图名解析成真正的视图View(如通过一个JSP路径返回一个真正的JSP页面)。
视图(JSP,FreeMarker等):View是一个接口,实现类支持不同的View类型(JSP、FreeMarker、Excel等)
总体来说设计模式
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型模式,共十一种:策略模式、模板方法模式、观察者模、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
关系模型
is-a:继承关系 has-a:从属关系 like-a:组合关系
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
懒汉模式
饿汉模式
工厂模式的主要类型
1、简单工厂
2、工厂方法
3、抽象工厂
Spring Boot可以建立独立的Spring应用程序;
内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了;
无需再像Spring那样搞一堆繁琐的xml文件的配置;
可以自动配置Spring.SpringBoot将原有的XML配置改为Java配置,将bean注入改为使用注解注入的方式(@Autowire),并将多个xml,properties配置浓缩在一个appliaction.yml配置文件中。
提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
整合常用依赖(开发库,例如spring-webmvc、 jackson-json、validation-api和tomcat等),提供的POM可以简化Maven的配置当我们引入核心依赖时,SpringBoot会自引入其他依赖。
1.SpringBoot的核心配置文件有哪些?
SpringBoot的核心配置文件有application和bootstarp配置文件。
2.他们的区别是什么?
application文件主要用于Springboot自动化配置文件。
bootstarp文件主要有以下几种用途:
自动装配,就是自动去把第三方组件的Bean装载到IOC容器里面,不需要开发人员再去写Bean相关的一个配置,在SpringBoot应用里,只需要在启动类上加上@SpringBootApplication注解就可以去实现自动装配,SpringBootApplication注解是一个复合注解,真正去实现自动装配的是EnableAutoConfiguration注解
自动装配的实现的主要依靠三个核心的关键技术
在我看来呢,SpringBoot是约定优于配置这一理念下的一个产物,所以在很多的地方都会看到这一类的思想,它出现了让开发人员可以更加聚焦的在业务代码的编写上,而不需要去关心和业务无关的配置,其实啊,这种装配的思想在spring framework3.x版本里面的Enable注解就已经有了实现的一个雏形,Enable注解是一个模块驱动的意思,也就是说我们只需要增加Enable注解,就能自动打开某个功能,而不需要针对这个功能去做Bean的配置,Enable注解具体的底层呢,也是去帮我们自动去完成这样一个模块相关Bean的注入的
微服务不只是springcloud框架
一个项目的部署从单体式架构到SOA架构到微服务架构
对于一个项目,有很多的服务,微服务中就会产生一个服务集群,集群之间产生很多的关联
对于这些服务需要管理,就有一个注册中心来同一注册管理
对于各式各样的服务就有文件的配置,对服务集群的配置文件统一管理的配置中心
对于服务的外部访问,服务网关就来处理多种外部请求,请求路由,负载均衡
服务集群需要对于数据进行数据的保存,连接数据库,数据库也需要集群分化
对于数据库的请求过多会导致居多问题,分布式缓存就能缓解数据的请求处理
然后对于数据库的数据查询,有时候也会产生很多性能缺失,分布式搜索应运而生
服务与服务之间的请求也会产生访问不对的,消息队列就来管理这些服务间的请求
还有分布式日志管理记录数据服务之间的请求,系统监控/链路追踪也是对访问请求的追踪监控等
然后使用Jenkins对你的微服务项目进行统一编译,docker进行打包,产生镜像
在通过Kubernets,Rancher进行项目部署,这一套流程下来就是微服务
服务网格就是Service Mesh,它是专门用来去处理服务端通信的一个基础设施层,它主要功能是去处理服务之间的一个通信并且负责实现请求的可靠性传递,Service Mesh我们通常可以把它称为第三代微服务架构,也意味着它是在原来的微服务架构的基础上去做的升级,为了更好的就说明Service Mesh那我就不得不说一下关于微服务架构这一块的知识
首先当我们去把一个电商系统以微服务架构的方式去进行拆分之后,就会包含Web Server,Payment,inventory等等,这些微服务应用呢,全部被部署到Docker容器,由于每个服务的业务逻辑是独立的,比如说像支付服务它会负责支付的业务逻辑,订单服务么去实现订单的处理逻辑,Web Server呢,去实现客户端请求的响应等等,所以服务之间必须要去相互通信才能够去实现功能的一个完整性,比如用户把一个商品加入购物车,那么请求会进入到Web Server,然后转发到购物车服务去进行处理并且最终存入到数据库,在这个过程中啊,每个服务之间必须要知道对方的通信地址并且当有新的节点加入进来的时候,还需要去对这些通信地址进行动态的维护,所以在第一代的微服务架构里面,每一个微服务除了要实现自己的业务逻辑以外,还需要去解决上下游的寻址和通信,以及像容错等一些问题,于是就有了第二代微服务架构
第二代微服务架构,它引入了服务注册中心来去实现服务之间的一个寻址,并且服务之间的容错机制,负载均衡也逐步形成了独立的服务框架,比如主流的SpringCloud或者Spring Cloud Alibaba,在第二代微服务架构里,负责业务开发不仅仅需要关注业务的逻辑,还需要去花大量精力去处理微服务中的一些基础性的配置工作,虽然SpringCloud已经尽可能的去完成这些事情,但是对于开发人员来说,学习SpringCloud以及针对SpringCloud的配置和维护仍然存在比较大的一个挑战,另外也增加了整个微服务架构的一个复杂性。
实际上,在我看来微服务中的所有的这些服务,注册也好,容错也好,安全工作等等也好,都是为了去保证服务之间通信的一个可靠性,于是呢就有了第三代微幅架构
原本的微服务框架里面的微服务的基础能力,被进一步地从一个SDK演进到了一个独立的代理进程SideCar,SideCar的主要职责是负责处理各个微服务之间的通信,承载了原本第二代微服务架构中的服务发现,调用容错,服务治理等这样一些功能,实现微服务的能力和业务逻辑实现彻底的解耦,之所以我们称Service Mesh是服务网格,是因为在大规模的服务架构中,每一个服务的通信都是由SideCar来代理的,各服务之间的通信拓扑呢,看起来就像一个网格形状,SideCar的开源框架很多,有很多其中具有典型代表性的叫Istio,它是Google开源的一个这样的Service Mesh框架
CAP原则又称CAP定理,指的是在一个分布式系统中
一致性(Consistency)
可用性(Availability)
分区容错性(Partition tolerance)
利用restTemplate,在启动文件上配置RestTemplate来调用http请求,在服务层通过RestTemplate调用需调服务路径,加上返回数据的Class类,实现远程调用
30秒一次,客服端往服务器发送一次心跳,证明服务还存在
1.在Application文件中添加Bean的自定义IRUle类方法
2.在配置文件中选择负载均衡规则
默认是懒加载,可以在配置文件中选择开启饥饿加载(可以选择单个还是多个服务器采用饥饿加载)
nacos默认服务地址是8848
对于非临时实例,和Eureka一样的心跳监测
对于临时实例,nacos会主动询问服务是否存在
对于配置文件会先寻找服务名加环境的配置,然后是服务名不加环境的配置,最后才是本地配置(有统一配置管理的情况下)
基于业务划分 服务-集群-实例 优先选择本地集群
基于概念划分 环境隔离 namespace 不同环境下不可见
可以通过两种方式实现配置管理热更新
- 在Value 结合@RefreshScope刷新
- 通过@ConfigurationProperties注入
zuul阻塞式 ,gateway响应式
跨域有域名不同和域名相同情况下的端口不同两种情况
解决方案:CORS 跨域资源共享
先讲述在高并发场景下,一个系统没有被保护的情况
例如:下游服务宕机导致上游服务请求量增加导致上游服务不可用或者宕机
超时
当服务a调用服务b的时候,给服务a的调用线程设置一个超时时间,出现请求异常,超出超时时间,就需要赶紧释放掉服务按对应的线程资源
限流
“雪崩问题”一般发生在高并发场景下,所以可以在服务中增加限流控制,一旦超过阈值就不在处理新的请求
断路器
断路器有三种状态,开启,关闭和半开放
仓壁模式
针对不同的目标设定不同的线程池
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;
分布式锁是一种跨进程,跨机器节点的一种锁
他可以用来来保证多个机器节点对共享资源的排它机制
我觉得他对于线程锁本质上是一样的,线程锁的生命周期是单进程多线程,分布式锁的生命周期是多进程多机器节点
在本质上都需要满足锁的几个基本重要特性:
所以只要满足这些锁的特性的技术组件都能满足分布式锁
1.关系型数据库:
他可以使用到唯一的约束,来实现锁的排他性,如果要针对某个来进行加锁,就可以设置一个包含方法名称的一个字段并且把方法名称设置成唯一的约束
抢占锁的逻辑就变成了往表里去插入一条数据,如果已经有其它的线程获得了某个方法的锁,那么这个时候再去插入这个数据一定会失败,这样就实现了锁的互斥性
虽然这个锁的实现方法看起来很简单,但是实现比较完整的分布式锁还是需要到考虑到重入性,锁的失效机制,没有抢占掉锁的线程阻塞等,都会比较麻烦
2.Redis
通过实现SETNX命令实现锁的排他性,当key不存在就返回1,存在就返回0
然后还可以使用expire命令去设置锁的失效时间,从而避免死锁的问题
当然有可能存在锁过期了,但是业务逻辑还没有执行完成 怎么办呢
所以这种情况下,我们可以写个定时任务对指定的key去进行续期,Redision这个开源组件提供了一个分数锁的封装实现,并且内置了一个叫Watch Dog的机制来对Key做续期
所以我认为Redis这种锁的设计已经能够解决百分之99的问题了
分布式锁应该是一个Cp模型,Redis是一个AP模型,所以在集群架构下,由于数据的一致性问题导致极端情况下出现多线程或者多进程抢占锁的情况很难避免
3.基于CP模型的分布式锁ZooKeeper/etcd
在数据一致性方面,Zookeeper用到了zab协议保证数据的一致性
etcd用到了raft算法保证数据的一致性
在锁的互斥方面,Zookeeper基于有序节点再结合Watch机制来实现互斥和唤醒
etcd可以基于Prefix机制和Wach机制
Redis官方提供了一个叫RedLock的解决办法,实现上会相对复杂
阿里巴巴高性能轻量级java RPC框架
致力于提供高性能,透明化的RPC远程调用调用方案,以及SOA服务治理
Docker是一个快速交付应用,运行应用的技术
1.可以将程序及其依赖,运行环境一起打包为一个镜像,迁移到任意操作系统
2.运行时利用沙盒机制形成隔离容器,各个应用互不干扰
3.启动移除都可以通过一行命令完成,方便快捷
分布式多人协作版本控制系统,是一种文件管理系统,被它管理的文件内容发生任何变化它都能记录下来,我们可以根据这个记录对一个文件溯源与特定的场景操作。版本控制系统不仅可以应用于软件源代码的文本文件,而且可以对任何类型的文件进行版本控制。
Git分为四块区域:工作区,暂存区,本地历史仓库和远程仓库
工作区可以将数据add提交到暂存区
暂存区可以commit提交到本地历史仓库
本地仓库可以push上传到远程仓库
远程仓库fetch/clone到本地仓库
本地仓库reset到暂存区
暂存区checkout到工作区
远程仓库也可以pull到工作区
Git add . 提交当前目录未提交的文件暂存区
Git add * 提交所有位置未提交的文件到暂存区
Git commit -m 注释内容
Git status 查看工作区是否有未提交
Git rm file 删除文件
Git log 查看提交的文件版本信息
git reflog 查看所有的版本信息
git reset head ^ 回退到上一个版本
Git pull remoteprojectname 更新代码库
Git push remoteprojectname master 将本地提交到远程库
分支使用
查看分支:git branch
创建分支:git branch name
切换分支:git checkout name
创建+切换分支:git checkout –b name
合并某分支到当前分支:git merge name
删除分支:git branch –d name
Maven 主要服务于基于 Java 平台的项目构建、依赖管理和项目信息管理。
FreeMarker是一个用Java语言编写的模板引擎。它基于模板来生成文本输出
在我的理解freemarker就是一种工具,它能把jsp页面转换成静态页面,为用户的访问节省时间,同样减少服务器的压力。
每次 HTTP 请求都是独立的,无相关的,默认不需要保存上下文信息的
四元组就是在TCP协议里面去确定一个客户端连接的组成要素,它包括
源IP地址
目标IP地址
源端口号
源端口号每次建立的时候是系统自动分配的
目标端口号
当一个客户端和服务端去建立一个TCP连接的时候,通过原IP地址目标IP地址,源端口号和目标端口号来确定一个唯一的TCO连接,服务器的IP和端口是不变的,只要客户端的IP和端口彼此不同就OK
一个计划性很强
善于与人交谈(适应性很强)
我能从不同的角度看问题,不管面对多大的困难我都能完成我的工作
有责任感
我有时候急于求成,一旦接手一个任务,总是想要尽快把它赶完,总觉得做完了一件事情心里才舒服。但是,欲速则不达,太追求效率,就会牺牲准确度。我现在总是提醒自己准确度第一位,效率第二位,这样会好得多。
首先我刚刚毕业,经验方面不足,不过我会积极完成工作,积累工作经验,其次,我有时候性子可能有点急点,对效率低的人缺乏点耐心,但我会在平时注意控制自己的语速和讲话,培养自己的耐心。
每个人的优缺点不一,但是别人厉害的地方我都会学习,取长补短.毕竟每个人的优势不一样.
然后说自己的优点在哪
我对工资没有硬性要求。我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会,所以只要条件公平,我则不会计较太多
我受过系统的软件编程的训练,不需要进行大量的培训。而且我本人也对编程特别感兴趣。因此,我希望公司能根据我的情况和市场标准的水平,给我合理的薪水
如果你必须自己说出具体数目,请不要说一个宽泛的范围,那样你将只能得到最低限度的数字。最好给出一个具体的数字这样表明你已经对当今的人才市场作了调查,知道像自己这样学历的雇员有什么样的价值。
刚进公司,需要对环境、业务、代码短期内提高熟悉度,会选择适量加班,公司和项目需要加班时,会和大家一起拼命,这是保证项目进度和质量的自我要求。平时都能高效完成任务,一般不需要加班
乐观,有趣,有目标
可以问公司的一些项目情况,晋升制度,员工职业规划,培训机制,或者面试个人表现
企业面试时询问家庭问题不是非要知道求职者家庭的情况,探究隐私,企业不喜欢探究个人隐私,而是要了解家庭背景对求职者的塑造和影响。企业希望听到的重点也在于家庭对求职者的积极影响。企业最喜欢听到的是:
我很爱我的家庭!我的家庭一向很和睦,虽然我的父亲和母亲都是普通人,他们的行动无形中培养了我认真负责的态度和勤劳的精神。他们的一言一行也一直在教导我做人的道理。
多说一些团队向的事情
玩一玩团队竞技的moba游戏,喜欢和朋友一起打打羽毛球,喜欢唱歌,也喜欢查看新闻同时了解前沿技术
说一说三下乡,或者当部长,虽然自己做的一些事很普通但是当自己完成之后,做到了负责,就有感觉到很有成就
坚持良好的作息生活,坚持自律学习,运动等