LOADING

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

2024途虎一面

自我介绍

算法:无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,”pwke” 是一个子序列,不是子串。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class LongestSubstring {
    public static int lengthOfLongestSubstring(String s){
        int n=s.length();
        int res=0;
        int[] index = new int[128];
        for(int j=0,i=0;j<n;j++){
            i = Math.max(index[s.charAt(j)],i);
            res = Math.max(res, j-i+1);
            index[s.charAt(j)] = j+1;
        }
        return res; 
    }   
    
    public static void main(String[] args) throws IOException {
        // 输入字符串S
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String S=reader.readLine();
        int res = lengthOfLongestSubstring(S);
        System.out.println(res);

    }
}

创建线程的方式

  1. 继承Thread类
  2. 使用Runnable接口
  3. 使用Callable接口

线程池的参数

  1. 核心线程数:线程池中的常驻核心线程数也就是最少的线程数
  2. 最大线程数:线程池中允许的最大线程数
  3. 空闲线程存活时间:空闲线程多久会被销毁
  4. 空闲线程存活时间单位:空闲线程存活时间的单位
  5. 工作队列:用于存储等待执行的任务的阻塞队列,有四种类型:直接提交队列,有界队列,无界队列,优先级队列
  6. 线程工厂:创建新线程使用的工厂
  7. 拒绝策略:当线程池中的线程数达到最大线程数时,如果还有新的任务提交,就会使用拒绝策略来处理新的任务,有四种拒绝策略:AbortPolicy:直接抛出异常,不处理新的任务;
    CallerRunsPolicy:使用调用者所在的线程来执行任务
    DiscardOldestPolicy:丢弃队列中最老的任务
    DiscardPolicy:直接丢弃新的任务

线程池运行原理

提交任务,先判断任务是否为空,如果为空,抛出异常NullPointerException,不为空后开始获取线程池状态和线程池数的组合值,如果线程池中的线程数小于核心线程数,创建新线程开始任务,如果大于核心线程数,将任务添加到阻塞队列中,如果阻塞队列已满,判断线程池中的线程数是否小于最大线程数,如果小于,创建新线程开始任务,如果大于,使用拒绝策略处理新的任务。

线程安全的集合

  1. Vector:线程安全的动态数组,底层使用数组实现,线程安全是通过synchronized关键字实现的。
  2. HashTable
  3. ConcurrentHashMap
  4. ConcurrentLinkedQueue
  5. CopyOnWriteArrayList:线程安全的动态数组,底层使用数组实现,线程安全是通过ReentrantLock实现的。

ConcurrentHashMap的实现原理

实现原理:本质上是HashMap,只是保证线程安全的hashMap,数组+链表+红黑树
1.7版本中保证线程安全的方式是分段锁,1.8版本中保证线程安全的方式是CAS+Synchronized,1.8版本中使用CAS+Synchronized保证线程安全的效率比1.7版本中使用分段锁保证线程安全的效率要高。
1.7中使用分段锁的原因是:因为1.7中使用的是数组+链表的数据结构,当多个线程同时对一个链表进行操作时,会导致链表的数据结构被破坏,从而导致数据错误。所以1.7中使用分段锁,将数组分成多个段,每个段都有一个锁,当多个线程同时对一个链表进行操作时,只需要对链表所在的段加锁,就可以保证线程安全。但是如果HashEntry中数据过多,遍历时间复杂度为O(n),效率比较低。
1.8使用CSA+Sync的原因是:因为1.8中使用的是数组+链表+红黑树的数据结构,主要还是由于在1.8中sync锁的优化,sync锁的开销大大降低,同时1.8中使用红黑树,遍历时间复杂度为O(logn),效率比较高。

实现子线程运行完,主线程再运行的几种方式

  1. 使用join方法
  2. 使用CountDownLatch
  3. 使用CyclicBarrier
  4. 使用FutureTask
  5. 使用Condition
  6. synchronized的等待/通知机制

Spring的设计模式

  1. 单例模式:保证一个类只有一个实例,并提供一个全局访问点。
  2. 工厂模式:Sprinmg中的BeanFactory就是工厂模式的体现,BeanFactory使用了工厂模式来管理Bean的创建。
  3. 代理模式:Spring中的AOP就是使用了代理模式,Spring中的AOP使用了动态代理,动态代理又分为JDK动态代理和CGLIB动态代理。
  4. 观察者模式:Spring中的事件驱动模型就是观察者模式的体现,Spring中的事件驱动模型使用了观察者模式来实现。
  5. 适配器模式:Spring框架中的MVC框架中的HandlerAdapter就是适配器模式的体现,HandlerAdapter使用了适配器模式来实现。
  6. 模板方法模式:Spring中的JdbcTemplate就是模板方法模式的体现,JdbcTemplate使用了模板方法模式来实现。
  7. 策略模式:Spring中的依赖注入机制就是基于策略模式实现的,不同的策略可以实现不同的依赖注入方式
  8. 享元模式:Spring中的Bean是单例的,这就是享元模式的体现,享元模式可以减少对象的创建,从而减少内存的使用。

Bean的生命周期

