6

躬身入局,干货分享,2023年春招后端技术岗(Python)面试实战教程,Offer今始为君发 - 刘...

 1 year ago
source link: https://www.cnblogs.com/v3ucn/p/17136367.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

躬身入局,干货分享,2023年春招后端技术岗(Python)面试实战教程,Offer今始为君发

早春二月,研发倍忙,杂花生树,群鸥竟飞。为什么?因为春季招聘,无论是应届生,还是职场老鸟,都在摩拳擦掌,秣马厉兵,准备在面试场上一较身手,既分高下,也决Offer,本次我们打响春招第一炮,躬身入局,让2023年的第一个Offer来的比以往快那么一点点。

打开某垂直招聘平台,寻找2023年的第一个猎物:

20230216200218_48397.png

投递简历之后,如约进行面试。

正规公司的面试一般都是笔试先行,笔试题的作用非常务实,就是直接筛掉一批人,提高面试效率,需要注意的是,在这个环节中,往往无法用搜索引擎进行检索,所以,你的大脑就是Python解释器,你的笔将会代替程序的输出:

# 实现字符串反转,以逗号作为切割符,切割的子串以单词作为单元反转  
# 输入:hello world, god bless you  
# 输出:world hello, you bless god

这道题网上没有原题,但其实并不难,考点在于应聘者对于Python基础和复合数据类型内置方法的熟悉程度,题目中所谓的字符串反转并不是真正意义的字符串反转,而是以单词为单元的反转,同时加入了逗号分割逻辑,所以只要对字符串内置方法split,rstrip和列表内置方法join以及reverse的用法足够了解,就可以直接写出解法:

def reseverWords(s:str) ->  str:  
    all_str =   ""  
    s   =    s.split(',')  
    for x   in  s:  
        lis=    x.split()  
        lis.reverse()  
        all_str +=  ' '.join(lis)+', '  
    all_str=all_str.rstrip(', ')  
    return  all_str  
print(reseverWords(str1))

第二题是SQL语句题目,请写一条sql,按照地区的分组聚合数据进行排序:

id    name       location  
--    -----      --------  
1     Mark       US  
2     Mike       US  
3     Paul       Australia  
4     Pranshu    India  
5     Pranav     India  
6     John       Canada  
7     Rishab     India

排序后结果:

id    name       location  
--    -----      --------  
4     Pranshu    India  
5     Pranav     India  
7     Rishab     India  
1     Mark       US  
2     Mike       US  
3     Paul       Australia  
6     John       Canada

这道题也无法在网上查证,一般的分组聚合只是查一个数,这个是按照数量进行排序,并且其实并不展示数量,也可以理解为展示的为分组数据的明细排行榜:

SELECT x.*   
  FROM my_table x   
  JOIN (SELECT location, COUNT(*) total FROM my_table GROUP BY location) y  
    ON y.location = x.location  
 ORDER   
    BY total DESC  
     , id;

思路是先分组,随后按照分组的聚合数据根据地区字段连表排序即可。

通过笔试题筛选后,进入自我介绍环节,一般介绍技术栈和简单的项目经历即可,参考示例:

您好(下午好/上午好),我是19年毕业的,在RD(Research and Development engineer即研发工程师岗位)岗差不多有三年左右的工作经验,一开始在一家创业型公司起步,当时主力开发语言是python,,使用mtv架构,在公司主要和业务打交道,开发和维护后台的API,大概沉淀了两年左右吧,我跳槽到了第二家公司,薪酬实现了double,在新的技术团队里,我接触到了前后端分离项目,也学习了异步编程思想,主力框架是tornado,前端技术也有所涉猎,比如vue框架,了解了数据双向绑定理念,同时也学习了在业务解耦和服务封装层面比较流行的docker容器技术,这项技术使我平时开发和测试工作都提高了效率,最近一年左右吧,我经常使用的web框架是tornado,这个框架我个人非常喜欢,它的异步非阻塞特性让我对异步编程思想的认识更深入了。我也尝试过remote这种工作形式,也锻炼了我在团队中的沟通能力,其实三四年下来,做过的东西解决过的问题也挺多的,待过大团队也经历过小团队,给我的感觉就是互联网企业随着发展,技术和行业边界其实是越来越模糊的,也就是说技术都是具有相通性的,我个人来讲,优势就是技术涉猎比较广,前后端都接触过,踩得坑也比较多,在特定领域有一定的深入,比如异步编程这块。另外我觉得搞开发的,学习能力,总结能力很重要,所以我一直保持着写技术博客的习惯,这样经过沉淀,可以提高一个人的分析能力,也就是解决问题的能力,我的介绍完了,谢谢。

进程、线程和协程的区别

