欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python知识之 方法与函数、偏函数、轮询和长轮询、流量削峰、乐观锁与悲观锁

程序员文章站 2023-11-04 12:28:10
方法与函数 函数需要手动传参self、cls,方法自动传,比如对象方法自动传self,类方法自动传cls,而函数相对而言需要手动传,比如静态绑定的函数,self是需要手动传值得,比如我们平常使用的函数都是手动传值。 判断函数和方法的方式 使用types模块中的FunctionType和MethodT ......

方法与函数

  函数需要手动传参self、cls,方法自动传,比如对象方法自动传self,类方法自动传cls,而函数相对而言需要手动传,比如静态绑定的函数,self是需要手动传值得,比如我们平常使用的函数都是手动传值。

判断函数和方法的方式

使用types模块中的functiontype和methodtype进行判断

Python知识之 方法与函数、偏函数、轮询和长轮询、流量削峰、乐观锁与悲观锁

 偏函数

  偏函数的作用在于:当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

  也就是说,当我们需要使用一个函数f1,该函数需要传多个参数时候,可以先试用functools.partial创建一个偏函数f2,该函数f2可以先传一个参数a进去,然后再使用f2传入其它参数b、c执行,效果等同于f1(a, b, c)执行一样。

示例:

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

print(int("333666"))  # 333666

但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做n进制的转换:

print(int("333666", base=8))  # 112566
print(int("333666", base=16))  # 3356262

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个new_int()的函数,默认把base=2传进去:

def new_int(x, base=2):
    return int(x, base)

print(new_int("1000000"))  # 64
print(new_int("10011100"))  # 156

functools.partial就是帮助我们创建一个偏函数,不需要我们自己定义new_int(),可以直接使用下面的代码创建一个新的函数new_int:

from functools import partial

new_int = partial(int, base=2)
print(new_int("10000000"))  # 128

所以,functiools.partial的作用就是把一个函数的某些参数固定住(设为一个默认参数),返回一个新的函数,调用这个函数会更方便些。

tips:创建偏函数时,实际上可以接受函数对象,*args,**kwargs这三个参数

from functools import partial
new_min = partial(min, 10)
print(new_min(20, 12, 16))  # 10
# 相当于
args = (10, 20, 12, 16)
min(*args)

 轮询和长轮询、http长连接和短连接、websocket

 轮询

  短轮询的基本思路就是浏览器每隔一段时间向浏览器发送http请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。

  这种方式的优点是比较简单,易于理解,实现起来也没有什么技术难点。缺点是显而易见的,这种方式由于需要不断的建立http连接,严重浪费了服务器端和客户端的资源。尤其是在客户端,距离来说,如果有数量级想对比较大的人同时位于基于短轮询的应用中,那么每一个用户的客户端都会疯狂的向服务器端发送http请求,而且不会间断。人数越多,服务器端压力越大,这是很不合理的。

  因此短轮询不适用于那些同时在线用户数量比较大,并且很注重性能的web应用。

var xhr = new xmlhttprequest();
    setinterval(function(){
        xhr.open('get','/user');
        xhr.onreadystatechange = function(){

        };
        xhr.send();
    },1000)

长轮询

ajax实现:

  当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)才返回。 。 客户端javascript响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。

  长轮询和短轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源。长轮询的缺点在于,连接挂起也会导致资源的浪费。

function ajax(){
        var xhr = new xmlhttprequest();
        xhr.open('get','/user');
        xhr.onreadystatechange = function(){
              ajax();
        };
        xhr.send();
    }

  轮询与长轮询都是基于http的,两者本身存在着缺陷:轮询需要更快的处理速度;长轮询则更要求处理并发的能力;两者都是“被动型服务器”的体现:服务器不会主动推送信息,而是在客户端发送ajax请求后进行返回的响应。而理想的模型是"在服务器端数据有了变化后,可以主动推送给客户端",这种"主动型"服务器是解决这类问题的很好的方案。web sockets就是这样的方案。

http的长连接和短连接

1. http协议与tcp/ip协议的关系

  http的长连接和短连接本质上是tcp长连接和短连接。http属于应用层协议,在传输层使用tcp协议,在网络层使用ip协议。ip协议主要解决网络路由和寻址问题,tcp协议主要解决如何在ip层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致。tcp有可靠,面向连接的特点。

  tcp/ip是个协议组,可分为三个层次:网络层、传输层和应用层。

  在网络层有ip协议、icmp协议、arp协议、rarp协议和bootp协议。

  在传输层中有tcp协议与udp协议。

  在应用层有:tcp包括ftp、http、telnet、smtp等协议

  udp包括dns、tftp等协议

  更细致地了解,可以参考这篇文章:细说osi七层协议模型及osi参考模型中的数据封装过程

