社招 牛客 2022 秋

原面经链接:
https://www.nowcoder.com/discuss/508672172324339712?sourceSSR=search

mysql索引结构

  1. B+Tree索引 最常见的索引类型,大部分引擎都支持 B+ 树索引
  2. Hash索引 底层数据结构是用哈希表实现的, 只有精确匹配索引列的查询才有效, 不支持范围查询
  3. R-tree(空间索引)空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
  4. Full-text(全文索引)是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES

B+树,B树的区别

  1. 所有的数据都会出现在叶子节点。
  2. 叶子节点形成一个单向链表。
  3. 非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。

秋招 牛客 2022 电话面

原面经链接:
https://www.nowcoder.com/discuss/404011530516283392?sourceSSR=search

rpc的执行流程

RPC(远程过程调用)是一种用于实现分布式系统中不同进程或计算机之间通信的技术。下面是RPC的执行流程:

  1. 客户端发起调用:客户端应用程序向远程服务发送请求调用,通常通过调用本地的代理对象来实现。代理对象封装了与远程服务的通信细节,使客户端调用看起来像是本地调用。
  2. 传输请求数据:客户端的代理对象将请求数据进行序列化,并通过网络传输到远程服务。
  3. 服务端接收请求:远程服务接收到请求数据,并将其进行反序列化,还原为可执行的服务方法调用。
  4. 执行服务方法:远程服务执行相应的服务方法,并返回执行结果。
  5. 传输响应数据:服务端将执行结果进行序列化,并通过网络传输回客户端。
  6. 客户端接收响应:客户端代理对象接收到响应数据,并将其进行反序列化,得到最终的执行结果。
  7. 返回调用结果:客户端代理对象将执行结果返回给客户端应用程序,完成远程调用。
    总结来说,RPC的执行流程包括客户端发起调用、传输请求数据、服务端接收请求、执行服务方法、传输响应数据、客户端接收响应以及返回调用结果。通过这种方式,可以在分布式系统中实现不同进程或计算机之间的远程通信。

rpc调用时怎么知道该对方地址的?

在RPC调用中,客户端需要知道远程服务的地址才能进行调用。以下是一些常见的方式来确定远程服务的地址:
配置文件:客户端可以通过配置文件来指定远程服务的地址。在配置文件中,可以设置远程服务的IP地址和端口号等信息。客户端在启动时读取配置文件,并使用其中指定的地址进行RPC调用。
服务注册与发现:客户端可以通过服务注册与发现的机制来获取远程服务的地址。在这种机制中,服务提供者将自己的地址注册到注册中心,而客户端从注册中心查询可用的服务列表,并选择合适的地址进行调用。常见的服务注册与发现工具有ZooKeeper、Consul、Etcd等。
DNS解析:客户端可以使用域名解析来获取远程服务的地址。在这种方式中,服务提供者将自己的地址绑定到一个域名上,而客户端通过域名解析获取对应的IP地址。客户端可以使用解析后的IP地址进行RPC调用。
服务代理:有时候,客户端可能无法直接获得远程服务的地址,而是通过一个代理服务器进行调用。客户端将RPC请求发送给代理服务器,代理服务器负责将请求转发给实际的远程服务。在这种情况下,客户端只需要知道代理服务器的地址,而无需了解实际远程服务的地址。

rpc和http的区别

RPC(远程过程调用)和HTTP(超文本传输协议)是两种不同的通信协议,它们有以下几个主要区别:

  1. 概念和用途:
    RPC是一种用于实现分布式系统中不同进程或计算机之间通信的技术,它的目的是让远程调用看起来像是本地调用,提供了直接调用远程服务的能力。
    HTTP(应用层协议)是一种面向传输层的协议(面向传输层的协议是指在应用层中使用的协议,依赖于传输层协议来提供数据传输服务),用于在客户端和服务器之间传输超文本数据。它主要用于Web应用程序*的通信,例如浏览器和服务器之间的请求和响应。
  2. 通信方式:
    RPC通常使用底层的二进制协议进行通信,如Protocol Buffers、Thrift等,以提高性能和效率。
    HTTP使用文本协议进行通信,数据以文本形式传输,通常使用JSON或XML格式进行数据交换。
  3. 传输协议:
    RPC可以在不同的传输协议上运行,如TCP、UDP等。它可以根据具体需求选择适合的传输协议。
    HTTP使用TCP作为传输协议,并使用标准的HTTP请求和响应格式。
    4, 可扩展性:
    RPC通常提供更高级别的抽象和功能,例如服务注册与发现、负载均衡、容错机制等,以支持复杂的分布式系统。
    HTTP相对简单,通常需要使用其他技术和工具来实现复杂的系统功能,例如使用RESTful API来构建可扩展的Web服务。
  4. 应用场景:
    RPC通常用于构建高性能和实时性要求较高的分布式系统,例如微服务架构、远程调用等。
    HTTP广泛应用于Web开发,用于实现客户端和服务器之间的通信,例如网页浏览、API调用等。

