ITPub博客

首页 > 数据库 > 数据库开发技术 > 腾讯想去,但一面就凉凉了...

腾讯想去,但一面就凉凉了...

原创 数据库开发技术 作者:皇子谈技术 时间:2024-06-05 18:03:00 0 删除 编辑

就算再怎么凉凉,别否定自己,这是一个双向选择的过程。

星标公众号,第一时间收到消息 | 学习网站:https://itgogogo.cn

大家好,我是皇子。

近期,腾讯动作频繁,不过也迎来了多重面的正向消息。

腾讯游戏在5月28日的年度发布会上公布了34款游戏产品的最新进展,并且在其中包括13款重点新作。例如:《极品飞车:集结》和《塔瑞斯世界》。

而此前一周,5月21日发布的手机游戏《地下城与勇士》(DNF手游)在市场上就已经引起热烈反响,发布后据第三方游戏研究机构评估,首周收入预估超过1.4亿美元。

接着是5月29日,腾讯成长守护上线了一键屏蔽陌生人、自我账号管理、绿色手机上报、一键禁玩禁充,共同组成的腾讯游戏防沉迷"四件套"。

最后5月30日重磅发布,一款面向C端的AI 大模型APP --- 腾讯元宝。

刚发布时,我自己也去体验了一把,可以说是打通了微信公众号的任督二脉,能够将本就不开放的公众号文章作为知识库,为混元大模型提供了高质量的信息检索源。

腾讯元宝这是自去年9月混元大模型正式亮相一来,面向C端的一个重磅级AI 大模型产品。

或许将会促成腾讯结合微信、公众号、视频号、腾讯云等数百个被注入了混元大模型能力的产品,进行更深层次的整合,促成腾讯新的生态形成。

放一张Z Finance整理的腾讯生成式AI布局全景图

介绍完腾讯最近的大动作,我们开始介绍一位很想去腾讯后端开发的一面面经。

虽然凉凉了,但是可以发现腾讯在实习生的招聘方面,要求了解和掌握的知识点蛮广的。

稍微整理一下涉及到了:算法、Golang、操作系统、网络、Redis、MySQL。

面了一个小时,这位同学反馈,凉凉的主要原因是在网络部分准备不充分。

所以本次分享会着重展开这部分内容进行复盘。

算法

删除链表中重复元素

这里我们区分下该链表是已排序还是未排序。

针对未排序的链表

难度:中等。

golang手推代码:

// ListNode 定义链表节点结构体
type ListNode struct {
Val int
Next *ListNode
}

// removeDuplicatesFromUnsortedList 从未排序链表中移除重复元素
func removeDuplicatesFromUnsortedList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
// 如果链表为空或只有一个节点,则没有重复元素,直接返回
return head
}

// 创建一个map用于存储已经出现过的节点值
seen := make(map[int]bool)
dummy := &ListNode{Next: head} // 创建一个哑节点,简化头节点的处理
prev := dummy // prev用于记录当前节点的前一个节点

// 遍历链表
for curr := head; curr != nil; {
if seen[curr.Val] {
// 如果当前节点的值已经在map中出现过,说明是重复元素
// 将prev的Next指向curr的下一个节点,从而移除curr节点
prev.Next = curr.Next
curr = prev.Next // 更新curr为prev的下一个节点
} else {
// 如果当前节点的值没有在map中出现过,则将其加入map
seen[curr.Val] = true
// 移动prev和curr到下一个节点
prev = curr
curr = curr.Next
}
}

// 返回处理后的链表的头节点(即dummy节点的下一个节点)
return dummy.Next
}

针对已排序的链表:

难度:简单。

思路:

1、需要遍历链表,比较当前节点和下一个节点的值,如果相同则删除下一个节点

2、并且当开始出现相同时,需要一次性遍历完下一个节点是否同样出现了重复元素。

Golang

new 的作用是什么?

new 创建一个该类型的实例,并且返回指向该实例的指针。

new 函数是内建函数,函数定义:func new(Type) *Type

注意:

- 传递给 new 函数的是一个类型,而不是一个值

- 使用 new 函数来分配空间

- 返回值是指向这个新分配的地址的指针


make 的作用是什么?

make 的作用是为 slice, map or chan 的初始化 然后返回引用。make 函数是内建函数,函数定义:

func make(Type, size IntegerType) Type

