LOADING

加载过慢请开启缓存 浏览器默认开启

2024招银科技后端一面(一)

Spring中的@Transcational注解怎么起作用的

@Transactional注解是Spring提供的事务管理注解,它可以作用在类上,也可以作用在方法上。当作用在类上时,表示该类的所有方法都是事务性的,当作用在方法上时,表示该方法是事务性的。当@Transactional注解作用在方法上时,Spring会为该方法生成一个代理类,该代理类会在方法执行前开启事务,在方法执行后提交或回滚事务,从而实现事务的管理。

基本原理:将对应的方法通过注解元数据,标注在业务方法或者所在的对象上,然后再业务执行期间,通过AOP拦截器反射读取元数据信息,从而决定是否开启事务,以及事务的隔离级别,传播行为,超时时间,只读属性等。

设计模式了解吗?

  1. 单例模型:保证一个类只有一个实例,并提供一个全局访问点。
  2. 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类。
  3. 抽象工厂模式:定义了一个接口用于创建相关或者依赖对象的家族,而不需要明确指定具体类。
  4. 生成器模式:分装一个复杂对象的构建过程,并可以按步骤构造。
  5. 原型模式:通过复制现有的实例来创建新的实例。无需知道相应类的信息。

结构模式

  1. 适配器模式:将某个类的接口转换成客户端期望的另一个接口的表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
  2. 装饰者模式:动态的给对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更加灵活。
  3. 代理模式:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。也就是所谓的中介
  4. 外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。也就是所谓的门面模式。
  5. 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立的变化。
  6. 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
  7. 享元模式:运用共享技术有效的支持大量细粒度的对象。

关系模式

  1. 模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  2. 策略模式:定义一系列算法,把他们一个个封装起来,并且使他们可以相互替换。
  3. 观察者模式:定义了对象之间的一对多依赖,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  4. 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
  5. 责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
  6. 命令模式:将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
  7. 状态模式:创建表示各种状态的对象和一个行为随着状态对象改变而改变的context对象。
  8. 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
  9. 访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。
  10. 中介者模式:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。

服务端如何检测客户端的登陆状态,登陆限流的实现方式

  1. 通过session来检测客户端的登陆状态,当客户端登陆时,服务端会为该客户端创建一个session,当客户端退出时,服务端会销毁该session,当客户端再次访问时,服务端会检测该客户端是否有session,如果有则表示该客户端已经登陆,否则表示该客户端未登陆。
  2. 通过令牌来检测客户端的登陆状态,当客户端登陆时,服务端会为该客户端生成一个令牌,当客户端退出时,服务端会销毁该令牌,当客户端再次访问时,服务端会检测该客户端是否有令牌,如果有则表示该客户端已经登陆,否则表示该客户端未登陆。
  3. 使用ip地址来验证:服务端可以记录客户端的IP地址,在后续的请求中通过比对IP地址来判断客户端的登录状态。但是这种方式存在一定的风险,因为客户端的IP地址可能会发生变化。
  4. 使用心跳机制:客户端在登录成功后,定期向服务端发送心跳请求,服务端通过接收心跳请求来判断客户端的登录状态。如果服务端在一定时间内没有收到客户端的心跳请求,可以认为客户端已经下线。

登陆限流:限制ip地址最多允许登陆的账号数量,使用请求频率限制的方式来实现。

动态链接和静态链接

静态链接:在编译时将程序中用到的各个函数的目标代码加入到可执行文件中,使生成的可执行文件完全独立,运行时不再需要依赖于静态链接库,因此生成的可执行文件比较大。
动态链接:在编译时并不将程序中用到的各个函数的目标代码加入到可执行文件中,而是在程序运行时由系统动态的加载目标代码,因此生成的可执行文件比较小。

代码规范,命名规范

  1. 代码规范:代码规范是指在编写代码时应该遵循的一些规则,如缩进,注释,命名等。
  2. 命名规范:命名规范是指在命名时应该遵循的一些规则,如类名,方法名,变量名等。

发生死锁如何解决

  1. 抢占资源:操作系统强制释放某些进程所占用的资源
  2. 回滚进程:将某些进程的运行时间退回到某个时间点,释放其占用的资源
  3. 杀死进程:操作系统强制杀死某些进程