什么时候用rpc,什么时候用http

当选择使用RPC或HTTP时,主要考虑以下几个因素:

  1. 性能要求:如果您的应用程序对于性能和效率有较高的要求,特别是在分布式系统中需要频繁进行远程调用或数据传输,那么RPC可能是更好的选择。由于RPC使用二进制协议和较低的开销,可以提供更高的性能和更低的延迟。
  2. 功能需求:如果您的应用程序需要更复杂的功能,例如服务注册与发现、负载均衡、容错机制等,那么RPC可能是更适合的选择。RPC框架通常提供这些功能的支持,可以更好地满足分布式系统的需求。
  3. 简单性和易用性:如果您的应用程序只需要进行简单的请求和响应,而不需要复杂的功能和高性能,那么HTTP可能是更简单和易用的选择。HTTP使用文本协议和广泛支持的标准,可以更容易地与其他系统进行集成和交互。
  4. 开发生态系统:考虑到可用的工具、库和支持,以及开发社区的活跃度,也是选择RPC或HTTP的因素之一。根据您的编程语言和技术栈,可能会有更成熟和丰富的RPC框架或HTTP库可供选择。

单例模式的double check写法

当使用双重检查锁(Double-Check Locking)来实现单例模式时,需要注意线程安全性和可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
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;
}
}

在上述代码中,关键的地方是将instance声明为volatile,这可以确保在多线程环境下,对instance的读写操作保持可见性。另外,使用双重检查锁的目的是在并发情况下减少同步开销,只在实例为null时才进行同步创建实例。
将instance声明为volatile的原因是为了避免指令重排序。

秋招 牛客 2022 面经

原面经地址
https://www.nowcoder.com/feed/main/detail/324fac3d213f4254ab1766965a678bd9?sourceSSR=search

redis 的数据结构有哪些

Redis是一种高性能的键值存储系统,支持多种数据结构。以下是Redis中常用的数据结构:
字符串(String):存储一个字符串值。
列表(List):有序的字符串列表,可以在头部或尾部插入元素。
集合(Set):无序的唯一元素集合,支持集合的交、并、差等操作。
有序集合(Sorted Set):有序的唯一元素集合,每个元素都关联一个分数,可以按照分数排序。
哈希(Hash):键值对的无序散列集合,适用于存储对象。
位图(Bitmap):由二进制位组成的数据结构,支持对位的操作。
HyperLogLog:用于基数计数的数据结构,可以估计一个集合中的不重复元素数量。

除了上述常用的数据结构,Redis还提供了一些特殊的数据结构和功能,如地理位置(Geospatial)支持、发布/订阅(Pub/Sub)消息传递、Lua脚本执行等。

一些使用场景示例

  1. 字符串(String):
    缓存存储:将经常使用的数据存储在Redis的字符串中,以加快读取速度。
    计数器:将字符串作为计数器,例如网站的页面访问量、用户签到次数等。
  2. 列表(List):
    消息队列:将消息作为列表的元素,实现简单的消息队列,支持先进先出(FIFO)的处理方式。
    最新动态:将最新的动态消息按照时间顺序存储在列表中,可以轻松获取最新的N条动态。
  3. 集合(Set):
    标签系统:将每个对象的标签存储为集合,可以进行标签的交集、并集等操作,方便进行对象的分类和检索。
    好友关系:将用户的好友关系存储为集合,可以进行共同好友、推荐好友等操作。
  4. 有序集合(Sorted Set):
    排行榜:将用户的得分和用户名存储在有序集合中,可以按照得分排序,实现游戏排行榜、文章热度榜等功能。
    时间轴:将帖子或事件的发布时间和内容存储在有序集合中,可以按照时间范围获取最新的帖子或事件。
  5. 哈希(Hash):
    用户信息存储:将用户的信息(如用户名、年龄、性别等)存储在哈希中,可以方便地获取和更新用户信息。
    商品信息存储:将商品的名称、价格、库存等信息存储在哈希中,方便进行商品的查询和更新。