make(T, args)函数的目的和 new(T)不同,make仅仅用于创建 slice, map, channel 而且返回类型是实例


Slice 的扩容机制,有什么注意点?

Go1.18之前

Go 中切片扩容的策略是这样的:

  • 首先判断,如果新申请容量大于 2 倍的旧容量,最终容量就是新申请的容量
  • 否则判断,如果旧切片的长度小于 1024,则最终容量就是旧容量的两倍
  • 否则判断,如果旧切片长度大于等于 1024,则最终容量从旧容量开始循环增加原来的 1/4, 直到最终容量大于等于新申请的容量
  • 如果最终容量计算值溢出,则最终容量就是新申请容量

Go1.18开始

内置函数Append对切片的扩容算法发生变化:和Go 1.17以1024作为大小分界不同,Go 1.18使用256作为threshold


介绍下GMP

  • G(Goroutine):我们所说的协程,为用户级的轻量级线程,每个 Goroutine 对象中的 sched 保存着其上下文信息。
  • M(Machine):对内核级线程的封装,数量对应真实的 CPU 数(真正干活的对象)。
  • P(Processor):即为 G 和 M 的调度对象,用来调度 G 和 M 之间的关联关系,其数量可通过 GOMAXPROCS()来设置,默认为核心数。

GMP的调度流程

操作系统

关于并发/共享/虚拟异步的概念

并发

并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。

并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。

操作系统通过引入进程和线程,使得程序能够并发运行。

共享

共享是指系统中的资源可以被多个并发进程共同使用。

有两种共享方式:互斥共享和同时共享。

互斥共享的资源称为临界资源,例如打印机等,在同一时刻只允许一个进程访问,需要用同步机制来实现互斥访问。

虚拟

虚拟技术把一个物理实体转换为多个逻辑实体。

主要有两种虚拟技术:时(时间)分复用技术和空(空间)分复用技术。

多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占用处理器,每次只执行一小个时间片并快速切换。

虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。

异步

异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。


进程通信(IPC)

进程同步与进程通信很容易混淆,它们的区别在于:

进程同步:控制多个进程按一定顺序执行;

进程通信:进程间传输信息。进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。

IPC(Inter-Process Communication,进程间通信)是指在操作系统中,不同进程之间进行数据交换和通信的机制。


包括几种常见的实现进程间通信的方式:

管道:适用于父子进程之间的通信,例如一个进程创建子进程并与其进行通信。

FIFO:也称为命名管道,去除了管道只能在父子进程中使用的限制。FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。

消息队列:

相比于 FIFO,消息队列具有以下优点:

  • 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
  • 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
  • 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。

适用于需要异步通信的场景,例如一个进程将消息发送到队列中,其他进程可以在需要时从队列中接收消息。

信号量:它是一个计数器,用于为多个进程提供对共享数据对象的访问。

适用于进程间的同步和互斥操作,例如多个进程需要对共享资源进行访问控制。

共享存储:允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。需要使用信号量用来同步对共享存储的访问。多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用内存的匿名段。

套接字与其它通信机制不同的是,它可用于不同机器间的进程通信。

适用于网络通信和跨主机通信的场景,例如不同主机上的进程需要进行数据交换和通信。


是否使用过awk,基于Nginx日志统计UV

AWK 是一种处理文本文件的语言,是一个强大的文本分析工具

它是专门为文本处理设计的编程语言,也是行处理软件,通常用于扫描,过滤,统计汇总等工作,数据可以来自标准输入也可以是管道或文件。

根据访问IP统计UV

awk '{print $1}' access.log|sort | uniq -c |wc -l


网络

IP 地址编址方式

IP 地址的编址方式经历了三个历史阶段:分类、子网划分、无分类。

分类:

分类由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。IP 地址 ::= {< 网络号 >, < 主机号 >}

子网划分:

子网划分通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。

IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >}

要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0。

注意,外部网络看不到子网的存在。

无分类:

无分类无分类,即无类别域间路由(Classless Inter-Domain Routing、CIDR)消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。

IP 地址 ::= {< 网络前缀号 >, < 主机号 >}

CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。

一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 构成超网 。在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。


建立连接的三次握手

假设 A 为客户端,B 为服务器端。

1、首先 B 处于 LISTEN(监听)态,等待客户的连接请求。

2、A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。

3、B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1(x+数据长度。因为tcp建立连接三次握手中的固定的数据长度,所以是x+1),同时也选择一个初始的序号 y。

