个人介绍
面试官你好
自我规划
对于我未来的职业生涯规划
python的内存管理
python的内存管理机制
关于python的存储问题
(1)由于python中万物皆对象,所以python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它
(2)对于整数和短小的字符等,python会执行缓存机制,即将这些对象进行缓存,不会为相同的对象分配多个内存空间
(3)容器对象,如列表、元组、字典等,存储的其他对象,仅仅是其他对象的引用,即地址,并不是这些对象本身
关于引用计数器
(1)一个对象会记录着引用自己的对象的个数,每增加一个引用,个数加一,每减少一个引用,个数减一
(2)查看引用对象个数的方法:导入sys模块,使用模块中的getrefcount(对象)方法,由于这里也是一个引用,故输出的结果多1
(3)增加引用个数的情况:1.对象被创建p = Person(),增加1;2.对象被引用p1 = p,增加1;3.对象被当作参数传入函数func(object),增加2,原因是函数中有两个属性在引用该对象;4.对象存储到容器对象中l = [p],增加1
(4)减少引用个数的情况:1.对象的别名被销毁del p,减少1;2.对象的别名被赋予其他对象,减少1;3.对象离开自己的作用域,如getrefcount(对象)方法,每次用完后,其对对象的那个引用就会被销毁,减少1;4.对象从容器对象中删除,或者容器对象被销毁,减少1
(5)引用计数器用法:
import sys
class Person(object):
pass
p = Person()
p1 = p
print(sys.getrefcount(p))
p2 = p1
print(sys.getrefcount(p))
p3 = p2
print(sys.getrefcount(p))
del p1
print(sys.getrefcount(p))
多一个引用,结果加1,销毁一个引用,结果减少1
(6)引用计数器机制:利用引用计数器方法,在检测到对象引用个数为0时,对普通的对象进行释放内存的机制
关于循环引用问题
(1)循环引用即对象之间进行相互引用,出现循环引用后,利用上述引用计数机制无法对循环引用中的对象进行释放空间,这就是循环引用问题
(2)循环引用形式:
class Person(object):
pass
class Dog(object):
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
即对象p中的属性引用d,而对象d中属性同时来引用p,从而造成仅仅删除p和d对象,也无法释放其内存空间,因为他们依然在被引用。深入解释就是,循环引用后,p和d被引用个数为2,删除p和d对象后,两者被引用个数变为1,并不是0,而python只有在检查到一个对象的被引用个数为0时,才会自动释放其内存,所以这里无法释放p和d的内存空间
关于垃圾回收(底层层面--原理)
(1)垃圾回收的作用:从经过引用计数器机制后还没有被释放掉内存的对象中,找到循环引用对象,并释放掉其内存
(2)垃圾回收检测流程:
一.任何找到循环引用并释放内存:1.收集所有容器对象(循环引用只针对于容器对象,其他对象不会产生循环引用),使用双向链表(可以看作一个集合)对这些对象进行引用;2.针对每一个容器对象,使用变量gc_refs来记录当前对应的应用个数;3.对于每个容器对象,找到其正在引用的其他容器对象,并将这个被引用的容器对象引用计数减去1;4.经过步骤3后,检查所有容器对象的引用计数,若为0,则证明该容器对象是由于循环引用存活下来的,并对其进行销毁
二.如何提升查找循环引用过程的性能:由一可知,循环引用查找和销毁过程非常繁琐,要分别处理每一个容器对象,所以python考虑一种改善性能的做法,即分代回收。首先是一个假设--如果一个对象被检测了10次还没有被销毁,就减少对其的检测频率;基于这个假设,提出一套机制,即分代回收机制。
img
通过这个机制,循环引用处理过程就会得到很大的性能提升
关于垃圾回收时机(应用层面--重点)
(1)自动回收:
img
(2)手动回收:这里要使用gc模块中的collect()方法,使得执行这个方法时执行分代回收机制
import objgraph
import gc
import sys
class Person(object):
pass
class Dog(object):
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
其中objgraph模块的count()方法是记录当前类产生的实例对象的个数
关于内存管理机制的总结(重点)
综上所述,python的内存管理机制就是引用计数器机制和垃圾回收机制的混合机制
三元表达式
x=4
y=2
print(x if x > y else y)
闭包函数的应用
闭包的意义:返回的函数对象,不仅仅是一个函数对象,还在函数外包裹了一层作用域,这就使得这个函数无论在哪调用,都优先使用自己外层包裹的作用域。
应用领域:延迟计算、爬虫
迭代器
具有__iter__()方法的就是可迭代对象,除了数字类型和函数类型都是可迭代对象
x = 1 # 不可迭代对象
s = 'jiayi' # 可迭代对象
lt = [1, 2, 3] # 可迭代对象
dic = {'a': 1, 'b': 2} # 可迭代对象
tup = (1,) # 元组只有一个元素必须得加逗号# 可迭代对象
se = {1, 2, 3} # 可迭代对象
f = open('time.py') # 可迭代对象
str="ypp"
print(type(str))#<class 'str'>
str_iter=str.__iter__()#把str变为迭代器对象
for i in str:
print(i) #基于索引(基于上一次结果),通过__next__进行迭代,for循环可以使得可迭代对象变成迭代器
匿名函数
匿名函数就是没有名字的函数,使用一次就会被回收,加括号就可以运行,一般和sorted,map,filter方法连用
p = lambda x,y:x+y
print(p(4,6))
装饰器
1. 什么是装饰器?
装饰器指的是为被装饰对象添加功能,因此定义装饰器就是定义一个函数,只不过是该函数是用来为其他函数添加额外的功能
注意:
装饰器本身是可以任意调用的对象
被装饰的对象也可以是任意可调用了的对象
2. 为什么要用装饰器
装饰器的实现必须遵循两大原则
不修改被装饰的源代码
不修改被装饰的对象的调用方式
装饰器就是在遵循以上两个原则的前提下被装饰对象添加功能
redis
string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)
1.操作string
set zifuchuan guojiadong //设置字符串
get zifuchuan //获得刚刚设置的字符串
以下为截图
通过刚刚这两条命令我们就将一个key为zifuchuan,value为guojiadong的值存贮到了redis中并且获得了zifuchan的value
sting类型操作整形
set zifuchuan2 3 //甚至string类型的value为3
incr zifuchuan2 //让刚刚设置的value自增1
decrby zifuchuan2 2 //让value值减2
2.redis操作list
list类型是一个有序的列表,是从左到右还是从右到左,他允许从左或者右都可以进行推入和弹出,他并不要求值是不同,如果从左到右,那么就相当于我们常说的队列,也就是遵循先入先出
lpush list1 12 //从左边推入元素
rpush list1 12 //从右边推入元素
lpop list1 //从左边推出元素
rpop list1 //从右边推出元素
llen list1 //列出list1中元素的个数
3.redis操作set
set要求数据都是唯一的,相同的值是不能被插入2次及以上的
sadd set1 12 //插入12到set1中
scard set1 //返回当前set1中元素的个数
sismember set1 12 //判断12是否在set1这个集合中,返回1说明在,返回0说明不在
srem set1 12 //将12从set1中删除
4.redis操作hash
hash允许多个键值对存贮在一个key中
hset hash1 key1 12 //向 hash1中插入key为key1,value为12
hset hash1 key2 13 //向hash1中插入key 为key2,value为13
hget hash1 key1 //获得hash1中key1的value
hlen hash1 //查看hash1中有几个元素
hset hash1 key1 14 //直接修改hash1中key1的value,即使之前已经设置,会直接覆盖
hmget hash1 key1 key2 //一次获取hash1中key1和key2的值,会根据key1和key2的顺序返回,顺序不同,返回结果也不同
5.redis操作sort set
要求分数为浮点型,方式:score value,还有一个隐藏的rank,rank排序如果出现score相同,那么安装value值的字典排序来排序(a,b,c...1,2,3....)
zadd zset1 10.1 val1 //增加score为10.1,value为val1到zset1的映射
zadd zset1 9.1 val2 //增加score为9.1,value为val2到zset1的映射
zcard zset1 //查看zset1中元素的个数
zrange zset1 0 2 withscores //按照排名0到2把分数一起打印出来
zrank zset1 val2 //指定val2查看当前排名
设置键
[root@host ~]# /usr/local/redis/bin/redis-cli
127.0.0.1:6379> set name linux
OK
127.0.0.1:6379> get name
"linux"
127.0.0.1:6379> keys *
1) "name"
判断键是否存在
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists hehe
(integer) 0
如果存在返回整数类型1.否则返回0
删除键
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> del name
(integer) 0
获取键值的数据类型
127.0.0.1:6379> type name
2019.9.16更新:增加了对缓存雪崩,缓存穿透,缓存击穿的描述。并附上本人对Redis单线程,多线程实现的理解。
评论区有一些争执,但是大家都很友善,比如这个单线程是否能让Redis更快尤其是大家争论的重点。
(以下单线程仅指Redis负责存取这块的线程只有一个,而非Redis中只有一个进程)
我先给个我的结论,单线程的Redis在瓶颈是cpu的io时(这不是大多数应用的实际应用场景),确实速度会比多线程慢。但是,我们实际应用场景中很少会遇到瓶颈是CPU的io的情况,这时候单线程优势就凸显出来了。
实现很简单!性能又不会比多线程差,并且,单线程确实不用处理上下文的切换,cpu利用率会比多线程高,这时候采用单线程实现是一种很划算的做法。当然,如果你的宽带和内存牛逼到了使得你的io成为瓶颈,这时候也只能使用多线程了。
面试时考官让我挑一种自己熟悉的NoSQL数据库讲一讲,我当场就蒙了,我就用过sql server,mysql和Oracle这几种,这几种就算从名字看也知道是sql数据库嘛,绞尽脑汁,我福至心灵,答出,Redis!
先说说Redis是什么吧小老弟?
Redis嘛,就是一种运行速度很快,并发很强的跑在内存上的NoSql数据库,支持键到五种数据类型的映射。
来来来,讲一讲为什么Redis这么快?
首先,采用了多路复用io阻塞机制
然后,数据结构简单,操作节省时间
最后,运行在内存中,自然速度快
Redis为什么是单线程的?
Redis官方很敷衍就随便给了一点解释。不过基本要点也都说了,因为Redis的瓶颈不是cpu的运行速度,而往往是网络带宽和机器的内存大小。再说了,单线程切换开销小,容易实现既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
如果万一CPU成为你的Redis瓶颈了,或者,你就是不想让服务器其他核闲置,那怎么办?
那也很简单,你多起几个Redis进程就好了。Redis是keyvalue数据库,又不是关系数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程上就可以了。redis-cluster可以帮你做的更好。
单线程可以处理高并发请求吗?
当然可以了,Redis都实现了。有一点概念需要澄清,并发并不是并行。
(相关概念:并发性I/O流,意味着能够让一个计算单元来处理来自多个客户端的流请求。并行性,意味着服务器能够同时执行几个事情,具有多个计算单元)
我们使用单线程的方式是无法发挥多核CPU 性能,有什么办法发挥多核CPU的性能嘛?
我们可以通过在单机开多个Redis 实例来完善!
警告:这里我们一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理,一个正式的Redis Server运行的时候肯定是不止一个线程的,这里需要大家明确的注意一下!
例如Redis进行持久化的时候会以子进程或者子线程的方式执行(具体是子线程还是子进程待读者深入研究)
简述一下Redis值的五种类型
String 整数,浮点数或者字符串
Set 集合
Zset 有序集合
Hash 散列表
List 列表
有序集合的实现方式是哪种数据结构?
跳跃表。
请列举几个用得到Redis的常用使用场景?
缓存,毫无疑问这是Redis当今最为人熟知的使用场景。再提升服务器性能方面非常有效;
排行榜,在使用传统的关系型数据库(mysql oracle 等)来做这个事儿,非常的麻烦,而利用Redis的SortSet(有序集合)数据结构能够简单的搞定;
计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;
好友关系,利用集合的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能;
简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦;
Session共享,以PHP为例,默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息。
一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大,而放在redis中,因为redis 是放在内存中的可以很高效的访问
简述Redis的数据淘汰机制
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru从所有数据集中挑选最近最少使用的数据淘汰
allkeys-random从所有数据集中任意选择数据进行淘汰
noeviction禁止驱逐数据
Redis怎样防止异常数据不丢失?
RDB 持久化
将某个时间点的所有数据都存放到硬盘上。
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长。
AOF 持久化
将写命令添加到 AOF 文件(Append Only File)的末尾。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:
选项同步频率always每个写命令都同步everysec每秒同步一次no让操作系统来决定何时同步
always 选项会严重减低服务器的性能;
everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
讲一讲缓存穿透,缓存雪崩以及缓存击穿吧
缓存穿透:就是客户持续向服务器发起对不存在服务器中数据的请求。客户先在Redis中查询,查询不到后去数据库中查询。
缓存击穿:就是一个很热门的数据,突然失效,大量请求到服务器数据库中
缓存雪崩:就是大量数据同一时间失效。
打个比方,你是个很有钱的人,开满了百度云,腾讯视频各种杂七杂八的会员,但是你就是没有netflix的会员,然后你把这些账号和密码发布到一个你自己做的网站上,然后你有一个朋友每过十秒钟就查询你的网站,发现你的网站没有Netflix的会员后打电话向你要。你就相当于是个数据库,网站就是Redis。这就是缓存穿透。
大家都喜欢看腾讯视频上的《水果传》,但是你的会员突然到期了,大家在你的网站上看不到腾讯视频的账号,纷纷打电话向你询问,这就是缓存击穿
你的各种会员突然同一时间都失效了,那这就是缓存雪崩了。放心,肯定有办法解决的。
缓存穿透:
1.接口层增加校验,对传参进行个校验,比如说我们的id是从1开始的,那么id<=0的直接拦截;
2.缓存中取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿:
最好的办法就是设置热点数据永不过期,拿到刚才的比方里,那就是你买腾讯一个永久会员
缓存雪崩:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
嗦一下Redis中的Master-Slave模式
连接过程
- 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
- 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
- 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
主从链
随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。
Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
分片
分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。
假设有 4 个 Redis 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... ,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。
还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。根据执行分片的位置,可以分为三种分片方式:
客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。
代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
服务器分片:Redis Cluster
插拔式设计思想
首先我们在配置文件中写中间件的那个形式,然后我们写了比如有一个发送短信类还有发送微信类,还有发qq消息类,我们中间件里屏蔽掉不想用的微信类,那么执行项目的时候就不会执行到微信类,其中涉及到了importlib 动态导入、反射等知识点
单例模式
模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。说白了,程序运行期间只存在一个实例对象能减少内存资源。
几种方式:1.使用模块,2.使用装饰器3.使用类4.基于new方法实现5.基于metaclass方式实现
Django 框架
生命周期
1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端
请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post,体现在url之中.
2. url经过Django中的wsgi,再经过Django的中间件,最后url到过路由映射表,在路由中一条一条进行匹配,
一旦其中一条匹配成功就执行对应的视图函数,后面的路由就不再继续匹配了.
3. 视图函数根据客户端的请求查询相应的数据.返回给Django,然后Django把客户端想要的数据做为一个字符串返回给客户端.
4. 客户端浏览器接收到返回的数据,经过渲染后显示给用户.
django中间件
用户访问频率限制
用户是否是黑名单 白名单
所有用户登录校验
只要是涉及到网址全局的功能 你就应该考虑使用中间件
rocess_request,
process_response,
process_view,
process_exception,
process_render_template,
process_request(******):请求来的时候 会从上往下依次经过每一个中间件里面process_request,一旦里面返回了HttpResponse对象那么就不再往后执行了 会执行同一级别的process_response,如果没有该方法则直接跳过,走下一个中间件
如果该方法里返回了 HttpResponse 对象,那么会直接从当前中间件的 process_response 方法 从下往上依次执行返回,不会再接着往下执行,该方法可以实现对用户身份的校验,访问频率的限制,用户权限的校验...
process_response(***):响应走的时候 会从下往上依次进过每一个中间件里面的process_response
该方法可以帮你实现缓存机制(减缓服务器、数据库的压力)
docker
docker images 查看镜像
docker search 镜像名称 搜索镜像
docker pull 镜像名称 拉取镜像
docker rmi 镜像ID 删除镜像
docker rmi `docker images -q` 删除所有镜像
docker ps 查看正在运行的容器
docker ps –a 查看所有的容器
容器操作:镜像运行,容器启动,就是一个个操作系统
创建容器:
第一种
docker run -it --name=mycentos centos:centos7 /bin/bash
exit 退出容器,容器也就停止了
第二种
docker run -di --name=mycentos2 centos:centos7 不进入容器 内部
docker exec -it mycentos2 /bin/bash 进入到容器内容
exit退出,容器不停止
docker stop id号或者名字 容器停止
docker start id号或者名字 启动容器
celery
处理大量消息的分布式系统
专注于实时处理的异步任务队列
同时也支持任务调度
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
任务执行单元
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
任务结果存储
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
2.使用场景
异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
定时任务:定时执行某件事情,比如每天数据统计
mysql
事务:
1.原子性:事务是一组不可分割的单位,要么同时成功,要么同时不成功
2.一致性:事物前后的数据完整性应该保持一致(数据库的完整性:如果数据库在某一时间点下,所有的数据都符合所有的约束,则称数据库为完整性的状态)
3.隔离性:事物的隔离性是指多个用户并发访问数据时,一个用户的事物不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离
4.持久性:持久性是指一个事物一旦被提交,它对数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
悲观锁乐观锁
悲观锁:总是假设最坏的情况,每次拿数据都认为别人会修改数据,所以要加锁,别人只能等待,直到我释放锁才能拿到锁;数据库的行锁、表锁、读锁、写锁都是这种方式,
应用场景:
写多读少
乐观锁:总是假设最好的情况,每次拿数据都认为别人不会修改数据,所以不会加锁,但是更新的时候,会判断在此期间有没有人修改过;如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。版本号的方式,即当前版本号如果对应上了就可以写入数据,如果判断当前版本号不一致,那么就不会更新成功,
应用场景:
写少读多
轮询和长轮询
轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。
长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
实例:WebQQ、Hi网页版、Facebook IM。
三次握手四次挥手
第一次握手 客户端向服务端请求连接
第二次握手 服务器收到请求报文后,如果同意连接,则发出确认报文,询问客户端是否准备好,确认和询问合并成一次握手
第三次握手 客户进程收到确认后,还要向服务器给出确认,回应服务端已经准备好
第一次挥手 用来关闭客户端到服务端的连接。客户端进程发出连接释放报文,并且停止发送数据。
第二次挥手 服务器收到客户端的连接释放报文,但是服务器若发送数据,客户端依然要接受。客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,但还得等待服务器发送连接释放报文(因为在这之前还需要接受服务器发送的最后的数据)。
第三次挥手 服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。服务器将最后的数据发送完毕 后,就向客户端发送连接释放报文,等待客户端的确认。
第四次挥手 客户端收到服务器的连接释放报文后,必须发出确认。服务器只要收到了客户端发出的确认,立即进入CLOSED(关闭)状态。
思考:那么为什么是4次挥手呢?
为了确保数据能够完成传输。
django的正向和反向查询
正向查询:由子表查询父表
反向查询:由父表查询子表
# 4.查询出版社是东方出版社出版的书籍名称
# res = models.Publish.objects.filter(name='东方出版社').values('book__title','addr')
# print(res)
# 5.查询作者是jason的写过的书的名字和价格
# res = models.Author.objects.filter(name='jason').values('book__title','book__price')
# print(res)
# 5.查询书籍是python入门的作者的手机号
# res = models.Book.objects.filter(title='python入门').values('authors__author_detail__phone')
分库分表
水平分库:
结果:
每个库的结构都一样;
每个库的数据都不一样,没有交集;
所有库的并集是全量数据;
垂直分库:将业务逻辑类的数据库分为两个库,一个用户相关的放在一个库,订单相关的放在另一个库
结果:
每个库的结构都不一样;
每个库的数据也不一样,没有交集;
所有库的并集是全量数据;
水平分表:
每个表的结构都一样;
每个表的数据都不一样,没有交集;
所有表的并集是全量数据;
垂直分表:
每个表的结构都不一样;
每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;
所有表的并集是全量数据;
web相关技术栈
前端
bootstrap
Bootstrap是一个非常好的入门选择,教程示例非常丰富,颜值也还过得去。
栅格布局自带响应式,常用的颜色都有类可以直接调用。
Font Awesome
图标字体库和CSS框架,毕竟只有图标字体和CSS,所以一般配合其他的样式框架使用。
jQuery
jQuery 是一个 JavaScript 库。
jQuery 极大地简化了 JavaScript 编程。
jQuery 很容易学习。
直接操作DOM
vue
elementui
web框架
django
http协议
全称:超文本传输协议
1.四大特性
1.基于TCP/IP之上作用于应用层
2.基于请求响应
3.无状态 每次连接一次只处理一个请求,
不保存用户状态 不利于保持连接 所以才有cookie session的产生...
4.无连接:
长连接 websocket(HTTP协议的大补丁):没有数据传也要保持tcp连接
Websocket 的最大特点是,服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送消息,
2.数据格式
请求格式
请求首行(请求方式,协议版本。。。)
请求头(一大堆k:v键值对)
请求体(真正的数据 发post请求的时候才有 如果是get请求不会有)
响应格式
响应首行
响应头
响应体
restful api
2.1 数据的安全保障
url链接一般都采用https协议进行传输
注:采用https协议,可以提高数据交互过程中的安全性
2.2 接口特征表现
用api关键字标识接口url:
https://api.baidu.com
https://www.baidu.com/api
注:看到api字眼,就代表该请求url链接是完成前后台数据交互的
2.3 多数据版本共存
在url链接中标识数据版本
https://api.baidu.com/v1
https://api.baidu.com/v2
注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
2.4 数据即是资源
接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
https://api.baidu.com/users
https://api.baidu.com/books
https://api.baidu.com/book
注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
https://api.baidu.com/place/search
https://api.baidu.com/login
2.5 资源操作由请求方式决定
操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书书
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
3.响应状态码
3.1 正常响应
响应状态码2xx
200:常规请求
201:创建成功
3.2 重定向响应
响应状态码3xx
301:永久重定向
302:暂时重定向
3.3 客户端异常
响应状态码4xx
403:请求无权限
404:请求路径不存在
405:请求方法不存在
3.4 服务器异常
响应状态码5xx
500:服务器异常
4.响应结果
4.1 响应数据要有状态码、状态信息以及数据本身
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"location":{
"lat":31.415354,
"lng":121.357339
},
"address":"月罗路2380号",
"province":"上海市",
"city":"上海市",
"area":"宝山区",
"street_id":"339ed41ae1d6dc320a5cb37c",
"telephone":"(021)56761006",
"detail":1,
"uid":"339ed41ae1d6dc320a5cb37c"
}
...
]
}
4.2 需要url请求的资源需要访问资源的请求链接
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"img": "https://image.baidu.com/kfc/001.png"
}
...
]
}
短连接和长链接
首先介绍下短链接和长连接的区别:
短连接
连接->传输数据->关闭连接
比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
具体就是 浏览器client发起并建立TCP连接 -> client发送HttpRequest报文 -> server接收到报文->server handle并发送HttpResponse报文给前端,发送完毕之后立即调用socket.close方法
->client接收response报文->client最终会收到server端断开TCP连接的信号->client 端断开TCP连接,具体就是调用close方法。
也可以这样说:短连接是指SOCKET连接后,发送接收完数据后马上断开连接。
因为连接后接收了数据就断开了,所以每次数据接受处理不会有联系。 这也是HTTP协议无状态的原因之一。
长连接
连接->传输数据->保持连接 -> 传输数据-> ...........->直到一方关闭连接,多是客户端关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。
HTTP在短链接和长连接上的选择:
HTTP是无状态的 ,也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话
HTTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持(貌似最新的HTTP1.1 可以显示的指定 keep-alive),但还是无状态的,或者说是不可以信任的。
如果浏览器或者服务器在其头信息加入了这行代码 Connection:keep-alive
TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了带宽。
实现长连接要客户端和服务端都支持长连接。
什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。
总之,长连接和短连接的选择要视情况而定。
多线程和多进程
纠正概念:进程其实不是个执行单位,进程是一个资源单位,每个进程内自带一个线程,线程才是cpu上的执行单位
抽象理解:
进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。
线程:cpu最小的执行单位
进程:资源集合/资源单位.
线程运行 = 运行代码
进程运行 = 各种资源 + 线程
进程和线程的区别
线程:单个线程的内存空间数据共享(进程内的数据)
进程:物理内存空间隔离(多个进程内存空间彼此隔离)
进程是告诉操作系统开辟内存空间
线程是告诉操作系统执行一条任务代码(线程的创建速度是进程的100倍)
线程开启
函数开启
from threading import Thread
import time
def task(name):
print('%s is runing '%name)
time.sleep(2)
print('%s is done'%name)
t=Thread(target=task,args=('子线程',))
t.start()
类开启
class Task(Thread):
def run(self):
print('%s is runing ' % self.name)
time.sleep(2)
print('%s is done' % self.name)
t = Task()t.start()print('zhu')
进程开启
导入multiprocessing包
from multiprocessing import Process,current_process
import time
def foo():
print('进程 start')
time.sleep(2)
print('进程 end')
if __name__ == '__main__':
p = Process(target=foo)
p.start()
print(p.is_alive()) # True#打印与子进程同时进行
time.sleep(5)#在五秒内的第三秒进程就已经结束了
print(p.is_alive()) # 代码运行完了就算死了 False
print('主')
mysql的引擎
MYISAM:支持3中存储方式:静态型,动态型,压缩型
优点:占用的空间小,存储的速度快
缺点:不支持事务和并发
innoDB:
优点:提供事务的支持,回滚,崩溃修复能力,多版本事务并发控制
缺点:读写效率较差,占用的数据库空间较大
blackhole
Memory:内存中对数据创建表,数据全部存储在内存
缺点:生命周期短
优点:读写速度非常快,对数据的安全性要求比较低的时候可以选择memory
linux文件操作命令
文件创建:普通文件 (touch) 目录文件----文件夹(mkdir)
文件删除:普通文件(rm) 目录文件(rmdir-----删除空目录 rm -r 删除非空目录)
文件拷贝:普通文件(cp原文件路径+文件名 拷贝的目的地) 目录文件(cp -r 原文件路径+文件名 拷贝目的地的路径)把目录里面的文件都拷贝过去
文件剪切/重命名:普通文件(mv) 目录文件(mv)
边移动边重命名:如下图 stu3目录下本来就有一个project1所以剪切过来要重命名。这里的project1和project2是相同的文件
mv /home/stu1/project1 (这里中间要用空格隔开)/home/stu1/project2 单纯的重命名
修改属性:(文件类型一旦创建就不能修改,权限可以修改,链接数不能改,是随着操作变化的;属主可以改;属组可以修改;文件大小不能改,是你往里面加了东西才修改)
修改属主:只能root用户修改,chown newuser(改成谁) filename(所改的文件)
修改属组:只能root用户修改,chgrp newgroup(新的组,原来在那个组不用关心) filename
修改权限:chomd
1、用字符方式修改 chmod a(all所有的)/u/g/o +/-/=(赋值) r/w/x/rw/rx/wx/rwx
2、用数字方式修改:
每个n代表一个用户的权限,一共有3个n,每个n都是0-7的数字
chmod nnn filename
linux基本命令
ls 查看当前目录下有什么文件和文件夹 ls -a可以查看隐藏的文件
pwd命令 该命令的英文解释为print working directory(打印工作目录)。输入pwd命令,Linux会输出当前目录。
cd 跟路径 :切换目录
ls -l :显示文件详细信息
ls -a : 显示所有文件 (包含隐藏文件文件名以“.”开头的都是隐藏文件)
cd .. :可以退回上级目录
cd - :两个目录之间来回切换
cd ~ :直接返回家目录
pwd:查看当前目录的绝对路径/查看当前位置
linux 下的文件夹
/bin:存储系统所使用命令的可执行文件
/home:普通用户的家目录 (cd home)
/dev:外部设备(存的是设备的接口 通过接口能访问设备)
/proc:虚拟目录 以进程为单位存储内存的映射--------一些统计信息
/mnt:临时挂载点(将接口挂载到临时挂载点进行操作)
/usr:第三方软件的一些文档
/etc:系统配置目录
/lib:库文件(静态库 共享库(动态库))
nginx
1、什么是Nginx
Nginx是一个高性能的HTTP和反向代理服务器,及电子邮件代理服务器,同时也是一个非常高效的反向代理、负载平衡。
2、为什么要用Nginx
跨平台、配置简单,非阻塞、高并发连接:处理2-3万并发连接数,官方监测能支持5万并发,
内存消耗小:开启10个nginx才占150M内存 ,nginx处理静态文件好,耗费内存少,
内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
节省宽带:支持GZIP压缩,可以添加浏览器本地缓存
稳定性高:宕机的概率非常小
接收用户请求是异步的:浏览器将请求发送到nginx服务器,它先将用户请求全部接收下来,再一次性发送给后端web服务器,极大减轻了web服务器的压力,一边接收web服务器的返回数据,一边发送给浏览器客户端, 网络依赖性比较低,只要ping通就可以负载均衡,可以有多台nginx服务器 使用dns做负载均衡,事件驱动:通信机制采用epoll模型(nio2 异步非阻塞)
3、为什么Nginx性能这么高
得益于它的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决
4、Nginx是如何处理一个请求的
首先,nginx在启动时,会解析配置文件,得到需要监听的端口与ip地址,然后在nginx的master进程里面先初始化好这个监控的socket,再进行listen,然后再fork出多个子进程出来, 子进程会竞争accept新的连接。此时,客户端就可以向nginx发起连接了。当客户端与nginx进行三次握手,与nginx建立好一个连接后,此时,某一个子进程会accept成功,然后创建nginx对连接的封装,即ngx_connection_t结构体,接着,根据事件调用相应的事件处理模块,如http模块与客户端进行数据的交换。最后,nginx或客户端来主动关掉连接,到此,一个连接就寿终正寝了
5、正向代理
一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理
正向代理总结就一句话:代理端代理的是客户端
6、反向代理
反向代理是指以代理服务器来接受internet上的连接请求,然后将请求,发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器
反向代理总结就一句话:代理端代理的是服务端++++
7、动态资源、静态资源分离
动态资源、静态资源分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路,动态资源、静态资源分离简单的概括是:动态文件与静态文件的分离
8、为什么要做动、静分离
在我们的软件开发中,有些请求是需要后台处理的(如:.jsp,.do等等),有些请求是不需要经过后台处理的(如:css、html、jpg、js等等文件),这些不需要经过后台处理的文件称为静态文件,因此我们后台处理忽略静态文件。这会有人又说那我后台忽略静态文件不就完了吗,当然这是可以的,但是这样后台的请求次数就明显增多了。在我们对资源的响应速度有要求的时候,我们应该使用这种动静分离的策略去解决,动、静分离将网站静态资源(HTML,JavaScript,CSS,img等文件)与后台应用分开部署,提高用户访问静态代码的速度,降低对后台应用访问,这里我们将静态资源放到nginx中,动态资源转发到tomcat服务器中
9、负载均衡
负载均衡即是代理服务器将接收的请求均衡的分发到各服务器中,负载均衡主要解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量,减少后台服务器大并发压力