面试记录一

面试记录

  1. 项目介绍

  2. 开发中遇到的技术难题

1. 说一说数据库设计的几大原则

  1. 规范化设计原则
  • 第一范式(1NF):确保数据库表中的每个字段都是不可分割的基本数据项,即字段具有原子性,不可再分。
  • 第二范式(2NF):在满足1NF的基础上,确保表中的所有非主键列都完全依赖于主键,而不是依赖于主键的一部分。
  • 第三范式(3NF):在满足2NF的基础上,确保表中的非主键列不依赖于其他非主键列,即消除传递依赖。
  • BC范式(BCNF)
    BC范式是第三范式(3NF)的扩展,它要求所有的非主属性完全函数依赖于任何一个候选键。换句话说,如果表中有多个候选键,那么表中的所有非主属性都不能仅依赖于候选键的一部分(即不是完全依赖于任何一个候选键)。BC范式消除了由于多值依赖(虽然BCNF的定义并不直接涉及多值依赖,但多值依赖是导致违反BCNF的常见原因之一)而导致的冗余和更新异常。

BCNF的一个关键特点是,它只要求非主属性完全函数依赖于候选键,而不要求非主属性之间不存在传递依赖。这使得BCNF成为比3NF更严格的规范化级别。

  • 第四范式(4NF)
    第四范式是在BCNF的基础上进一步限制数据依赖的范式。它主要针对的是多值依赖(MVD, Multi-Valued Dependency)问题。在4NF中,要求表中的所有非平凡多值依赖的左侧(即依赖的“决定因素”)必须是超键(Superkey)。换句话说,如果表中的一个非平凡多值依赖X ->-> Y存在,那么X必须是表的一个超键。

多值依赖是数据库设计中一种特殊的数据依赖关系,它表示一个属性(或属性集)的值对应另一个属性(或属性集)的多个可能值。4NF通过要求所有非平凡多值依赖的左侧为超键,来消除由于多值依赖引起的数据冗余和更新异常。
2. 主键与外键原则

  • 主键设计:每个表都应该有一个主键,用于唯一标识表中的每一行记录。主键的选择应考虑到查询效率和数据完整性。
  • 外键设计:外键用于建立表与表之间的关联。通过外键约束,可以确保数据的一致性和完整性。
  1. 索引与查询优化原则
  • 合理使用索引:索引是提高数据库查询性能的重要手段。通过为表中的关键字段创建索引,可以加快查询速度。但需要注意的是,索引也会占用额外的存储空间,并可能影响更新性能。
  • 查询优化:在编写SQL查询语句时,应尽量避免使用复杂的子查询和连接操作,以减少查询时间和提高查询效率。

2. OOM问题的出现原因及解决(腾讯)

  1. 内存泄漏:
  • 定义:内存泄漏是指程序在申请内存后,无法释放已不再使用的内存空间,导致这些内存无法被再次使用,随着时间的推移,可用内存越来越少。
  • 解决方法:
    使用工具(如VisualVM, JProfiler, MAT等)进行内存分析,查找内存泄漏点。
    审查代码,特别是关注那些持有长生命周期对象引用短生命周期对象的场景。
    确保使用完资源后正确关闭,如数据库连接、文件流等。
  1. 堆内存设置过小:
  • 原因:JVM启动参数中指定的堆内存大小(如-Xms和-Xmx)可能不足以满足应用程序的实际需求。
  • 解决方法:
    增加JVM的堆内存大小,通过调整-Xms和-Xmx参数。
    根据应用程序的实际内存需求,合理分配年轻代(Young Generation)和老年代(Old Generation)的大小。
  1. 大量对象短时间内被创建:
  • 原因:应用程序在短时间内创建了大量对象,并且这些对象在短时间内无法被垃圾回收器回收。
  • 解决方法:
    优化代码,减少不必要的对象创建。
    使用对象池等技术重用对象。

3. java中volatile和synchronized有什么区别?

  1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  3. volatile仅能实现变量的修改可见性,并不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

synchronized和ReentrantLock

  1. 用法不同
  • synchronized:
    可以用来修饰普通方法、静态方法和代码块。
    当线程进入同步代码块或同步方法时,会自动获取锁,并在执行完毕或抛出异常时自动释放锁。
  • ReentrantLock:
    是 java.util.concurrent.locks 包下的一个类,实现了 Lock 接口。
    需要显式地创建 ReentrantLock 对象,并调用其 lock() 方法来获取锁,调用 unlock() 方法来释放锁。
    如果不手动释放锁,可能会导致死锁。因此,通常推荐使用 try-finally 语句块来确保锁的释放。
  1. 锁的公平性
  • synchronized:
    只支持非公平锁,即锁的分配不是按照请求锁的顺序进行的。
  • ReentrantLock:
    支持公平锁和非公平锁两种模式。
    默认情况下是非公平的,但可以通过构造函数的参数来指定为公平锁。
    当配置为公平锁时,多个线程在等待同一个锁时,将按照它们请求锁的顺序来依次获得锁。
  1. 锁的尝试获取
  • synchronized:
    无法做到尝试非阻塞地获取锁,即线程在请求锁时如果锁不可用,将会一直等待直到锁被释放。
  • ReentrantLock:
    提供了 tryLock() 方法,该方法尝试获取锁,如果成功则返回 true,否则立即返回 false,线程不会被阻塞。
    还提供了 tryLock(long timeout, TimeUnit unit) 方法,允许线程在尝试获取锁时设置一个超时时间
  1. 锁的绑定条件
  • synchronized:
    只能通过 Object 类的 wait()、notify() 和 notifyAll() 方法来实现线程间的条件等待与通知。
    这些方法必须在 synchronized 代码块或 synchronized 修饰的方法中调用,否则会抛出 IllegalMonitorStateException。
  • ReentrantLock:
    可以通过 newCondition() 方法创建多个 Condition 对象。
    线程可以调用 condition.await() 进行等待,其他线程可以调用 condition.signal() 或 condition.signalAll() 进行通知。
    这种方式支持更精细的线程间协作,可以避免“伪唤醒”问题,并使线程等待特定条件而不是仅仅等待锁的释放。
  1. 锁的灵活性和扩展性
  • synchronized:
    是 JVM 层面的同步机制,使用起来相对简单,但灵活性较差。
    不支持公平锁,也不支持尝试非阻塞地获取锁等高级功能。
  • ReentrantLock:
    提供了更多的灵活性和控制选项,如尝试获取锁、设置超时时间、支持公平锁等。
    是一个类,可以扩展,例如 ReentrantReadWriteLock 就是在 ReentrantLock 基础上实现的读写锁,提供了更复杂的读写权限控制。
  1. 性能
  • 在某些情况下,ReentrantLock 的性能可能优于 synchronized,因为它提供了更多的灵活性和控制选项。然而,具体性能差异取决于应用场景和 JVM 的实现。

4. 有了解java的原子类?实现原理是什么?

  • 采用硬件提供原子操作指令实现的,即CAS。每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全。
  • 原子类

原子类(如 AtomicInteger、AtomicLong 等)是java.util.concurrent.atomic包下的一部分,它们利用CAS(Compare-And-Swap)操作来确保操作的原子性,从而无需使用 synchronized。CAS操作包括三个操作数:

  • 内存位置(V):在Java中,就是主内存中某个变量的地址。
  • 预期原值(A):线程预期该位置(V)应该有的值。
  • 新值(B):线程希望将该位置(V)设置成的值。

如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值,整个操作是原子的。

优点:
原子类的优点是性能高,特别是在高并发场景下,因为它们避免了线程阻塞和上下文切换的开销。但是,它们的使用场景相对有限,主要适用于基本数据类型的原子操作。

总结:

  • 使用场景:如果操作的是基本数据类型,且需要保证操作的原子性,推荐使用原子类。如果操作的是复杂对象或方法,且需要保证线程安全,则可以使用 synchronized。
  • 性能:在高并发场景下,原子类通常比 synchronized 有更好的性能表现,因为 synchronized 可能会导致线程阻塞。
  • 易用性:synchronized 关键字的使用相对简单直观,而原子类则需要一定的学习和理解成本。

5. spring主要使用了哪些?IOC实现原理是什么?AOP实现原理是什么?

一、Spring主要使用的技术