进程、线程和协程,从来就是Python面试中聚讼不休的一个话题,只要我们还在使用Python,就一定逃离不了三程问题:

首先明确一下进程和线程的概念,进程系统进行资源分配的基本单位,一台机器上可以有多个进程,每个进程执行不同的程序,每个进程相对独立,拥有自己的内存空间,隔离性和稳定性比较高,进程之间互不影响,但是资源共享相对麻烦,系统资源占用相对高,同时进程可以利用cpu多核资源,适合cpu密集型任务,比如一些统计计算任务,比如计算广告转化率,uv、pv等等,或者一些视频的压缩解码任务,进程还有一个使用场景,就是后期部署项目的时候,nginx反向代理后端服务,往往需要开启多个tornado服务来支持后台的并发,就是利用了多进程的互不干扰,就算某个进程僵死,也不会影响其他进程,进程使用的是mulitprossing库 ,往往是先声明进程实例,里面可以传入消费方法名称和不定长参数args,然后将实例放入指定进程数的容器中(list),通过循环或者列表推导式,使用start方法开启进程,join方法阻塞主进程。

线程是系统进行资源调度的最小单位,它属于进程,每一个进程中都会有一个线程,由于线程操作是单进程的,所以线程之间可以共享内存变量,互相通信非常方便,它的系统开销比进程小,它是线程之间由于共享内存,会互相影响,如果一个线程僵死会影响其他线程,隔离性和稳定性不如进程,同时,线程并不安全,如果对同一个对象进行操作,需要手动加锁,另外从性能上讲,多线程会触发python的全局解释器锁,导致同一时间点只会有一个线程运行的交替运行模式,线程适用于io密集型任务,所谓io密集型任务就是大量的硬盘读写操作或者网络tcp通信的任务,一般就是爬虫和数据库操作,文件操作非常频繁的任务,比如我负责开发的审核系统,需要同时对mysql和redis有大量的读写操作,所以我使用多线程进行消费。线程使用的是Threading库 ,往往是先声明线程实例,里面可以传入消费方法名称和不定长参数args,然后将实例放入指定线程数的容器中(list),通过循环或者列表推导式,使用start方法开启线程,join方法阻塞主线程。

协程是一种用户态的轻量级线程,协程的调度完全由用户控制,不像进程和线程是系统态,所以在不主动切换协程的情况下,操作全局变量的时候,可以无需加锁(这里有坑,协程库内置也是有锁的,但是看场景,如果使用场景内没有主动切换协程(await)写操作就不需要加锁,如果单协程执行过程中,主动切换了协程,写操作则需要加锁 协程是否加锁问题),只需要判断资源状态即可,效率非常高,同时协程是单线程的,即可以共享内存,又不需要系统态的线程切换,同时也不会触发gil全局解释器锁,所以它性能比线程要高。具体使用场景和线程一样,适合io密集型任务,所谓io密集型任务就是大量的硬盘读写操作或者网络tcp通信的任务,一般就是爬虫和数据库操作,文件操作非常频繁的任务,比如我负责开发的审核系统,需要同时对mysql和redis有大量的读写操作,所以我后期将多线程改造成协程进行消费。协程我使用的python原生协程库asyncio库,首先通过asyncio.ensure_future(doout(4))方法建立协程对象,然后根据当天审核员数量指定开启协程数,和多线程以及多进程的区别是,协程既可以直接传实参,也可以传不定长参数,很方便,然后通过await asyncio.gather(*tasks)方法启动协程,需要注意的是,主方法需要声明成async方法,并且通过asyncio.run(main())来启动。协程虽然是python异步编程的最佳方式,但是我认为它也有缺点,那就是异步写法导致代码可读性下降,同时对编程人员的综合素质要求高,并不是所有人都能理解协程的工作方式,以及python原生协程的异步写法。

Python中的深拷贝和浅拷贝

仅次于三程问题的明星面试题,一般情况下,大家都会说浅拷贝修改复制对象会影响原对象,而深拷贝不会,但其实,浅拷贝会有三种细分的情况:

1.拷贝不可变对象:只是增加一个指向原对象的引用,改变会互相影响。

>>> a = (1, 2, [3, 4])  
>>> b = copy.copy(a)  
>>> b  
... (1, 2, [3, 4])  
# 改变一方,另一方也改变  
>>> b[2].append(5)  
>>> a  
... (1, 2, [3, 4, 5])

2.拷贝可变对象(一层结构):产生新的对象,开辟新的内存空间,改变互不影响。

>>> import copy  
  
