自我介绍
面试官你好,我叫韦文霞,我是即将毕业桂林信息科技学院,目前掌握Java基础以及JavaWeb知识,熟悉Spring、SpringBoot、SpringCloud、Mybatis、MybatisPlus开发框架,接近近期做的项目是每日菜谱大全使用SpringCloud微服务技术架构来实现的,这个项目主要针对用户学每日菜谱大全app,用户可以根据自己的需求进行不同种类的学习,列如西餐,烘焙等等。用户不仅可以学习做菜,还可以发布一些自己的菜谱,促进交流。主要分为app端,自媒体端,平台管理端。其中主要核心业务是内容端用户菜谱的图文上传和审核功能。其中运用到了springboot,mq,es,redis,mysql等技术,以上是我的自我介绍。
菜谱的发布
因用户程序 并将投票英勇的程序进行统计和计算 用户打开应用程序要求用户更新他本人的用户排名
1和2 的结果应用可视化来实现
app当中收费和免费的内容进行区分
对收费内容做一个模糊处理 应用程序内嵌入系统使用微信或者支付宝都可以
用户在学习烹饪菜谱发布文章时可以添加文章的标题,文章正文(文本和图片),然后选择文章分类去进行一个发布。然后发布的时候需要选择封面的布局方式,我们的布局方式有三种分别单图布局、三图布局、和无图布局。然后还有一种自动布局,就是把正文中的图片提取出来,如果是1~2张图片的话,就去截取第一张作为文章的封面图片。如果图片是超过三张的,就是截取前三张作为文章的封面图片,如果是没有图片的话,我们这边给它设置一个默认图片,也就是logo。编辑好这些信息之后我们可以选择是立即发布还是定时发布,我们是通过RabbitMQ用ttl加死信队列来实现的,设置一个时间节点,到了这个时间节点就会自动去发布。发布之前是要对文章进行审核的。
1、配置正常队列和正常的交换机,并指定RoutingKey
2、在正常队列配置绑定死信交换机和死信队列及消息过期时间
3、消费者监听死信队列进行消费 以此来完成延迟队列的功能 生产者在发送消息时指定消费的过期时间即可【发布时间-当前时间】
文章的审核
文章审核的时候做了两个审核,分为自动审核和人工审核。 当用户提交发布文章之后,会异步的发消息给rabbitmq提交审核,平台管理端审核微服务提供消息监听(接受文章信息),处理自动审核。 首先远程调用fegin接口查询文章,文章的状态必须是待审核的状态才能进行三阶段审核(文本内容审核和图片审核封装了对应的工具类)
(1)一阶段审核我们在本地维护一个敏感词词库,然后用DFA(确定有穷自动机)技术,一次性的把所有的敏感词存储到了多个map中,在审核时,去判断文章中是否包含这些敏感词。
1.一个词中第一个字在hashMap中存在的话。得到一个新的map = hashMap.get("")。
2.如果map == null,则不是敏感词。不为空的话进行下一步。
3.获取map中的isEnd标识,通过isEnd是否等于1来判断该词是否为最后一个。如果isEnd == 1表示该词为敏感词,更新敏感词的长度,否则跳至1。 DFA算法,去匹配文章里面的内容。 不用我们分词,有穷自动机自己会根据规则去匹配。 底层使用的是一个Map存储结构,把敏感词的每个词都当作key,然后挨个去匹配。如果匹配上了,会判断一个isEnd属性是否为0,如果为0,则表示敏感词没结束,如果1,就表示结束了,就证明这是一个敏感词。 第三方的。 RabbitMQ原生的 @RabbitListener(bindings=里面有队列、交换机、路由key)
(2)二阶段审核进行阿里云内容安全的文本审核,如果是pass代表审核成功,如果是block代表有敏感词,文章审核失败,如果是review代表审核不确定,就需要交给后台管理端进行人工审核,管理员可以查看文章内容,如果没有敏感词,修改文章状态为人工审核通过
(3)三阶段审核进行阿里云内容安全的图片审核,考虑到图片上也可能存在敏感词,那么我们就采用了OCR光学识别技术把图片中的文字提取之后,先进行一阶段审核,再进行二阶段审核
(4)如果三阶段审核通过了,判断发布时间是否小于等于当前时间,如果小于等于直接发消息通知文章微服务去发布文章。如果未到发布时间,将消息发送到RabbitMQ的死信队列,并 设置消息失效时间。然后可以远程调用fegin接口保存或者修改文章,然后通过mq异步调用文章详情页,这个详情页我们用了freemarker模板去生成的,然后和图片一起保存到minio服务器上,再将他们的url存到数据库中。当初在测试阶段,我们是用了阿里云oss来存储,考虑到开发成本,我们经过商讨,就选择了minio服务器存储,它是一个开源的,配置相对简单,开箱就可以使用,而且对于我们业务的数据的存储来说也是没问题的。
用户端文章搜索
在用户端上方的搜索框中,用户可以输入关键字来搜索文章。我们使用的是es搜索引擎进行全文搜索,它会根据用户输入的关键字通过分词查找文章列表。将文章相关的数据存储到es中的好处是,es的查询效率比直接查询数据库要快。搜索时,我们会查询es并按照发布时间的倒序排列来返回结果。
5.后面还有个热点文章查询我们是这么做的,通过用户行为(评论、点赞、收藏)的权重比例来计算文章分值,按照分值来给文章排序,当时我们也是经过讨论后面是按点赞是乘3、评论是乘5、收藏是乘8这个比例计算的。使用xxl-job定时任务每天完成一次文章分值的计算,把分值较大的文章数据存入到redis中,用户端用户查询文章列表的时候,优先从redis中查询热度较高的文章数据,这样也能保证数据的查询性能。
6.用户行为产生的数据我们用的是mongoDB,因为mongoDB具有高读写性能、高可扩展性和高可用性等特点,适合存储海量数据和支持高并发访问。Redis的内存比较小,里面主要储标记性的数据,如是否点赞、是否收藏等。而且在评论功能中,数据的安全性要求相对较低,可以容忍一定程度的数据丢失
难点
因为帖子表的内容字段就是长文本,在高并发的时候出现了内存溢出的问题,然后我是这么解决的:我把帖子表就分成了主表和内容表,然后在展示帖子的时候就不查内容,只查询帖子主表,就解决了这个问题。,为了进一步提升查询帖子的效率,我就把帖子的那些内容 : 就是长文本的,用静态化模板引擎技术freemarker生成了一个静态网页,然后传到了minio之中, 然后就把返回来的路径传到新闻表之中。这个时候查询帖子详细页就不用到数据库去查,就直接去minio上面查,就减轻了数据库的压力
遇到的问题
一、在这个项目中,我做的这个文章审核模块,流程有点长,刚开始一次请求可能时间很长,因为中间要执行好几步骤,而阿里那边的接口,这与网络也有关系,图片过大也导致时间长,有时候图片会有十来张,一起打包过去就会很慢
后来进行了优化,阿里那边请求达到3秒就算超时了,然后我们就限制了文章内容和图片数量,同时做了异步处理,利用多线程进行分批审核,分批传送到阿里的接口;这里用的@Async注解
二、在一些极端的情况下,自媒体人会在5分钟的间隔内点击分布,并且想要在这5分钟之内审核通过并定时发布,比如:扫描间隔是10-15分钟之内,自媒体人在11发布,并且想要在13分钟的时候定时发布,这个时候就会扫描不到。后来我在用户提交审核的时候做了个判断,看他定时发布的时间和点击发布的时间是否小于5,小于5的话就给他弄一个新的redis对队列。
之前动态的发布和审核都说过了,今天讲一下后期的功能优化,比如可能出现这样的问题:自媒体微服务 调用动态微服务是通过Feign来实现的,如果动态微服务出问题了,自媒体微服务也会跟着出现问题,然后就会一直卡着,后面操作也无法进行,这个就是第一个服务雪崩的问题,解决方案就是就是使用一个==熔断器的组件hystrix==来实现服务降级,具体操作就是在动态微服务的feign接口创建一个服务降级的实现类,接口指定上这个个实现类,自媒体那边开启降级的扫描,并且在配置中心开启熔断器的设置,
后面还有一个问题就是审核的时间过长会导致反应很慢,因为都是在一个微服务,所以用的是线程池实现异步的方法,大大提高了接口访问速度,在自动审核的接口加上@async注解,启动类加上@enableasync注解就可以了
优势
我认为我的优势在于良好的沟通能力和团队合作精神。我善于倾听他人的意见和建议,能够协调各部门之间的关系,以达成项目的最终目标。此外,我在学习新知识和适应新环境方面也有较强的能力,能够快速掌握Java编程语言和相关技术,并在实际项目中应用 。
缺点
不大气。不大气与性格和经历有关,不大气有很多理解,我更倾向于是不自信,可能是我过于拘谨的性格和经济比较拮据的经历,在为人处事方面,曾经有人提示我不大气,放不开,我也意识到是一个很大的缺陷,不仅要了解更多处事规则,更要大气。
用友总结实习面试
数组和List转换的方法?
使用Arrays.asList()方法:
数组转List:可以使用Arrays.asList()方法将数组转换为List。例如,将一个String数组转换为List:
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array)List转数组:虽然Arrays.asList()方法返回的List是固定大小的,不能添加或删除元素,但可以通过将其包装在一个ArrayList中来实现动态扩展:
List<String> list = Arrays.asList(array);
List<String> dynamicList = new ArrayList<>(list);使用List.toArray()方法:
List转数组:可以使用List.toArray()方法将List转换为数组。例如,将一个String List转换为String数组:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
String[] array = list.toArray(new String[list.size()]);数组转List:可以通过创建一个指定类型的数组,然后使用Arrays.asList()方法将数组转换为List。例如,将一个Integer数组转换为List:
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);使用Stream API:
List转数组:可以使用Stream API的toArray()方法将List转换为数组。例如,将一个String List转换为String数组:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
String[] array = list.stream().toArray(String[]::new);数组转List:可以通过Stream API的Stream.of()方法将数组转换为List。例如,将一个Integer数组转换为List:
Integer[] array = {1, 2, 3};
List<Integer> list = Stream.of(array).collect(Collectors.toList());List和set的区别?
**List和Set的主要区别在于它们的存储特性、数据结构和使用场景** 。
存储特性
List:List是一个有序的集合,元素按照插入顺序排列,允许存储重复的元素。通过索引可以访问、修改和删除元素12。
Set:Set是一个无序的集合,不保证元素的插入顺序,且不允许存储重复的元素。如果尝试添加重复元素,操作会被忽略12。
数据结构
List:常见的实现类包括ArrayList、LinkedList和Vector。ArrayList基于动态数组,适合频繁读取元素的场景;LinkedList基于双向链表,适合频繁插入和删除操作的场景;Vector是一个线程安全的List实现。
Set:常见的实现类包括HashSet、LinkedHashSet和TreeSet。HashSet基于哈希表,提供快速的查找和插入操作;LinkedHashSet保持插入顺序;TreeSet保证元素的自然顺序或自定义顺序。
使用场景
List:适用于需要保持元素顺序且允许重复元素的场景,如存储列表数据、需要随机访问元素的场景
http的状态码有哪些?
HTTP状态码主要分为五大类:1xx(信息性状态码)、2xx(成功状态码)、3xx(重定向状态码)、4xx(客户端错误状态码)和5xx(服务器错误状态码)
1xx(信息性状态码)
这些状态码表示服务器已收到请求,但还需要进一步处理。常见的有:
100 Continue:请求的初始部分已被接受,客户端应继续发送其余部分。
101 Switching Protocols:服务器根据客户端的要求正在切换协议14。
2xx(成功状态码)
这些状态码表示请求已成功被服务器处理。常见的有:
200 OK:请求成功。
- 201 Created:资源已创建。
- 202 Accepted:请求已被接受,但尚未处理。
- 204 No Content:服务器成功处理了请求,但没有返回任何内容12。
3xx(重定向状态码)
这些状态码表示需要客户端进一步操作才能完成请求。常见的有:
- 301 Moved Permanently:资源已永久移动到新位置。
- 302 Found:资源临时移动到另一个位置。
- 304 Not Modified:资源自上次请求后没有更改,无需重新获取12。
4xx(客户端错误状态码)
这些状态码表示客户端的请求有误。常见的有:
- 400 Bad Request:请求格式不正确。
- 401 Unauthorized:需要身份验证。
- 403 Forbidden:服务器拒绝请求。
- 404 Not Found:服务器找不到请求的资源12。
5xx(服务器错误状态码)
这些状态码表示服务器在处理请求时遇到错误。常见的有:
- 500 Internal Server Error:服务器内部错误。
- 503 Service Unavailable:服务不可用
get请求和post请求的区别,安全性?
1、数据传输方式:GET请求的数据是通过URL传递的,数据量较小,一般不超过2KB,而POST请求的数据是通过HTTP请求体传递的,数据量较大,一般不限制大小。
2、安全性:GET请求的数据会被添加到URL中,所以数据很容易被第三方获取,不够安全;而POST请求的数据不会被添加到URL中,所以数据比GET请求更加安全。
3、幂等性:GET请求具有幂等性,也就是说,对于相同的请求,服务器的响应是相同的;而POST请求不具备幂等性,即对于相同的请求,服务器的响应可能会不同。
4、缓存:GET请求会被浏览器缓存,所以对于相同的请求,浏览器会直接从缓存中获取数据,从而提高响应速度;而POST请求不会被缓存。
5、使用场景:GET请求适合用于获取数据,如查找、浏览等操作;而POST请求适合用于提交数据,如注册、登录等操作。
session、token、cookie的区别?
**Session、Token和Cookie在功能、存储位置、安全性等方面存在显著区别**
功能与存储的位置:
- Session:Session是一种服务器端的状态管理机制,用于跟踪和存储用户在Web应用程序中的状态信息。Session数据存储在服务器上,每个Session有一个唯一的ID,通常通过Cookie或URL参数传递给客户端。
- Token:Token是一种代表用户身份和权限的数据结构,通常是一串加密的字符串。Token不存储在服务器上,每次客户端请求时携带Token,服务器通过解密和验证Token来确认用户身份。
- Cookie:Cookie是一种存储在客户端的小型文本文件,用于在用户浏览器和Web服务器之间传递数据。服务器通过HTTP响应的Set-Cookie头部将Cookie发送到客户端,客户端在后续请求中通过Cookie头部发送回服务器
- 安全性与隐私保护
- Session:由于Session数据存储在服务器上,安全性较高。但单个服务器处理过多请求时,可能会增加服务器负担,尤其是在分布式系统中,Session管理较为复杂。
- Token:Token通常包含加密信息,服务器不需要存储任何关于令牌本身的信息,因此安全性较高且适用于分布式系统。Token的缺点是每次请求都需要验证,可能会增加处理时间。
- Cookie:Cookie存储在客户端,容易被篡改,安全性较低。此外,每个域的Cookie数量有限制,且浏览器对Cookie的大小和数量有限制
适用场景:
- Session:适用于需要大量数据处理和个性化服务的场景,如在线购物车、用户登录状态管理等。
- Token:适用于需要高安全性和可扩展性的场景,如API认证、微服务架构等
依赖注入的方式?
依赖注入是Spring框架的核心特性之一,主要用于减少组件之间的耦合性,通过外部容器管理对象的创建和依赖 关系。依赖注入的实现方式主要有三种:属性注入、构造器注入和字段注入。
- 属性注入:这是最常用的依赖注入方式。通过setter方法注入Bean的属性值或依赖对象。属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。
- 构造器注入:通过构造方法注入依赖对象。这种方式要求在构造方法中指定所有必要的依赖项。构造器注入的优点是创建对象时必须指定所有参数,确保对象创建后其成员变量一定有值,缺点是需要指定全部参数。
- 字段注入:使用注解方式直接在字段上注入依赖对象。这种方式相对较少使用,主要用于注解方式实现的依赖注入。
依赖注入的好处
依赖注入的主要好处包括:
- 减少代码耦合:通过外部容器管理依赖关系,减少了组件之间的直接依赖,提高了代码的可维护性和可测试性。
- 提高灵活性:依赖关系由外部容器管理,使得对象之间的依赖关系更加灵活,易于调整和修改。
- 便于测试:依赖关系由外部容器管理,使得对象的创建和依赖关系在测试环境中更容易模拟和控制。
IOC的底层是怎么实现的?
IOC(Inversion of Control,控制反转)的底层实现主要依赖于以下几个关键技术:反射、依赖注入、容器和配置文件(或注解)。
反射
IoC容器通常使用Java的反射机制来动态地实例化对象、调用方法和设置字段值。通过反射,IoC容器可以在运行时动态地获取类的信息并实例化、配置对象,从而减少代码耦合度。
依赖注入
依赖注入(Dependency Injection,DI)是IoC的核心机制之一。它通过构造函数、Setter方法或字段注入等方式,将对象所需的依赖关系注入到对象中。IoC容器负责创建对象实例,并自动注入所需的依赖,从而实现对象之间的解耦。
容器
容器(Container)是IoC的核心,负责创建和管理对象。在Spring框架中,容器通过读取配置文件或注解来创建对象,并将其存储在内部的一个Map结构中,以供程序在需要时获取。
配置文件(或注解)
配置文件(如XML、JSON)或注解是容器创建和管理对象的依据。在Spring框架中,可以使用XML配置文件或注解来定义对象的创建方式、依赖关系等。例如,通过<bean>标签定义对象,并通过<property>标签设置依赖关系。
具体实现步骤
- 初始化IoC容器:通过读取配置文件(如
applicationContext.xml),创建IoC容器实例。 - 创建Bean:在容器中定义一个工厂类,该类包含一个创建Bean的方法。该方法通过反射加载类并创建对象实例。
- 依赖注入:容器根据配置文件或注解中的信息,将依赖注入到对象中。
- 获取Bean:当需要使用某个Bean时,从容器中获取该实例并进行使用
Spring Boot配置文件有哪几种格式?
**[application.properties](https://www.baidu.com/s?rsv_dl=re_dqa_generate&sa=re_dqa_generate&wd=application.properties&rsv_pq=a2dad5d200685044&oq=Spring Boot配置文件有哪几种格式%3F&rsv_t=846c/nayRhyRW20GS7ksjtLkrSD1vTQPBOW5uzAmATAr6kV8k7dWOeTuNjd1gwtj1xbx&tn=baiduhome_pg&ie=utf-8)**:这是一种键值对类型的配置文件,格式简单明了,易于理解和编辑。例如:
server.port=9090**[application.yml](https://www.baidu.com/s?rsv_dl=re_dqa_generate&sa=re_dqa_generate&wd=application.yml&rsv_pq=a2dad5d200685044&oq=Spring Boot配置文件有哪几种格式%3F&rsv_t=846c/nayRhyRW20GS7ksjtLkrSD1vTQPBOW5uzAmATAr6kV8k7dWOeTuNjd1gwtj1xbx&tn=baiduhome_pg&ie=utf-8)(或[application.yaml](https://www.baidu.com/s?rsv_dl=re_dqa_generate&sa=re_dqa_generate&wd=application.yaml&rsv_pq=a2dad5d200685044&oq=Spring Boot配置文件有哪几种格式%3F&rsv_t=846c/nayRhyRW20GS7ksjtLkrSD1vTQPBOW5uzAmATAr6kV8k7dWOeTuNjd1gwtj1xbx&tn=baiduhome_pg&ie=utf-8))**:这是YAML格式的配置文件,YAML是一种直观的数据序列化格式,支持复杂的数据结构。YAML文件以数据为核心,比XML更加简洁。例如:
server:
port: 9090- YAML文件可以使用
.yml或.yaml作为扩展名,通常使用.yml。在Spring Boot中,.yml和.yaml的使用方式相同,但一般推荐使用.yml12。
配置文件优先级
在Spring Boot中,配置文件的优先级如下:
- application.properties:最高优先级
- application.yml:次之
- application.yaml:最低优先级3。
YAML文件的基本语法和特点
- 缩进表示层级关系:YAML文件使用缩进来表示数据的层级关系,通常使用空格进行缩进,不允许使用Tab键。相同层级的元素左侧对齐即可,缩进的空格数量不重要3。
- 字符串处理:字符串可以不用引号或使用单引号。单引号中的特殊字符不会被转义,双引号中的特殊字符会被转义
SpringBoot怎么去自定义端口?
在Spring Boot中,您可以通过以下几种方式来自定义应用的端口:
在application.properties或application.yml配置文件中设置端口:
Properties
# application.properties
server.port=8081或者使用YAML格式:
Yaml
server:
port: 8081在启动类中通过编程方式设置端口:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setDefaultProperties(Collections
.singletonMap("server.port", "8081"));
app.run(args);
}
}在运行应用时通过命令行参数来指定端口:
java -jar target/myapplication-0.0.1-SNAPSHOT.jar --server.port=8081如果你使用Spring Boot Maven或Gradle插件,可以在pom.xml或build.gradle文件中设置:
Maven (pom.xml):
<properties>
<server.port>8081</server.port>
</properties>Gradle (build.gradle):
ext {
serverPort = 8081
}SpringBoot是怎么实现自动装配的?
SpringBoot实现自动装配的核心是@EnableAutoConfiguration注解和@SpringBootApplication元注解,它们都是在启动类上使用的。@EnableAutoConfiguration注解告诉SpringBoot应用启动时,尝试自动配置Spring容器。
@SpringBootApplication是一个组合注解,包含@EnableAutoConfiguration,还包括@ComponentScan和@Configuration。@ComponentScan会扫描指定包路径下的所有使用@Component,@Service,@Repository,@Controller等注解的类,并注册为Spring的bean。
MySql索引的优点和缺点?
MySQL索引的优点:
-
提高数据检索速度。
-
减少磁盘I/O。
-
加快表与表之间的连接。
-
使得分组和排序操作更快。
-
保证数据的唯一性。
MySQL索引的缺点:
- 索引会占据磁盘空间。
- 更新表时,索引也需要更新,这会降低数据更新操作的速度。
- 在插入和删除记录时,索引需要动态调整,降低了数据的插入和删除操作的速度。
- 复合索引需要更多的时间去优化和使用,不总是优于单一索引。
项目中的redis是怎么用的?
在Java中使用Redis,你可以使用Jedis库,这是一个流行的Redis客户端。以下是一个简单的例子,展示了如何在Java项目中使用Redis:
首先,添加Jedis依赖到你的项目中。如果你使用Maven,可以在pom.xml中添加如
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>最新版本号</version>
</dependency>
然后,你可以使用以下代码来连接Redis并执行一些基本操作:
import redis.clients.jedis.Jedis;
public class RedisExample {
public static void main(String[] args) {
// 连接到Redis服务器,这里假设Redis运行在本地并使用默认端口6379
Jedis jedis = new Jedis("localhost", 6379);
// 设置键值对
jedis.set("key", "value");
// 获取键对应的值
String value = jedis.get("key");
System.out.println("获取键'key'对应的值: " + value);
// 关闭连接
jedis.close();
}
}确保Redis服务器正在运行,并且你的Java应用程序有权限连接到该服务器。上面的代码展示了如何连接到 Redis、设置键值对、获取键对应的值,并在最后关闭连接。根据需要,你可以使用Jedis提供的其他方法来执行更复杂的操作,例如列表、集合、有序集合、哈希等数据类型的操作。
了解多少用友
**用友公司主要从事软件开发和应用服务,特别是在企业及政府、社团组织管理与经营信息化领域。**用友网络科技股份有限公司(简称“用友网络”)成立于1995年,是亚太本土管理软件、ERP软件、集团管理软件、人力资源管理软件、客户关系管理软件、小型企业管理软件、财政及行政事业单位管理软件、汽车行业管理软件、烟草行业管理软件、内部审计软件及服务的提供商。此外,用友还是中国领先的企业云服务、医疗卫生信息化、管理咨询及管理信息化人才培训提供商。
为什么选择用友
选择用友的主要原因包括其全面的功能覆盖、高效的企业管理、优化资源配置、增强数据准确性、促进业务协同、支持企业决策、降低运营成本以及优质的服务态度。
Feign
什么是Feign?它有什么优点?
Feign是一个声明试的Web服务客户端,它让编写Http客户端变得更加简单。
Feign主要的优点包括:
声明试接口: 通过注解定义接口,无需手动创建HTTP请求。
集成简单: 与Spring Cloud集成非常方便,可以轻松配置使用。
支持多种编码器和解码器:支持 JSON、XML 等多种数据格式的编解码。
可扩展性强:可以通过自定义拦截器、编码器和解码器来扩展功能
// 定义 Feign 客户端接口
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
// 使用 Feign 客户端
@Service
public class UserService {
@Autowired
private UserClient userClient;
public User getUser(Long id) {
return userClient.getUserById(id);
}
}Feign 的工作原理是什么?
Feign 的工作原理主要包括以下几个步骤:
1.接口定义:通过注解定义 Feign 客户端接口。 2.代理生成:Feign 会为每个接口生成一个动态代理对象。 3.请求构建:当调用接口方法时,Feign 会根据方法参数和注解构建 HTTP 请求。 4.请求发送:使用 HTTP 客户端(如 Apache HttpClient 或 OkHttp)发送请求。 5.响应处理:接收响应并将其转换为 Java 对象。
// 定义 Feign 客户端接口
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
// 使用 Feign 客户端
@Service
public class UserService {
@Autowired
private UserClient userClient;
public User getUser(Long id) {
return userClient.getUserById(id);
}
}Feign 如何实现负载均衡?
Feign 可以通过集成 Ribbon 实现负载均衡。Ribbon 是一个客户端负载均衡器,可以在多个服务实例之间分配请求。Feign 会自动使用 Ribbon 的负载均衡功能,只需在 application.yml 中配置服务名即可。
#application.yml
spring:
application:
name: client-app
cloud:
loadbalancer:
ribbon:
enabled: true
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
关于登录token管理
整体描述:自媒体和管理端使用用户名和密码登录,先判断用户名,再比较密码,
如果一致通过jwt生成token后返回给前端,以后前端访问其他请求时携带token,
在网关中统一校验token是否合法,如果合法解析出userId放到请求头中传递到微服务,
微服务中 我们采用拦截器获取userId放到ThreadLocal中,这样在Controller和service层都可以获取userId
细节:
1、密码采用的是md5加密算法+随机盐的方式,随机盐是在新增用户时随机产生的
2、jwt 包含三部分内容,头、载荷、签名,其中头和载荷用的是base64位加密算法,签名用的是你选择的比如HS256 RS256
3、不用ThreadLocal可以吗,可以,但是这样就需要在每个Controller的方法中使用request.getHeader方法获取userId了
4、token生成的时候是否有过期时间?有,两个小时
5、token怎么续签(续期),前端每隔一段时间发起重新获取token的请求
6、退出登录,token怎么处理?放到了Redis中,标记为黑名单,就是标记成这个token不能用了
==VUE前端==
的生命周期可以分为以下几个核心阶段,每个阶段都伴随着特定的钩子函数(生命周期钩子)来执行相应的操作:
创建阶段:
beforeCreate:实例被创建后、数据观测和事件配置之前调用。此时,组件的属性如data、methods、computed等还不可见。 created:完成了实例的初始化,数据观测设置完毕,但DOM还未生成。在此阶段,可以进行一些依赖于data或props的初始化工作,例如调用API获取数据。 挂载阶段:
beforeMount:在挂载开始之前调用。这时模板编译已完成,el选项指定的元素已替换为render函数的内容,但是尚未插入文档流中。 mounted:完成挂载,即组件的DOM元素已被挂载到父组件中,并可以在DOM树上进行查询,但可能还有异步更新未完成。这个钩子常用于在组件加载完成后执行DOM相关的操作,如添加第三方库、访问DOM节点或触发初始计算。 更新阶段:
beforeUpdate:数据发生变化时调用,此时变化的数据已经合并到了虚拟DOM中,但是在视图重新渲染前调用。 updated:完成对虚拟DOM的重新渲染和打补丁之后调用。此时组件DOM已经更新,可以在这里执行依赖于DOM状态的操作。然而,不建议在这个钩子中频繁进行DOM操作,因为它会执行多次且每次都会导致重渲染。 销毁阶段:
beforeDestroy:实例销毁之前调用,此时实例仍完全可用,但即将解绑所有指令与事件监听器,可以在这时进行清理工作,比如解除订阅、定时器清除等。 destroyed:实例已经被销毁,所有的绑定关系都被解除了,DOM元素也从页面中移除,此钩子函数主要用于资源清理工作。
前端往后端传递参数的方式有多种,主要根据传递的上下文(如 URL、请求体、请求头等)以及所使用的 HTTP 方法来决定。
params 传参
参数拼接在地址 url 的后面给后台;地址栏中可见
使用data传参
使用 data 传参,其会把数据拼接在请求体里 (body参数);地址栏中看不到请求参数
headers 传参
headers 传参 方式 用的比较少
对于这个接口而言,query是接口被调用时接收参数的形参;params即是传参方式,而query就是参数,其会自动拼接在请求地址的 url 后面,以问号分隔,数据之间通过&连接;这样我们可以直接在地址栏中看到请求的参数
分页查询
用的是mybatis,我们也用了mybatis-plus,因为一些基础的增删改查我们也是用plus去做的。
分页这块用的是plus通过分页对象page进行封装。用到方法是mapper.selectpage。传入一个pageinfo对象,这个对象参数需要,传入一个页码,以及传入需要查询的条数,然后就这样进行一个分页查询。
如果通过sql来实现的话用到的是limit关键字来进行分页的查询。Limit参数的话有两个值,第一个是起始条数,第二个的话是每页显示多少条。第一个参数,需要我们手动计算得到他的起始位置,后面是截取多少条。
resultMap的话,如果说需要将我的返回值做一个映射的话,映射类型需要做一个resultMap。如果实体类属性名 和 数据库表列名 不一致,那就不能自动封装数据,就需要定义一个resultmap,完成不一致的属性名和列名的映射。比如pojo里的类user中的字段叫id,而数据库中的表名叫user_id,那就需要通过resultmap对他的属性做一个映射。
在xml中resultmap也可以做action做一个一对一,或者通过conlaction做一对多的映射关系的添加。
Resulttype就不一样。就直接指定一个返回值类型的全路径类名就可以了。比如要返回一个user对象,这个user属性与数据库表是一模一样的情况下,那么就可以通过resulttype进行返回。如果pojo与表中字段的属性名不一样,那就需要添加映射关系,需要使用resultmap来做。
冒泡排序
冒泡排序通过重复遍历待排序的数列,每次遍历都会将未排序部分的最大元素“冒泡”到已排序序列的末尾。具体步骤如下:
- 从数列的第一个元素开始,比较相邻的两个元素。
- 如果前一个元素比后一个元素大,则交换这两个元素的位置。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素会是最大的数。
- 针对所有的元素重复以上步骤,除了最后一个。
- 持续这个过程,直到排序完成。
递归调用的基本概念
递归调用的核心在于函数通过调用自身来解决问题,通常需要一个终止条件来防止无限递归。例如,在计算阶乘的递归函数中,当n=0或n=1时,函数停止递归并返回结果。
递归调用的应用实例
-
计算阶乘: 递归调用可以用于计算阶乘。例如,
n! = n * (n-1)!,通过不断调用自身来计算阶乘值。 -
学生年龄问题:假设有5个学生坐在一起,每个学生的年龄比前一个学生大2岁。如果知道第一个学生是10岁,可以通过递归函数计算第五个学生的年龄。
递归调用的优缺点
- 代码简洁:递归调用可以使代码更加简洁,易于理解。
- 解决问题高效:对于一些可以分解为相似子问题的问题,递归调用可以高效地解决问题。
缺点:
- 性能问题:递归调用可能导致大量的函数调用栈,特别是在没有适当终止条件的情况下,可能导致栈溢出错误。
- 资源消耗:递归调用需要额外的内存来保存调用状态,可能会增加资源消耗。
MQ
RabbitMQ 是一个消息中间件,它接受并转发消息。
你可以把它当做一个 快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里。
RabbitMQ 与快递站的主要区别在于,它不处理快件而是 接收,存储 和 转发 消息数据。
MQ最核心三大作用:解耦、异步、削峰
解耦:Mq服务与服务之间解耦,feign调用如果服务没开就报错了,mq两个服务不影响另一个是否运行
补充:spring的作用,更具体说IOC的本质也是解耦,把对象创建交给spring容器去做,解的是类与类之间的耦合
异步:同步效率太低了,异步可以多个任务同时执行,每个任务执行速度更快,最终可以提升接口的QPS。(提高QPS的两种方式:增加服务器、降低请求响应时间)
额外拓展:如何优化请求,1 用redis内存型缓存 2多线程 3 MQ 4集群,这四种都可以服务上述两种提高QPS的两种方式。
削峰:防止请求洪灾,即请求太多太多来不及处理。把主动推送改成消费者手动抓取,预抓取总数设置--Channel的qos来抓取一定的数量—例如每次抓取200条。
MQ有五种工作模式,简单模式,工作模式,fanout、firect、topic,常用的有简单模式,direct是绝对路径,topic可以加通配符
如何保证消息不丢失,一个是生产者端开启确认机制,confirm确保消息到交换机,return保证消息到队列,mq本身的话就是开启默认默认持久化机制,持久化到硬盘,消费者开启ack机制,或者自动ack加重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预
消费重复性问题,也就是如何保证消息的幂等性,幂等性就是多次执行同一个操作,得到的结果是一样的,比如数据库的查询 删除就是天然幂等,解决办法是生产者发送消息是携带一个消息的唯一id,消费者消费时通过redis的setnx指令设置一个key为消息id,值为0的操作,0就表示消息正在消费,1表示消息已经消费完成,如果设置成功表示当前消息没有消费,消费后手动ack把redis的值改成1,如果设置失败表示消息已经消费或者正在消费,这时获取redis消息key的值,判断是否为1,是1的话手动ack
最后说一下消息的积压,产生的原因生产者生产的速度远高于消费者消费的速度,解决是临时增加消费者,提高消费能力
ES
倒排索引:就是分成有意义的词,得到一个词条列表,将词条当做key,包含该词条的文档id作为值,词条是唯一的,所以给词条创建索引,提高搜索效率,用词条查得到文档id,根据id查到文档
es作用就是在海量数据中快速搜索数据
Gateway服务网关
Gateway网关是一种API网关,具有路由、负载均衡、安全认证、限流熔断等特性,用于统一管理和分发请求,提高系统的可用性、安全性和可扩展性。网关可以根据请求的路由规则将请求转发到不同的服务实例,并提供过滤器机制来实现请求的加解密、鉴权、限流等功能。通过Gateway的配置,可以有效管理和保护微服务架构中的各个服务。
DFA算法
DFA是一种状态机模型,它由一组状态、一个起始状态、一组输入符号以及在状态之间基于输入符号的转移规则组成。在敏感词过滤的上下文中,每个状态代表字符串匹配过程中的一个阶段,起始状态是空字符串,输入符号通常是字符集中的单个字符,而状态转移则根据字符来决定。
在实际项目中,DFA算法实现的敏感词过滤可以有效避免误报和漏报,同时保证处理速度。考虑到性能,可能需要在内存和计算效率之间做出权衡,例如,通过压缩状态图来减少内存占用,或者使用启发式方法来优化搜索路径。
DFA算法是实现敏感词过滤的一种强大工具,其优势在于高效、准确和易于实现。在处理大量文本数据时,这种算法能够提供实时过滤,且对资源的需求相对较低。无论是在社交媒体平台、在线论坛还是其他需要进行内容审查的应用中,DFA都是一个值得考虑的解决方案。
Nginx
Nginx是一个开源的web服务器和反向代理服务器,它也可以用作负载均衡器和HTTP缓存。
想象一下,一个餐厅的厨师和顾客之间有一个非常高效的服务员,这个服务员就像Nginx一样。
在餐厅中,服务员负责接收顾客的订单,把订单传给厨师,然后在菜品准备好后把菜品端给顾客。这个过程就像Nginx处理HTTP请求一样。
CDN
CDN 是为加快网络访问速度而建立在现有网络之上的分布式网络。它依靠部署在全球各地边缘节点的服务器群,通过负载均衡、内容发布、内容管理和内容存储的功能,由CDN服务器集群分担源站点服务器集群的压力。当用户请求内容时,CDN系统会根据用户的地理位置、网络状况以及服务器的负载情况,将请求导向最近的、最合适的服务器,使用户可以就近获取已缓存的访问资源,从而避免网络拥堵,加快访问速度。
CDN优势和应用场景:
-
加速访问速度:CDN通过在全球各地部署服务器,使用户能够从距离更近的服务器获取内容,显著减少延迟,提高访问速度。
-
增强网站稳定性:在高流量情况下,CDN通过分散用户请求到多个服务器,减轻了源服务器的负荷,提升了网站的可用性和抗压能力。
-
安全防护:CDN通常包括各种安全防护措施,如DDoS攻击防御、DNS劫持和内容劫持保护等,有助于防止恶意攻击,确保内容的安全分发。
-
实现全球覆盖:CDN的全球服务器网络意味着企业可以无缝地向世界各地的用户分发内容,解决了跨地域访问速度慢的问题。
-
成本效益:通过减少对源站带宽的依赖和优化内容的传输方式,CDN有助于降低网络带宽成本。
JAVA基础
==final 有什么用?==
用于修饰类、属性和方法
被 final 修饰的类不可以被继承
被 final 修饰的方法不可以被重写
被 final 修饰的变量不可以被改变,被 final 修饰不可变的是变量的引 用,而不是引用指向的内容,引用指向的内容是可以改变的
==Java 有哪些数据类型==
基本数据类型: 整数类型(byte,short,int,long) 浮点类型(float,double) 字符型(char) 布尔型(boolean)
引用数据类型: 类(class) 接口(interface) 数组([])
继承和实现的区别?
1.数量不同:java只支持接口的多继承,不支持“继承”的多继承,继承在java中具有单根性,子类只能继承一个父类。总结就是:单继承,多实现。 2.修饰不同:继承:extends 实现:implements 3.属性不同:在接口中只能定义全局变量和无实现的方法。而在继承中可以定义属性,方法,变量,常量等。 4.调用不同:当接口被类实现时,在类中一定要实现接口中的抽象方法;而继承想调用哪个方法就调用哪个方法。
==抽象类和接口的区别?==
==抽象类是用来捕捉子类的通用特性的==。接口是抽象方法的集合。
==从设计层面来说,抽象类是对类的抽象,是一种模板设计==,接口是行为的抽象,是一种行为的规范。
==抽象类只能单继承==,而接口之间可以多继承,接口之间的继承用extends,类实现接口用implements. 注意:一个类可以继承一个类,同时实现多个接口;
2.抽象类和接口的区别?
抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
抽象类只能单继承,而接口之间可以多继承,接口之间的继承用extends,类实现接口用implements.
注意:一个类可以继承一个类,同时实现多个接口;
====和equals的区别?==
==在基本数据类型间的比较是比较值是否相同,用在引用数据类型是比较内存地址是否相同;
equals一般都是重写了equals()方来来对两个对象的内容比较,如果不重写,使用的就是Object中的equals方法,比较的是对象的内存地址。
3.为什么重写equals方法,还必须要重写hashcode方法
1.保证是同一个对象,equals比的是地址,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,
重写hashcode方法就是为了避免这种情况的出现。
2.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
3.减少了equals的使用次数,执行效率就大大提高了
在使用 HashMap 的时候,用 String 做 key 有什么好处?
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字 符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再 次计算,所以相比于其他对象更快。
String (字符串常量,创建的是一个不可变的字符串对象内存开销大不建议使用)
StringBuilder (可变的字符串对象,线程不安全的,要效率用它)
StringBuffer (可变的字符串对象,线程安全的,里面有加锁要安全用它) (重要)
1.如果要操作少量的数据用string
2.单线程操作字符串缓冲区下大量数据用stringbuilder
3.多线程操作字符串缓冲区下大量数据用stringbuffer
==Map==
map是Java中的一个接口,用于存储键值对(key-value pairs),其中每个键都是唯一的,但值可以重复。Map接口不继承自Collection接口,因此它与List、Set等集合类不同,属于双列集合。Map的主要特点包括:
-
键值对存储:Map存储的是键值对,其中键用于唯一标识一个条目,而值则是该条目的具体内容1。
-
键的唯一性:同一个Map中不能存在两个相同的键,但如果两个键相等(根据equals方法判断),则后插入的键值对会覆盖之前的键值对1。
-
值的可重复性:虽然键必须唯一,但值可以重复1。
-
无序性:大多数Map实现并不保证元素的顺序,除非使用了如LinkedHashMap这样的特定实现1。
Map的常见实现类及其特点:
- **HashMap**:基于哈希表实现,提供快速的查找、插入和删除操作,但不保证元素的顺序。允许null键和null值1。
- **TreeMap**:基于红黑树实现,能够对键进行自然排序或通过指定的比较器排序。不允许null键,但允许null值1。
- **LinkedHashMap**:结合了HashMap和链表的优点,既保持了插入顺序,又提供了高效的查找性能1。
- **Hashtable**:类似于HashMap,但线程安全,不允许null键和null值。由于其同步特性,性能通常低于HashMap
==mybatis # 和 $的区别?==
#预编译sql,防止sql注入
$ 字符串拼接,会出现sql注入的问题。
什么是sql注入? 在sql后面拼接 or 1=1 此时这个sql会永远执行成功
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
==重载和重写==
==重载==:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
==重写==:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
==面向对象的三大特性==
1.封装 封装就是把抽象出来的数据(属性)和对数据的操作(方法)封装在一起,数据封装在方法内部,我们只需要调用方法就能对数据进行操作
2.继承 子类继承父类,通过子类可以调用父类里面的方法和属性 Java只有单继承,没有多继承
3.多态 即同一方法可以根据对象的不同而采用多种不同的行为方式 多态存在的条件 1.有继承关系 2.子类重写父类方法 3.父类引用指向子类对象
什么是堆
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
堆是由操作系统管理的一片空间,事先是没有在进程空间里分配的(比如你在没有分配堆的时候就访问堆空间会报一个内存访问错误),一般是由程序动态的分配出来,一旦分配了以后,一般需要程序去释放自己的堆空间。堆的空间较大,但访问速度没有栈快。堆受垃圾处理器GC管理(GC会去找那些很久没有引用地址指向的内存块,把它们清理掉。
什么是栈
栈(数据结构):一种先进后出的数据结构,是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有FILO的特性,在编译的时候可以指定需要的Stack的大小。
栈上是向下填充的,数据只能从栈的顶端插入和删除(先进后出原则)。把数据放入栈顶称为入栈(push),从栈顶删除数据称为出栈(pop)。栈的空间较小,但访问速度快。栈的生长方向是有高地址向低地址生长的,栈的清理是由系统自动完成的。
1、堆栈空间分配不同
栈(操作系统):由操作系统(编译器)自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
2、堆栈缓存方式不同
栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
3、堆栈数据结构不同
堆(数据结构):堆可以被看成是一棵树,如:堆排序。先进先出的结构。
栈(数据结构):一种先进后出的数据结构。
什么是反射机制?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring框架也大量使用到了反射机制。获取反射的三种方式:new对象,class.forname+类路径还有类名.class的方式
什么是深拷贝? 什么是浅拷贝?**
浅拷贝:在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但对引用类型的成员变量只进行引用的传递,
并没有创建一个新的对象,当对引用类型的内容修改会影响被拷贝的对象。
深拷贝:在拷贝一个对象时,除了对基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建一个新的对象来保存引用类型的成员变量
jdk8的新特性
引入了lambda表达式和方法引用
Stream 流的常用方法:
-
forEach:遍历Stream中的每个元素,对每个元素执行特定操作。
-
map:将Stream中的每个元素映射到另一个元素。可以将一个类型的Stream转换为另一个类型的Stream。
-
filter:通过传递一个谓词来过滤Stream中的元素。只有符合条件的元素才会被保留。
-
flatMap:将Stream中的元素转换为Stream,然后将各个Stream串联成一个Stream。
-
sorted:对Stream中的元素进行排序。可以传递一个Comparator来指定排序方式。
-
limit:截取Stream中指定数量的元素。
-
distinct:去重Stream中的元素,保留不同的元素。
-
reduce:使用一个初始值和一个BinaryOperatort将Stream中的所有元素进行累计操作。
-
collect:将Stream中的元素一并操作聚合成一个集合。
JDK8引入的新的日期时间API是java.time包,提供了比原来的API更加简单、直接和全面的日期、时间、时区、时间间隔等对象,方便开发人员进行日期时间处理,减少了很多之前在使用日期时间方面所遇到的问题。
新的日期时间API包括以下类:
-
LocalDate:代表只含有日期部分的日期时间,例如 "2019-10-01"。
-
LocalTime:代表只含有时间部分的日期时间,例如 "10:00:00"。
-
LocalDateTime:代表既有日期部分又有时间部分的日期时间,例如 "2019-10-01T10:00:00"。
-
ZonedDateTime:带时区的日期时间,例如 "2019-10-01T10:00:00+08:00"。
-
DateTimeFormatter:日期时间转换格式化工具类。支持自定义格式化和预定义的格式化样式。
Java工具类及其用途
字符串处理工具类:用于字符串拼接、分割、替换、转换大小写等操作。
时间日期工具类:提供日期的格式化、解析、计算和比较等功能。
文件操作工具类:用于文件的创建、复制、删除、移动、重命名等操作,以及读取和写入文件内容的方法。
数据校验工具类:包含判断是否为空、是否为数字、是否为邮箱等常见的数据校验方法
加密解密工具类:提供MD5、SHA1、AES等常用加密算法的封装。
随机数工具类:用于生成随机数和随机字符串。
编码转换工具类:提供字符编码转换的方法,如字符串与字节数组、字符串与16进制字符串的相互转换
网络请求工具类:封装HTTP请求,包括GET、POST、PUT、DELETE等请求方法。
日志工具类:用于打印日志信息,方便程序调试和排查问题
集合
Arraylist集合是单列集合,它的底层是数组,它的特点是存取有序、可以重复。存取有序的原因是因为底层是数组,按照索引来存放和遍历取出,可重复是因为add方法没有做元素判断处理。Arraylist的初始长度是0(JDK1.6版本以前初始容量就是10),在首次添加元素的时候扩容成10,当10个元素都存满准备添加 11个元素的时候进行扩容,扩容成原来的1.5倍(1.5倍 + 1(JDK1.5时候))。选取默认容量为10,jdk官方的解释其实就是10是一个比较中位的大小,相比于1太小100太大,没有特殊的原因。
Arraylist底层源码不是特别复杂,我看过add方法的源码。第一次添加数据的时候,由于elementdata就是底层存元素的数组是空数组,就会走扩容grow方法,并且扩容的大小是静态成员属性defaultcapacity10,扩容完成后把待添加的元素存储到这个elementdata数组的第一个索引位置上。后续再添加元素的时候,会去比较“当前元素个数+1”和数组长度哪个大,如果后者大,就不需要扩容,所以在下一个索引位置直接添加元素;如果前者大,其实就是存不下了就又会走到grow方法,而grow方法内部记录旧容量和新容量,新容量是旧容量加上它右移1位(其实就是除2),相当于新容量是旧容量的1.5倍,(同时也会判断一下是否超过最大值),grow方法最后还走一个arrays.copy方法,扩容后把旧数组的元素全部复制到新数组,最后再执行下一个索引位置添加元素。
与他对应的是linkedlist是双向链表结构,也是有序可重复的集合,这两个集合的区别是arraylist进行数据查询效率明显比link快,linked无规则插入效率可能会比arraylist要快,因为如果做尾插arraylist也是很快的。
Hashmap是一种双列集合,底层数据结构1.8以前是数组+单向链表,1.8以后是数组+单向链表或红黑树。存储特点是存取无序,存取无序是存储位置根据hash判断出来的,hash计算无序,而不可重复是因为存入元素的时候会去判断hashcode值和equal方法是否相等,若相当则覆盖,所以是无法存储相同元素即不可重复的。
Hashmap1.7初始容量0,首次put的时候扩容到默认初始容量16(为什么初始容量是16 是因为2个幂次方在位运行情况下效率最高。),当存储元素个数达到扩容阈值-16*0.75(默认扩容因子)=12个 并且最后一个添加元素计算的索引位置元素非空时,才会触发扩容扩容成原容量的2倍。添加元素时如果计算下标位置相同即哈希碰撞,会去从头结点开始逐一比较key值是否相同,不相同的话就挂在链表的末端,采用的是头插法。
1.7版本存在的问题:二是正因为采用头插法,所以并发情况可能会产生死链问题,三是链表挂的过长因为查询效率过低的问题。
Hashmap1.8初始容量0,首次put的时候扩容到默认初始容量16,当存储元素个数超过扩容阈值12个会触发扩容-添加第13个必扩容,扩容成原容量的2倍。添加元素时会计算key的hash值,hash取模数组长度,得到元素插入的下标,判断当前下标有没有元素,没有就直接放,有的话会判断hashcode和equals是否相等,都相等替换value,不相等就头插法插入到链表,采用的是尾插法。如果链表长度达到8,并且数组长度到64就会出现红黑树,如果数组没到64那就先扩容
hashmap1.8基本解决了出现环链的问题 尾插法和红黑树
多线程
什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个 不同的线程来执行不同的任务。
线程和进程区别
进程
一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进 程可以有多个线程,比如在 Windows 系统中,一个运行的 xx.exe 就是一个进程。
线程
进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至 少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
多线程优势劣势?
多线程的好处: 可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可 以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单 个程序创建多个并行执行的线程来完成各自的任务。
多线程的劣势:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
**先说一下创建线程的方式,**有继承thread类,实现runnable接口 实现callable接口,runnable和callable相同点是都是接口,用start方法启动线程,不同点是runnable的run方法没有返回值,callable接口的call方法有返回值,需要调用futuretask的get方法。Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常线程启动使用的是start方法,start方法用于启动线程,run是方法用于执行线程的运行时代码
创建线程池用自定义线程池时有七个核心参数,核心线程数,最大线程数,临时线程最大存活时间,时间单位,等待队列,创建线程工厂,拒绝策略,核心参数的设置一般分两种场景,如果是cpu密集型场景,比如解压计算等操作一般设置为n+1, 如果是io密集型比如数据库读写等通常用2n+1,
拒绝策略的话有丢弃任务并抛出异常,这个是默认的,有丢弃任务不抛异常,丢弃等待最久的线程,调用执行自己的线程运行run方法,线程池中执行顺序是核心线程满了后看等待队列,再看临时线程,临时线程是最大线程减去核心线程数,再有就走拒绝策略了,
之前项目中有用到过自定义线程池,用线程池的原因是文章发布成功后,需要调用审核方法,审核用时过长会影响文章发布响应的接口的响应,如果是不同的微服务可以用消息中间件,但是在同一个微服务所以用的多线程,操作是在要执行异步的方法上打@async注解,在启动类上打@enableasync注解
线程额生命周期有六种状态,新建状态:创建线程对象,就绪状态:通过start调用,阻塞状态:无法获取锁对象,等待状态:超时等待状态,死亡状态
spring默认的bean都是单例bean都是不安全的,平时都是用的里面你的方法,并没有对成员变量进行修改,多个线程共享了数据对数据进行了修改,所以线程是不安全的,解决方案就是加锁,synchronized锁和lock锁,前者是jvm层面的事关键字,lock是api层面的是一个接口,前者出异常会释放锁,不会有死锁,方法结束后解锁,lock相反,会死锁,要手动释放锁
**线程的安全特性:**原子性:一个线程操作不能被其他线程打断,全部成功或者失败,有序性:执行有序 可见性:一个线程修改数据后,对其他线程是可见的
**什么是死锁?**A需要B解锁,B需要A解锁,两个都在中间互相等待,却谁也无法满足条件,从而发生阻塞,就是死锁。
MYSQL
==什么是索引?==
索引是数据库中一种用于加快数据检索速度的数据结构。它通过对数据库表中一列或多列的值进行排序,建立指向数据记录的引用,从而快速定位到所需的数据,提高查询效率
索引的作用
加快数据检索速度:索引相当于书的目录,可以直接通过索引找到数据,而不需要全表扫描,大大减少了IO操作。
保证数据唯一性:通过创建唯一性索引,可以确保表中每一行数据的唯一性。
加速表连接:在表和表之间的连接操作中,索引可以显著提高效率。
减少查询时间:在使用ORDER BY和GROUP BY子句进行数据检索时,索引可以减少分组和排序的时间。
==索引有哪几种类型?==
主键索引: 数据列不允许重复,不允许为 NULL,一个表只能有一个主键。
唯一索引: 数据列不允许重复,允许为 NULL 值,一个表允许多个列创建唯一索 引。可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引 • 可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引
普通索引: 基本的索引类型,没有唯一性的限制,允许为 NULL 值。 • 可以通过 ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引 • 可以通过 ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引
**全文索引:**是目前搜索引擎使用的一种关键技术。 • 可以通过 ALTER TABLE table_name ADD FULLTEXT (column); 创建全文索引
什么是数据库的事务?
事务是一个不可分割的数据库操作序列,也是数据库并发控制的一个基本单位,它执行的结果必须是让数据库从一种一致性状态转变成另一种一致性状态。事务是逻辑上的一组操作,要全都执行,要么全都不执行。
事务最经典也经常被拿出来说例子就是转账了。 假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:将小明 的余额减少 1000 元,将小红的余额增加 1000 元。万一在这两个操作之间突然出 现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不 对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
事物的四大特性(ACID)介绍一下?
关系性数据库需要遵循 ACID 规则,具体内容如下:
-
原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作 要么全部完成,要么完全不起作用;
-
一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的 结果是相同的;
-
隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各 并发事务之间数据库是独立的;
-
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即 使数据库发生故障也不应该对其有任何影响。
==sql语句的执行顺序?==
from(找表) > join(连表查询) > on(连表条件) > where(过滤数据) > group by(分组) > having(分组过滤) > select(显示数据) > order by(排序) > limit(分页)
join关键字的作用?
join分为 left join(左外连接,左表展示全部数据,右表匹配) , right join(右外连接,右表展示全部数据,左表匹配) , inner join (内连接,匹配两张表的共有的数据)join一般结合on关键字一起出现。
使用DELETE语句删除指定条件的数据。例如,删除表中name字段为'John'的所有行:
DELETE FROM 表名 WHERE name = 'John';
使用TRUNCATE TABLE语句删除所有数据并重置自增列。例如,删除表中的所有数据:
TRUNCATE TABLE 表名;
group by关键字的作用?
做分组查询,结合 having进行条件过滤
聚簇索引:
索引和数据在同一个文件中,只有主键索引是聚簇索引,其他普通索引,唯一索引,联合索引是非聚簇索引
mysql性能优化:
**优化方式:**sql语句优化、索引优化、存储结构优化、硬件直接升级就行
数据库结构优化(水平|垂直)垂直拆分 冷热分离 热数据是经常被查询的数据 ,垂直拆分就是拆分成多个表存储也已用sharding-jdbc/mycat(阿里的),水平拆分(类似数据分片,如果单表数据2000w以上性能就很低了,为了不超过可以水平拆分。每个表数据差不多就行)
索引优化。**索引存储结构:**B树数据都是在节点上,数据大存不了多少,存的多的话层数多查的慢,B+树非叶子节点上存的是索引值和指向下个索引的指针,叶子存的数据多,降低了树的高度,三层就能存2000多W,节点之间有指针,提升区间查询效率,不用hash是因为hash只支持等值查询,不支持范围查询
**这些列需要建索引:**1主键是已经自带索引了 2where后面的条件字段需要加 3聚合列要加 4外键要加(但是现在都是逻辑外键了)。这些列不要加索引:重复数据太多的列-比如性别、年龄(且范围查询对索引有影响)、密码;索引不是建的越多越好:不是,影响增删改的性能,要具体情况具体分析。
**存储引擎优化。**把数据写到硬盘的就是引擎。对比innodb和myisam:前者支持事务,支持行锁和表锁,增删改较多的用这种,主键自增的也用这种;后者没有事务、没有外键,但是查询效率比较快(因为事务的效率是比较低的,要存很多表,undo-logo,bin-log等)。Memeory引擎-类似redis一样的内存存储。如果只有查询可以用myisam。
sql语句优化。Sql语句优化的作用:1避免索引失效2防止全表扫描。
失效的情况:
1模糊查询(右边加%时候效率会很低)
2范围查询
3索引字段条件计算
4联合索引没有第一个索引的查询条件(如果少了最后一个索引没关系,少了中间就相当于单列索引)使用!= 使用or null
下面诗的解释(防止索引失效,要避免):
1等值情况效率是最高的,精准匹配,row差不多等于1次匹配一次。
2联合索引的时候(效率比单个索引效率高),但是有最左匹配原则(第一个条件必须有,不然索引会直接失效)3 如果联合索引语句中中间的是没有当条件,不会走联合索引而是走了最左的普通单列索引;如果语句中第三个条件没有关系还是走联合索引
4 索引列上如果做了计算直接原地失效
5 索引字段范围查询,索引没原地失效,但是全表扫描了性能低;mysql
会降低性能但不会失效
6 模糊查询like左右都加%原地失效,左边不加右边加%不会失效但性能低range-真想模糊用ES;
7 写性能比较低
8 用!=/is null/or变成全表扫描失效或低性能。
Explain关键字,作用是解析sql语句。1 Id没作用,2 select type查询类型,无需关注;3 type表示索引的效果,等级如下const> eq_ref > ref >range>index > all 一般来说起码到range,最好到ref级别 ;4 prossible_ley 当前用的索引名字(看索引是否失效) 5 rows代表查询匹配了几次(如果匹配了数据量的次数就代表全表扫描了-性能最低) 5 key_len 数据越小越好…
什么是慢查询?
==阈值是指在一个特定系统中,当某个参数达到这个界限时会触发某种变化或者响应的一个临界值==
慢查询是指那些执行时间超过一定阈值 (通常称为“慢查询阈值”) 的SQL查询。 这些查询往往消耗较多的系统资源,影响数据库性能,甚至可能导致数据库响应变慢或挂起 (暂时停止的状态)
为什么会产生慢查询?
慢查询产生的原因有很多,以下是一些常见的原因:
索引缺失: 如果查询涉及的表没有合适的索引,数据库可能需要进行全表扫描,这会导致查询变慢。
索引失效: 如果查询条件不能有效地利用现有索引,或者索引的选择性不高,查询性能也会受到影响。
查询复杂度高: 复杂的查询语句,比如涉及多个表的联接操作、子查询、大量的分组或排序等,可能会导致慢查询。
大量数据处理: 查询涉及到大量的数据时,如果没有有效的优化手段,查询速度会明显下降。
并发问题: 高并发场景下,数据库可能需要处理大量的请求,这可能导致某些查询因等待资源而变慢。
硬件限制: CPU、内存或磁盘I/O等硬件资源不足也可能导致查询变慢。
配置不当: 数据库配置参数不合理,如缓冲池大小、连接数等,也可能导致慢查询。
根据原因选择相应的优化策略
针对上述原因,可以选择以下优化策略:
创建索引:为查询条件中的字段创建索引,以加速查询。
优化查询语句:使用合适的查询方式、合理的条件筛选等,减少查询的数据量。
调整数据库参数:根据数据库的实际情况,调整数据库参数以提高性能。
数据库分区:对表进行水平或垂直分区,以减少查询时需要扫描的数据量。
使用缓存:将查询结果缓存到内存中,以便下次查询时直接从内存中读取。
线上慢sql定位:
使用Druid数据库监控,还可以发出告警信息,通过日志定位使用explain去分析优化
数据库的乐观锁和悲观锁是什么?怎么实现的?
数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库 中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制 (乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
乐观锁:
乐观的认为当前并发并不严重,当真的出现并发的时候,再用锁(机制)来解决安全问题。比如Java中的 CAS+Version,适用于读多写少的场景
悲观锁:
总有刁民要害朕,悲观的认为当前并发是非常严重的,所以需要通过上锁来保证同步问题。比如Java中Synchronized或者Lock,适用于写多读少的场景
**表锁:**锁整个表,行锁:锁单行记录,防止其他事务对此进行update和delete
死锁:
数据库的隔离级别:读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。这个基本不会用,会出现脏读、不可重复读、幻读的情况。脏读是指在A事务处理过程中,读到了B事务未提交的数据,导致两次查询结果不同
读已提交:这个是oracle默认的,会出现不可重复读和幻读,总的来看,两者都是对数据进行了两次查询,但两次查询的结果都不一样,但是不可重复读针对的是更新和删除,幻读针对的是插入。
可重复读:是mysql的默认隔离级别,存在幻读,幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读,解决不可重复读的方法是 锁行,解决幻读的方式是 锁表。还有就是串行话。
**表设计:**设计数据库表,我的经验就是先从产品原型中提炼出表名称,比如产品原型中,有一些基础数据的维护,那么这些大概率都是一些基本表。这类的表设计相对比较简单,就是从产品原型中提取输入和输出项,此外,我们还会加上逻辑删除字段、状态字段、创建日期和更新日期这样的字段。 另外还有一些经验,虽然理论上我们要遵循三大范式,但是实际上是要有变通的,比如空间换时间。
Spring
Spring 是一个轻量级 Java 开发框架,最早有 Rod Johnson 创建,目的是为了解 决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的 JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发 Java 应用程序 提供全面的基础架构支持。Spring 负责基础架构,因此 Java 开发者可以专注于 应用程序的开发。
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方 案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动 器,开发者能快速上手。
spring就是springframework 解决耦合,springboot快速构建spring环境,没有对spring增强
springcloud是微服务架构,本质是很多技术的集合,比如注册中心,负载均衡,网关,熔断器,feign
自动装配,简单来说就是自动把第三方组件的 Bean 装载到 Spring IOC 器里面,不需要开发人员再去写 Bean 的装配配置。 在 Spring Boot 应用里面,只需要在启动类加上@SpringBootApplication 注解就 可以实现自动装配。 @SpringBootApplication 是 一 个 复 合 注 解 ,里面有三个注解组成的,有@springbootconfiguration底层就是@configuration表示引导类就是一个配置类,还有compomentscan 是组件扫描,引导类所在的包和子包都能被spring容器扫描到,真正实现自动装配的注解是 @EnableAutoConfiguration,这个里面有两个注解有一个@Import(AutoConfigurationImportSelector.class这个配置类被导入了),会执行selectImports 方法,会调用SpringFactoriesLoader.loadFactoryNames,会加载 META-INF/spring.factories 中所有bean的全类名,基于反射创建bean对象
SpringCloud五大组件
Eureka:Eureka是Netflix开发的一个用于实现服务注册和发现的服务。Eureka主要由两部分组成:Eureka服务器和Eureka客户端。服务提供方需要将自身信息注册到Eureka,由Eureka保持这些信息(如服务名称、IP、端口等)。服务消费者向Eureka拉取服务列表信息,利用负载均衡算法选择一个服务进行调用
Feign:Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得更加简单。它支持多种注解,使得HTTP调用就像调用本地方法一样简单
Zuul/Gateway:Zuul和Gateway都是服务网关组件,用于统一服务入口,实现路由和安全控制。Zuul是Spring Cloud早期的网关组件,而Gateway是Spring Cloud官方推荐的替代方案,提供了更丰富的功能和更好的性能
Hystrix:Hystrix是一个用于处理分布式系统的延迟和容错的库,主要实现服务熔断和降级功能。当某个服务调用失败时,Hystrix可以防止级联失败,保护系统的稳定性
Ribbon:Ribbon是一个客户端负载均衡器,主要用于实现客户端的负载均衡。它可以在服务消费者端进行负载均衡,选择一个合适的服务器进行调用,从而简化远程调用过程
spring注解: @RestController,@Controller ,@ResponseBody
Springboot注解:
@SpringBootApplication
作用:标注一个主程序类,表明这是一个Spring Boot应用程序的入口
@Configuration
作用:标识一个类作为配置类,类似于Spring XML配置文件。配置类可以包含一个或多个@Bean注解的方法,这些方法会返回要注册到Spring应用上下文中的Bean。
@EnableAutoConfiguration
作用:启用Spring Boot的自动配置机制,根据项目中的依赖和应用上下文自动配置Spring应用程序。
@ComponentScan
作用:指定要扫描的包,以便发现和注册Spring组件。
组件注解:
@Component
作用:将一个类标识为Spring组件(Bean),可以被Spring容器自动检测和注册。
@Service
作用:标识服务层组件,实际上是@Component的一个特化,用于表示业务逻辑服务
@Repository
作用:标识持久层组件,实际上是@Component的一个特化,用于表示数据访问组件。数据访问组件通常涉及与数据库交互、执行数据操作(如增删改查)、数据转换和持久化等任务。
@Controller
作用:标识控制层组件,实际上是@Component的一个特化,用于表示Web控制器。用于标识Spring MVC控制器,处理Web请求并返回视图。
@RestController
作用:标识一个RESTful Web服务的控制器,实际上是@Controller和@ResponseBody的结合。返回的对象会自动序列化为JSON或XML,并写入HTTP响应体中。
请求映射注解
@RequestMapping
作用:提供路由信息,负责URL到Controller中的具体函数的映射。可以应用在类级别和方法级别。在类级别上使用时,它定义了该控制器处理请求的基本路径;在方法级别上使用时,进一步细化了具体的请求路径和请求方法(如GET、POST、PUT、DELETE等)。
@GetMapping
作用:映射HTTP GET请求到处理方法上。
@PostMapping、@PutMapping、@DeleteMapping
作用:分别映射HTTP POST、PUT、DELETE请求到处理方法上。
参数绑定注解
@PathVariable
作用:从URI路径中提取参数值,将其映射到方法的参数上。常用于RESTful服务中,允许动态地将URL中的部分作为方法参数使用。
@RequestParam
作用:从查询字符串中提取参数。
@RequestBody
作用:将HTTP请求体的内容(如JSON、XML等)映射到一个Java对象。通常用于POST请求中,将客户端发送的数据绑定到方法的参数上。
@ResponseBody
作用:将方法的返回值转换为指定格式(如JSON、XML)作为HTTP响应的内容返回给客户端。常用于RESTful服务中,标识方法返回的对象不是视图名称,而是直接写入HTTP响应体中的数据。
@RequestHeader
作用:从HTTP请求头中提取数据。
其他常用注解
@Autowired
作用:用于自动装配Spring Bean。它可以应用在字段、构造函数、方法上。当应用在字段上时,Spring会自动查找并注入匹配类型的Bean;当应用在构造函数上时,在创建对象时会自动注入所需的Bean;当应用在方法上时,Spring会在调用该方法时自动注入所需的参数。
@Value
作用:用于注入配置文件中的属性值。
@Profile
作用:用于指定配置文件的环境,可以用来区分开发、测试和生产环境。
@ConditionalOnBean、@ConditionalOnMissingBean
**作用:**用于根据条件决定是否注册某个Bean。@ConditionalOnBean当容器中存在指定的Bean时才生效,而@ConditionalOnMissingBean则是在容器中不存在指定的Bean时才生效。
今天说一下mybatis的mapper映射文件的标签,有insert、update、delete、select、resultmap、collection、assocation、if、where、set、foreach、trim、sql
Spring 框架中用到的设计模式:
工厂模式:Spring 容器本质是一个大工厂,使用工厂模式通过 BeanFactory、 bean 对象。
单例模式:Bean默认为单例模式。代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术。模板方法:用来解决代码重复的问题。比如. RestTemplate, redistemlate,mongotemplate
springBean生命周期,
四个阶段:
1 基于字节码创建beanDefinition对象交给工厂beanfactory
2 实例化bean,反射方式
3 bean属性填充,bean取名
4 如果有增强就常见代理对象 5 最后放入单例池
mybatis的工作原理:解析配置通过构建者模式创建sqlsessionfactory对象,通过sqlsessionfactory对象获取sqlsession对象,用的是工厂模式,通过sqlsession获取mapper接口代理,用的是jdk动态代理
==再说一下spring的ioc和aop==
ioc是一种思想,翻译过来是控制反转,把对象的控制权交给spring容器来管理,以前是自己创建,现在让其给造还给管理,需要就从容器中拿,这个容器就是一个大map,key就是bean的id,值就是bean的对象,底层是工厂模式,通过工厂的制定方式获取对象,反射创建对象。DI是依赖注入,是ioc的一种具体表现形式,注入方式有setter注入、构造器注入、工厂注入、自动注入,平时用的最多的就是autowired和resource autowired是spring的注解,是按类型自动注入,resource是jdk的注解,按照名称注入
==spring事务==
将⼀组操作封装成⼀个执⾏单元,要么全部成功要么全部失败。
为什么要⽤事务?
⽐如转账分为两个操作:第⼀步操作:A 账户 -100 元,第⼆步操作:B 账户 +100 元。如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么 A 账户平⽩⽆故的 100 元就“⼈间蒸发”了。如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败。
事务特性
事务有4 ⼤特性(ACID):原⼦性(Atomicity,或称不可分割性)、持久性(Consistency) 、⼀致性(Durability)和隔离性(Isolation,⼜称独⽴性)
原⼦性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中 间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个 事务从来没有执⾏过⼀样。 ⼀致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯ 作。 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 **隔离性:**数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务 并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
AOP是面向切面编程,底层是动态代理,动态代理分两块,jdk动态代理必须有接口,cglib类不能被final修饰,项目中比如拦截器,过滤器,异常处理器都用到AOP,实质就是在不改变源代码的基础上对功能进行增强,自己写过AOP,统计方法运行时长用过,还有记录当前的时间做过,自定义注解并增强,事务的传播行为有required主要做增删改,必须有事务,如果没有事务新开一个,有的话就加入,supports是查询,可有可无,当前没有就没有,有的话就支持,AOP的注解有aspect神功当前类是切面类,pointcut是指定切入点表达式,其他还有各种通知最常用是环绕通知around
spring事务传播行为:required用于增删改,必须有事务,如果当前方法没有事务,新开一个事务。如果有事务,则加入。supports用作查询,可有可无,如果当前方法没有事务,就没有事务。如果有事务,则支持
事务什么情况下会失效? 数据库存储引擎不是innodb;2 注解添加到了私有方法上-代理对象需要有公共方法;3 try catch会失效, 4 事务传播特性是support的时候会失效 5 分布式(多个服务间)事务会失效,只能保证本地事务-单个数据库
spring循环依赖不会报错,spring用的三层缓存机制解决了,一级缓存放的是完整的bean,二级放的半成品的bean,三级放的工厂bean,为什么需要三级缓存? 二级缓存解决不了有aop的循环依赖。spring采用了三级缓存。
Redis
==先说一下redis的五种数据类型==
String:存令牌、验证码、做抽奖;
Set:朋友圈点赞共同好友
List:延迟审核、替代mq队列(比mq效率更高)、限流|秒杀超卖(秒杀一有时间限制,二有数量限制。为了防止多个请求来把库存扣成负数,实现方式是把可以被秒杀的假设10个商品的标记添加到redis的list中,用户请求过来了就pop一个,pop拿到值才可以下单,所以最多只能pop10个。Redis是单线程 线程安全的,多个请求pop只能在外面排队等待)
Zset:热点排行榜 根据分数排序
Hash:购物车(大key存用户id、小key商品id,value个数)
redis为什么那么快?
一个是基于内存读写,还有是单线程,那么快因为底层是三大部分:IO多路复用程序+队列+文件事件分派器。而文件事件分派器就是用来分派执行任务的,不需要依靠cpu的性能,而是内存和网络。
4.0是单线程,没有现成安全问题,Redis6.0之后提供了多线程,仅针对于网络传输,默认是不开的
==redis持久化?==
Redis存内存,万一异常断电数据可能就丢了。为了保证数据不丢,所以要做持久化。持久化就是把数据存储到硬盘中。Redis持久化 RDB方式---快照方式,恢复数据很快,但会丢数据,60s执行了10000次操作,就快照一次,300s执行了100次请求,就快照一次,3600s执行了一次请求,就快照一次(备份)。redis持久化 AOF方式---日志方式,不会丢,但是影响性能,恢复速度慢每执行一个命令,都会记录到日志中,日志越来越多越来越大,恢复的时候是靠一条条日志逐行读写
实际该怎么用:1 两种都用,5.0之后混合模式 2 不用持久化,因为影响redis性能,只存临时数据不担心丢失
==问题:Redis面试三剑客-缓存穿透、缓存雪崩、缓存击穿?==
用户知道缓存和数据库没有该数据,恶意大量攻击请求(写个死循环一直访问,mysql会宕机)
解决办法:布隆过滤器,原理是把数据库所有key存到redis,请求发来时就看看这个key集合有没有,如果请求redis没有,直接返回。有再去访问数据库
缓存雪崩:意思是大量数据同时失效,都打到数据库
解决方案:不设置过期时间或错开失效时间,设置成随机值,添加二级缓存?
缓存击穿:热点数据突然失效或丢失导致大量数据攻击数据库
解决方案:1多个请求只放行一个即可,即加锁,sychronized。2 加分布式锁,分布式锁概念,多个进程抢占只有一个人能成功-子线程
怎么保证数据的一致性:高并发的时候要保持数据库和redis保持同步,不同步会出现脏写 脏读的情况,用阿里的connal,之前用的延迟双删有一个缺点,第二次删除可能会有几秒的误差,connal稳定性比较好,实现原理是他把自己当成一个从数据库,去mysql主数据库拉取数据,mysql更新数据到binlog里面,connal从binlog拉取再同步到redis里面,这样就可以保持数据的一致性,数据少的时候可以延时双删,高并发不行
Redis架构,是集群部署还是单点部署?哨兵的选举机制?
单点,用阿里的redis云服务器(集群),不会宕机。回答单点的话就说宕机的话就重启。
主从复制。主节点负责增删改,从节点负责查。多个重节点比如一个可以250个,两个就可以有500个访问。原生的redis主从复制万一主节点挂了,就没法增删改了。所以引入了哨兵机制,哨兵会监控若主机挂了,会选举一个主(没哨兵默认不会选举)。一主一从三哨兵,防止哨兵也挂了,且2个哨兵没法选举。哨兵是高可用方案,不是集群。
Redis官方集群redisCluster(集群反而分片了?),三台都是主,最少六台服务器,三主三从。三台redis集群也是16个数据库。数据放哪儿,是根据hash值放在对应的hash槽(16384个槽位)。哨兵的选举机制看一下
==分布式锁==
多个服务想操作同一个数据,只有一个人能成功,核心是生成一个唯一的标识,谁拿到了这个标识,才能进来有操作权,通用解决方案是到某个可以存储(比如redis)的地方,去拿一个唯一的锁(或者说标识),然后考虑锁到期的问题,redis用setnx可以操作,如果这个key在redis已经存在了,表示什么都不干,如果setnx 没有这个key成功了表示拿到锁了,设置锁的时候一定要保证过期时间,不然永远锁着了,
这个时候可以用Redission提供的一种自动延期机制看门狗,总的流程就是,首先获取到锁(这个锁30s后自动释放),然后对锁设置一个延迟任务(10s后执行),延迟任务给锁的释放时间刷新为30s,这样就达到了如果一直不释放锁(程序没有执行完)的话,看门狗机制会每10s将锁的自动释放时间刷新为30s。
分布式锁应该有哪些特征
「互斥性」: 任意时刻,只有一个客户端能持有锁。
「锁超时释放」:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
「可重入性」: 一个线程如果获取了锁之后,可以再次对其请求加锁。
「高性能和高可用」:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
「安全性」:锁只能被持有的客户端删除,不能被其他客户端删除
分布式事务
传统数据库事务特征:A原子性:事务中的所有操作,要么全部成功,要么全部失败 C一致性:事务开始前到事务结束后,数据状态需要一致 I隔离性:多个事务之间的操作相互隔离,互不影响 D持久性:事务一旦提交,则永久的保存到磁盘,不可逆
分布式理论中有CAP定理:一致性 可用性 分区容错性 base理论:放弃强一致性,追求最终一致性,必须要保证p不然就是单节点,ap是最终一致性 cp是强一致性
在项目中有使用阿里的seata来做分布式事务,seata支持四种模式:XA AT SAGA TCC ,其中用的就是AT来保证最终一致性,AT实现原理是执行sql提交到日志表undolog,给TC汇报事务状态,如果两个分支事务都成功提交会删除log,如果分支事务失败,sql和undolog都恢复数据,项目实际操作就是数据库在每个涉及到分布式事务中添加undolog表,引入依赖,修改配置文件,最后在事务方法的入口加上一个注解@Globaltransational
linux
ls cd mkdir pwd当前目录 进入编辑器vi i 编辑 esc退出编辑 :wq保存退出
rm删除 tail 只看尾巴几行 ps -ef | grep 查找 top看进程 chomd-R 777权限
关闭某PID进程:kill -端口号 PID lsof -i:8080 --查看8080端口占用
docker命令: docker images 查看镜像 pull 拉取 docker run -d -p 运行 docker ps 正在运行镜像
docker logs 日志 DockerCompose 服务编排
git常用命令: git init 初始化 git add 从工作区,添加到版本库 git commit -m”xxx” 从暂存区,添加到分支
git status 查看状态 git log 查看版本库的日志 git reflog 查看所有日志 git reset —head 版本号—-切换
git stash 保存 git stash list 查看 “某个地方” 存储的所有记录 git stash clear 清空 “某个地方”
git branch dev—————创建分支
git branch -d dev———-删除分支
git checkout dev————切换分支
git merge dev—————-合并分支
git branch———————查看所有分支
git clone https:xxx——-克隆
git add origin https:xxx-起个别名
git push origin dev ——添加到dev分支
git pull origin master—拉代码
git fetch origin master-去仓库获取
git merge origin/master-和网上下的master分支合并