Spring框架中大量使用了设计模式来增强其灵活性和可扩展性,包括但不限于以下几种:

  • 工厂模式:Spring通过BeanFactory、ApplicationContext等接口创建并管理对象实例,将对象的创建与使用解耦,使得程序更加灵活和可扩展。
  • 单例模式:Spring中的Bean默认都是单例的,即在整个Spring IoC容器中,每个Bean只会有一个实例。这通过Bean的scope属性进行控制,当scope为singleton时,即表示使用单例模式。
  • 代理模式:Spring的AOP功能大量使用了代理模式,通过在目标方法执行前后添加额外的行为(如日志、事务管理等),而这些额外的行为是通过代理对象来实现的。Spring提供了两种代理方式:JDK动态代理和CGLIB代理。
  • 模板方法模式:在Spring的JdbcTemplate、HibernateTemplate等类中,使用了模板方法模式,定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现,这种方式使得Spring可以为不同的数据库操作提供统一的接口,同时允许用户根据自己的需求进行定制。
  • 观察者模式:Spring的事件处理机制就是观察者模式的一个应用,当某个事件发生时,所有注册的观察者都会自动收到通知并作出相应的处理,这种方式使得事件的处理更加解耦和灵活。

二、IoC实现原理
IoC(控制反转)是Spring框架的核心,它的实现原理(工厂模式+反射)主要包括以下几个方面:

  • 配置文件解析:在Spring IoC容器启动时,会先进行XML配置文件的解析,通过类扫描器扫描指定的包路径,找出所有标注为@Component、@Service、@Controller、@Repository等注解的Java类,并将它们加载到JVM中。
  • Bean定义注册:将扫描出来的各个Java类根据它们的注解类型,解析成相应的Bean定义对象,并将这些Bean定义对象保存在Spring IoC容器内的Bean定义注册表中。
  • Bean实例创建:Spring IoC容器根据Bean定义注册表中的信息,通过反射机制创建一个个Bean实例,并将其保存在单例Bean缓存池中。
    属性注入:在Bean实例化之后,Spring IoC容器会检测Bean中是否有需要自动注入的属性,并根据属性类型进行匹配注入。属性注入可以通过构造器注入、Setter方法注入以及注解注入等方式实现。
  • 生命周期管理:Spring IoC容器中的Bean在创建和销毁时都会触发相应的生命周期事件,Spring IoC容器要负责监听和管理这些事件。当Spring IoC容器启动时,会先执行所有注册的Bean的初始化方法;当Spring IoC容器关闭时,会执行所有注册的Bean的销毁方法。

BeanFactory 和 ApplicationContext有什么区别?

是spring的核心接口,都可以作为容器,ApplicationContext是BeanFactory的子接口。

  • BeanFactory: 是Spring IoC容器所定义的最底层的接口,它提供了基本的IoC容器功能,如Bean的加载、实例化、配置和依赖关系的维护等。BeanFactory允许在运行时动态地添加和删除Bean,同时也可以管理Bean的生命周期和依赖关系。然而,由于BeanFactory的功能相对较为底层和原始,因此一般不直接提供给开发人员使用

  • ApplicationContext:是BeanFactory的子接口,它扩展了BeanFactory的功能,并提供了更多面向实际应用的功能。ApplicationContext接口不仅包含了BeanFactory的所有功能,还增加了许多其他功能,如国际化支持、事件传播、声明式服务等。此外,ApplicationContext还提供了许多便捷的方法来获取Bean,以及支持基于注解和Java配置的方式来声明Bean及其依赖关系。

区别:

① BeanFactroy采用的是延迟加载形式来注入Bean的,使用到bean才会加载。ApplicationContext一次性加载所有bean。
② BeanFactory需要手动注册,而ApplicationContext则是自动注册。
③ BeanFactory不支持国际化,ApplicationContext支持国际化(实现MessageSource接口)。

在实际应用中,开发人员通常使用ApplicationContext作为Spring IoC的容器,因为它提供了更加完善和便捷的功能。ApplicationContext有许多实现类,如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext等,这些实现类可以根据不同的配置文件位置(如类路径、文件系统或Web服务器)来加载和配置Spring容器。

三、AOP实现原理
AOP(面向切面编程)是Spring框架的另一个重要特性,它的实现原理主要是通过动态代理实现的:

  • 代理对象创建:当为某个Bean或者某些Bean配置切面时,Spring会为其创建代理对象。这个代理对象会在调用原始对象的方法时,加入额外的行为(如日志、事务管理等)。
  • 动态代理选择:Spring的AOP主要使用了两种动态代理方式:JDK动态代理和CGLIB动态代理。如果代理类实现了接口,Spring默认会使用JDK动态代理;如果代理类没有实现接口,则使用CGLIB动态代理。
  • 方法调用拦截:通过动态代理,当调用原始对象的方法时,实际上是调用了代理对象的方法。代理对象的方法会在调用原始对象的方法前后,执行额外的行为。这些额外的行为是通过代理对象内部的逻辑来控制的。
  • AOP支持:Spring IoC容器还提供了AOP的支持,通过动态代理技术,在Bean调用时动态地将一些公共逻辑织入Bean的方法中,从而避免了代码的冗余。

综上所述,Spring通过大量使用设计模式和技术来实现其功能,其中IoC和AOP是Spring框架最为核心的特性。IoC通过配置文件解析、Bean定义注册、Bean实例创建、属性注入和生命周期管理等步骤实现控制反转;AOP则通过动态代理技术实现面向切面编程,将额外的行为织入到原始对象的方法调用中。

5.JDK动态代理和CGLIB代理

一、JDK动态代理

  • 原理
    JDK动态代理是通过Java反射机制来实现的,它要求目标对象必须实现一个接口。在运行时,通过Proxy类的newProxyInstance()方法动态地生成一个实现了目标对象所有接口的代理类,并创建代理对象。所有对代理对象的方法调用都会被转发给InvocationHandler处理器去处理。

  • 实现步骤
    定义接口:首先定义一个接口,该接口中包含了目标对象的所有方法。
    创建InvocationHandler对象:创建一个实现了InvocationHandler接口的代理处理器对象,该对象用于处理代理对象的方法调用。
    创建代理对象:通过Proxy类的newProxyInstance()方法创建代理对象。该方法需要传入三个参数:ClassLoader对象、Class[]对象和InvocationHandler对象。
    调用代理对象方法:通过代理对象调用目标对象的方法时,代理对象会将方法调用转发给InvocationHandler对象处理。在InvocationHandler对象的invoke()方法中,可以对方法调用进行增强处理,然后再将处理后的方法调用结果返回给代理对象。

  • 优缺点
    优点:
    基于接口代理,不需要修改目标类的代码。
    简单易用,是Java原生的支持。
    缺点:
    只能代理实现了接口的类。
    相对于CGLIB代理,性能可能稍低(在JDK 1.8及以后版本中,JDK动态代理的性能已经得到了很大提升)。

二、CGLIB代理

  • 原理
    CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它通过继承目标对象来创建代理对象。CGLIB在运行时动态地生成一个目标对象的子类,并重写目标对象的所有非final方法,在重写的方法中加入增强代码,以实现对目标对象的代理访问。

  • 实现步骤
    创建Enhancer对象:首先创建一个Enhancer对象,它是CGLIB的核心类,用于生成代理对象。
    设置父类和回调函数:通过setSuperclass()方法设置目标对象的父类,然后通过setCallback()方法设置回调函数,即MethodInterceptor对象。
    创建代理对象:调用Enhancer对象的create()方法,生成代理对象。在生成代理对象的过程中,CGLIB会动态创建目标对象的子类,并重写目标对象的所有非final方法,在重写的方法中加入了增强代码。
    调用代理对象方法:通过代理对象来调用目标对象的方法时,CGLIB会先判断是否是代理对象自己实现的方法,如果不是,则调用父类(即目标对象)的方法。在调用父类方法之前和之后,CGLIB会自动调用回调函数中的intercept()方法,以实现增强逻辑的执行。

  • 优缺点
    优点:
    不需要目标对象实现接口。
    可以代理没有实现接口的类。
    缺点:
    由于采用继承的方式实现代理,会生成目标对象的子类,因此在性能上相对较低(相对于JDK动态代理)。
    无法代理final类或final方法。