>>> a = [1, 2, 3]  
>>> b = copy.copy(a)  
>>> b  
... [1, 2, 3]  
# 查看两者的内存地址,不同,开辟了新的内存空间  
>>> id(b)  
... 1833997595272  
>>> id(a)  
... 1833997595080  
>>> a is b  
... False  
# 改变了一方,另一方不会改变  
a = [1, 2, 3]    b = [1, 2, 3]  
>>> b.append(4)  
>>> a  
... [1, 2, 3]  
>>> a.append(5)  
>>> b  
... [1, 2, 3, 4]

3.拷贝可变对象(多层结构):产生新的对象,开辟新的内存空间,不改变包含的子对象则互不影响、改变包含的子对象则互相影响。

>>> import copy  
  
>>> a = [1, 2, [3, 4]]  
>>> b = copy.copy(a)  
>>> b  
... [1, 2, [3, 4]]  
# 查看两者的内存地址,不同,开辟了新的内存空间  
>>> id(b)  
1833997596488  
>>> id(a)  
1833997596424  
>>> a is b  
... False  
# 1.没有对包含的子对象进行修改,另一方关我卵事  
a = [1, 2, [3, 4]]    b = [1, 2, [3, 4]]  
>>> b.append(5)  
>>> a  
... [1, 2, [3, 4]]  
>>> a.append(6)  
>>> b  
... [1, 2, [3, 4], 5]  
# 2.对包含的子对象进行修改,另一方也随之改变  
a = [1, 2, [3, 4]]    b = [1, 2, [3, 4]]  
>>> b[2].append(5)  
>>> a  
... [1, 2, [3, 4, 5]]  
>>> a[2].append(6)  
>>> b  
... [1, 2, [3, 4, 5, 6]]

高并发如何进行处理的

既然JD(Job Describe)中提到了高并发,那么就一定会问高并发问题,一般情况下,涉及高并发场景的基本上都是外部系统,此时需要简单介绍一下系统的容量是多少,比如有注册用户数、日活、QPS等等。然后就是提供具体方案,一般的手段是加缓存,数据库读写分离,数据库 sharding 等等。高并发背景下,整个系统瓶颈一般都在数据库。

除了上述的一些常规方案,业内最常用的缓解高并发的手段是使用异步任务队列:

为了解决生产者和消费者过度耦合的效率低下问题,我设计了一个缓冲区,生产者不会直接和消费者产生关系,而是通过缓冲区解耦,这个缓冲区就是异步任务队列,队列容器我采用redis数据库,因为redis性能优势比较明显,同时内置的list数据类型比较契合队列这种数据结构,工具类内置了,初始化方法,入队方法,出队方法,队列长度,以及查重唯一方法。每当商户提交表单,此时并不会修改状态,而是将表单数据入库,同时将商户uid进行入队操作,遵循fifo原则,在消费者端使用异步的方式进行消费,也就是出队操作,每一个线程对应一个审核员,通过消费方法进行传参,每次将出队的商户uid和线程传入的审核员id进行组合分配,出队之后并发数已经得到了控制,随后在mysql端进行update操作,达到异步分配审核的目的。

保持幂等性

如果面试中提到了异步任务队列(消息队列),那么幂等性操作几乎一定会在后续的问题中提及,所谓幂等性,简单来说就是对于同一个系统,在同样条件下,一次请求和重复多次请求对资源的影响是一致的,就称该操作为幂等的。比如说如果有一个接口是幂等的,当传入相同条件时,其效果必须是相同的。在RabbitMQ中消费幂等就是指给消费者发送多条同样的消息,消费者只会消费其中的一条。例如,在一次购物中提交订单进行支付时,当网络延迟等其他问题造成消费者重新支付,如果没有幂等性的支持,那么会对同一订单进行两次扣款,这是非常严重的,因此有了幂等性,当对同一个订单进行多次支付时,可以确保只对同一个订单扣款一次。

具体手段:

事实上,当审核任务出队之后,如果在消费端出现意外,这个意外包含但不限于出对后tornado宕机、mysql宕机等等,导致出队任务没有进行流程化处理,所以我采用了ack验证机制,也就是缓冲区队列从单队列升级为双队列,把rpop出队改成redis内置的rpoplpush的原子性操作,出队后立即进入确认队列,在消费端完成审核任务后,对ack队列进行确认移除操作,如此,一次审批任务才算完结,如果任务生命周期内,任务一直存在于确认队列没有出队,那么轮询任务会将任务id移出确认队列,重新在缓冲区队列进行入队操作,这样就避免了,僵审任务的问题。

技术面试虽然是一种信息不对等的较量,但是只要认真研究JD(Job Describe),做好相关的知识储备,基础常识不要翻车(包含但不限于Python基础/数据库基础),那么作为应聘者拿一个Offer也不是想象中的那么难,本次面试的实战录音可以在B站(Youtube)搜索刘悦的技术博客查阅,欢迎诸君品鉴。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK