123456

发布于:2024-05-19 ⋅ 阅读:(150) ⋅ 点赞:(0)

Java基础

说说封装、继承和多态

封装、继承和多态是Java面向对象编程的三个重要概念。

封装是指将数据和方法封装在一个类中,通过访问控制修饰符来控制对数据的访问权限。通过封装,可以隐藏实现细节,提高代码的安全性和可维护性。

继承是指一个类可以继承另一个类的属性和方法。被继承的类称为父类或超类,继承的类称为子类或派生类。子类可以重写父类的方法,实现自己的逻辑。通过继承,可以实现代码的重用和扩展。

多态是指同一个方法在不同的对象上可以有不同的行为。多态通过继承和重写实现。父类的引用可以指向子类的对象,通过父类的引用调用同名的方法时,根据实际的对象类型确定调用哪个类的方法。多态可以提高代码的灵活性和可扩展性。

综上所述,封装、继承和多态是Java面向对象编程的基本概念,它们可以使代码更加模块化、可维护和可扩展。

重载和重写的区别

  • 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行的多态性。
  • 重载发生在一个类中,重写发生在子类与父类之间。
  • 参数方面:重写的话要求我们的参数必须一致,而重载则是侧重于同一个方法兼容不同的参数类型和参数个数。
  • 返回值:重写的时候我们的返回值可以相同也可以是类返回类型的子类,重载的时候在参数类型或者个数不同的情况下可以是任何返回类型。
  • 修饰权限:重写的访问权限是不能比父类更加的严格的,重载的访问权限修饰符可以是任意的。

Java集合类

          Collection 接口
          /      |       \
        List    Set     Queue
       /   \           /
ArrayList  LinkedList
            \
           Vector

             Map 接口
             /     |     \
          HashMap TreeMap LinkedHashMap
 

说说反射

Java中的反射是指在运行时动态地获取类的信息并操作类的成员(字段、方法、构造函数等)。通过反射,可以在程序运行时获取类的结构信息,并在运行时创建对象、调用方法、访问和修改字段等。

使用反射可以实现一些动态性较高的功能,如动态创建对象、动态调用方法、动态修改类的属性等。但是反射会带来一定的性能损耗,同时也破坏了类的封装性,因此在使用反射时需要谨慎考虑。

使用场景

反射的使用场景有很多,以下是比较常见的几种反射的使用场景:

  1. 编程开发工具的代码提示,如 IDEA 或 Eclipse 等,在写代码时会有代码(属性或方法名)提示,这就是通过反射实现的。
  2. 很多知名的框架如 Spring,为了让程序更简洁、更优雅,以及功能更丰富,也会使用到反射,比如 Spring 中的依赖注入就是通过反射实现的。
  3. 数据库连接框架也会使用反射来实现调用不同类型的数据库(驱动)。

优缺点分析

反射的优点如下:

  1. 灵活性:使用反射可以在运行时动态加载类,而不需要在编译时就将类加载到程序中。这对于需要动态扩展程序功能的情况非常有用。
  2. 可扩展性:使用反射可以使程序更加灵活和可扩展,同时也可以提高程序的可维护性和可测试性。
  3. 实现更多功能:许多框架都使用反射来实现自动化配置和依赖注入等功能。例如,Spring 框架就使用反射来实现依赖注入。

反射的缺点如下:

  1. 性能问题:使用反射会带来一定的性能问题,因为反射需要在运行时动态获取类的信息,这比在编译时就获取信息要慢。
  2. 安全问题:使用反射可以访问和修改类的字段和方法,这可能会导致安全问题。因此,在使用反射时需要格外小心,确保不会对程序的安全性造成影响。

Java进阶

SpringAOP和 IOC

AOP是面向切面编程,它是对某一类事情的集中处理,它是一种思想,而Spring AOP是对这个思想的具体实现。

最具体的实现就是登录验证,除了登录和注册等几个功能不需要做用户登录验证之外,其他几乎所有页面调用的前端控制器(Controller)都需要先验证用户登录的状态。

IOC控制反转是Spring框架的一个核心特性,它通过将对象之间的依赖关系交由Spring容器来管理,实现了系统的松耦合和可扩展性。

AOP思想应用在测试中:

1.比如在写UI自动化的时候,我们在自动化方法的执行前,我们可能需要做一些数据准备或者清理,这个时候我们可以通过在方法的执行前插入切面,实现自动化数据的准备和清理,当时我写UI自动化是用py写的,用py封装的底层是java其中了解过自动化数据的准备和清理利用aop的思想和相应的技术来实现的。总体就是做一些前置的准备。

Spring,SpringBoot和SpringMVC的区别

Spring是一个轻量级的Java开发框架,提供了以依赖注入和面向切面编程为核心的功能。它提供了一种松散耦合的开发方式,使得应用程序的组件容易测试和维护。

Spring Boot是Spring框架的一种快速开发框架,它简化了Spring应用的配置和部署。Spring Boot使用约定大于配置的原则,通过自动配置和起步依赖简化了项目的搭建和开发过程。它集成了常用的第三方库和工具,提供了快速开发Web应用、微服务等的能力。

Spring MVC 是Spring生态系统中的一个Web框架,遵循模型-视图-控制器(MVC)的架构模式。它通过将应用程序逻辑分离为模型(数据)、视图(用户界面)和控制器(处理请求和管理数据流)三个组件,提供了一种结构化的构建Web应用程序的方法。Spring MVC被广泛用于开发Web应用程序,提供了请求映射、数据绑定和视图解析等功能。

JVM的类加载机制

JVM的类加载机制是指在运行Java程序时,JVM如何加载和初始化类的过程。

JVM的类加载机制分为三个步骤:加载、链接和初始化。

  1. 加载:加载是指查找并加载类的字节码文件。类加载器负责在运行时查找类的字节码文件,并将其加载到内存中。JVM内置了三个类加载器:启动类加载器、扩展类加载器和应用程序类加载器。启动类加载器负责加载JVM自身需要的类,扩展类加载器负责加载Java的扩展类库,应用程序类加载器负责加载用户自定义的类。

  2. 链接:链接是指将类的字节码文件中的符号引用转换为直接引用的过程,链接分为三个阶段验证:验证阶段检查字节码文件的合法性,确保其符合JVM规范;
    准备:准备阶段为类的静态变量分配内存,并设置默认值;
    解析:解析阶段将符号引用转换为直接引用,例如将方法调用转换为内存地址。

  3. 初始化:初始化是指执行类的初始化代码的过程。当类被加载和链接后,JVM会执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。类的初始化是在第一次使用类时才会触发,且只会执行一次。

双亲委派模型

双亲委派模型(又称父委派模型)是Java中的类加载机制的一种实现方式,用于保证类的唯一性和安全性。

在双亲委派模型中,类加载器之间形成了一种层次关系,除了顶层的启动类加载器外,每个类加载器都有一个父类加载器。当一个类加载器需要加载某个类时,它首先将这个任务委派给父类加载器,直到达到顶层的启动类加载器。只有当父加载器无法加载该类时,子加载器才会尝试加载。

优点

1.保证类的唯一性,当一个类被加载之后,它会被缓存在对应的类加载器的内存中,如果之后再次需要加载该类,加载器会直接返回缓存中的类,而不会重新加载。

2.保证了类的安全性,在加载一个类时,首先会由启动类加载器加载核心类库,这些类库经过严格审核和测试,保证了其安全性。如果用户自定义的类与核心类库中的类同名,由于父加载器优先加载的原则,用户自定义的类不会被加载,从而防止了恶意代码的执行。

双亲委派模型在JVM中的实现非常简单,每个类加载器在加载类时,会首先检查自己是否已经加载过这个类,如果没有则将加载任务交给父加载器。如果父加载器加载失败,则由子加载器尝试加载。这样层次结构的搜索机制保证了类的唯一性和安全性。

垃圾回收算法

划分垃圾

引用计数法

所谓的引用计数就是给我们的对象额外的增加一块区域用于计算这个对象被引用的次数每有一个引用引用该对象这个计数器就会加1,当有一个引用指向null,这个计数器就减1,如果计数器等于0了,那就释放该对象。