三、使用场景

  • JDK动态代理:主要用于实现AOP(面向切面编程)等场景,特别是在目标对象实现了接口的情况下。
  • CGLIB代理:当目标对象没有实现接口时,或者需要代理final类时,可以考虑使用CGLIB代理。在Spring AOP等框架中,如果目标对象没有实现接口,Spring通常会使用CGLIB来创建代理对象。

6. 饿汉式单例模式与懒汉式单例模式的区别

  • 实例创建时机
  1. 饿汉式:在类被加载到JVM时,就会立即创建类的实例。这种方式的好处是简单且线程安全,但缺点是如果单例对象较大或创建过程复杂,可能会增加类加载的时间,并占用不必要的内存资源,尤其是当单例对象在程序生命周期中很少被使用时。
  2. 懒汉式:在类被加载时不会立即创建实例,而是在第一次调用getInstance()方法时才创建实例。这种方式实现了延迟加载,节省了资源,但需要注意线程安全问题,因为在多线程环境下可能会创建多个实例,破坏单例的唯一性。
  • 线程安全性
  1. 饿汉式:由于实例的创建是在类加载时完成的,且instance是静态的final变量,因此它是线程安全的,无需额外的同步措施。
  2. 懒汉式:在多线程环境下,如果不加同步措施,懒汉式单例模式可能会出现多个实例的情况。为了解决这个问题,可以在getInstance()方法上加锁(如synchronized关键字),但这会降低性能。
    另一种更高效的解决方案是使用双重检查锁定模式或静态内部类模式来实现懒汉式单例模式,这两种方式都能在保证线程安全的同时提高性能。
  • 性能与资源占用
  1. 饿汉式:虽然简单且线程安全,但如果单例对象较大或创建过程复杂,可能会增加类加载时间和内存占用。
  2. 懒汉式:通过延迟加载实例,节省了资源,但需要注意线程安全问题和同步带来的性能开销。

综上所述,如果单例对象较小且创建过程简单,且对资源占用不敏感,可以选择饿汉式单例模式;如果单例对象较大或创建过程复杂,且对资源占用敏感,可以选择懒汉式单例模式,并注意解决线程安全问题。

7. mybatis中$和#区别(合资)

  1. #{}(预处理)
  • 功能:#{}是 MyBatis 提供的参数占位符,它用于预处理 SQL 语句中的参数。MyBatis 会使用 PreparedStatement 来设置参数值,这样可以有效防止 SQL 注入攻击。
  • 使用场景:当你需要在 SQL 语句中插入变量时,应该使用 #{}。MyBatis 会将 #{} 中的内容解析为参数名,并在执行时通过 PreparedStatement 的 setInt 或 setString 等方法来设置具体的值。
  1. 字符串替换${}
  • 功能:${} 是 MyBatis 提供的另一种参数替换方式,它用于直接替换 SQL 语句中的字符串。这种方式不会进行预处理,而是直接将参数值拼接到 SQL 语句中。
  • 使用场景:${} 主要用于动态 SQL 语句的拼接,比如表名、列名等动态变化的情况。但是,由于它直接将参数值拼接到 SQL 语句中,因此存在 SQL 注入的风险,使用时需要格外小心。

总结

  • 安全性:#{}提供了更高的安全性,因为它使用了 PreparedStatement,可以有效防止 SQL 注入。而 ${} 则直接将参数值拼接到 SQL 语句中,存在 SQL 注入的风险。
  • 使用场景:#{} 主要用于 SQL 语句中的参数值替换;而 ${} 主要用于动态 SQL 语句的拼接,如动态表名、动态列名等。
  • 性能:在大多数情况下,#{} 和 ${} 的性能差异可以忽略不计。但是,由于 ${} 可能会导致 SQL 语句的频繁编译(如果 SQL 语句中包含动态部分),因此在某些情况下可能会对性能产生一定影响。然而,这种影响通常很小,并且可以通过缓存等技术来优化。

8. 缓存框架有使用过哪些?memcache和redis有什么区别?项目中,怎么去选择?

缓存有:ehcache,memcache和redis等

区别:

  1. 数据类型和灵活性
  • Memcache:主要支持简单的键值对(key-value)存储,数据类型较为单一。这种简单的存储方式使得Memcache在处理大量简单的数据缓存时非常高效,但不适合需要复杂数据结构或操作的场景。
  • Redis:支持多种数据类型,包括字符串(strings)、列表(lists)、集合(sets)、有序集合(sorted sets)和哈希表(hashes)等。这使得Redis在处理复杂数据结构时更加灵活和高效,适用于更多样化的应用场景。
  1. 数据持久化和恢复
  • Memcache:仅将数据存储在内存中,不提供数据持久化功能。一旦服务器宕机或重启,缓存中的数据将全部丢失,无法恢复。因此,Memcache更适合用于缓存那些可以容忍数据丢失的场景。
  • Redis:提供了多种数据持久化机制,包括RDB快照和AOF日志。这些机制可以在服务器重启后恢复数据,保证数据的持久性。因此,Redis更适合用于需要保证数据不丢失的场景。
  1. 分布式和高可用性
  • Memcache:虽然可以通过一些工具(如magent)实现集群和分布式部署,但其原生并不直接支持分布式。此外,Memcache在节点故障时,需要手动进行数据的迁移和恢复,这增加了维护的复杂性和成本。
  • Redis:支持多种高可用架构,如主从同步、哨兵模式(Sentinel)和Cluster集群等。这些架构可以在节点故障时自动进行数据的迁移和恢复,保证服务的连续性。因此,Redis在分布式和高可用性方面更具优势。
  1. 特定应用场景
  • Memcache:由于其简单高效的特点,Memcache常被用于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和session等。在电子商务类网站中,Memcache可以用于缓存分类数据等读多写少的数据。
  • Redis:Redis的应用场景更加广泛。除了作为缓存层外,Redis还可以用于会话存储、计数器、消息队列、分布式锁、地理位置应用、实时排行榜等多种场景。其丰富的数据结构和功能特性使得Redis能够应对更多样化的业务需求。

项目使用选择:

redis是单线程实现,若需要使用控制某些并发状态时,可以使用redis.项目中需要使用 复杂的list,set操作时,同时可以对数据进行持久化。

当存储数据较大时,如100k以上,那memcache性能较好,在多核上,memcache较好

Redis基本原理

  1. 内存存储
  • 核心特点:Redis将数据存储在内存中,这使得它能够以极快的速度响应读写请求。内存存储方式显著降低了数据访问的延迟,提高了系统的整体性能。
  • 优势:高速读写操作,能够处理大量并发访问。
  1. 键值对存储
  • 存储模型:Redis以键值对的形式存储数据,每个键都与一个特定的值相关联。这种存储模型使得Redis能够灵活地组织数据,并快速访问所需信息。
  • 应用场景:键值对存储模型适用于多种场景,如缓存、会话管理、消息队列等。
  1. 多种数据类型支持
  • 数据类型:Redis支持多种数据类型,包括字符串、哈希表、列表、集合和有序集合等。这些数据类型提供了丰富的数据结构选择,以满足不同的应用需求。
  • 灵活性:不同的数据类型适用于不同的场景,使得Redis在缓存应用中具有高度的灵活性和高效性。
  1. 持久化
  • 机制:Redis提供了两种持久化方式:RDB(Redis数据库)和AOF(Append Only File)。RDB在指定时间间隔内将内存中的数据快照保存到硬盘上,而AOF则记录了所有对数据的修改操作,通过重放这些操作来恢复数据。
  • 目的:确保在Redis服务器重启或发生故障时,数据不会丢失。
  1. 缓存淘汰策略
  • 策略:Redis通过设置缓存的最大内存容量,当达到容量限制时会触发缓存淘汰策略。常见的淘汰策略包括LRU(最近最少使用)、LFU(最不常使用)和随机替换等。
  • 目的:优化内存使用,确保缓存中存储的是最有价值的数据。
  1. 分布式部署
  • 特性:Redis支持分布式部署,通过在多个节点之间共享数据进行负载均衡和高可用性。分布式Redis使用一致性哈希算法来确定数据在哪个节点中存储,同时使用复制和故障转移机制来提高可用性。
  • 优势:可扩展性高,能够满足大规模应用的需求。
  1. 缓存机制的高级特性
  • 缓存击穿:当缓存中某个热点数据过期时,大量请求直接访问数据库,导致数据库压力骤增。Redis通过加锁更新、逻辑过期等方式来避免缓存击穿。
  • 缓存穿透:查询缓存和数据库中都不存在的数据,导致每次请求都直接打到数据库。Redis通过缓存空值/默认值、使用布隆过滤器等方式来防止缓存穿透。
  1. 布隆过滤器:利用布隆过滤器快速判断一个元素是否在一个集合中,从而避免无效查询。
  2. 空对象缓存:对不存在的数据也进行缓存,但缓存的值是一个特殊标记(如空对象或特定的占位符),这样后续请求可以直接返回这个特殊标记,避免查询数据库。
  3. 参数校验:在业务层对请求参数进行严格的校验,避免无效请求。
  • 缓存雪崩:大量缓存数据在同一时间过期,导致大量请求直接访问数据库,可能引发系统崩溃。Redis通过提高缓存可用性、集群部署、设置多级缓存等方式来应对缓存雪崩。
  1. 缓存失效时间随机化:将缓存的失效时间设置为一个随机值,避免大量缓存同时失效。
  2. 多级缓存:使用多级缓存架构,如Nginx本地缓存、Redis远程缓存等,分散压力。
  3. 热点数据预热:在系统启动时或低峰期,将热点数据提前加载到缓存中,避免缓存冷启动时的压力。
  • 缓存一致性解决方案:缓存一致性是指缓存和数据库中的数据不一致,导致业务逻辑出现问题。解决方案包括:
  1. 读写分离:对于读操作,优先从缓存中获取数据;对于写操作,先更新数据库,然后异步更新缓存。
  2. 异步更新缓存:通过消息队列等异步方式,确保数据库更新后缓存能够及时得到更新。
    3.双写策略:同时更新数据库和缓存,但需要确保操作的原子性。
  3. 最终一致性方案:在某些对一致性要求不是非常高的场景下,可以采用最终一致性方案,允许缓存和数据库之间存在短暂的不一致。
  • 缓存容量限制解决方案:缓存容量受限时,无法缓存所有的数据。解决方案包括:
  1. LRU缓存淘汰算法:使用最近最少使用(LRU)等缓存淘汰算法,淘汰冷数据,保留热点数据。
  2. 分片缓存:将缓存数据按照一定规则进行分片,分散存储在不同的缓存节点上。
  1. 分布式缓存:采用分布式缓存架构,如Redis Cluster等,提高缓存的容量和可扩展性。
  • 缓存并发竞争解决方案:多个请求同时请求同一个缓存时,可能导致并发竞争。解决方案包括:
  1. 分布式锁:使用分布式锁(如Redis锁、Zookeeper锁)来避免并发竞争,确保同一时间只有一个请求能够更新缓存。
  2. CAS操作:利用比较并交换(CAS)等原子操作来更新缓存,避免并发冲突。

9. Autowired和Resource关键字的区别

  • @Autowired是Spring提供的注解,@Resource是Java EE提供的注解。
  • @Autowired默认根据类型进行自动装配,如果有多个匹配的Bean,则根据名称进行匹配;@Resource默认根据名称进行自动装配,如果找不到匹配的Bean,则根据类型进行匹配。
  • 使用@Autowired需要引入Spring框架的依赖,而使用@Resource则不需要。

10.Spring MVC的理解

在Spring MVC中,模型(Model)表示应用程序的数据和业务逻辑,视图(View)负责展示数据给用户,控制器(Controller)处理用户请求并调度模型和视图之间的交互

11.Spring MVC的工作原理

  1. 用户发送请求到springmvc框架提供的DispatcherServlet 这个前端控制器
  2. 前端控制器会去找处理器映射器(HandlerMapping),处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet 。
  3. 根据处理器映射器返回的处理器,DispatcherServlet 会找“合适”的处理器适配器(HandlerAdapter)
  4. 处理器适配器HandlerAdpater会去执行处理器(Handler开发的时候会被叫成Controller也叫后端控制器) 执行之前会有转换器、数据绑定、校验器等等完成上面这些才会去正在执行Handler
  5. 后端控制器Handler执行完成之后返回一个ModelAndView对象
  6. 处理器适配器HandlerAdpater会将这个ModelAndView返回前端控制器DispatcherServlet。前端控制器会将ModelAndView对象交给视图解析器ViewResolver。
  7. 视图解析器ViewResolver解析ModelAndView对象之后返回逻辑视图
  8. 前端控制器DispatcherServlet对逻辑视图进行渲染(数据填充)之后返回真正的物理View并响应给浏览器。

12.spring bean的生命周期

  1. 实例化(Instantiation):在这个阶段,Spring容器会根据配置文件或注解创建Bean的实例。可以通过构造函数实例化或者通过工厂方法实例化。
  2. 属性赋值(Population):在实例化后,Spring容器会将配置文件或注解中定义的属性值注入到Bean实例中。可以通过setter方法注入属性值,也可以通过字段注入
  3. 初始化(Initialization):在属性赋值完成后,Spring容器会调用Bean的初始化方法。可以通过配置文件中的init-method属性或者@PostConstruct注解来指定初始化方法
  4. . 使用(In Use):在初始化完成后,Bean可以被应用程序使用。此时Bean处于活动状态,可以执行业务逻辑
  5. 销毁(Destruction):当应用程序关闭或者不再需要Bean时,Spring容器会调用Bean的销毁方法进行清理工作。可以通过配置文件中的destroy-method属性或者@PreDestroy注解来指定销毁方法。

13 解释Spring支持的几种bean的作用域?

Spring框架支持以下几种bean的作用域:

  1. Singleton(单例):这是默认的作用域,每个Spring容器中只会存在一个实例。无论在应用程序的任何地方注入该bean,都会得到同一个实例。这种作用域适用于那些无状态的bean,或者对于那些需要共享数据的bean。
  2. . Prototype(原型):每次从容器中获取该bean时,都会创建一个新的实例。每个实例都有自己的状态,因此适用于那些需要维护状态的bean。
  3. Request(请求):每个HTTP请求都会创建一个新的实例,该实例仅在当前请求的范围内有效。适用于Web应用程序中需要在每个请求中使用的bean。
  4. . Session(会话):每个用户会话都会创建一个新的实例,该实例仅在当前用户会话范围内有效。适用于Web应用程序中需要在每个用户会话中使用的bean。
  5. . Global Session(全局会话):类似于Session作用域,但仅适用于基于portlet的Web应用程序。在基于portlet的Web应用程序中,全局会话表示整个应用程序的会话。

14. mysql优化

  1. 优化数据库设计
  • 合理的表结构:确保表结构合理,避免不必要的冗余字段,使用适当的数据类型。
  • 使用索引:合理创建索引可以大大提高查询速度,但过多的索引也会降低写操作的性能,因此需要根据实际情况选择。
  • 规范化和反规范化:在保持数据完整性的同时,适当的反规范化可以减少查询的复杂度和提高查询效率
  1. 优化查询
  • 使用EXPLAIN分析查询:通过EXPLAIN命令查看MySQL如何执行你的SQL语句,分析查询是否使用了索引,是否进行了全表扫描等。
  • 优化SELECT语句:确保只查询需要的列,避免使用SELECT *;使用WHERE子句过滤记录;尽量使用表连接代替子查询。
  • 使用连接(JOINs)代替子查询:在可能的情况下,使用连接(JOINs)替代子查询可以提高性能
  • 使用索引:为经常查询的列和经常用于连接的列创建索引。但注意索引并非越多越好,因为索引会占用额外的磁盘空间,并可能影响插入、删除和更新操作的性能。
  • 优化JOIN操作:确保JOIN操作中的ON子句或WHERE子句中的条件列都已经被索引。
  1. 索引
    (1). 确定需要索引的列
  • 高频查询列:对查询条件(WHERE子句)、排序条件(ORDER BY子句)和分组条件(GROUP BY子句)中经常出现的列添加索引。
  • 唯一性列:对于具有唯一约束的列,添加唯一索引(UNIQUE INDEX)不仅可以提高查询速度,还可以确保数据的唯一性。
  • 外键列:对于外键列,通常也需要索引,因为外键关联查询是非常常见的操作。
    (2). 选择合适的索引类型
  • B-Tree索引:MySQL中最常用的索引类型,适用于全键值、键值范围或键值前缀查找。
  • 哈希索引:基于哈希表的索引,适用于等值查询,但不适用于范围查询。
  • 全文索引:用于搜索文本中的关键词,适用于TEXT或CHAR类型的列。
  • 空间索引:用于地理空间数据类型。
    (3). 创建索引
    在MySQL中,可以使用CREATE INDEX语句或ALTER TABLE语句来添加索引。例如:
CREATE INDEX idx_column_name ON table_name(column_name);  
  
ALTER TABLE table_name ADD INDEX idx_column_name(column_name);

(4). 避免索引失效

  • 不要在索引列上进行函数操作或计算:这会导致索引失效,因为MySQL无法利用索引进行查找。
  • 尽量使用索引列的最左前缀:对于复合索引(索引包含多个列),MySQL可以利用索引的最左前缀来加速查询。
  • 注意数据类型匹配:在比较时,如果数据类型不匹配,MySQL可能会进行类型转换,从而导致索引失效。
  • LIKE语句操作:一般情况下不鼓励使用LIKE操作,如果非使用不可,注意LIKE "%aaa%"不会使用索引,而LIKE "aaa%"可以使用索引。
  • 避免NOT IN和<>操作:这些操作可能会导致索引失效。

(5). 监控和优化索引

  • 定期分析索引的使用情况:使用SHOW INDEX FROM table_name;来查看表的索引信息,使用EXPLAIN或EXPLAIN ANALYZE语句来分析查询的执行计划,查看是否利用了索引。
  • 删除无用的索引:无用的索引会占用额外的磁盘空间,并可能降低更新表的速度。定期检查和删除不再需要的索引。
  • 优化索引结构:根据查询模式和数据变更模式,可能需要调整索引的列顺序或类型,以优化性能。
  • 通过合理使用索引,可以显著提高MySQL数据库的性能。然而,也需要注意索引的维护成本和潜在的负面影响,避免过度索引。
  1. 缓存
    应用层缓存:使用Redis、Memcached等缓存系统,缓存数据库查询结果,减少对数据库的访问次数。
  2. 并发和锁
    理解并优化锁:MySQL中的锁(如表锁、行锁)可以保护数据的完整性和一致性,但不当的使用会导致性能瓶颈。
    使用适当的隔离级别:根据应用的需求,选择合适的事务隔离级别可以减少锁的竞争。

15.分布式锁的实现

  1. 基于数据库的分布式锁
    利用数据库的唯一索引来实现分布式锁。例如,在数据库中创建一个锁表,包含方法名、锁持有者等信息,并给方法名加上唯一索引。当一个节点想要获取锁时,就向表中插入一条记录,如果插入成功则表示获取锁成功;如果插入失败(因为唯一索引冲突),则表示锁已被其他节点持有。释放锁时,删除对应的记录即可。

缺点:数据库性能瓶颈可能会成为问题,且数据库操作需要一定的时间,不适合高并发场景。

  1. 基于Redis的分布式锁
    Redis是一个高性能的键值存储系统,支持多种数据类型。利用Redis的某些特性(如SETNX命令或SET命令的NX、PX选项)可以实现分布式锁。

实现方式:

使用SET key value [NX|XX] [EX seconds|PX milliseconds]命令,其中NX表示键不存在时设置键,PX设置键的过期时间(毫秒)。

客户端尝试使用SET key value NX PX milliseconds命令来设置锁,如果设置成功,则获取锁成功;如果设置失败,则表示锁已被其他客户端持有。
释放锁时,可以使用DEL key命令来删除锁。
注意:为了防止客户端崩溃导致锁无法释放(锁永久丢失),可以在设置锁时加入一个唯一标识符(如UUID),并在释放锁时进行验证,确保只有锁的持有者才能释放锁。

  1. 基于ZooKeeper的分布式锁
    ZooKeeper是一个高性能的协调服务,它主要用于管理大型分布式系统中的数据一致性。ZooKeeper提供了一套丰富的API来实现分布式锁。

实现方式:

创建一个临时顺序节点作为锁标识。
每个客户端尝试获取锁时,都去创建这个临时顺序节点。
客户端获取自己创建的节点的序号。
客户端检查自己是否是序号最小的节点,如果是,则获取锁成功;如果不是,则监听序号比自己小的前一个节点的删除事件。
当前一个节点被删除时(即前一个锁的持有者释放了锁),客户端再次检查自己是否是序号最小的节点,如果是,则获取锁成功。

优点:ZooKeeper的强一致性使得锁的实现更加可靠,且ZooKeeper的节点监控机制可以自动处理锁的释放问题。

16. JVM内存模型是如何?垃圾回收机制有哪些?如何对JVM进行调优?

由栈和堆组成,栈是运行时单位,堆内存则分为年轻代、年老代、持久代等,年轻代中的对象经过几次的回收,仍然存在则被移到年老代;持久代主要是保存class,method,filed等对象。

sun回收机制:主要对年轻代和年老代中的存活对象进行回收,分为以下:

JVM调优主要是对堆内容和回收算法进行配置,需要对jdk产生的回收日志进行观察,同时通过工具(Jconsole,jProfile,VisualVM)对堆内存不断分析,这些优化是一个过程,需要不断地进行观察和维护。

  1. JVM内存模型
    JVM(Java虚拟机)内存模型是Java运行时环境的核心组成部分,它定义了Java程序在执行过程中如何分配和管理内存。JVM内存模型主要包括以下几个部分:
  • 堆(Heap):
    是JVM管理的最大一块内存区域,主要用于存放对象实例。
    在JDK 1.8及之后,堆内存被分为新生代(Young Generation)和老年代(Old Generation)。新生代又被细分为Eden区、From Survivor区(也称为S0区)和To Survivor区(也称为S1区)。
    堆内存的大小可以通过JVM启动参数-Xms(设置初始堆大小)和-Xmx(设置最大堆大小)来调整。
  • 方法区(Method Area):
    在JDK 1.8之前,方法区被称为永久代(PermGen space),用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
    在JDK 1.8及之后,永久代被元空间(Metaspace)所取代,元空间直接使用物理内存,不再受JVM内存大小参数限制。
  • 栈(Stack):
    包括虚拟机栈(Java虚拟机栈)和本地方法栈(Native Method Stack)。
    虚拟机栈是线程私有的,用于存储局部变量表、操作数栈、动态连接、方法返回地址等信息。
    每个方法被执行时,JVM都会在虚拟机栈中创建一个栈帧(Stack Frame),用于存储该方法的局部变量等信息。
  • 程序计数器(Program Counter Register):
    是一块较小的内存空间,几乎可以忽略不计,它作为当前线程所执行的字节码的行号指示器。
    字节码解释器工作时,就是通过改变程序计数器的值来选取下一条需要执行的字节码指令。
  1. 垃圾回收机制

Java的垃圾回收机制(Garbage Collection, GC)通过自动回收不再使用的对象,释放内存空间,提高应用程序的性能和可靠性。主要的垃圾回收机制包括:

  • 标记-清除(Mark-Sweep):
    首先标记出所有需要回收的对象,然后统一回收被标记的对象。
    这种方法简单但容易产生内存碎片。
  • 标记-整理(Mark-Compact):
    在标记的基础上,让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
    这种方法解决了内存碎片问题,但效率较低。
  • 复制(Copying):
    将可用内存划分为大小相等的两块,每次只使用其中一块。当这块内存用完时,就将还存活的对象复制到另一块内存上,然后清理掉已使用的内存。
    这种方法适用于对象存活率较低的场景,如新生代内存。
    分代收集(Generational Collection):
    根据对象的存活周期将内存划分为不同的区域,如新生代和老年代,分别采用不同的垃圾回收策略。
    新生代通常使用复制算法,老年代则可能使用标记-清除、标记-整理等算法。
  1. JVM调优
    JVM调优通常涉及以下几个方面:
  • 调整堆大小:
    使用-Xms和-Xmx参数设置JVM启动时的初始堆大小和最大堆大小。
    根据应用的实际需求调整堆大小,以避免频繁进行垃圾回收或内存溢出。
  • 选择合适的垃圾收集器:
    根据应用的特性选择合适的垃圾收集器,如Parallel GC、CMS、G1等。
    不同的垃圾收集器有不同的设计目标和适用场景,选择合适的垃圾收集器可以显著提高应用的性能。
  • 监控运行时性能:
    使用APM(Application Performance Management)工具如Dynatrace、New Relic或开源工具如Prometheus来监控JVM的性能指标。
    通过监控工具可以实时了解应用的性能表现,并及时发现和解决潜在的性能问题。
  • 分析内存泄漏:
    利用工具如Eclipse Memory Analyzer定期检查和优化内存泄漏问题。
    内存泄漏是导致应用性能下降和崩溃的常见原因之一,及时分析和解决内存泄漏问题对于保持应用的稳定性和性能至关重要。
  • 调整GC参数:
    根据应用的实际情况调整GC参数,如新生代与老年代的比例、Survivor区的比例等。
    合理的GC参数设置可以优化垃圾回收的性能和效率。

17. Dubbo的原理和机制

  1. dubbo的核心服务是什么?
  • 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。

  • 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

  • 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

  1. dubbo能做什么?
  • 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。

  • 软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。

  • 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。

采用spring的配置方式进行配置,完全透明化的接入应用,对应用没有任何入侵,只需要spring加载dubbo的配置就可以了。

  1. Dubbo 的架构是怎样的?
    答: Dubbo 的架构主要分为三层:服务接口层、服务实现层和服务注册层。服务接口层是提供给客户端的接口,服务实现层是具体的服务实现,服务注册层负责服务的注册和发现。
  • 接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现

  • 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心

  • 服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和 服务端的 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory

  • 服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService

  • 路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router和LoadBlancce

  • 监控层(Monitor):RPC调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor和MonitorService

  • 远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker和Exporter

  • 信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和 Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer

  • 网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为Channel、Transporter、Client、Server和Codec

  • 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool

18. Spring Cloud有哪些核心组件

  • Eureka:服务发现和注册中心,用于管理微服务实例的注册和发现。服务提供者将自己的信息注册到Eureka Server上,服务消费者从Eureka Server上获取服务提供者的信息,从而进行远程调用。

  • Ribbon:客户端负载均衡器,它基于客户端的负载均衡策略,可以在服务消费者端进行负载均衡。Ribbon内置了多种负载均衡策略,如随机、轮询、加权轮询等。

  • Hystrix:断路器模式的实现,用于解决分布式系统的雪崩效应。当某个服务出现故障时,Hystrix会快速失败并返回错误信息,而不是等待服务响应超时,从而避免整个系统被拖垮。

  • Feign:声明式的Web服务客户端,它使得编写Web服务客户端变得更加简单。通过Feign,开发者只需要创建一个接口并使用注解来定义服务调用的方法,Feign会自动处理服务的请求和响应。

  • Config:集中式的配置管理,它可以将应用的配置信息存储在Git、SVN等版本控制系统中,并通过HTTP或Spring Cloud Bus等方式进行配置信息的传递和更新

19. Eureka在服务发现中扮演什么角色

Eureka是Spring Cloud的服务发现和注册中心。它提供了服务注册和发现的功能。服务提供者启动时将自己的信息(如IP、端口、服务名等)注册到Eureka Server上,服务消费者从Eureka Server上获取服务提供者的信息列表,然后从中选择一个服务实例进行远程调用。Eureka还支持高可用集群模式,以保证服务的可靠注册和发现。

20. Ribbon是如何实现负载均衡的?

回答:
Ribbon是Spring Cloud的客户端负载均衡器,它基于客户端的负载均衡策略,可以在服务消费者端进行负载均衡。Ribbon内置了多种负载均衡策略,如随机、轮询、加权轮询等。
当服务消费者需要调用某个服务时,Ribbon会根据配置的负载均衡策略,从服务提供者列表中选择一个服务实例进行调用。这样可以实现服务的负载均衡,提高系统的吞吐量和可用性。

21.消息队列如何保证消息不丢失

丢失原因:

  1. 生产者发送失败:
  • 网络故障导致生产者无法连接到消息队列。
  • 生产者发送消息到错误的主题或队列。
  • 消息过大超过队列限制,被自动丢弃。
  1. 消费者处理失败:
  • 消费者在处理消息时发生逻辑错误或异常,导致消息处理失败。
  • 消费者错误地确认了消息(如在消息还没处理完时就发送了确认),导致消息从队列中移除。
  • 消费者在处理消息时崩溃,导致正在处理的消息丢失。
  1. 消息队列本身故障:
  • 消息队列崩溃或不可用,导致生产者无法发送消息,消费者无法获取消息。
  • 消息队列容量不足,无法存储所有消息。
  • 消息队列配置错误,导致消息处理不当。

解决方法:

  1. 持久化机制:
  • 消息队列应将消息持久化存储到磁盘或数据库中,而不是仅存储在内存中。这样,即使在应用程序或消息队列服务器重启后,消息仍然可以被恢复。
  1. 消息确认机制(ACK机制):
  • 生产者发送消息后,等待消息队列的确认消息。只有收到确认后,才认为消息发送成功。
  • 消费者在成功处理消息后,发送确认消息给消息队列,确保消息已经被正确处理。如果消息队列在一定时间内没有收到确认消息,可以重新发送消息给其他消费者处理。
  1. 重试和死信队列:
  • 当消费者处理消息失败时,可以采取重试机制,尝试重新消费消息。可以设置最大重试次数,如果超过最大次数仍然失败,可以将消息放入死信队列等待后续处理。
  1. 分布式事务:
  • 对于需要确保消息和数据库事务一致性的场景,可以使用分布式事务来确保消息不丢失。通过事务消息的方式,确保消息发送和数据库操作在同一个本地事务中完成,从而确保两者的一致性。
  1. 监控和告警:
  • 对消息队列的运行状态进行监控,当发现异常或潜在问题时,及时触发告警通知相关人员进行处理。这有助于及时发现并解决可能导致消息丢失的问题。
  1. 网络检查与重试:
  • 在发送消息前检查网络连接,如果网络故障则进行重试。
  1. 错误处理与异常捕获:
  • 在生产者和消费者的代码中实现错误处理和异常捕获机制,确保在出现问题时能够及时处理并避免消息丢失。
  1. 适当配置:
  • 确保消息队列的配置正确,包括持久化、复制、容量等参数的设置,以满足业务需求并避免消息丢失。

22. RabbitMQ如何避免消息堆积?

消息堆积问题产生的原因往往是因为消息发送的速度超过了消费者消息处理的速度。因此解决方案无外乎以下三点:

  1. 提高消费者处理速度

消费者处理速度是由业务代码决定的,所以我们能做的事情包括:

尽可能优化业务代码,提高业务性能
接收到消息后,开启线程池,并发处理多个消息
优点:成本低,改改代码即可

缺点:开启线程池会带来额外的性能开销,对于高频、低时延的任务不合适。推荐任务执行周期较长的业务。

  1. 增加更多消费者

一个队列绑定多个消费者,共同争抢任务,自然可以提供消息处理的速度。

优点:能用钱解决的问题都不是问题。实现简单粗暴

缺点:问题是没有钱。成本太高

  1. 增加队列消息存储上限

在RabbitMQ的1.8版本后,加入了新的队列模式:Lazy Queue

这种队列不会将消息保存在内存中,而是在收到消息后直接写入磁盘中,理论上没有存储上限。可以解决消息堆积问题。

优点:磁盘存储更安全;存储无上限;避免内存存储带来的Page Out问题,性能更稳定;

缺点:磁盘存储受到IO性能的限制,消息时效性不如内存模式,但影响不大。

23.map用自己的对象作为key有什么注意点

  1. hashCode() 方法的实现:
    Map 通过 hashCode() 方法来确定对象在桶(bucket)中的位置。如果两个对象相等(即 equals(Object obj) 返回 true),则它们的 hashCode() 方法必须返回相同的整数值。
    因此,自定义类应当重写 hashCode() 方法以提供一个合理且有效的哈希码,这个哈希码能够反映出对象的“相等性”。
  2. equals(Object obj) 方法的实现:
    自定义类也应当重写 equals(Object obj) 方法来定义对象的等价性。这个方法决定了对象何时应该被视为“相等”。
    equals(Object obj) 方法的实现应当与 hashCode() 方法保持一致性。即,如果两个对象相等,那么它们的哈希码也必须相等。
  3. 不变性:
    用作Map键的对象最好是不可变的(immutable)。一旦对象的哈希码在其被用作键之后发生变化,将会导致无法预测的行为,包括丢失键值对和程序崩溃。
    如果键对象包含可变字段,而这些字段又被用作计算哈希码的一部分,那么这种情况尤其需要注意。
  4. 线程安全:
    如果在多线程环境中使用包含自定义键的Map,并且键或值本身不是线程安全的,那么需要考虑Map的线程安全问题。Java的ConcurrentHashMap提供了线程安全的Map实现。
  5. 内存泄漏:
    在使用复杂对象作为键时,还需要注意内存泄漏的风险。确保这些对象在不再需要时能够被垃圾回收器回收。
  6. 性能考虑:
    如果自定义对象的hashCode()和equals()方法实现不佳,可能会影响Map的性能,尤其是在大型Map或频繁操作的场景中。
  7. null 值:
    在大多数Map实现中,null值可以作为键或值(但具体情况取决于具体的Map实现,如HashMap允许null键和值,而TreeMap不允许null键)。如果你使用自定义对象作为键,并计划包含null键,那么你需要清楚了解这一行为。

