最近面试问的问题
1. 垃圾回收机制的理解
答:Python的GC(垃圾回收机制Garbage Controller)机制自动实现内存管理,主要有3种方式(引用计数为主,标记清除和分代回收为辅)
- 引用计数
原理:每一个对象都维护一个引用计数的字段,记录这个对象被引用的次数,如果有新的引用指向对象,则对象引用计数就加一,引用被销毁时,对象引用减一,当对象的引用计数为0时,该内存被释放。
这种方法主要存在两种问题:
- 需要维护引用计数,存在执行效率的问题
- 无法解决循环引用的问题
-
标记清除
原理:标记清除是针对循环引用问题的回收机制,作用的对象是容器类型的对象(比如:list、set、dict等)。通过根节点对象对有相同的活动对象打上标记,然后回收没有被标记的非活动对象
缺点:标记和清除的过程效率都不高 -
分代回收
原理:分代回收是建立在标记清除的基础上的一种辅助回收容器对象的GC机制。分代回收根据系统中内存存活的时间把它们划分为三种集合,每一个集合称为一"代",Python将内存分为了"3"代,分别 为年轻代(第0代)、中年代(第1代)、老年代(第2代),它们的垃圾收集频率与对象的存活时间的增大而减小。而新创建的对象都在第0代(年轻代),第0代的集合总数达到上限后,会触发GC机制,然后回收对象,释放内存,不能被回收的的移动到中年代,以此类推。
举例:
当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划到集合B中去。当垃圾收集开始工作时,大多数情况都仅仅对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。
在这个过程中,集合B的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会由于这样的分代的机制而被延迟。
2. 进程和线程的区别,通信方式有哪些
进程与线程的区别:
做个简单的比喻:进程=火车,线程=车厢
- 线程在进程下行进(单独的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很容易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一辆火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能行进在不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进行)-“信号量”
答:进程间通信方式有以下6种
-
管道
解释:所谓管道就是内核的一串缓存,遵循先进先出的原则。从管道的一段写入数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据,管道传输的数据是无格式的流且大小受限。
通信原理:首先通过fork创建子进程,创建子进程的时候会复制父进程的文件描述符,这样两个进程各有两个fd,两个进程就可以通过各自的fd写入和读取同一个管道文件实现跨进程通信了
优缺点:通信效率低,不适合进程间频繁交换数据 -
消息队列
解释:消息队列是保存在内核中的消息链表。在发送数据的时候,会分成一个个独立的数据单元,也就是消息体,消息体是用户自定义的数据局类型,消息发送方和接收方都需要约定好数据类型,所以每个消息体大小都是固定的。
通信原理:进程A要给进程B发送消息,进程A把数据放在对应的消息队列上就可以正常返回,进程B需要的时候再去读取数据就可以,同理进程B要给进程A发送消息也是如此。
优缺点:通信不及时,附件大小也受限。不适合大数据的传输 -
共享内存
解释:消息队列的读取和写入的过程都会发生用户态与内核态直接的消息拷贝过程,从而导致进程间通信速率变慢。
原理:共享内存机制就是拿出一块虚拟的地址来,映射到相同的物理内存中,这样进程A写入的数据,进程B就可以立马读取
优缺点:提高进程间通信速率 -
信号量
解释:为了防止多进程竞争共享资源,而造成数据错乱,所以需要保护机制
原理:其实就是一个整型的计数器,主要用于实现进程间的互斥和同步,而不是用于缓存进程间通信的数据 -
信号
解释:信号事件的来源主要有硬件命令和软件来源,是进程间通信机制中唯一的异步通信机制。 -
socket
解释:本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口
3. Python线程的执行方式
答:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态,当系统有一个以上CPU时,则线程的操作有可能非并发.当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行。
由于Python的多线程并不能真正支持并行,实际上仍采用的是单核串行的方式,这与它的GIL(全局解释器锁)有关。因此,我们需要异步并行地执行某些程序时,可以采用多进程的方式
并发:指多个事情在同一时间段内处理
并行:指多个事情在同一时间点处理
并发的多个任务是抢占资源,而并行是不抢占资源,并且只有在多核CPU情况下才有并发
4. restful的特征
答:resful称之为表现层状态转化,一种面向资源的软件架构风格,它具有以下特征:
-
资源:就是网络的一个实体,是一个具体的信息,表示格式可以有JSON,TEXT,XML等
-
统一接口:数据的元操作,即CRUD(create,read,update,delete,即数据的增删改查),分别对应HTTP方法,以及对应返回的状态码:
GET: 获取资源
POST:创建资源
PUT:更新资源
DELTE:删除资源 -
URL: 可以用一个URL指向它,每一种资源对应一个特征的URL
-
无状态:即所有的资源都可以通过URL定位,而这个定位与其他资源无关,也不会因为其他资源的变化而改变
-
幂等性:一次请求和多次请求,资源状态都是一样的,无论调用多少次都不会产生副作用
5. flask钩子和Django中间件
答:钩子函数是在客户端和服务端交互的前后进行数据处理,是通过装饰器函数形式实现的,一共有4种钩子函数
- before_first_request
在对应用程序实例的第一个请求之前注册要运行的函数,只会运行一次。 - before_request
在每次请求前运行的函数 - after_request
如果没有抛出错误,在每次请求后执行 - teardown_request
注册在每一个请求的末尾,不管是否有异常,每次请求的最后都会执行。
Django的中间价,一共有5种
中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。
- process_request
执行路由之前被调用,在每个请求上调用, 返回None或HttpResponse对象 - process_view
调用视图之前被调用,在每个请求上调用, 返回None或HttpResponse对象 - process_template_response
该方法对视图函数返回值有要求,必须是一个含有render方法类的对象,才会执行此方法 - process_exception
只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象 - process_response
所有响应返回浏览器被调用,在每个请求上调用,返回HttpResponse对象
6. 序列化
答:把变量从内存中变成可存储或传输的过程称之为序列化。Python提供2个包,json和pickle
7. orm的理解
答:orm称之为对象关系映射,是描述对象和关系型数据库之间的一种规范。将对象映射到数据库的关系模型上
flask-sqlalchemy
pymysql
8. MTV/MVC
答:MTV和MVC是一种设计模式,flask框架是MVC模式,Django是MTV模式
M:Model:数据存储层,处理所有数据相关的业务,和数据库进行交互,并提供数据的增删改查;
T:Template:模板层(也叫表现层)具体来处理页面的显示;
V:View:业务逻辑层,处理具体的业务逻辑,它的作用是连通Model 层和 Template 。
M:Modle 代表数据存储层,是对数据表的定义和数据的增删改查;
V:View 代表视图层,是系统前端显示部分,它负责显示什么和如何进行显示;
C:Controller 代表控制层,负责根据从 View 层输入的指令来检索 Model 层的数据,并在该层编写代码产生结果并输出。
9. Mysql如何加索引,主键ID和普通索引的执行速度哪个快
答:
- create index index_name on table_name (colume_name)
- alert table table_name add index index_name(colume_name)
主键索引的查询速度快,是因为主键索引的叶子节点存放了主键ID和所有数据,而普通索引的叶子结点只存放了主键id,需要在去聚集索引查询得到所有数据
10. 如何保证实时数据
答:
1.可以采用定时任务的方式进行数据的实时更新
2.实时更新次数较少的可以使用while True方法不断的请求接口(轮询)
11. Elasticsearch优化
1. 设计阶段的优化
- 根据业务增量需求,可以采用基于日期模板创建索引
- 使用别名进行索引管理
- 每天定时对索引做force_merge操作,释放空间
- 采取冷热分离机制,热数据存储到SSD,提高索引效率,冷数据定期进行缩减操作,减少存储
- 仅对需要分词的字段设置分词器
- mapping阶段充分结合各个字段的属性,是否需要检索,是否需要存储
2. 写入优化
- 写入前设置副本为0,禁用刷新机制
- 采取批量写入
- 尽量使用生成的ID
查询优化
- 禁止批量terms
- 充分利用倒排索引机制,能keyword就尽量kerword
- 设置合理的路由机制
- 数据量大的时候,可以先基于时间敲定索引在检索
部署优化
- 使用集群
- 部署机器配置尽量在64G内存大小
12. List等数据结构实现原理
13. 字典如何排序,有序字典的使用
答:
版本3.7的变化:确保字典顺序是插入顺序。这种行为是来自CPython3.6版本的一个实现细节
因此,列表和字典基本的区别,只能是字典的值是通过键进行访问的,键可以是任何不可变类型,而列表值是使用整数进行索引的,只有这点区别了。
版本3.6的方法有:
1.纯字典排序
14. Redis项目中的使用场景
15. celery+redis的原理
- 当发起一个 task 时,会向 redis 的 celery key 中插入一条记录。
- 如果这时有正在待命的空闲 worker,这个 task 会立即被 worker 领取。
- 如果这时没有空闲的 worker,这个 task 的记录会保留在 celery key 中。
- 这时会将这个 task 的记录从 key celery 中移除,并添加相关信息到 unacked 和 unacked_index 中。
- worker 根据 task 设定的期望执行时间执行任务,如果接到的不是延时任务或者已经超过了期望时间,则立刻执行。
- worker 开始执行任务时,通知 redis。(如果设置了 CELERY_ACKS_LATE = True 那么会在任务执行结束时再通知)
- redis 接到通知后,将 unacked 和 unacked_index 中相关记录移除。
- 如果在接到通知前,worker 中断了,这时 redis 中的 unacked 和 unacked_index 记录会重新回到 celery key 中。(这个回写的操作是由 worker 在 “临死” 前自己完成的,所以在关闭 worker 时为防止任务丢失,请务必使用正确的方法停止它,如: celery multi stop w1 -A proj1)
- 在 celery key 中的 task 可以再次重复上述 2 以下的流程。
16. Mysql锁的机制,隔离级别
答:隔离级别一共有4种,分别是读未提交,读提交,可重复度,串行化
锁根据粒度来分:行锁,表锁,页锁
根据加锁机制来分:悲观锁和乐观锁
根据兼容性:共享锁和排它锁
17. flask底层原理,上下文管理
18. 设计模式有哪些
答:
单例模式:是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个是实例时,单例对象就能派上用场。单例对象的要点有三个:一是某个类只能有一个实例;二是它必须自行创建整个实例,三是它必须自行向整个系统提供这个实例。
工厂模式:提供一个创建对象的接口,不像客户端暴露创建对象的过程,使用一个公共的接口来创建对象,可以分为三种:简单工厂、工厂方法、抽象工厂。一个类的行为或其算法可以在运行时更改,这种类型的设计模式属于行为型模式。
策略模式:是常见的设计模式之一,它是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。换句话来讲,就是针对一个问题而定义出一个解决的模板,这个模板就是具体的策略,每个策略都是按照这个模板进行的,这种情况下我们有新的策略时就可以直接按照模板来写,而不会影响之前已经定义好的策略。
门面模式:门面模式也被称作外观模式。定义如下:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。门面模式注重统一的对象,也就是提供一个访问子系统的接口。门面模式与模板模式有相似的地方,都是对一些需要重复方法的封装。但本质上是不同的,模板模式是对类本身的方法的封装,其被封装的方法也可以单独使用;门面模式,是对子系统的封装,其被封装的接口理论上是不会被单独提出来使用的。
19. 慢查询定位
20. 装饰器是什么,执行顺序
答:装饰器本质上是一个函数,其目的是在不改变其原有函数的功能和代码下,对这个函数新增额外功能。其原理是利用闭包。
所谓闭包就是函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,就称之为闭包
执行顺序:越靠近函数底部的越先执行,原则就是先近后远
21. Mysql和Postsql有什么区别
Postgresql相比MySQL的优势
- 在SQL的标准实现要比MySQL完善,而且功能实现比较严谨
- 存储过程的功能比MySQL要好,具备本地缓存执行的能力
- 对表连接支持比较完整,优化器的功能比较完整,支持索引类型较多
- PG主表采用堆表存放,MySQL采用索引组织表,能够支持比MySQL更大的数据量
- PG的主备复制属于物理复制,相对比MySQL基于binlog的逻辑复制,数据的一致性更加可靠,性能较高
- MySQL存在锁机制影响并发问题,而PG不存在
MySQL相对PG的优势
- innodb的基于回滚实现MVCC机制,相对PG是基于XID的MVCC是占优的,速度高于PG
- MySQL采用索引组织表,这种存储方式非常适用于主键匹配的增删查改
- MySQL的优化器比较简单,系统表,运算符实现都很精简,非常适合简单的操作
PG具备更高的可靠性,对数据一致性完整性支持高于MySQL;而MySQL查询速度较快,更加适用于逻辑相对简单,数据可靠性向要求较低的情况。
22. Gunicorn和uswgi的区别
23. Python远程调用ssh的方式
答:paramiko依赖包
24. 查看Linux目录下占用内存最多的目录命令
答:du -ah --max-depth=1
25. ssh登录的端口是多少
答:22
26. docker如何查看进程
答:docker top 进程
27. 项目中MySQL的优化是有哪些
一. 设计上的优化
1. 用多个小表代替一张大表
2. 批量插入代替单条插入
3. 合理控制缓存空间
4. 字段设计上尽量遵循小而简单的原则
二. 查询上的优化
1. 尽量避免全表扫描,避免使用select * 查询
2. 对需要频繁查询对字段建立索引, 索引的创建需要满足原则,比如长索引对建立需要满足最左前缀原则等
3. 避免多个范围条件的查询
4. 对limit的优化,用 id > 10000 limit 10 代替 limit 10000, 10,因为这样前10000条就不会被查询
5. 尽量不要在where 后面加上 or 查询,这样会放弃索引而采用全表扫描
6. 尽量避免在where后面对字段进行表达式计算
28. 套接字
29. flask和Django的区别
30. I/O多路复用
答:多路指多个tcp链接,复用指单个线程或者是少数线程
31. Redis过期策略的理解
过期策略:
1. 定期删除:每隔一段时间执行删除过期key的操作,通过限制删除的时长和频率,来减少删除操作对CPU时间的占用。优缺点介于定时删除和惰性删除中间。
2. 惰性删除:过期的时候不删除,只有在查询的时候判断key是否过期。若过期,则删除。优点在于只取当前key,节省cpu,缺点在于大量的key在超出过期时间后,并且很久一段时间内没有被获取,过期key占用了内存。
3. 定时删除:在给key设置过期时间的同时,设置一个定时器,在时间到来的时候,redis会自动删除过期的key。优点在于可以保证内存的过期key被删除,缺点在于过期key较多时会占用cpu的时间,影响性能
淘汰机制:
1. volatile-lru:淘汰所有已经设置过期时间最久未使用的key
2. volatile-random:随机淘汰所有已经设置过期时间的key
3. volatile-ttl:优先淘汰更早过期的key
4. allkeys-lru:淘汰所有key中时间最久未使用的key
5. allkeys-random:随机淘汰任意key
6. noeviction:不会淘汰key,当内存不足时,会抛出异常
7. volatile-lfu:淘汰设置过期时间了中最少使用的key
8. allkeys-lfu:淘汰整个键值对中最少使用的key
32. auth协议
33. 数据库的三范式
- 字段不可分。
- 有主键,非主键字段依赖主键。
- 非主键字段不能互相依赖。
34. docker打包python镜像
- 打包python依赖包:pip freeze > requirements.txt
- 编写dockerfile文件,格式如下
# 基于的基础镜像
FROM python:3.9.6
# 维护者信息
MAINTAINER name chy@163.com
# 将docker_test目录下的代码添加到镜像中的code文件夹(两个目录参数中间有空格分开)
ADD ./docker_test /code
# 设置code文件夹是工作目录
WORKDIR /code
# 安装支持
RUN pip install -r requirements.txt
#docker运行时即运行app.py文件
CMD ["python","/code/app.py"]
35. 制作镜像
docker build -t imagename Dockerfilepath
docker build -t d_t .
36. 运行镜像
docker run -t -i d_t
37. websocket编程
答:websocket是一种在单个TCP连接上进行全双工通信的协议。websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在websocket API中,客户端和服务端只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
原理:websocket协议需要通过握手协议建立的TCP连接来传输数据,具体实现是通过HTTP协议建立通道,然后在此基础上用真正websocket协议进行通信。
websocket的特点:
- 建立在TCP协议之上,服务端实现比较容易
- 与HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用HTTP协议,因此握手时不容易被屏蔽,能通过各种HTTP代理服务器
- 数据格式比较轻量,性能开销小,通信高效
- 可以发送文本,也可以发送二进制数据
- 没有同源策略,客户端可以和任意服务器通信
- 协议标识符是ws(ws://example.com:80/some/path)
Websocket和HTTP的区别
相同点:
- 都是一样基于TCP,都是可靠协议,均属于应用层协议
不同点: - websocket在建立时,数据是通过HTTP传输,但是在建立之后,真正传输数据时候是不需要HTTP协议
- websocket是双向通信协议,模拟socket协议,可以双向发送或接受信息,而HTTP是单向的
- websocket是需要浏览器和服务器建立连接的,而HTTP是浏览器发起向服务器的连接
- HTTP只能推送静态资源,而websocket可以推送指定信息
websocket如何判断离线:
答:当客户第一次发送请求至服务端时会携带唯一标识,以及时间戳,客户端到DB或者是缓存到去查询该请求的唯一标识,如果不存在就回存入DB或者缓存中,第二次客户端定时再次发送请求依旧携带唯一标识等信息去查询该请求的唯一标识,如果存在就把上次的时间戳取出,使用当前时间戳减去上次时间戳,得出的毫秒秒数判断是否大于指定时间,如果小于的话就是在线,否则就是离线
38. git中的merge和rebase的区别
答:merge和rebase都是用来合并代码,最终结果都是一样的。
merge:会多出一次merge commit操作,也就是说把公共分支和你当前的commit 合并在一起,形成一个新的 commit 提交.
rebase:始终把最新的修改放到最前头,把当前分支的 commit 放到公共分支的最后面,只会有一次commit.
39. docker 中的add和copy的区别
add:包含copy的操作,但是add能拷贝远程url的文件到镜像中,且将这些资源添加到镜像镜像的文件系统
copy:copy不能拷贝远程的文件,且将这些资源添加到容器的文件系统中。
40. 排序算法有哪些,python中sorted排序的原理
41. 协程和线程的优缺点
42. 索引失效的情况
- 查询条件有or,即使其中部分条件带索引,也不会使用
- 对于复合索引,如果不使用最左前缀索引,后续类也无法使用
- like查询以%开头(以%结尾时索引未失效)
- 存在索引列的数据隐式转换,则用不上索引
- where子句中使用数学运算、函数
- MySQL估计使用全部扫描比使用索引快,则不使用索引
43. 什么情况下不推荐使用索引
- 数据唯一性差(一个字段取值只有几种)的字段不使用索引
- 频繁更新的字段不要使用索引
- 字段不在where出现时不添加索引
- where子句里对索引列使用不等于情况
44. 高并发的解决方案
45. 三次握手和四次挥手
46. JWT的实现原理
答:JWT全程是JSON WBB TOKEN,其本质是把用户信息通过加密后生成得到一个字符串,是一种基于token的访问策略,可以克服跨域问题,一共有三部分组成,分别是头部,载荷,密钥组成
1. 头部:是一个描述JWT元数据的json对象,格式如下
{
"alg": "HS256",
"typ": "JWT"
}
# alg属性:表示签名用的算法,默认为MHAC SHA256(写为HS256)
# typ属性:表示令牌的类型。JWT默认
2. 载荷:是JWT的主体内容,也是JSON对象,包含需要传递的数据,七个默认字段+自定义私有字段,字段如下
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:定义在什么时间之前,该jwt都是不可用的.
iat:发布时间
jti:JWT
3. 签名:是一个签证信息,有三部分组成
1. header(base64之后)
2. payload(base64之后)
3. secret
47. GIL详解
1. 定义:GIL称之为全局解释器锁,是解释器中一种线程同步的方式,是CPython解释器的实现特性。对于每一个解释器进程都具有一个GIL,它的直接作用是限制单个解释器进程中多线程的并行执行,使得即使在多核处理上对于单个解释器进程来说,同一时刻运行的线程仅有一个。
2. 有了GIL锁是否还需要关注线程安全:答案是肯定的,首先GIL保证每一条字节码在执行过程中的独占性,即每一条字节码的执行都是原子性。GIL具有释放机制,所以GIL并不会保证字节码在执行过程中线程不会进行切换。GIL和线程互斥锁的粒度是不同的,GIL是Python解释器级别的互斥,保证的是解释器级别共享资源的一致性,而线程互斥锁是用户级别的互斥,保证的是Python程序级别共享数据的一致性。所以我们仍需要线程互斥锁及其他线程同步方式来保证数据的一致。
3. 在GIL中如何提高性能
1. 在IO密集型任务下,我们可以使用多线程或者协程来完成
2. 可以选择更换Jython等没有GIL的解释器(**不推荐使用,因为会错过众多C语言模块的特性**)
3. 使用多进程来代替多线程
4. 将计算密集型任务转移至Python中的C/C++模块