面试常见题目汇总

投简历网站

拉勾招聘,前程无忧,boss,等

.
.
.
.
.
.

git 使用注意事项


git账号密码
邮箱:965493842@qq.com
账号:tengjincheng      密码:tengyifan888

从别人的仓库clone下来的代码,你要推到你远程仓库,要先把远程仓库的地址,
改成你要推的你的远程的空仓库的地址
或者你直接把别人的代码forked 你的仓库里,然后你再从你的仓库里面复制clone地址
到你的本地克隆下来,添加代码后,就可以直接提交了,直接提交到你的远程仓库了
就和别人仓库里面的代码没关系了
---------------------------------------------------

如果是一个本地的项目,想要推到你的远程gitee仓库去,要先建出一个空仓库出来
复制对应的网址
本地仓库往上推前,要先设置远程仓库,才能推到远程仓库
git remote add teng https://gitee.com/tengjincheng/luffy_api.git
# teng是 你给远程仓库起的名字   后面的网址就是该仓库名要绑定的远程仓库的网址

git push 远程仓库名字 对应的分支名字
就把本地对应分支的代码,推到远程仓库对应分支上去了

---------------------------------------------------
---------------------------------------------------

.
.
.

git 命令汇总

# 常见git命令
git init  # 初始化仓库
git status  # 查看文件变化 (只能看暂存区和工作区)
git add .   # 当前路径下所有文件变更都提交
git commit -m '注释'   # 把暂存区所有内容,提交到版本库

git reset --hard 版本号   # 回到某个版本

git branch  # 查看分支
git branch 分支名   # 创建分支
git checkout dev  # 切换到dev分支

先来到master分支: git checkout master
执行合并命令:git merge dev   # 把本地dev合并到我本地(master)上
# ( 也可以本地的dev分支往远程的dev上推,在远程点pull Requests 也可以合并 )

git remote add 远程仓库名字 仓库网址   # 设置远程仓库的名字
git push 远程仓库名字 对应的分支名字  # 将本地对应分支的代码,推到远程仓库对应分支


git clone 网址  # 把远程仓库的代码拉到本地仓库
# 还可以用pycharm 点击vcs,点击get from version control ,
# 把远程仓库网址粘贴到url里面 点击clone 也可以直接克隆下来

-----------------------------

# 出现冲突的原因
	1 多人在同一分支开发冲突, 一般发生在将远程仓库代码pull拉下来的时候
	2 分支合并出现冲突,自己确定要保留主的还是分支的,再提交代码就行了

# 首先为什么要pull往下拉代码,因为你一开始从远程仓库克隆的代码
# 在加代码的过程中,可能别人也在一直往master主分支里面贡献代码
# 当你想将代码提交到远程仓库的时候,可能master已经被别人升了好几版了
# 这个时候你的master已经不是最新的了,你推不上去的!!!
# 所以要你先将远程仓库最新的代码pull下来,自动与自己的代码合并
# 然后你才能再往远程的仓库上推!!!

你如果想少出现这种冲突,勤拉取代码 git pull origin master或者dev
保证自己代码是最新的

分支合并出现冲突,自己确定要保留主的还是分支的,再提交代码就行了

----------------------------------------------------

# 来到公司后,领导给把你拉到了公司的git仓库后,你大致要做的事情
1. 先从仓库里面把项目克隆到本地的dev分支上
2. 安装项目所需的依赖模块
3. 运行起项目文件
4. 编写代码,提交到远程仓库,如果有冲突解决冲突

----------------------------------------------------

.
.
.
.

sql语句查询


# 1
# 给定一个员工表(employes)和一个部门表(departments),编写-个SQL查询语句,
# 列出每个部门的部门名称department_name, 及该部门的员工数量(employee count)。

SELECT d.department_name, COUNT(e.employee_id) AS employee_count
FROM departments AS d
LEFT JOIN employees AS e ON d.department_id = e.department_id
GROUP BY d.department_name;

# 为什么要以部门表为基准,去左连接员工表,目的就是连表后,
# 让没有绑定部门的员工数据不显示,两表连接后,再按部门分组
# 最后求每一个部门分组里面的员工数量

--------------------------------------------------------------------

# 2 查询所有学生的所有科目的所有成绩
先理下最合理的表结构,学生表放学生的一些字段,比如年龄性别等。课程表里放课程的名称,老师姓名等信息
最后分数表里面放 学生id 课程id 分数等信息


select Student.name,Course.name,Grade.score
from Student
inner join Grade on Student.id = Grade.student_id
inner join Course on Course.id = Grade.Course_id


# 3 查询所有学生的学号、姓名、选课数、总成绩
SELECT student.学号, name, count(score) AS class_num ,SUM(score) AS total_score
FROM score LEFT JOIN student ON( student.学号 = score.学号)
GROUP BY score.学号
ORDER BY SUM(score) DESC

# 以分数表作为基准,左连接学生表,大表里面就会去掉没有成绩的学生了
# 再以分数表表里面的学号分组,对分数计数,对分数求和

.
.
.
.
.

django的orm语句


查询年龄大于18的用户数据
# res = models.User.objects.filter(age__gt=28)
# res = models.User.objects.filter(age__gt=28).values('name','age')
# print(res)


大于等于__gte       小于等于__lte        equal 等于
    # res = models.User.objects.filter(age__gte=18)
    # res = models.User.objects.filter(age__lte=38)


查询年龄是18或者28或者38的数据  __in=()  成员运算
    # res = models.User.objects.filter(age__in=(18, 28, 38))


查询注册年份是2022年的数据  __year=2022
# res = models.User.objects.filter(register_time__year=2022)


针对一对多 插入数据可以直接填写表中的实际字段
    models.Book.objects.create(title='三国演义', price=888.88, publish_id=1)


针对一对多 插入对象也可以填写表中的类中字段名
    publish_obj = models.Publish.objects.filter(pk=1).first()
    models.Book.objects.create(title='水浒传', price=555.66, publish=publish_obj)

所以如果等于号前面是表中的实际字段,那等于号后面就要写一个具体的数数据!!!
如果等于号前面是模型表中的字段名(也就是没自动加_id前的样子),那等于号后面就要写一个对应的对象!!!

------------------------------------
注意:当对象点虚拟的外键字段名的时候,比如书籍对象点虚拟外键字段authors 时:
如果如果后面跟的是  add set remove clear  则是操作第三张虚拟关系表!!!
当你使用其他方式 比如 all filter 等的时候则是操作的是 对应的作者表Author!!!
只有对象点字段名后跟的是add set remove clear 才会跳到对应的那张虚拟表去!!
-------------------------------------

# 跨表查询
mysql的跨表查询有两种方法,一种是子查询,一种是连表操作 ,ORM也类似

正向查询:由外键字段所在的表数据查询关联的表数据    正向查询
谁手上有外键字段,查别人就是正向查询!!!

---------------------------------

ORM跨表查询的口诀(极其重要!!!)
	正向查询按外键字段名
	反向查询按表名小写加_set

---------------------------------

# ORM的跨表查询分两种,

一种是基于对象的跨表查询,通过点的方式
	正向查询按外键字段名
	反向查询按表名小写加_set

---------------------------------------------------

一种是基于连表的跨表查询,通过__杠杠的方式
	正向查询按外键字段名
	反向查询按表名小写(没有_set!!!)  别搞混了!!!


# 基于对象的跨表查询
1.查询主键为1的书籍对应的出版社名称   正向查询
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish.name)

2.查询主键为4的书籍对应的作者姓名   正向查询
book_obj = models.Book.objects.filter(pk=4).first()
print(book_obj.authors.all())   # QuerySet[作者对象1,作者对象2]
print(book_obj.authors.all().values('name'))

3.查询jason的电话号码   正向查询
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.author_detail.phone)



查询北方出版社出版过的书籍     # 反向查询
publish_obj = models.Publish.objects.filter(name='北方出版社').first()
print(publish_obj.book_set.all())  # Queryset[书对象1,书对象2,]
print(publish_obj.book_set.all().values('name'))



查询jason写过的书籍
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set.all())  # Queryset[书对象1,书对象2,]
print(author_obj.book_set.all().values('name'))


-------------------------------------------------

# 基于连表的跨表查询
1.查询主键为1的书籍对应的出版社名称    # 正向查询
res = models.Book.objects.filter(pk=1).values('publish__name','title')
print(res)  # 结果是个queryset列表里面套了一个字典,对应value里面的字段信息

res = models.Book.objects.filter(pk=1).values('publish__name','title').first()
print(res)  # 就直接是个字典了


values()括号里面就是要写字段名,可以写自己表里面的字段名,也可以写关联表里面的字段名!!
通过在书表里面的外键字段publish,拿关联表里面的对于name字段的数据

# 而且要注意最后字典里面的键是'publish__name'   values里面写啥,字典里键就是啥!!!

.
.
.
.

当在浏览器输入一个地址,按下回车发生了什么


# 1 将域名地址解析为 ip 地址。 先从本地dns,没有再到根域名服务器,再没有再往上找
# 2 TCP 三次握手  三次握手的目的是为了确认双方的接收能力和发送能力是否正常。
# 3 发送请求  页面将请求内容以请求报文形式发送给服务器,由服务器分析处理。
# 4 返回数据  当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应
# 5 页面渲染  浏览器会根据 html 文件 渲染出页面出来
# 6 TCP 四次挥手
四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代表不能再向对方发送数据


.
.
.
.
.
.
.

is” 和“==”的区别

Python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。

 ==  是用来比较对象的数值(内容)的
 is  是用来比较对象的身份的(内存地址的)  它是以id来判断的

.

python 小整数池


# 作用:节省内存,提高解释器执行效率

数值比较小的整数,可能在程序中会非常频繁地使用,就需要频繁的申请内存空间

为了避免因创建相同的值,而重复申请内存空间所带来的效率问题,使用了小整数池技术。
不同类里面,变量的值只要一样,内存地址就一样
如果超过[-5,256]范围外,不同类里面,变量的值一样,内存地址也不一样,会重新申请内存的

但是pycharm有优化,扩大了小整数池的范围了
字符串也创了一个池,值在池范围内的,即使在不同类里面,内存地址也是一样的!!!
具体范围不知道

.
.
.
.

什么叫偏函数 /'pɑrʃəl/

# 通过partial类实例化,传入原函数与原函数的部分参数,此时生成的对象可以看出是偏函数
# 然后再通过对象加括号,触发双下call方法,传入剩余参数  所得结果
# 和原函数一次性传所有值运行的结果一样

# 偏函数作用: 实现原函数分步传值的效果

from functools import partial
def add(a,b,c):
    return a+b+c

add888=partial(add,2)
print(add888(3,4))

.
.
.

字符编码理论


# 最开始美国的码    1个字节的二进制数字表示一个字符
    ASCII码:记录了英文字母跟数字的对应关系 用8bit(1字节)来表示一个英文字符
----------------------
# 后来各国自己的码  GBK 2个字节的二进制数字表示一个字符
    比如中国的GBK码:用至少(2字节)来表示一个字符
----------------------
# 全球通用的码
    unicode万国码是字符的一种编码方式 所有的语言的每一个字符都有一个对应的数字
    所有语言的字符都有一个唯一的Unicode编号

    可变长字符编码系列 utf8(常用) utf16

    解码的时候,如何知道是按一个字节还是两个字节,还是三个字节解码?
    每一个字节开头有个标志位
    0 代表1个字节  对应一个字符
    110 代表两个字节  对应一个字符
    1110 代表三个字节  对应一个字符
    所以只要在解码的时候,先根据开头的数字,判断出要解码字符是用几个字节组成的
    然后再根据后面对应的数字去解码
----------------------------------------------------

# utf-8由于它的长度不固定,使得在内存处理字符变得复杂。
解决策略是:
在内存中存储字符时还是使用unicode编码,内存处理方便。
而在文件的存储中,则使用utf-8编码,可以压缩内存,节省空间。

即从文件中读取utf-8编码到内存时,会自动转换为unicode编码,
而从内存中将字符保存到文件时,则自动转换为utf-8编码。
----------------------------------------------------

# unicode是字符的一种编码方式,但没规定变成的二进制数字怎么存储
# utf-8是unicode的具体存储形式!!!

# 用4字节形式表示一个字符  4字节形式也称UTF-32  是静态的,而且是固定长度的
# 但用4个字节来表示一个字符,很浪费内存,这也是为什么会有utf8的原因!!!

如果第一个字节(leading byte)的最高三位是110,那么表示这个字符占2个字节,第二个字节的最高2位是10
蓝色部分的数字组合在一起,就是实际的码位值。
image
.
假如要表示的字符,其码位值是413,那么就表示如下,
image
.
.
.

vue常见语法总结



.
.
.

#1  面试官要看你项目
	- 编码水平
    - 公司的看不了,给他看的,全是个人项目
    - 公司项目看不了,签了保密协议


#2  数据库如何处理的?
	-云数据库:阿里云数据库,花钱,买服务---》账号和密码---》公司不需要自己搭建mysql
    	-mysql
        -redis
        -mongodb
    -自己的数据库,部署在云服务器上的数据库,是你自己的
    
    
# 3 你用过什么云产品?
	- 阿里云的ecs,服务器
    - 阿里云的oss ,对象存储
    - 云短信
    - 七牛云 文件存储
    
    
# 4 看看你的数据库
	-配置文件 dev.py  连的是本地127.0.0.1
    -你们上线怎么弄
    	-我不知道,就是给我一个地址,端口,用户名密码
        
    - 上线的数据库服务和项目服务 是在同一台及其上吗?

# 5 celery用过
	定时任务
    定时
    异步任务
    
    
    
# 6 多线程用过
	-用过
    -怎么用的?两种方式
    	-类实例化得到对象Thread类 传入target 任务函数,对象.start
        -写一个类,继承Thread,重写run方法,写任务  类实例化得到对象  对象.start
    -如果io密集型,用多线程,计算密集型用多进程 ---》只针对于cpython
    
    
# 7 mysql 用过,索引你知道吗?
	唯一索引和联合索引有什么区别
    
    
#8 1千万w,性别字段(男女),查询时候,这个字段要不要加索引
	-因为它就两种状态,建立索引是没用的,即便建立索引,也不会走
    
    


# 9 实现踢下线
	-session机制---》表中把它那条记录删除
    -token机制----》
    	-下线人id----》放个位置
        -进入到认证类中---》

# 10 实现黑名单功能
	-建立个黑名单表
    	-id  用户id ,ip,时间。。。

.
.
.

什么是 深浅拷贝

# 浅拷贝  是会将对象的每个属性进行依次复制,
         当对象的属性值是不可变类型时,复制的是对象的值
         但是当对象的属性值是可变类型时,复制的是对象的引用

# 深拷贝 会复制出一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象。

所以 对于不可变类型来说,深浅拷贝都是一样的,重新复制一份原数据,和原对象不共享内存地址

对于可变类型来说,
    深拷贝还是完全复制出一个新的数据与原数据不共享内存地址
    浅拷贝则是复制的是引用,与原数据共享内存地址!!!


----------------------------------------------
# 双下new与双下init的区别
new 是类加括号初始化的时候,生成一个空对象
init 是类加括号初始化生成一个空对象,并在将对象实例化的时候,触发

----------------------------------------------
# python的可变数据类型与不可变数据类型是什么
可变数据类型是数据值发生变化,内存地址不变  例如 字典,列表,集合,及文件对象
不可变数据类型是 数据值发生改变,其内存地址发生改变  例如 整型,浮点型,字符串,元组,布尔

----------------------------------------------
# 什么是生成器? 什么应用场景
函数内部写有yield关键字,调用该函数时,不会运行该函数的代码,会返回一个生成器。

需要用该生成器点next()方法,才能运行该生成器,并在运行到yield时,会暂停,
并返回当前的迭代值,当生成器再次点next()方法时,会从原来 暂停 的地方继续执行,
直到再次遇到 yield  并返回此次的迭代值
实现的效果就是,一边循环一边产生数据。

生成器的作用,可以获得庞大的数据,同时占用较小的内存。


.
.
.

什么是闭包函数?


def outer():
    x=1
    def inner():
        print(x)
    return inner

# 定义在函数体内部的函数,并且该内部的函数对外部名称空间的变量有引用
# 该函数就叫闭包函数

---------------------------------------------------

# 1 数据库三大范式是什么
第一范式(1NF)是指在关系模型中,数据库表的每一列都是不可分割的原子数据项,
第一范式主要是保证数据表中的每一个字段的值必须具有原子性,每个字段的值是不可再拆分的最小数据单元


第二范式(2NF)的要求是满足第一范式,且非主键字段,必须完全依赖于主键,
     如果表是复合主键,非主键字段不能仅依赖主键的一部分


第三范式(3NF)的要求是满足第二范式,且表中的非主键列必须和主键直接相关而不能间接相关。
也就是说数据表中的所有非主键字段不能依赖于其他非主键字段.
这个规则的意思是所有非主属性之间不能有依赖关系,它们是互相独立的

------------------------------------------------------

范式的优缺点:
优点:数据的标准化有助于消除数据库中的数据冗余

缺点:
1 降低了查询效率,因为范式等级越高,设计出来的表就越多,进行数据查询的时候就可能
需要关联多张表,不仅代价昂贵,而且可能会使得一些索引失效

2 范式只是提出设计的标标准,实际设计的时候,我们可能为了性能和读取效率违反范式的原则,
通过增加少量的冗余或重复的数据来提高数据库的读取性能,减少关联查询,实现空间换时间的目的

------------------------------------------------------

第一范式(1NF)
image
.
image
.
.
第二范式(2NF)
image
image
.
.
第三范式(3NF)
image
.
.
.
.
.

事务的特性和隔离级别


事务的四大特性:acid   a原子性,c一致性,i隔离性,d持久性
	# 原子性(Atomicity):数据库把“要么全做,要么全部做”的这种规则称为原子性
	# 隔离性(Isolation):事务之间相互隔离,不受影响,这与事务的隔离级别密切相关
	# 一致性(Consistency):事务执行前后的状态要一致,可理解为数据一致性
	# 持久性(Durable):事务完成之后,她对数据的修改是永恒的,即时出现故障也能够正常保持


当在执行事务的时候,在未提交的状态下,只是改了内存的数据,硬盘的真实的数据还没改了!!
一旦执行了事务的二次确认,会将内存中改的数据同步到硬盘中,

---------------------------------------------

隔离级别:
1.  read uncommitted(未提交读,或者叫读未提交)
  读未提交隔离级别:事务B读取到了事务A未提交的数据
  导致:脏读:读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,
  也就是可能最终不会存到数据库中,也就是不存在的数据。
  读到了并一定最终存在的数据,这就是脏读

---------------------------

2.  read committed(提交读,或读已提交)----大多数数据库系统默认的隔离级别
    读已提交隔离级别:
    一个事务从开始直到提交之前对数据库所作的任何修改,对其他事务都是不可见的。
    导致:
    不可重复读:在一个事务内,最开始读到的数据,和事务结束前的任意时刻
    读到的同一批数据出现不一致的情况。

    原因是:在第一个事务中的两次读数据之间,由于第二个事务提交了对于数据的修改

----------------------------

    3.  repeatable read(可重复读) ---------MySQL默认隔离级别
    可重复读隔离级别:
    事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,
    那么事务A再读该数据,读到的还是原来的内容。

    会导致幻读:
    幻读 一个事务在执行时,另一个事务插入了一条数据,从而导致第一个事务操作
    完成之后发现结果与预想的不一致,跟产生了幻觉一样。
    解决办法:mvcc机制加间隙锁

    # 间隙锁  假如 id=6的数据不存在,那么该查询语法就锁住了id=6所在的间隙了
    # 当对一个不存在的数据加锁后,默认就是锁定前后两条数据之间的区间
    # 当其他事务再尝试向该区间插入数据时,就会陷入阻塞,
    # 只有当持有间隙锁的事务结束后,才能继续执行插入操作。
    select * from `users` where id = 6 lock in share mode;

----------------------------

    4.  serializable(串行读) -------不会产生脏读与幻读,但是执行效率很低。
    强制事务串行执行,很少使用该级别,会导致执行效率很低

.
.
.
.
.
.

mysql有哪些索引类型,分别有什么作用


# 正确答案:
    # 聚簇索引,聚集索引,主键索引,主键,如果不存在主键,隐藏一个主键,构建聚簇索引
    # 辅助索引,普通索引 index
    # 唯一索引 unique
    # 联合索引,组合索引,多列索引:unique_to

.
.
.
.
.
.
.
.
.

补充 auth表是怎么加密密码,与校验明文密码是否正确的?

django 的auth  user表,密码是加密的,即便的同样的密码,密文都不一样
	-每次加密,都会随机生成一个盐

# 比如 auth表中的用户密码为
pbkdf2_sha256$260000$B9ZRmPFpWb3H4kdDDmgYA9$CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=

pbkdf2_sha256   加密方式
260000   固定数字
B9ZRmPFpWb3H4kdDDmgYA9   随机生成的盐

CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=
这个是用注册的密码和盐拼在一起然后用加密算法生成的字符串

当登录的时候,用户提交用户名与密码过来后,从数据库表里面拿出该用户的盐与提交过来的密码合在一起
,然后用加密算法再生成字符串,然后比较生成的字符串 与 数据库里面的该加密字符串
CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=  进行比较,对了就确认用户密码输入正确

.
.
.
.
.
小补充:

静态资源的存放路径比如:
<img src="/static/123.png" alt="">

static 前面一定不要忘了/ 斜杠  有了斜杠后该图片的绝对路径就变成了
http://127.0.0.1:5000/static/123.png   ip端口加相对路径了

static前面不加斜杠会导致,可能会导致图片无法正常显示!!!

.
.
.
.

什么是qps,tps,并发量,pv,uv


# qps:
Queries Per Second,每秒查询率,一台服务器每秒能够响应的查询次数

--------------------------------------

# tps:
Transactions Per Second,是每秒处理的事务数
该事务 包括一条消息入和一条消息出,加上一次用户数据库访问,整体叫一个事务数

--------------------------------------

# 并发量:
系统每秒钟可以处理的请求或事务的数量,反应了系统的负载能力

--------------------------------------

# PV(Page View):
页面访问量,即页面浏览量或点击量,用户每次刷新即被计算一次。
可以统计服务一天的访问日志得到

--------------------------------------

# UV(Unique Visitor):
独立访客,统计1天内访问某站点的用户数。
可以统计服务一天的访问日志并根据用户的唯一标识去重得到

--------------------------------------

# DAU(日活):
DAU(Daily Active User),日活跃用户数量。
常用于反映网站、app、网游的运营情况。DAU通常统计一日(统计日)之内,
登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似

--------------------------------------

# MAU(Month Active User):
月活跃用户数量,指网站、app等去重后的月活跃用户数量

.
.
.
.
.
.

什么是接口幂等性问题,如何解决?


# 幂等性:
多次操作,结果或者说效果是一致的

# 接口幂等性:无论调用多少次接口,产生的效果是一样的
	get请求     获取数据 天然幂等
	put请求     修改数据 天然幂等
	delete请求  删除数据 天然幂等
	post请求    新增数据,会出现不幂等的情况,需要把它做成幂等性的

-------------------------------------------------

# 解决办法:

1 唯一主键 unique  新增用户,用户名唯一,但是订单问题解决不了

2 token机制,随机字符串
	在使用新增数据接口之前,先发送请求到后端获取到token,
	比如在页面加载出来的时候,就先发送请求到后端,拿到token,
	并且同时后端服务器,保存该生成的token到redis数据库
	所以只要在该页面,调用新增接口,就让请求头携带随机字符串token,到后端,
	校验这个token在不在我们保存的redis里面,如果在,说明正常,
	删除token,允许新增。
	如果不在,说明新增操作已经执行过一次了,redis中的token串已经被删掉了
	就不允许新增!!!

3 前端:按钮只能点击一次

.
.
.
.
.

什么是 GIL锁 ?


1 什么是GIL锁 ?
	Global Interpreter Lock 全局解释器锁
	它的本质就是一个大的互斥锁,它是cpython的一个机制,GIL只存在于cpython解释器中
	它限制了同一个进程下,线程只有获取到了gil锁,才能执行,如果没有拿到gil锁,线程是不能执行的


2 gil锁的作用是什么 ?
	使得在同一进程内,任何时刻仅有一个线程在执行

-------------------------------------------------------------------

3 为什么要有GIL锁 ?
	不是为了保证数据安全的, 保证数据安全用互斥锁就可以了,gil锁不能保证数据安全!!

	python的垃圾回收机制,需要用一条垃圾回收的线程来做!!!
	如果同一时刻,一个进程下,有多个线程在同时执行,
	其他线程刚创建的数据还没来得及绑定变量,就可能被垃圾回收线程给回收掉 !!!
	所以用gil锁保证在同一进程内,任何时刻仅有一个线程在执行
	但后来随着多核的出现,导致python的多线程并不能利用多核

-------------------------------------------------------------------

.
.
.
.

python的垃圾回收机制是什么样的 ?


1 python的垃圾回收机制是什么样的?
	-高级一点的语言,为了保证内存的使用效率,都会有垃圾回收的机制。
	而python使用以下三种方式来做垃圾回收
-----------------------------------------------------

# 引用计数
	-有多少个变量名指向它,它的引用计数就为几,
	-当引用计数为0,说明没有变量名指向它了,这块内存空间就会被回收掉
	-引用计数存在的问题:循环引用问题,内存空间里面还有指向其他内存空间的变量名,导致无法回收


# 标记清除
	-是为了解决引用计数存在的循环引用问题的
	-第一阶段是标记阶段,它会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收
	-简单说就是   它会把循环引用的内存空间,打上标记,然后回收掉


# 分代回收
	把对象分为三代,一开始,对象在创建的时候,放在一代中,
	如果在一次一代的垃圾回收检查中,该对象存活下来,就会被放到二代中,
	放在二代中,说明该变量名被用到的概率,比放在一代中的变量名用到的概率高
	同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中,
	检查第一代中的对象的时间间隔最短,优先回收,第二代中的对象检查的时间间隔长一点
	第三代检查的时间间隔最长

----------------------------------------------------

.
.
.

解释为什么计算密集型用多进程,io密集型用多线程 ?

-----------------------------------------------------

1 解释为什么计算密集型用多进程,io密集型用多线程

	-由于GIL锁的存在,即便是多核机器,同一时刻,也只能有一个线程在执行
	-线程需要cpu去调度执行
	-如果开了多线程,是计算密集型,计算是消耗cpu的,
	假设是四核电脑,不能充分利用这四个核,只能有一个核在执行,开多线程没有用


	-而如果计算密集型,开了多进程,GIL是在cpython解释器进程中的,
	-在进程中开启线程执行计算,可以充分利用多核优势
	-开了一个进程,就开了一个cpython解释器的进程,就会有一个gil锁

	-由于GIL锁的存在,如果是计算密集型,开启多线程,不能利用多核优势,
	-开启多进程,可以利用多核优势

-----------------------------------------------------

2 即便是io密集型,用多进程是不是会显著提高效率?
	本身开启进程是非常消耗资源的,如果是io密集型,没有必要开多进程,并不会有显著的效率提升
	io密集型推荐使用多线程,io不耗费cpu,开启多线程,在一个时间段内是有并发效果的

.
.
.
.
.
.

为什么有了gil锁还要互斥锁


gil:全局解释器锁,线程要执行,必须先获得到gil锁,才能执行,
    保证了垃圾回收线程不会将其他线程正在使用的变量给回收掉

互斥锁:为了保证多线程并发操作数据(变量)的安全,而设置的锁,
保证在加锁和释放锁之间的代码,其他线程不能运行

gil本质也是一个大的互斥锁

--------------------------------------------------------

# 出现了数据错乱,出现了多条线程操作变量,出现的并发安全问题
a=0
线程1要计算: a += 1
    	1 线程1 拿到gil锁
    	2 读取a=0
    	3 假设时间片到了,释放gil锁,释放cpu
    	4 等待下次被调度执行
    	10 轮到它了,再获取gil锁
    	11 继续往下执行:计算 a+1
    	12 把结果赋值给a ,a=1    # 这地方就有问题了,按理应该是a=2  才是合理的,所以要有互斥锁
    	13 释放gil锁

线程2要计算: a += 1
    	5 线程2获得了gil锁
    	6 读取a=0
    	7 计算a+1
    	8 把结果赋值给a ,现在 a=1
    	9 释放gil锁

--------------------------------------------------

# 什么临界区?
    会出现并发安全问题的这段代码称之为临界区 ,所以要在临界区加锁

	# 加锁
	6 读取a=0
	7 计算a+1
	8 把结果赋值给a ,a=1
	# 释放锁

--------------------------------------------------

# 互斥锁保证数据安全
    a=0
    线程1要计算: a+=1
        1 线程1 拿到gil锁
        # 加互斥锁
        2 读取a=0
        3 假设时间片到了,释放gil,释放cpu
        4 等待下次被调度执行

        7 轮到它了,获取gil锁
        8 继续往下执行:计算a+1
        9 把结果赋值给a ,a=1
        10 释放gil锁

    线程2要计算: a += 1
        5 线程2获得了gil锁
        # 获取互斥锁,获取不到
        6 释放gil锁
        11 获得gil锁

        #加锁
        12 读取a=1
        13 计算a+1
        14 把结果赋值给a ,a=2   # 这样不同线程间的临界区代码,通过互斥锁,实现串行,保证数据不错乱
        15 释放锁
        16 释放gil锁

# gil锁并不锁住临界区,临界区需要我们自己 加互斥锁

.
.
.
.
.

进程,线程和协程 重要


进程:进程是资源分配的最小单位,一个应用程序运行,会至少开启一个进程
     比如qq程序右键运行后,会以一个进程的形势运行在内存里面
     一个qq可以有很多功能,原因是该进程里面有很多线程

线程:线程是cpu调度的最小单位,或者说是cpu执行的最小单位,一个进程下至少有一个线程


协程:单线程下实现并发效果,程序层面遇到io,控制任务的切换,从而不释放cpu

# 说白了进程就是线程的容器,线程是进程内部的执行流
# 进程之间是相互独立的,线程之间是共享资源的
# 操作系统只会为进程分配资源,不会为线程分配资源


# 注意开启子进程与开启子线程执行任务函数代码,   是一个异步操作!!!
# 主进程或主线程立马就往下执行了,是不会等子进程或子线程运行完了再往下执行代码的!!

----------------------------------------------------

代码如何实现
# 开启多进程两种方式
	-1 写一个类,继承Process,重写类的run方法---》实例化得到对象,对象.start 开启了进程
	-2 通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
    你在哪里用过


# 同理开启线程两种方式
	-1 写一个类,继承Thread,重写类的run方法---》实例化得到对象,对象.start 开启了进程
	-2 通过Thread类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
    你在哪里用过

------------------------------------
# 开启协程 重要!!!
	早期之前:借助于第三方gevent模块,该模块是基于greelet写的

	后来用 asyncawait 关键字,可以不借助于第三方模块,开启协程
		import asyncio      导入内置模块
		必须写在一个函数前, async def task()---->这个函数执行的结果是协程函数
		await 只要是io操作的代码,前面必须加 await

---------------------------------------------------

# 在哪用过
	我一般遇到计算密集型的操作,我会开多进程
	一般遇到 io密集型的操作,我一般开多线程
	爬别人数据,喜欢开多线程,因为爬虫 io居多

	程序中,异步做一件事情,也可以开多线程
		比如一个视图函数,要异步的把数据写的文件中
		异步的发送钉钉通知
		异步的发送邮件

	但实际上,在项目中,不需要我们开启进程线程,可以借助于第三方的框架比如celery就可以做异步的操作
	而celery的worker,就是进程线程架构

	django框架,是支持并发,我们没有开启多进程和多线程,
	但是符合uwsgi的web服务器在进入djagno框架之前,开启了进程和线程来执行视图函数

.
.
.
.
.

什么是鸭子类型

# 鸭子类型是  编程语言中动态语言中的一种概念
# 不需要显式的继承一个类
# 不去关心这个对象本质是什么,而是关心这个对象所具有的方法与属性。
l1 = [1, 2, 3]
print(len(l1))

class B:
    # 我们需要B这个类和它产生的对象也像列表一样也有一个长度怎么办?添加个方法试试
    def __len__(self):
        return 10

    def __iter__(self):
        return iter([1, 2, 3])

b = B()

print(len(b))   # 可以计算长度  结果是10

# 可以正常for循环
for i in b:
    print(i)

# 也就是说python解释器不会去检查发生多台的对象到底属于是什么类型
# 只会去检查有没有对应的方法,检查到了,就可以用
# 也就是说无论对象b到底是什么类型,只要我们给它添加想要的方法和属性
# 它就变成了什么

python 是鸭子类型的语言      java不是鸭子类型的语言

.
.
.

接口是什么意思


    接口就是在一个程序的类中实现的某一个功能,而这个功能有可以被其他的成员或者是对象所引用 。
    接口是规范或约束子类的行为,让这些子类属于同一类。可以简单的理解为父类

.
.
.
.
.
.
.
.
.
.

什么是http协议 与 https协议 有什么区别


HTTP是应用层协议
超文本传输协议( Hyper Text Transfer Protocol,HTTP )是一个简单的请求-响应协议,
它通常运行在TCP之上。

-------------------------------------------------

超文本传输安全协议 ( Hypertext Transfer Protocol Secure )
HTTPS 主要由两部分组成:HTTP + SSL / TLS

SSL  Secure Socket Layer 安全套接字层协议   用于为数据通信提供安全支持

TLS  Transport Layer Security 传输层安全协议
用于在两个通信应用程序之间提供保密性和数据完整性

--------------------------------------------------

# http和https的区别
	https=http+ssl/tls
	监听端口不一样, http是8080    https是443
	http明文传输   https密文传输

.
.
.
.
.

什么是WebSocket协议

----------------------------------------------------------------

WebSocket 是独立的、创建在 TCP 上的协议。
WebSocket 是一种在单个TCP连接上进行全双工通信的协议
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性
的连接,并进行双向数据传输。

----------------------------------------------------------------

# WebSocket出现的原因
很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的时间间隔(如每1秒),
由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。
这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求

在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

也是要先客户端发送请求,连接服务端,服务端响应连接请求后,不断开连接,这样
服务端就可以一直朝客户端发消息了!!!

----------------------------------------------------------------

.
.
.

并发编程里面的一些名词解释


semaphore信号量作用:  一次性产生多把互斥锁

Event事件作用:  实现了一个线程控制另一个线程代码的执行
当子进程或者子线程中有event.wait()代码,那么一定要等某一个地方执行了event.set()后,
event.wait()下面的代码才能继续正常运行!!!否则就卡在event.wait()这不运行了!!!


进程池的作用:提前创建好固定数量的进程供后续程序的调用,程序的数量超出,则需要等待
线程池的作用:提前创建好固定数量的线程供后续程序的调用,程序的数量超出,则需要等待


.
.
.

什么是猴子补丁,有什么用途


猴子补丁:在程序运行过程中,动态替换程序中代码的一种技术
-------------------------------------------
# 用途:

-1 比如咱们json模块,用内置的json,效率低,有一个第三方的ujson模块
	-不改变原来程序代码,把程序中所有json都替换成ujosn
	-在程序运行的入口处:import ujson as json
-------------------------------------------

-2 django中 pymysql的替换
    # 本来python2中 django中用来连接mysql的模块用的是mysqlDB  conn也是用mysqlDB类生成的对象
    # 但是python3中 mysqlDB用不了
    import pymsql
    pymysql.install_as_mysqlDB()  # 这句话执行完了后,原来的mysqlDB类生成的对象conn 替换成了
                                  # pymsql类生成的对象conn了
-------------------------------------------

-3
from gevent import monkey
monkey.pach_all()

gevent---》猴子补丁---》monkey.pach_all()---》动态的替换内部的会阻塞的代码,比如:
	-time
	-socket
	-把所有内置的会释放gil锁的模块,动态替换成gevent自己写的,不会释放gil锁的模块
-同步代码:io操作,会阻塞,会释放gil锁,这条线程就会释放cpu
-所以写了一个monkey.pach_all()方法
-动态的把所有同步的代码,都替换成异步代码,动态的替换所有会阻塞的代码
-异步代码,遇到io,不释放gil锁

.
.
.
.
.
.
.
.

描述 从浏览器输入一个地址,到看到页面信息,经历的过程


1 浏览器输入域名后,DNS解析域名,获取对应的IP地址与端口
(先从浏览器缓存、再从本机缓存、再从本机host文件、再从DNS服务器找 一级一级往上找)
如果都解析不到,页面就会报错。解析完后,向解析出的IP地址与端口,建立TCP连接,进行3次握手

2 浏览器向某个地址发送http请求

3 如果后端服务是使用nginx转发,nginx把http请求转发给web框架(django,flask等),进入框架

4 后端服务器以http响应的形式,返回给客户端浏览器

5 客户端浏览器把http响应体的内容展示在浏览器上,http响应的状态码与响应头内容不展示在浏览器页面上

6 四次挥手断开tcp连接---》http协议1.1版本有个keep-alive可以保持长连接,所以不一定会断开tcp连接

# 参考博客 https://blog.csdn.net/m0_52165864/article/details/126313277

.
.
.
.

什么叫 sql的迪卡尔积 ?


select * from emp2,dep2;  会将两张表中所有的数据对应一遍
就是无脑用一个表的所有行数据,分别与另一个表里面的每一行数据进行拼接,而形成的数据
假设emp2表里面有10行数据,dep2表里面有5行数据,这样连表查出来的表里面就有50条数据
可以简单地理解为一张表里面的每条数据,与另一张表里面的每条数据间的任意组合。
当两张表的数据很多的时候,所以这样连表操作,极其浪费资源!!!
所以用笛卡尔积的方式连表并不合理!!!

.
.
.
.

sql的 左连接,右连接,内连接,全连接 是什么?

有时候我们需要的数据不在同一张表中,就需要连表操作,但是用笛卡尔积的方式连表操作,及其浪费资源
所以有了 左连接,右连接,内连接,全连接 的连表操作了