4、A 收到 B 的连接确认报文后,还要向 B 发出确认,ACK=1,确认号为 y+1,序号为 x+1。

5、B 收到 A 的确认后,连接建立。


TCP三次握手的原因

第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。

客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。

客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。

如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。

TCP 关闭连接的四次挥手

以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。

1、A 发送连接释放报文,FIN=1。

2、B 收到之后发出确认,此时 TCP 属于半关闭状态,进入CLOSE-WAIT状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。

3、当 B 不再需要连接(B在处理的请求全部发送给了A)时,发送连接释放报文,FIN=1。

4、A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。

5、B 收到 A 的确认后释放连接。

四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。

这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

2MSL是什么

MSL(Maximum Segment Lifetime)是TCP协议中的一个参数,它指的是一个分段在网络中最长的生存时间。MSL的具体取值通常是由操作系统决定的,并且通常在几十秒到几分钟之间。

2MSL是MSL的两倍,即2倍的最大分段生存期时间。在TCP连接关闭过程中,主动关闭连接的一方发送最后一个ACK报文后,需要等待2MSL的时间,才能认为连接已经彻底关闭。

time_wait 产生原因(等待2MSL意义)

TCP连接在关闭时,主动关闭连接的一方会进入TIME_WAIT状态,这个状态会持续2个MSL(Maximum Segment Lifetime,最大报文生存时间)的时间,通常是4分钟,以确保所有可能还在网络中的数据包被接收或过期。

当系统中存在大量短时间内频繁建立和断开的TCP连接时,可能会导致TIME_WAIT状态的套接字数量激增,从而占用系统资源并影响新连接的建立。

假设是客户端向服务端发起断开连接,2MLS 为了保证客户端发送的最后一个ACK报文段能够到达服务器。

因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文,则服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器,最后客户端和服务器都能正常的关闭。

Redis

什么是缓存穿透问题?

先来看一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。

**缓存穿透:**指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

通俗点说,读请求访问时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,这就是缓存穿透。


缓存穿透一般都是这几种情况产生的:

业务不合理的设计,比如大多数用户都没开守护,但是你的每个请求都去缓存,查询某个userid查询有没有守护。

业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。

黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。


如何避免缓存穿透呢?一般有三种方法

1.如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。

2.如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)

3.使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。

❝

简单来说,布隆过滤器(BloomFilter)是一种数据结构。特点是存在性检测,如果布隆过滤器中不存在,那么实际数据一定不存在;如果布隆过滤器中存在,实际数据不一定存在。相比于传统数据结构(如:List、Set、Map等)来说,它更高效,占用空间更少。缺点是它对于存在的判断是具有概率性。


什么是缓存雪奔问题?

缓存雪奔:指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。

产生的原因:缓存雪奔一般是由于大量数据同时过期造成的

解决:

可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。

Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群啦。


什么是缓存击穿问题?

缓存击穿:指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。

缓存击穿与缓存雪崩的区别:看着有些像但其实是有区别的,缓存雪奔是指数据库收到大量的请求,造成压力过大甚至down机;缓存击穿只是大量并发请求到了DB数据库层面。可以认为击穿是缓存雪奔的一个子集吧。有些文章认为它俩区别,是区别在于击穿针对某一热点key缓存,雪奔则是很多key。

解决方案就有两种:

  1. 使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
  2. “永不过期”,是指没有设置过期时间,或者是热点数据快要过期时,通过定时任务去更新和设置过期时间。

MySQL

MySQL的基本架构

  • 客户端:最上层的服务并不是MySQL所独有的,大多数基于网络的客户端/服务器的工具或者服务都有类似的架构。比如连接处理、授权认证、安全等等。
  • Server层:大多数MySQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有的内置函数(例如,日期、时间、数学和加密函数),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
  • 存储引擎层:第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。Server层通过API与存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对上层的查询过程透明。

一条查询 SQL 的执行过程

1、先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限会先查询缓存(MySQL8.0 版本以前,MySQL8.0 版本之后去除大部分的查询缓存)。

2、如果没有缓存,解析器进行语法分析,提取 sql 语句中 select 等关键元素,然后判断 sql 语句是否有语法错误,比如关键词是否正确等等。

3、语法解析之后,MySQL的服务器会对查询的语句进行优化,确定执行的方案。

