总结下负载均衡的常用方案及适用场景

轮询调度

以轮询的方式依次请求调度不同的服务器;实现时,一般为服务器带上权重;这样有两个好处:

针对服务器的性能差异可分配不同的负载;
当需要将某个结点剔除时,只需要将其权重设置为0即可;
优点:实现简单、高效;易水平扩展;

缺点:请求到目的结点的不确定,造成其无法适用于有写的场景(缓存,数据库写)

应用场景:数据库或应用服务层中只有读的场景;

随机方式

请求随机分布到各个结点;在数据足够大的场景能达到一个均衡分布;

优点:实现简单、易水平扩展;

缺点:同Round Robin,无法用于有写的场景;

应用场景:数据库负载均衡,也是只有读的场景;

哈希:

根据key来计算需要落在的结点上,可以保证一个同一个键一定落在相同的服务器上;

优点:相同key一定落在同一个结点上,这样就可用于有写有读的缓存场景;

缺点:在某个结点故障后,会导致哈希键重新分布,造成命中率大幅度下降;

解决:一致性哈希 or 使用keepalived保证任何一个结点的高可用性,故障后会有其它结点顶上来;

应用场景:缓存,有读有写;

一致性哈希:

在服务器一个结点出现故障时,受影响的只有这个结点上的key,最大程度的保证命中率;

如twemproxy中的ketama方案;

生产实现中还可以规划指定子key哈希,从而保证局部相似特征的键能分布在同一个服务器上;

优点:结点故障后命中率下降有限;

应用场景:缓存;

根据键的范围来负载:

根据键的范围来负载,前1亿个键都存放到第一个服务器,1~2亿在第二个结点;

优点:水平扩展容易,存储不够用时,加服务器存放后续新增数据;

缺点:负载不均;数据库的分布不均衡;(数据有冷热区分,一般最近注册的用户更加活跃,这样造成后续的服务器非常繁忙,而前期的结点空闲很多)

适用场景:数据库分片负载均衡;

根据键对服务器结点数取模来负载:

根据键对服务器结点数取模来负载;比如有4台服务器,key取模为0的落在第一个结点,1落在第二个结点上。

优点:数据冷热分布均衡,数据库结点负载均衡分布;

缺点:水平扩展较难;

适用场景:数据库分片负载均衡;

纯动态结点负载均衡:

根据CPU、IO、网络的处理能力来决策接下来的请求如何调度;

优点:充分利用服务器的资源,保证个结点上负载处理均衡;

缺点:实现起来复杂,真实使用较少;

不用主动负载均衡:

使用消息队列转为异步模型,将负载均衡的问题消灭

负载均衡是一种推模型,一直向你发数据,那么,将所有的用户请求发到消息队列中,所有的下游结点谁空闲,谁上来取数据处理;转为拉模型之后,消息了负载的问题;

优点:通过消息队列的缓冲,保护后端系统,请求剧增时不会冲垮后端服务器;

水平扩展容易,加入新结点后,直接取queue即可;

缺点:不具有实时性;

应用场景:不需要实时返回的场景;

比如,12036下订单后,立刻返回提示信息:您的订单进去排队了…等处理完毕后,再异步通知;

转:https://www.cnblogs.com/data2value/p/6107653.html

from: https://www.pomelolee.com/1787.html

前言

随着互联网的发展,各种高并发、海量处理的场景越来越多。为了实现高可用、可扩展的系统,常常使用分布式,这样避免了单点故障和普通计算机cpu、内存等瓶颈。

但是分布式系统也带来了数据一致性的问题,比如用户抢购秒杀商品多台机器共同执行出现超卖等。有些同学容易将分布式锁与线程安全混淆,线程安全是指的线程间的协同。如果是多个进程间的协同需要用到分布式锁,本文总结了几种常见的分布式锁。

基于数据库

悲观锁—事务

比如用户抢购秒杀商品的场景,多台机器都接收到了抢购的请求,可以将获取库存、判断有货、用户付款、扣减库存等多个数据库操作放到一个事务,这样当一台机器与数据库建立链接请求了抢购商品这个事务,另外的机器只能等这个机器将请求完成才能操作数据库。在实际应用场景中,常常库存与交易是两个独立的系统,这时的事务是一个分布式事务,需要用到两段式、三段式提交。

优点:是比较安全的一种实现方法。

缺点:在高并发的场景下开销是不能容忍的。容易出现数据库死锁等情况。

乐观锁—基于版本号

