厉害了!京东要新招 16000 人

小林coding

共 10342字,需浏览 21分钟

 ·

2024-08-02 16:26

图解学习网站:https://xiaolincoding.com

大家好,我是小林。

前几个星期,我还刚说完美团今年秋招招 6000 人,是校招大户。

结果,昨天看到京东秋招也开始了,好家伙,没想到开设的就业岗位更多!

累计提供 10000+ 就业岗位,再加上 6000+实习岗位,累计起来就是 1.6w 人招聘需求,今年难得有这么大规模的招人需求。


截图来自京东招聘公众号

更关键的是,将并提供极具竞争力的薪酬待遇,之前我看到有同学跟我反馈,他在京东实习,就开了 1.2w 实习薪资,比不少一线大厂的实习薪资都高。

注意,这个是实习薪资,这个实习薪资在小公司,都能招一个 3 年左右的开发人员了,我只能说,京东有钱!

那京东校招的正式薪资是多少呢?京东去年的校招薪资我去看了下,我给大家整理一下:

京东年总包构成 = 月薪 x 16 + 房补

  • 普通 offer:19.5k~23k*16,年包:31w~37w
  • sp offer:24k~27k*16,年包:38w~43w
  • ssp offer:29k~30k*16,年包:46w~48w

等今年京东校招薪资出来,到时候,我们在做一下对比,看一下是否开的更高了。

既然京东秋招准备开始了,那么给大家分享京东的 Java后端面经,给准备参加秋招的同学做一个参考。

属于一面, 问了MySQL、Redis、MQ、Java 方面的八股,问题不算多,整体就问了 10 多个技术问题,但是问的都是比较细节的,这次面试没有手撕算法,但是并不是说京东不考算法,算法只要是大厂,90%概率都会考的。

京东面试

MyISAM 和 InnoDB 的区别?

  • 事务:InnoDB 支持事务,MyISAM 不支持事务,这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一。
  • 索引结构:InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚簇索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  • 锁粒度:InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。
  • count 的效率:InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快。

MySQL  的事务隔离级别有哪些?

  • 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
  • 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
  • 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别
  • 串行化(serializable);会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

按隔离水平高低排序如下:针对不同的隔离级别,并发事务时可能发生的现象也会不同。也就是说:

  • 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;
  • 在「读提交」隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
  • 在「可重复读」隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象;
  • 在「串行化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。

接下来,举个具体的例子来说明这四种隔离级别,有一张账户余额表,里面有一条账户余额为 100 万的记录。然后有两个并发的事务,事务 A 只负责查询余额,事务 B 则会将我的余额改成 200 万,下面是按照时间顺序执行两个事务的行为:

在不同隔离级别下,事务 A 执行过程中查询到的余额可能会不同:

  • 在「读未提交」隔离级别下,事务 B 修改余额后,虽然没有提交事务,但是此时的余额已经可以被事务 A 看见了,于是事务 A 中余额 V1 查询的值是 200 万,余额 V2、V3 自然也是 200 万了;
  • 在「读提交」隔离级别下,事务 B 修改余额后,因为没有提交事务,所以事务 A 中余额 V1 的值还是 100 万,等事务 B 提交完后,最新的余额数据才能被事务 A 看见,因此额 V2、V3 都是 200 万;
  • 在「可重复读」隔离级别下,事务 A 只能看见启动事务时的数据,所以余额 V1、余额 V2 的值都是 100 万,当事务 A 提交事务后,就能看见最新的余额数据了,所以余额 V3 的值是 200 万;
  • 在「串行化」隔离级别下,事务 B 在执行将余额 100 万修改为 200 万时,由于此前事务 A 执行了读操作,这样就发生了读写冲突,于是就会被锁住,直到事务 A 提交后,事务 B 才可以继续执行,所以从 A 的角度看,余额 V1、V2 的值是 100 万,余额 V3 的值是 200万。

这四种隔离级别分别是怎么实现的?

  • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
  • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
  • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 MVCC 机制来实现的,它们的区别在于创建 Read View 的时机不同,「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View

MySQL 的 redo log 作用是什么?