缺点:

1.空间利用率低:当我们对象本身就很小的时候此时加上一个计数器就会显得很浪费空间。

2.循环问题:

基于可达性分析

通过额外的线程,定期的针对整个内存空间的对象进行扫描。有一些起始位置(GCRoots),会类似于 深度优先遍历一样,把可以访问到的对象进行标记,能标记的就是可达对象,如果没标记就是不可达,就是垃圾。

回收方法

标记-清除
标记就是可达性标记,清除就是直接释放内存,释放之后的内存可能是不连续的,就是内存碎片,所以当内存碎片多了就会影响我们开辟大一点的内存,明明内存是够的但是因为内存碎片的存在就导致了开辟不了那么大的内存。

缺点:产生内存碎片,后期难以开辟大的内存空间。

复制算法
为了解决内存碎片,引入的复制算法。就是把申请的内存一分为二,然后不是垃圾的,拷贝到内存的另一边,然后把原来的一半内存空间整体都释放。

缺点:

  1. 内存空间利用率低
  2. 当保留的对象多,要释放的对象少,此时复制开销就很大。

标记整理

首先,通过可达性分析标记所有被引用的对象。然后,在整理阶段,将所有存活的对象向一端移动,并且清理掉末尾的垃圾对象。

缺点:但是这种方法依然不能解决复制搬运的开销问题,复制和搬运的开销依然很大。

分代回收

当前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。

FullGC

Full GC(Full Garbage Collection)是指对整个堆内存进行垃圾回收的过程。在进行 Full GC 时,会对年轻代和老年代(以及永久代或元数据区)中的所有对象进行回收。

Full GC 通常发生在以下情况之一:

  1. 显式触发:通过调用 System.gc() 方法显式触发垃圾回收。虽然调用该方法只是向 JVM 发出建议,但在某些情况下,JVM 可能会选择执行 Full GC。
  2. 老年代空间不足:当老年代空间不足时,无法进行对象的分配,会触发 Full GC。此时,Full GC 的目标是回收老年代中的无效对象,以释放空间供新的对象分配。
  3. 永久代或元数据区空间不足:在使用永久代(Java 8 之前)或元数据区(Java 8 及之后)存储类的元数据信息时,如果空间不足,会触发 Full GC。

Full GC 是一种较为耗时的操作,因为它需要扫描和回收整个堆内存。在 Full GC 过程中,应用程序的执行通常会暂停,这可能会导致较长的停顿时间(长时间的停顿会影响应用程序的响应性能)。 为了避免频繁的 Full GC,通常采取一些优化措施,如合理设置堆大小、调优垃圾回收参数、减少对象的创建和存活时间等。

synchronized升级过程

关键字的特性:

  • 互斥:某个线程执行到某个对象的synchronized中,其他对象如果也执行到同一个对象的synchronized中就会阻塞等待
  • 刷新内存(保证内存可见性):由于编译器优化,可能导致数据的读取并不会每次都从内存中读取,加上关键字后就会禁止这样的优化
  • 可重入:对于同一线程连加两次锁,并不会造成死锁。

我们的JVM把Synchronized分成了几个等级在不同的时候会是不同的锁,这几个过程分别是

无锁->偏向锁->轻量级锁->重量级锁

偏向锁 

偏向锁不是真的加了锁,而是在我们首个线程运行到此处的时候我们的偏向锁会给当前的对象头做一个标记,此时没有真正的加锁,当然也就避免了资源的消耗,因此此时我们就是一个单线程,既然是单线程又何必加锁呢,但是当我们后面有别的线程来的时候这个时候我们对象里面的锁标记会判断发现不是刚刚的线程,这个时候再不加锁的话就会出现线程的问题,所以这个时候就会加上刚刚标记里面的锁,因为之前标记好了所以加锁的速度很快,然后此时就变成了轻量级锁。

偏向锁的意义在于节省资源,在单线程的情况下没必要做无畏的资源浪费。

轻量级锁

接着我们就进入到了轻量级锁的阶段,此时我们认为当前的锁竞争还不是很激烈自适应为自旋锁,此时的轻量级线程是通过cas来实现的,竞争中的线程会一直的申请这个锁(因为线程不多竞争不激烈)。

自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源.
因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了.
也就是所谓的 "自适应"。

重量级锁

当我们的线程数量进一步的增多,竞争更加的激烈此时就会变成重量级锁,此处的重量级锁就是指用到内核提供的 mutex 。

此时我们执行加锁操作,进入操作系统的内核,然后去判定当前的锁是否被占用,如果没有被占用的话那就加锁然后返回用户态,如果当前的锁已经被占用的话,那就加锁失败此时的线程进入锁的等待队列,等待操作系统的唤醒,经过一系列的操作该锁被其它线程释放然后唤醒等待的线程,让其重新尝试获取该锁。

重量级锁的意义在于解决高竞争的情况但是相应的也会付出很多的资源,所以直到最后我们才开启重量级锁。

synchronized VS Lock

synchronized 和 Lock 主要的区别有以下几个方面:

  1. 锁的获取方式:synchronized 是隐式获取锁的,即在进入 synchronized 代码块或方法时自动获取锁,退出时自动释放锁;而 Lock 需要程序显式地获取锁和释放锁,即需要调用 lock() 方法获取锁,调用 unlock() 方法释放锁。
  2. 锁的性质:synchronized 是可重入的互斥锁,即同一个线程可以多次获得同一把锁,而且锁的释放也只能由获得锁的线程来释放;Lock 可以是可重入的互斥锁,也可以是非可重入的互斥锁,还可以是读写锁。
  3. 锁的粒度:synchronized 是以代码块和方法为单位进行加锁和解锁,而 Lock 可以精确地控制锁的范围,可以支持多个条件变量。
  4. 性能:在低并发的情况下,synchronized 的性能优于 Lock,因为 Lock 需要显式地获取和释放锁,而 synchronized 是在 JVM 层面实现的;在高并发的情况下,Lock 的性能可能优于 synchronized,因为 Lock 可以更好地支持高并发和读写分离的场景。

总的来说,synchronized 的使用更加简单,但是在某些场景下会受到性能的限制;而 Lock 则更加灵活,可以更精确地控制锁的范围和条件变量,但是使用起来比较繁琐。需要根据具体的业务场景和性能需求来选择使用哪种锁机制。

MySQL数据库

创建表的语句

create table if not exists 表名(
    字段1 类型1 约束,
    字段2 类型2 约束,
    ...
);

create table products (
    id int primary key auto_increment,
    name varchar(50) not null,
    price decimal(10,2) default 0
);

MySQL索引的类型有哪些分别适用于哪些场景

按照字段分

主键索引:数据列不允许重复,不允许为NULL,一个表只有一个主键。

唯一索引:数据列不允许重复,允许为NULL,一个表允许多个列创建唯一索引。

普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值。

全文索引:是目前搜索引擎使用的一种关键技术,对文本的内容进行分词、搜索。

类型分类:

  1. B-Tree索引:适用于等值查询、范围查询和排序操作。B-Tree索引可以按照索引列的顺序存储数据,并且支持快速的查找和插入操作。

  2. 哈希索引:适用于等值查询。哈希索引通过哈希算法将索引列的值映射为哈希码,并将哈希码与对应的数据行关联。但是哈希索引不适用于范围查询和排序操作。

  3. 全文索引:适用于在文本数据中进行全文搜索。全文索引可以快速查找包含指定关键词的文档。

  4. 空间索引:适用于存储和查询具有空间维度的数据,例如地理位置数据。MySQL的R-Tree索引可以处理空间数据。

聚簇索引:

按照主键或唯一约束条件来组织数据,查询效率高,但插入、更新和删除操作可能会影响写入性能。

非聚簇索引:

独立于表的物理存储方式,可以包含多个索引,查询效率相对于聚簇索引较低,但对插入、更新和删除操作的影响较小。

二者核心区别:数据和索引是否绑定在一起,聚簇索引是绑定在一起的而非聚簇索引是没有绑定在一起的。

索引失效的场景有哪些

1.违背最左原则的时候