在Spring中,Bean的生命周期可以分为以下几个阶段:

  1. 实例化:Spring容器根据配置文件或注解等方式,创建Bean的实例。

  2. 属性赋值:Spring容器将Bean的属性值通过构造函数、setter方法等方式注入到Bean实例中。

  3. 初始化前回调方法:在Bean实例化和属性赋值完成之后,Spring容器会调用Bean的初始化前回调方法,例如InitializingBean接口的afterPropertiesSet()方法或者自定义的init()方法。

  4. 初始化后回调方法:在Bean的初始化前回调方法执行完之后,Spring容器会调用Bean的初始化后回调方法,例如BeanPostProcessor接口的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法。

  5. 使用:Bean实例化和初始化完成后,可以被其他Bean或者应用程序使用。

  6. 销毁前回调方法:当Bean不再被需要时,Spring容器会调用Bean的销毁前回调方法,例如DisposableBean接口的destroy()方法或者自定义的destroy()方法。

  7. 销毁:在Bean的销毁前回调方法执行完之后,Spring容器会销毁Bean实例。

需要注意的是,Bean的生命周期可以通过配置文件中的init-method和destroy-method属性来自定义初始化前和销毁前的回调方法。同时,BeanPostProcessor接口的实现类可以对Bean的属性进行修改和增强。

MySQL的索引数据结构

  1. B+树
  2. Hash索引
  3. Full-Text索引:全文索引,只能用于MyISAM引擎,用于查找文本中的关键字,而不是直接与索引中的值进行比较。
  4. Bitmap索引:位图索引,只能用于MyISAM引擎,用于查找文本中的关键字,而不是直接与索引中的值进行比较。

索引失效的场景

  1. 索引列上使用了函数,导致索引失效
  2. 对索引使用左或者左右模糊匹配
  3. 对索引列进行表达式计算
  4. 对索引隐式类别转换:如用整数查询字符串类型的字段,会导致索引失效,但是用字符串查询整数类型的字段,不会导致索引失效,因为mysql在遇到字符串和数字比较的时候,会自动将字符串转化为数字。
  5. 联合索引非最左匹配
  6. Where子句中的OR条件,如果OR条件中有一个条件列没有索引,那么即使其他条件列有索引,也不会使用索引。

redis的基本数据类型

  1. String:字符串类型,最基本的数据类型,可以是字符串、整数或者浮点数。
  2. List:列表类型,底层是链表,可以添加一个元素到列表的头部或者尾部。
  3. Set:集合类型,底层是哈希表,可以添加一个元素到集合中,添加之前会判断元素是否存在。
  4. Hash:哈希类型,底层是哈希表,可以添加一个键值对到哈希表中。
  5. Zset:有序集合类型,底层是跳跃表,可以添加一个带分数的成员到有序集合中,添加之前会判断成员是否存在。

三种特殊的数据类型:

  1. HyperLogLog:基数统计算法,用于统计一个集合中的不同元素的个数。
  2. Bitmaps:位图,可以将字符串看作是二进制位串,可以对二进制位串进行操作。
  3. Geospatial:地理位置,可以对地理位置进行操作。

user表中有很多属性,该使用什么数据结构来存储

根据场景来分:
如果是查询用户信息,那么可以使用hash类型来存储,key为用户id,value为用户信息。
如果是查询用户的好友列表,那么可以使用set类型来存储,key为用户id,value为用户的好友id。

缓存击穿、缓存穿透、缓存雪崩

  1. 缓存击穿:缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
  2. 缓存穿透:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
  3. 缓存雪崩:缓存雪崩是指在某一个时间段,缓存集中过期失效,而查询数据量巨大,引起数据库压力过大甚至宕机。

解决方法:

缓解缓存击穿的措施

  1. 设置热点数据永不过期,或者设置过期时间为一个较长的时间,这样可以避免热点数据过期失效时,大量请求直接访问数据库。
  2. 加载缓存时加锁,在加载缓存时,可以使用分布式锁等机制,避免多个请求同时访问数据库
  3. 使用互斥锁:在缓存失效时,可以使用互斥锁等机制,避免多个请求同时访问数据库。
  4. 使用布隆过滤器:在缓存失效时,可以使用布隆过滤器等机制,过滤掉无效的请求,避免大量请求直接访问数据库。
  5. 增加缓存容量:增加缓存容量,可以避免缓存击穿的发生。

缓解缓存雪崩的措施

  1. 设置不同的过期时间,将缓存的过期时间设置为不同的时间,避免同时失效导致大量请求直接访问数据库。
  2. 使用缓存预热,提前加载缓存,避免在缓存失效时,大量请求直接访问数据库。
  3. 使用多级缓存,将缓存分为多级,例如本地缓存,分布缓存等
  4. 使用限流策略:在该并发时,可以使用限流策略,例如漏桶算法,立牌算法等,控制请求的流量
  5. 数据库容灾:在缓存失效时,可以使用数据库容灾机制,例如读写分离,主从复制等,避免数据库宕机。

MQ的使用场景,架构

MQ为什么用NameServer,而不使用Zookeeper

项目

redis缓存淘汰策略

  1. volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
  4. allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。
  5. allkeys-random:从数据集中任意选择数据淘汰。
  6. no-enviction:禁止驱逐数据。当内存不足以容纳新写入数据时,新写入操作会报错。
  7. volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰。
  8. allkeys-lfu:从数据集中挑选最不经常使用的数据淘汰。