2. 如何理解http协议是无状态的

  http协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。http是一个无状态的面向连接的协议,无状态不代表http不能保持tcp连接,更不能代表http使用的是udp协议(无连接)。

3. 什么是长连接、短连接?

  在http/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次http操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个html或其他类型的 web页中包含有其他的web资源,如javascript文件、图像文件、css文件等;当浏览器每遇到这样一个web资源,就会建立一个http会话。 

  短连接:

  连接->传输数据->关闭连接

  http是无状态的,浏览器和服务器每进行一次http操作,就建立一次连接,但任务结束就中断连接。

  也可以这样说:短连接是指socket连接后发送后接收完数据后马上断开连接。

  但从 http/1.1起,默认使用长连接,用以保持连接特性。使用长连接的http协议,会在响应头有加入这行代码:

connection:keep-alive

  在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输http数据的 tcp连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。keep-alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。

  长连接 

  连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。 

  长连接指建立socket连接后不管是否使用都保持连接,但安全性较差。 

  http协议的长连接和短连接,实质上是tcp协议的长连接和短连接。

3.1 tcp连接

  当网络通信时采用tcp协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接 时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的

经典的三次握手建立双向连接图:

Python知识之 方法与函数、偏函数、轮询和长轮询、流量削峰、乐观锁与悲观锁

经典的四次挥手断开连接图:

 Python知识之 方法与函数、偏函数、轮询和长轮询、流量削峰、乐观锁与悲观锁

tcp的握手挥手过程,具体推荐参看黄日成的:从 tcp 三次握手说起:浅析tcp协议中的疑难杂症 

3.2 tcp短连接

  我们模拟一下tcp短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server 发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在 client/server间传递一次读写操作

短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段

3.3 tcp长连接

  接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

  首先说一下tcp/ip详解上讲到的tcp保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务 器端检测到这种半开放的连接。

  如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:

  ①客户主机依然正常运行,并从服务器可达。客户的tcp响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
  ②客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的tcp都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
  ③客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
  ④客户机正常运行,但是服务器不可达,这种情况与2类似,tcp能发现的就是没有收到探查的响应。

3.4 长连接短连接操作过程

短连接的操作步骤是:
建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接
长连接的操作步骤是:
建立连接——数据传输...(保持连接)...数据传输——关闭连接

4. 长连接和短连接的优点和缺点

  由上可以看出,长连接可以省去较多的tcp建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测tcp连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可 以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

  短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在tcp的建立和关闭操作上浪费时间和带宽。

  长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。

5. 什么时候用长连接,短连接?
  长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个tcp连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就ok了,不用建立tcp连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

   而像web网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。

websocket

  websocket是html5定义的一个新协议,与传统的http协议不同,该协议可以实现服务器与客户端之间全双工通信。简单来说,首先需要在客户端和服务器端建立起一个连接,这部分需要http。连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。

  websocket的优点是实现了双向通信,缺点是服务器端的逻辑非常复杂。现在针对不同的后台语言有不同的插件可以使用。

  具体可以看看这篇文章:websocket请求过程分析及web聊天室

流量削峰 

  短时间内大量用户对同一服务器发送请求,导致瞬间流量巨大,也就是高并发剧增,这种流量出现峰值的现象,由于服务器的处理资源能力的有限的,所以峰值出现的时候,很容易导致服务器宕机,用户无法访问的情况出现。

  具体实例有很多:比如每年的春运买票同一时间大佬用户去抢购车票,比如淘宝双十一抢购活动,又比如秒杀活动,都是在短时间内用户发起请求导致流量剧增,服务器顶不住就会宕机。

  解决的办法之一就是流量削峰方案:

  削峰方案本质上就是更多的延缓用户请求,以及层层过滤用户的访问需求,遵循“最后落地到数据库的请求数要尽量少”的原则,来变相减轻服务器的压力。

总结:

1.对于秒杀这样的高并发场景业务,最基本的原则就是将请求拦截在系统上游,降低下游压力。如果不在前端拦截很可能造成数据库(mysql、oracle等)读写锁冲突,甚至导致死锁,最终还有可能出现雪崩等场景。

2.划分好动静资源,静态资源使用cdn进行服务分发。

3.充分利用缓存(redis等):增加qps,从而加大整个集群的吞吐量。

4.高峰值流量是压垮系统很重要的原因,所以需要kafka等消息队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。

乐观锁和悲观锁

悲观锁
  总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

  悲观锁使用场景

  比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。

乐观锁

  总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和cas算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。

  乐观锁使用场景

  比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。