Redo log是MySQL中用于保证持久性的重要机制之一。它通过以下方式来保证持久性:

  1. Write-ahead logging(WAL):在事务提交之前,将事务所做的修改操作记录到redo log中,然后再将数据写入磁盘。这样即使在数据写入磁盘之前发生了宕机,系统可以通过redo log中的记录来恢复数据。
  2. Redo log的顺序写入:redo log采用追加写入的方式,将redo日志记录追加到文件末尾,而不是随机写入。这样可以减少磁盘的随机I/O操作,提高写入性能。
  3. Checkpoint机制:MySQL会定期将内存中的数据刷新到磁盘,同时将最新的LSN(Log Sequence Number)记录到磁盘中,这个LSN可以确保redo log中的操作是按顺序执行的。在恢复数据时,系统会根据LSN来确定从哪个位置开始应用redo log。

为什么 Redis 性能高?

官方使用基准测试的结果是,单线程的 Redis 吞吐量可以达到 10W/每秒,如下图所示:之所以 Redis 采用单线程(网络 I/O 和执行命令)那么快,有如下几个原因:

  • Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构,因此 Redis 瓶颈可能是机器的内存或者网络带宽,而并非 CPU,既然 CPU 不是瓶颈,那么自然就采用单线程的解决方案了;
  • Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。
  • Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

Redis 数据结构有哪些?

Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)随着 Redis 版本的更新,后面又支持了四种数据类型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五种数据类型的应用场景:

  • String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
  • List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
  • Hash 类型:缓存对象、购物车等。
  • Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
  • Zset 类型:排序场景,比如排行榜、电话和姓名排序等。

Redis 后续版本又支持四种数据类型,它们的应用场景如下:

  • BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
  • HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
  • GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
  • Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。

redis 除了可以缓存,还可以有什么功能?

Redis实现消息队列

  • 使用Pub/Sub模式:Redis的Pub/Sub是一种基于发布/订阅的消息模式,任何客户端都可以订阅一个或多个频道,发布者可以向特定频道发送消息,所有订阅该频道的客户端都会收到此消息。该方式实现起来比较简单,发布者和订阅者完全解耦,支持模式匹配订阅。但是这种方式不支持消息持久化,消息发布后若无订阅者在线则会被丢弃;不保证消息的顺序和可靠性传输。
  • 使用List结构:使用List的方式通常是使用LPUSH命令将消息推入一个列表,消费者使用BLPOPBRPOP阻塞地从列表中取出消息(先进先出FIFO)。这种方式可以实现简单的任务队列。这种方式可以结合Redis的过期时间特性实现消息的TTL;通过Redis事务可以保证操作的原子性。但是需要客户端自己实现消息确认、重试等机制,相比专门的消息队列系统功能较弱。

Redis实现分布式锁

  • set nx方式:Redis提供了几种方式来实现分布式锁,最常用的是基于SET命令的争抢锁机制。客户端可以使用SET resource_name lock_value NX PX milliseconds命令设置锁,其中NX表示只有当键不存在时才设置,PX指定锁的有效时间(毫秒)。如果设置成功,则认为客户端获得锁。客户端完成操作后,解锁的还需要先判断锁是不是自己,再进行删除,这里涉及到 2 个操作,为了保证这两个操作的原子性,可以用 lua 脚本来实现。
  • RedLock算法:为了提高分布式锁的可靠性,Redis作者Antirez提出了RedLock算法,它基于多个独立的Redis实例来实现一个更安全的分布式锁。它的基本原理是客户端尝试在多数(大于半数)Redis实例上同时加锁,只有当在大多数实例上加锁成功时才认为获取锁成功。锁的超时时间应该远小于单个实例的超时时间,以避免死锁。该方式可以通过跨多个节点减少单点故障的影响,提高了锁的可用性和安全性。

Java 对象的创建过程说一下?

在Java中创建对象的过程包括以下几个步骤:

  1. 类加载检查:虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程
  2. 分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
  3. 初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
  4. 进行必要设置,比如对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
  5. 执行 init 方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始——构造函数,即class文件中的方法还没有执行,所有的字段都还为零,对象需要的其他资源和状态信息还没有按照预定的意图构造好。所以一般来说,执行 new 指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全被构造出来。

synchronized 锁实现的原理是什么?

synchronized是Java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁,使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。

执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。

执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。

synchronized是排它锁,当一个线程获得锁之后,其他线程必须等待该线程释放锁后才能获得锁,而且由于Java中的线程和操作系统原生线程是一一对应的,线程被阻塞或者唤醒时时会从用户态切换到内核态,这种转换非常消耗性能。

从内存语义来说,加锁的过程会清除工作内存中的共享变量,再从主内存读取,而释放锁的过程则是将工作内存中的共享变量写回主内存。

实际上大部分时候我认为说到monitorenter就行了,但是为了更清楚的描述,还是再具体一点。如果再深入到源码来说,synchronized实际上有两个队列waitSet和entryList。

  1. 当多个线程进入同步代码块时,首先进入entryList
  2. 有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1
  3. 如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁
  4. 如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null

Kafka 和 RocketMQ 的区别是什么?

  • 架构设计
    • Kafka:分布式发布-订阅系统,由生产者、消费者、Broker、Topic、Partition 和 ZooKeeper 构成。
    • RocketMQ:由 NameServer、Broker、生产者和消费者构成,支持多种消息模式。
  • 高可用设计
    • Kafka:通过副本机制、领导者和追随者模式、控制器和 ZooKeeper 协调实现高可用。
    • RocketMQ:采用主从架构,NameServer 无状态设计,支持同步和异步复制。
  • 延迟消息:Kafka 不支持,RocketMQ 提供多个预定义级别延迟。
  • 集群扩展
    • Kafka:通过增加 Broker 扩展,依赖 Zookeeper,分区重新平衡负载。
    • RocketMQ:区分 Broker 和 NameServer 角色,扩展时可增加 Broker 和 NameServer。
  • 使用场景
    • RocketMQ:适合事务消息、顺序消息、广播消息、定时延迟消息等场景,如电商、金融等对可靠性要求高的行业。
    • Kafka:适用于日志聚合、流式处理、事件驱动架构、数据集成和分布式系统冗余备份等场景。

Kafka 为什么性能高?

1)页缓存技术

Kafka是基于操作系统的页缓存来实现写入的,操作系统本身有一层缓存,叫做page cache,是在内存里的缓存,我们也可以称之为 os cache,意思就是操作系统自己管理的缓存。

Kafka在写入磁盘文件的时候,可以直接写入到这个os cache里,也就是仅仅写入到内存中,接下来由操作系统自己决定什么时候把os cache里的数据真的刷入磁盘文件中。这样可以很大提升写性能


2)磁盘顺序写

Kafka写数据的时候,Kafka的消息是不断追加到文件末尾的,而不是在文件的随机位置写入数据,这个特性使Kafka可以充分利用磁盘的顺序读写性能。顺序读写不需要磁盘磁头的寻道时间,避免了随机磁盘寻址的浪费,只需很少的扇区旋转时间,所以速度远快于随机读写。

Kafka中每个分区是一个有序的,不可变的消息序列,新的消息不断追加到Partition的末尾,在Kafka中Partition只是一个逻辑概念,Kafka将Partition划分为多个Segment,每个Segment对应一个物理文件,Kafka对segment文件追加写,这就是顺序读写。

2)零拷贝

在消费数据的时候,实际上要从kafka的磁盘文件里读取某条数据然后发送给下游的消费者。

Kafka在读数据的时候为了避免多余的数据拷贝,使用了零拷贝技术。也就是说直接让os cache里的数据发送到网卡后然后传输给下游的消费者,跳过中间从os cache拷贝到kafka 进程缓存和再拷贝到socket缓存中的两次缓存,同时也减少了上下文切换。

在Linux Kernel2.2之后出现了一种叫做“零拷贝(zero-copy)”系统调用机制,就是跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户缓冲区”。

Kafka 使用到了 mmap+write(持久化数据) 和 sendfile(发送数据) 的方式来实现零拷贝。分别对应 Java 的 MappedByteBuffer 和 FileChannel.transferTo。

3)分区并发

kafka中的topic中的内容可以分在多个分区(partition)存储,每个partition又分为多个段segment,所以每次操作都是针对一小部分做操作,很轻便,并且增加并行操作的能力

4)批量发送

Kafka允许进行批量发送消息,Productor发送消息的时候,可以将消息缓存在本地,等到了固定条件发送到kafka,可减少IO延迟 (1):等消息条数到固定条数 (2):一段时间发送一次

5)数据压缩

Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩,压缩的好处就是减少传输的数据量,减轻对网络传输的压力。

批量发送和数据压缩一起使用,单条做数据压缩的话,效果不太明显。消息发送时默认不会压缩,可使用compression.type来指定压缩方式,可选的值为snappy、gzip和lz4

实习问题

  • 实习中有一定复杂度或难度比较大的项目;
  • 怎么理解实习工作解决的业务问题;
  • 实习中意见和别人有没有不同,怎么处理;

推荐阅读:
终于成了!网站又整了个大的!
来银行面试了,有点简单?
腾讯放水了,但我还是输了...

浏览 779
1点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
1点赞
评论
收藏
分享

手机扫一扫分享

分享
举报