left join 左连接:以左表为基准 展示左表所有的数据 如果右表没有对应项,则用NULL填充

right join 右连接:以右表为基准 展示右表所有的数据 如果左表没有对应项,则用NULL填充

inner join 内连接:以两张表共有的数据连表,左表与右表独有的数据都不要

union 全连接:以左右表为基准 展示所有数据 各自表没有的对应项,全部NULL填充

-----------------------------------
select * from emp inner join dep on emp.dep_id=dep.id;
emp inner join dep 将两个表连接在一起,on后面接的连接的依据!!!
-----------------------------------
select * from emp left join dep on emp.dep_id=dep.id;
以左表为基准 展示左表所有的数据,右表如果没有对应项则用NULL填充!!!
-----------------------------------
select * from emp left join dep on emp.dep_id=dep.id
union
select * from emp right join dep on emp.dep_id=dep.id;
左右表都为基准,展示所有的数据,各自没有的全部NULL填充!!!
# 实际上就是将左连接查出来的数据,与右连接查出来的数据,放到一起,做一个去重操作!!!
-----------------------------------

内连接以两张表共有的数据连表
image
.
左连接 以左表为基准 展示左表所有的数据 右表如果没有对应项则用NULL填充!!!
image
.
全连接
image
.
.
.

全连接的 union和union all的区别 ?


union就是全连接
主要区别在于,union会自动去除重复行,而union all不会 !!!
因此,如果你想要完全保留所有的行,包括重复的行,可以使用 union all
# 数据必须所有字段都一样才能算重复

select 出来结果,union,union all都是对结果进行合并,求并集
union 会去除重复的数据
union all 不会去除重复的数据

# 比如:
select name,id form user;
    id  name
    1   lqz
    2   zs

select name,id form book;
    id  name
    1   lqz
    2   西游记
    3   金瓶没

# 用union all 不会去重
select name,id form user union all select name,id form book;
    id  name
    1   lqz
    1   lqz
    2   zs
    2   西游记
    3   金瓶没
-------------------------------------------
# 用union会去重
select name,id form user union select name,id form book;
    id  name
    1   lqz
    2   zs
    2   西游记
    3   金瓶没

.
.
.

什么是socket

Socket是   HTTP协议所在的应用层 与 TCP/UDP协议所在的传输层 中间的一个  软件抽象层!!!

它把复杂的TCP/UDP协议的接口,隐藏在Socket简单的接口后面,这样用户只需要调用socket简单的接口
就可以调用 复杂的TCP/UDP协议的接口了

.
.
.

面向对象的三大特性,及使用场景

继承:表示类与类之间资源的从属关系   继承的本质为了节省代码
封装: 就是将数据和功能'封装'起来
多态: 一类事物的多种形态

继承的使用场景:
定义的一个类,该类里面需要调用使用父类里面的方法,
或者在父类方法的基础上再加一些新的功能,就要用到继承

封装的使用场景:
一般是当多个类里面有相同的方法或属性时,可以把这些共有方法与属性抽取出来,
封装到一个新的类里面,这样其他的类只需要继承该类就可以了

多态的使用场景:
比如 当多个子类继承同一个父类,并重写了该父类的方法,
     这样多个子类里面就具有相同的方法,但是每个子类里面该方法的执行代码却不一样

.
.
.
.
.
.

什么叫双写一致性,及如何保证? 重要!!!

解释:指的是数据库写操作 和 缓存更新操作 的一致性问题
# 从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案!!!

如果mysql中数据变了,缓存不会自动变化,就会出现数据不一致的问题
-----------------------------------------------------

# 不依赖于给缓存设置过期时间,也保证双写一致性的三种策略 :
	1 修改数据库,再删除缓存   # 下次请求来了,会再走一次数据库
	2 修改数据库,再更新缓存   # 下次请求来了,会直接走redis     不太推荐!!
	3 定时更新缓存 ----------实时性差

--------------------------------------------------
# 修改数据库,再删除缓存方案的优缺点:
优点:理论上来讲,是最好的方案
缺点:如果删缓存失败,也会出现数据不一致的情况
怎么解决:提供一个重试机制即可,比如起一个 while True 循环  加个计数  删除失败就继续再删
         并计数加1,计数设置个上限, 如果连续删除次数到了上限还没删除成功,通知后端并结束循环
         这时候就可能是redis出问题了,双写一致性就已经不是首要问题了!!!
--------------------------------------------------
# 修改数据库,再更新缓存方案的优缺点:

优点:比删除缓存的方案 当请求再来时,少走一次数据库
缺点:并发情况下可能会出现缓存脏数据,线程不太安全  比如
线程A更新了数据库
线程B更新了数据库
因为网络等原因,导致线程B先更新了缓存
线程A更新了缓存     # 这就导致了缓存出现脏数据了!!!可以加互斥锁解决但是影响性能了 需要权衡一下
还有一种情况就是: 假如业务操作是写多读少的场景,缓存就被频繁的更新,浪费性能 。
--------------------------------------------
# 定时更新缓存方案的缺点:实时性差

.
.
.
.
.
.

tcp 三次握手和四次挥手 重要!!!


# 补充:
	TCP是可靠连接,使用三次握手,四次挥手保证了可靠连接,数据不会丢失

	SYN:SYN=1 表示要建立连接
	FIN:表示断开连接
	ACK:ACK=1 表示我收到了
	seq:随机数,建立连接无论客户端还是服务端要建立连接就要要携带
	ack:回应请求就要加1返回

---------------------------------------------------

# 三次握手:建立链接

第一次握手:
客户端发送一个建立通信通道的链接请求
	客户端发送:喂(SYN=1),我是lqz(seq=随机数),并处于SYN_SEND状态
	服务端:没收到处于: listen 状态, 收到了处于: SYN_RCVD状态

-------------------------

第二次握手:
服务端确认了建立通信通道的链接请求,
	服务端发送:收到(ACK=1),lqz啊(ack=随机数+1),客户端到服务端的通信通道建立
               并接着发送一个  喂(SYN=1),我是刘亦菲(seq=另一个随机数)
               建立服务端到客户端的通信通道的链接请求  处于:SYN_RCVD状态
	客户端:没收到服务端返回的第二次:SYN_SEND状态,一旦收到就是established

-------------------------

第三次握手:
客户端确认了建立通信通道的链接请求
	客户端发送:收到(ACK=1),刘亦菲你好(ack=另一个随机数+1)
               客户端处于 连接建好的状态 established
	服务端:收到后,处于established

三次握手后,就建立了双向通道了!!!
-------------------------------------------------

# 大白话:三次握手   每一次发送 带的什么东西  一定要背出来
	第一次:客户端向服务端发送建立连接请求,携带一个随机数(SYN=1,seq=随机数)

	第二次:服务端回应客户端的建立连接请求(表示收到ACK=1, 并回应ack=随机数+1),
	服务端接着发送建立连接请求(建立连接SYN=1,加seq=另一个随机数)

	第三次:客户端回应服务端的建立连接请求(ACK=1,ack=另一个随机数+1)


# 洪水攻击:
    同一时间有大量的客户端请求建立连接 会导致服务端一直处于SYN_RCVD状态,服务端接收到了大量的syn请求,处于rcvd状态
-------------------------------------------------

图解
image
.
.
.
.

# 四次挥手:断链接

	第一次:客户端向服务端发起断开连接的请求(FIN=随机数) ,客户端不再发送数据了
	第二次:服务端收到后,回复这个请求(ACK=1,ack=随机数+1)
	第三次:服务端向客户端发起断开连接的请求(FIN=另一个随机数),服务端不再发送数据了

	第四次:客户端收到后,回复这个请求(ACK=1,ack=另一个随机数+1)


--------------------------------------------------
# 四次不能合并为三次 ?
	因为服务端接收到客户端的fin后,可能还有数据要发送给客户端!!!

客户端发来的FIN报文,仅仅表示自己不再发送数据了,但还是可以接受数据的。
所以服务端先发送ACK回应客户端,当服务端数据发送完了,或者不想发了,
再发送FIN报文给客户端表示关闭链接,因此,关闭链接时ACK和FIN是分开发送的。

--------------------------------------------------

image
.
.
.
.
.

osi七层协议,哪七层,每层有哪些


	osi七层:   应用层 表示层 会话层    传输层  网络层  数据链路层  物理连接层
	或五层结构:  应用层(三个合成一个)   传输层  网络层  数据链路层  物理连接层

---------------------------------------------

-应用层 表示层 会话层(应用层)
	-应用层协议:http,ftp,https,dns
	-表示层:https=http+ssl  的加密
	-会话层:负责建立、管理和终止表示层实体之间的会话连接

-传输层:
	-tcp协议
	-udp协议
	-端口:端口协议

-网络层	-ip地址协议

-数据链路层:
	-mac地址:以太网协议
	-数据帧:电信号的分组方式

-物理层:
	-物理介质,网线

-----------------------------------------

.
.
.

tcp和udp的区别? udp用在哪里了?


区别:
  TCP协议称之为流式协议、面向连接的可靠协议(数据不容易丢失)
  UDP协议称之为数据报协议、面向无连接的不可靠协议

# 用在哪里了?
-比如:
	udp:一些聊天工具,dns协议用的udp协议
	tcp:浏览器发送http请求     mysql或redis客户端与服务端通信


TCP我们可以打比方为打电话 你一句我一句
UDP我们可以看成是发短信 只要发了就行 不管对方看不看

.
.
.
.
.
.
.
.

web服务器到底是什么?


客户端(浏览器,app)   跟   服务端(web框架)之间的东西 ,又叫服务器中间件
----------------------------------------------

# nginx  apache  是一类东西,就是做请求转发
# uWSGI,wsgiref 是一类东西 针对于python的web框架
# tomcat, 针对java的web框架

----------------------------------------------

  浏览器--》--Nginx--》--web服务器--》--web框架

----------------------------------------------

.
.
.

WSGI uWSGI uWSGI, cgi, fastcgi 分别是什么?


# 大写的WSGI与小写wsgi是一个东西
# WSGI:(Python Web Server Gateway Interface)
	是为Python定义的 Web服务器 和 Web应用程序或框架之间的 一种通用的接口协议
	# 一句话总结: 为Python定义的 web服务器和web框架之间的接口标准

# 符合WSGI协议的web服务器有哪些:uWSGI  wsgiref  werkzeug等

----------------------------------------------
----------------------------------------------

# uWSGI:
    符合wsgi协议的web服务器,用c写的,性能比较高,咱们通常用来部署django,flask
    # 一句话总结:一个符合WSGI协议的 web服务器,
    # 处理发来的请求及返回响应。

----------------------------------------------
----------------------------------------------

# 小写的uwsgi
# uwsgi:
    是uWSGI服务器  自己的标准
    用于定义传输信息的类型,是用于前端服务器与 uwsgi 的通信规范
    # 1、一句话总结: uWSGI自有的一个协议

----------------------------------------------
----------------------------------------------

# cgi:(Common Gateway Interface/CGI)通用网关接口协议
    是一个用来  规定了web服务器(nginx,apache)到 请求处理程序(django,flask,
    等web框架)之间传输数据的标准

    所有bs架构软件都是遵循CGI协议的
    一句话总结:一个标准,定义了客户端浏览器与服务器之间如何传输数据

----------------------------------------------
----------------------------------------------

# FastCGI:(Fast Common Gateway Interface/FastCGI) 快速通用网关接口协议
    是一种让交互程序与Web服务器通信的协议。 FastCGI 是 CGI的增强版本

    常见的fastcgi服务器:Apache,Nginx,Microsoft IIS
    # 一句话总结:  CGI的增强版  从而使服务器可以同时处理更多的网页请求

----------------------------------------------
----------------------------------------------

.
.
.
.
.
.
.

什么叫上下文管理器? 如何自定制上下文管理器?


# 上下文管理器:

一个类,实现了__enter__和__exit__方法,这个类的对象就是上下文管理器对象
就可以使用with关键字,使用with关键字后,
执行with下面的代码前,先执行上下文管理器里面的双下__enter__方法
with下面的代码执行完后,再执行上下文管理器里面的双下__exit__方法

------------------------------------------------------
# 使用场景:

上下文管理器 适用于那些进入和退出之后自动执行一些代码的场景,
比如文件、网络连接、数据库连接或使用锁,s使用事务的编码场景等

或者简单地说:一个东西的打开与关闭, 就可以使用到上下文管理器
------------------------------------------------------

