之前文章提到了阿里云经典网络问题,这里操作一把。
1,首先扫端口
nmap -v -sT 10.161.91.0/16 -p 6379 –open
发现一些IP和端口
2,随便找一个登录
redis-cli -c -h 10.161.29.xx -p 6379
这里用-c的原因是redis采用的是集群,允许集群模式
scan 0看下数据:
发现很轻易就获得了数据!!
之前文章提到了阿里云经典网络问题,这里操作一把。
1,首先扫端口
nmap -v -sT 10.161.91.0/16 -p 6379 –open
发现一些IP和端口
2,随便找一个登录
redis-cli -c -h 10.161.29.xx -p 6379
这里用-c的原因是redis采用的是集群,允许集群模式
scan 0看下数据:
发现很轻易就获得了数据!!
阿里云上的 VM 在 10.25.0.0/16 这个网段。
$ nmap -v -sT 10.25.6.0/24 –open
你还可以扫个更大的网段(专扫redis):
$ nmap -v -sT 10.25.0.0/16 -p 6379 –open
本文将分两个专题去理解HTTPS。
专题一:HTTPS为什么安全
1、http为什么不安全?
http协议属于明文传输协议,交互过程以及数据传输都没有进行加密,通信双方也没有进行任何认证,通信过程非常容易遭遇劫持、监听、篡改,严重情况下,会造成恶意的流量劫持等问题,甚至造成个人隐私泄露(比如银行卡卡号和密码泄露)等严重的安全问题。
可以把http通信比喻成寄送信件一样,A给B寄信,信件在寄送过程中,会经过很多的邮递员之手,他们可以拆开信读取里面的内容(因为http是明文传输的)。A的信件里面的任何内容(包括各类账号和密码)都会被轻易窃取。除此之外,邮递员们还可以伪造或者修改信件的内容,导致B接收到的信件内容是假的。
比如常见的,在http通信过程中,“中间人”将广告链接嵌入到服务器发给用户的http报文里,导致用户界面出现很多不良链接; 或者是修改用户的请求头URL,导致用户的请求被劫持到另外一个网站,用户的请求永远到不了真正的服务器。这些都会导致用户得不到正确的服务,甚至是损失惨重。
要解决http带来的问题,就要引入加密以及身份验证机制。
如果Server(以后简称服务器)给Client(以后简称 客户端)的消息是密文的,只有服务器和客户端才能读懂,就可以保证数据的保密性。同时,在交换数据之前,验证一下对方的合法身份,就可以保证通信双方的安全。那么,问题来了,服务器把数据加密后,客户端如何读懂这些数据呢?这时服务器必须要把加密的密钥(对称密钥,后面会详细说明)告诉客户端,客户端才能利用对称密钥解开密文的内容。但是,服务器如果将这个对称密钥以明文的方式给客户端,还是会被中间人截获,中间人也会知道对称密钥,依然无法保证通信的保密性。但是,如果服务器以密文的方式将对称密钥发给客户端,客户端又如何解开这个密文,得到其中的对称密钥呢?
说到这里,大家是不是有点儿糊涂了?一会儿密钥,一会儿对称密钥,都有点儿被搞晕的节奏。在这里,提前给大家普及一下,这里的密钥,指的是非对称加解密的密钥,是用于TLS握手阶段的; 对称密钥,指的是对称加解密的密钥,是用于后续传输数据加解密的。下面将详细说明。
这时,我们引入了非对称加解密的概念。在非对称加解密算法里,公钥加密的数据,有且只有唯一的私钥才能够解密,所以服务器只要把公钥发给客户端,客户端就可以用这个公钥来加密进行数据传输的对称密钥。客户端利用公钥将对称密钥发给服务器时,即使中间人截取了信息,也无法解密,因为私钥只部署在服务器,其他任何人都没有私钥,因此,只有服务器才能够解密。服务器拿到客户端的信息并用私钥解密之后,就可以拿到加解密数据用的对称密钥,通过这个对称密钥来进行后续通信的数据加解密。除此之外,非对称加密可以很好的管理对称密钥,保证每次数据加密的对称密钥都是不相同的,这样子的话,即使客户端病毒拉取到通信缓存信息,也无法窃取正常通信内容。
上述通信过程,可以画成以下交互图:
但是这样似乎还不够,如果通信过程中,在三次握手或者客户端发起HTTP请求过程中,客户端的请求被中间人劫持,那么中间人就可以伪装成“假冒客户端”和服务器通信;中间人又可以伪装成“假冒服务器”和客户端通信。接下来,我们详细阐述中间人获取对称密钥的过程:
中间人在收到服务器发送给客户端的公钥(这里是“正确的公钥”)后,并没有发给客户端,而是中间人将自己的公钥(这里中间人也会有一对公钥和私钥,这里称呼为“伪造公钥”)发给客户端。之后,客户端把对称密钥用这个“伪造公钥”加密后,发送过程中经过了中间人,中间人就可以用自己的私钥解密数据并拿到对称密钥,此时中间人再把对称密钥用“正确的公钥”加密发回给服务器。此时,客户端、中间人、服务器都拥有了一样的对称密钥,后续客户端和服务器的所有加密数据,中间人都可以通过对称密钥解密出来。
中间人获取对称密钥的过程如下:
为了解决此问题,我们引入了数字证书的概念。服务器首先生成公私钥,将公钥提供给相关机构(CA),CA将公钥放入数字证书并将数字证书颁布给服务器,此时服务器就不是简单的把公钥给客户端,而是给客户端一个数字证书,数字证书中加入了一些数字签名的机制,保证了数字证书一定是服务器给客户端的。中间人发送的伪造证书,不能够获得CA的认证,此时,客户端和服务器就知道通信被劫持了。加入了CA数字签名认证的SSL会话过程如下所示:
3、https协议简介
为什么是简单地介绍https协议呢?因为https涉及的东西实在太多了,尤其是其中的加解密算法,十分复杂,作者本身对这些算法也研究不完,只是懂其中的一些皮毛而已。这部分仅仅是简单介绍一些关于https的最基本原理,为后面分析https的建立过程以及https优化等内容打下理论基础。
对称加密是指:加密和解密使用相同密钥的算法。它要求发送方和接收方在安全通信之前,商定一个对称密钥。对称算法的安全性完全依赖于密钥,密钥泄漏就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信至关重要。
3.1.1 对称加密又分为两种模式:流加密和分组加密
流加密是将消息作为字节流对待,并且使用数学函数分别作用在每一个字节位上。使用流加密时,每加密一次,相同的明文位会转换成不同的密文位。流加密使用了密钥流生成器,它生成的字节流与明文字节流进行异或,从而生成密文。
分组加密是将消息划分为若干个分组,这些分组随后会通过数学函数进行处理,每次一个分组。假设使用64位的分组密码,此时如果消息长度为640位,就会被划分成10个64位的分组(如果最后一个分组长度不到64,则用0补齐之后加到64位),每个分组都用一系列数学公式进行处理,最后得到10个加密文本分组。然后,将这条密文消息发送给对端。对端必须拥有相同的分组密码,以相反的顺序对10个密文分组使用前面的算法解密,最终得到明文消息。比较常用的分组加密算法有DES、3DES、AES。其中DES是比较老的加密算法,现在已经被证明不安全。而3DES是一个过渡的加密算法,相当于在DES基础上进行三重运算来提高安全性,但其本质上还是和DES算法一致。而AES是DES算法的替代算法,是现在最安全的对称加密算法之一。
3.1.2 对称加密算法的优缺点:
优点:计算量小、加密速度快、加密效率高。
缺点:
(1)交易双方都使用同样密钥,安全性得不到保证;
(2)每次使用对称加密算法时,都需要使用其他人不知道的惟一密钥,这会使得发收信息双方所拥有的钥匙数量呈几何级数增长,密钥管理成为负担。
RSA:诞生于1977年,经过了长时间的破解测试,算法安全性很高,最重要的是,算法实现非常简单。缺点就是需要比较大的质数(目前常用的是2048位)来保证安全强度,极其消耗CPU运算资源。RSA是目前唯一一个既能用于密钥交换又能用于证书签名的算法,RSA 是最经典,同时也是最常用的是非对称加解密算法。
(1)CPU计算资源消耗非常大。一次完全TLS握手,密钥交换时的非对称解密计算量占整个握手过程的90%以上。而对称加密的计算量只相当于非对称加密的0.1%。如果后续的应用层数据传输过程也使用非对称加解密,那么CPU性能开销太庞大,服务器是根本无法承受的。赛门特克给出的实验数据显示,加解密同等数量的文件,非对称算法消耗的CPU资源是对称算法的1000倍以上。
(2)非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是2048位,意味着待加密内容不能超过256个字节。
https协议中身份认证的部分是由CA数字证书完成的,证书由公钥、证书主体、数字签名等内容组成。在客户端发起SSL请求后,服务端会将数字证书发给客户端,客户端会对证书进行验证(验证这张证书是否是伪造的?也就是公钥是否是伪造的),如果证书不是伪造的,客户端就获取用于对称密钥交换的非对称密钥(获取公钥)。
1、身份授权。确保浏览器访问的网站是经过CA验证的可信任的网站。
2、分发公钥。每个数字证书都包含了注册者生成的公钥(验证确保是合法的,非伪造的公钥)。在SSL握手时会通过certificate消息传输给客户端。
3、验证证书合法性。客户端接收到数字证书后,会对证书合法性进行验证。只有验证通过后的证书,才能够进行后续通信过程。
(2)RA(证书注册及审核机构)检查实体的合法性(在注册系统里面是否注册过的正规公司)。
(3)CA(证书签发机构)签发证书,发送给申请者实体。
(4)证书更新到repository(负责数字证书及CRL内容存储和分发),实体终端后续从repository更新证书,查询证书状态等。
申请者拿到CA的证书并部署在网站服务器端,那浏览器发起握手并接收到证书后,如何确认这个证书就是CA签发的呢?怎样避免第三方伪造这个证书?答案就是数字签名(digital signature)。数字签名是证书的防伪标签,目前使用最广泛的SHA-RSA(SHA用于哈希算法,RSA用于非对称加密算法)。数字签名的制作和验证过程如下:
1、数字签名的签发。首先是使用哈希函数对待签名内容进行安全哈希,生成消息摘要,然后使用CA自己的私钥对消息摘要进行加密。
2、数字签名的校验。使用CA的公钥解密签名,然后使用相同的签名函数对签名证书内容进行签名,并和服务端数字签名里的签名内容进行比较,如果相同就认为校验成功。
需要注意的是:
(1)数字签名签发和校验使用的非对称密钥是CA自己的公钥和私钥,跟证书申请者(提交证书申请的公司实体)提交的公钥没有任何关系。
(2)数字签名的签发过程跟公钥加密的过程刚好相反,即是用私钥加密,公钥解密。(一对公钥和私钥,公钥加密的内容只有私钥能够解密;反过来,私钥加密的内容,也就有公钥才能够解密)
(3)现在大的CA都会有证书链,证书链的好处:首先是安全,保持CA的私钥离线使用。第二个好处是方便部署和撤销。这里为啥要撤销呢?因为,如果CA数字证书出现问题(被篡改或者污染),只需要撤销相应级别的证书,根证书依然是安全的。
(4)根CA证书都是自签名,即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的非对称密钥进行签名和验证的。
(5)怎样获取根CA和多级CA的密钥对?还有,既然是自签名和自认证,那么它们是否安全可信?这里的答案是:当然可信,因为这些厂商跟浏览器和操作系统都有合作,它们的根公钥都默认装到了浏览器或者操作系统环境里。
数据传输过程中的完整性使用MAC算法来保证。为了避免网络中传输的数据被非法篡改,或者数据比特被污染,SSL利用基于MD5或SHA的MAC算法来保证消息的完整性(由于MD5在实际应用中存在冲突的可能性比较大,所以尽量别采用MD5来验证内容一致性)。 MAC算法是在密钥参与下的数据摘要算法,能将密钥和任意长度的数据转换为固定长度的数据。发送者在密钥的作用下,利用MAC算法计算出消息的MAC值,并将其添加在需要发送的消息之后,并发送给接收者。接收者利用同样的密钥和MAC算法计算出消息的MAC值,并与接收到的MAC值比较。如果二者相同,则报文没有改变;否则,报文在传输过程中被修改或者污染,接收者将丢弃该报文。 SHA也不能使用SHA0和SHA1,山东大学的王小云教授(很牛的一个女教授,大家有兴趣可以上网搜索一下她的事迹)在2005年就宣布破解了 SHA-1完整版算法,并获得了业内专家的认可。微软和google都已经宣布16年及17年之后不再支持sha1签名证书。
专题二:实际抓包分析
本文对百度搜索进行了两次抓包,第一次抓包之前清理了浏览器的所有缓存;第二次抓包是在第一次抓包后的半分钟内。
百度在2015年已经完成了百度搜索的全站https,这在国内https发展中具有重大的意义(目前BAT三大家中,只有百度宣称自己完成了全站HTTPS)。所以这篇文章就以www.baidu.com为例进行分析。
同时,作者采用的是chrome浏览器,chrome支持SNI (server Name Indication) 特性,对于HTTPS性能优化有很大的用处。
注:SNI是为了解决一个服务器使用多个域名和证书的SSL/TLS扩展。一句话简述它的工作原理就是:在和服务器建立SSL连接之前,先发送要访问的域名(hostname),这样服务器根据这个域名返回一个合适的证书。目前,大多数操作系统和浏览器都已经很好地支持SNI扩展,OpenSSL 0.9.8已经内置这一功能,新版的nginx和apache也支持SNI扩展特性。
本文抓包访问的URL为:http://www.baidu.com/
(如果是https://www.baidu.com/,则以下结果不一样!)
抓包结果:
可以看到,百度采用以下策略:
(1)对于高版本浏览器,如果支持 https,且加解密算法在TLS1.0 以上的,都将所有 http请求重定向到 https请求
(2)对于https请求,则不变。
【详细解析过程】
可以看到,我的电脑访问的是http://www.baidu.com/,在初次建立三次握手的时候, 客户端是去连接 8080端口的(我所在小区网络总出口做了一层总代理,因此,客户端实际和代理机做的三次握手,代理机再帮客户端去连接百度服务器)
由于小区网关设置了代理访问,因此,在进行https访问的时候,客户端需要和代理机做”HTTPS CONNECT tunnel” 连接(关于”HTTPS CONNECT tunnel”连接,可以理解为:虽然后续的https请求都是代理机和百度服务器进行公私钥连接和对称密钥交换,以及数据通信;但是,有了隧道连接之后,可以认为客户端也在直接和百度服务器进行通信。)
fiddler抓包结果:
3、client hello
在客户端问候中,有四个字节以Unix时间格式记录了客户端的协调世界时间(UTC)。协调世界时间是从1970年1月1日开始到当前时刻所经历的秒数。在这个例子中,0x2516b84b就是协调世界时间。在他后面有28字节的随机数(random_C),在后面的过程中我们会用到这个随机数。
如果出于某种原因,对话中断,就需要重新握手。为了避免重新握手而造成的访问效率低下,这时候引入了session ID的概念, session ID的思想很简单,就是每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的“对称密钥”,而不必重新生成一把。
因为我们抓包的时候,是几个小时内第一次访问https://www.baodu.com ,因此,这里并没有Session ID.(稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID)
session ID是目前所有浏览器都支持的方法,但是它的缺点在于session ID往往只保留在一台服务器上。所以,如果客户端的请求发到另一台服务器(这是很有可能的,对于同一个域名,当流量很大的时候,往往后台有几十台RS机在提供服务),就无法恢复对话。session ticket就是为了解决这个问题而诞生的,目前只有Firefox和Chrome浏览器支持。
RFC2246中建议了很多中组合,一般写法是”密钥交换算法-对称加密算法-哈希算法,以“TLS_RSA_WITH_AES_256_CBC_SHA”为例:
(a)TLS为协议,RSA为密钥交换的算法;
(b)AES_256_CBC是对称加密算法(其中256是密钥长度,CBC是分组方式);
(c)SHA是哈希的算法。
浏览器支持的加密算法一般会比较多,而服务端会根据自身的业务情况选择比较适合的加密组合发给客户端。(比如综合安全性以及速度、性能等因素)
当我们去访问一个站点时,一定是先通过DNS解析出站点对应的ip地址,通过ip地址来访问站点,由于很多时候一个ip地址是给很多的站点公用,因此如果没有server_name这个字段,server是无法给与客户端相应的数字证书的,Server_name扩展则允许服务器对浏览器的请求授予相对应的证书。
服务器在收到client hello后,会回复三个数据包,下面分别看一下:
4、Server Hello
4.1、我们得到了服务器的以Unix时间格式记录的UTC和28字节的随机数 (random_S)。
4.2、Seesion ID,服务端对于session ID一般会有三种选择 (稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID):
(1)恢复的session ID:我们之前在client hello里面已经提到,如果client hello里面的session ID在服务端有缓存,服务端会尝试恢复这个session;
(2)新的session ID:这里又分两种情况,第一种是client hello里面的session ID是空值,此时服务端会给客户端一个新的session ID,第二种是client hello里面的session ID此服务器并没有找到对应的缓存,此时也会回一个新的session ID给客户端;
(3)NULL:服务端不希望此session被恢复,因此session ID为空。
4.3、我们记得在client hello里面,客户端给出了多种加密族 Cipher,而在客户端所提供的加密族中,服务端挑选了“TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256”
(a)TLS为协议,RSA为密钥交换的算法;
(b)AES_256_CBC是对称加密算法(其中256是密钥长度,CBC是分组方式);
(c)SHA是哈希的算法。
这就意味着服务端会使用ECDHE-RSA算法进行密钥交换,通过AES_128_GCM对称加密算法来加密数据,利用SHA256哈希算法来确保数据完整性。
5、Certificate
在前面的https原理研究中,我们知道为了安全的将公钥发给客户端,服务端会把公钥放入数字证书中并发给客户端(数字证书可以自签发,但是一般为了保证安全会有一个专门的CA机构签发),所以这个报文就是数字证书,4097 bytes就是证书的长度。
我们打开这个证书,可以看到证书的具体信息,这个具体信息通过抓包报文的方式不是太直观,可以在浏览器上直接看。(点击chrome浏览器左上方的绿色锁型按钮)
6、Server Hello Done
我们抓的包是将 Server Hello Done 和 server key exchage 合并的包:
7、客户端验证证书真伪性
客户端验证证书的合法性,如果验证通过才会进行后续通信,否则根据错误情况不同做出提示和操作,合法性验证包括如下:
(1)证书链的可信性trusted certificate path,方法如前文所述;
(2)证书是否吊销revocation,有两类方式离线CRL与在线OCSP,不同的客户端行为会不同;
(3)有效期expiry date,证书是否在有效时间范围;
(4)域名domain,核查证书域名是否与当前的访问域名匹配,匹配规则后续分析;
8、秘钥交换
这个过程非常复杂,大概总结一下:
(1)首先,客户端利用CA数字证书实现身份认证,利用非对称加密协商对称密钥。
(2)客户端会向服务器传输一个“pubkey”随机数,服务器收到之后,利用特定算法生成另外一个“pubkey”随机数,客户端利用这两个“pubkey”随机数生成一个 pre-master 随机数。
(3)客户端利用自己在 client hello 里面传输的随机数 random_C,以及收到的 server hello 里面的随机数 random_S,外加 pre-master 随机数,利用对称密钥生成算法生成 对称密钥enc_key:enc_key=Fuc(random_C, random_S, Pre-Master)
9、生成session ticket
如果出于某种原因,对话中断,就需要重新握手。为了避免重新握手而造成的访问效率低下,这时候引入了session ID的概念, session ID(以及session ticke)的思想很简单,就是每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的“对话密钥”,而不必重新生成一把。
因为我们抓包的时候,是几个小时内第一次访问 https://www.baodu.com 首页,因此,这里并没有 Session ID.(稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID)
session ID是目前所有浏览器都支持的方法,但是它的缺点在于session ID往往只保留在一台服务器上。所以,如果客户端的请求发到另一台服务器,就无法恢复对话。session ticket就是为了解决这个问题而诞生的,目前只有Firefox和Chrome浏览器支持。
后续建立新的https会话,就可以利用 session ID 或者 session Tickets , 对称秘钥可以再次使用,从而免去了 https 公私钥交换、CA认证等等过程,极大地缩短 https 会话连接时间。
10、利用对称秘钥传输数据
三、半分钟后,再次访问百度:
有这些大的不同:
由于服务器和浏览器缓存了 Session ID 和 Session Tickets,不需要再进行 公钥证书传递,CA认证,生成 对称密钥等过程,直接利用半分钟前的对称密钥加解密数据进行会话。
2、Server Hello
2017年1月28日,正月初一,微信公布了用户在除夕当天收发微信红包的数量——142亿个,而其收发峰值也已达到76万每秒。百亿级别的红包,如何保障并发性能与资金安全?这给微信带来了超级挑战。面对挑战,微信红包在分析了业界“秒杀”系统解决方案的基础上,采用了SET化、请求排队串行化、双维度分库表等设计,形成了独特的高并发、资金安全系统解决方案。实践证明,该方案表现稳定,且实现了除夕夜系统零故障运行。
本文将为读者介绍百亿级别红包背后的系统高并发设计方案,包括微信红包的两大业务特点、微信红包系统的技术难点、解决高并发问题通常使用的方案,以及微信红包系统的高并发解决方案。
微信红包(尤其是发在微信群里的红包,即群红包)业务形态上很类似网上的普通商品“秒杀”活动。
用户在微信群里发一个红包,等同于是普通商品“秒杀”活动的商品上架;微信群里的所有用户抢红包的动作,等同于“秒杀”活动中的查询库存;用户抢到红包后拆红包的动作,则对应“秒杀”活动中用户的“秒杀”动作。
不过除了上面的相同点之外,微信红包在业务形态上与普通商品“秒杀”活动相比,还具备自身的特点:
首先,微信红包业务比普通商品“秒杀”有更海量的并发要求。
微信红包用户在微信群里发一个红包,等同于在网上发布一次商品“秒杀”活动。假设同一时间有10万个群里的用户同时在发红包,那就相当于同一时间有10万个“秒杀”活动发布出去。10万个微信群里的用户同时抢红包,将产生海量的并发请求。
其次,微信红包业务要求更严格的安全级别。
微信红包业务本质上是资金交易。微信红包是微信支付的一个商户,提供资金流转服务。
用户发红包时,相当于在微信红包这个商户上使用微信支付购买一笔“钱”,并且收货地址是微信群。当用户支付成功后,红包“发货”到微信群里,群里的用户拆开红包后,微信红包提供了将“钱”转入折红包用户微信零钱的服务。
资金交易业务比普通商品“秒杀”活动有更高的安全级别要求。普通的商品“秒杀”商品由商户提供,库存是商户预设的,“秒杀”时可以允许存在“超卖”(即实际被抢的商品数量比计划的库存多)、“少卖”(即实际被抢的商户数量比计划的库存少)的情况。但是对于微信红包,用户发100元的红包绝对不可以被拆出101元;用户发100元只被领取99元时,剩下的1元在24小时过期后要精确地退还给发红包用户,不能多也不能少。
以上是微信红包业务模型上的两大特点。
在介绍微信红包系统的技术难点之前,先介绍下简单的、典型的商品“秒杀”系统的架构设计,如下图所示。
该系统由接入层、逻辑服务层、存储层与缓存构成。Proxy处理请求接入,Server承载主要的业务逻辑,Cache用于缓存库存数量、DB则用于数据持久化。
一个“秒杀”活动,对应DB中的一条库存记录。当用户进行商品“秒杀”时,系统的主要逻辑在于DB中库存的操作上。一般来说,对DB的操作流程有以下三步:
a. 锁库存
b. 插入“秒杀”记录
c. 更新库存
其中,锁库存是为了避免并发请求时出现“超卖”情况。同时要求这三步操作需要在一个事务中完成(所谓的事务,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行)。
“秒杀”系统的设计难点就在这个事务操作上。商品库存在DB中记为一行,大量用户同时“秒杀”同一商品时,第一个到达DB的请求锁住了这行库存记录。在第一个事务完成提交之前这个锁一直被第一个请求占用,后面的所有请求需要排队等待。同时参与“秒杀”的用户越多,并发进DB的请求越多,请求排队越严重。因此,并发请求抢锁,是典型的商品“秒杀”系统的设计难点。
微信红包业务相比普通商品“秒杀”活动,具有海量并发、高安全级别要求的特点。在微信红包系统的设计上,除了并发请求抢锁之外,还有以下两个突出难点:
首先,事务级操作量级大。上文介绍微信红包业务特点时提到,普遍情况下同时会有数以万计的微信群在发红包。这个业务特点映射到微信红包系统设计上,就是有数以万计的“并发请求抢锁”同时在进行。这使得DB的压力比普通单个商品“库存”被锁要大很多倍。
其次,事务性要求严格。微信红包系统本质上是一个资金交易系统,相比普通商品“秒杀”系统有更高的事务级别要求。
普通商品“秒杀”活动系统,解决高并发问题的方案,大体有以下几种:
方案一,使用内存操作替代实时的DB事务操作。
如图2所示,将“实时扣库存”的行为上移到内存Cache中操作,内存Cache操作成功直接给Server返回成功,然后异步落DB持久化。
这个方案的优点是用内存操作替代磁盘操作,提高了并发性能。
但是缺点也很明显,在内存操作成功但DB持久化失败,或者内存Cache故障的情况下,DB持久化会丢数据,不适合微信红包这种资金交易系统。
方案二,使用乐观锁替代悲观锁。
所谓悲观锁,是关系数据库管理系统里的一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。对应于上文分析中的“并发请求抢锁”行为。
所谓乐观锁,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
商品“秒杀”系统中,乐观锁的具体应用方法,是在DB的“库存”记录中维护一个版本号。在更新“库存”的操作进行前,先去DB获取当前版本号。在更新库存的事务提交时,检查该版本号是否已被其他事务修改。如果版本没被修改,则提交事务,且版本号加1;如果版本号已经被其他事务修改,则回滚事务,并给上层报错。
这个方案解决了“并发请求抢锁”的问题,可以提高DB的并发处理能力。
但是如果应用于微信红包系统,则会存在下面三个问题:
基于以上原因,微信红包系统不能采用乐观锁的方式解决并发抢锁问题。
综合上面的分析,微信红包系统针对相应的技术难点,采用了下面几个方案,解决高并发问题。
1.系统垂直SET化,分而治之。
微信红包用户发一个红包时,微信红包系统生成一个ID作为这个红包的唯一标识。接下来这个红包的所有发红包、抢红包、拆红包、查询红包详情等操作,都根据这个ID关联。
红包系统根据这个红包ID,按一定的规则(如按ID尾号取模等),垂直上下切分。切分后,一个垂直链条上的逻辑Server服务器、DB统称为一个SET。
各个SET之间相互独立,互相解耦。并且同一个红包ID的所有请求,包括发红包、抢红包、拆红包、查详情详情等,垂直stick到同一个SET内处理,高度内聚。通过这样的方式,系统将所有红包请求这个巨大的洪流分散为多股小流,互不影响,分而治之,如下图所示。
这个方案解决了同时存在海量事务级操作的问题,将海量化为小量。
2.逻辑Server层将请求排队,解决DB并发问题。
红包系统是资金交易系统,DB操作的事务性无法避免,所以会存在“并发抢锁”问题。但是如果到达DB的事务操作(也即拆红包行为)不是并发的,而是串行的,就不会存在“并发抢锁”的问题了。
按这个思路,为了使拆红包的事务操作串行地进入DB,只需要将请求在Server层以FIFO(先进先出)的方式排队,就可以达到这个效果。从而问题就集中到Server的FIFO队列设计上。
微信红包系统设计了分布式的、轻巧的、灵活的FIFO队列方案。其具体实现如下:
首先,将同一个红包ID的所有请求stick到同一台Server。
上面SET化方案已经介绍,同个红包ID的所有请求,按红包ID stick到同个SET中。不过在同个SET中,会存在多台Server服务器同时连接同一台DB(基于容灾、性能考虑,需要多台Server互备、均衡压力)。
为了使同一个红包ID的所有请求,stick到同一台Server服务器上,在SET化的设计之外,微信红包系统添加了一层基于红包ID hash值的分流,如下图所示。
其次,设计单机请求排队方案。
将stick到同一台Server上的所有请求在被接收进程接收后,按红包ID进行排队。然后串行地进入worker进程(执行业务逻辑)进行处理,从而达到排队的效果,如下图所示。
最后,增加memcached控制并发。
为了防止Server中的请求队列过载导致队列被降级,从而所有请求拥进DB,系统增加了与Server服务器同机部署的memcached,用于控制拆同一个红包的请求并发数。
具体来说,利用memcached的CAS原子累增操作,控制同时进入DB执行拆红包事务的请求数,超过预先设定数值则直接拒绝服务。用于DB负载升高时的降级体验。
通过以上三个措施,系统有效地控制了DB的“并发抢锁”情况。
3.双维度库表设计,保障系统性能稳定
红包系统的分库表规则,初期是根据红包ID的hash值分为多库多表。随着红包数据量逐渐增大,单表数据量也逐渐增加。而DB的性能与单表数据量有一定相关性。当单表数据量达到一定程度时,DB性能会有大幅度下降,影响系统性能稳定性。采用冷热分离,将历史冷数据与当前热数据分开存储,可以解决这个问题。
处理微信红包数据的冷热分离时,系统在以红包ID维度分库表的基础上,增加了以循环天分表的维度,形成了双维度分库表的特色。
具体来说,就是分库表规则像db_xx.t_y_dd设计,其中,xx/y是红包ID的hash值后三位,dd的取值范围在01~31,代表一个月天数最多31天。
通过这种双维度分库表方式,解决了DB单表数据量膨胀导致性能下降的问题,保障了系统性能的稳定性。同时,在热冷分离的问题上,又使得数据搬迁变得简单而优雅。
综上所述,微信红包系统在解决高并发问题上的设计,主要采用了SET化分治、请求排队、双维度分库表等方案,使得单组DB的并发性能提升了8倍左右,取得了很好的效果。
微信红包系统是一个高并发的资金交易系统,最大的技术挑战是保障并发性能与资金安全。这种全新的技术挑战,传统的“秒杀”系统设计方案已不能完全解决。在分析了业界“秒杀”系统解决方案的基础上,微信红包采用了SET化、请求排队串行化、双维度分库表等设计,形成了独特的高并发、资金安全系统解决方案,并在平时节假日、2015和2016春节实践中充分证明了可行性,取得了显著的效果。在刚刚过去的2017鸡年除夕夜,微信红包收发峰值达到76万每秒,收发微信红包142亿个,微信红包系统的表现稳定,实现了除夕夜系统零故障。
方乐明,现任微信支付应用产品系统负责人,主要从事微信红包、微信转账、微信群收款等支付应用产品的系统设计、可用性提升、高性能解决方案设计等,曾负责2015、2016和2017年春节微信红包系统的性能优化与稳定性提升,取得良好的效果。
原文链接:http://www.infoq.com/cn/articles/2017hongbao-weixin
在介绍yield前有必要先说明下Python中的迭代器(iterator)和生成器(constructor)。
一、迭代器(iterator)
在Python中,for循环可以用于Python中的任何类型,包括列表、元祖等等,实际上,for循环可用于任何“可迭代对象”,这其实就是迭代器
迭代器是一个实现了迭代器协议的对象,Python中的迭代器协议就是有next方法的对象会前进到下一结果,而在一系列结果的末尾是,则会引发StopIteration。任何这类的对象在Python中都可以用for循环或其他遍历工具迭代,迭代工具内部会在每次迭代时调用next方法,并且捕捉StopIteration异常来确定何时离开。
使用迭代器一个显而易见的好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。
比如要逐行读取一个文件的内容,利用readlines()方法,我们可以这么写:
1 2 |
for line in open("test.txt").readlines(): print line |
这样虽然可以工作,但不是最好的方法。因为他实际上是把文件一次加载到内存中,然后逐行打印。当文件很大时,这个方法的内存开销就很大了。
利用file的迭代器,我们可以这样写:
1 2 |
for line in open("test.txt"): #use file iterators print line |
这是最简单也是运行速度最快的写法,他并没显式的读取文件,而是利用迭代器每次读取下一行。
二、生成器(constructor)
生成器函数在Python中与迭代器协议的概念联系在一起。简而言之,包含yield语句的函数会被特地编译成生成器。当函数被调用时,他们返回一个生成器对象,这个对象支持迭代器接口。函数也许会有个return语句,但它的作用是用来yield产生值的。
不像一般的函数会生成值后退出,生成器函数在生成值后会自动挂起并暂停他们的执行和状态,他的本地变量将保存状态信息,这些信息在函数恢复时将再度有效
1 2 3 4 5 6 7 8 |
>>> def g(n): ... for i in range(n): ... yield i **2 ... >>> for i in g(5): ... print i,":", ... 0 : 1 : 4 : 9 : 16 : |
要了解他的运行原理,我们来用next方法看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
>>> t = g(5) >>> t.next() 0 >>> t.next() 1 >>> t.next() 4 >>> t.next() 9 >>> t.next() 16 >>> t.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
在运行完5次next之后,生成器抛出了一个StopIteration异常,迭代终止。
再来看一个yield的例子,用生成器生成一个Fibonacci数列:
1 2 3 4 5 6 7 8 9 10 |
def fab(max): a,b = 0,1 while a < max: yield a a, b = b, a+b >>> for i in fab(20): ... print i,",", ... 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , |
看到这里应该就能理解生成器那个很抽象的概念了吧~~