springboot和spring的区别

  1. springboot是spring的一套快速配置脚手架,可以基于springboot快速开发单个微服务,spring是一个庞大的生态圈。
  2. springboot提供了一套默认的配置,使用springboot可以不用或者少用配置文件,spring需要使用xml或者注解配置。
  3. 依赖管理,springboot提供了依赖管理功能,可以帮助开发人员管理应用程序的依赖关系,它使用了Maven和Gradle的依赖管理功能,并提供了一个中央仓库,可以快速下载和管理各种依赖库,spring需要开发人员自己管理应用程序的依赖关系。
  4. 简化部署,在SpringBoot中使用了嵌入式WEB容器,可以将应用程序打包成一个可执行的JAR文件并直接运行,避免了繁琐的部署过程,spring需要部署到外部的WEB容器中。

SpringBoot的starter原理,自己开发需要注意什么

starter是一个空jar,仅提供辅助性依赖管理,这些依赖可能是一个或者多个jar包,当我们需要使用某个功能时,只需要引入对应的starter即可,starter会自动引入需要的依赖,这样就可以简化项目的依赖管理,避免了版本冲突等问题。
Starter相当于模块,它能将模块锁需的依赖整合起来并对模块内的Bean根据环境进行自动配置,使用者只需要依赖相应功能的starter,无需做过多的配置和依赖,springboot就能自动扫描并加载相应的模块

  1. 它整合了这个模块需要的依赖库;
  2. 提供对模块的配置项给使用者、并可以对配置项提供默认值,使得使用者可以不指定配置时提供默认配置项值,也可以根据需要指定配置项值;
  3. 提供自动配置类对模块内的Bean进行自动装配

Starter的开发步骤

  1. 新建Maven项目,在项目的POM文件中定义使用的依赖;
  2. 新建配置类,写好配置项和默认的配置值,指明配置项前缀;
  3. 新建自动装配类,使用@Configuration和@Bean来进行自动装配;
  4. 新建spring.factories文件,指定Starter的自动装配类;

springboot的事务

Spring Boot提供了方便的事务管理机制,支持使用注解或编程方式来管理事务。

  1. 声明式事务管理

使用注解的方式来管理事务,可以在方法上添加@Transactional注解,表示该方法需要被事务管理器管理。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
    }
}

在上述代码中,saveUser方法上添加了@Transactional注解,表示该方法需要被事务管理器管理。当该方法被调用时,如果发生异常,事务管理器会自动回滚事务,保证数据的一致性。

  1. 编程式事务管理

使用编程方式来管理事务,可以通过TransactionTemplate类来实现。在该类中,可以通过execute方法来执行需要被事务管理器管理的方法。

@Service
public class UserService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private UserRepository userRepository;

    public void saveUser(User user) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                userRepository.save(user);
            }
        });
    }
}

在上述代码中,通过TransactionTemplate类的execute方法来执行需要被事务管理器管理的方法。在该方法中,可以通过TransactionStatus对象来控制事务的提交或回滚。当发生异常时,可以通过调用status.setRollbackOnly方法来回滚事务。
总结:
Spring Boot提供了方便的事务管理机制,支持使用注解或编程方式来管理事务。使用注解的方式比较简单,可以通过在方法上添加@Transactional注解来实现。使用编程方式需要手动控制事务的提交或回滚,可以通过TransactionTemplate类来实现。

算法题

first(ex(am)pl)e将括号里的字符串由内外翻转,并去掉括号:结果为firstlpamxee

public class Solution {
    public String reverseParentheses(String s) {
        Stack<String> stack = new Stack<>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                stack.push(sb.toString());
                sb.setLength(0);
            } else if (c == ')') {
                sb.reverse();
                sb.insert(0, stack.pop());
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}
class Solution {
public:
    string reverseParentheses(string s) {
        stack<string> stk;
        string str;
        for (auto& ch : s) {
            if (ch == '(') {
                stk.push(str);
                str = "";
            } else if (ch == ')') {
                reverse(str.begin(), str.end());
                str = stk.top() + str;
                stk.pop();
            } else {
                str.push_back(ch);
            }
        }
        return str;
    }
};

map接口的实现类

  1. HashMap:基于哈希表实现,线程不安全,允许存储null键和null值,无序。
  2. HashTable:基于哈希表实现,线程安全,不允许存储null键和null值,无序。
  3. sortedMap:基于红黑树实现,线程不安全,无序。
  4. LinkedHashMap:基于哈希表和双向链表实现,线程不安全,允许存储null键和null值,有序。
  5. TreeMap:基于红黑树实现,线程不安全,不允许存储null键和null值,有序。

spring里面有个@configuration注解,它的作用是什么

通常用于标记一个类作为配置类,配置类通常用于定义Spring Bean配置,声明依赖关系以及进行一些初始化操作。使用@configuration注解的类会被Spring容器扫描并用于构建应用程序的应用上下文。
作用:

  1. 定义配置类
  2. 定义Spring Bean:在配置类中,使用@Bean注解来定义Spring Bean,这些Bean会被Spring容器管理,你可以在应用程序的其他地方使用它们
  3. 声明依赖关系:在配置类中,使用@Autowired注解来声明依赖关系,Spring容器会自动装配依赖关系
  4. 组织复杂的配置:在大型应用程序中,可能需要对多个 Bean 进行配置,定义多个依赖关系。使用 @Configuration 注解可以帮助你组织和管理这些复杂的配置。
  5. 条件化配置:@Configuration 类可以与其他注解(如 @ConditionalOnProperty)结合使用,从而实现条件化配置。这意味着在满足特定条件时才会创建配置类中定义的 Bean。

@service和@repository的区别

@Service注解用于标记一个类作为服务类,通常用于定义业务逻辑层,它是一个典型的业务层Bean,使用@Service注解的类会被Spring容器扫描并用于构建应用程序的应用上下文。

@repository注解用于标记一个类作为数据访问层Bean,通常用于定义数据访问层,它是一个典型的数据访问层Bean,使用@Repository注解的类会被Spring容器扫描并用于构建应用程序的应用上下文。

MySQL中乐观锁和悲观锁的应用

乐观锁:乐观锁是一种乐观思想,认为数据一般不会发生冲突,所以在读取数据时不会加锁,只有在更新数据时才会加锁,当发生冲突时,会回滚事务,重新读取数据,再次尝试更新数据。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
悲观锁:悲观锁是一种悲观思想,认为数据一般会发生冲突,所以在读取数据时会加锁,当发生冲突时,会回滚事务,重新读取数据,再次尝试更新数据。共享锁,排它锁,意向共享锁,意向排它锁,行锁,表锁,读锁,写锁都是悲观锁。

一个用户表 如何建立索引 查询号码为139开头,性别为男的用户数量?那假如查询尾号为0000的呢?

  1. 建立联合索引:在号码和性别上建立联合索引,这样可以提高查询效率。
# 创建一个含有用户名字,号码,性别的表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `phone` varchar(255) DEFAULT NULL,
  `sex` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
# 在号码和性别上建立联合索引
CREATE INDEX idx_phone_sex ON user (phone, sex);
# 查询号码为139开头,性别为男的用户数量
select count(*) FROM user where phone like '139' and sex = '男';
# 查询尾号为0000的用户数量
select count(*) FROM user where phone like '%0000';

Redis里面的过期时间key是如何判断失效的

key的过期时间是根据unix时间戳的方式进行存储的,这就意味着无论redis是否使用,key的过期时间都会被准确的计算,不会因为redis的重启而导致key的过期时间丢失。
两种过期策略:

  1. 定时删除:在设置key的过期时间时,同时创建一个定时器,让定时器在过期时间来临时,立即执行对key的删除操作。
  2. 使用惰性删除:在获取key时,先判断key是否过期,如果过期,就删除key,如果没有过期,就返回key。

算法:一个数组中找存不存在相同的两个数,如果存在,返回这两个数的下标

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin>>n;
    vector<int> nums(n);
    for(int i=0;i<n;i++){
        cin>>nums[i];
    }
    unordered_map<int,int> mp;
    for(int i=0;i<n;i++){
        if(mp.count(nums[i])){
            cout<<mp[nums[i]]<<" "<<i<<endl;
            return 0;
        }
        mp[nums[i]]=i;
    }
    cout<<"-1 -1"<<endl;
    return 0;
}

RabbitMQ的如何保证不重复消费消息,如何保证消息不丢失

不重复消费:等幂性

  1. 通过消息全局ID或唯一标识:每次消费消息之前根据消息id判断该消息是否已被消费过,如果已经消费了,则不处理,如果没有消费,则处理消息。
  2. 利用Redis的setnx命令,给消息分配一个全局ID,消费该消息时,先去Redis中查询有没有消费记录,没有就以键值对形式写入Redis,有则不处理。

消息不丢失:

  1. 消息持久化:将消息持久化到磁盘中,当RabbitMQ重启时,会从磁盘中读取消息。消息持久化需要设置两个参数,一个是交换机持久化,一个是队列持久化。
  2. confirm机制:生产者发送消息后,会等待RabbitMQ的确认,如果RabbitMQ收到消息,就会给生产者发送一个确认消息,如果RabbitMQ没有收到消息,就会给生产者发送一个未确认消息,生产者可以根据这个未确认消息进行重发。
  3. 事务机制ACK:消费者从队列中获取消息后,会直接给RabbitMQ发送一个ACK,表示已经收到消息,如果消费者没有发送ACK,RabbitMQ会认为该消息没有被消费,会将该消息重新发送给其他消费者。

Redis持久化机制

  1. RDB:快照形式,将内存中的数据以快照的形式写入到磁盘中,当Redis重启时,会从磁盘中读取数据。缺点:快照频率不好把握,频率太高会影响性能,频率太低会导致数据丢失。
  2. AOF:日志形式,将内存中的数据以日志的形式写入到磁盘中,当Redis重启时,会从日志中读取数据。缺点:数据恢复速度慢,占用磁盘空间大。当AOF文件过大,会触发重写机制,

RDB底层机制

Redis客户端执行bgsave命令 或 自动触发bgsave命令;
父进程先判断:当前是否在执行save,或bgsave/bgrewriteaof(aof文件重写命令)的子进程
如果存在:则父进程直接返回。 如果不存在:父进程执行fork操作创建一个子进程,fork过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
fork完毕后返回 “ Background saving started” 信息并不再阻塞父进程,并可响应其他命令
子进程创建临时RDB文件,待数据写入完毕后,对原有文件进行原子替换;
同时子进程退出,并发送信号给父进程表示完成rdb持久化,父进程更新统计信息

RDB中的核心思路是Copy-on-Write

在正常的快照操作中,Redis主进程会fork一个子进程来处理rdb文件的写入,此时Redis服务还会接受其他客户端包括写请求在内的任何命令。

在子进程执行备份阶段,和redis主进程接受其他客户端写请求阶段,这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。

spring实现事务的方法

  1. 编程式事务管理:使用编程方式来管理事务,可以通过TransactionTemplate类来实现。在该类中,可以通过TransactionStatus对象来控制事务的提交或回滚。当发生异常时,可以通过调用status.setRollbackOnly方法来回滚事务。
  2. 声明式事务管理:使用注解的方式来管理事务,可以在方法上添加@Transactional注解,表示该方法需要被事务管理器管理。当该方法被调用时,如果发生异常,事务管理器会自动回滚事务,保证数据的一致性。
  3. 代理模式:Spring中的AOP就是使用了代理模式,Spring中的AOP使用了动态代理,动态代理又分为JDK动态代理和CGLIB动态代理。Spring中的事务管理就是使用了代理模式,Spring中的事务管理使用了动态代理,动态代理又分为JDK动态代理和CGLIB动态代理。
  4. 基于TransactionAwareDataSourceProxy的事务管理:在Spring中,可以通过TransactionAwareDataSourceProxy类来实现事务管理,该类可以将数据源包装成事务感知的数据源,从而实现事务管理。

Mysql的ACID,分别是什么,隔离级别,造成的问题

  1. 原子性:事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全部不执行。
  2. 一致性:事务执行前后,数据保持一致。
  3. 隔离性:在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
  4. 持久性:事务完成之后,事务对数据的修改就是永久的,即使系统故障也不会丢失。

隔离级别:

  1. 读未提交:事务中的修改,即使没有提交,对其他事务也是可见的,会导致脏读、不可重复读、幻读。
  2. 读已提交:事务中的修改,只有提交之后,其他事务才能看到,会导致不可重复读、幻读。
  3. 可重复读:事务中的修改,只有提交之后,其他事务才能看到,会导致幻读。
  4. Serializable:事务中的修改,只有提交之后,其他事务才能看到,不会导致脏读、不可重复读、幻读。

了解readview吗,与隔离级别的关系

readview是一个事务内部的数据结构,用于存储事务开始时,当前系统中已经提交的事务ID列表,事务开始时,会将当前系统中已经提交的事务ID列表存储到readview中,事务执行过程中,会根据readview中的事务ID列表来判断当前事务是否可以看到其他事务的修改。

对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同:

「读提交」隔离级别是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
「可重复读」隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。
这两个隔离级别实现是通过「事务的 Read View 里的字段」和「记录中的两个隐藏列」的比对,来控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。

慢查询优化,什么字段适合建立索引

慢查询优化:

  1. 使用索引:可以通过explain命令来查看sql语句的执行计划,如果发现有全表扫描,可以通过在查询条件上建立索引来优化。
  2. 如果是索引没起作用,优化SQL语句
  3. 数据量大的表,可以考虑分库分表

适合建立索引的字段

  1. 频繁查询的字段
  2. 在where和on子句中经常使用的字段
  3. 区分度高的字段
  4. 有序的字段

Mysql的索引结构

  1. B+树
  2. Hash索引
  3. Full-text全文索引

Mysql持久化原理,redo log和binlog的写入机制是什么

通过日志的方式来实现持久化,日志分为redo log和binlog,redo log是InnoDB引擎特有的日志,用于保证事务的持久性,binlog是MySQL的归档日志,用于实现主从复制。
redo log重做日志:InnoDB引擎特有的日志,用于保证事务的持久性,当事务提交时,会将事务的修改操作记录到redo log中,当数据库发生异常重启时,可以通过redo log来恢复数据。
binlog归档日志:MySQL的归档日志,用于实现主从复制,当主库发生修改时,会将修改操作记录到binlog中,从库会从主库中读取binlog,然后执行相同的修改操作。

接口很慢,如何排查,优化策略

  1. 优化索引,通过查询线上日志或者监控报告,找出慢查询的sql语句,然后通过explain命令来查看sql语句的执行计划,如果发现有全表扫描,可以通过在查询条件上建立索引来优化。
  2. 优化SQL:比如避免使用select *,分库分表,读写分离,缓存,尽量不用同时join过多表
  3. 如果是远程调用,可以考虑使用并行代用,或者修改存储的数据内容结构,

项目哪里用到了并发,如何保证并发安全

为了保证并发安全,可以采取以下几种方法:

互斥锁:使用锁机制来保证同一时间只有一个线程或进程可以访问共享资源,其他线程或进程需要等待锁的释放才能访问。常见的锁包括互斥锁、读写锁、自旋锁等。

原子操作:使用原子操作来保证对共享资源的操作是不可分割的,即要么全部执行成功,要么全部不执行。原子操作可以通过硬件支持或者使用原子操作库来实现。

信号量:使用信号量来控制并发访问的数量,通过设置信号量的初始值和对信号量的P操作和V操作来实现对共享资源的并发访问控制。

读写锁:对于读多写少的场景,可以使用读写锁来提高并发性能。读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。