最左匹配原则是指在使用复合索引(也叫联合索引),即由多个列组成的索引时,只有从索引的最左边的列开始进行查询,才能利用到该复合索引。

例如,如果有一个联合索引为 (col1, col2, col3),那么只有按照以下顺序进行查询才能利用该索引:

  • col1
  • col1, col2
  • col1, col2, col3

而如果查询是这样的:

  • col2
  • col2, col3

则不能使用该复合索引。

2.列参数运算的时候

如果索引列使用了运算,那么索引也会失效,如下图所示: 

image.png

 3.查询列使用函数方法的时候

查询列如果使用任意 MySQL 提供的函数就会导致索引失效,比如以下列使用了 ifnull 函数之后的执行计划如下: 

image.png

4.索引列发生类型转换的时候

5. 模糊匹配的时候%在最前面的时候。

sql查询过慢如何排查以及优化

1.查看慢查询的日志文件有没有打开

show variables like ‘slow_query_log’;

2.因为默认是没有开启的所以一般我们需要手动的去开启

set global slow_query_log=on;

3.查看慢查询的时间

show variables like ‘long_query_time’;

4.explain查看分析SQL执行计划

explain select * from log

5.通过explain分析具体是那一条sql的问题

explain 具体的sql语句

优化方式

  1. 学会使用explain关键字分析查询语句的执行计划
  2. 在MySQL中,可以使用EXPLAIN关键字来分析查询语句的执行计划,以帮助优化查询性能。EXPLAIN语句返回一个描述查询执行计划的结果集,包括了查询的操作顺序、使用的索引、表之间的连接方式等信息。
  3. 是否索引失效了
  4. 是否进行了全表扫描
  5. 是否有seect * 这种字段
  6. 表的结构是否存在问题
  7. 是否存在联表查询表的数量过多出现笛卡尔积这种

char和varchar区别

  • char存储固定长度字符串类型;varchar存储可变长度字符串类型
  • char在存储空间上比varchar更占用空间,但在查询和排序操作可能会更快。
  • 大数据量做检索时,char会更快。

索引的数据结构B+树

不适合的数据结构

能提高搜索效率的数据结构有二叉搜索树哈希表,但两者都并不适合作为数据库的索引:

二叉搜索树在最坏的情况下,树高将会非常高,导致磁盘的IO次数过多,查询效率也很低;

哈希表的搜索时间复杂度虽然为O(1),但是其只适用于值相等的情况,不适用于> < 的情况;

B+树的优点

1、所有数据都存储在叶子节点上,非叶子节点只存储索引信息,这样可以减少非叶子节点的存储空间,提高效率。

2、叶子节点之间使用指针连接,形成一个有序的链表,这样可以方便范围查询和排序操作。

3、所有叶子节点之间高度相同,可以形成一个平衡的树结构,这样可以保证查询的性能稳定,不会受到数据分布的影响。

计算机网络

七层模型/五层模型

快速了解网络原理_~小明学编程的博客-CSDN博客

TCP和UDP的区别

有无连接:TCP 是面向连接的协议,需要在客户端和服务器之间建立一个稳定的连接,然后再进行数据传输;而 UDP 是无连接的协议,数据包可以直接发送给目标主机,不需要事先建立连接。

字节流与数据报:TCP传输的是字节流而UDP传输的是数据报。

可靠性:TCP 采用可靠的数据传输方式,即在传输过程中使用序号、确认号和重传机制等控制手段来保证数据的可靠传输;而 UDP 采用不可靠的数据传输方式,数据包可能会丢失或重复,不提供数据可靠性保障。

数据传输效率不同:由于 TCP 需要进行连接、序号确认等额外的数据包传输,因此在数据传输效率方面相对于 UDP 要低一些。

应用场景不同:TCP 适用于要求数据传输可靠性高的场景,如网页浏览、文件下载、电子邮件等;而 UDP 适用于实时性要求较高的场景,如视频会议、在线游戏等。

TCP请求携带什么可以保证可靠性

序列号:用于标识每一个发送的数据段,保证数据能够按照正确的顺序到达接收方。

确认号:用于确认接收到的数据,接收方将已接受的数据的下一个序列号发送给对方。

校验和:用于检查数据在传输过程中是否出现错误或损坏。

窗口大小:用于控制发送方发送数据的速度,接收方会根据自己的处理能力和网络状况动态地调整窗口大小,以保证数据能够及时传输。

TCP粘包问题怎么解决

1、设置消息边界:在发送数据时,在消息末尾加上特殊字符或特殊标记作为消息边界。

2、消息定长:在发送数据时,将数据按照固定长度进行分割,每个消息的长度都相同,接收方按照消息长度进行接收和处理。

3、使用消息头:在发送数据时,将消息的长度添加到消息头中发送,接收方先接收消息头,再根据消息头中的长度信息接收信息体。

URL的执行流程

1.解析协议

URL 以协议开头,如 http://、https://、ftp:// 等。浏览器会根据协议类型来决定采用何种方式获取资源。

2.解析域名

URL 中包含了一个域名或 IP 地址,浏览器需要解析这个域名或 IP 地址,将其转换为 IP 地址。这个过程通常包含了以下步骤:

  1. 浏览器首先会查询本地 DNS 缓存,看是否有缓存该域名对应的 IP 地址。

  2. 如果本地 DNS 缓存中没有该域名对应的 IP 地址,则浏览器会向本地 DNS 服务器发出请求,以获取域名对应的 IP 地址。

  3. 如果本地 DNS 服务器也没有该域名对应的 IP 地址,则会向根 DNS 服务器发送请求,以获取该域名的权威 DNS 服务器。

  4. 接着本地 DNS 服务器会向权威 DNS 服务器发出请求,获取该域名对应的 IP 地址,并将结果返回给浏览器。

3.解析端口号

URL中可以包含端口号,如果没有指定端口号,浏览器会默认使用协议的默认端口号。

4.解析路径

URL中包含了资源的路径,浏览器会向服务器请求这个路径对应的资源。

5.传递查询字符串

URL 中还可以包含查询字符串,它是用来传递参数的。浏览器会将查询字符串中的参数发送给服务器,以便服务器进行处理。

6.解析锚点

URL 中还可以包含锚点,用于定位页面的特定位置。浏览器会将锚点信息保存在浏览器历史记录中,以便用户在后续访问时能够直接跳转到指定位置。

7.请求资源

当浏览器解析完 URL 之后,就会向服务器发送请求,请求相应的资源。

8.接收资源

服务器接收到浏览器的请求之后,会返回相应的资源。浏览器会将资源下载到本地,然后解析和渲染页面,最终呈现给用户。

get请求和post请求的区别

1.数据传输方式

GET 请求的数据是通过 URL 传递的,即将数据拼接到 URL 的后面,以 ? 分隔,参数之间以 & 符号分隔。因此, GET 请求对于传输的数据大小是有限制的,通常在几千个字符之内。而 POST 请求则是将数据放在 HTTP 请求的请求体中进行传输,没有大小限制,可传输较大的数据量。

2.用途

get一般用于获取数据而post一般用来提交数据。

3.幂等

get是幂等的而post是不幂等的(不幂等就是多次执行相同操作,结果不同)

4.缓存

get请求可以被缓存但是post请求不能被缓存。

http和https的区别

  • 安全性:

    • HTTP:是一种明文协议,所有数据在传输过程中都是以纯文本形式发送的。这意味着在网络上传输的数据可以被窃听和截获,存在安全风险,特别是对于敏感信息(例如登录凭据、信用卡号等)的传输。
    • HTTPS:是一种通过SSL/TLS加密的安全协议。在HTTPS中,传输的数据是经过加密的,因此更加安全,即使被截获,也很难被破解。因此,HTTPS适用于需要保护敏感信息的网站,例如银行、电子商务网站等。
  • 默认端口:

    • HTTP:默认使用端口号80。
    • HTTPS:默认使用端口号443。
  • 证书要求:

    • HTTP:不需要证书,任何网站都可以使用HTTP协议。
    • HTTPS:使用HTTPS协议的网站需要获得一个SSL证书,证书由受信任的证书颁发机构(CA)颁发。SSL证书用于验证网站的身份,并确保通信过程中的数据安全。
  • 性能:

    • HTTP:由于不涉及加密解密过程,相对于HTTPS,通常具有更好的性能。
    • HTTPS:由于涉及加密解密操作,可能会稍微增加服务器的负担,导致稍微降低一些性能。但现代计算机和服务器的性能已经相当强大,一般用户很难察觉到性能差异。

