网络编程和并发
物理层:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
数据链路层:定义了电信号的分组方式
网路层:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
传输层:建立端口到端口的通信
会话层:建立客户端与服务端连接
表示层:对来自应用层的命令和数据进行解释,按照一定格式传给会话层。如编码、数据格式转换、加密解密、压缩解压
应用层:规定应用程序的数据格式
c/s架构,就是client(客户端)与server(服务端)即:客户端与服务端的架构。
b/s架构,就是brosver(浏览器端)与sever(服务端)即:浏览器端与服务端架构
优点:统一了所有应用程序的入口、方便、轻量级
三次握手:
第一次握手
1:客户端先向服务端发起一次询问建立连接的请求,并随机生成一个值作为标识
第二次握手
2:服务端向客户端先回应第一个标识,再重新发一个确认标识
第三次握手
3:客户端确认标识,建立连接,开始传输数据
四次挥手 ---> 断开连接
第一次挥手
客户端向服务端发起请求断开连接的请求
第二次挥手
服务端向客户端确认请求
第三次挥手
服务端向客户端发起断开连接请求
第四次挥手
客户端向服务端确认断开请求
TCP/UDP区别
TCP协议是面向连接,保证高可靠性传输层协议
UDP:数据丢失,无秩序的传输层协议(qq基于udp协议)
tcp:可靠,因为只要对方回了确认收到信息,才发下一个,如果没收到确认信息就重发
UDP:不可靠,它是一直发数据,不需要对方回应
流式协议: TCP协议,可靠传输
数据报协议: UDP协议,不可传输
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,
对用户来说,一组简单的接口就是全部。
服务端:
创建socket对象,
绑定ip端口bind(),
设置最大链接数listen(),
accept()与客户端的connect()创建双向管道,等到联接,
send(), recv(), 收发数据
close()
客户端:
创建socket对象,
connect()与服务端accept()创建双向管道 ,
send(),
recv(),
close()
import socket
# 实例化对象 = 开机
server = socket.socket()
# 绑定 = 插电话卡
server.bind(('127.0.0.1', 18000))
# 开监听,半连接池,最大等待用户
server.listen(5)
while True:
# 被动接受TCP客户的连接,(阻塞式)等待连接的到来
# 来电话建立通讯
sock, addr = server.accept() # sock 连接通道 和 ip地址
while True:
# 等于每次来电话他最多说多少个字
try:
msg = sock.recv(1024).decode('utf-8') # 单次接收的最大字节
print(msg)
# 返回给客户端信息
# 等于对方说话你给他说的回复信息
sock.send('已收到'.encode('utf-8'))
except Exception:
break
# 如果客户端异常断开服务器也会断开,所有用异常处理
# 挂电话
sock.close() # 关闭连接
server.close() # 关闭socket 也就等于出现意外的异常直接关闭
客服端
创建socket对象
connect()与accept()创建双向管道
send()
recv()
close()
import socket
# 实例化对象
client = socket.socket()
# 绑定
client.connect(('127.0.0.1', 18000))
# 发送信息给服务器
while True:
msg = input('发送给服务器的内容:').encode('utf-8')
if not msg:
print('你自己终止了程序')
break
client.send(msg)
# 接收字节
msg = client.recv(1024).decode('utf-8')
print('服务器发送回来的内容:%s' % msg)
只有TCP有粘包现象,UDP永远不会粘包
粘包:在获取数据时,出现数据的内容不是本应该接收的数据,如:对方第一次发送hello,第二次发送world,
我方接收时,应该收两次,一次是hello,一次是world,但事实上是一次收到helloworld,一次收到空,这种现象叫粘包
原因
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
什么情况会发生:
1、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
2、接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,
服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
socketserver,多个客户端连接,单线程下实现并发效果,就叫多路复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销
都是i/o多路复用的机制,监视多个socket是否发生变化,本质上都是同步i/o
select,poll实现需要自己不断轮询所有监测对象,直到对象发生变化,在这个阶段中,
可能要睡眠和唤醒多次交替,而epoll也需要调用epoll_wait不断轮询就绪链表,但是当对象发生变化时,
会调用回调函数,将变化的对象放入就绪链接表中,并唤醒在epoll_wait中进入睡眠的进程。
虽然都会睡眠和唤醒,但是select和poll在被唤醒的时候要遍历整个监测对象集合,
而epoll只要判断就绪链表是否为空即可,节省了大量cpu的时间
select、poll、epoll都是IO多路复用的机制,但select,poll,epoll本质上都是同步I/O,
因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的.
FD(文件描述符)
select模型
优点:
1:可移植性好,在某些Unix系统不支持poll()
2:对于超时值提供了更好的精度:微妙,而poll是毫秒
缺点:
1:最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。
2:效率问题,select每次调用都会线性扫描全部的FD集合,所以将FD_SETSIZE 改大,会越慢
3:需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
poll本质上和select 没有区别,它将用户传入的数组拷贝到内核空间,
它没有最大连接数的限制,原因是它基于链表来存储的但是同样有一个缺点:
大量的fd的数组被整体复制于用户态和内核地址空间,而不管这样的复制是不是有意义
防火墙是一个分离器、一个限制器,也是一个分析器,有效地监控了内部网和Internet之间的任何活动,保证了内部网络的安全
作用
防火墙是网络安全的屏障
可以强化网络安全策略
对网络存取和访问进行监控审计
防止内部信息的外泄
除了安全作用,防火墙还支持具有Internet服务特性的企业内部网络技术体系VPN(虚拟专用网)。
线程是指进程内的一个执行单元,
# 进程
进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
# 线程
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度
# 协程和线程
协程避免了无意义的调度,由此可以提高性能;但同时协程也失去了线程使用多CPU的能力
进程与线程的区别
(1)地址空间:线程是进程内的一个执行单位,进程内至少有一个线程,他们共享进程的地址空间,而进程有自己独立的地址空间
(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是
(4)二者均可并发执行
(5)每个独立的线程有一个程序运行的入口
协程与线程
(1)一个线程可以有多个协程,一个进程也可以单独拥有多个协程,这样Python中则能使用多核CPU
(2)线程进程都是同步机制,而协程是异步
(3)协程能保留上一次调用时的状态
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理
应用(总结):
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
1. 每个cpython进程内都有一个GIL
2. GIL导致同一进程内多个进程同一时间只能有一个运行
3. 之所以有GIL,是因为Cpython的内存管理不是线程安全的
4. 对于计算密集型用多进程,多IO密集型用多线程
实现线程局部变量的传递。
ThreadLocal 最常用的地方:
为每个线程绑定一个资源(数据库连接,HTTP请求,用户身份信息等),这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
https://www.cnblogs.com/zgq0/p/8780893.html
# 并发:同一时刻只能处理一个任务,但一个时段内可以对多个任务进行交替处理(一个处理器同时处理多个任务)
# 并行:同一时刻可以处理多个任务(多个处理器或者是多核的处理器同时处理多个不同的任务)
# 类比:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。
线程锁:
多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误! so,不使用线程锁, 可能导致错误
大家都不陌生,主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有有一个线程在执行该段代码。
当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。
进程锁:
也是为了控制同一操作系统中多个进程访问一个共享资源,
只是因为程序的独立性,各个进程是无法控制其他进程对资源的访问的,
但是可以使用本地系统的信号量控制(操作系统基本知识)。
非阻塞:不等待
即:遇到IO阻塞不等待(setblooking=False),(可能会报错->捕捉异常)
- sk=socket.socket()
- sk.setblooking(False)
异步:回调,当达到某个指定的状态之后,自动调用特定函数
实例
nb_async.py 实现异步非阻塞的模块
异步体现在回调上,回调就是有消息返回时告知一声儿进程进行处理。非阻塞就是不等待,不需要进程等待下去,继续执行其他操作,不管其他进程的状态。
1:交换机:是负责内网里面的数据传递(arp协议)根据MAC地址寻址
路由器:在网络层,路由器根据路由表,寻找该ip的网段
2:路由器可以处理TCP/IP协议
3:路由器可以把一个IP分配给很多个主机使用,这些主机对外只表现出一个IP。
交换机可以把很多主机连起来,这些主机对外各有各的IP。
4:交换机是做端口扩展的,也就是让局域网可以连进来更多的电脑。
路由器是用来做网络连接,也就是;连接不同的网络
在互联网上,所有的地址都是ip地址,现阶段主要是IPv4(比如:110.110.110.110)。
但是这些ip地址太难记了,所以就出现了域名(比如http://baidu.com)。
域名解析就是将域名,转换为ip地址的这样一种行为。
Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,
当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,
一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。
浏览器访问网站,要首先通过DNS服务器把要访问的网站域名解析成一个唯一的IP地址,之后,浏览器才能对此网站进行定位并且访问其数据。
文件路径:C:\WINDOWS\system32\drivers\etc。
将127.0.0.1 www.163.com 添加在最下面
修改后用浏览器访问“www.163.com”会被解析到127.0.0.1,导致无法显示该网页。
生产者与消费者模式是通过一个容器来解决生产者与消费者的强耦合关系,生产者与消费者之间不直接进行通讯,
而是利用阻塞队列来进行通讯,生产者生成数据后直接丢给阻塞队列,消费者需要数据则从阻塞队列获取,
实际应用中,生产者与消费者模式则主要解决生产者与消费者生产与消费的速率不一致的问题,达到平衡生产者与消费者的处理能力,而阻塞队列则相当于缓冲区。
应用场景:用户提交订单,订单进入引擎的阻塞队列中,由专门的线程从阻塞队列中获取数据并处理
优势:
1;解耦
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。
将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
2:支持并发
生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只能一直等着
而使用这个模型,生产者把制造出来的数据只需要放在缓冲区即可,不需要等待消费者来取
3:支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。
当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
目的是使用户可以就近到服务器取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。
cdn 即内容分发网络
LVS :Linux虚拟服务器
作用:LVS主要用于多服务器的负载均衡。
它工作在网络层,可以实现高性能,高可用的服务器集群技术。
它廉价,可把许多低性能的服务器组合在一起形成一个超级服务器。
它易用,配置非常简单,且有多种负载均衡的方法。
它稳定可靠,即使在集群的服务器中某台服务器无法正常工作,也不影响整体效果。另外可扩展性也非常好。
https://www.cnblogs.com/Rivend/p/12071571.html
keepalived介绍
keepalived观察其名可知,保持存活,在网络里面就是保持在线了,
也就是所谓的高可用或热备,它集群管理中保证集群高可用的一个服务软件,
其功能类似于heartbeat,用来防止单点故障(单点故障是指一旦某一点出现故障就会导致整个系统架构的不可用)的发生。
说到keepalived就不得不说VRRP协议,可以说这个协议就是keepalived实现的基础,那么首先我们来看看VRRP协议。
VRRP协议介绍
学过网络的朋友都知道,网络在设计的时候必须考虑到冗余容灾,包括线路冗余,设备冗余等,防止网络存在单点故障,那在路由器或三层交换机处实现冗余就显得尤为重要。
在网络里面有个协议就是来做这事的,这个协议就是VRRP协议,Keepalived就是巧用VRRP协议来实现高可用性(HA)的发生。
VRRP全称Virtual Router Redundancy Protocol,即虚拟路由冗余协议。对于VRRP,需要清楚知道的是:
1)VRRP是用来实现路由器冗余的协议。
2)VRRP协议是为了消除在静态缺省路由环境下路由器单点故障引起的网络失效而设计的主备模式的协议,
使得发生故障而进行设计设备功能切换时可以不影响内外数据通信,不需要再修改内部网络的网络参数。
3)VRRP协议需要具有IP备份,优先路由选择,减少不必要的路由器通信等功能。
4)VRRP协议将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器IP(一个或多个)。
然而,在路由器组内部,如果实际拥有这个对外IP的路由器如果工作正常的话,就是master,或者是通过算法选举产生的,
MASTER实现针对虚拟路由器IP的各种网络功能,如ARP请求,ICMP,以及数据的转发等,其他设备不具有该IP,状态是BACKUP。
除了接收MASTER的VRRP状态通告信息外,不执行对外的网络功能,当主级失效时,BACKUP将接管原先MASTER的网络功能。
5)VRRP协议配置时,需要配置每个路由器的虚拟路由ID(VRID)和优先权值,使用VRID将路由器进行分组,具有相同VRID值的路由器为同一个组,
VRID是一个0-255的整整数,;同一个组中的路由器通过使用优先权值来选举MASTER。,优先权大者为MASTER,优先权也是一个0-255的正整数。
HAProxy 是一款提供高可用性、负载均衡以及基于TCP(第四层)和HTTP(第七层)应用的代理软件,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。
HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。
HAProxy运行在时下的硬件上,完全可以支持数以万计的 并发连接。
并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。
数据库和缓存(46题)
关系型数据库(需要有表结构)
mysql、oracle 、 spl、server、db2、sybase
非关系型数据库(是以key-value存储的,没有表结构)(NoSQL)
MongoDB
MongoDB 是一个高性能,开源,无模式的文档型数据库,开发语言是C++。它在许多场景下可用于替代传统的关系型数据库或键/值存储方式。
Redis
Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。目前由VMware主持开发工作。
InnoDB
支持事务
支持表锁、行锁(for update)
表锁:select * from tb for update
行锁:select id,name from tb where id=2 for update
myisam
查询速度快
全文索引
支持表锁
表锁:select * from tb for update
NDB
高可用、 高性能、高可扩展性的数据库集群系统
Memory
默认使用的是哈希索引
数据库的三大特性:
'实体':表
'属性':表中的数据(字段)
'关系':表与表之间的关系
----------------------------------------------------
# 数据库设计三大范式:
1:确保每列保持原子性(即数据库表中的所有字段值是不可分解的原子值)
2:确保表中的每列都是和主键相关(表中只能保存一种数据,不可以把多种数据保存在同一张表中)--->完全属于当前表的数据
3:确保每列都和主键直接相关,而不是间接相关(在一个数据库表中保存的数据只能与主键相关)----> 消除传递依赖(间接)
比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。
而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。
数据库五大约束'
1.primary KEY:设置主键约束;
2.UNIQUE:设置唯一性约束,不能有重复值;
3.DEFAULT 默认值约束
4.NOT NULL:设置非空约束,该字段不能为空;
5.FOREIGN key :设置外键约束。
事务用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性。
事务的特性:
原子性: 确保工作单元内的所有操作都成功完成,否则事务将被中止在故障点,和以前的操作将回滚到以前的状态。
一致性: 确保数据库正确地改变状态后,成功提交的事务。
隔离性: 使事务操作彼此独立的和透明的。
持久性: 确保提交的事务的结果或效果的系统出现故障的情况下仍然存在。
Mysql实现事务
InnoDB支持事务,MyISAM不支持
# 启动事务:
# start transaction;
# update from account set money=money-100 where name='a';
# update from account set money=money+100 where name='b';
# commit;
'start transaction 手动开启事务,commit 手动关闭事务'
FK(一对多)
下拉框里面的数据就需要用FK关联另一张表
M2M(多对多)
多选的下拉框,或者checkbox
创建一个商城表---包含(id,商品名,每一个商品对应数量)
create table product
(id primary key auto_increment,
pname varchar(64),
pcount int);
group by 分组对聚合的条件进行筛选需要通过havhing
SQL的left join 、right join、inner join之间的区别
left join (左连接) 返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右连接) 返回包括右表中的所有记录1和左表中联结字段相等的记录
inner join(内连接): 只返回两个表中联结字段相等的行
https://www.cnblogs.com/wupeiqi/articles/5729934.html
触发器:
对数据库某张表的增加、删除,修改前后定义一些操作
函数:(触发函数是通过select)
聚合函数:max/sum/min/avg
时间格式化:date_format
字符串拼接:concat
存储过程:
将SQL语句保存到数据库中,并命名,以后在代码调用时,直接调用名称即可
参数类型:
in 只将参数传进去
out 只拿结果
inout 既可以传,可以取
函数与存储过程区别:
本质上没区别。只是函数有如:只能返回一个变量的限制。而存储过程可以返回多个。而函数是可以嵌入在sql中使用的,可以在select中调用,而存储过程不行。
视图:
视图是一个虚拟表,不是真实存在的(只能查,不能改)
单列
功能
普通索引:加速查找
唯一索引:加速查找 + 约束:不能重复(只能有一个空,不然就重复了)
主键(primay key):加速查找 + 约束:不能重复 + 不能为空
多列
联合索引(多个列创建索引)-----> 相当于单列的普通索引
联合唯一索引 -----> 相当于单列的唯一索引
ps:联合索引的特点:遵循最左前缀的规则
其他词语:
·· - 索引合并,利用多个单例索引查询;(例如在数据库查用户名和密码,分别给用户名和密码建立索引)
- 覆盖索引,在索引表中就能将想要的数据查询到;
联合索引
主键是能确定一条记录的唯一标示。例如,身份证证号
外键:用于与另一张表的关联,是能确定另一张表记录的字段,用于保持数据的一致性
主键 外键
定义 唯一标识一条记录,不能有重复的,不允许为空 表的外键是另一张表的主键,外键可以有重复的,可以为空
作用 用来保证数据完整性 用来与其他表建立联系的
个数 主键只能有一个 一个表可以有多个外键
聚合函数
max/sum/min/avg
时间格式化
date_format
字符串拼接
concat(当拼接了null,则返回null)
截取字符串
substring
返回字节个数
length
1.- like '%xx'
select * from tb1 where name like '%cn';
2.- 使用函数
select * from tb1 where reverse(name) = 'wupeiqi';
3.- or
select * from tb1 where nid = 1 or email = '[email protected]';
特别的:当or条件中有未建立索引的列才失效,以下会走索引
select * from tb1 where nid = 1 or name = 'seven';
select * from tb1 where nid = 1 or email = '[email protected]' and name = 'alex'
4.- 类型不一致
如果列是字符串类型,传入条件是必须用引号引起来,不然...
select * from tb1 where name = 999;
5.- !=
select * from tb1 where name != 'alex'
特别的:如果是主键,则还是会走索引
select * from tb1 where nid != 123
6.- >
select * from tb1 where name > 'alex'
特别的:如果是主键或索引是整数类型,则还是会走索引
select * from tb1 where nid > 123
select * from tb1 where num > 123
7.- order by
select email from tb1 order by name desc;
当根据索引排序时候,选择的映射如果不是索引,则不走索引
特别的:如果对主键排序,则还是走索引:
select * from tb1 order by nid desc;
8.- 组合索引最左前缀
如果组合索引为:(name,email)
name and email -- 使用索引
name -- 使用索引
email -- 不使用索引
修改配置文件
slow_query_log = OFF 是否开启慢日志记录
long_query_time = 2 时间限制,超过此时间,则记录
slow_query_log_file = /usr/slow.log 日志文件
log_queries_not_using_indexes = OFF 为使用索引的搜索是否记录
下面是开启
slow_query_log = ON
long_query_time = 2
log_queries_not_using_indexes = OFF
log_queries_not_using_indexes = ON
注:查看当前配置信息:
show variables like '%query%'
修改当前配置:
set global 变量名 = 值
导出现有数据库数据:(当有提示出入密码。-p就不用加密码)
mysqldump -u用户名 -p密码 数据库名称 >导出文件路径 # 结构+数据
mysqldump -u用户名 -p密码 -d 数据库名称 >导出文件 路径 # 结构
导入现有数据库数据:
mysqldump -uroot -p密码 数据库名称 < 文件路径
1、创建数据表时把固定长度的放在前面()
2、将固定数据放入内存: 例如:choice字段 (django中有用到,数字1、2、3…… 对应相应内容)
3、char 和 varchar 的区别(char可变, varchar不可变 )
4、联合索引遵循最左前缀(从最左侧开始检索)
5、避免使用 select *
6、读写分离
- 实现:两台服务器同步数据
- 利用数据库的主从分离:主,用于删除、修改、更新;从,用于查;
读写分离:利用数据库的主从进行分离:主,用于删除、修改更新;从,用于查
7、分库
- 当数据库中的表太多,将某些表分到不同的数据库,例如:1W张表时
- 代价:连表查询
8、分表
- 水平分表:将某些列拆分到另外一张表,例如:博客+博客详情
- 垂直分表:讲些历史信息分到另外一张表中,例如:支付宝账单
9、加缓存
- 利用redis、memcache (常用数据放到缓存里,提高取数据速度)
如果只想获取一条数据
- select * from tb where name=‘alex’ limit 1
char 和 varchar 的区别(char可变, varchar不可变 )
查看有没有命中索引,让数据库帮看看运行速度快不快
explain select * from table;
当type为all时,是为全表索引
select * from tb where name = ‘Oldboy-Wupeiqi’
select * from tb where name = ‘Oldboy-Wupeiqi’ limit 1
是这样的的,用where条件过滤出符合条件的数据的同时,进行计数,
比如limit 1,那么在where过滤出第1条数据后,他就会直接把结果select出来返回给你,整个过程就结束了。
没做唯一索引的话,前者查询会全表扫描,效率低些
limit 1,只要找到对应一条数据,就不继续往下扫描.
然而 name 字段添加唯一索引了,加不加limit 1,意义都不大;
答案一:
先查主键,在分页。
select * from tb where id in (
select id from tb where limit 10 offset 30
)
答案二:
按照也无需求是否可以设置只让用户看200页
答案三:
记录当前页 数据ID最大值和最小值
在翻页时,根据条件先进行筛选;筛选完毕之后,再根据limit offset 查询。
select * from (select * from tb where id > 22222222) as B limit 10 offset 0
如果用户自己修改页码,也可能导致慢;此时对url种的页码进行加密(rest framework )
1、索引合并是把几个索引的范围扫描合并成一个索引。
2、索引合并的时候,会对索引进行并集,交集或者先交集再并集操作,以便合并成一个索引。
3、这些需要合并的索引只能是一个表的。不能对多表进行索引合并。
简单的说,索引合并,让一条sql可以使用多个索引。对这些索引取交集,并集,或者先取交集再取并集。
从而减少从数据表中取数据的次数,提高查询效率。
在索引表中就能将想要的数据查询到
- 实现:两台服务器同步数据
- 利用数据库的主从分离:主,用于删除、修改、更新;从,用于查;
方式一:是视图里面用using方式可以进行指定到哪个数据读写
from django.shortcuts import render,HttpResponse
from app01 import models
def index(request):
models.UserType.objects.using('db1').create(title='普通用户')
# 手动指定去某个数据库取数据
result = models.UserType.objects.all().using('db1')
print(result)
return HttpResponse('...')
方式二:写配置文件
class Router1:
# 指定到某个数据库取数据
def db_for_read(self, model, **hints):
"""
Attempts to read auth models go to auth_db.
"""
if model._meta.model_name == 'usertype':
return 'db1'
else:
return 'default'
# 指定到某个数据库存数据
def db_for_write(self, model, **hints):
"""
Attempts to write auth models go to auth_db.
"""
return 'default'
再写到配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'db1': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
DATABASE_ROUTERS = ['db_router.Router1',]
1、分库
当数据库中的表太多,将某些表分到不同数据库,例如:1W张表时
代价:连表查询跨数据库,代码变多
# 2、分表
水平分表:将某些列拆分到另一张表,例如:博客+博客详情
垂直分表:将某些历史信息,分到另外一张表中,例如:支付宝账单
区别
1:redis不仅支持简单的key_value类型,还支持字典,字符串,列表,集合,有序集合类型
2:内存使用效率对比,使用简单的key-value存储的话,
Memcached的内存利用率更高而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。
3.性能对比:由于Redis只使用单核,而Memcached可以使用多核,.
所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,
4.Redis虽然是基于内存的存储系统,但是它本身是支持内存数据的持久化的,而且提供两种主要的持久化策略:RDB快照和AOF日志。
而memcached是不支持数据持久化操作的。
5.集群管理不同,Memcached本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。
Redis默认支持16个数据库,可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库
Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。
- 连接
- 直接连接:
import redis
r = redis.Redis(host='10.211.55.4', port=6379)
r.set('foo', 'Bar')
print r.get('foo')
- 连接池:
import redis
pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
r = redis.Redis(connection_pool=pool)
r.set('foo', 'Bar')
print r.get('foo')
- 如果一个列表在redis中保存了10w个值,我需要将所有值全部循环并显示,请问如何实现?
一个一个取值,列表没有iter方法,但能自定义
def list_scan_iter(name,count=3):
start = 0
while True:
result = conn.lrange(name, start, start+count-1)
start += count
if not result:
break
for item in result:
yield item
for val in list_scan_iter('num_list'):
print(val)
场景:投票系统,script-redis
优势:
- 高可用
- 分担主压力
注意:
- slave设置只读
从的配置文件添加以下记录,即可:
slaveof 1.1.1.1 3306
帮助我们自动在主从之间进行切换
检测主从中 主是否挂掉,且超过一半的sentinel检测到挂了之后才进行进行切换。
如果主修复好了,再次启动时候,会变成从。
启动主redis:
redis-server /etc/redis-6379.conf 启动主redis
redis-server /etc/redis-6380.conf 启动从redis
在linux中:
找到 /etc/redis-sentinel-8001.conf 配置文件,在内部:
- 哨兵的端口 port = 8001
- 主redis的IP,哨兵个数的一半/1
找到 /etc/redis-sentinel-8002.conf 配置文件,在内部:
- 哨兵的端口 port = 8002
- 主redis的IP, 1
启动两个哨兵
redis集群、分片、分布式redis
redis-py-cluster
集群方案:
- redis cluster 官方提供的集群方案。
- codis,豌豆荚技术团队。
- tweproxy,Twiter技术团队。
redis cluster的原理?
- 基于分片来完成。
- redis将所有能放置数据的地方创建了 16384 个哈希槽。
- 如果设置集群的话,就可以为每个实例分配哈希槽:
- 192.168.1.20【0-5000】
- 192.168.1.21【5001-10000】
- 192.168.1.22【10001-16384】
- 以后想要在redis中写值时,
set k1 123
将k1通过crc16的算法,将k1转换成一个数字。然后再将该数字和16384求余,如果得到的余数 3000,那么就将该值写入到 192.168.1.20 实例
16384
RDB:每隔一段时间对redis进行一次持久化。
- 缺点:数据不完整
- 优点:速度快
AOF:把所有命令保存起来,如果想到重新生成到redis,那么就要把命令重新执行一次。
- 缺点:速度慢,文件比较大
- 优点:数据完整
voltile-lru: 从已设置过期时间的数据集(server.db[i].expires)中挑选最近频率最少数据淘汰
volatile-ttl: 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru: 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random: 从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
参看script—redis源码
from scrapy.utils.reqser import request_to_dict, request_from_dict
from . import picklecompat
class Base(object):
"""Per-spider base queue class"""
def __init__(self, server, spider, key, serializer=None):
"""Initialize per-spider redis queue.
Parameters
----------
server : StrictRedis
Redis client instance.
spider : Spider
Scrapy spider instance.
key: str
Redis key where to put and get messages.
serializer : object
Serializer object with ``loads`` and ``dumps`` methods.
"""
if serializer is None:
# Backward compatibility.
# TODO: deprecate pickle.
serializer = picklecompat
if not hasattr(serializer, 'loads'):
raise TypeError("serializer does not implement 'loads' function: %r"
% serializer)
if not hasattr(serializer, 'dumps'):
raise TypeError("serializer '%s' does not implement 'dumps' function: %r"
% serializer)
self.server = server
self.spider = spider
self.key = key % {'spider': spider.name}
self.serializer = serializer
def _encode_request(self, request):
"""Encode a request object"""
obj = request_to_dict(request, self.spider)
return self.serializer.dumps(obj)
def _decode_request(self, encoded_request):
"""Decode an request previously encoded"""
obj = self.serializer.loads(encoded_request)
return request_from_dict(obj, self.spider)
def __len__(self):
"""Return the length of the queue"""
raise NotImplementedError
def push(self, request):
"""Push a request"""
raise NotImplementedError
def pop(self, timeout=0):
"""Pop a request"""
raise NotImplementedError
def clear(self):
"""Clear queue/stack"""
self.server.delete(self.key)
class FifoQueue(Base):
"""Per-spider FIFO queue"""
def __len__(self):
"""Return the length of the queue"""
return self.server.llen(self.key)
def push(self, request):
"""Push a request"""
self.server.lpush(self.key, self._encode_request(request))
def pop(self, timeout=0):
"""Pop a request"""
if timeout > 0:
data = self.server.brpop(self.key, timeout)
if isinstance(data, tuple):
data = data[1]
else:
data = self.server.rpop(self.key)
if data:
return self._decode_request(data)
class PriorityQueue(Base):
"""Per-spider priority queue abstraction using redis' sorted set"""
def __len__(self):
"""Return the length of the queue"""
return self.server.zcard(self.key)
def push(self, request):
"""Push a request"""
data = self._encode_request(request)
score = -request.priority
# We don't use zadd method as the order of arguments change depending on
# whether the class is Redis or StrictRedis, and the option of using
# kwargs only accepts strings, not bytes.
self.server.execute_command('ZADD', self.key, score, data)
def pop(self, timeout=0):
"""
Pop a request
timeout not support in this queue class
"""
# use atomic range/remove using multi/exec
pipe = self.server.pipeline()
pipe.multi()
pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0)
results, count = pipe.execute()
if results:
return self._decode_request(results[0])
class LifoQueue(Base):
"""Per-spider LIFO queue."""
def __len__(self):
"""Return the length of the stack"""
return self.server.llen(self.key)
def push(self, request):
"""Push a request"""
self.server.lpush(self.key, self._encode_request(request))
def pop(self, timeout=0):
"""Pop a request"""
if timeout > 0:
data = self.server.blpop(self.key, timeout)
if isinstance(data, tuple):
data = data[1]
else:
data = self.server.lpop(self.key)
if data:
return self._decode_request(data)
# TODO: Deprecate the use of these names.
SpiderQueue = FifoQueue
SpiderStack = LifoQueue
SpiderPriorityQueue = PriorityQueue
# 通过发布订阅模式的PUB、SUB实现消息队列
# 发布者发布消息到频道了,频道就是一个消息队列。
# 发布者:
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
conn.publish('104.9MH', "hahahahahaha")
# 订阅者:
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
pub = conn.pubsub()
pub.subscribe('104.9MH')
while True:
msg= pub.parse_response()
print(msg)
对了,redis 做消息队列不合适
业务上避免过度复用一个redis,用它做缓存、做计算,还做任务队列,压力太大,不好。
发布和订阅,只要有任务就给所有订阅者没人一份
发布者:
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
conn.publish('104.9MH', "hahaha")
订阅者:
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
pub = conn.pubsub()
pub.subscribe('104.9MH')
while True:
msg= pub.parse_response()
print(msg)
Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别
(不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作,
所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务.
是 Twtter 开源的一个 Redis 和 Memcache 代理服务器,主要用于管理 Redis 和 Memcached 集群,
减少与Cache 服务器直接连接的数量。
import redis
pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
conn = redis.Redis(connection_pool=pool)
# pipe = r.pipeline(transaction=False)
pipe = conn.pipeline(transaction=True)
# 开始事务
pipe.multi()
pipe.set('name', 'bendere')
pipe.set('role', 'sb')
# 提交
pipe.execute()
注意:咨询是否当前分布式redis是否支持事务
在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。
假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,
EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。
面试题:你如何控制剩余的数量不会出问题?
方式一:- 通过redis的watch实现
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
# conn.set('count',1000)
val = conn.get('count')
print(val)
with conn.pipeline(transaction=True) as pipe:
# 先监视,自己的值没有被修改过
conn.watch('count')
# 事务开始
pipe.multi()
old_count = conn.get('count')
count = int(old_count)
print('现在剩余的商品有:%s',count)
input("问媳妇让不让买?")
pipe.set('count', count - 1)
# 执行,把所有命令一次性推送过去
pipe.execute()
方式二 - 数据库的锁
import redis
conn = redis.Redis(host='192.168.1.41',port=6379)
conn.set('count',1000)
with conn.pipeline() as pipe:
# 先监视,自己的值没有被修改过
conn.watch('count')
# 事务开始
pipe.multi()
old_count = conn.get('count')
count = int(old_count)
if count > 0: # 有库存
pipe.set('count', count - 1)
# 执行,把所有命令一次性推送过去
pipe.execute()
在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。
有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大
,而且很多简单的实现其实只需采用稍微增加一点复杂的设计就可以获得更好的可靠性。
用Redis实现分布式锁管理器的算法,我们把这个算法称为RedLock。
实现
- 写值并设置超时时间
- 超过一半的redis实例设置成功,就表示加锁完成。
- 使用:安装redlock-py
from redlock import Redlock
dlm = Redlock(
[
{"host": "localhost", "port": 6379, "db": 0},
{"host": "localhost", "port": 6379, "db": 0},
{"host": "localhost", "port": 6379, "db": 0},
]
)
# 加锁,acquire
my_lock = dlm.lock("my_resource_name",10000)
if my_lock:
# J进行操作
# 解锁,release
dlm.unlock(my_lock)
else:
print('获取锁失败')
redis分布式锁?
# 不是单机操作,又多了一/多台机器
# redis内部是单进程、单线程,是数据安全的(只有自己的线程在操作数据)
----------------------------------------------------------------
\A、B、C,三个实例(主)
1、来了一个'隔壁老王'要操作,且不想让别人操作,so,加锁;
加锁:'隔壁老王'自己生成一个随机字符串,设置到A、B、C里(xxx=666)
2、来了一个'邻居老李'要操作A、B、C,一读发现里面有字符串,擦,被加锁了,不能操作了,等着吧~
3、'隔壁老王'解决完问题,不用锁了,把A、B、C里的key:'xxx'删掉;完成解锁
4、'邻居老李'现在可以访问,可以加锁了
# 问题:
1、如果'隔壁老王'加锁后突然挂了,就没人解锁,就死锁了,其他人干看着没法用咋办?
2、如果'隔壁老王'去给A、B、C加锁的过程中,刚加到A,'邻居老李'就去操作C了,加锁成功or失败?
3、如果'隔壁老王'去给A、B、C加锁时,C突然挂了,这次加锁是成功还是失败?
4、如果'隔壁老王'去给A、B、C加锁时,超时时间为5秒,加一个锁耗时3秒,此次加锁能成功吗?
# 解决
1、安全起见,让'隔壁老王'加锁时设置超时时间,超时的话就会自动解锁(删除key:'xxx')
2、加锁程度达到(1/2)+1个就表示加锁成功,即使没有给全部实例加锁;
3、加锁程度达到(1/2)+1个就表示加锁成功,即使没有给全部实例加锁;
4、不能成功,锁还没加完就过期,没有意义了,应该合理设置过期时间
# 注意
使用需要安装redlock-py
----------------------------------------------------------------
from redlock import Redlock
dlm = Redlock(
[
{"host": "localhost", "port": 6379, "db": 0},
{"host": "localhost", "port": 6379, "db": 0},
{"host": "localhost", "port": 6379, "db": 0},
]
)
# 加锁,acquire
my_lock = dlm.lock("my_resource_name",10000)
if my_lock:
# 进行操作
# 解锁,release
dlm.unlock(my_lock)
else:
print('获取锁失败')
\通过sever.eval(self.unlock_script)执行一个lua脚本,用来删除加锁时的key
致性哈希
一致性hash算法(DHT)可以通过减少影响范围的方式,解决增减服务器导致的数据散列问题,从而解决了分布式环境下负载均衡问题;
如果存在热点数据,可以通过增添节点的方式,对热点区间进行划分,将压力分配至其他服务器,重新达到负载均衡的状态。
Python模块--hash_ring,即Python中的一致性hash
redis 有一个keys命令。
# 语法:KEYS pattern
# 说明:返回与指定模式相匹配的所用的keys。
该命令所支持的匹配模式如下:
1、?:用于匹配单个字符。例如,h?llo可以匹配hello、hallo和hxllo等;
2、*:用于匹配零个或者多个字符。例如,h*llo可以匹配hllo和heeeello等;
2、[]:可以用来指定模式的选择区间。例如h[ae]llo可以匹配hello和hallo,但是不能匹配hillo。同时,可以使用“/”符号来转义特殊的字符
# 注意
KEYS 的速度非常快,但如果数据太大,内存可能会崩掉,
如果需要从一个数据集中查找特定的key,最好还是用Redis的集合结构(set)来代替。