24. 策略模式在哪些java中间件

  1. Spring框架:
  • 数据验证:Spring提供了Validator接口,允许开发者定义不同的验证策略,如电子邮件验证、值范围验证等。通过实现不同的验证器类并在控制器中使用@Valid注解,可以实现动态的数据验证策略选择。
  • 缓存管理:Spring Cache提供了Cache接口,允许定义不同的缓存策略,如LRU(最近最少使用)缓存、FIFO(先进先出)缓存等。通过在服务方法上使用@Cacheable注解,可以指定使用的缓存策略。
  1. Jakarta EE(前身为Java EE):
  • 事务管理:Jakarta EE容器提供了事务管理服务,允许定义不同的事务策略,如REQUIRED、REQUIRES_NEW等。通过在服务方法上使用@Transactional注解,可以指定使用的事务策略。
  • 依赖注入:Jakarta EE的CDI(上下文和依赖注入)规范允许使用注释(如@Inject)将依赖项注入到类中。虽然依赖注入本身并不直接体现策略模式,但依赖注入可以与策略模式结合使用,以实现算法的动态选择。
  1. JSF(JavaServer Faces):
  • 转换器和验证器:JSF提供了转换器和验证器机制,允许开发者实现不同的转换和验证策略,如日期和时间转换器、数值范围验证器等。这些转换器和验证器可以根据需要在页面上动态选择和应用。
  • 响应生命周期管理:JSF管理着响应的各个阶段,开发者可以定义不同的响应生命周期策略,如禁止某一生命周期阶段或自定义特定阶段的行为。
    服务治理中间件:
  1. Spring Cloud:虽然Spring Cloud本身并不直接体现策略模式,但它支持的服务发现、负载均衡、配置管理等功能可以通过集成不同的策略来实现动态选择。例如,负载均衡策略可以根据请求量、响应时间等因素动态选择服务器。
  2. 缓存中间件:
  • Redis、Ehcache等缓存中间件通常支持多种缓存策略,如LRU、LFU(最少使用频率)等。这些策略的选择可以通过配置缓存中间件来实现,从而间接体现了策略模式的思想。
  1. 消息队列中间件:
  • 如RabbitMQ、Apache Kafka等消息队列中间件虽然不直接应用策略模式,但它们的消息路由、消息处理等功能可以通过配置不同的路由策略和消息处理策略来实现类似的效果。

25. 实现一个线程安全的单例模式

public class Singleton {  
    // 使用volatile关键字防止指令重排序  
    private static volatile Singleton instance;  
  
    // 私有构造函数,防止外部实例化  
    private Singleton() {}  
  
    // 公共的静态方法,返回唯一实例  
    public static Singleton getInstance() {  
        if (instance == null) { // 第一次检查  
            synchronized (Singleton.class) { // 同步块  
                if (instance == null) { // 第二次检查  
                    instance = new Singleton(); // 实例化  
                }  
            }  
        }  
        return instance;  
    }  
}

或:

public class Singleton {  
    // 使用volatile关键字防止指令重排序  
    private static volatile Singleton instance;  
  
    // 私有构造函数,防止外部实例化  
    private Singleton() {}  
  
    // 公共的静态方法,返回唯一实例  
    public static Singleton getInstance() {  
        if (instance == null) { // 第一次检查  
            synchronized (Singleton.class) { // 同步块  
                if (instance == null) { // 第二次检查  
                    instance = new Singleton(); // 实例化  
                }  
            }  
        }  
        return instance;  
    }  
}

26.创建三个线程依次打印1-100

import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.ReentrantLock;  
  
public class ThreadPrint {  
    private static final int MAX = 100;  
    private static int i = 1;  
    private static final ReentrantLock lock = new ReentrantLock();  
    private static final Condition condition1 = lock.newCondition();  
    private static final Condition condition2 = lock.newCondition();  
    private static final Condition condition3 = lock.newCondition();  
    private static int currentThread = 1;  
  
    public static void main(String[] args) throws InterruptedException {  
        Thread t1 = new Thread(() -> print(1, condition1, condition2));  
        Thread t2 = new Thread(() -> print(2, condition2, condition3));  
        Thread t3 = new Thread(() -> print(3, condition3, condition1));  
  
        t1.start();  
        t2.start();  
        t3.start();  
  
        t1.join();  
        t2.join();  
        t3.join();  
    }  
  
    private static void print(int threadId, Condition current, Condition next) throws InterruptedException {  
        lock.lock();  
        try {  
            while (i <= MAX) {  
                while (currentThread != threadId) {  
                    current.await();  
                }  
                System.out.println("t" + threadId + ": " + i);  
                i++;  
                currentThread = threadId % 3 + 1; // 循环1, 2, 3  
                next.signal();  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
}

27.说说ArrayList 的扩容机制?

  • 当使用add方法的时候首先调用ensureCapacityInternal方法,传入 size+1进去,检查是否需要扩容

  • 如果空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10,获取“默认的容量”和要扩容的大小两者之间的最大值

  • 和当前数组长度比较,如果 if (minCapacity - elementData.length > 0)执行grow扩容方法

  • 将数组扩容为原来的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1);

  • 检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量

  • 再检查新容量newCapacity 是否超出了ArrayList所定义的最大容量,若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)

  • ArrayList 中copy数组的核心就是System.arraycopy方法,将original数组的所有数据复制到copy数组中,这是一个本地方法

28.Spring事务