4、完成查询优化后,按照生成的执行计划调用数据库引擎接口,返回执行结果。


MySQL索引的数据结构

MySQL的默认存储引擎是InnoDB,它采用的是B+树结构的索引。

B+树:只有叶子节点才会存储数据,非叶子节点只存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。

在这张图里,有两个重点:

  • 最外面的方块,的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(粉色所示)和指针(黄色/灰色所示),如根节点磁盘包含数据项(左开右闭)17和35,包含指针P1、P2、P3,P1表示小于等于17的磁盘块,P2表示在大于17和小于等于35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、4、5……、65。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
  • 叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表,可以进行范围查询。

索引失效的场景

  • 在索引列上使用mysql的内置函数,索引失效。
  • 对索引列运算(如,+、-、*、/),索引失效。
  • 索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。
  • 索引字段上使用is null, is not null,可能导致索引失效。字段为空是可以走索引的,但是部分场景可能会失效,字段尽量设置为非null然后设置默认值
  • 如果字段类型是字符串,where时一定用引号括起来,否则会因为隐式类型转换,索引失效
  • like通配符可能导致索引失效。最左前缀不仅仅适用于组合索引,还适用于varchar的like语句,但是要注意,只有like "XXX%"的情况走索引,like "%XXX"和like "%XXX%"是不走索引的。
  • 查询条件包含or或者in,可能导致索引失效。具体是由MySQL优化器内部决定是全表扫描还是索引查找,用效率较高的一种方式。
  • 联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。
  • 左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。
  • MySQL优化器估计使用全表扫描要比使用索引快,则不使用索引:MySQL优化器内部就是找到一个索引扫描行数最少的方案去执行语句。那么扫描行数怎么来判断的?是逐行统计数据表的数据吗?其实并不是,而是根据统计信息来预估的值,这个统计信息就是我们常说的索引的“区分度”。

建议:在使用前最好还是先用 explain 来试试到底sql语句走不走索引,然后选择较优的sql。


一条SQL查询语句的执行顺序?

  • FROM:对FROM子句中的左表<left_table>和右表<right_table>执行笛卡儿积(Cartesianproduct),产生虚拟表VT1
  • ON:对虚拟表VT1应用ON筛选,只有那些符合<join_condition>的行才被插入虚拟表VT2中
  • JOIN:如果指定了OUTER JOIN(如LEFT OUTER JOIN、RIGHT OUTER JOIN),那么保留表中未匹配的行作为外部行添加到虚拟表VT2中,产生虚拟表VT3。如果FROM子句包含两个以上表,则对上一个连接生成的结果表VT3和下一个表重复执行步骤1)~步骤3),直到处理完所有的表为止
  • WHERE:对虚拟表VT3应用WHERE过滤条件,只有符合<where_condition>的记录才被插入虚拟表VT4中
  • GROUP BY:根据GROUP BY子句中的列,对VT4中的记录进行分组操作,产生VT5
  • ROLLUP|CUBE:对表VT5进行CUBE或ROLLUP操作,产生表VT6(CUBE和ROLLUP是用于在GROUP BY语句中生成更复杂的分组统计的扩展功能,它们主要用于数据分析和报表生成场景,能够提供不同维度的汇总数据)
  • HAVING:对虚拟表VT6应用HAVING过滤器,只有符合<having_condition>的记录才被插入虚拟表VT7中。
  • SELECT:第二次执行SELECT操作,选择指定的列,插入到虚拟表VT8中
  • DISTINCT:去除重复数据,产生虚拟表VT9
  • ORDER BY:将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10
  • LIMIT:取出指定行的记录,产生虚拟表VT11,并返回给查询用户


觉得有用,点个转发、在看、赞再走呀❤️

推荐阅读

米哈游放弃了,五一先稳住

腾讯三面,被拷打了

B站三面,挑战有点大

得物不错,可惜止步于二面


关于作者:一位热爱技术,并在职场与自媒体间探索的实践者,希望通过分享个人经验和见解,帮助更多人实现自我成长和价值。

如果您对我的公众号内容感兴趣,欢迎关注我(关注回复“简历”,可获得精美简历模板)。

也可以在下方添加我微信和一支烟花AI社区技术群,一起交流技术、职场心得。欢迎~

原文作者:皇子谈技术;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2023-12-21

  • 博文量
    113
  • 访问量
    56509