redis的过期键删除策略

Redis采用了一种惰性删除(Lazy deletion)的策略来处理过期键的删除。
具体来说,Redis
并不会主动监视和删除过期键
,而是在客户端请求访问某个键时,才会检查该键是否过期,并在必要时进行删除。
过期键删除策略的主要原因是为了提高Redis的性能和响应速度。如果Redis每次都主动监视和删除过期键,会增加系统的负担和延迟。因此,Redis将主动删除过期键的操作延迟到了需要访问该键时进行,避免了频繁的过期键删除操作。

具体的过期键删除策略如下:

客户端请求访问某个键时,Redis会检查该键是否过期。
如果键已过期,则Redis会立即删除该键。
如果键未过期,则继续执行客户端请求的操作。
需要注意的是,过期键删除策略虽然能够提高Redis的性能,但也存在一定的副作用。当过期键的数量较多时,可能会导致Redis在处理客户端请求时,需要额外的时间进行过期键的删除操作,从而影响系统的响应速度。

为了解决这个问题,Redis引入了定期删除(Eviction)和惰性删除相结合的策略。定期删除会定期检查一定数量的过期键并删除它们,以防止过期键堆积过多。而惰性删除则是在客户端请求时进行过期键的删除,以确保过期键及时被删除。

AOP在工作中用过吗?哪些场景?

AOP是一种编程范式,通过将横切关注点(如日志记录、性能监控、事务管理等)从主业务逻辑中分离出来,提供了一种更加模块化和可复用的方式来处理横切关注点。
以下是一些我在工作中使用AOP的场景:
日志记录:通过AOP可以在方法执行前后记录方法的入参、返回值、执行时间等信息,方便后续的调试和排查问题。
性能监控:使用AOP可以在方法执行前后计算方法的执行时间,并记录下来,用于性能优化和监控。
事务管理:AOP可以在方法执行前后开启和提交事务,保证方法的原子性和一致性。
安全控制:通过AOP可以在方法执行前进行权限验证,确保只有具备相应权限的用户才能访问敏感的方法。
缓存管理:使用AOP可以在方法执行前检查缓存中是否存在所需数据,如果存在则直接返回缓存数据,减少数据库访问的次数。
异常处理:AOP可以捕获方法抛出的异常,并进行统一的处理和日志记录,避免代码中重复的异常处理逻辑。

数据库的锁你知道哪些?

在关系型数据库中,常见的几种数据库锁包括:
共享锁(Shared Lock):也称为读锁,多个事务可以同时获得共享锁,用于读取数据,不阻塞其他事务的共享锁。
排他锁(Exclusive Lock):也称为写锁,只有一个事务可以获得排他锁,用于修改或删除数据,其他事务无法获取共享锁或排他锁。
行级锁(Row-level Lock):锁定数据库表中的单独一行,可在并发环境下实现更细粒度的锁控制,减少锁竞争。
表级锁(Table-level Lock):锁定整个数据库表,适用于对整个表进行操作的事务,如表的重建、重命名等。
页级锁(Page-level Lock):锁定数据库中的页面,通常是连续的数据页,适用于需要访问多行数据的事务。
数据库级锁(Database-level Lock):锁定整个数据库,用于进行数据库级别的操作,如备份、还原等。
除了上述常见的锁类型,不同的数据库管理系统可能还有其他特定的锁类型和机制,如行锁的实现方式(行锁、间隙锁、Next-Key锁等)。

行锁怎么加?

在关系型数据库中,行级锁的实现方式可以通过两种常见的方式来加锁:悲观锁和乐观锁。

  1. 悲观锁:
    在悲观锁的实现方式下,数据库会在读取数据时自动为所读取的行加上行级锁,阻止其他事务对该行进行修改。
    在读取数据时,可以使用SELECT … FOR UPDATE语句来获取悲观锁。这会将所选行加上排他锁(Exclusive Lock),其他事务无法对该行进行修改,直到当前事务释放锁。
  2. 乐观锁:
    在乐观锁的实现方式下,数据库并不主动加锁,而是通过使用版本号(或时间戳)来实现并发控制。
    每行数据都包含一个版本号字段,当事务读取数据时,会将版本号一同读取到应用程序中。
    在更新数据时,事务会查当前数据的版本号是否与初始读取时的版本号一致,如果一致,则更新数据并递增版本号;如果不一致,则表示数据已被其他事务修改,需要进行相应处理(如回滚事务或重新读取数据)。