乐观锁常常用于分布式系统对数据库某张特定表执行update操作。考虑线上选座的场景,用户A和B同时选择了某场次电影的一个座位,都去将座位的状态设置为已售。

设想这样的执行序列:
1、用户A判断该座位为未售状态;
2、用户B判断该座位为未售状态;
3、用户A执行update座位为已售;
4、用户B执行update座位为已售。

这样会出现同一个座位售出两次的情况,解决方案是在这张数据库表中增加一个版本号的字段。执行操作前读取当前数据库表中的版本号,在执行update语句时将版本号放在where语句中,如果更新了记录则说明成功,如果没有更新记录,则说明此次update失败。

加了乐观锁的执行序列:
1、用户A查询该座位,得到该座位是未售状态,版本号是5;
2、用户B查询该座位,得到该座位是未售状态,版本号是5;
3、用户A执行update语句将座位状态更新为已售,版本号更新为6;
4、用户B执行update语句时此时这个座位的记录版本号为6,没有版本号为5的这个座位的记录,执行失败。

优点:乐观锁的性能高于悲观锁,并不容易出现死锁。

缺点:乐观锁只能对一张表的数据进行加锁,如果是需要对多张表的数据操作加分布式锁,基于版本号的乐观锁是办不到的。

基于memcached

memcached可以基于add命令加锁。memcached的add指令是指如果有这个key,add命令则失败,如果没有这个key,则add命令成功。并且memcached支持设置过期时间的add原子操作。并发add同一个key也只有一个会成功。

基于memcached的add指令加分布式锁的思路为:定义一个key为分布式锁的key,如果add一个带过期时间的key成功则执行相应的业务操作,执行完判断锁是否过期,如果锁过期则不删除锁,如果锁没过期则删除锁。带过期时间是防止出现机器宕机,一直不能释放锁。

很多人基于memcached实现的分布式锁没有判断锁是否过期,执行完相应的业务操作直接删除锁会出现以下问题。

设想这样的执行序列:
1、机器A成功add一个带过期时间的key;
2、机器A在执行业务操作时出现较长时间的停顿,比如出现了较长时间的GC pause;
3、机器A还未在较长的停顿中恢复出来,锁已经过期,机器B成功add一个带过期时间的锁;
4、此时机器A从较长的停顿中恢复出来,执行完相应业务操作,删除了机器Badd的锁;
5、此时机器B的业务操作是在没有锁保护的情况下执行的。

但是memcached并没有提供一个判断key是否存在的操作,需要依赖于加锁的时候的时钟与执行完业务操作的时钟相减获得执行时间,将执行时间与锁的过期时间进行对比。或者将锁key对应的value设置为当前时间加上过期时间的时钟,执行完相应的业务操作获取锁key的值与当前时钟进行对比。

注:过期时间一定要长于业务操作的执行时间。

优点:性能高于基于数据库的实现方式。

基于redis

redis提供了setNx原子操作。基于redis的分布式锁也是基于这个操作实现的,setNx是指如果有这个key就set失败,如果没有这个key则set成功,但是setNx不能设置超时时间。

基于redis组成的分布式锁解决方案为:
1、setNx一个锁key,相应的value为当前时间加上过期时间的时钟;
2、如果setNx成功,或者当前时钟大于此时key对应的时钟则加锁成功,否则加锁失败退出;
3、加锁成功执行相应的业务操作(处理共享数据源);
4、释放锁时判断当前时钟是否小于锁key的value,如果当前时钟小于锁key对应的value则执行删除锁key的操作。

注:这对于单点的redis能很好地实现分布式锁,如果redis集群,会出现master宕机的情况。如果master宕机,此时锁key还没有同步到slave节点上,会出现机器B从新的master上获取到了一个重复的锁。

设想以下执行序列:
1、机器AsetNx了一个锁key,value为当前时间加上过期时间,master更新了锁key的值;
2、此时master宕机,选举出新的master,新的master正同步数据;
3、新的master不含锁key,机器BsetNx了一个锁key,value为当前时间加上过期时间;

这样机器A和机器B都获得了一个相同的锁;解决这个问题的办法可以在第3步进行优化,内存中存储了锁key的value,在执行访问共享数据源前再判断内存存储的锁key的value与此时redis中锁key的value是否相等如果相等则说明获得了锁,如果不相等则说明在之前有其他的机器修改了锁key,加锁失败。同时在第4步不仅仅判断当前时钟是否小于锁key的value,也可以进一步判断存储的value值与此时的value值是否相等,如果相等再进行删除。

此时的执行序列:
1、机器AsetNx了一个锁key,value为当前时间加上过期时间,master更新了锁key的值;
2、此时,master宕机,选举出新的master,新的master正同步数据;
3、机器BsetNx了一个锁key,value为此时的时间加上过期时间;
4、当机器A再次判断内存存储的锁与此时的锁key的值不一样时,机器A加锁失败;
5、当机器B再次判断内存存储的锁与此时的锁key的值一样,机器B加锁成功。

注:如果是为了效率而使用分布式锁,例如:部署多台定时作业的机器,在同一时间只希望一台机器执行一个定时作业,在这种场景下是允许偶尔的失败的,可以使用单点的redis分布式锁;如果是为了正确性而使用分布式锁,最好使用再次检查的redis分布式锁,再次检查的redis分布式锁虽然性能下降了,但是正确率更高。

基于zookeeper

基于zookeeper的分布式锁大致思路为:
1、客户端尝试创建ephemeral类型的znode节点/lock;
2、如果客户端创建成功则加锁成功,可以执行访问共享数据源的操作,如果客户端创建失败,则证明有别的客户端加锁成功,此次加锁失败;
3、如果加锁成功当客户端执行完访问共享数据源的操作,则删除znode节点/lock。

基于zookeeper实现分布式锁不需要设置过期时间,因为ephemeral类型的节点,当客户端与zookeeper创建的session在一定时间(session的过期时间内)没有收到心跳,则认为session过期,会删除客户端创建的所有ephemeral节点。

但是这样会出现两个机器共同持有锁的情况。设想以下执行序列。
1、机器A创建了znode节点/lock;
2、机器A执行相应操作,进入了较长时间的GC pause;
3、机器A与zookeeper的session过期,相应的/lock节点被删除;
4、机器B创建了znode节点/lock;
5、机器A从较长的停顿中恢复;
6、此时机器A与机器B都认为自己获得了锁。

与基于redis的分布式锁,基于zookeeper的锁可以增加watch机制,当机器创建节点/lock失败的时候可以进入等待,当/lock节点被删除的时候zookeeper利用watch机制通知机器。但是这种增加watch机制的方式只能针对较小客户端集群,如果较多客户端集群都在等待/lock节点被删除,当/lock节点被删除时,zookeeper要通知较多机器,对zookeeper造成较大的性能影响。这就是所谓的羊群效应。

优化的大致思路为:
1、客户端调用创建名为“lock/numberlock”类型为EPHEMERAL_SEQUENTIAL的节点;
2、客户端获取lock节点下所有的子节点;
3、判断自己是否是序号最小的节点的,如果是最小的节点则加锁成功,如果不是序号最小的节点,则在比自己小的并且最接近的节点注册监听;
4、当被关注的节点删除后,再次获取lock节点下的所有子节点,判断是否是最小序号,如果是最小序号则加锁成功;

优化后的思路,虽然能一定程度避免羊群效应,但是也不能避免两个机器共同持有锁的情况。

python的100个实例005-与时间有关的一切

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Python有关时间的操作
#datetime是模块,datetime模块包含一个datetime类,
# 通过from datetime import datetime导入的是datetime这个类。
from datetime import datetime, timedelta, timezone
print(datetime.now())
mydate = datetime(2018, 11, 11, 20, 00)
print(mydate)
#Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数。
print(mydate.timestamp())
mytimeStamp = 1519637291.0
print(datetime.fromtimestamp(mytimeStamp)) #localtime
print(datetime.utcfromtimestamp(mytimeStamp)) #utctime
cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
print(cday)
print(datetime.now().strftime('%a, %b %d %H:%M'))
# 日期相加减 参数只有timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
date = datetime(2018, 11, 11, 20, 00)
print(date + timedelta(hours=10)) # +10个小时
print(date - timedelta(days=1)) # 减1天
print(date + timedelta(days=1, hours=2)) # 加1天2小时
#转换时区
tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00
print(datetime.now().replace(tzinfo=tz_utc_8))
#获取一个时间正确的时区,并强制设置为该时区
bj_dt = datetime.now().replace(tzinfo=tz_utc_8)
print("基准时区", bj_dt)
bj_dt = bj_dt.astimezone(timezone(timedelta(hours=8)))
print("北京时区", bj_dt)
tokyo_dt = bj_dt.astimezone(timezone(timedelta(hours=9)))
print("东京时区", tokyo_dt)