预防死锁:

  1. 破坏互斥条件:将某些资源改为非独占性的,也就是允许多个进程同时访问该资源。
  2. 破坏不可抢占条件:当某个进程占用了某些资源,但是又需要申请其他资源时,如果申请不到,则释放已经占用的资源。
  3. 破坏占用且申请条件:当某个进程占用了某些资源,但是又需要申请其他资源时,如果申请不到,则释放已经占用的资源,当申请到资源后,再重新申请已经释放的资源。
  4. 破坏循环等待条件:将系统中的所有资源进行排序,然后按照顺序申请资源,这样就不会出现循环等待的情况。

检测死锁:

  1. 资源分配图法:将系统中的所有资源和进程都用图表示出来,然后检测是否存在环路,如果存在环路,则表示发生了死锁。
  2. 使用jstack命令,jconsole工具或者jvisualvm工具来检测死锁。

进程和线程的区别

  1. 进程是资源分配的最小单位,线程是程序执行的最小单位。
  2. 进程拥有独立的地址空间,文件描述符,堆栈等系统资源,线程共享进程的资源。它们可以访问相同的变量和数据
  3. 进程是操作系统进行调度基本单位,进程间切换需要保存和恢复整个进程的上下文,开销较大,线程是进程中的一个执行单元,线程键的切换只需要保存和恢复少量寄存器的上下文,开销较小。
  4. 进程间通信要通过进程间的通信机制,如管道,消息队列,共享内存等,线程之间的通信通过共享内存来实现。
  5. 线程的崩溃会导致整个进程的崩溃,但是进程的崩溃不会导致整个系统的崩溃。

链表和数组的区别

  1. 数组是一种顺序存储的线性表,链表是一种链式存储的线性表。
  2. 链表中的元素在内存中不是连续存储的,而是通过指针相连,数组中的元素在内存中是连续存储的。
  3. 内存分配方式不同,数组在定义时就需要分配足够的内存空间,而链表在添加元素时才会分配内存空间。
  4. 读取元素的方式不同,数组可以通过下标来读取元素,时间复杂度为O(1),链表需要从头开始遍历,时间复杂度为O(n)。
  5. 增删改元素的方式不同,数组增删改元素时,需要移动元素,时间复杂度为O(n),链表增删改元素时,只需要修改指针,时间复杂度为O(1)。

阐述一下TCP/IP协议

TCP/IP协议是一组用于在网络上进行通信的协议集合。它是互联网的基础协议,用于实现不同计算机之间的数据传输和通信。

TCP/IP协议由两个主要协议组成:传输控制协议(TCP)和互联网协议(IP)。TCP负责将数据分割成小的数据包,并在发送和接收端之间建立可靠的连接。IP则负责将数据包从源地址传输到目标地址,并负责路由和寻址。

TCP/IP协议还包括其他一些重要的协议,如用户数据报协议(UDP)、互联网控制消息协议(ICMP)、互联网组管理协议(IGMP)等。UDP是一种无连接的协议,用于快速传输数据,但不保证可靠性。ICMP用于在网络中传递错误消息和控制消息。IGMP用于在多播网络中管理组成员。

TCP/IP协议采用分层结构,分为四个层次:网络接口层、网络层、传输层和应用层。网络接口层负责与物理网络接口进行通信。网络层负责处理数据包的路由和寻址。传输层负责提供可靠的数据传输服务,包括TCP和UDP协议。应用层负责处理特定的应用程序,如HTTP、FTP、SMTP等。

TCP/IP协议具有以下特点:可靠性、灵活性、可扩展性和互联网的全球性。它能够在不同的操作系统和硬件平台上运行,并且支持各种不同的应用程序和服务。TCP/IP协议被广泛应用于互联网、局域网和广域网等网络环境中,成为了现代网络通信的基础。

编程实现输出字符串中出现次数最多的字符和次数