使用SELECT … FOR UPDATE语句可以获取行级锁 经过什么样的情况会出现锁多行的现象?

使用SELECT … FOR UPDATE语句可以获取行级锁,确保在事务中对所选行的数据进行修改时不会被其他事务干扰。在以下情况下,可能会出现锁多行的现象:

  1. 事务中查询的条件导致多行被匹配:
    如果SELECT … FOR UPDATE语句的查询条件导致多行数据被匹配,那么这些行都会被加上行级锁。例如,如果查询条件没有限制唯一性,或者使用了范围查询,可能会导致多行的锁被加上。
  2. 并发事务同时执行SELECT … FOR UPDATE语句:
    如果多个并发事务同时执行SELECT … FOR UPDATE语句,并且这些事务查询的数据存在交叉的情况,那么可能会导致锁多行的现象。
    当事务A执行SELECT … FOR UPDATE语句并加锁时,事务B也执行了相同的SELECT … FOR UPDATE语句并且查询的数据与事务A的查询结果有交集,那么事务B也会对其中的行加上行级锁。
  3. 事务持有锁的时间过长:
    如果事务持有锁的时间过长,其他事务可能会在此期间等待获取锁。如果事务A持有某些行的锁,并且在执行期间没有及时释放锁,那么其他事务可能会因等待而锁住更多的行。

有一个相同的请求接口,请求了两次服务器,有什么处理方案?(这块是想考察幂等)

  1. 请求唯一标识:
    在每个请求中添加一个唯一的标识符,例如在请求头或请求参数中添加一个UUID,并将此标识符与已处理的请求进行比对。
    服务器在接收到请求时,先检查该标识符是否已存在,如果已存在,则可以判断为重复请求,直接返回之前的处理结果,而不执行重复的操作。
  2. 幂等接口设计:
    在设计接口时,将接口设计为幂等操作,即使接口被重复调用,也只会产生一次结果或影响。例如,对于更新操作,可以使用PUT方法来替代POST方法,确保同一请求多次调用时只会更新一次数据。
  3. 状态检查与处理:
    在服务器端,可以通过记录请求的处理状态,如将请求结果或关键信息存储在数据库中,以便后续重复请求时进行状态检查。
    当收到重复请求时,服务器可以查询之前的处理结果并返回,而不再执行重复的操作。
  4. 乐观锁:
    在数据库操作中,可以使用乐观锁的机制来保证幂等性。
    乐观锁通过在数据库表中添加版本号字段,每次更新时检查版本号是否一致。如果版本号一致,则进行更新操作;如果版本号不一致,则表示已经被其他请求修改,可以返回错误或忽略当前请求。

有个高并发的积分系统,想要在数据库增加积分,你会怎么实现?

  1. 数据库设计:
    创建一个用户积分表,包含用户ID、积分总数等字段。
    为用户ID字段添加唯一索引,以提高查询和更新性能。
  2. 缓存方案:
    使用缓存技术(如Redis)来存储用户的积分数据,以提高读取性能和减轻数据库的压力。
    用户的积分数据在首次查询时从数据库加载到缓存中,并定期进行更新或在用户积分变动时更新缓存。
  3. 并发控制:
    使用乐观锁机制来避免并发操作的冲突。在用户积分更新时,读取当前积分和版本号,然后在更新时检查版本号是否一致,如一致则进行更新,否则需要进行回滚或重试。
  4. 事务管理:
    对于某些需要多个操作组合的积分变动场景(如积分转账等),可以使用数据库事务来保证操作的原子性和一致性。
    在事务中执行多个积分操作,如增加积分、减少积分等,并在事务提交时进行校验和处理。
  5. 异步处理:
    使用消息队列(如Kafka或RabbitMQ)将积分变动请求发送到消息队列中,进行异步处理。
    在消费者端从消息队列中获取积分变动请求,并进行相应的积分更新操作。
  6. 监控和日志:
    在系统中添加监控和日志功能,记录关键指标和异常情况,以便及时发现和解决问题。
    监控可以包括并发请求量、响应时间、错误率等指标,用于系统性能和稳定性的评估和优化。