# 如何使用
class A:
    def __enter__(self):
        print('进入with语句块时执行此方法,如果有返回值会赋值给as声明的变量')
        return 'xxx'

    def __exit__(self, exc_type, exc_val, exc_tb):
         print('退出with代码块时,执行此方法)
         print('1', exc_type)
         print('2', exc_val)
         print('3', exc_tb)

with A() as f:      # f 就是双下enter返回的值
    print(f)    # xxx
    print(type(f) )   # <class 'str'>
    print('哈哈哈')

print('上下文管理器结束了')  # 执行该代码前,会执行双下exit方法


进入with语句块时执行此方法,如果有返回值会赋值给as声明的变量
xxx
<class 'str'>
哈哈哈

退出with代码块时,执行此方法
1 None
2 None
3 None

上下文管理器结束了

-----------------------------------------------------

.
.
.
.
.

Python是值传递还是引用传递


# 严格意义上来说,python既不是值传递,也不是引用传递

可变类型数据当参数传到函数中,在函数中修改会影响原来的变量,不是重新赋值
不可变类型数据当参数传到函数中,在函数中修改不会影响原来的变量

# 因为python一切皆对象,对象都是指向内存地址,都是引用


-----------------------------------------------

# 什么是值类型,什么是引用类型
	值类型的数据     是一个变量=具体的值(一块内存空间放着这个变量的值)
	引用类型的数据   是一个变量=内存地址(内存地址再绑定值)

值类型的数据是变量名直接绑定值
引用类型的数据是变量名先绑定内存地址,内存地址再绑定最终的值
-----------------------------------------------

# 什么是值传递      什么是引用传递
	如果是 引用类型的数据,传到函数里面,数据修改就会影响原来的
	如果是 值类型的数据,传到函数里面,数据修改不会影响原来的

-----------------------------------------------

.
.
.

python一切皆对象怎么理解 ?


python一切皆对象,对象都是指向内存地址,都是引用
所以所有东西都可以赋值给一个变量名
# 所以python可以:
	直接把一个函数/对象 作为另一个函数的参数,
	用一些基本类型的数据,直接去点一些方法

.
.
.

django 的命令是怎么制作的,有空再学学

.
.
.

django 中 什么叫信号


当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。

.
.
.

Dockerfile用过吗?常永命令有哪些

Dockerfile是由一系列命令和参数构成的脚本文件   就是一个文件,用来构建镜像的

FROM 基础镜像  # 基于哪个基础镜像来 构建新镜像
MAINTAINER lqz   # 声明 新镜像的创建者
ENV key value    # 设置环境变量 (可以写多条)
RUN command  # 在容器执行的命令


.
.
.
.
.
.
.
.
.
.
.

装饰器

# 装饰器:本质是一个闭包函数

作用是在不改变 被装饰对象源代码和调用方式的基础上,为它加入新功能
     闭包函数:定义在函数体内部,但是用到外部名称空间的变量的函数
     使用场景:
        一些视图函数,需要登录用户才能访问,就可以加登录装饰器
        django中局部去除csrf认证   view = csrf_exempt(view)
        flask的视图函数的路由注册就是基于装饰器 @app.route('/路径')
        通过装饰器,运行add_url_rule函数,注册路由

.
.
.
.

django的信号用过吗?如何用,干过什么


------------------------------------------------

	django提供的一种通知机制,他是一种设计模式,观察者模式,
	在发生某种变化的时候,通知某个函数执行

	内置信号:如果是内置信号用起来简单,
	比如:pre_save,pre_delete,数据库数据保存删除前 就会触发信号
	只需要写个函数,跟内置信号绑定,
	当信号被触发,函数就会执行
------------------------------------------------

	在django中 有两种绑定信号的方式
	1 @receiver(信号名)  装饰对应的函数
	2 信号名.connect(对应的函数名)


	自定义信号:就比内置信号多了两步:1 定义信号   2 触发信号 (信号.send)


# 信号的使用场景:
比如自定义信号的使用,可以在用户异地登录的时候,触发自定义信号,执行信号绑定的,
发送短信提醒的函数

.
.
.
.
.
.
.

django 的设计模式


Django其实也是一个MTV 的设计模式。MTV是Model、Template、View三个单词的简写,分别代表模型、模版、视图。

.
.
.
.
.
.
.

restful规范

10个 说几个出来就行了

多版本共存,路径中带版本信息
在请求地址中带过滤条件
返回数据中带错误信息
响应中状态码
数据即是资源,均使用名词,尽量不出现动词

.
.
.
.
.
.

并发 并行


# 并发  同一时间段内,执行多个任务的能力

# 并行  同一时刻,执行多个任务的能力

# 并行必须是多cpu才能支持

.
.
.
.

阻塞 非阻塞


# 程序运行的状态  阻塞态  就绪态  执行态
# 程序执行的角度

# 阻塞   程序在等待某个操作完成期间,自身cpu被释放了,则称该程序在该操作上是阻塞的
比如程序在等待读写操作完成期间,或者sleep期间 ,自身不能干其他事情,因为cpu已经被调走了


# 非阻塞   程序在等待某个操作过程中,自身cpu不释放,则称该程序在该操作上是非阻塞的
只要程序在等待某个操作的过程中,cpu不放就是非阻塞!!就可以继续运行干别的事情
比如协程 在等待sleep操作的过程中,程序层面自动切换,运行其他代码,cpu就不释放了!

# https://zhuanlan.zhihu.com/p/621717027

.
.
.
.

同步与异步的区别:

----------------------------------------------------
# 调用程序的角度

# 同步:只有执行完前一个任务,才会执行下一个任务。同步意味着有序
# 异步:当一个任务已经执行,无需等待该任务执行完成,就可以切换到另外一个任务上。异步意味着无序

.
.
.

什么是IPC ? 进程间如何通信 ?


IPC 进程间通信

# 1 什么是IPC,如何进行进程间通信
    -IPC:Inter-Process Communication,进程间通信
    -两种情况:
        -同一台机器上的两个进程通信
        -不同机器上的两个进程进行通信

------------------------------------------
# 如何通信:
-------------
    -pyton中多进程与多线程模块里的queue队列  可以做进程间通信   先进先出
          q = Queue(10)   # 生成队列并规定能存放数据的量
          q.put(数据)     # 往队列里放数据
          q.get()         # 从队列里面取数据
          # 该队列只能满足简单的进程间通信用
--------------
    -消息队列:  redis就可以做消息队列,rabbitmq,kafka
--------------
    -socket套接字: 展现形式比如:服务和服务之间通过接口调用

.
.
.

什么叫正向代理 反向代理

# 2 正向代理,反向代理
	-正向代理:代理的是客户端  比如 VPN  爬虫代理池    # 服务器看不到客户端
	-反向代理:代理的是服务端  比如 nginx    # 客户端看不到服务器

.
.
.
.
.

什么叫粘包问题 如何解决


	-因为TCP是流式协议,tcp客户端发送的多个数据包就会像水流一样流向TCP服务端,
	多个数据包就会'粘'在一起,区分不开是几个数据包,造成了粘包现象

# 解决办法:
	-1 每个包设置结束标志符    http协议采用这种方法  /r/n/r/n
	-2 每个包设置固定大小的头,头可以解析出包的大小


# 机器与机器间通信只需要通过ip地址就行了

.
.
.
.
.
.

http协议详情,http协议版本,http一些请求头


# 特点:
	1 基于请求响应--》服务端不能主动给客户端推送消息---》websocket协议
	2 无状态无连接---》不能做会话保持---》才出现了cookie,session,token
	3 基于tcp之上的应用层协议

---------------------------------------------
# 详情:
-请求协议:
	请求首行:请求方式(get,post,delete),请求地址,请求http协议版本号/r/n
             请求头:key:value   (cookie,useragent,referer,x-forword-for)
             请求体:编码方式
	-响应协议:
            响应首行:响应状态码(1xx,2xx),响应单词描述
            响应头:key:value    跨域问题的响应头
            响应体:html格式:浏览器中看到的   json格式给客户端使用

----------------------------------------------
-协议版本
-0.9:
           HTTP协议的最初版本,功能简陋,仅支持请求方式GET,并且仅能请求访问HTML格式的资源
------------------------------------------
-1.0:
           但是1.0版本的工作方式是每次TCP连接只能发送一个请求(默认短链接),
           当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接,
           就是不支持keep-alive
-------------------------------------------
-1.1:
           引入了持久连接(persistent connection),即TCP连接默认不关闭,
           可以被多个请求复用,不用声明Connection: keep-alive。客户端和服务器
           发现对方一段时间没有活动,就可以主动关闭连接。
-------------------------------------------
-2.0:
           多路复用:对于 HTTP/1.x,即使开启了长连接,请求的发送也是串行发送的,
           在带宽足够的情况下,对带宽的利用率不够,HTTP/2.0 采用了多路复用的方式,
           可以并行发送多个请求,提高对带宽的利用率!!!
--------------------------------------------
-3.0:
            HTTP3.0又称为HTTP Over QUIC,其弃用TCP协议,
            改为使用基于UDP协议的QUIC协议来实现

.
.
.
.

GET请求和POST请求的区别


    post更安全(不会作为url的一部分)
    post发送的数据更大(get有url长度限制)
    post能发送更多的数据类型(get只能发送ASCII字符)
    post比get慢
    post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作

.
.
.
.

如何实现服务器给客户端发送消息,websocket是什么?用过吗


-websocket:一种通信协议,区别于http协议,可在单个TCP连接上进行全双工通信。允许服务端主动向客户端推送数据。浏览器和服务器只需要完成一次tcp连接,两者之间就可以建立持久性的连接,并进行双向数据传输
    -没有跨域问题
    -应用场景:
    	-只要涉及到服务端主动给客户端发送消息的场景,都可以用
        -实时聊天:我项目没写
        -服务端发生变化,主动向客户端推送一个通知
        -监控服务端的内存使用情况
    -djanog中使用channles模块实现


-# django后端如何实现websocket参考博客  https://zhuanlan.zhihu.com/p/371500343

.
.
.
.
.

悲观锁和乐观锁,如何实现


-无论是悲观锁还是乐观锁,都是人们定义出来的概念,仅仅是一种思想,与语言无关
---------------------------------------
# 并发控制:
    并发控制:当程序中出现并发问题时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的,这种手段就叫做并发控制
    没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题

-----------------------------------------
# -悲观锁:
    当要对一条数据进行修改的时候,为了避免同时被其他人修改,
    在修改数据之前先锁定防止并发,再修改的方式 被称之为悲观并发控制 又名"悲观锁"
    之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。
    当对这一行的数据加了悲观锁后,其他线程对这行数据,连读的权利都没有了。

# -悲观锁实现方式:
	1 传统的关系型数据库(如mysql)使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
	2 编程语言中的线程锁,比如python的互斥锁
	3 分布式锁:redis实现的分布式锁等

--------------------------------------------

.
.
.

乐观锁


# -乐观锁:
	通过程序实现(没有真正的一把锁),不会产生死锁现象。
	总是假设读数据和要改数据的期间,别人不会修改,所以不会上锁,
	只在更新的时候会判断一下,期间数据有没有发生变化,如果有,就不做修改!!!

----------------------------------------------

# 乐观锁实现方案:
1 版本号控制:在数据表中增加一个版本号字段,读操作的时候先获取版本号
      每次更新数据时,再获取一下版本号与之前获取的版本号,如果一致,说明期间数据没改
      更新数据,并将版本号加 1    否则更新失败

2 时间戳方式:在数据表中增加一个时间戳字段,
      每次更新数据时,查看时间戳与一开始查看的时间戳,如果一致,说明期间数据没改
      更新数据,并更新时间戳    否则更新失败

----------------------------------------------
# 悲观锁乐观锁使用场景

并发量:如果并发量不大,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题, 建议乐观锁
响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高
冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大
重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低
读多写少: 乐观锁适用于读多写少的应用场景,这样可以提高并发粒度

------------------------------------------------

.
.
.
.
.

django中如何开启事务

#django中使用:下单,秒杀场景

# 先建个表
from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    count = models.IntegerField()

class Order(models.Model):
    order_id = models.CharField(max_length=64)
    order_name = models.CharField(max_length=32)
-------------------------------
# django中如何开启事务
	-全局开启:每个http请求都在一个事务中
        DATABASES = {
         'default': {
             'ENGINE': 'django.db.backends.mysql',
             'NAME': 'lqz',
             'HOST': '127.0.0.1',
             'PORT': '3306',
             'USER': 'lqz',
             'PASSWORD': 'lqz123',
              #全局开启事务,绑定的是http请求响应整个过程
             'ATOMIC_REQUESTS': True,
         }
    }

------------------------
-每个视图函数开启
    	from django.db import transaction
        @transaction.atomic
        def seckill(request):
            pass
------------------------
-局部开启  在某一段代码上开启事务
    	from django.db import transaction
        def seckill(request):
            with transaction.atomic():
                print('hahaha')
            # 事务里面的所有代码都在一个事务中
            # 一旦在with下面写同级的代码,事务就提交了,事务里面的代码才会真正执行
            # 用了with上下文就不需要手动提交事务了
            return HttpResponse('秒杀成功')

------------------------

# 回滚点Savepoint
	设置回滚点:sid = transaction.savepoint()
	提交回滚点:transaction.savepoint_commit(sid)
	回滚到回滚点:transaction.savepoint_rollback(sid)

	事务提交后,自动运行回调函数  transaction.on_commit(send_email)

-------------------------
# 事务
提交整个事务       transaction.commit()
整个事务回滚        transaction.rollback()
-------------------------
## 回滚点的使用 代码示范
def seckill(request):
    # 局部开启事务
    with transaction.atomic():
        sid = transaction.savepoint()  # 设置回滚点
        try:
            book = Book.objects.get(pk=1)
            book.name = 'xxx'
            book.save()     # 没有真正提交
            Order.objects.get(pk=1)
            transaction.savepoint_commit(sid)
        except Exception as e:
            # 如发生异常,回滚到指定地方
            transaction.savepoint_rollback(sid)
            print('出异常了,回滚')

    return HttpResponse('秒杀成功')
-------------------------
# 提交事务后执行的函数
def send_email():
    print('发送邮件给卖家了')

def seckill(request):
    with transaction.atomic():
        book = Book.objects.get(pk=1)
        book.count = book.count - 1
        book.save()
        transaction.on_commit(send_email)
    return HttpResponse('秒杀成功')

.
.
.
.
.
.

django中如何使用悲观锁与悲观锁


## 使用mysql的行锁 做悲观锁

# Django中使用悲观锁锁定一个对象,需要使用select_for_update()方法。
# 它本质是一个行级锁,能锁定所有匹配的行,如果查询所有,可以锁住整张表,直到事务结束

import datetime
import time
import random

@transaction.atomic
def seckill(request):
    sid = transaction.savepoint()   # 设置回滚点
    book = Book.objects.select_for_update().filter(pk=1).first()
    # 锁住查询到的book对象,其他人连该对象读都读不了,直到这个视图函数结束,锁才释放

    if book.count > 0:
        print('库存可以,下单')
        Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单')
        # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
        time.sleep(2)  # 模拟延迟
        book.count=book.count-1
        book.save()
        transaction.savepoint_commit(sid)
        return HttpResponse('秒杀成功')
    else:
        transaction.savepoint_rollback(sid)
        return HttpResponse('库存不足,秒杀失败')

--------------------------------------------
--------------------------------------------

# django中使用乐观锁  在改数据前比较一下与上面读的数据一不一样,一样才改

@transaction.atomic
def seckill(request):
    # 乐观锁可能会失败,我们一直尝试秒杀,直到秒成功或库存不足
    while True:
        sid = transaction.savepoint()
        book = Book.objects.filter(pk=1).first()
        count = book.count              # 先查一下库存
        print('现在的库存为:%s' % count)

        if book.count > 0:
            print('库存可以,下单')
            Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单')
            # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
            time.sleep(2)  # 模拟延迟
            res = Book.objects.filter(pk=1, count=count).update(count=count - 1)
            print(res)
            if res >= 1:  # 表示修改成功
                transaction.savepoint_commit(sid)
                return HttpResponse('秒杀成功')
            else:  # 修改不成功,回滚
                transaction.savepoint_rollback(sid)
                print('被别人扣减了,继续秒杀')
                continue  # 继续秒杀
        else:
            transaction.savepoint_rollback(sid)   # 库存不足也回滚
            return HttpResponse('库存不足,秒杀失败')

.
.
.
.
.
.

docker相关知识

# docker的出现解决了什么?
解决了应用程序本地运行环境与生产运行环境不一致的问题
让快速扩展、弹性伸缩变得简单

# 详细解释为什么解决了不一致问题:
当我们用Dockerfile文件构建出一个python解释器 与 所有依赖的模块如django与uwsgi等 的镜像时
并且Dockerfile有一个cmd命令 启动项目的命令

镜像推到远程仓库,就可以在任意电脑上,先把项目文件拉下来,再把镜像拉下来,所有的依赖就有了,
镜像运行成容器,项目就直接运行起来了!!!

--------------------------------------------
# Docker的网络模式有哪些?
HOST模式 、Container模式 、None模式 、 bridge模式

--------------------------------------------
systemctl start docker    启动docker
systemctl stop docker     停止docker
--------------------------------------------
docker info     查看docker概要信息
docker pull 镜像名称:标签     拉取镜像    不指定标签,会下载最新版本

docker images    查看本地镜像
docker rmi 镜像ID     删除镜像
---------------------------------------------
docker ps   查看正在运行的容器
docker ps -a     查看所有容器

docker run -it --name=容器名 镜像名:标签   # 将镜像运行成指定名称的容器并进入
docker start mycentos    # 启动停止的容器
---------------------------------------------
docker exec 容器id 命令       # 容器执行命令
docker exec -it  容器id /bin/bash    # 外部进入到容器内部

docker run -id --name=lqz -v /home/sss:/sss centos:7   # -v做目录映射
                          -p 8888:3306                 # 做目录映射

docker rm 容器id      # 删除对应id的已停止的容器
docker rm `docker ps -a -q`   # 删除所有已停止的容器
---------------------------------------------
docker commit 容器名 镜像名    # 把容器做成镜像
docker save -o 镜像名.tar 镜像名    # 镜像文件打包成压缩包
docker load -i 压缩包   # 压缩包解压成镜像
---------------------------------------------
# Dockerfile构建镜像
先编写Dockerfile文件
FROM 基础镜像
ENV key value      # 设置环境变量
ADD 宿主机文件目录  容器目录    # 将宿主机的文件复制到容器内,压缩文件会后自动解压
CMD:启动容器时需要执行的命令
WORKDIR 目录名     # 设置工作目录


# 基于dockerfile构建镜像    要在Dockerfile文件所在的目录下 执行该命令
docker build -t='镜像名' .

# docker-compose 单机容器编排
docker-compose up   # 启动管理的容器
---------------------------------------------

.
.
.

多路复用

.
.
.
.

ssh

.
.
.
.
.

celery相关知识

# 如何使用见文章 https://www.cnblogs.com/tengyifan888/articles/17344382.html

# celery架构:
任务中间件 broker   需要借助于redis 或 rabbitmq
任务执行单元 worker
结果存储库 backend    需要借助于redis 或 mysql


# 主要的使用场景
    执行耗时任务用异步
    执行一些循环定时任务或者延迟任务

.
.
.
.
.
.
.
.

shell 脚本


.
.
.
.
.
.
.
.
.

内网穿透

你的电脑没有公网ip,所以你电脑上起的服务项目,别人访问不到,
需要有一个具有公网ip的服务器 来监听你的电脑的端口
这样别人访问这个公网ip的的服务器,服务器转发请求到你的电脑上
就实现内网穿透了

.
.
.

匿名函数

# 没有名字的函数
语法结构  lambda 形参:返回值

.
.
.


表锁情况解决
生成器迭代器

垃圾回收
get请求celery异步下载图片流程
链表查询业务场景
具体项目业务流程

.
.
.

http常用状态码

1XX:服务端已经接收到了你的请求正在处理,客户端可以继续发送或者等待
2XX:200 OK      请求成功,服务端发送了对应的响应
3XX:302(临时定向) 304(永久定向)   重定向(想访问网页A,但是自动调到了网页B)
4XX:403访问权限不够   404请求资源不存在
5XX:服务端内部错误
--------------------------------------------

200 OK:表示客户端请求成功。服务器返回的数据在响应体中。
404 Not Found:表示服务器无法找到请求的资源。通常是因为客户端请求的 URL 错误或请求的资源不存在。
500 Internal Server Error:表示服务器内部发生错误,无法完成请求。通常是因为服务器程序出现了异常。
401 Unauthorized:表示客户端请求的资源需要身份验证,但是客户端未提供有效的身份凭证。通常是因为客户端未提供正确的用户名和密码。


------------------------------------
80 http
1080 socks代理
443 https
3389 windows远程桌面

3389端口是 Windows远程桌面的服务端口号。通过远程桌面协议,
用户可以像操作本机一样操作远程的电脑 ,实现远程控制。
使用3389端口进行远程桌面连接需要在远程计算机上开启远程桌面服务,并设置允许远程连接。

1080端口是Socks代理服务的默认监听端口。Socks(Socket Secure)是一种网络协议,

.
.
.

代码判断一个数是否是质数

# 简单实现
def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True
-----------------------------
# 高级一点
def is_prime1(n):
    if n <= 1:
        return False
    for i in range(2, int(n ** 0.5 + 1)):
        if n % i == 0:
            return False
    return True

print(is_prime(499))

.
.
.
.
.
.
.

跨域问题


从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
跨域是由浏览器的同源策略造成的。
前后端分离项目,由于前端项目启动的ip地址与端口 与 后端项目是不一样的  所以肯定会有跨域问题

解决办法:后端往响应头里面放东西 或者用第三方的中间件

.
.
.
.
.
.
.

锁相关总结 参考博客 https://zhuanlan.zhihu.com/p/489305763


.
.
.

常见sql查询语句

# 默认按order by字段升序排序  加descent 就变成降序排列
select name,birth from students where  score>=80 order by birth descent

# 分组
select post as '部门',max(salary) as '最高薪资' from emp group by post;


# 统计每个部门的部门名称 和 部门下的员工姓名
select dep,group_concat(name) from emp group by dep;

# 子查询

# 连表查表

.
.
.
.

面试题


# 将字符串妆化成字典 {'k': 1, 'k1': 2, 'k2': 3, 'k3': 4}  形式
s1 = 'k:1/k1:2|k2:3|k3:4'
d1 = {kv.split(':')[0]: int(kv.split(':')[1]) for kv in s1.replace('/','|').split('|')}
print(d1)


# 给定两个列表l1 l2 找出两个列表中相同与不同的元素
l1 = [1, 2, 3, 4, 9]
l2 = [1, 2, 8, 5, 6]

common_list = []

for i in l1:
    if i in l2:
        common_list.append(i)

different_list = [x for x in l1 if x not in l2]
print(common_list)
print(different_list)

-------------------------------------------
# Python中内置的数据结构有几种?

1. 列表(list):一种有序的可变容器,其中的元素可以是任意类型。

2. 元组(tuple):一种有序的不可变容器,其中的元素可以是任意类型。

3. 字符串(str):一种不可变的序列类型,用于存储字符。

4. 集合(set):一种无序的可变容器,其中的元素不可重复。

5. 字典(dict):一种无序的可变容器,其中存储了键值对,用于快速查找。

6. 范围(range):包含数值序列的对象,通常用于循环。

-------------------------------------------
# 简单谈一下 深拷贝 浅拷贝 拷贝

浅拷贝:
    浅拷贝是对 对象的每个属性依次复制,当属性的值是不可变类型时,复制的是对象的值
    当属性的值是可变类型时,复制的是对象的引用!!!

深拷贝:
    会完全复制出一个一模一样的对象,但是新对象与原对象不共享同一个内存地址
    所以对于可变类型与不可变类型的数据,深拷贝都是完全复制出一份新对象,并且与原对象不共享内存地址

-----------------------------------------------

# 如何查看操作系统监听的端口
netstat -nlp     linux 命令

动态查看系统应用进程和cpu使用情况   top
查看cpu信息  cat /proc/cpuinfo

查看内存信息的使用情况  free

查看磁盘空间使用情况  df -h

查看系统中所有的进程  ps aux

# 如何用python程序来收集这些服务器主机的信息?
可以使用第三方的库
subprocess模块    # 该模块的方法,可以查看当前主机的信息
paramiko模块   # 通过SSH连接远程Linux服务器,执行命今获取远程服务器的资源信息

------------------------------------------------

# 内网环境中 别人无法访问你的django项目 可能有那些原因

自己项目配置文件看下:
1. ALLOWED_HOSTS   设置允许哪些主机ip访问   看有没有问题  直接改成允许所有 = ['*']
2. 看下静态文件的路由配置对不对
3. 查看项目启动的ip地址是不是0.0.0.0    python manage.py runserver 0.0.0.0:8000
4. 检查防火墙是不是拒绝了其他电脑浏览器的请求,关闭防火墙或添加允许访问

.
.
.

docker 的出现解决了哪些问题

# 解决了应用程序本地运行环境与生产运行环境不一致的问题
# 让快速扩展、弹性伸缩变得简单

# 解决了应用程序资源使用的问题,docker会一开始就为每个程序指定内存分配和CPU分配

.
.
.
.
.
.
.
.
.

F与Q查询

当查询条件也就是等号右边不是明确的,查询条件也需要从数据库中获取 就需要使用F查询
# 查询库存数大于卖出数的书籍
res = models.Book.objects.filter(kucun__gt=F('maichu'))


# res = models.Book.objects.filter(Q(pk=1) | Q(price__gt=2000))    # |是or
当查询条件是 或 的时候 用Q查询,因为默认的filter参数都是且的关系

.
.
.

ORM查询优化


# 优化主要就是优化的sql执行的次数,优化查询数据的时间!!
only与defer 关键字 优化查询数据的时间
res = models.Book.objects.filter(price__gt=20)  # 查出所有对象的所有字段数据
res = models.Book.objects.filter(price__gt=20).only('title', 'price')
# 查出所有对象的'title', 'price'与price字段数据   假如只需要这两个字段的数据,显然该查询用时少

# defer() 除了括号里的数据其他的都查
------------------------------------------------------
# select_related与prefetch_related 优化的sql执行的次数

res = models.Book.objects.filter(price__gt=20)
for obj in res:
    print(obj.publish.name)   # 当用书对象点关联的出版社表里面的数据时,每次查询都需要走SQL查询


# 通过外键将两个表连接起来了,查询的数据放到一条条数据里
res = models.Book.objects.filter(price__gt=20).select_related('publish')
for obj in res:
    print(obj.publish.name)      # 不再走SQL查询!!!

.
.
.
.
.
.
.
.
.

sql语句的慢查询优化

----------------------------------------------

# 1 慢查询日志记录 定位慢SQL
    # 查看慢查询日志配置
    SHOW VARIABLES LIKE 'slow_query_log%'
    # 手动开启查询日志
    SET GLOBAL slow_query_log = 'ON';

    # 查看设置的超时时间,超过该时间就定义为慢查询
    show variables like 'long_query_time'
    # 设置超时时间,设置好后,需要在下次会话后才能生效!!!
    SET GLOBAL long_query_time = 5

    # 查看分析执行耗时有没有开启
    show variables like '%profil%'

    # 查看sql的运行时间
    show profiles

-----------

# 2 查看sql语句的执行效率:
在sql语句的前面加一个 explain

运行的结果
type字段对应的值是range级别,及以上就可以接受
rows字段对应的值是 mysql估算要找到所需的记录,需要读取的行数
extra字段包含
    查询的其他信息  Using index使用了覆盖索引  Using where使用了条件过滤


为经常需要排序、分组和联合操作的字段建立索引
为常作为查询条件的字段建立索引  等等
----------------------------------------------
----------------------------------------------

# 慢查询sql优化的思路 ⾸先要搞明⽩慢的原因是什么?
      是查询条件没有命中索引?是加载了不需要的数据列?还是查的数据量太⼤?

这三个方向是最关键的!!!

# 1 查询条件where后面的字段如果没有命中索引,就会导致全表扫描!!!
      修改语句或者修改索引,使得语句可以尽可能的命中索引

# 2 加载了不需要的数据列
      重写sql语句,去掉多余查询的字段,增加索引覆盖的机率

# 3 查的数据量太⼤:
      确定是否需要查询⼤量的数据,
      优化sql语句,字段加索引,热数据加缓存,主从复制读写分离
      如果表里的数据量太大,sql语句也无法再优化了,可以考虑分表

----------------------------------------------
----------------------------------------------

# 为经常需要排序、分组和联合操作的字段建立索引
  因为如果按照没有索引的字段进行排序,会将要查的数据全部查出来,然后再排序
  但是如果是按照索引的字段进行排序,因为索引本身是有序的,就可以先按索引排序后
  然后按照普通索引依次获取到主键索引,然后再依次通过主键索引拿到对应的每一条数据


# 为常作为查询条件的字段建立索引  等等
----------------------------------------------

.
.
.
.
.
.

sql语句优化面试题

where 后面的条件字段只要没索引就是全表扫描!!!

select 后面的查询字段如果有索引,就可以不用回表,查询速度就会变快!!!

------------------------------------------
# 假设age字段是普通索引

select * from table where age > 20 limit 100,10
# 这条sql语句的底层并不是跳过offset⾏,⽽是取offset+N⾏,
# 然后放弃前offset⾏,返回N⾏
所以当offset特别⼤的时候,效率就⾮常的低下,limit 10000000,10
10000010个数据全查出来了,又舍弃前10000000

------------------------------------------

# 解决办法:
select * from table where age > 20 limit 1000000,10  # 优化成下面的!!!
select * from table where id in (select id from table where age > 20 limit 1000000,10)

# 这样虽然也load了⼀百万的数据,但是由于索引覆盖,
# 索引中包含了查询所需要的字段数据,不用回表了,所以速度也会很快!!!


# where 后面的条件字段只要没索引就是全表扫描!!!!

.
.
.
.
.
.

什么叫索引 与索引覆盖


# 索引是⼀种特殊的⽂件,索引是一种数据结构,它们包含着对数据表⾥所有行记录的引⽤指针。
# 更通俗的说,索引就相当于⽬录

b+树与b*的只有叶子节点才会存放真正的数据,主与支节点只会存放索引数据

索引有哪些优缺点?
优点:⼤加快数据的检索速度
缺点:会降低增/改/删的执⾏效率
      当对表中的数据进⾏增加、删除和修改的时候,索引也要动态的维护

--------------------------------------
--------------------------------------

# 索引覆盖就是覆盖索引

覆盖索引(covering index)
指一个查询语句的执行只需要从辅助索引中就可以得到查询记录,-------------
而不需要回表,去查询聚集索引中的记录。可以称之为实现了索引覆盖。

因此我们需要尽可能的在 select 后只写必要的查询字段,以增加索引覆盖的⼏率。
---------
# 覆盖索引就是指索引中包含了查询所需要的所有字段数据,
# 这种情况下就不需要再进行回表查询了。

.
.
.
.
.

什么叫回表查询 如何避免回表 ?


# 回表查询 :
先通过普通索引的B+树,查询到该普通索引对应的主键值,再通过主键索引的B+树,
查询到该主键索引对应的行记录数据 就叫回表查询!!!

----------------------------------------
----------------------------------------

# 示范:表中有四条记录:
  1, shenjian, m, A
  3, zhangsan, m, A
  5, lisi, m, A
  9, wangwu, f, B

两个B+树索引分别为:
(1id为PK,聚集索引,叶子节点存储行记录;
(2)name为KEY,普通索引,叶子节点存储PK值,即id;
普通索引无法直接定位行记录,需要扫码两遍索引树
select * from t where name='lisi';

---------------------------------------------

# 只要实现了索引覆盖就可以不需要回表了!!!

# 什么情况下就是索引覆盖了呢 ?
select id from t where name='lisi';
由于查询的值是id, 而id的值已经在name普通索引树上了,
因此可以直接提供查询结果,不需要回表

当SQL语句的所有查询字段(select列)和查询条件字段(where子句)全都包含在一个索引中,
便可以直接使用索引查询而不需要回表。

即在这个查询里,索引name已经 “覆盖了” 我们的查询需求id数据,故称为索引覆盖。

image
.
.
.
.
.
.
.

sql注入问题 及如何解决


SQL注入:利用特殊符号的组合产生特殊的含义,从而避开正常的业务逻辑
比如前端用户名输入框里面输入 jason' -- kasdjksajd    密码框里不输入
username='jason' -- kasdjksajd'
password=''
sql = " select * from userinfo where name='%s' and pwd='%s' " % (username, password)
# sql语句就变了  select * from userinfo where name='jason' -- kasdjksajd' and pwd=''

cursor.execute(sql)  # 执行sql语句 会出sql注入问题

----------------------------------------------
----------------------------------------------

# 解决办法:
1 使用ORM语句 不用pymaysql模块执行原生sql语句
2 或者不要手动拼接字符串,交给excute方法,
sql = " select * from userinfo where name=%s and pwd=%s "
cursor.execute(sql, (username, password))

----------------------------------------------

.
.
.
.
.
.
.

rbac 是什么?

基于角色的访问控制 Role-Based Access Control

用户表  用户与角色多对多表 角色表  角色与权限多对多表  权限表

5张表实现对用户访问的控制,一般用户不直接与权限表关联  通过角色表与权限表关联

.
.
.
.
.

位运算

>> 右移动运算符:把左边的数字的各个二进位,全部右移右边位的位数,再转变成10进制数据


47>>3     # 结果是 5
00101111   整体往右移3位  变成了00000101   就变成10进制5
00000101


.
.
.
.
.
.

python代码防泄露方案

# 公司写的项目部署在客户机器上,源代码会泄露掉,怎么办

	-方案一:项目在服务器上启动起来,项目代码直接全部删掉,因为项目已经加载到内存里面了,
            项目只要不停,都没事,一停就废了

	-方案二:将项目用pipinstaller 编译打包成可执行文件,别人要想破解要反编译,成本会比较高

	-方案三:做到docker镜像中---》运行容器必须传一个》》- e password=公司的授权码
          只需要在项目里面第一行通过request对象
          取到环境变量Request.ServerVariables('password') 检查授权码是不是公司给的
          如果不是,不让项目跑起来!!!

.
.
.
.
.
.
.

csrf 是什么

跨站请求伪造:一个假网站模拟真网站,朝真网站的后端发送请求

解决方案:
# 1  token串的校验:
在核心页面打开时,就在页面生成了一个隐藏的token串,
客户在该页面提交数据到后端的时候,先校验token对不对,token不对直接拒绝请求,
这样可以避免假网站的朝真网站发请求了

# 2  请求头的referer 的检验
但假网站也可能会伪造该请求头  所以作用可能不大!!!

.
.
.

global与nonlocal 申明变量


global作用: 局部名称空间修改全局名称空间的变量
nonlocal作用: 局部内层名称空间修改局部外层名称空间的变量

age = 18

def index():
    name = 'jason'
    global age
    age = 20
    print(age)    # 20

    def inner():
        nonlocal name
        name = 'kevin'

    inner()
    print(name)   # kevin

index()

---------------------------------
# 补充一点:
while 循环 和 for循环 下面的代码不存在, 局部名称空间的概念的
def monkey_peach():
    p = 1
    # 调用zhen_chu_5函数 返回false就运行p = p + 1
    # 然后继续走while这行的条件判断 触发zhen_chu_5(p)函数的再次运行!!!
    while not zhen_chu_5(p):
        p = p + 1   # while下面没有局部名称空间的概念,所以变量p的值直接改了!!!
    print(p)

# 只有函数里面的代码有局部名称空间的概念!!!

.
.
.

python 常用的内置模块

# time      # 提供和时间相关的一些功能
# datetime    # 提供和时间相关的一些功能
--------------------------------------------
# os   # 主要与操作系统打交道
   os.mkdir()  os.makedirs()   # 创单级或多级目录

   os.rmdir(r'd2\d22\d222')  # 删除单极目录d222
   os.removedirs(r'd2\d22\d222\d2222')    # 只能删除空的多级目录!!!
   os.listdir(r'路径')  # 获取指定路径下的文件名称

   os.remove(r'一般写绝对路径')    # 只删路径的最后的那一个文件!!!

   os.path.dirname(__file__)  # 获取当前执行文件所在的目录路径
   os.path.abspath(__file__)  # 获取当前执行文件的绝对路径

   os.getcwd()  # 获取当前执行文件所在的目录路径
   os.path.exists(r'D:\day19'))  # 判断目录或文件是否存在
   os.path.join(r'D:\day19',r'模块使用.py')  # 路径拼接 会自动在两路径间加斜杠

   os.environ  # 获取系统环境变量
--------------------------------------------

# pathlib    os模块的升级版 简化了很多操作 用起来更方便
    import pathlib
    from pathlib import Path

    # 对于文件的操作要先用Path类括号里放路径,生成路径对象
    file = Path(r"wuhu.txt")

    pathlib.Path.cwd()  # 获得当前执行文件所在的目录路径
    PurePath(__file__).parent   # 获取当前文件所在的上一级目录

    Path(__file__).resolve()  # 获取当前执行文件的绝对路径
    Path(__file__).resolve().parent   # 获取当前执行文件的上一级目录路径

    file.state().st_size  # 获得文件大小
    file.is_file()  # 检查路径是否为文件
    file.mkdir()  # 创建单级目录
    file.mkdir(parents=True)  # 创建多级目录
    file.rmdir()  # 删除单极目录

    file.unlink()  # 只删路径的最后的那一个文件!!!

    file = Path(r'D:\pythonProject03\day19')
    file.joinpath(r'模块使用.py')    # 路径拼接

    # 拿目录下的所有
    file = Path(r"D:\pythonProject\20230420")
    l1 = [i.name for i in file.iterdir() if i.is_file()]
    print(l1)     # 才能拿到之前os.dirlist('file')得到目录下文件名列表的结果

--------------------------------------------
# random  产生随机数、随机取样等操作
--------------------------------------------
# json  序列化模块
    将对象序列化为字符串
		json_str = json.dumps(dict1)  # 操作数据用dumps
		with open(r'a.txt','w',encoding = 'utf8') as f:
			json.dump(dict1,f)   # 操作文件用dump
    将字符串反序列化为对象

同理json字符串直接反序列化用loads   dict1 = json.loads(json_str)
如果是文件 就直接打开反序列化成字典了
with open(r'a.txt','r',encoding = 'utf8') as f:
    dict1 = json.load(f)
--------------------------------------------

# pickle 序列化模块  用于json模块不能序列化的数据  用它来序列化
 比如日期对象、
--------------------------------------------

# logging模块
是Python内置的标准模块,主要用于输出运行日志

--------------------------------------------

.
.
.

collections模块

collections模块在Python内置的数据类型的基础上,提供了额外的高性能数据类型。
collections模块的OrderedDict类构建的字典可以支持顺序
使用该模块可以大大简化Python代码,提高代码效率

# 1.命名元组:namedtuple     一个工厂函数用来,创建一个带名字的元组子类

qqqq = namedtuple('Points', ['x', 'y'])   # 生成一个新的带名字的元组子类
p1 = qqqq(1, 2)    # 生成命名元组对象

print(p1)  # 结果为  point(x=1, y=2)

print(p1.x)  # 1  可以通过对象点的方法 取值
----------------------------------------

# 2 deque  双端对列    是一个类,没有继承列表类

q = deque(['a', 'b', 'c'], maxlen=100)
# 生成的这个双端队列对象,可以从队列的左边弹出元素,也可以从队列的右边弹出元素
# 可以往该双端队列的左边添加元素,也可以往该双端队列的右边添加元素
# 这就流弊了通过双端对列既可以实现:
    队列先进先出的效果: 元素从双端对列左边添加,从右弹出,就是队列效果
    也能实现堆栈先进后出的效果:元素从双端对列右边添加,从右边弹出,就是堆栈效果

----------------------------------------
----------------------------------------

# 3.OrderedDict 有序字典类     继承类字典类

# 让有序字典也具有队列与队栈的效果!!!

od = OrderedDict(a=1, b=2, c=3, d=4,e=5)
print(od)
# OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)])

print(d.popitem(last=True)) # 先弹后放进的键值对 ('e', 5)
print(d.popitem(last=False)) # ('a', 1)
print(d) # OrderedDict([('b', 2), ('c', 3), ('d', 4)]

# 如果 last 值为true  先弹 最后放进的键值对
# 如果 last 值为False  先弹 最先放进的键值对

----------------------------------------
----------------------------------------

# 4.计数器类  继承了字典类

# 用于对不可变类型的数据计数,非常方便

from collections import Counter    # 该计数器类也是继承了字典类!!!
res = 'abacababaccbcbaca'
print(Counter(res))
Counter({'a': 7, 'b': 5, 'c': 5})

t1 = [1,2,3,4,5,6,7,8,9,1,5,6]
print(Counter(t1))
# Counter({1: 2, 5: 2, 6: 2, 2: 1, 3: 1, 4: 1, 7: 1, 8: 1, 9: 1})

t2 = (1,2,3,4,5,6,7,8,9,1,5,6print(Counter(t2))
# Counter({1: 2, 5: 2, 6: 2, 2: 1, 3: 1, 4: 1, 7: 1, 8: 1, 9: 1})

----------------------------------------

.
.
.
.

哈希函数

不可变类型的数据 才能哈希!!!
一种将任意长度的输入消息压缩成某一固定长度的消息摘要的函数。
哈希函数所必须的性质:
    可应用于任意大小的数据块。
    产生定长的输出。
    对任意给定的x,计算H(x)比较容易,用硬件和软件均可以实现。

.
.
.
.
.
.
.
.
.

数据库连接池的作用


# 池化技术,实现资源的复用,避免资源的重复创建与销毁带来的开销
(这里的资源所代表的有,数据库连接、进程、线程等等!!!)

数据库连接池也是一种池化技术
应用程序每一次向数据库发起增删改查操作的时候,都需要创建链接,在数据库访问量比较大的情况下
频繁的创建链接会带来比较大的性能开销,连接池的核心思想是应用程序在启动的时候,
初始化一个连接池出来,里面维持一定数量的与数据库的链接,这样当应用程序需要与数据库建立连接的时候
只需要从池中取出连接使用即可,使用完毕再将连接放回池中,
这样就避免了程序,每一次要操作数据库都要先和数据库建立连接,后续再销毁连接,所带来的资源开销

连接池有哪些参数:初始化连接数、最大连接数(如果连接数不够,后续获取连接的线程会阻塞)、
    最大空闲连接数(没有请求时池中保留的最大空闲连接)、最小空闲连接数、
    最大等待时间(连接池里的连接用完,后续线程要等待的时间超过该时间就会提示超时异常!!)
    无效连接清除、
#总结 控制数据库最大的连接数, 避免了频繁连接数据库与断开数据库,影响系统性能

image
.
.

进程池与线程池的作用


# 线程池
# 频繁地创建线程既会消耗太多内存,也会耗时太多,线程池限制线程的数量上限
  线程执行完一个函数后不会被销毁,而是会等待去执行下一个新的任务函数,从而减少资源消耗

--------------------------------------------------

# 进程池
    限制进程的数量上限,减少内存的消耗等
    进程执行完一个函数后不会被销毁,而是会等待去执行下一个新的任务函数

.
.
.

mysql 相关知识


# Innodb引擎:Innodb引擎提供了对数据库ACID事务的⽀持。默认用的它
  并且还提供了⾏级锁和外键的约束。它的设计的⽬标就是处理⼤数据容量的数据库系统。

# MyIASM引擎(原本Mysql的默认引擎):不提供事务的⽀持,也不⽀持⾏级锁和外键。
# MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不⾼。

-------------------------------------------

.
.
.

mvcc多版本并发控制


数据库并发场景有三种,分别为:
读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
-------------------------------------------------------

MVCC+悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突(写的操作,mysql会自动加行锁)
MVCC+乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突(CAS版本号控制,更新前看下版本号对不对)

-------------------------------------------------------

# 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制!!!
    并发读写数据库时,读操作时不阻塞写操作,写操作也不阻塞读操作。

它的实现原理主要是依赖记录中的 3个隐式字段,  undo log ,  ReadView 来实现的
--------------------------------------------
# 3个隐式字段:
    TRX_ID      # 事务id
    ROLL_PTR  # 回滚点指针 存的是上个版本的指针地址,通过它可以把undo log里不同版本的修改数据链接成一个链表
    ROW_ID    # 隐藏的主键

    # 把这个链表就叫做版本链,根据这个版本链就可以找到这条数据的所有历史版本
    # mvcc就是通过链表这种方式来维护多个版本的!!!
--------------------------------------------
# undo log    回滚日志

--------------------------------------------
# ReadView  开启一个事务后,进行快照读(不加锁的读)的操作的时候产生的快照,
   什么情况下会进行快照读:select查询的时候,数据修改时,也可以通过设置让事务启动时就快照读
    快照里记录了
		# 1 活跃事务id列表:记录了快照读的时候,所有的正活跃事务id,活跃事务:是开启但未提交的事务
		# 2 当前活跃的事务id里面,最小的事务id  ,也就是事务id列表里最小的值
		# 3 产生快照时,数据库分配给下一个事务的id值
		# 4 产生快照时,当前事务的id(只有对表中的数据做改动时,才会为事务分配id,否则id默认为0)
--------------------------------------------

# 关键是要读取哪个版本的数据了???
    用读取的版本数据的id 去ReadView里面匹配,来确定该版本数据可不可见,
    如果不可见,就通过回滚指针,到undo log 里面获取上一个版本的id 再到ReadView里面匹配,
    如果确定该版本可见,就把该版本数据读出来!!!

# 1 如果当前事务,读取了一个数据版本的事务id,与当前事务的id 值相等 说明在访问自己修改过的记录,
    所以该数据版本,可以被当前事务访问,是可见的

# 2 如果当前事务,读取了一个数据版本的事务id,比活跃事务id列表里的最小值还要小,
    说明该数据版本是已经提交的事务生成的,所以该数据版本,也是可见的

# 3 如果当前事务,读取了一个数据版本的事务id,比数据库分配给下一个事务的id值还要大,
    说明该数据版本是由将来还没有启动的事务生成的,所以对于当前事务而言是看不见的

# 4 如果当前事务,读取的数据版本的事务id,在当前活跃的事务id列表里面:
    说明该数据版本是由当前还没有提交的事务生成的,所以是不可见的

--------------------------------------------

# mvcc 就是通过版本链,再加上ReadView,来一起实现并发事务的访问的

--------------------------------------------
--------------------------------------------

# 注意可重复读隔离级别的mvcc多版本控制策略  与  提交读的隔离级别的mvcc多版本控制策略  是不一样的

# 可重复读隔离级别下 mvcc多版本控制策略:
    假如当A与B事务一起开启后,并读了p数据,A事务改了p数据,并提交事务后,
    B事务读的p数据还是老的p数据,虽然A事务已经提交了,对B事务来说没卵用
    怎么通过版本链和ReadView来实现的 ?
    见下图,
    主要就是因为读的事务,只有在第一次读的时候,才会产生快照readview,
    后面再读行数据时通过数据的事务id到快照里的去按规则判断,该数据师傅可见
--------------------------------------------
# 提交读的离级别下 mvcc多版本控制策略:
    每次查数据的时候,都会进行快照读而产生快照,然后拿着行数据的事务id号,去刚刚产生的快照里面
    去比对,然后判断出该行数据是否可见

# 总结:
    可重复读隔离级别下,事务开启后,只有在第一次查数据的时候,会快照读产生快照,
    后面再查数据不会再产生快照了,都是用的第一次的快照来判断行数据是否可见的

    提交读隔离级别下,事务开启后,每一次查数据的时候,都会快照读产生快照,
    然后用最新的快照判断行数据是否可见的

# 也就是不管是可重复读隔离级别,还是提交读隔离级别,对于判断行数据是否可见,的判断规则都是一样的
# 只是两种隔离级别下,在事务开启后查数据的时候,去生成readview快照的规则是不一样的

# 可重复读在一个事务里,快照只会生成一次
# 提交读在一个事务里,每次查数据都会生成一个新的readview快照,所以能更新活跃未提交事务的id列表

--------------------------------------------

整体通过回滚指针,链接成一个链表,形成版本链
image
.
.

可重复读隔离级别下:

假设:事务A主要是修改数据,事务B主要是查数据
当更新完事务A后name的值由北冥变成北冥-2并且也提交了,事务B所查到的数据 还是老数据name=北冥
事务A修改数据的时候会生成快照,所以A快照里面的任务id变成2,当前未提交的事务列表里面的也只有2
事务B第一次查询数据的时候,也会生成快照,B快照里面的任务id变成3,并且当前未提交的事务列表里面有2,3
image
.
.
事务A提交后,数据提交到数据库了,页记录里面该数据的事务id是2
但是事务B再次查询数据的时候,不会再快照读产生快照了,所以还是会拿当前行数据里面的版本id 2 去
事务B一开始得快照里面的,当前未提交的活跃事务列表里面[2,3]比较一下,
所以事务B得出的结论就是,该数据是由未提交的事务产生的,
虽然我们知道事务A已经提交了,
但是根据快照的判断规则,事务B只能得出的结论是 该数据是由未提交的事务产生的,因为列表里有 2 !!
就会通过回滚指针获取上一个版本的id 1 ,再到事务B的快照里面去比较,
确定该版本的数据是已提交的数据,产生的,所以就读出来了

也就是靠这个原理,在可重复的读的隔离级别下,解决了不可重复读的问题
因为对于当前事务比如说F 第一次查询数据,生成的快照里面的,当前未提交的活跃事务列表里面的其他事务,
即使后来其他事务提交了,但是事务F的快照里面的列表里面 这些事务的id还在里面了
所以事务F在读数据库行数据的时候,通过事务id到快照里面去判断的时候,还是会判断出这些事务版本的
数据是不可见的!!!!!
然后通过回滚指针一直找到,比列表里面最小的事务id还要小的,事务id版本对应的数据!!!
image
.
.
.
.
.
.

提交读隔离级别下:

关键点就是在提交读隔离级别下,事务B每次查询数据都会先进行快速读,产生一个快照,然后用行数据的事务id
去最新的快照里面去判断,这时候得出的结论就不一样了,为什么
因为再次查询时,产生的快照里面,当前未提交的活跃事务列表里面会更新,只剩下2了,事务A的修改数据提交后,
再快照读的时候,未提交的活跃事务列表里面就已经没有1了,所以后续事务B,在读数据库行数据的时候,
通过事务id到快照里面去判断的时候,事务1已经不在列表里面了,版本号时事务1的是可见的,所以就能读出来
所以在提交读的隔离级别下,事务B第二次读的时候,事务A提交的修改的版本数据,对于事务B来说是可见的
这也是为什么 提交读的隔离级别下 会出现不可重复的现象了!!!
image
.
.
.
.
.
.
.
.
.
image

B树 B+树 B*树 区别


# 补充树结构的每一个节点,是放在一个磁盘块里面,每个磁盘块的大小是固定的一般是16kb
# 一个节点就需要一次io
----------------------
个人理解   可能不对!!!
# 树结构的最后的叶子节点里存放的索引与数据的多少,一是由16kb限制,
# 二是由该叶子节点的上一层的指针,左右两边的关键字(键值),限制的

--------------------------------------------

# B树
    B树中所有的节点,都可以存放数据与索引(键值+指针)
--------------------
# B+树
    B+树索引 叶子节点有直接指向其他叶子节点的指针,而且是双下指针
    只有叶子节点才会存放 键值+数据
    其他节点只会存放索引(键值+指针)
--------------------
# B*树
    在支节点也添加了指向相邻支节点的指针,而且是双下指针
    只有叶子节点才会存放数据,其他节点只会存放索引

# 注意: 这里的数据有可能是表里面行数据本身,也可能是行数据对应的内存地址!!!

-------------------------------------------------
# 补充:

# 辅助索引在查询数据的时候,最后还是会借助于主键索引
# 辅助索引的特点是,树结构的叶子节点存放的是主键值!!!

# 所以根据辅助索引查数据,先要走辅助索引树拿到数据的主键值
# 再走主键索引树,根据主键值,找到叶子节点上的数据!!!

------------------------------------------

# 单一节点里面能够存储的数据越多,树的层级就越矮,查询数据的速度就越快!!!

------------------------------------------------

.
.
.
.
.

mysql 为什么使用B+树 而不是B树


# B树结构的好处:
    B树的分支节点也可以存储真实数据与索引,所以把访问频繁的数据,放在靠近根节点的地方
    这种情况下,热点数据的查询效率就会比较高

----------------------------------------

# B+树结构的好处:
    由于B+树的分支节点只存放索引数据,所以支节点的分叉就会多很多,每个分支节存的索引数据也更多,
    所以B+树层级对比B树层级也矮,所以一次读取可以在内存里面,获取更多的索引数据,提高了搜索的效率

    而且B+叶子节点有双向指针,指向相邻的叶子节点,对于空间的利用率更高
    所以B+树如果进行全数据的遍历,找到最下面的叶子结点,
    然后通过链表的形式,顺序遍历,就能拿到全部的数据了,减少io次数,磁盘读写代价更低
    如果是B树,想要遍历所有的数据,就需要将所有支节点都走一遍,加上B树的层级又高,io次数肯定更多
    B+树的查询效率更加稳定,B+树更适合范围查找

    但是B树如果进行全数据的遍历,需要对树的每一层都需要去遍历

.
.
.

InnoDB引擎 与 MyISAM引擎 的区别


# 主键索引的叶子节点保存的是行数据的物理地址,还是行数据本身?


# MyISAM索引文件和数据文件是分离的(非聚集),叶子节点保存的是行数据的物理地址
# innodb 叶子节点保存的是行数据本身

注意这里的data 可不一定代表的是行数据啊
在 MyISAM引擎里 data代表的是行数据的内存地址
在 innodb引擎里 data代表的是正真的行数据!!!由区别的

B+Tree 支结点不存储数据,只存键值+指针; 叶子结点不存储指针,存的是键值+data数据
image
.
.
.
.
.
.

MyISAM索引实现

叶结点的data域存放的是数据记录的地址。下面是MyISAM索引的原理图:
MyISAM的索引文件仅仅保存数据记录的地址,
在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,
只是主索引要求key是唯一的,而辅助索引的key可以重复

MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,
然后以data域的值为地址,读取相应数据记录。
image
.
.
.

InnoDB索引实现

虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。
叶结点data域保存了完整的行数据记录。这个索引的key是数据表的主键
可以看到叶结点包含了完整的数据记录,InnoDB的辅助索引data域存储相应记录主键的值而不是地址
image
.
image
.
.
.
.
.

面向对象知识总结


# 对象查找名字的顺序
先从自己的名称空间中查找,再去产生该对象的类中查找,再去继承的父类中找

-------------------------------------------------
# 多继承情况下父类中名字的查找顺序
菱形继承	广度优先(从左往右每条道走到闭环前的点,换下一条道,最后才会找闭环的点)

非菱形继承	深度优先(从左往右每条道走完为止)

# 对象.mro()   可以直接获取名字的查找顺序
-------------------------------------------------

# type与object的区别
    object是基类,任何的类都是继承自它
    # object是type产生的
    # 但是type又继承了object
    type是元类,任何的类都是type类产生的


------------------------------------------------
------------------------------------------------

# 子类重写父类的某个方法

class MyList(list):
    def append(self, values):      # 在子类里面再定义一个append方法
        if values == 'jason':
            print('jason不能尾部追加')
            return
        super().append(values)     # 子类再调用父类的append方法,if不走就走它
obj.append(111)
obj.append(222)
obj.append(333)
obj.append('jason')             # 就实现append方法不能往对象obj里面追加jason
print(obj)

-----------------------------

# 隐藏:  类在定义阶段 名字前面有两个下划线 那么该名字会被隐藏起来 无法直接访问
class Book:
    __name = 'haha'

    def __speak(self):
        print('111')

    def run(self):
        print(self.__name)   # 在内部可以直接,对象点名字拿到
        self.__speak()


obj = Book()
# print(obj._Book__name)   haha
# print(obj._Book__speak())   111    需要点 _类名.__隐藏的名字 ,才能拿到
obj.run()

# 在类的外部,要想访问隐藏的名字,需要点 _类名.名字 ,才能拿到
# 但是在类的内部,可以直接对象点名字拿到
--------------------------------------------------

# 伪装: 将类里面的方法伪装成类里面的数据,调用被伪装的函数时就不用加括号,对象点函数名,就运行函数了
class Book:
    @property
    def speak(self):
        print('hahaha')

obj = Book()
obj.speak   # 直接运行speak函数了

--------------------------------------------------

.
.
.
.
.

面向对象的双下魔法方法

# 类中定义的双下魔法方法,都是重写了,父类object里已有的方法双下魔法方法,
# 不需要去调用,在特定的条件下会自动触发运行

------------------------------------------

__init__   # 类创建空对象后触发,init方法作用是:给空对象添加独有数据

__str__  # 对象被执行打印操作的时候自动触发

__call__  # 对象叫括号的时候自动触发


__getattr__  # 对象点一个不存在的名字时,自动触发

__getattribute__   # 对象只要点名字就会自动触发
# 对象只要点名字就触发,有个坏处,对象想拿自己名称空间有的名字,没法正常拿了!!!
# 而且有了__getattribute__,对象点不存在的名字,只触发它,不会再触发__getattr__了
# 如果想拦截属性,可以重写该方法。

__setattr__    # 对象点名字设置值或改值时,会触发


__enter__    # 当对象 被with上下文管理的开始,自动触发, 且方法的返回值会被as后面的变量名f接收

__exit__   # with下面的子代码运行完毕后,会自动触发


__call__  # 对像加括号会触发类里面的双下call方法, 类加括号会触发元类里面的双下call方法

__new__   # 类加括号,先触发元类的双下new,再触发类自己的双下init,并把空对象传给self行参


__get__
# 一个类拥有双下get方法,该类产生的对象,又是另一个类的类属性对应的值
# 那么另一个类或者类的对象去调用该类属性的时候,就会自动触发该类里面的双下get方法

------------------------------------------
# 代码示范:
class Book(object):
    def __init__(self, name):
        self.my_name = name  # 会触发__setattr__方法
        print('打印init')

    def __str__(self):
        return f'哈哈{self.my_name}'

    def __call__(self):
        print(f'嘿嘿{self.my_name}')

    def __getattr__(self, item):
        print(f'对象点不存在的名字触发---{item}')

    # 对象只要点名字就触发,有个坏处,对象想拿自己名称空间有的名字,没法正常拿了!!!
    # 而且有了__getattribute__,对象点不存在的名字,只触发它,不会再触发__getattr__了
    # def __getattribute__(self, item):
    #     print(f'对象只要点名字就触发---{item}')

    def __setattr__(self, key, value):
        print('给对象设置值,或修改值时触发')


obj = Book('lqz')  # 先触发__setattr__方法   再打印init
print(obj)  # 哈哈lqz
obj()  # 嘿嘿lqz
obj.lyf  # 对象只要点名字就触发---lyf
obj.my_name  # 对象只要点名字就触发---my_name

---------------------------------------------

class Desc:
    def __get__(self, instance, owner):
        return '哈哈哈 TestDes:__get__'

class Main:
    des = Desc()

t = Main()
print(t.des)  # main类的对象点des属性,就会触发Desc类里面的双下get
print(Main.des)   # main类点des属性,就会触发Desc类里面的双下get

# 拥有双下get方法的类,产生的对象,是另一个类的类属性对应的值
# 那么另一个类或者类的对象去调用该类属性的时候,就会触发该类里面的双下get方法

---------------------------------------------
---------------------------------------------

# 面试题
补全Context类里面的代码,使得整体运行不报错
class Context:
    pass
with Context() as f:
    f.do_something()
-----------------------
class Context:
    def __enter__(self):
        return self
    def do_something(self):
        pass
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

with Context() as f:
    f.do_something()

---------------------------------------------
# 面试题
用面向对象的方法,生成一个字典,并让该字典可以通过点的方式,给字典添加值改值与取值
注意:python 自带的字典类型是不支持通过字典点的方式来进行取值的!!

class Mydict(dict):

    def __setattr__(self, key, value):
        self[key] = value

    def __getattr__(self, item):
        return self.get(item)

obj = Mydict()

obj.name = 'lqz'  # 对象点名字设置值或改值 ,会触发__setattr__
obj.age = 23    # 触发了__setattr__ , 就不会给对象设置值或改值了

print(obj.__dict__)  # {}  可以看到名称空间是没有name与age的
print(obj.age)  # 23  利用对象点不存在的名字
print(obj)  # {'name': 'lqz', 'age': 23}

--------------------------------------------------

.
.
.
.
.
.

面向对象之 元类


# type方法是用来查看产生对象的类的名字

class A:
    pass

class B:
    pass

print(type(A), type(B))  # <class 'type'> <class 'type'>
print(type(type))  # <class 'type'>  type类还是由type类产生的

-----------------------------
object 叫基类  所有类都继承它
type叫元类  所有类都是它产生的

-----------------------------
# 创建类的两种方式

# 方式一  直接定义
class Teacher:
    pass


# 方式二  由元类产生
cls = type(name, (bases,), dict)    # 传 类的名字、类的父类、类的名称空间   生成并返回一个类

-----------------------------

# 元类定制类的产生行为
    类加括号生成对象 ,触发类里的__init__
    # 元类加括号生成类的时候,会触发元类的__init__方法,该方法可以控制元类生成类的行为


# 元类定制对象的产生行为
    对象加括号会触发   产生对象的类里面的 __call__
    # 类加括号会触发  产生类的类(元类)里面的__call__


-----------------------------------------------
-----------------------------------------------

# 元类控制类的创建过程

#  需求:所有的类必须首字母大写 否则无法产生

class MyMetaClass(type):
    def __init__(self, what, bases=None, dict=None):
        print('what>>:', what)  # Haha 类的名字
        print('bases>>:', bases)  # 类的父类
        print('dict>>:', dict)  # 类的名称空间
        # 判断字符串首字母是否大写
        if not what.istitle():
            raise TypeError('懂不懂规矩 类名首字母应该大写啊!!!')
        super().__init__(what, bases, dict)  # 子类继承调用父类type里面的__init__方法


obj = MyMetaClass('Haha',(object,),{})

------------------------------------------------
------------------------------------------------

# 元类控制对象的创建过程

# 需求: 给对象添加独有数据的时候 必须采用关键字参数传参"

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        if args:  # 如果args接收到了值,说明用户有用位置参数传参了
            raise TypeError("Jason要求对象的独有数据必须按照关键字参数传参!!!")
        return super().__call__(*args, **kwargs)


class Student(metaclass=MyMetaClass):
    def __init__(self, name, age, gender):
        self.name = name
    def __call__(self):
        print(888)

obj = Student(name='jason',age= 18,gender= 'male')
# 该语法先触发元类里面的__call__方法,把括号里面的参数交给__call__去处理

print(obj.__dict__)  # {'name': 'jason'}
obj()  # 888
------------------------------------------------

# 总结

# 当我们在py文件里面定义了一个类以后,并且代码执行到了定义类的代码后
# 会触发元类里面的双下init,来控制类的生成

--------------------------------

# 类加括号生成对象,会先执行元类的双下call方法,
# 双下call方法里面干了3件事:

# 1 产生一个空对象(元类产生的类点自己的__new__方法)
# 2 调用类里面双下init(元类产生的类点自己的__init__方法)
# 3 返回实例化的对象

------------------------------------------------

.
.
.
.
.

什么是反射,python中如何使用反射


-反射:是程序在运行过程中,通过字符串来操作对象的属性和方法

    getattr(obj,'name')
    # 根据字符串获取对象与它的类里,对应的属性的值或方法对应的值(函数体代码的内存地址)

    setattr(obj,'name','value')
    # 根据字符串给对象设置属性,或修改对象的属性(只作用在自己的名称空间)

    hasattr(obj,'name')   # 判断对象与它的类里,是否有某一个字符串对应的属性或方法

    delattr(obj,'name')  # 根据字符串删除对象里面的名字,注意不是删类里面的名字!!!

--------------------
class Book:
    name = '宝贝'

    def speak(self):
        print('hahaha')


obj = Book()
print(hasattr(obj, 'speak'))  # True
getattr(obj, 'speak')()  # hahaha

setattr(obj, 'name', '畜生')  # 对象的名称空间设置了name='畜生'
print(obj.name)  # 畜生

setattr(obj, 'speak', '狗')  # 对象的名称空间设置了speak='狗'
print(obj.speak)  # 狗


delattr(obj, 'name')  # 对象名称空间删除了name='畜生'
print(obj.name)  # 宝贝  拿的是类里面的name对应的值

delattr(obj, 'speak')  # 对象名称空间删除了speak='狗'
obj.speak()  # hahaha

.
.
.
.
.
.
.
.

redis 的使用

五大数据结构:字符串、列表、哈希、集合、有序集合

3种特殊类型:BitMaps位图 、HyperLogLog基数统计 、GEO地理位置


redis通过pipeline管道  实现事务

.
.
.
.
.
.
.

装饰器的种类

当有多层装饰器: 加载顺序自下而上,执行顺序自上而下  # 直接记,原理有点烦
---------------------------------------
---------------------------------------

# 无参装饰器
@outer  # register = outer(register)
def register():
    pass


# 有参装饰器
def outer_plus(mode):
    def outer(func_name):
        def inner(*args, **kwargs):
            res = func_name(*args, **kwargs)
            return res
        return inner
    return outer

@outer_plus('lqz')   # register = outer(register)
def register():
    pass

# @outer_plus('lqz')的运行结果还是 @outer
----------------------------------------
----------------------------------------

# 1 函数装饰器
@outer  # register = outer(register)
def register():
    pass

-----------------------------------------

# 2 类作为装饰器
class Wrapper():
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # 前面加代码
        print('我在你前面')
        res = self.func(*args, **kwargs)
        # 后面加代码
        print('我在你后面')
        return res


@Wrapper  # add=Wrapper(add)
def add():
    print('add')
# Wrapper(add) 触发Wrapper类的双下init  并给类的对象添加了func方法
# 现在add是Wrapper类的对象


add()  # 对象加括号触发类里的双下__call__   运行func方法就是运行被装饰的函数

-----------------------------------------

# 3 加在类上面的装饰器(可以用装饰器给对象添加一些独有的属性或方法)
def speak():
    print('说话了')

def wrapper(cls):
    def inner(*args, **kwargs):
        res = cls(*args, **kwargs)
        res.name = 'lqz'
        res.speak = speak
        return res
    return inner

class Animal:
    pass

@wrapper  # 语法糖会把Person当参数传入到装饰器中   Person=wrapper(Person)
class Person(Animal):
    def __init__(self, hobby):
        self.hobby = hobby


p = Person('篮球')
print(p.name)
p.speak()
print(p.hobby)

# 类有继承父类,也一样,装饰器也能正常用

.
.
.
.
.
.

什么叫 缓存穿透 缓存击穿 缓存雪崩

---------------------------------------------------

# 缓存穿透是指:
缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,
造成数据库短时间内承受⼤量请求⽽崩掉。

# 解决⽅案
1. 业务逻辑前置校验  如请求携带的数据是否合理
2. 从缓存取不到的数据,在数据库中也没有取到,将其缓存设为空值 key-null或默认值,并设置一个过期时间
3. 访问频率限制,同一个ip或者id一分钟内最多访问多少次,防⽌攻击⽤户暴⼒攻击

# 使用布隆过滤器(重要)
4. 在写入数据时,将键使用布隆过滤器进行标记(相当于设置白名单),
    查询业务请求发现,缓存中无对应数据时,可先通过查询布隆过滤器,判断数据是否在白名单内,
    如果不在白名单内,则直接返回空或失败,不让走数据库了!!!

5. 用户黑名单限制

---------------------------------------------------
---------------------------------------------------

# 缓存雪崩是指:

缓存中大量热点数据同时过期,后⾯的请求都会落到数据库上,
造成数据库短时间内承受⼤量请求⽽崩掉。

# 解决⽅案
1. 缓存数据的过期时间设置偏长点并且是随机数,防⽌同⼀时间⼤量数据,过期现象发⽣。
2. 热点数据可以设置永不失效等
3. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
--------------------------------------------------
--------------------------------------------------

# 缓存击穿是指:
单个热点数据,在不停的扛着大并发,在这个热点数据失效的瞬间,持续的大并发请求,直接请求到数据库,
导致数据库压力骤增,这种情况就是缓存击穿

# 解决⽅案
热点数据设置永不过期

在value内部设置一个比缓存(Redis)过期时间短的过期时间标识,当请求获取缓存的value值时,
发现该值快过期时,马上延长内置的这个时间,并重新从数据库加载数据,设置到缓存中去。

给查redis缓存数据的操作加个互斥锁,牺牲一点redis的查询效率
这样第一个请求查redis没有,到数据库查,查出数据更新到redis
后续请求拿到锁后,再查redis时,已经能拿到数据,这样不会去数据库查了

.
.
.
.

布隆过滤器原理与使用


.
.
.
.

redis 使用 常用命令

注意在python中用的操作redis的命令,和直接在redis客户端的命令是不一样的


# python中使用普通连接

from redis import Redis  # 1 导入模块的Redis类
conn = Redis(host='127.0.0.1', port=6379)  # 2 实例化得到对象
conn.set('age',19)  # 3 设置值
conn.close()

---------------------------------------------
---------------------------------------------

# python中使用连接池连接    先单独建个文件,把连接池对象做成单例

import redis
POOL = redis.ConnectionPool(max_connections=100, host='127.0.0.1', port=6379)
# 创建一个最大连接数为100的redis连接池
---------------
import redis
from pool import POOL
# 做成模块后,导入,无论导入多少次,导入的都是那一个POOL对象

# 假设该方法是写在视图类或者视图函数中的
def task():
    # 从池里面拿一个连接
    conn = redis.Redis(connection_pool=POOL)
    # 如果报错了,可能是连接池的数量设置的太小,线程太多导致
    print(conn.get('name'))

---------------------------------------------
---------------------------------------------

import redis

conn = redis.Redis()

# 字符串相关操作
conn.set('hobby','篮球',ex=3)   # 设置键hobby 值'篮球' 过期3秒
conn.set('name','lqz',nx=True)  # 有键对应的值就不修改,没有就设置该键值对进去

conn.get('hobby')  # 获取值
res=conn.strlen('hobby')   # 统计值对应的字节长度
conn.decr('age'2)  # 自减2  不写数字就自减1
conn.incr('age',2)  # 自增2,不写数字就自增1

------------------------------
# 列表相关操作

res = conn.lpop('girls')    # 从左侧弹出数据
res = conn.llen('girls')   # 列表里面数据个数
res = conn.lrange('girls', 0, 1)  # 前闭后闭区间  按索引区间拿多个值
res = conn.lrange('girls', 0, -1)  # 拿列表里所有值 但列表里的元素是二进制的形式

conn.lpush('girls', '刘亦菲', '迪丽')   # 从左侧插入    redis默认是从列表右侧插入

res = conn.lrange('list1', 0, -1)
print('11'.encode('utf8') in res)   # 判断元素'11'在不在列表里
------------------------------

# 哈希相关操作
conn.hset('userinfo','name','lqz')  # 给userinfo对应的字典设置键与值
res=conn.hget('userinfo','name')  # 拿值
res=conn.hmget('userinfo',['name','age'])  # 批量拿值
res=conn.hlen('userinfo')    # 获取字典长度

------------------------------

# 集合相关操作
res = conn.sismember("test", "6")  # 判断元素6在不在 名字叫test的集合里面  在就返回true
res = conn.sinter(["tag1","tags2"])  # 返回名字叫tag1、tags2集合的交集

------------------------------
# 其他操作

res=conn.type('girls')  # 判断数据的类型
res=conn.randomkey()  # 随机弹出一个数据的名字(键)
res=conn.keys('*')  # 拿库里面所有数据对应的名字
res=conn.keys('w?')  # 拿库里面所有数据对应的以w开头,后面只有一个任意字符的名字
res=conn.exists('userinfo')  # 判断库里面有没有userinfo名字的数据

.
.
.
.

redis客户端里常敲的命令


expire(name ,time)   # 设置数据的过期时间(秒)

TTl(name)  # 获取键对应数据还有多长时间过期

setex name 过期时间S value   # 添加值,并添加过期时间

keys *   # 查看所有键

exists name  # 如果直接判断名字对应的数据在不在


.
.
.
.
.
.

redis的管道


# 事务---》四大特性:原子性、一致性、隔离性、持久性

# redis通过管道可以支持事务!!!

# 集群的redis是不支持事务的
   (多机集群情况下,每台机器都起来redis服务,但所存放的redis数据是不一样的,都加起来,才是总数据)

# 只有单实例,才支持所谓的事务(单实例是只有一台机器,只跑了一个redis的服务)
# 支持事务是基于管道的

------------------------------------------------

-执行 张三转账给你 的命令  一条一条执行
	张三 金额 -100    conn.decr('zhangsan_je',100)
	你   金额 100     conn.incr('lisi_je',100)
	# 但是现在,在张三转弯帐后,突然断电程序挂了,你的账户没加上去,出现了数据问题了

# 当把这两条命令,放到一个管道中,先不执行,当执行excute,一次性执行多个命令

------------------------------------------------
------------------------------------------------

# redis中管道如何使用
import redis

# 模拟转账操作
conn = redis.Redis(host='127.0.0.1', port=6379)   # conn是Redis类产生的连接对象

p_obj = conn.pipeline(transaction=True)  # 开启事务   pipeline方法调用后返回的是管道对象
p_obj.multi()  # 提供一个管道

p_obj.decr('zhangsan_je', 100)
# raise Exception('断电或程序崩了')  # 这样后程序崩了,zhangsan数据就不会减了
p_obj.incr('lisi_je', 100)


p_obj.execute()  # 一次性把管道里所有命令,按顺序全部执行,期间有一个命令执行失败,回滚到开始
conn.close()


# redis通过管道实现 支持事务
------------------------------------------------

.
.
.

redis为什么并发量高和速度快


纯内存的操作,读写速度非常快
单线程避免了线程间的切换
io多路复用,一个线程可以接受特别高的并发

.
.
.

redis的长慢命令优化


# 单线程架构,命令一个个执行
# 如果有长慢命令,会造成整个redis的阻塞

# 设置一下慢查询队列的最大长度,以及最大查询时间,
# 查询语句的查询时间超过该时间,就会放到慢查询日志里面去

slowlog get     # 获取慢查询队列的所有慢命令

# 哪些命令会造成慢查询
使用复杂度过高的命令: O(N)以上的命令(求两集合的并集、数据的排序等命令)
    复杂度过高的命令,底层代码的执行次数多,所以更耗时

大value问题: value的值过大   key对应的值尽量避免写入过大

数据集中过期: 将过期时间随机化,分散集中过期的时间,降低 Redis清理过期 key的压力

.
.
.

Bitmap的使用原理

位图的使用
	假设:1亿用户,5千万活跃用户
	要统计每一天的活跃用户是多少,怎么办?

首先每天按日期在redis里面创一个对应的字符串
比如 set data1 c                        # 随便设置个简单的字符串 c
每天0点计数器归零,每来一个用户登录 setbit data1 n 1
# 给位图对象按计数器数字,给对应位置设置为1
# 这样一天下来,只要用 bitcount data1  统计一下该字符串里面有多少个1
# 就是今天所有的登录用户数了

.
.
.
.
.

redis的集群


.
.
.

redis 怎么持久化


使用RDB 或者AOF  或者混合持久化方案来实现
都是要在redis的配置文件里面,配置一些参数,设置触发持久化方案的条件


RDB文件恢复到内存的速度快,但存在丢失数据的风险
aof文件,设置每秒从缓冲区刷到硬盘,这样系统崩了,也只会丢一秒的数据,
但是aof文件恢复到内存的速度慢,要一条一条的执行

混合持久化的方案,是每次aof重写的时候,拍个快照生成rdb数据,并将这些数据,写入新建的AOF文件中
在AOF重写开始之后,执行的Redis命令,则会已aof记日志的形式,
追加到刚刚新建的AOF文件中的rdb数据的后面

# redis持久化过程中出错了怎么恢复数据
重启redis就行了,redis会自动加载持久化文件到缓存里面去的

# 换句话说,在开启了RDB-AOF混合持久化功能之后,服务器生成的AOF文件将由两个部分组成,
    其中位于AOF文件开头的是RDB格式的数据,而跟在RDB数据后面的则是AOF格式的数据

# Redis服务器启动并载入AOF文件时,它会检查AOF文件的开头是否包含了RDB格式的内容
# 如果包含,那么服务器就会先载入开头的RDB数据,然后再载入之后的AOF数据
# 如果AOF文件只包含AOF数据,那么服务器将直接载入AOF数据

image
.
.
.
.
.

django中使用mysql 的主从与读写分离


.
.
.
.
.
.
.

黑白名单机制实现


# 白名单机制实现:
比如项目ip地址的白名单,公司内部的系统,不是公司的内部的ip地址不能访问
(一般是用redis的集合,在中间件或者权限类里面检查ip地址在不在集合里面
  sismember set88 v1  )

# 黑名单机制实现:
检测到一些恶意ip,直接放到黑名单里,拒绝该用户访问!!!  也是用redis的集合做方便

.
.
.
.

cookie session token 大致原理


session 是存在后端的    cookie是存在前端的
-------------------------------------------
-------------------------------------------

# cookie
账户密码校验没问题后,生成cookie,放到重定向对象里
obj = redirect('/home/')    # 重定向的整体代码看成是一个对象
obj.set_cookie('name', username)    # 添加cookie信息到对象
return obj     # 执行重定向

后端将cookie放在响应头里面,返回响应对象给前端浏览器,浏览器从响应对象里面,拿出响应头里的cookie
放到浏览器里面

浏览器会将存在浏览器里面的所有cookie,在发送请求的时候,会放到请求头里面

另一个请求到后端后,从请求头里面,获取name键对应的值,有的话就认为登录了
request.COOKIES.get('name'):

-------------------------------------------
-------------------------------------------

# django 中有一个session表

登录成功后
request.session['key'] = value  # 生成随机字符串 与加密的value 放到session表中
django的中间件会自动将随机字符串,放到响应头里面,发送给前端,浏览器拿到头里面的session字符串存起来

当前端请求头中带着该随机字符串,到后端后,
中间件会自动获取请求头里面的 随机字符串,去django_session表查下有没有该随机字符串
要是有,再根据该随机字符串 拿session表中加密的数据
并自动解密数据并处理到request.sesion.get()中

# 如果中间件中,发现请求头中的随机字符串,在session表中没有,说明随机字符串有问题
# 在中间件就返回了,进入不了视图函数了

-------------------------------------------
-------------------------------------------
# token机制
django有自带的jwt登录认证类
token的使用大致流程:

一般情况下,用户登录时,如果用户名与密码校验没问题后,
用token机制的话,先用该用户对象生成荷载
再用荷载生成token
最后将token返回给前端

不管jwt自带的认证类还是自己写的认证类,都会有如下操作:
payload = jwt_decode_handler(token)
# 重点代码 用token获取荷载,并且还校验了token串里面的签名是否正确
就是用秘钥将token中解码出的头与荷载,用同样的算法生成签名,
与token里解析出的签名比较,判断签名是的正确,签名通过
然后再用荷载拿到用户id,通过ORM可以拿到用户对象了
最后 认证类的authenticate方法  返回用户对象与token

用户对象与token 后续又解压赋值给了request对象的 user 与auth 属性
所以只要请求过了用户登录认证类,
就可以用request对象去点user拿到当前登录用户的数据库对象
                    点auth就能拿到token

-------------------------------------------

.
.
.
.

21 种设计模式

何为设计模式?

设计模式的分门别类

2、工厂方法(Factory Method)模式

.
.

单例(Singleton)设计模式

# 4、单例(Singleton)设计模式
from threading import Thread, Lock
import time


# 1 利用装饰器来实现,每一个想实现的单例的类,在上面加个该装饰器函数就行
def singleton(cls):
    _instance = {}
    lock = Lock()

    def wrapper(*args, **kwargs):
        if cls not in _instance:
            lock.acquire()
            if cls not in _instance:
                _instance[cls] = cls(*args, **kwargs)
            lock.release()
        return _instance[cls]

    return wrapper


# 这个地方 为什么要判断类的属性是否为空? 高并发的情况下  如果前几个子线程都先判断出_instance为None
# 那么就都会对象实例化,生成的对象就不一样了  但是让先判断出_instance为None的子线程,再抢锁,枪完锁后,让类生成对象
# 这这样第二个子线程在抢到锁后,再判断时,_instance已经有值了,就在放锁了,其他执行慢的子线程,在走第一个判断时,发现有值
# 直接连抢锁的操作都不走了!!后面的子线程就都没有抢锁放锁的操作了,直接返回对象了

# 这个地方如果先定义_instance = None 不让等于空字典  那么就要在闭包函数里面先申明该变量是nonlocal
# 内层局部名称空间直接修改外层局部名称空间中的数据,要在内层对变量先用nonlocal申明


@singleton
class B:
    def __init__(self):
        time.sleep(1)  # 模拟并发情况下,对象实例化时有网络延迟

    def __getattr__(self, attr):
        return attr


def task():
    obj = B()
    print('\n')
    print(id(obj))


for i in range(50):
    t = Thread(target=task, )
    t.start()


---------------------------------------------
from threading import Thread, Lock
import time

lock = Lock()


# 2 在类里面写一个绑定给类的方法,并且在类里面先写一个类属性_instance = None
# 这样在类的方法里去做判断,类属性有值,就直接返回类属性,如果类属性没有值,就让类名加括号传init需要的参数
class B:
    def __init__(self, name):
        self.name = name
        time.sleep(1)  # 模拟并发情况下,对象实例化时有网络延迟

    _instance = None

    @classmethod
    def singleton(cls):
        if cls._instance is None:
            lock.acquire()
            if cls._instance is None:
                cls._instance = cls('teng')
            lock.release()
        return cls._instance


def task():
    obj = B.singleton()   # 该方法的返回值,对象就是单例
    print(id(obj))


for i in range(5):
    t = Thread(target=task, )
    t.start()


-----------------------------------

.
.
.
.

5、生成器(Builder)模式

-----------------------------------------------
10、观察者(Observer)模式
定义:观察者(Observer) 模式定义对象间的一种一对多的依赖关系,
     当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。


-----------------------------------------------
12、迭代器(Iterator Pattern)模式
14、装饰器(Decorator)模式



-----------------------------------------------
15、责任链(Chain of Responsibility)模式

16、状态(State Pattern)模式

17、策略(Strategy)模式

18、命令(Command) 模式

19、享元(Flyweight)模式

20、代理(Proxy Pattern)模式

21、备忘录(Memento Pattern)模式



1、原型(Prototype)模式

3、抽象工厂(AbstractFactory) 模式




6、适配器(Adapter Pattern)模式

7、桥接(Bridge)模式

8、外观(Facack) 模式

9、中介者(Mediator)模式

11、组合(Composite)模式


13、访问者(Visitor Pattern)模式


image
.
.
.
.

mysql表如果没有设置主键


如果有一个字段是唯一索引,会用该字段作为主键
如果也没有唯一索引,会生成一个隐藏主键,目的就是为了用该隐藏主键构建B+树

.
.
.
.
.

python基础

求结果:or and


   v1 = 1 or 3       # 1   or 是左边符号就取左边
   v2 = 1 and 3     # 3      and是两边都符合取右边的
   v3 = 0 and 2 and 1     # 0    如果有不符合直接取不符合
   v4 = 0 and 2 or 1  # 1     or 有符合的就取符合的那一边
   v5 = 0 and 2 or 1 or 4     # 1
   v6 = 0 or False and 1  # False

.
.
.
.
.

字符串


s1 = 'helloworld'    s2 = 'kk'

s1[1:5]       # 左闭右开 从索引1一直切取到索引4   结果 ello
len(s1)    # 求长度
s1.strip()   # 移除字符串首尾指定的字符 不写就是移除空格
s1.split('elloworl')   # 切割掉字符串中指定的字符,剩下的部分放在元组里面  ['h','d']
s1.upper()    # 字符串字母转大写
s1.replace('elloworl','ar')   # 替换
'aaa'.join([s1,s2])   # 字符串件插入并拼接

res = 'helLO wORld hELlo worLD'
print(res.index('wORld'))    # 找不到直接报错
print(res.find('wORld'))   # 找不到默认返回-1    find用的比较多!!!

.
.
.
.

列表 的常见使用场景


可以肤浅的将列表理解为一个无限长,无限宽的抽屉,它可以装任何我们想要装的元素(自动扩容),
并且所有装进去的元素都是根据装进去时间先后排序的

列表可以将大量的任务装在其中,通过循环我们可以取出其中的任务,并且使用相同的代码结构来处理任务
可以简化我们的代码量

l1 = [111, 222, 333, 444, 555, 666, 777, 888]    l2 = [999,]
l1[0] = 123  # 按索引修改列表数据
l1.append('干饭')   # 尾部追加
l1.insert(0, 'jason')  # 按索引位置往列表插入数据  原数据整体往后
l1 + l2  # l2列表里数据整体加到l1列表里
l1.remove(444)  #移除
l1.pop(1)  # 按索引弹出
l1.sort(reverse=True)  # 改成降序排序

.
.
.
.

元组 的应用场景


t1 = (11, 22, 33, 44, 55, 66)
# 元组内索引绑定的内存地址不能被修改!!!
# 元组不能新增或删除数据!!!
# 列表中的元素可以修改,而元组之中的元素不可以修改。


# 元祖的应用场景   1 打包解包   2 让函数返回多个值

    # 把多个用逗号分隔的值赋给一个变量时,多个值会打包成一个元组类型;
    # 把一个元组赋值给多个变量时,元组会解包成多个值然后分别赋给对应的变量,
    # 函数的返回值是多个值的时候,会组装成一个元祖返回

t = 11, 22, 33, 44, 55, 66
print(type(t))  # <class 'tuple'>
x, y, z, i, j, k = t
print(x, y, z, i, j, k)

.
.
.
.
.
.

字典 的应用场景


字典(Dictionary)是一种无序的数据结构,用于存储键值对(key-value pairs)的集合。
每个键(key)都是唯一的,而对应的值(value)可以重复。
字典的特点是使用键来访问值,而不是使用索引。



user_dict = {'username': 'jason','password': 123}
user_dict.get('username')  # 取值
user_dict['username'] = 'tony'  # 该值

res = user_dict.pop('password')  # 将字典里面键对应的一组键值对都弹出字典了 res接收到弹出的值
del user_dict['username']   # 直接将这一组键值对都删除了

user_dict.popitem()  # 弹出键值对 先弹最后后放进去的!!

# 字典三剑客  得到的数据都在列表里面!!!
user_dict.keys()   # 一次性获取字典所有的键
user_dict.values()   # 一次性获取字典里所有的值
user_dict.items()   # 一次性获取字典里所有的键值对数据,放在一个个元组里

.
.
.
.
.
.

for循环注意问题


# 列表外给定一个值,当要删除列表里多个和该值相同的值时
# 无论for循环的是列表的索引还是列表的值,也无论用的是列表点pop(索引)删  还是 点remove(值)删
# 都有问题,删掉一个数据后列表的长度就变短了,最后都会报索引超出范围的错!!!

-----------------------------------------------
# for 是按索引位置依次拿值的,依次拿索引的 0 1 2 3

a = [1, 2, 3, 4, 5]
for i in a:
    a.remove(i)
    print(a)

# 第一次  [2, 3, 4, 5]      第一次删索引0的位置,这个时候列表已经变了
# 第二次  [2, 4, 5]         第二次删索引1的位置,上面列表删1的位置删的3
# 第三次  [2, 4]            第三次删索引2的位置,同理删上面列表2的位置删的是5
# 所以用for循环 是删不干净列表里的值的

------------------------------------------------
# 删除列表里的1元素

l1 = [1, 1, 12, 3, 4]
for i in range(5):
    if l1[i] == 1:
        l1.pop(i)
        print(l1)
# 会报错 IndexError: list index out of range
# 列表超出索引的范围,为什么?
# i一开始为0的时候,移除了列表的0位的1,这时候列表其他位置的数据会往前移动变成 [1, 12, 3, 4]
# 此时该列表的索引1的位置是12  不符合 , 索引2的位置是3 也不符合
# 索引3的位置是4 也不符合   此时的列表没有索引4,
# 所以range最后一次for循环出的4  已经超出了列表的索引范围

------------------------------------------------

# 同理用remove也是不行
a = [1, 1, 2, 1, 3, 4, 5]
for i in a:
    if i == 1:
        a.remove(i)
print(a)  # [2, 1, 3, 4, 5]

------------------------------------------------

.
.
.
.
.
.
.

导入问题 重要


# 在一个s20文件里创建对象
class B:
    pass
b = B()
---------------
# 在另一个s21文件里导入该对象
from s20 import b
---------------
# 再在另一个文件里面,给对象添加私有属性
from s21 import b
b.name = 123    # 每一个py文件运行完,所有存储在内存中的数据全部会丢失
---------------
# 再在其他文件里面,拿对象的属性
from s21 import b
print(b.name)    # 拿不到 会报错   每一个py文件运行完,所有存储在内存中的数据全部会丢失

-----------------------

# 所以要想实现跨文件给对象添加值,与获取值, 需要把文件写在项目里
# 项目启动以后不停,就可以实现跨文件给一个对象,添加值,与修改值


# 如果是一个项目,项目运行起来后,会把所有的文件,能运行的都运行一遍,所有文件在一个进程里面
# 所以根据同一个文件不会被导入两次的原理,在一个项目的不同文件夹下,导入的对象,都是同一个对象


# 如果不是一个项目,单py文件运行,该py文件就是一个进程,文件运行结束,进程里面的数据就被回收了
# 所以单py文件运行一次后,导入的对象就被销毁,另一个单py文件再运行,导入的对象又是一个新的对象了!!

-----------------------

.
.
.
.
.

双token认证


pip3 install djangorestframework-simplejwt -i https://pypi.douban.com/simple
# 该模块中提供双token


在登陆成功的时候,
后端返回两个token给前端,一个是有效期长的token,一个是有效期短的token给前端
登录成功后,前端发送请求都是带着有效期短的token,去后端进行校验,有效期短的token一旦过期
前端就需要再带着有效期长的token去后端校验,后端确认长token没有问题后,
再签发一个短token给前端,然后前端再带着短token去发送请求。
# 长token只有在登录成功后,从后端发给前端      及短token过期后,从前端发送到后端
# 被抓包软件抓到的概率会低一点

----------------------------

# 还有一种方式,让生成的token与登录时的ip地址做绑定,这样别人就算抓到token,
# 他的电脑ip的地址还是过不了认证!!! 除非别人又拿到你的token,又拿到你的ip地址,概率比较小

----------------------------

.
.
.
.
.


http请求的所有东西,都在request对象里面,本身是一个http请求,到了python中被封装成了request一个对象


视图函数最后返回的response对象里,包含了http响应的所有东西,

.
.
.

闭包函数参数延时性 问题


def func_outer():
    func_list = []
    for i in range(4):
        def func_inner():
            return i * i

        func_list.append(func_inner)
    return func_list


for func in func_outer():
    print(func())  # 9 9 9 9

# 原因是:func_inner函数并没有被调用, 所以添加到列表里面的的4份func_inner函数还没有传值了
# 所以func_outer函数的执行结果就是,往列表里面加了4个func_inner函数
# 每一个func_inner函数运行的时候,需要用到变量i,此时func_outer函数里面的for循环已经执行完了
# 此时i变量名对应的值是3,所以每一个func_outer函数执行的都是,3 * 3  所以结果都是9

.
.
.

关于生成器表达式的一些注意问题


def aaa():
    print(1)


l1 = [aaa() for i in range(4)]  # 列表生成表达式

l2 = (aaa() for j in range(4))
# 这个特殊他是一个生成器,所以aaa()不会运行,必须要对生成器for循环的时候,才会执行aaa函数


print(l1)
print(l2)
for k in l2:
    print(k)

.
.
.

关于匿名函数的调用问题


def aaa():
    return [lambda x: x * i for i in range(4)]
    # lambda匿名函数 整体类似于代表的是是一个函数名,不会被主动执行
    # 需要用res = lambda x: x * i  接收再用res() 才能运行该匿名函数
    # 内置函数中配合匿名函数使用时,底层会自动去运行匿名函数
    # 所以列表里面是4个匿名函数 [ lambda x: 3x , 总共4个匿名函数 ]

print(aaa())  # 结果对应的是4个匿名函数的内存地址

print([m(2) for m in aaa()])  # [6, 6, 6, 6]

# 所以最后对列表for循环出的每一个,匿名函数加括号运行,并传2进去,最后的结果全是6

.
.
.
.

关于元祖的使用注意事项


a = (1, 2, 4, [1, 3, 5])
res = a[3].append(2)
print(res)  # None
print(a)  # (1, 2, 4, [1, 3, 5, 2])

a[3] = a[3].append(2)  # 报错!!!

# 原因
# 元祖的元素是不可变类型,所以对应的元素是列表,你在列表里面添加数据不会报错
# 但是你要改变元祖里该列表元素的数据类型,就会报错了

.
.
.
.
.
.
.
.

彻底搞懂 迭代器,生成器,与可迭代对象

----------------------------------------------

# 迭代:一种不依赖于索引取值的方式,
       可以通过 for循环 或者 点双下next() 一个个的取值,它就称之为 迭代

-----------------------------------------------

# 可迭代对象:

    但凡对象内置有__iter__方法,并且返回值是一个迭代器对象, 那么该对象称为可迭代对象!
    比如 字典,列表,字符串,元组,集合,文件对象

---------------------------------------------------

# 迭代器官方定义:
当类中定义了__iter__方法与__next__方法,并且__iter__方法方法返回了对象本身即self
每执行一次__next__方法,返回一个数据,如果没有数据了,则需要主动抛出一个异常
这个类就是迭代器类,
该迭代器类实例化生成的就是迭代器对象!!!


------------------------------------------------------

# 可迭代对象调用__iter__方法,为什么会变成迭代器对象
    因为__iter__⽅法,返回了⼀个迭代器对象

------------------------------------------------------

# 生成器:生成器本质就是迭代器
    函数中只要有yiele关键字,这个函数被加括号调用,它就变成了生成器!!!

---------------------------------------------------

# 生成器的作用:可以获得庞大的数据,同时占用内存小,节约内存空间
什么样的场景可以用到迭代器或生成器:
比如有一个数据量很大的数据源,数据源不在内存中,比如在数据库中,
现在要用sql语句查数据库里面很多条数据,这种情况下,
把查的代码做成迭代器后,就会节约内存空间,不用一次性把数据全部加载到内存中去,
迭代一次,从数据库查一点,加载到内存中

.
.

生成器,迭代器器,可迭代对象详解


# 创建一个迭代器类,并用该类实例化生成迭代器对象

from builtins import StopIteration


class Teng(object):
    def __init__(self):
        self.counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1
        if self.counter == 5:
            raise StopIteration()
        return self.counter


obj2 = Teng()
res1 = obj2.__next__()
res2 = obj2.__next__()
res3 = obj2.__next__()
res4 = obj2.__next__()
res5 = obj2.__next__()  # 走到这步会报错,因为主动抛异常了
print(res1, res2, res3, res4, res5)

obj1 = Teng()
for i in obj1:
    print(i)  # 可以把迭代器对象里面的值一个一个的取出来,一直取完
    # for 循环的底层会自动捕获异常

# 迭代器对象支持通过__next__方法取值,如果取值结束,则自动抛出异常
# for循环底层是先用迭代器对象先点__iter__方法,获取返回值迭代器对象本身,
# 然后再通过对象点__next__方法,并将返回值赋值给变量i
# 对象点__next__方法,运行过程中出现异常,会被捕获,并结束迭代器对象点__next__方法的取值

# 所以我们用到的for循环,它的内部实际上是基于迭代器的,基于迭代器的__next__方法,
# 来实现迭代取值的

-----------------------------------------
-----------------------------------------

# 创建一个生成器对象
def func():
    yield 1
    yield 2


# 创建生成器对象,(内部是根据解释器自带的生成器类,generator类创建的对象),生成器类的内部也声明了:__iter__,__next__方法)
obj1 = func()  # 得到生成器对象,该对象中也有__iter__,__next__方法
res1 = obj1.__next__()
res2 = obj1.__next__()
res3 = obj1.__next__()  # 取不到值的时候,会自动报错

obj2 = func()
for i in obj2:
    print(i)  # for循环取不到,报错被捕获,并结束for循环

# 所以根据迭代器的定义,生成器也是一种特殊的迭代器


-----------------------------------------
-----------------------------------------

# 可迭代对象官方定义:
# 如果一个类里面定义了__iter__方法,并返回了一个迭代器对象
# 则我们称这个类创建的对象为可迭代对象!!!

def func():
    yield 1
    yield 2
    yield 3


obj1 = func()


class Foo(object):
    def __iter__(self):
        # return 迭代器对象
        return obj1


obj2 = Foo()  # 就生成了可迭代对象了!

# 如果一个对象是可迭代对象,那么该对象将可以被for循环,因为for循环底层是先把对象点__iter__方法,拿到返回值(迭代器对象),
# 然后通过返回值去点__next__方法,去迭代取值

for i in obj2:
    print(i)

# 迭代器对象与可迭代对象,都可以被for循环,迭代取值!!!


.
.

python的内置函数 range就是一个可迭代对象

------------------------------------

res = range(10)  # 可迭代对象
print(dir(res))  # 能看到里面有__iter__方法,但没有__next__方法

res1 = res.__iter__()  # 迭代器对象
print(dir(res1))  # 能看到里面有__iter__方法,与__next__方法

------------------------------------

class IterRange(object):
    def __init__(self, max_num):
        self.max_num = max_num
        self.counter = -1

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1
        if self.counter == self.num:
            raise StopIteration()
        return self.counter

------------------------------
# 配合可迭代类,与迭代器对象,构建自定义Xrange方法,Xrange就是一个可迭代对象

class Xrange(object):
    def __init__(self, max_num):
        self.max_num = max_num

    def __iter__(self):
        return IterRange(self.max_num)


obj = Xrange(100)
for i in obj:
    print(i)


--------------------------------
# 还可以配合可迭代类,与生成器对象,构建自定义Xrange方法,Xrange就是一个可迭代对象

class Xrange(object):
    def __init__(self,max_num):
        self.max_num = max_num
    def __iter__(self):
        counter = 0
        while counter < self.max_num:
            yield counter
            counter +=1

obj = Xrange(100)
for i in obj:
    print(i)

---------------------------------------

.
.
.

python中 列表与字典 都是可迭代对象


from collections.abc import Iterator, Iterable

# Iterable判断对象是不是可迭代对象  Iterator判断对象是不是迭代器对象
v1 = [11, 22, 33]
print(isinstance(v1, Iterator))  # False
v2 = v1.__iter__()
print(isinstance(v2, Iterator))  # True


print(isinstance(v1, Iterable))  # True
print(isinstance(v2, Iterable))  # True

.
.
.
.

Django ORM中的N+1问题


# 假设有以下模型
class Book(model.Model):
    """书籍"""
    models.ForeignKey(Author)  # 这里忽略了其他参数,请注意
    pass

class Author(model.Model):
    """作者"""
    models.ForeignKey(Country)
    pass

class Country(model.Model):
    """城市"""
    pass

--------------------------------------

# 假设有如下查询代码,假设book表里面有100条数据
books = Book.objects.order_by("title")
for book in books:
    print(book.title, "by", book.author.name)

# 那么通过for循环得到的N个对象,每一个对象点author外键,都会执行一次sql
# 所以最后执行了N+1次的sql语句

# 如果 n 累计下来,这里就会产生严重的性能问题
--------------------------------------

如果我们再获取作者的城市信息
books = Book.objects.order_by("title")
for book in books:
    print(
        book.title,
        "by",
        book.author.name,
        "from",
        book.author.country.name,
    )

# 循环第三步每次循环将增加对 country 表的查询,所以现在的总查询次数是 2n+1

--------------------------------------
--------------------------------------

# 如何解决 N+1 问题

django 给我们提供了两个将多次查询集成为一个复杂查询的方法,select_related 和 prefetch_related

# select_related  底层基于连表的查询
# prefetch_related  底层基于子查询

# 如果正常用表模型对象点关联表里面的字段时,肯定会走sql查询,但是用了select_related后
# 底层是先连表后查询,再封装到书籍对象里面去
books = Book.objects.order_by("title").select_related("author")
for book in books:
    print(book.title, "by", book.author.name)

# 先将书表和作者表拼到一起,然后把大表里面的对应的数据封装到一个个书籍对象里面去
# 这样再用对象去点外键表里面的字段的时候就不会再走sql查询了
# 所以用了select_related后,最后总共执行了1次sql,而不是 N+1次了

# 所以用了prefetch_related后,最后总共执行了2次sql,而不是 N+1次了
--------------------------------------

.
.
.
.
.

web服务器的介绍与作用


# 实现WSGI协议的web服务器:
uWSGI:     用c语言写的,性能比较高
wsgiref:   性能较低,用于开发环境
werkzeug:  符合wsgi协议的web服务器+工具包(封装了一些东西),flask使用werkzeug
gunicorn:  python写的
tornado:   tornado包含了框架和服务器

---------------------------------------------------

# web服务器实现了什么?
将客户端发送的HTTP请求,转化到python变量。
将python变量中的数据,转化为HTTP响应。

---------------------------------------------------

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

posted @   tengyifan  阅读(118)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示