public class Main {
    public static void main(String[] args) {
        String str = "abcaakjbb";
        char[] chars = str.toCharArray();
        Map<Character, Integer> map = new HashMap<>();
        for (char c : chars) {
            if (map.containsKey(c)) {
                map.put(c, map.get(c) + 1);
            } else {
                map.put(c, 1);
            }
        }
        int max = 0;
        char maxChar = ' ';
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {
            if (entry.getValue() > max) {
                max = entry.getValue();
                maxChar = entry.getKey();
            }
        }
        System.out.println(maxChar + " " + max);
    }
}
#include <iostream>
#include <map>
using namespace std;
int main() {
    string str = "abcaakjbb";
    map<char, int> m;
    for (int i = 0; i < str.length(); i++) {
        if (m.find(str[i]) != m.end()) {
            m[str[i]]++;
        } else {
            m[str[i]] = 1;
        }
    }
    int max = 0;
    char maxChar = ' ';
    for (map<char, int>::iterator it = m.begin(); it != m.end(); it++) {
        if (it->second > max) {
            max = it->second;
            maxChar = it->first;
        }
    }
    cout << maxChar << " " << max << endl;
    return 0;
}

线程和协程的区别

调度方式:线程是由操作系统进行调度的,而协程是由程序员手动进行调度的。

并发性:线程是并发执行的,多个线程可以同时运行在多个CPU核心上。而协程是顺序执行的,只有一个协程在运行,其他协程需要等待当前协程的执行完成。

切换开销:线程的切换需要保存和恢复整个线程的上下文,开销较大。而协程的切换只需要保存和恢复协程的上下文,开销较小。

内存占用:线程的内存占用比较大,每个线程都需要独立的栈空间。而协程的内存占用比较小,多个协程可以共享同一个栈空间。

同步方式:线程通过共享内存或者消息传递的方式进行同步。而协程通过消息传递或者显式的调度方式进行同步。

编程模型:线程的编程模型比较传统,使用锁、条件变量等同步原语进行并发控制。而协程的编程模型比较灵活,可以使用异步编程、事件驱动等方式进行并发控制。

总的来说,线程适合于CPU密集型任务,可以充分利用多核CPU的计算能力。而协程适合于IO密集型任务,可以充分利用IO等待时间进行其他任务的执行,提高程序的并发性能。

进程间通信是什么

进程间通信是指两个或多个进程之间进行数据交换的过程。进程间通信的方式有很多种,如管道,消息队列,共享内存等。

单例与静态变量的区别

单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。在单例模式中,通过私有化构造函数,对外部隐藏实例化的细节,通过静态方法获取唯一的实例。

静态变量是类的一个属性,被所有实例共享,可以在类的任何地方访问。静态变量在类加载时被初始化,并且只会被初始化一次。

区别如下:

  1. 单例模式是一种设计模式,用于确保一个类只有一个实例,而静态变量是类的一个属性。
  2. 单例模式通过私有化构造函数,对外部隐藏实例化的细节,通过静态方法获取唯一的实例,而静态变量可以在类的任何地方访问。
  3. 单例模式可以实现更复杂的逻辑,例如延迟实例化、线程安全等,而静态变量只是一个全局共享的属性。
  4. 单例模式可以被继承和扩展,而静态变量不支持继承。
  5. 单例模式可以实现接口,而静态变量不能实现接口。

什么是JVM?JVM方法区,JVM类加载机制

JVM是Java虚拟机的缩写,是Java程序运行的环境。JVM是Java程序和操作系统之间的一层抽象,它负责将Java程序翻译成操作系统能够识别的指令,然后交给操作系统执行。

JVM方法区:JVM方法区是JVM的一块内存区域,用于存储类的结构信息,如类的字段、方法、接口等。JVM方法区是线程共享的,它的生命周期和JVM的生命周期一致。

JVM类加载机制:JVM类加载机制是指JVM在运行时将类加载到内存中的过程。JVM类加载机制分为三个步骤:加载、连接和初始化。

  1. 加载:将类的字节码文件加载到内存中,并将其转换成JVM能够识别的数据结构,如Class对象。
  2. 连接:将类的二进制数据合并到JVM的运行状态中,分为验证、准备和解析三个阶段。
  3. 初始化:为类的静态变量赋予正确的初始值,执行类的静态代码块。

索引的应用,唯一索引和主键的区别

索引是数据库中用于提高查询效率的数据结构。它可以加快数据的检索速度,减少数据库的访问时间。
唯一索引是一种限制性索引,它要求索引列中的值必须是唯一的,即不允许有重复的值。唯一索引可以用来保证数据的完整性,避免重复数据的插入。
主键是一种特殊的唯一索引,它要求索引列中的值必须是唯一的,并且不允许为空。主键可以用来唯一标识表中的每一条记录,它在数据库中具有唯一性和非空性的特点。主键可以用来建立表与表之间的关系,作为外键的参照。

区别:

  1. 唯一索引可以允许空值,而主键不允许为空值。
  2. 表中只能有一个主键,但可以有多个唯一索引。
  3. 主键是一种特殊的唯一索引,它在数据库中具有唯一性和非空性的特点,而唯一索引只要求索引列中的值必须是唯一的。
  4. 主键可以用来建立表与表之间的关系,作为外键的参照,而唯一索引不能作为外键的参照。

总结:唯一索引是一种限制性索引,它要求索引列中的值必须是唯一的;主键是一种特殊的唯一索引,它要求索引列中的值必须是唯一的,并且不允许为空。

RR和RC的区别

RR(读已提交):一个事务只能读取到已经提交的数据,不能读取到未提交的数据。RR是数据库默认的隔离级别。
RC(读已提交):一个事务只能读取到已经提交的数据,不能读取到未提交的数据。RC是Oracle数据库的默认隔离级别。

mybatisPlus和mybatis的区别,mybatisPlus的优点

MybatisPlus是Mybatis的增强工具,它在Mybatis的基础上增加了很多实用的功能,如自动生成代码,分页插件,性能分析插件等。
MybatisPlus的优点如下:

  1. 简化开发:MybatisPlus可以通过代码生成器自动生成代码,减少了开发人员的工作量。
  2. 支持分页:MybatisPlus内置了分页插件,可以方便的实现分页功能。
  3. 支持自动填充:MybatisPlus内置了自动填充插件,可以方便的实现自动填充功能。
  4. 乐观锁和逻辑删除:MybatisPlus内置了乐观锁和逻辑删除插件,可以方便的实现乐观锁和逻辑删除功能。
  5. 注解支持:减少了对于XML的依赖,通过使用注解,开发者可以直接在实体类上进行定义和配置,提高了代码的可读性和可维护性。

局限性:过于依赖框架,对于复杂的数据库操作或特殊需求不够灵活。

都支持多数据源配置。

假设有多台服务器,用户同时登陆过去,这些服务器怎么才能知道用户是同一个用户?负载均衡怎么实现?

在多台服务器的情况下,要确定用户是否是同一个用户,可以通过以下几种方法:

  1. 用户认证信息:在用户登录时,服务器可以生成一个唯一的认证信息(例如,令牌),并将其发送给用户。当用户发送请求时,服务器可以检查请求中是否包含这个认证信息。如果认证信息匹配,则可以确定是同一个用户。
  2. 会话跟踪:服务器可以使用某种机制(如Cookie或HTTP头部)来跟踪用户会话。当用户在不同的服务器之间跳转时,这些服务器可以共享会话信息,从而识别出同一个用户。
  3. 负载均衡:负载均衡器可以将用户请求分配到不同的服务器上。如果负载均衡器具备会话跟踪功能,它可以将来自同一用户的请求路由到同一台服务器上,从而让该服务器可以维护用户的会话状态。

实现负载均衡的方法有很多种,以下是其中几种常见的方法:

  1. 静态负载均衡:静态负载均衡是一种手动分配负载的方法。在这种方法中,根据服务器的性能和其他因素,管理员可以手动配置负载均衡规则,将用户请求分配到不同的服务器上。
  2. 动态负载均衡:动态负载均衡是一种自动分配负载的方法。在这种方法中,负载均衡器可以根据服务器的性能、负载和其他因素,自动选择一台服务器来处理用户请求。常见的动态负载均衡算法包括轮询、随机、加权轮询和加权随机等。
  3. DNS负载均衡:DNS负载均衡是一种利用DNS来分配用户请求的方法。在这种方法中,DNS服务器可以配置多个IP地址,并将用户请求映射到不同的服务器上。这种方法的优点在于,不需要在客户端或服务器端进行额外的配置。
  4. 反向代理:反向代理是一种将来自客户端的请求转发到服务器上的技术。在这种技术中,反向代理服务器可以作为客户端和服务器之间的中间件,处理所有的请求和响应。通过配置反向代理,可以实现负载均衡、安全性和性能优化等功能。

SQL编程题实现在user表中查询年纪第三大且性别为女性的用户记录

select * from user where gender="f" order by age desc limit 1 offset 2;

联合索引失效的情况

  1. 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
  2. 当我们在查询条件中对索引列使用函数,就会导致索引失效。
  3. 当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
  4. MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
  5. 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
  6. 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

扫码登录的流程

用户打开PC端或移动端,进入登录界面。
服务端和客户端进行交互,客户端生成二维码并展示给用户。
用户使用移动端扫描该二维码,获取二维码中的信息。
移动端将扫描到的信息发送给服务端。
服务端对接收到的信息进行处理,如验证用户身份、生成临时token等。
服务端将临时token返回给移动端。
移动端将该token展示给用户,完成登录过程。

如何理解Java面向对象

  1. 封装:封装是指将数据和方法包装到类中,对外部世界隐藏内部实现细节的过程。封装可以提高代码的可读性和可维护性,提高代码的复用性。
  2. 多态:多态是指同一种行为具有多种不同表现形式或形态的能力。多态可以提高代码的灵活性和可扩展性,提高代码的复用性。
  3. 继承:继承是指子类继承父类的属性和方法。继承可以提高代码的复用性,提高代码的可扩展性。
  4. 抽象:抽象是指将事物的共性抽取出来形成类的过程。抽象可以提高代码的复用性,提高代码的可扩展性。

Spring事务原理

Spring事务的原理是基于AOP(面向切面编程)技术实现的。Spring AOP拦截到使用@Transactional注解的方法调用,事务管理器开始一个新的事务,方法执行,对数据库进行操作,方法执行结束,事务管理器根据方法执行结果(成功或异常)提交或回滚事务。

在Spring事务中,可能会遇到以下问题:

  1. 同一类中的方法调用:如果在同一个类中,一个没有使用@Transactional注解的方法调用另一个使用了@Transactional注解的方法,事务将不会生效。因为在同一个类中,方法调用不会经过Spring AOP代理,导致事务管理器无法创建事务。解决这个问题,可以将使用@Transactional注解的方法提取到另一个类中,然后在原类中通过依赖注入(DI)来调用新类的方法。
  2. 异常类型不匹配:Spring事务默认只回滚运行时异常(RuntimeException)。如果方法抛出的是检查异常(CheckedException),则需要使用@Transactional(rollbackFor = Exception.class)注解显式指定回滚异常类型。
  3. 事务传播行为配置错误:事务传播行为(Propagation)决定了当一个事务方法调用另一个事务方法时,如何进行事务管理。错误的配置可能导致事务失效。
  4. 使用了不支持事务的数据库:某些数据库(如MyISAM引擎的MySQL)不支持事务。在这种情况下,使用Spring事务是无效的。

rocketmq和kafka的区别

架构设计方面:都是基于发布/订阅模式的消息系统,但是kafka的设计更为简单,主要由生产者,消费者和kafka集群三部分组成,生产者将消息发布到kafka集群中的Broker节点,然后消费者从Broker节点上获取消息进行消费,其数据模型是基于Topic和Partition的,,每个Topic可以有多个partition,每个partition可以从多个Broker节点上复制,从而实现了高可用性。而rocketmq的设计更为复杂,主要由namesrv, Broker和Producer/Consumer三个角色构成,其中Namesrv主要负责服务注册和发现,Broker节点负责存储和传输消息,Producer和Consumer分别将消息发送到和从Broker节点中获取消息。RocketMQ也是基于Topic和Partition的数据模型,但它采用了一种主从复制的机制,确保了数据的高可用性和容错性。

性能比较:在吞吐方面,Kafka更为出色,在延迟方面,RocketMQ更为出色。由于RocketMQ通过采用Zero Copy技术和缓存池技术来降低延迟,而Kafka则通过批量发送和异步处理的方式来提高吞吐量,但相应的会增加一定的延迟。

可靠性比较:kafka使用的多个副本机制,每个Partition都有多个副本,当主副本出现故障时,可以从副本中选举出新的主副本,从而实现了高可用性。而RocketMQ则采用了主从复制的机制,当主节点出现故障时,需要手动将从节点切换为主节点,从而实现了高可用性。