数据副本:对于一些不需要实时一致性的共享资源,可以将其复制多份,每个线程或进程操作自己的副本,避免并发冲突。

串行化:对于一些关键的操作或共享资源,可以通过串行化执行来保证并发安全。即将并发操作转化为顺序执行,避免并发冲突。

事务处理:对于数据库等支持事务的系统,可以使用事务来保证并发安全。事务可以通过锁机制、MVCC(多版本并发控制)等方式来实现。

synchronized特性

  1. 可重入性:同一个线程可以拥有同一把锁多次
  2. 独享锁:每个对象只有一个锁,当一个线程获取锁后,其他线程只能等待。
  3. 悲观锁:当一个线程获取锁后,其他线程只能等待,不能中断。
  4. 原子性:同步代码块中的代码是原子操作,要么全部执行成功,要么全部不执行。
  5. 可见性:当一个线程获取锁后,会清空工作内存中的共享变量,从而使用共享变量时需要从主内存中重新获取最新的值,当释放锁后,会将工作内存中的共享变量刷新到主内存中。

什么时候会出现死锁,如何避免死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。死锁产生的原因是:两个或两个以上的进程在执行过程中,因竞争资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
避免死锁的方式:破坏死锁产生的四个条件之一即可。

算法

#include<bits/stdc++.h>
using namespace std;
// 开辟一个数组将小于等于n的斐波那契数列存储起来
vector<long long> fib;
int main(){
    // 判断当前的数是否可以由斐波那契数列中的两个数相乘得到
    long long n;
    cin>>n;
    fib.push_back(1);
    fib.push_back(2);
    int i = 2;
    while(fib[i-1] <= n){
        fib.push_back(fib[i-1] + fib[i-2]);
        i++;
    }
    // 遍历斐波那契数列,判断当前的数是否可以由斐波那契数列中的两个数相乘得到,并存储当前与斐波那契数列中的数的差值的最小值
    long long res = n;
    int a = 0, b = 0;
    for(int i = 0; i < fib.size(); i++){
        for(int j = i; j < fib.size(); j++){
            if(fib[i] * fib[j] == n){
                res = 0;
                 // 输出当前的fib[i]和fib[j]
                cout << fib[i] << "," << fib[j] <<","<< 1 << endl;
                return 0;
            }
            res = min(res, abs(n - fib[i] * fib[j]));
        }
    }
    if(res!=0)
        cout << fib[fib.size()-2] << " " << fib[fib.size()-3] << " " << 0 << endl;
    return 0;
}

redis的优点

  1. 性能高:Redis是基于内存的,内存的读写速度非常快,可以达到10w+的QPS,是目前主流的NoSQL数据库中性能最快的。
  2. 丰富的数据结构:Redis支持丰富的数据结构,包括字符串、列表、哈希、集合、有序集合等,可以满足不同场景下的需求。
  3. 丰富的特性:Redis支持事务、持久化、发布订阅、Lua脚本、LRU驱动事件、自动过期等特性,可以满足不同场景下的需求。
  4. 丰富的客户端支持:Redis支持Java、Python、C/C++、PHP、Go等多种语言,可以满足不同语言的开发需求。

缓存穿透的解决方案

  1. 布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
  2. 缓存无效值:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

布隆过滤器的原理

布隆过滤器的主要原理是使用一组哈希函数,将元素映射成一组位数组中的索引位置。当要检查一个元素是否在集合中时,将该元素进行哈希处理,然后查看哈希值对应的位数组的值是否为1。如果哈希值对应的位数组的值都为1,那么这个元素可能在集合中,否则这个元素肯定不在集合中

B树和B+树的区别

  1. B+树内节点不存储数据,只存储索引,叶子节点存储数据,B树内节点和叶子节点都存储数据。
  2. B+树的叶子节点之间是通过链表来连接的,B树的叶子节点之间没有连接。

索引失效的情况

  1. 索引列上使用了函数,导致索引失效
  2. 对索引使用左或者左右模糊匹配
  3. 对索引列进行表达式计算
  4. 对索引隐式类别转换:如用整数查询字符串类型的字段,会导致索引失效,但是用字符串查询整数类型的字段,不会导致索引失效,因为mysql在遇到字符串和数字比较的时候,会自动将字符串转化为数字。
  5. 联合索引非最左匹配
  6. Where子句中的OR条件,如果OR条件中有一个条件列没有索引,那么即使其他条件列有索引,也不会使用索引。

聚簇索引和非聚簇索引

聚簇索引:索引结构和数据一起存放的索引,叶子节点存储的是数据,非叶子节点存储的是索引,InnoDB的主键索引就是聚簇索引。
非聚簇索引:索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据

一条SQL发送到MySQL服务器后,MySQL服务器的执行流程

  1. 连接器:负责跟客户端建立连接、获取权限、维持和管理连接。
  2. 查询缓存:MySQL会先查询缓存,如果命中缓存,则直接返回结果,不需要执行后面的步骤。(8.0之后的版本不在有这个步骤了)
  3. 解析器:对SQL语句进行解析,识别SQL语句的语法是否正确,如果不正确,则直接返回错误信息,不需要执行后面的步骤。
  4. 执行SQL:如果SQL语句通过解析,则会进入优化器,优化器会根据表的索引情况、表的大小、SQL语句的复杂度等情况,生成多个执行计划,然后通过成本估算器,选择成本最低的执行计划,生成执行计划树,然后通过执行器执行SQL语句,返回结果。

数据的隔离级别和会出现的问题

  1. 读未提交:事务中的修改,即使没有提交,对其他事务也是可见的,会导致脏读、不可重复读、幻读。
  2. 读已提交:事务中的修改,只有提交之后,其他事务才能看到,会导致不可重复读、幻读。
  3. 可重复读:事务中的修改,只有提交之后,其他事务才能看到,会导致幻读。
  4. Serializable:事务中的修改,只有提交之后,其他事务才能看到,不会导致脏读、不可重复读、幻读。

MySQL间隙锁,如何加锁

间隙锁:当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫做间隙(GAP),InnoDB也会对这个间隙加锁,这种锁机制就是间隙锁。
间隙锁的作用是防止其他事务在当前事务读取或插入数据时,插入新的数据行或修改已有的数据行,从而确保当前事务读取的数据是一致的。
只在可重复读隔离级别可以使用间隙锁
加锁过程:

  1. 在事务中执行查询语句,并使用FOR UPDATE子句来锁定查询结果的行。
  2. 如果需要锁定索引范围内的间隙,可以使用SELECT … FOR UPDATE语句,并在查询条件中使用BETWEEN和AND来指定索引范围

bin log日志, red log日志,undo log日志

binlog:MySQL的归档日志,用于实现主从复制,当主库发生修改时,会将修改操作记录到binlog中,从库会从主库中读取binlog,然后执行相同的修改操作。
redolog:InnoDB引擎特有的日志,用于保证事务的持久性,当事务提交时,会将事务的修改操作记录到redo log中,当数据库发生异常重启时,可以通过redo log来恢复数据。
undolog:用于实现事务的原子性,当事务回滚时,会根据undolog来恢复数据。

MVCC是什么,实现原理是什么

MVCC是一种并发控制的方法,用来解决读-写冲突。在MVCC协议下,每个读操作会看到一个一致性的snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时间点,不同的事务看到的数据是不同的。在读取数据的时候,不仅会读到最新的数据,还会读到所有已经提交的数据版本,然后使用undo log来构造一个一致性的snapshot。在可重复读隔离级别下,MVCC只会查找已经提交的数据版本,因此可以避免幻读的问题。

实现原理:通过undo_log多版本链条,加上开启事务时产生的readView(不同隔离级别有不同产生策略),然后再有一个查询的时候,根据readView进行判断的机制,来决定读取哪个版本的数据。实现了多事务并发执行,保证只能读开启事务前提交的数据和当前事务修改的数据,其他情况都不会读到。

如何排查慢查询,如何分析慢SQL

排查慢查询和分析慢SQL可以通过以下步骤进行:

  1. 开启慢查询日志:在数据库配置文件中开启慢查询日志功能,设置慢查询阈值,一般以执行时间为依据,可以根据具体需求设置。

  2. 查看慢查询日志:查看数据库慢查询日志文件,一般默认位置为数据库安装目录下的日志文件夹。可以使用命令行工具或日志查看工具打开日志文件。

  3. 找出慢查询SQL:在慢查询日志中找出执行时间较长的SQL语句,可以根据执行时间排序或设定的阈值进行筛选。

  4. 分析慢查询SQL:对于慢查询SQL,可以通过以下方法进行分析:

    a. Explain分析:使用数据库的Explain命令来分析SQL语句的执行计划,查看是否存在索引使用不当、全表扫描等问题。

    b. 优化SQL语句:根据Explain分析结果,对慢查询SQL进行优化,可以考虑添加合适的索引、优化查询条件、调整SQL语句结构等。

    c. 数据库性能优化:除了优化SQL语句,还可以考虑对数据库进行性能优化,如调整数据库参数、增加硬件资源、分表分库等。

    d. 使用慢查询日志分析工具:有些数据库管理工具或第三方工具提供了慢查询日志分析功能,可以帮助更直观地分析慢查询SQL,找出问题所在。

  5. 进行性能测试:对优化后的SQL语句进行性能测试,验证优化效果。

redis的常用数据类型,如果是一个实体类,用什么数据结构才存储合适

  1. 字符串:字符串是Redis最基本的数据类型,Redis的字符串是动态字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。字符串最大长度为512M。
  2. 哈希:Redis的哈希是一个string类型的field和value的映射表,适合用于存储对象。
  3. 列表:Redis列表是简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边),也可以根据下标获取列表中的元素。
  4. 集合:Redis的集合是string类型的无序集合,集合成员是唯一的,集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是O(1)。
  5. 有序集合:Redis有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数,Redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数可以重复。
  6. bitmap:Redis的bitmap是一种特殊的字符串类型,它的内部实现是一个由二进制位组成的数组,数组的每个元素只能是0或1,字符串最大长度是512M。
  7. GEO:Redis的GEO是一种用于存储地理位置信息的数据结构,它是一种特殊的字符串类型,它的内部实现是一个由经度和纬度组成的二维空间索引,字符串最大长度是512M。

如果要存储一个实体类,可以考虑使用哈希(Hash)数据类型。哈希可以将一个实体类的各个属性存储为键值对,其中键是属性名,值是属性值。这样可以方便地对实体类进行读取和更新操作。

例如,假设有一个User实体类,包含属性id、name和age,可以使用哈希数据结构存储如下:

HSET user:1 id 1
HSET user:1 name "John"
HSET user:1 age 25

这样就可以通过命令HGETALL user:1来获取该实体类的所有属性值。

线程安全的集合类

  1. ConcurrentHashMap:ConcurrentHashMap是线程安全的HashMap,它的实现原理是将数据分成多个Segment,每个Segment都是一个类似于HashMap的结构,每个Segment都有自己的锁,当一个线程占用了Segment的锁,其他线程可以访问其他Segment。
  2. Vector:Vector是线程安全的List,它的实现原理是在方法上加锁,保证同一时间只有一个线程可以访问Vector。
  3. HashTable:HashTable是线程安全的HashMap,它的实现原理是在方法上加锁,保证同一时间只有一个线程可以访问HashTable。

spring中常见的设计模式

  1. 工厂模式:Spring中的BeanFactory就是工厂模式的一种实现,它可以根据配置文件或注解来创建Bean对象。
  2. 单例模式:Spring中的Bean默认是单例的,可以通过配置文件或注解来设置Bean的作用域。
  3. 代理模式:AOP就是使用了代理模式,Spring中的AOP使用了动态代理,动态代理又分为JDK动态代理和CGLIB动态代理。
  4. 原型模式:用于创建重复的对象 spring中使用scope=”prototype”来实现原型模式
  5. 适配器模式:Spring中的适配器模式包括BeanNameAutoProxyCreator和HandlerAdapter,BeanNameAutoProxyCreator是一个Bean后置处理器,用于根据Bean的名称自动创建代理对象,HandlerAdapter用于根据处理器的类型自动创建适配器。
  6. 观察者模式:Spring中的事件驱动模型就是观察者模式的一种实现,它包含事件、事件监听器、事件发布者和事件管理器。
  7. 模板模式:Spring中的JdbcTemplate就是模板模式的一种实现,它封装了JDBC操作的公共代码,将JDBC操作的细节交给了回调函数。
  8. 责任链模式:Spring中的Filter就是责任链模式的一种实现,它将请求的处理分成多个Filter,每个Filter只处理自己关心的请求,然后将请求传递给下一个Filter,直到所有的Filter都处理完请求。

关系型数据库有哪些

  1. MySQL
  2. SQL Server
  3. Oracle

数据库索引底层数据结构,b+树有什么有点,索引失效的场景

微服务的组件有哪些

微服务的组件通常包括以下几个部分:

  1. 服务注册与发现:用于注册和发现微服务的组件,常用的有Consul、Eureka等。
  2. 服务网关:用于统一管理和路由微服务的组件,常用的有Zuul、Spring Cloud Gateway等。
  3. 负载均衡:用于将请求分发到不同的微服务实例上,常用的有Ribbon、Nginx等。
  4. 服务间通信:用于微服务之间的通信,常用的有RESTful API、消息队列等。
  5. 服务容错与熔断:用于处理微服务之间的故障和异常,常用的有Hystrix、Resilience4j等。
  6. 配置中心:用于集中管理微服务的配置信息,常用的有Spring Cloud Config、Apollo等。
  7. 日志与监控:用于收集和监控微服务的日志和指标,常用的有ELK、Prometheus等。
    服务之间的通信通常有以下几种方式:
  8. 同步调用:微服务直接通过HTTP或RPC等协议进行同步调用。
  9. 异步消息:微服务通过消息队列进行异步通信,发送消息和接收消息是独立的。
  10. 事件驱动:微服务通过事件总线发布和订阅事件,实现解耦和异步通信。
  11. 数据库共享:微服务通过共享数据库进行数据交互,但这种方式可能会引入数据一致性的问题。
    具体选择哪种通信方式,需要根据业务需求和场景来决定。

线程池有哪些,线程池的核心参数

线程池有以下几种:

  1. 固定大小线程池:线程池中的线程数量是固定的,当一个线程完成任务后,它会立即开始执行下一个任务。
  2. 缓存线程池:线程池中的线程数量是不固定的,当一个线程完成任务后,它会被销毁,如果有新的任务到来,线程池会创建新的线程来处理任务。
  3. 单线程线程池:线程池中只有一个线程,它会依次处理任务,确保任务的执行顺序。
    线程池的核心参数包括:
  4. corePoolSize:线程池中的核心线程数,即线程池中始终存在的线程数量。
  5. maximumPoolSize:线程池中允许的最大线程数,当任务数量超过核心线程数时,线程池会创建新的线程来处理任务,直到达到最大线程数。
  6. keepAliveTime:当线程池中的线程数量超过核心线程数时,多余的线程会在空闲一定时间后被销毁。
  7. workQueue:用于存放等待执行的任务的队列,当线程池中的线程数量达到核心线程数时,新的任务会被放入队列中等待执行。
  8. threadFactory:用于创建新线程的工厂类。
  9. rejectedExecutionHandler:当线程池中的线程数量达到最大线程数且队列已满时,新的任务会被拒绝执行,拒绝执行处理器用于处理这种情况。
本文作者:GWB
当前时间:2023-11-09 11:11:07
版权声明:本文由gwb原创,本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议。
转载请注明出处!