https的加密过程

  1. 握手过程: 在客户端(通常是Web浏览器)请求HTTPS页面时,服务器会返回一个包含公钥的数字证书。该证书是由数字证书颁发机构(CA)签发的,用于验证服务器的身份。

  2. 证书验证: 客户端收到服务器的证书后,会验证该证书的有效性。验证包括检查证书的签发者是否可信、证书是否过期等。如果证书有效,客户端就可以信任服务器的身份。

  3. 密钥交换: 客户端接下来会生成一个临时的对称密钥(也称为会话密钥),用于加密数据。然后,客户端使用服务器的公钥对该对称密钥进行加密,然后将加密后的密钥发送给服务器。

  4. 加密通信: 服务器收到加密后的对称密钥后,使用自己的私钥进行解密,得到对称密钥。之后,客户端和服务器都使用这个对称密钥来加密和解密数据,以保证通信的机密性和完整性。

总体而言就是先非对称加密然后对称加密。

操作系统

进程和线程的区别

  • 进程是操作系统分配资源的基本单位,也是竞争计算机系统资源的基本单位线程是最小的执行单元,是进程内部调度的基本单位。
  • 一个进程可以有多个线程,但是一个线程只属于一个进程。
  • 线程共享进程的地址空间,而进程之间的空间是相互独立的。
  • 多进程要比多线程健壮,一个进程崩溃之后,不会对其他进程产生影响,但是一个线程崩溃之后, 整个进程都会崩溃。

进程间的通信

  1. 管道(Pipe):管道是一种半双工的通信方式,可以在具有亲缘关系的进程之间进行通信。它可以分为匿名管道和命名管道。匿名管道只能在具有父子关系的进程之间使用,而命名管道可以在无关进程之间使用。

  2. 共享内存(Shared Memory):共享内存是一种高效的进程间通信方式,它允许多个进程访问同一块内存区域。进程可以通过读写共享内存来进行通信,而无需进行显式的数据拷贝。

  3. 消息队列(Message Queue):消息队列是一种进程间通信的方式,通过在内核中创建一个消息队列,进程可以将消息发送到队列中,其他进程可以从队列中读取消息。消息队列可以实现进程之间的异步通信。

  4. 信号量(Semaphore):信号量是一种同步机制,用于控制多个进程对共享资源的访问。进程可以使用信号量来进行互斥操作,避免对共享资源的竞争。

  5. 套接字(Socket):套接字是一种网络通信的方式,可以在不同主机上的进程之间进行通信。套接字可以通过TCP或UDP协议进行通信,可以实现进程间的远程通信。

常用Linux命令

  • ls:列出当前目录中包含的文件和目录
  • ll:用列表的方式来列出目录中的内容
  • pwd:显示当前目录的绝对路径
  • cd:切换到指定目录
  • touch:创建一个空文件
  • cat:查看文件内容
  • echo:打印内容到控制台,也可用于写文件
  • mkdir:创建目录
  • rm:删除
  • cp:复制文件或目录(复制目录要加上-r选项)
  • mv:移动目录或文件(或者给文件重命名)
  • find:查找文件
  • kill -9:杀死进程 -9为强制终止信号,可以强制杀死进程
  • netstat -tulp | grep 8080:查看8080端口的进程,tcp,udp,监听状态,进程ID
  • ps aux | grep java:查看所有Java进程
  • nohup java -jar xxx.jar &:后台启动Spring Boot项目
  • tail -f filename:查看日志(默认最后10行)
  • cat -n filename | grep “关键字”:查看日志
  • chmod 777 a.txt:更改a.txt文件的权限值为777
  • nslookup:查看DNS解析结果
  • telnet/netcat  ip地址  端口号                                 查看对方端口有没有打开

常见查日志的命令

  1. tail命令:用于查看日志文件的末尾内容。例如,tail -f /var/log/syslog可以实时显示系统日志文件的最新内容。

  2. cat命令:用于查看整个日志文件的内容。例如,cat /var/log/messages可以显示所有系统消息的日志文件内容。

  3. less命令:用于分页查看较大的日志文件。例如,less /var/log/auth.log可以在分页模式下查看认证日志文件的内容。

  4. grep命令:用于在日志文件中搜索指定的关键词。例如,grep "error" /var/log/syslog可以查找系统日志中包含"error"关键词的行。

  5. head命令:用于显示文件的开头内容,默认显示前10行。 例如:head /var/log/syslog,其中-n 100代表显示前100行内容,-c表示字节

vim


vim 是一个知名的文本编辑器. 前面学习的 cat, less, head, tail 等命令只能查看文本, 不能编辑文本. 使用 vim 就可以进行编辑了。

1.打开/创建文件:

vim + 文件名

2.进入插入模式:

vim 打开文件后默认是普通模式. 普通模式下键盘的按键表示一些特殊功能的快捷键
使用 i 键可以进入到插入模式. (左下角提示 --INSERT-- ) 然后就可以像记事本一样正常编辑了。

3.保存:

在插入模式下不能保存文件, 需要先回到 普通模式 . 按下 Esc 回到普通模式.
在普通模式下输入 :w , 再按下回车, 即可保存文件
4.退出:

在插入模式下不能退出, 需要先回到 普通模式.
在普通模式下输入 :q , 再按下回车, 即可退出,我们也可以:wq同时进行保存和退出。
 

进程的状态有哪些

1.新建状态(New):进程刚刚被创建,操作系统正在为其分配资源。

2.就绪状态(Ready):进程已经准备好运行,等待CPU分配时间片。

3.运行状态(Running):CPU正在执行进程的指令。

4.阻塞状态(Blocked):进程在等待某些事件发生,如等待IO操作完成或等待某个信号量被释放等。

5.终止状态(Terminated):进程执行完成或者被操作系统强制终止,释放所有资源。

Redis

Redis的数据类型

  1. 字符串(String):用于存储单个值,可以是字符串、整数或浮点数。适用于缓存、计数器、分布式锁等场景。

  2. 列表(List):按插入顺序存储多个值的有序集合。适用于消息队列、最新消息列表等场景。

  3. 哈希(Hash):存储多个键值对的无序集合。适用于存储对象、用户信息、配置信息等场景。

  4. 集合(Set):存储多个唯一的、无序的元素的集合。适用于存储标签、好友列表、点赞用户等场景。

  5. 有序集合(Sorted Set):存储多个成员及其对应的分值的有序集合。适用于排行榜、热门文章列表等场景。

Redis事务

Redis 事务在特定条件下,才具备一定的原子性 

Redis 的事务模式具备如下特点:

  • 保证隔离性;
  • 无法保证持久性;
  • 具备了一定的原子性,但不支持回滚
  • 一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。

原子性:在命令出错的情况下保证原子性,语法错误不保证原子性。

事务的原理是将一个事务范围内的若干命令发送给Redis,然后再让Redis依次执行这些命令。

事务的生命周期:

  1. 使用MULTI开启一个事务

  2. 在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行

  3. EXEC命令进行提交事务

一个事务范围内某个命令出错不会影响其他命令的执行,不保证原子性:

127.0.0.1:6379> multi 
OK
127.0.0.1:6379(TX)> set aa aaa
QUEUED
127.0.0.1:6379(TX)> set aa bbb
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
127.0.0.1:6379> keys *
1) "jd:jdiuf:jeriuhfr"
2) "name"
3) "aa"
4) "huang"
5) "mylist"
127.0.0.1:6379> get aa
"bbb"

WATCH命令

WATCH命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行(类似于乐观锁)。执行EXEC命令之后,就会自动取消监控。

redis常见的命令

set:设置指定键的值。

SET key1  value1


get:获取指定键的值。

GET key1

del:删除指定键。

DEL key1

exists:检查指定键是否存在。

EXISTS key1

incr:将指定键的值递增1。

incr key

decr:将指定键的值递减1。

decr key

hset:在哈希中设置指定字段的值。

在哈希"user"中设置字段"name"的值为"John"

HSET user name John

hget:获取哈希中指定字段的值。

获取哈希"user"中字段"name"的值。

HGET user name

lpush:将一个或多个值插入列表的左侧。

LPUSH mylist item1

rpush:将一个或多个值插入列表的右侧。

lrange:获取列表中指定范围的元素。

lpop:移除并获取列表左侧的元素。

rpop:移除并获取列表右侧的元素。

sadd:向集合添加一个或多个成员。

smembers:获取集合中的所有成员。

srem:从集合中移除一个或多个成员。

zadd:向有序集合添加一个或多个成员。

zrange:获取有序集合中指定范围的成员。

zscore:获取有序集合中指定成员的分值。

expire:设置键的过期时间。

为什么Redis这么快

  • 数据存储在内存中:Redis 的数据存储在内存中,而内存的读写速度远远快于硬盘。这使得 Redis 能够实现非常快速的读写操作。
  • 单线程处理请求:Redis 是单线程的,因此可以避免线程切换和锁竞争等问题,提高了 CPU 的利用率和性能。
  • 高效的数据结构:Redis 提供了多种高效的数据结构,如哈希表、有序集合等,这些数据结构能够快速地进行插入、删除、查找和排序等操作。
  • 异步 I/O:Redis 使用异步 I/O 技术,可以在等待客户端输入或输出时继续处理其他请求,从而提高了系统的吞吐量。
  • 高效的持久化机制:Redis 提供了多种持久化机制,如 RDB、AOF 和混合持久化机制,这些机制运行都非常高效,可以在不影响性能的情况下保证数据的安全。

Redis可以实现哪些功能

  • 缓存:Redis 可以作为缓存系统,将热点数据存储在内存中,提高读写性能和响应速度,减少对后端数据存储的压力。
  • 消息队列:Redis 的发布订阅功能和 List 数据结构可以实现消息队列的功能,实现异步处理任务、解耦系统组件之间的依赖关系等。
  • 计数器和排行榜:Redis 的原子操作和 Sorted Set 数据结构可以实现计数器和排行榜的功能,支持快速地增加、减少和排序操作。
  • 分布式锁:Redis 的 SETNX 命令可以实现分布式锁,避免多个客户端同时修改同一个数据,保证数据的一致性和正确性。
  • 分布式会话管理:Redis 可以存储会话信息,实现分布式会话管理,支持会话的共享和迁移等功能。

Redis的分布式锁

实现原理

首先我们的redis是天然的支持分布式的,因此使用redis加锁我们默认都是分布式的。

在 Redis 中实现分布式锁可以使用 SETNX 和 EXPIRE 命令来实现,SETNX 是 "SET if Not eXists" 的缩写,是一个原子性操作,用于在指定的 key 不存在时设置 key 的值。如果 key 已经存在,SETNX 操作将不做任何事情,返回失败;如果 key 不存在,SETNX 操作会设置 key 的值,并返回成功。而 EXPIRE 是设置锁的过期时间的,主要为了防止死锁的发生。

set key value nx ex 锁的过期时间

分布式锁的问题

死锁问题:当我们的某一个线程对我们的key加了锁之后但是加完锁之后这个线程却挂了,那么这个线程不就不能释放这个锁了吗,同时别的线程也获取不到这个锁了,这个时候就产生了我们的死锁问题。

解决方案:解决的方案很简单就是设置一个过期的时间,这样即使我们的线程挂了等过期了之后redis将会自动删除如此一来就不会产生死锁了。

ABA问题:当我们的锁的过期时间是10s,但是我们线程1的执行时间是15s的时候,此时10s后锁过期线程2获取到锁,然后在15s的时候我们的线程1执行完毕删除锁,这个是不符合预期的,此时删除了我们线程2的锁。

解决方案:此时可以每个锁的 value 中添加拥有者的标识,删除之前先判断是否是自己的锁,如果是则删除,否则不删除。但是判断和删除之间不是原子性操作,所以依然有问题。此时可以使用 lua 脚本来判断并删除锁,lua 脚本可以保证 redis 中多条语句执行的原子性,所以就可以解决此问题了。

Redis持久化数据

Redis 持久化有以下 3 种实现方式:

  1. 快照方式(RDB, Redis DataBase)将某一个时刻的内存数据,以二进制的方式写入磁盘;
  2. 文件追加方式(AOF, Append Only File),记录所有的操作命令,并以文本的形式追加到文件中;
  3. 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险。

RDB 与 AOP 的区别

  • 存储方式:RDB 是通过快照(snapshot)机制,将 Redis 中的数据集以二进制文件的方式写入硬盘;AOF 则是通过将 Redis 服务器执行的所有写命令(例如 set、del、incrby 等)记录在 AOF 文件中,写入方式是追加写入。
  • 数据恢复:当 Redis 重启时,可以根据 RDB 文件或 AOF 文件来恢复数据。恢复 RDB 文件比恢复 AOF 文件快,因为 RDB 文件包含了一个时间点上的快照,可以直接将整个数据集加载到内存中。而恢复 AOF 文件则需要逐条执行文件中记录的命令,需要更长的时间。
  • 数据完整性:RDB 文件保存的是 Redis 在某个时间点的数据快照,如果 Redis 在快照操作之后宕机,可能会丢失最后一次快照后的数据。而 AOF 文件记录了 Redis 所有的写命令,因此即使 Redis 宕机,也可以根据 AOF 文件恢复数据。
  • 文件大小:RDB 文件通常比 AOF 文件小,因为它只保存了一个时间点的数据快照,而 AOF 文件保存了所有的写命令,会比 RDB 文件大。
  • 性能影响:AOF 文件追加写入方式可能会降低 Redis 的写性能,但可以提供更好的数据安全性,而 RDB 文件在进行快照时可能会阻塞 Redis 的服务。

Redis的过期删除策略

Redis 中有两种过期删除策略:

  1. 定期删除策略(定时任务方式):Redis 会定期地(默认每秒钟检查 10 次)随机抽取一部分设置了过期时间的键,检查它们是否过期,如果过期则删除。该策略可以通过配置文件中的 hz 参数进行调整。
  2. 惰性删除策略(懒汉式方式):当访问一个键时,Redis 会先检查该键是否过期,如果过期则删除。这意味着过期键可能会在访问时被删除,而不是在过期时立即删除。

Redis 定期删除策略并不会遍历删除每个过期键,而是采用随机抽取的方式删除过期键,同时为了保证过期扫描不影响 Redis 主业务,Redis 的定期删除策略中还提供了最大执行时间,以保证 Redis 正常并高效的运行。

Redis 定期删除流程如下:

  1. 从设置了过期时间的字典中随机取出 20 个键;
  2. 删除这 20 个键中过期的键;
  3. 如果过期 key 的比例超过 25% ,重复步骤 1。

同时为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。 

缓存穿透

缓存穿透是指查询一个一定不存在的数据(这个数据无论是在redis还是在数据库中都不存在),由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决方案

布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。

缓存null值:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,一来当我们再查询到这个空值的时候直接就可以从redis返回了。

问题:

  1. 空值做了缓存,意味着缓存中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。

  2. 缓存和存储的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如:过期时间设置为 5分钟,如果此时存储添加了这个数据,那此段时间就会出现缓存和存储数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

缓存击穿

缓存击穿是指一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!

解决方案

1.设置热点key永不过期

2.加互斥锁:在查询持久层数据库时,保证了只有一个线程能够进行持久层数据查询,其他的线程让它睡眠几百毫秒,等待第一个线程查询完会回写到Redis缓存当中,剩下的线程可以正常查询Redis缓存,就不存在大量请求去冲击持久层数据库了!

缓存雪崩

缓存雪崩是指在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉!

解决方案

  1. 加锁排队:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待;

  2. 数据预热:可以通过缓存 reload 机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀;

  3. 做二级缓存,或者双缓存策略:Cache1 为原始缓存,Cache2 为拷贝缓存,Cache1 失效时,可以访问 Cache2,Cache1 缓存失效时间设置为短期,Cache2 设置为长期。

  4. 在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。

如何保证缓存和数据库数据的一致性

  1. 缓存更新策略:当数据库中的数据发生变化时,需要及时更新缓存,可以采用主动更新和被动更新两种策略。主动更新是在数据库更新后,直接更新缓存中对应的数据;被动更新是在下一次查询缓存时,发现缓存已过期,重新从数据库中读取最新数据,并更新缓存。

  2. 双写一致性:双写一致性是指在更新数据库数据之前,先更新缓存数据,确保缓存和数据库中的数据保持一致。可以使用事务或者分布式锁来保证缓存和数据库的原子性操作。

  3. 延迟双删策略:在更新数据库数据后,先删除缓存中的数据,再进行数据库更新操作,这样可以保证在数据库更新期间,缓存中的数据已被删除,下一次查询时会从数据库中读取最新数据,并更新缓存。

Redis的分布式部署

Redis 有集群(功能),它的多机部署有以下 3 种:

  1. 主从同步:主从同步 (主从复制) 是 Redis 高可用服务的基石,也是多机运行中最基础的一个。我们把主要存储数据的节点叫做主节点 (master),把其他通过复制主节点数据的副本节点叫做从节点 (slave)。在 Redis 中一个主节点可以拥有多个从节点,一个从节点也可以是其他服务器的主节点;
  2. 哨兵模式:哨兵模式 Redis Sentinel 是 Redis 的一种运行模式,它专注于对 Redis 实例(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主及主从切换,实现故障转移,确保整个 Redis 系统的可用性;
  3. 集群模式:集群模式 Redis Cluster 是 Redis 3.0 版本推出的 Redis 集群方案,它将数据分布在不同的服务区上,以此来降低系统对单主节点的依赖,并且可以大大的提高 Redis 服务的读写性能。

MySQL和Redis的使用场景以及区别

MySQL的使用场景

  1. 事务性应用:MySQL适用于需要强一致性和事务支持的应用,如电子商务平台、金融系统等。
  2. 大规模数据存储:MySQL可以处理大量的结构化数据,适用于数据量较大的应用,如数据分析、报表生成等。
  3. 复杂查询和关联操作:MySQL支持复杂的SQL查询和关联操作,适用于需要多表关联查询的应用。
  4. 数据持久化:MySQL将数据持久化到磁盘,保证数据的长期存储和可靠性。

Redis的使用场景

  1. 缓存:Redis适用于高速缓存,可提供快速访问和响应时间,减轻后端数据库的负载。
  2. 会话管理:Redis可以用于存储和管理用户会话信息,实现状态共享和分布式会话管理。
  3. 消息队列:Redis支持发布/订阅模式,可以作为消息队列使用,实现异步消息处理和解耦应用组件。
  4. 计数器和排行榜:Redis的原子操作和高性能使其适用于实时计数器和排行榜的实现。

消息队列

什么是kafka?

Kafka 是一个分布式消息队列。Kafka 对消息保存是根据 Topic 进行归类,发送消息 者称为 Producer,消息接受者称为 Consumer,此外 kafka 集群有多个 kafka 实例组成,每个实例(server)称为 broker。

kafka的优点

消息系统

与传统的消息中间件一样,kafka也具有系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、可恢复性等功能,但是kafka还具有其他消息队列难以做到的消息顺序性保障及回溯性消费的功能。

系统解耦:

  • kafka作为消息中间件是允许我们异步通信的,生产者将消息发布到kafka的主题(topic)中,然后消费者将根据需要从topic中订阅或者消费信息,如此一来就大大降低了系统之间的依赖性,提高了系统的稳定性和可扩展性。
  • 解耦生产者和消费者之间的关系,如果没有消息队列的话生产者直接对接消费者,这样一来的话如果双方有何改动或者更新的话还需要考虑彼此,有了消息中间件就避免了这种情况的产生。

流量削峰:

  • 增加分区数:通过增加Kafka主题的分区数,可以提高Kafka集群的并行处理能力,从而更好地分摊高峰时段的流量。

  • 使用分区副本:通过为每个分区增加副本,可以将读写请求分散到多个副本上,从而提高集群的读写吞吐量。

  • 使用消费者组:将消费者分组,每个消费者组只处理一部分消息,这样可以将高峰时段的流量分散到多个消费者组上,提高整体处理能力。

消息的顺序性:

  • kafka的每个topic是有多个分区的,每个分区相当于是一个有序队列,当多个生产者并发发送消息到同一个分区时,Kafka会根据消息的关键字进行分区,并保证相同关键字的消息被写入同一个分区,从而保证消息的顺序性。

回溯性消费:

  • Kafka允许消费者根据需要进行回溯性消费,即可以消费之前已经消费过的消息。
  • 每个消费者在消费消息时,会维护一个消费的偏移量(Offset),表示已经消费的消息的位置。
  • 消费者可以通过设置偏移量来指定从哪个位置开始消费消息,从而实现回溯性消费。
  • Kafka会保留一定时间内的消息历史记录,消费者可以根据需要回溯到任意时间点的消息。
  • 回溯性消费可以用于重新处理之前的消息、进行数据分析或者进行消费者的故障恢复。

存储结构

每个Broker相当于是一个kafka的实例,每个Broker中会根据逻辑划分为不同的Topic,然后会对每个Topic进行分区,以此来实现负载均衡。

高吞吐量

Kafka通过顺序写入磁盘和批量压缩等技术,实现了高吞吐量的消息写入和读取,传统的写入方式包括顺序写入和随机写入,Kafka采用的是顺序写入,可以极大的提高速度,批量压缩技术就是将数据批量的进行压缩然后一起发送相当于100M的数据我们采用累计100M一次性发送和一次发送1M然后发送100次的区别显然前者的效率更加的高。

持久化

Kafka将消息持久化存储在磁盘上,即使在发生故障或重启后,消息也不会丢失,其中我们的当消息分发到partition的时候并不是直接就存到我们的磁盘上面的,Kafka会将数据进行一定量的缓存当数据缓存到指定的值的时候才会写到磁盘中去这就是批量压缩的概念。

可扩展性

Kafka的存储系统可以水平扩展,通过增加分区和增加存储节点来提高存储容量和吞吐量。

高可靠性

Kafka通过副本机制实现数据的冗余备份,保证了数据的可靠性和容错性,同时我们的生产者与消费者交互的时候都是与partition leader来进行交互的,当partition leader挂掉的时候会选举其它的副本充当partition leader,Kafka的选举机制是由ZooKeeper来进行维护的,详细过程如下:

  1. 当ZooKeeper发现某个Broker下线后,它会触发选举过程。选举过程的参与者包括所有在线的Broker。

  2. 在选举过程中,每个Broker都会尝试创建一个临时节点来竞选成为Leader。节点的路径中包含了一个递增的序列号,用于表示竞选的顺序。

  3. 当一个Broker成功创建了临时节点后,它会检查自己是否成为了最小序列号的节点。如果是,则该Broker成为新的Leader。

  4. 其他的Broker会监听ZooKeeper上临时节点的变化。一旦发现新的Leader产生,它们会更新自己的元数据信息,包括新的Leader的ID和地址等。

  5. 当宕机的Broker重新连接到ZooKeeper时,它会发现自己的临时节点已经不存在,从而意识到自己不再是Leader。它会重新加入集群,并接受新的Leader的指导。

Zookeeper:负责集群的元数据管理等功能,比如集群中有哪些 broker 节点以及 Topic,每个 Topic 又有哪些 Partition 等。

Kafka 消费过程分析

Kafka同时支持点对点和发布订阅两种消费模型。

  1. 点对点:也就是消息只能被一个消费者消费,消费完后消息删除

  2. 发布订阅:相当于广播模式,消息可以被所有消费者消费

消费者组

  • 如果消费者组中的某个消费者挂了,那么其中一个消费者可能就要消费两个partition了

  • kafka规定了每个 Partition 只能由消费组中的一个消费者进行消费

  • 如果只有三个partition,而消费者组有4个消费者,那么一个消费者会空闲

  • 如果多加入一个消费者组,无论是新增的消费者组还是原本的消费者组,都能消费topic的全部数据。(消费者组之间从逻辑上它们是独立的)

  • 一个topic有可能被多个消费者组订阅,但是一个一个分区同一时刻只能被一个消费者消费

消费过程

消费者在读的时候也很有讲究:正常的读磁盘数据是需要将内核态数据拷贝到用户态的,而Kafka 通过调用sendfile()直接从内核空间(DMA的)到内核空间(Socket的),少做了一步拷贝的操作。

消费者在消费的过程中会维护一个offset,Kafka就是用offset来表示消费者的消费进度到哪了,每个消费者会都有自己的offset,说白了offset就是表示消费者的消费进度

分区分配策略

range(范围)分配

当我们的消费者的数量小于分区数量的时候,排在前面的消费者将会分配到较多的partition如下图

图片

 当有4个分区的时候此时就可以均匀的分给这两个消费者没人两个了,但是这种分配将会产生一个问题那就是当我们的消费者组订阅多个topic的时候会产生如下的问题。

图片

 可以看到此时我们的消费者A总是订阅过多的partition当订阅的topic过多的时候这种情况就会越来越严重。

RoundRobin(轮询)分配

所谓轮询分配就是将我们所有的分区看作是一个整体,然后挨个的分给我们消费者组的每一个消费者。

图片

 由此以来就避免了我们上面的分配不均的问题了。

Sticky(粘性)策略

这个从字面看来意思就是粘性策略,大概是这个意思。主要考虑的是在分配均衡的前提下,让分区的分配更小的改动。

比如之前P0\P1分配给消费者A,那么下一次尽量还是分配给A。

这样的好处就是连接可以复用,要消费消息总是要和broker去连接的,如果能够保持上一次分配的分区的话,那么就不用频繁的销毁创建连接了。

kafka的缺点以及可能出现的问题

高热key问题

我们的消息在发送到topic的时候将会随机的发送到一个partition里面,随机的过程会对key进行hash这个key可能是我们的userid情况,也就是说如果我们的key活动比较的频繁就会导致消息全部都分发到同一个分区上面此时可能会导致我们的某一个broker负载过大。

无法弹性扩容

对partition的读写都在partition leader所在的broker,如果该broker压力过大,也无法通过新增broker来解决问题;

扩容成本高

集群中新增的broker只会处理新topic,如果要分担老topic-partition的压力,需要手动迁移partition,这时会占用大量集群带宽。

kafka对比其它mq

RocketMQ和Kafka一个显著的特点就是吞吐量非常的大,在如今的大数据时代吞吐量是非常的重要的。

接着就是对于消息可靠性RocketMQ和Kafka经过优化可以做到0丢失数据这也是ActiveMQ和RabbitMQ难以做到的。

图片

实习之Kafka问题

测试过程中遇到kafka相关的问题:

在我的第二个项目装修业务私单治理的测试过程中,因为是策略那边配置好的策略,所以我只要发布视频该条视频就能够进审,审核队列进审的时间过长。

生产者发送超时推断

一、资源不足,并且Kafka client的发送性能有限
通过公司Java在线诊断工具检测发现CPU资源大部分都被Kafka生产者的发送方法给占用了,猜测有两个地方可能有问题
    a. Kafka资源不足,消息吞纳不过来
    经oncall反馈,Kafka资源是够的,吞纳消息能力是正常的
    b. 我们发送端有问题
    我们资源不足了,需要更多的资源来发送消息;或者Kafka client的发送性能有限

二、存在部分topic分片数据倾斜的情况
每个topic下的分片都会分配到一定数量的broker实例,实例数量太少会导致资源分配不均匀,部分实例过载而部分实例资源有余。

消费者Lag过大判断

一、拉取指标太多消费不完
通过在线诊断工具检测发现内存大部分都消耗在指标消费的逻辑上,原先消费者是批量拉取,由于生产者的问题发送指标性能有限,因此推断指标拉取的太多导致对象堆积,所以最后YoungGC效率低下,大量对象都进入老年代导致FullGC频繁,GC时长变长

解决方法

消费者Lag过大问题
  1. 将批量消费改为单次拉取消费,使单位时间内拉取的指标少一点
生产者发送超时问题

不考虑扩资源,已经用了太多资源了

  1. 等消费者将指标消费Lag降低到稳定后将CPU资源让出来给生产者
  2. 考虑到topic的每个分片上可能会有数据倾斜的情况,我们尝试将我们的集群拆成多个小实例(40台12C20G-> 120台4C6G),将更多的资源分配去数据多的分片上
  3. 指标数据用lz4压缩后再发送给Kafka

测试开发

为什么需要自动化

1.节省人力,方便回归。

2.测试一些人力难以测试的场景,例如高频次的点击操作。

测试用例编写

登陆界面的测试用例

用户登录界面的测试用例分析_一个登录界面的测试用例-CSDN博客

微信红包测试用例

1.界面:微信红包的界面美观,界面的渲染,字段展示,

2.功能:表情包的选择功能,封面的选择功能,祝福语模块字数问题,金额模块输入负数,输入0,输入超过已经有的金额,金额,表情包,封面,缺少其中一个或者多个是否可以,成功发送之后是否正常减少已有金额。

3.适用性:是否会有指导。

4.兼容性:不同的平台是否可以正常使用这个功能。

5.性能:很多人用这个功能的时候服务器是否能扛得住压力。

微信朋友圈测试用例

微信发朋友圈测试用例_微信朋友圈测试用例-CSDN博客

淘宝购物车的测试用例

淘宝购物车的测试用例_淘宝购物车测试用例-CSDN博客

百度搜索的测试用例

以下是一些百度搜索的测试用例示例:

1. 正常搜索关键词:
   - 在搜索框中输入有效的关键词,如"测试用例",点击搜索按钮。
   - 验证搜索结果页面是否显示相关的搜索结果,并且与关键词相关的页面排名是否合理。

2. 搜索框为空时搜索:
   - 不输入任何关键词,直接点击搜索按钮。
   - 验证系统是否能正确提示关键词不能为空,并且不进行搜索操作。

3. 搜索结果为空:
   - 输入一个有效的关键词,但搜索结果为空。
   - 验证系统是否能正确显示搜索结果为空的提示信息。

4. 搜索结果分页:
   - 搜索一个关键词,查看搜索结果页面的分页功能。
   - 验证系统是否能正确显示分页导航,并且能够正确跳转到不同的搜索结果页。

5. 搜索结果排序:
   - 搜索一个关键词,查看搜索结果页面的排序功能。
   - 验证系统是否能正确显示排序选项,并且能够按照指定的排序方式对搜索结果进行排序。

6. 搜索结果过滤:
   - 搜索一个关键词,查看搜索结果页面的过滤功能。
   - 验证系统是否能正确显示过滤选项,并且能够按照指定的过滤条件对搜索结果进行过滤。

7. 搜索建议:
   - 在搜索框中输入部分关键词,查看搜索建议的提示内容。
   - 验证系统是否能正确显示相关的搜索建议,并且能够根据选择的建议进行搜索。

8. 相关搜索:
   - 查看搜索结果页面的相关搜索推荐内容。
   - 验证系统是否能正确显示与当前搜索关键词相关的其他搜索推荐。

9. 图片搜索:
   - 在搜索框中输入关键词,切换到图片搜索模式。
   - 验证系统是否能正确显示与关键词相关的图片搜索结果,并且能够进行图片的预览和下载。

10. 视频搜索:
    - 在搜索框中输入关键词,切换到视频搜索模式。
    - 验证系统是否能正确显示与关键词相关的视频搜索结果,并且能够进行视频的播放和分享。

这些是一些基本的测试用例示例,根据具体的业务需求和功能特点,可以进一步扩展和细化测试用例。

实习经历

遇到哪些问题以及怎么解决的

技术方面

工作中遇到的问题:

自动化:

1.跑自动化代码的时候,想要验证打标,也就是原来10个视频给一个视频打标之后应该是9个了,然后在跑代码的时候发现这个case没过。

排查:首先检查是否是我们的判罚工具失效了导致没有判罚成功,检查完没有问题,接着就是跑代码的时候打印个日志,发现先后又都是10没有变,通过观察发现是因为代码跑的太快了,数字没有及时的刷新所以没有边。

解决方案:在打了用户之后,刷新并且暂停一下然后获取当前元素解决问题。

业务测试:

1.刚开始测试的时候因为没有加黑白名单导致自己这边老是测不出来功能。

排查:想要检测一个有没有命中某个策略,然后去天狮策略平台查看某个事件但是查不到。因为此时还没有放量,想要测试某一个功能只能通过加白名单的方式去测试。

2.在线下测试过程中遇到私信的页面进不去,测试流程阻塞了,这个时候用charles做一个代理,抓取我们请求的包,然后发给开发人员看一下请求参数有没有问题,最终是因为测试环境的一个模块没有配置导致请求缺少参数,没有发送成功,最终添加相应的模块解决问题。

非技术方面

需求太多,导致效率低下。

排查问题

假如现在你遇到了一个页面渲染不出来了或者某个字段展示有问题你将会如何去排查呢?

1.首先需要明确问题,问题是在什么情况下产生的,是否真的是问题,是不是因为浏览器缓存,比如说前端已经修改了代码,但是因为你没有刷新缓存,或者你的页面还是昨天开的那个页面。

2.确定是问题了之后然后我会先用浏览器抓包看一下,养成这个好习惯,遇到问题先抓包看看是什么问题,这个时候需要简单的了解一下前端的结构,其实也不用过多了解,使用关键字在浏览器中搜索一下就行了,找到指定的包,然后再通过关键字搜索我们的响应基本定位到我们想要的字段了或者附近,接着基本可以根据字段的意思来得知这个字段的作用,当然如果是新开发的页面我们可以询问一下开发是否是这个字段,在我们找到这个字段之后可以观察是否缺失,如果没有缺失的话大概率就是前端的问题了,这个时候可以将截图+链接+描述发给前端人员,如果指定字段没有数据应该就是后端那边的问题了,接着找到负责这块的开发人员将链接+id+字段发给开发人员。

3.事后可以了解一下问题的原因,是配置问题还是代码逻辑,然后下次遇到的时候直接可以问定位的更加细节。

数据流转问题

你的一个业务的数据是如何流转的?

1.数据推送:因为是审核,首先我们得创建一条数据,一般会根据不同的情况用开发给的接口推或者给指定用户绑定策略推数据。

2.比如我们开发给的接口推数据,这个接口对应的那个方法只需要传递一个userid,然后方法中将这个用户的数据,推到策略平台。

3.策略平台会通过算法分析用户的行为,然后满足条件就调用sdk的一个异步方法并且塞一些数据进去比如证据或者视频接着将数据推到消息队列。

4.消费者去消费消息队列中的数据,我们的数据出现在我们的审核队列中去。

判罚维护一个level值

常见算法题汇总

字符串

1.验证IP地址的合理性

力扣

双指针

1.最长不重复字串

力扣

哈希

最长连续序列

二叉树

1.二叉树的中序遍历(非递归)

力扣

动态规划

1.最长重复字串

最长公共子串_牛客题霸_牛客网

2.最长回文字串

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

Git常见命令

  • git clone:从git服务器拉取代码
  • git config:配置开发者用户名和邮箱
  • git branch:创建、重命名、查看、删除项目分支
  • git checkout:切换分支
  • git status:查看文件变动状态
  • git add:添加文件变动到暂存区
  • git commit:提交文件变动到版本库
  • git push:将本地的代码改动推送到服务器
  • git pull:将服务器上的最新代码拉取到本地
  • git log:查看版本提交记录
  • git merge [branch]:合并分支

回滚操作

git log:查看版本提交记录

git checkout:切换到目标的版本

git reset --hard:确定要回滚到目标的版本并且丢弃后续的更改

HR面常见套路

说说你的优点和缺点

优点:

遇到问题喜欢刨根问底,不解决问题誓不罢休,我这个性格也比较的适合做测试吧,不放过任何一个小的bug,最大限度的保证产品的质量。

我在做错了一件事情的时候会反思自己做错的原因,做一个记录,然后分析其原因,争取在下一次遇到的时候可以解决,而不是遭遇挫折就垂头丧气,一蹶不振。

缺点:

有时候会钻牛角尖,比如在学习编程的时候遇到了某个bug然后为了解决这个bug会花很多的时间去调试以至于耽误了其他的工作。

遇到的困难

目前遇到比较大的困难就是实习之后的困难吧,面对很多个需求有时候会无从下手,手忙脚路。

解决方法:那天加班很晚把每个需求要做的事情我都给理了一遍,然后逐个处理,最后也是在mt的帮助下完成了任务。

最有成就感的一件事情

最后成就感的一件事,上班年接了一个大项目,刚开始的时候mt教我,后来我学会了之后,独自测试了两个月,然后上线之后我负责的模块没有出现一个问题,之前的其他同学接手的时候出现过线上问题的,我负责的那几个月没有出现过一个线上故障。

学校中校园运动会获得男子4*100的第三名以及男子200米的第五名(体现身体素质良好),平时比较喜欢打篮球和跑步,加上运动会前两周的一个比较专业的训练,最终取得了这么一个不错的成绩。

你觉的需要提升的点

时间管理,我之前对时间管理多是停留在按时完成某一个事情就行了,当事情或者业务需求变得多了的时候就变得手忙脚乱了,甚至觉得就不可能一次性完成这么多的事情,但是后面在我反思了一下然后尝试去规划某一个需求多久完成需要多久,然后严格的按照时间去做,当然刚开始的时候规划发现也不是像自己想的那么简单,因为你在做某些事情的时候会有其他的一些影响,例如产品突然来了个临时紧急的需求,这个时候就需要尽可能的早点完成可以完成的事情然后留下一些缓冲时间,总之目前还在完善我的时间管理吧。

技术方面的话,我的技术覆盖度还是不够高,例如性能测试这一块我解除的机会不多后面还需要一定的学习以及实战训练。

你选择工作考虑哪些方面

1.我优先考虑的是公司对我的职业发展以及个人的成长有什么帮助吧,在我为公司带来价值的同时希望自己也能给自己带来一定的提升,提升包括是技术方面或者处事相关的,总之让自己变得更有竞争力和价值吧,

2.考虑工作的环境或者团队的氛围如何,好的环境或者氛围可以让我们工作起来很舒服吧。

3.薪资待遇这一块。

有遇到过什么知识盲区吗怎么解决的

有的,比如说测试这块有个叫做泳道的技术,在刚开始的时候我是没接触过这块技术的,但是开发人员直接给了我一个泳道的ID让我开始测试。首先我自己在网络上搜索了这个技术,先了解背景为何要用,再了解如何使用,最后就是实战用法,遇到不会的解决不了网上也找不到的场景就问问身边的mt或者同事。

遇到不好沟通的同事怎么解决

1.沟通的时候尽可能的从双方共同的利益出发,可以先明确说明对彼此的好处,或者对于推动此次项目完成的好处。

2.分析痛点,别人不愿意配合的原因等,找出问题的关键然后解决。

3.可以多有些耐心,多思考多想办法,肯定有方法解决的,关键看自己愿不愿意想了。

你认为自己有哪些优势

1.自己最大的优势的话是自己的实习经历,在快手做了快半年的测试开发实习生了,相对于没有实习经验的同学来说的话可以很快的上手测试开发相关的工作,对公司来说培养的成本更低。

2.技能方面的话,自己无论是测试方面的话有着大量的实战经验,开发方面有自己的码云代码仓库简历里面也有。

3.本人擅长总结,简历里面也记录了我部分的学习经历,可以看一下我的博客,里面都是记录着我学了一块记录做的总结与分享。

4.本人热爱运动,身体素质良好,在校园运动会中获得过奖。

从实习当中学到了什么

1.学到了我们测试人员的一个真正的工作流程。

2.学到了如何去保证我们产品的一个质量。

3.学到了如何去与人沟通,了解人与人之间的关系,如何和同事们相处,例如一起吃个饭,一起健身。


网站公告

今日签到

点亮在社区的每一天
去签到