数据一致性方面:Kafka采用了基于Zookeeper的分布式协调机制,能够确保数据在Producer和Consumer之间的顺序性。而RocketMQ则需要在Producer端对消息进行排序,然后再发送到Broker节点中

消息事务方面:RocketMQ提供了完整的消息事务机制,能够保证消息在发送和接收过程中的一致性和可靠性。而Kafka并没有提供官方的事务支持,需要开发者自行处理。

RocketMQ的延时队列和ACK机制

RocketMQ的延时队列是指消息发送后,在一定时间内不会被消费者消费,而是在一定时间后才会被消费者消费。这种机制可以在某些场景下非常有用,比如订单支付成功后需要等待一定时间才能发货,这时候可以将订单信息发送到延时队列中,在一定时间后再将订单信息发送到正常队列中进行处理。

RocketMQ的ACK机制是指消费者在消费消息后,需要向服务器发送一个确认消息,告诉服务器已经成功消费了该消息。如果消费者没有发送确认消息,那么服务器会认为该消息没有被成功消费,会重新将该消息发送给其他消费者进行消费。这种机制可以保证消息的可靠性和一致性

MQ的推和拉模式的区别,如何选择

推模式:当消息到达MQ服务器时,MQ服务器会主动将消息推送给消费者,消费者只需要注册一个回调函数,当消息到达时,回调函数会被调用,从而实现消息的消费。推模式可以实现消息的实时性,但是如果消费者处理消息的能力不足,会导致消息堆积,从而影响MQ服务器的性能。
适合的场景:

  1. 消息的实时性要求比较高:当消息到达时,消费者需要立即进行消费。
  2. 发布-订阅模式:消息的生产者和消费者之间是一对多的关系,一个生产者可以向多个消费者发送消息,多个消费者可以同时消费同一条消息。

拉模式:当消息到达MQ服务器时,MQ服务器不会主动将消息推送给消费者,而是等待消费者主动向服务器拉取消息。消费者可以根据自身的处理能力来拉取合适数量的消息,从而避免了消息堆积的问题。但是拉模式无法实现消息的实时性,消费者需要定时的拉取消息,才能保证消息的实时性。
适合的场景:

  1. 资源受限的场景:消费者的处理能力有限,无法处理大量的消息,需要根据自身的处理能力来拉取合适数量的消息。
  2. 请求-响应模式:消息的生产者和消费者之间是一对一的关系,一个生产者只能向一个消费者发送消息,一个消费者只能消费一条消息。
  3. 批量处理:接受者可以一次性的拉取多条消息,然后一次性的进行处理。

阻塞和非阻塞的区别,同步和异步的区别:

阻塞和非阻塞的区别:

阻塞:当一个任务在执行过程中遇到了阻塞的情况,就会停止执行,直到阻塞条件被解除后才能继续执行。在阻塞状态下,任务会等待系统资源或者等待其他任务的完成。
非阻塞:当一个任务在执行过程中遇到了非阻塞的情况,它会继续执行而不会停止等待。任务会立即返回一个结果,无论这个结果是成功的还是失败的。
同步和异步的区别:

同步:同步操作是指一个任务在执行过程中,必须等待另一个任务完成后才能继续执行。在同步操作中,任务之间的执行顺序是固定的,按照一定的顺序依次执行。
异步:异步操作是指一个任务在执行过程中,不需要等待另一个任务的完成,而是继续执行其他任务。在异步操作中,任务之间的执行顺序是不确定的,可以同时执行多个任务。
总结: 阻塞和非阻塞主要描述的是任务在等待结果时的状态,而同步和异步主要描述的是任务之间的执行顺序。阻塞和同步的特点是任务之间按照固定的顺序依次执行,而非阻塞和异步的特点是任务之间可以并发执行。

如果自己实现一个非阻塞的IO,你会怎么做?

要实现一个非阻塞的IO,可以采用以下几个步骤:

使用非阻塞的套接字:在创建套接字时,设置套接字为非阻塞模式。这可以通过将套接字的文件描述符设置为非阻塞模式来实现,可以使用fcntl函数或者ioctl函数来完成。

使用非阻塞的文件描述符:对于文件IO,也可以将文件描述符设置为非阻塞模式。可以使用fcntl函数或者ioctl函数来设置非阻塞模式。

使用select、poll或epoll等多路复用机制:这些机制可以同时监视多个文件描述符的状态,当某个文件描述符就绪时,可以进行相应的处理。可以使用这些机制来检查套接字或文件描述符是否有数据可读或可写,从而实现非阻塞IO。

使用回调函数或事件驱动:可以使用回调函数或事件驱动的方式来处理IO事件。当某个IO事件就绪时,可以调用相应的回调函数或触发相应的事件处理函数。

使用线程池或协程:可以使用线程池或协程来处理IO操作,将IO操作放在独立的线程或协程中进行,从而避免阻塞主线程。

总的来说,实现非阻塞的IO需要结合以上几个步骤,使用非阻塞的套接字或文件描述符,配合多路复用机制和回调函数或事件驱动的方式来实现

什么是NIO,NIO的优点

非阻塞IO,是一种同步非阻塞的IO模型。在NIO中,当线程从某通道进行读写数据时,若没有数据可用时,会返回null,而不是像阻塞IO那样一直等待数据就绪。所以,NIO是非阻塞的,因为线程可以不必等待数据就绪,可以先处理其他事情。当数据就绪时,线程会得到通知,从而进行数据的读取或写入。
有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)。

redis的数据类型

  1. String:字符串类型,是最基本的数据类型,一个键最大能存储512MB。缓存一些用户的身份信息和常规的一些计数统计
  2. List:列表类型,是字符串列表的集合,按照插入顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边)。可以用来做异步队列
  3. Hash:哈希类型,是键值对的集合,是一个string类型的field和value的映射表,适合用于存储对象。可以存储用户信息
  4. Set:集合类型,是string类型的无序集合,集合中的元素是唯一的,不能重复。可以用来存储一些唯一性的数据,比如点赞用户的id,存储图像的id等
  5. ZSet:有序集合类型,是string类型元素的集合,不允许重复的成员。每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。可以用来做排行榜应用

Redis分布式锁的实现方式

  1. setnx + expire:使用setnx命令来获取锁,如果返回1则表示获取锁成功,否则表示获取锁失败。获取锁成功后,再使用expire命令来为锁设置一个过期时间,如果程序出现异常导致锁没有被释放,可以避免死锁的问题。释放锁的时候,可以使用Lua脚本来确保释放锁的原子性。

慢SQL查询优化

要优化慢 SQL 查询,可以采取以下几个步骤:

  1. 分析慢查询:首先要确定哪些查询是慢查询,可以通过数据库的性能监控工具或者日志分析工具来查看查询的执行时间和资源消耗情况。

  2. 检查索引:索引是提高查询性能的关键,可以通过使用适当的索引来加快查询速度。检查查询中使用的列是否有适当的索引,尤其是经常用于过滤、排序和连接的列。

  3. 优化查询语句:检查查询语句是否可以优化,可以通过以下几个方面来改进查询性能:

    • 减少查询返回的列数,只选择需要的列。
    • 避免使用通配符查询,尽量使用具体的条件。
    • 使用合适的连接方式,如INNER JOIN、LEFT JOIN等。
    • 使用合适的聚合函数和分组方式。
    • 避免使用子查询,尽量使用连接查询或者临时表来替代。
  4. 优化表结构:检查表的结构是否合理,可以考虑以下几个方面来优化表结构:

    • 拆分大表:如果一个表的数据量非常大,可以考虑将其拆分成多个小表,以减少查询的数据量。
    • 增加分区:对于大表,可以考虑使用分区表来提高查询性能。
    • 增加缓存:对于经常被查询的表,可以考虑增加缓存来减少查询的数据库访问次数。
  5. 避免全表扫描:全表扫描是查询性能低下的主要原因之一,可以通过以下几种方式来避免全表扫描:

    • 使用索引:确保查询中使用的列有适当的索引。
    • 使用覆盖索引:如果查询只需要返回索引中的列,可以使用覆盖索引来避免访问表数据。
    • 使用分页查询:对于大结果集的查询,可以使用分页查询来减少查询的数据量。