  1. Spring事务是如何实现的?
    解答:Spring事务底层是基于数据库事务和AOP(面向切面编程)机制实现的。对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean。当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解。如果加了,那么则利用事务管理器(TransactionManager)创建一个数据库连接,并修改数据库连接的autocommit属性为false,禁止此连接的自动提交。然后执行当前方法中的SQL语句。执行完当前方法后,如果没有出现异常就直接提交事务;如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务。
  2. Spring事务的隔离级别有哪些?
    解答:Spring事务的隔离级别对应的就是数据库的隔离级别,主要包括以下几种:
  • DEFAULT:这是默认值,表示使用底层数据库默认的隔离级别。
  • READ_UNCOMMITTED:允许读取尚未提交的数据,可能会导致脏读、不可重复读和幻读。
  • READ_COMMITTED:只能读取已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读。
  • REPEATABLE_READ:保证在同一个事务中多次读取同一数据时,数据是一致的,避免了脏读和不可重复读,但可能会出现幻读。
  • SERIALIZABLE(序列化):最严格的隔离级别,完全串行化地执行事务,避免了脏读、不可重复读和幻读,但性能开销最大。
  1. Spring事务的传播机制是什么?
    解答:Spring事务的传播机制是Spring事务自己实现的,它定义了事务在不同情况下的行为。主要包括以下几种传播行为:
  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • REQUIRES_NEW:创建一个新的事务,并暂停当前事务(如果存在)。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
  • NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个嵌套事务作为当前事务的一个子事务;如果当前没有事务,则行为类似于REQUIRED。
  1. @Transactional注解的失效情况有哪些?
    解答:@Transactional注解在某些情况下可能会失效,主要包括以下几种情况:
  • 方法不是public的:@Transactional只能用于public的方法上。如果方法不是public的(如private、protected等),则注解不会生效。
  • 方法被类内部调用:如果一个类中的方法A调用了同一个类中的方法B,而方法B上有@Transactional注解,则这个注解不会生效。因为Spring的事务管理是基于代理来实现的,而代理对象只能拦截从外部对方法的调用。
  • 异常类型不匹配:@Transactional注解默认只回滚运行时异常(RuntimeException)和错误(Error),而不回滚受检异常(checked exception)。如果希望回滚受检异常,需要在@Transactional注解的rollbackFor属性中指定异常类型。
  • 数据库不支持事务:如果使用的数据库不支持事务(如某些NoSQL数据库),则@Transactional注解也不会生效。
  1. Spring事务管理有哪些方式?
    解答:Spring事务管理主要有两种方式:编程式事务管理和声明式事务管理。
  • 编程式事务管理:通过编程的方式管理事务,需要手动控制事务的开启、提交和回滚。这种方式比较灵活,但代码复杂度较高。
  • 声明式事务管理:通过声明的方式管理事务,将事务管理的逻辑与业务逻辑分离。Spring提供了基于注解(如@Transactional)和基于XML配置两种方式来实现声明式事务管理。这种方式简化了事务管理的代码,提高了开发效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/888952.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker简述

1.安装dockers&#xff0c;配置docker软件仓库 安装&#xff0c;可能需要开代理&#xff0c;这里我提前使用了下好的包安装 启动docker systemctl enable --now docker查看是否安装成功 2.简单命令 拉取镜像&#xff0c;也可以提前下载使用以下命令上传 docker load -i imag…

大数据毕业设计选题推荐-B站热门视频数据分析-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

使用C语言获取iostat中的await值的方法和方案

使用C语言获取iostat中的await值的方法和方案 1. 准备工作2. 调用iostat命令并获取输出3. 解析iostat输出4. 完整实现和错误处理5. 注意事项在Linux系统中,iostat命令是sysstat软件包的一部分,用于监控系统的CPU、网卡、tty设备、磁盘、CD-ROM等设备的活动情况和负载信息。其…

鸿蒙OS投票机制

(基于openharmony5.0) 投票机制 param get | grep ohos.boot.time 图 投票机制参数图 只有当所有的投票完成&#xff0c;开机动画才会退出&#xff0c;整理需要投票的系统应用&#xff08;三方应用不参与投票&#xff09;如下图所示&#xff1a; 以进程foundation为例&…

基于Kafka2.1解读Producer原理

文章目录 前言一、Kafka Producer是什么&#xff1f;二、主要组件1.Kafka Producer1.1 partitioner1.2 keySerializer1.3 valueSerializer1.4 accumulator1.5 sender 2.Sender2.1 acks2.2 clientinFlightBatches 3. Selector3.1 nioSelector3.2 channels 4. 全局总览 总结 前言…

Arduino UNO R3自学笔记20 之 Arduino如何测定电机速度?

注意&#xff1a;学习和写作过程中&#xff0c;部分资料搜集于互联网&#xff0c;如有侵权请联系删除。 前言&#xff1a;在学习了Arduino的相关基础知识后&#xff0c;现在做个综合应用&#xff0c;给旋转的电机测速。 1.实验目的 测定旋转电机的转速。 2.实验器材-编码器 …

【hot100-java】二叉树的最近公共祖先

二叉树篇 我觉得是比两个节点的深度&#xff0c;取min&#xff08;一种情况&#xff09; DFS解题。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/ clas…

力扣题11~15

题11&#xff08;中等&#xff09;&#xff1a; 思路&#xff1a; 这种题目第一眼就是双循环&#xff0c;但是肯定不行滴&#xff0c;o(n^2)这种肯定超时&#xff0c;很难接受。 所以要另辟蹊径&#xff0c;我们先用俩指针&#xff08;标志位&#xff09;在最左端和最右端&am…

基于SpringBoot智能垃圾分类系统【附源码】

基于SpringBoot智能垃圾分类系统 效果如下&#xff1a; 系统首页界面 用户注册界面 垃圾站点页面 商品兑换页面 管理员登录界面 垃圾投放界面 物业登录界面 物业功能界图 研究背景 随着城市化进程的加速&#xff0c;生活垃圾的产量急剧增加&#xff0c;传统的垃圾分类方式已…

【C++】二叉搜索树+变身 = AVL树

&#x1f680;个人主页&#xff1a;小羊 &#x1f680;所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 前言一、AVL树二、AVL树的实现2.1 平衡因子2.2 旋转处理2.2.1 左单旋&#xff1a;插入新节点后单纯的右边高2.2.2 …

光路科技TSN交换机:驱动自动驾驶技术革新,保障高精度实时数据传输

自动驾驶技术正快速演进&#xff0c;对实时数据处理能力的需求激增。光路科技推出的TSN&#xff08;时间敏感网络&#xff09;交换机&#xff0c;在比亚迪最新车型中的成功应用&#xff0c;显著推动了这一领域的技术进步。 自动驾驶技术面临的挑战 自动驾驶系统需整合来自雷达…

大模型基础:基本概念、Prompt、RAG、Agent及多模态

随着大模型的迅猛发展&#xff0c;LLM 作为人工智能的核心力量&#xff0c;正以前所未有的方式重塑着我们的生活、学习和工作。无论是智能语音助手、自动驾驶汽车&#xff0c;还是智能决策系统&#xff0c;大模型都是幕后英雄&#xff0c;让这些看似不可思议的事情变为可能。本…

43 C 程序动态内存分配:内存区域划分、void 指针、内存分配相关函数(malloc、calloc、realloc、_msize、free)、内存泄漏

目录 1 C 程序内存区域划分 1.1 代码区 (Code Section) 1.2 全局/静态区 (Global/Static Section) 1.3 栈区 (Stack Section) 1.4 堆区 (Heap Section) 1.5 动态内存分配 2 void 指针&#xff08;无类型指针&#xff09; 2.1 void 指针介绍 2.2 void 指针的作用 2.3 …

Java基本数据类型和String类型的转换

1.基本介绍 在程序开发中&#xff0c;我们经常需要将基本数据类型转换成String类型。或者将String类型转为基本数据类型。 2.基本类型转String类型 语法&#xff1a;将 基本数据类型的值 “” 即可 3.String类型转基本数据类型 语法&#xff1a;通过基本类型的包装类调用…

【DataSophon】DataSophon1.2.1 整合Zeppelin并配置Hive|Trino|Spark解释器

目录 ​一、Zeppelin简介 二、实现步骤 2.1 Zeppelin包下载 2.2 work配置文件 三、配置常用解释器 3.1配置Hive解释器 3.2 配置trino解释器 3.3 配置Spark解释器 一、Zeppelin简介 Zeppelin是Apache基金会下的一个开源框架&#xff0c;它提供了一个数据可视化的框架&am…

浏览器动态移动的小球源码分享

浏览器动态移动的小球源码分享 <script>(function(a){var width100,height100,borderRadius100,circlefunction(){};circle.prototype{color:function(){let colour "#"Math.floor(Math.random()*255).toString(16)Math.floor(Math.random()*255).toString…

爬虫案例——爬取腾讯社招

案例需求&#xff1a; 1.爬取腾讯社招的数据&#xff08;搜索 | 腾讯招聘&#xff09;包括岗位名称链接时间公司名称 2.爬取所有页&#xff08;翻页&#xff09; 3.利用jsonpath进行数据解析 4.保存数据&#xff1a;txt文本形式和excel文件两种形式 解析&#xff1a; 1.分…

hdfs伪分布式集群搭建

1 准备 vmware 虚拟三台centos系统的节点三台机器安装好jdk环境关闭防火墙&#xff08;端口太多&#xff0c;需要的自行去开关端口&#xff09;hadoop压缩包解压至三台服务器 可在一台节点上配置完成后克隆为三台节点 2 host修改 vi /etc/hosts在每个节点上添加三台机器的i…

【Linux】Shell脚本基础+条件判断与循环控制

目录 一、介绍 1. Linux提供的Shell解析器 2. bash和sh关系 3. Centos默认的Shell解析器是bash 二、定义 1. 变量名的定义规则 2. 等号周围没有空格 3. 查看变量 4. 删除变量 5. 正确地定义数组 6. 将局部环境变量提升为全局 7. 正确选择引号 8. 特殊变量名 三…

QT实现QMessageBox中文按钮

这是我记录Qt学习过程心得文章的第二篇&#xff0c;主要是为了方便QMessageBox弹出框的使用&#xff0c;通过自定义的方式&#xff0c;将其常用的功能&#xff0c;统一封装成一个函数&#xff0c;还是写在了Skysonya类里面。 实现代码&#xff1a; //中文提示对话框 bool Sky…