封包:按照协议将数据封装起来,将一个完整的包发给服务端。
粘包:当存在 发送的数据有的是完整的,有的是不完整的 情况是,会出现粘包现象。
解包:将前端发来的数据包解析出来
心跳包:在间隔相同的时间内,客户端向服务端发送规定好的一个数据包,用来判断客户端与服务端一直保持连接的。
对 Spring 了解吗? Spring 是一个轻量级 Java 开发框架,目的是解决企业级开发中的业务层与其他各层的耦合问题。其核心就是控制反转IOC 和 面向切面编程 AOP。控制反转就是将创建对象的权力交给 Spring,实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给 IOC 容器,实现解耦;AOP 面向切面编程,就是通过配置切面、切点以及通知来减少系统中的重复代码,降低模块间的模块耦合,有利于未来的可扩展性。
Spring AOP 和 Aspect J AOP? Spring AOP 中已经集成了 Aspect J,Spring AOP 是运行时增强,即动态代理,包括 JDK 动态代理 和 CGLib 代理,是在运行时内存中临时生成代理类,也就是在运行时织入切面;Aspect J AOP 是编译时增强,即静态代理,在类加载器中通过字节码转换,将目标织入切点。
Spring MVC 熟悉吗? MVC是一种设计模式,Spring MVC 是一种 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,而且它天生与 Spring 框架集成。Spring MVC 框架下,我们一般把后端项目分为 Service 层用于处理业务,Dao 层用于数据库 *** 作,Beans 实体类和 Controller 控制层,给前台页面返回数据。
Spring MVC 工作原理?1、客户端(浏览器)发送请求,直接请求到 DispatcherServlet;
2、DispatcherServlet 根据请求信息调用 HandlerMapper,解析请求对应的Handler;
3、解析得到对应的 Handler,也就是我们说的 Controller 控制器;
4、HandlerAdapter 会根据 Handler 来调用真正的处理器处理请求并执行相对应的业务逻辑;
5、处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是逻辑上的视图;
6、ViewResolver 会根据逻辑视图查找实际的视图 View;
7、DispatcherServlet 把返回的 Model 传给 View 进行视图渲染;
8、DispatcherServlet 将 View 返回给 客户端(浏览器)。
Spring Boot 相比较 Spring,简化了配置,因为 Spring Boot 提供了自动配置,Spring Boot 的主启动器中,有一个SpringBootApplicaiton 注解,而 SpringBootApplication 中又引入了很多其他注解,其中包括一个叫EnableAutoConfiguration 的注解,这个注解中又通过反射机制导入了 AutoConfigurationimportSelector 这个类,这个类会加载位于 meta-INF 下的 spring.factories 文件,这个文件中就包含许多配置文件,当需要哪个的时候,就会根据其中的 ConditionalOnMissingBean 判断能否引用该配置。
★★★ Redis 缓存(高并发、应用场景)? 缓存,是数据交互的缓冲区,其目的是将读写速度慢的介质的数据保存在读写速度快的介质中,从而提高读写速度,减少时间消耗。日常业务中,常用的数据库就是 MySQL,常用的缓存就是 Redis。Redis 的读写性能比 MySQL 好很多,将 MySQL 的热点数据缓存到 Redis 中,可以提高读取性能,也能减小 MySQL 的读取压力。缓存算法有 LRU(最近很少使用)、LFU(最不经常使用)、FIFO(先进先出)。最简单的 LRU 算法就是基于 linkedHashMap 的,原理是让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。当 map 中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
这个项目中的应用场景就是在消息队列中应用 Redis 缓存技术,把数据添加到队列,减轻数据库访问压力;此外,还可以缓存热数据,就是那些经常被查询、被访问的数据,但是不经常被修改或删除的数据;还有就是秒杀系统,因为 Redis 是单线程的,就会避免某一时刻大量请求涌来导致数据库崩溃的现象。
某个极度热点的数据在某个时刻过期了,但恰好这个时间点有大量的并发请求涌来,这些请求发现缓存过期,会从数据库中加载数据并重新设置缓存,但大量的请求并发会瞬间击垮数据库。
解决方法:
1、使用互斥锁,保证每次只有一个线程去查询数据库并更新到缓存;
2、将过期时间设置在 value 中而非缓存中,当发现要过期,在后台通过异步线程进行缓存的构建。
查询一个不存在的数据,由于该数据不存在,也就不会写入缓存中,导致以后每次访问这个不存在的数据都要到数据库中访问,失去了缓存的意义。
解决方法:
1、缓存空对象,当从数据库查询到的数据为空,仍然缓存,并设置较短的过期时间;
2、使用布隆过滤器 BloomFilter。
缓存由于某些原因挂掉,所有请求全部到达数据库中,导致数据库无法承载访问压力崩溃。
解决方法:
1、本地缓存,即使分布式缓存挂了,可以将数据库查到的结果缓存到本地,避免后续请求全部到达数据库中;
2、数据库限流,通过设置数据库每秒的请求数,避免将数据库打挂掉。
持久化狭义理解,就是把数据保存到数据库中;广义理解,持久化包括与数据库相关的各种 *** 作,将有用的数据以某种技术保存起来,将来可以再次取出来用。Redis 是内存型数据库,为了防止重启、宕机或者机器故障等,需要将内存的数据备份持久化。
Redis 提供了两种持久化方式:
RDB(Redis Database)
RDB 持久化,将某个时间点的所有数据生成快照保存到磁盘上,Redis 启动时会读取该文件重构数据。Redis 默认使用RDB 持久化。
Redis 会单独创建 Fork 一个子进程来进行持久化,先把数据写入到一个临时文件中,待持久化全部完成再用这个临时文件替换上次持久化好的文件。整个过程主进程不进行任何 IO *** 作,以确保主进程性能。RDB 要比 AOF 更加高效,但 RDB 缺点是最后一次持久化后的数据可能会丢失。
Fork 的作用是复制一个与当前进程完全相同的子进程,子进程去做持久化。
AOF(Apend Of File)
AOF 持久化的是 以日志的形式记录每个写 *** 作(增量保存),将所有的写指令记录下来,读取指令不记录,只许 追加不许改写文件。Redis 启动时会读取该文件的重构数据,会读取文件中之前写的指令全部执行一次来完成恢复工作。与 RDB 快照持久化的方式相比,AOF 实时性更好。Redis 默认没有开启 AOF 的持久化,可以通过 appendonly yes 参数开启。如果同时开启两种持久化方式,Redis 会优先选择 AOF 还原数据。
是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量消峰等问题。
生产者,产生消息;
消费者,消费消息;
消息代理,存储和转发消息,包括拉取pull和推送push。
常用场景:应用解耦、异步处理、流量消峰、消息通讯、日志处理。
该项目中用到的场景有流量消峰以及消息通讯。
SpringSecurity 中有很多拦截器,有两个核心的:登陆验证拦截器 AuthenticationProcessingFilter 和 资源管理拦截器 AbstractSecurityInterceptor,但拦截器中的实现需要组件支持,所以有了认证管理器、决策管理器等组件来支撑;FilterChainProxy 是一个代理,真正起作用的是各个 Filter,这些 Filter 作为 Bean 被 Spring 管理,是SpringSecurity 的核心,不直接处理认证和授权,交由认证管理器和决策管理器处理。
1、客户端发起请求,就进入了Security过滤器链;
2、经过一系列过滤器,到达 FilterSecurityInterceptor 时,会拿到请求路径 URL,判断该请求 URL 是否需要验证,如果不需要验证,就直接访问接口 API;如果需要验证,判断当前请求线程中是否有authentication的认证对象,如果有就放行,如果没有就返回登录页面;
3、登录页面输入账号密码,会到达 UsernamePasswordAuthenticationFilter ,经过一系列 *** 作,验证成功就会被认证对象 authenticaiton 放到 securityContext 中,然后 FilterSecurityInterceptor 判断,当前请求线程是否有该认证对象,有就放行;返回的时候最后会通过 securityContextPersistenceFilter 判断当前相乘是否有securityContext,如果有就放进 session,下次再请求这个 URL,会首先在 seurityContextpersistenceFilter 过滤器中判断 session 中是否有 securityContext 对象,有,放到请求线程中,最后经过 FilterSecurityInterceptor 时再判断当前线程是否有认证对象,由于前面经过 securityContextpersistenceFilter,已经把认证对象放进了当前线程中,所以 FilterSecurityInterceptor 会直接放行,访问到我们的接口API。
SpringSecurity 要求容器中必须有 PasswordEncoder 实例,因为客户端输入的密码和数据库中保存的密码是否匹配是由 SpringSecurity 完成的,Security 中还没有默认的密码解析器。所以在自己定义登录逻辑的时候要求必须给容器注入PasswordEncoder的Bean 对象。BCryptPasswordEncoder 是 SpringSecurity 官方推荐的密码解析器,平时也多用该解析器。BCryptPasswordEncoder 是基于 Hash算法 实现的单向加密,可以通过 strength 控制加密强度,默认是10。
Mybatis 框架?为什么引入Mybatis?
JDBC编程,频繁的创建、连接、释放数据库连接对象,容易造成资源浪费,形象系统性能,就引入了连接池,而连接池可以在 mybatis-config.xml 中配置;JDBC 编程,sql 语句写在代码中,如果需要改变 sql 语句就要改动 Java代码,造成代码不易维护,而使用 mybatis 可以将 sql 语句配置在不同的 mapper.xml 文件中,与 Java代码 分离;mybatis 自动将sql 执行结果映射到 Java对象。
步骤:
1、创建 SqlSessionFactory 对象;
2、通过 SqlSessionFactory 获取 SqlSession 对象;
3、通过 SqlSession 获取Mapper代理对象;
4、通过Mapper代理对象,执行数据库 *** 作;
5、执行成功,使用 SqlSession 提交事务;执行失败,使用 SqlSession 回滚事务;
6、结束会话。
Mybatis 的动态 sql?
可以让我们在 XML 映射文件中,以 XML 标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
Mybatis 的几种动态 sql 标签:
、、、、等。
Mybatis 动态 sql 原理?
使用 OGNL 表达式,从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql。
Mybatis 中 # 和 $ 的区别?
二者都是在 Sql 中动态的传入参数。
#{}:解析为一个 JDBC 预编译语句的参数标记符,一个 #{} 被解析为一个参数占位符
${}:仅仅为一个纯粹的 String 替换,在动态 Sql 解析阶段将会进行变量替换,无法解决 Sql 注入问题。
Mybatis 中的批量存储?
如果结合了 SpringBoot 框架,就在 xml 文件中利用 动态 sql 标签进行批量处理;
如果是单纯 mybatis 框架,使用 BatchExecutor 完成批处理。
Mybatis 中的缓存?
Mybatis 中的缓存就是 Mybatis 在执行一次 sql 查询 或 sql 更新之后,这条 sql 语句并不会消失,而是被 Mybatis 缓存起来,当再次执行相同 sql 语句的时候,就会直接从缓存中进行提取,而不是再次 执行 sql 命令。
Mybatis 中的一级缓存?
一级缓存是 SqlSession 级别的缓存。在 *** 作数据库时需要构造 SqlSession 对象,在对象中有一个内存区域(HashMap)用于存储数据。SqlSession 之间的缓存数据区域(HashMap)是互相不影响的。如果是相同的 sql 语句,会优先命中一级缓存,避免直接对数据库进行 *** 作,提高性能。
Mybatis 中的二级缓存?
一级缓存最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 需要共享缓存,需要开启二级缓存,开启二级缓存后,会使用 Caching Executor 装饰 Executor ,进入一级缓存的查询流程前,先在 Caching Executor 进行二级缓存的查询。开启二级缓存后,同一个命名空间(namespace)所有的 *** 作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局变量。查询流程:二级缓存 —> 一级缓存 —> 数据库。二级缓存默认是不开启的,开启也比较简单,在 Mybatis 配置文件中 即可。
A 原子性 Atomicity:要么全做,要么全不做;
C 一致性 Consistency:一个事务在执行前后,数据库都必须处于正确的状态;
I 隔离性 Isolation:多个事务并发执行时,一个事务的执行不会影响其他事务的执行;
D 持久性 Durability:事务处理完成后,对数据的修改是永久的,即便系统故障也不会丢失。
NetFlix 发布的中间层服务开源项目,为客户端提供负载均衡服务。内部提供了一个叫做 ILoadBalance 的接口代表负载均衡器的 *** 作。
负载均衡算法:随机算法,Random随机;轮询及加权轮询,轮询比率;哈希算法等。
声明式 WebService 客户端,让微服务架构中服务与服务之间的调用更简单。与 Ribbon 类似,都是在客户端使用的负载均衡工具。Feign 默认集成了 Ribbon。没用 Feign 之前,使用 Ribbon+RestTemplate 实现服务与服务之间的远程通信,但实际开发中,往往一个接口会被多处调用,所以通常会针对每个微服务封装一个客户端来包装这些依赖服务的接口。使用 Feign 之后,只需要创建一个接口并用注解的方式在微服务接口上标注一个 Feign 注解即可,优雅而简单的实现了服务与服务之间的远程通信。
Nacos? 阿里巴巴开发的配置中心和注册中心。在项目中接触了注册中心的概念,注册中心是微服务架构中的“通讯录”,记录了服务和服务地址的映射关系。在分布式架构中,服务注册到注册中心,当服务需要调用其他服务时,就到注册中心中对应服务的服务地址进行调用。
接口和抽象类?抽象类,被 abstract 修饰的类。
抽象类不能创建实例对象,含有抽象方法的类必须定义为抽象类,但抽象类中的方法不一定是抽象方法。
如果子类没有实现抽象父类中的所有抽象方法,则子类必须声明为抽象类型。
接口,抽象类的一种特例,接口中的所有的方法都必须是抽象的。
两者对比:
抽象类可以有构造方法,接口中不能有构造方法;
抽象类中可以有普通成员变量,接口中没有普通成员变量;
抽象类可以含有非抽象的犯法,接口中的所有方法必须都是抽象的;
抽象类中可以包含静态方法,接口不能包含静态方法;
一个类可以实现多个接口,但只能继承一个抽象类。
switch(xxx)中的“xxx”只能是一个整数表达式或枚举常量,整数表达式可以是 Int 基本类型或 Integer 包装类型,所以 byte、short、char 这些可以隐式转换为 Int 的数据类型以及它们的包装类型都可以写在括号里。
long不符合 switch 语法规定,也不能隐式转换为Int,不能用于 switch 语句中。
顶层接口 Collection,子类包括 List、Set、队列
List 中有 ArrayList、linkedList 等。
List 是有序的,可存放重复元素的集合
Set 包括 HashSet、TreeSet、linkedSet 等。
Set 是无序的,内部不允许重复元素的集合。
还有一种集合是 Map,包括 HashMap、TreeMap 等。
Map 集合是由键值对结构组成,通畅由一个唯一键对应一个值,集合中键不允许重复,值可以重复。
HashMap 原理就是数组 + 链表。
采用数组存储数据,查询快,插入和删除困难;
采用链表存储数据,查询慢,插入和删除比较容易;
HashMap是由数组和链表组成的,又叫链表散列。满足:
快速存储(get()和put()方法);
快速查找(通过 key 去 get 一个value 时间复杂度非常低,效率非常高);
可伸缩(数组扩容;JDK 1.8单向链表如果长度超过8会演变成红黑树)
HashMap 默认负载因子是 0.75,也就是 75%,HashMap 初始长度默认是16,阈值 = 当前长度 * 0.75,当当前长度大于或等于阈值时会进行自动扩容。
HashMap 中的 hash 算法?
key.hashCode() 与 key.hashCode>>>16(右移 16 位) 进行异或运算。
Hash 冲突?
就是键 key,经过 hash 得到的结果之后去存 key-value 键值对的时候,发现该地址已经存了值 value,就发生了冲突,即 hash 冲突。
为什么是 0.75?
如果过大,加入是 1,也就是 HashMap 的容量必须要全部装满才允许扩容,但如果 HashMap 容量全部装满,就会产生大量 Hash 冲突,put、get *** 作效率会下降;如果过小,假如 0.5,虽然减少了 Hash 冲突,但有一半的空间没有利用,造成空间利用率低下。至于 0.75,也并不是随便产生的,根据牛顿二项式,计算得到 0.693,选用 0.75(即, 3/4)为了使算出来的容量是整数。每次扩容都是 2 的整数次幂,跟 3/4 计算保证得到整数。
为什么初始容量是16?
看 HashMap 源码可知道,当新 put 一个数据时,会计算其位于 table 数组,也就是桶中的下标:int index = key.hashCode&(length-1),因为是将二进制进行按位与运算, 16 - 1 = 15,二进制 1111,末位是 1,能保证计算后的下标既可以是奇数也可以是偶数,就会减少重复,减少 Hash 碰撞。理论上只要是二的整数次幂都可以,但如果是 4或者 8,很容易导致 map 扩容影响性能,太大又会浪费资源。
TreeMap 是 Map 集合针对排序的一个实现类,底层基于 红黑树(平衡二叉树:要么是空树;要么,左子树和右子树的高度差的绝对值不超过 1,而且左子树和右子树都是一颗平衡二叉树)实现;内部元素的存储顺序由键对应的类型实现Comparable 接口后通过重写 comparaTo() 方法实现的。
TreeMap 的使用要满足:
1、key的类型必须一致;
2、key对应的类必须实现 Comparable 接口。
“脏读”
读到了别的事务回滚前的脏数据。
比如,事务B 执行过程中修改了 数据X,在未提交前,事务A 读取了 数据X,而 事务B 却回滚了,则 事务A 就形成了“脏读”
“不可重复读”
当前事务先进行了一次数据读取,然后再次读取到的数据是别的事物修改成功的数据,导致两次读取到的数据不匹配。
比如,事务A 读取了一条数据,执行逻辑的时候,事务B 将这条数据改变了,然后 事务A 再次读取的时候,发现数据不匹配了,即所谓的“不可重复读”。
“幻读”
当前事务读第一次取到的数据比后来读取到数据的条目少。
比如,事务A 首先根据条件索引得到 N 条数据,然后 事务B 改变了这 N 条数据之外的 M 条 或者 增加了 M 条符合 事务A搜索条件的数据,导致 事务A 再次搜索时发现有 N + M 条数据了,就产生了“幻读”。
“不可重复读” 和 “幻读” 比较:
二者有些相似,但“不可重复读”针对的是 更新(Update) 和 删除(Delete);“幻读”针对的是 插入(Insert)。
从高到底分别为:
Serializable,串行化;
数据库最高隔离级别,事务“串行化执行”,即一个一个排队执行。“脏读”、“不可重复读”、“幻读”都可以避免。
但执行效率极差,性能开销也极大,基本没人用。
Repeatable Read,可重复读;
专门针对“不可重复读”情况而制定的隔离级别,可以有效避免“不可重复读”,也是 MySql 默认隔离级别。
查询仍然使用“快照读”,但,与“读提交”不同的是,当事务启动时,就不允许进行 “修改 *** 作(update)”了,而“不可重复读”问题正是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效避免“不可重复读”问题,但不能避免“幻读”问题,因为“幻读”问题是由于 “插入或删除 *** 作(Insert or Delete)” 产生的。
Read Commited,读提交;
读提交,只能读到已经提交了的内容。是 SQL Server 和 Oracle 的默认隔离级别。
保证了一个事务不会读到另一个并行事务已经修改但还未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读”问题。
为什么“读提交”和“读未提交”都没有查询加锁,但都避免了“脏读”?
“快照(Snapshot)”机制:既能保证一致性又不加锁的读称为 “快照读(Snapshot Read)”
Red Uncommited,读未提交
读未提交,可以读到还未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,正因为查询不加锁,这种隔离级别的一致性是最差的,可能产生“幻读”,“脏读”,“不可重复读”问题。一般不会用这种隔离级别。
单例模式:
某个类的实例在多线程环境下只会被创建一次。
单例模式有饿汉式单例模式和懒汉式单例模式:
饿汉式:线程安全,一开始就初始化创建对象;
懒汉式:线程不安全的,延迟初始化,创建 getIntance() 方法。
简单工厂模式
由一个工厂对象决定创建出哪一种产品类的实例。
让对象的调用者和对象的创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。
工厂方法(Factory Method)和抽象工厂(Abstract Factory)
使用简单工厂类,需要在工厂类中做逻辑判断;
工厂类虽然不在工厂类做判断,但带来了客户端代码与不同工厂类耦合。为解决耦合问题,在工厂类基础上再再增加一个工厂类,该工厂类不制造具体的被调用对象,而是创建不同工厂对象。
代理模式(Proxy)
客户端需要调用某个对象的时候,不关心是否准确的到该对象,只要一个能提供该功能的对象,返回该对象的代理(Proxy)。Spring 框架中的 面向切面(AOP) 就是通过代理模式,不过它是运行时增强,是动态代理,包括 JDK 动态代理和CGLib动态代理,在运行时内存中临时生成代理类,即在运行时织入切面。
线程是资源调度的基本单位。
线程的创建和启动?
方式一:继承 Thread 类
继承 Thread 类;重写 run() 方法;在 main() 方法中创建线程,调用对象的 start() 方法启动线程,如果调用的是 sun() 方法,仍然只有一个线程,并未开启新线程。
方式二:实现 Runnable 接口
实现 Runnable 接口;重写 run() 方法;在 main() 方法中创建线程、启动线程。
锁是怎么形成的?
例如,单线程下,执行 add() 方法进行数字递增,调用10次,结果就是10;但如果在多线程下,例 线程A 调用 add() 方法10次期间,线程B 也调用 add() 方法,最后会发现 线程A 的执行结果大于10,线程是不安全的。
要想线程安全,就要保证多线程间的同步 *** 作,需要给对象关联一个互斥体,即锁。锁的作用就是保证同一竞争资源在同一时刻只会有一个线程占用。
锁的实现方式?
一、synchronized 关键字
二、并发包中的锁类
死锁?
线程之间相互等着对方释放资源,而自己的资源又不释放给别人,这种情况就是死锁。只要其中一个线程释放了资源,就可以解除死锁。
1、减少数据访问
1.1 创建并使用正确的索引,主键及外键通常都要有索引
2、返回更少的数据
2.1 数据分页处理
2.2 只返回需要的字段
3、减少交互次数
3.1 batch DML,采用 batch *** 作一般不会减少很多数据库服务器的物理 IO,但会大大减少客户端与服务端的交互次数。
3.2 In List,一般 In 里面的值个数超过 20 个以后性能基本没什么太大变化,特别说明不要超过 100。
3.3 设置 Fetch Size
4、减少数据库服务器 CPU 运算
5、利用更多的资源
Sql 性能优化:
尽量使用列名代替 *;注意 where 语句的解析顺序;尽量使用 where 代替 having;尽量使用多表查询代替子查询等。
1、普通索引:最基本的索引,没有任何限制。
2、唯一索引:索引列的值必须要唯一,但允许有空值;如果是组合索引,列值的组合必须唯一。
3、主键索引:特殊的唯一索引,一个表只能有一个主键,不允许有空值。
4、组合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。
5、全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值比较。更像是一个搜索引擎,配合 match against *** 作使用。
delete 是 DML *** 作,每次从表中删除一行,可以配合 where 使用;
drop 是 DDL *** 作,会隐式提交,删除表结构及所有数据,删除表的结构所依赖的约束;
truncate 是 DDL *** 作,
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)