MySQL的隔离级别有哪些,不可重复读和幻读的区别,如何解决

MySQL的隔离级别有以下四种:

  1. 读未提交(Read Uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。
  2. 读提交(Read Committed):一个事务提交之后,它做的变更才会被其他事务看到。
  3. 可重复读(Repeatable Read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
  4. 串行化(Serializable):对于同一行记录,写会加写锁,读会加读锁,当出现读写锁冲突时,后访问的事务必须等前一个事务执行完成才能继续执行。
    其中,读未提交是最低的隔离级别,串行化是最高的隔离级别。
    读提交可能出现不可重复读和幻读的问题,不可重复读是指在同一个事务中,多次读取同一条记录,可能会得到不同的结果。幻读是指在同一个事务中,多次执行同一条查询语句,可能会得到不同的结果。
    不可重复读和幻读的区别在于,不可重复读是指在同一个事务中,多次读取同一条记录,可能会得到不同的结果。而幻读是指在同一个事务中,多次执行同一条查询语句,其中符合查询条件的记录数可能会不同。
    解决不可重复读的方法有以下两种:
  5. 使用锁:可以使用悲观锁或者乐观锁来解决不可重复读的问题。悲观锁是指在读取数据时,使用排他锁来锁定数据,从而避免其他事务对数据的修改。乐观锁是指在读取数据时,不使用锁来锁定数据,而是在更新数据时,检查数据是否发生了变化,如果发生了变化,则放弃更新操作。
  6. 修改隔离级别:可以将隔离级别设置为可重复读,从而避免不可重复读的问题。

解决幻读的方法有以下两种:

  1. 使用唯一索引:可以在查询语句中使用唯一索引,从而避免幻读的问题。
  2. 使用锁,乐观锁或者排它锁都可以解决幻读的问题。

海量数据如何判重

  1. 使用HashMap:可以将数据的特征值作为HashMap的key,将数据本身作为HashMap的value,然后遍历数据,将数据的特征值作为key存入HashMap中,如果key已经存在,则说明数据重复。
  2. 布隆过滤器:布隆过滤器是一种空间效率很高的随机数据结构,可以用来判断一个元素是否在集合中。它实际上是一个很长的二进制向量和一系列随机映射函数。可以将数据的特征值作为布隆过滤器的输入,如果返回值为true,则说明数据重复。

相同点和不同点:

  1. 相同点:都有概率误判的问题,即判断数据重复时,有可能会判断错误。
  2. 不同点:HashMap中不同元素的哈希值可能相同,从而导致误判,布隆过滤器则是元素存在时,可能判定元素不存在,但是元素不存在时,一定不会判定元素存在。

存储机制:hashmap使用一个数组来存储键值对,通过哈希函数将键映射到数组的索引位置,然后将值存储在对应的位置上。而布隆过滤器则使用一个位数组来存储数据,通过多个哈希函数将数据映射到位数组中的多个位置上,然后将对应的位置置为1。由于hashmap可以直接获取键值对的位置,所以查询的时间复杂度为O(1),而布隆过滤器需要多次查询位数组中的值,所以查询的时间复杂度为O(k),其中k为哈希函数的个数。

内存占用:hashmap需要根据数据规模来动态调整数组的大小,而布隆过滤器在预先设置位数组的大小后,不会随着数据规模的增加而增加内存占用。因此布隆过滤器更适合于海量数据的判重。

实现布隆过滤器:使用Guava库和Commons

concurrentHashMap的实现原理

ConcurrentHashMap是线程安全的哈希表,它是HashTable的替代品,比HashTable的扩展性更好。ConcurrentHashMap的实现原理如下:

无序数组找出两数之和等于目标值

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
int main(){
    int n, target;
    cin>>n>>target;
    vector<int> nums(n);
    for(int i=0;i<n;i++){
        cin>>nums[i];
    }
    unordered_map<int, int> map;
    for(int i=0;i<n;i++){
        if(map.find(target-nums[i])!=map.end()){
            cout<<map[target-nums[i]]<<" "<<i<<endl;
            break;
        }
        map[nums[i]]=i;
    }
    return 0;
}
本文作者:GWB
当前时间:2023-11-09 11:11:09
版权声明:本文由gwb原创,本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议。
转载请注明出处!