header:
header里面的信息, 是给浏览器看的, 包括以何种编码方式解析body等
body:
body里面的信息, 是给用户看的
HTTP报文:
它是HTTP应用程序之间发送的数据块。
这些数据块以一些文本形式的元信息开头,这些信息描述了报文的内容及含义,后面跟着可选的数据部分。
这些报文都是在客户端、服务器和代理之间流动。
HTTP报文的流动方向:
一次HTTP请求,HTTP报文会从“客户端”流到“代理”再流到“服务器”,
在服务器工作完成之后,报文又会从“服务器”流到“代理”再流到“客户端”
报文的语法:
所有的HTTP报文都可以分为两类,请求报文和响应报文。
请求和响应报文的基本报文结构大致是相同的,只有起始行的语法有所不同。
1.1请求报文:
它会向Web服务器请求一个动作
请求报文的格式:
起始行: <method> <request-URL> <version>
头部: <headers>
主体: <entity-body>
1.2响应报文:
它会将请求的结果返回给客户端。
响应报文的格式:
起始行: <version> <status> <reason-phrase>
头部: <headers>
主体: <entity-body>
下面是对各部分的简要描述
1、请求方法(method):
客户端希望服务器对资源执行的动作
比如,GET, POST, HEAD, DELETE, OPTIONS, PUT, TRACE
2、请求URL(request-URL):
要直接与服务器进行对话,只要请求URL是资源的绝对路径就可以了,服务器可以假定自己是URL的主机/端口
3、版本(version):
报文所使用的HTTP版本。
格式:HTTP/<主要版本号>.<次要版本号>
4、状态码(status-code):
状态码是三位数字,描述了请求过程中所发生的情况。
每个状态码的第一位数字都用于描述状态的一般类别(比如,“成功”、“出错”等等)
5、原因短语(reason-phrase):
数字状态码的可读版本,包含行终止序列之前的所有文本。
原因短语只对人类有意义,
因此,尽管响应行HTTP/1.0 200 NOT OK和HTTP/1.0 200 OK中原因短语的含义不同,
但同样都会被当作成功指示处理
6、头部(header):
可以有零个或多个头部,
每个首部都包含一个名字,后面跟着一个冒号(:),
然后是一个可选的空格,接着是一个值,
最后是一个CRLF首部是由一个空行(CRLF, Carriage-Return Line-Feed, 回车换行)结束的,
表示了头部列表的结束和实体主体部分的开始
7、实体的主体部分(entity-body):
实体的主体部分包含一个由任意数据组成的数据块,
并不是所有的报文都包含实体的主体部分,有时,报文只是以一个CRLF结束。
2.报文详解
举例如下
HTTP报文的组成部分:
对报文进行描述的
1.起始行
2.包含属性的头部块
3.可选的,包含数据的主体部分
2.1起始行
所有的HTTP报文都以一个起始行作为开始。
请求报文的起始行说明了要做些什么。
响应报文的起始行说明发生了什么。
请求报文的起始行:
该行包含了一个方法和一个请求的URL,还包含HTTP 的版本。
响应报文的起始行:
该行包含了响应报文使用的HTTP版本、数字状态码、原因短语。
2.2头部
HTTP首部字段向请求和响应报文中添加了一些附加信息。
本质上来说,它们只是一些key/value的列表。
头部和协议配合工作,共同决定了客户端和服务器能做什么事情。
头部的分类
2.2.1通用头部
既可以出现在请求报文中,也可以出现在响应报文中,它提供了与报文相关的最基本的信息
Connection:允许客户端和服务器指定与请求/响应连接有关的选项
Date:提供日期和时间标志,说明报文是什么时间创建的
MIME-Version:给出了发送端使用的MIME版本
Trailer:如果报文采用了分块传输编码方式,就可以用这个首部列出位于报文拖挂部分的首部集合
Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
Update:给出了发送端可能想要“升级”使用的新版本或协议
Via:显示了报文经过的中间节点(代理、网关)
Cache-Control:用于随报文传送缓存指示
2.2.2请求头部
请求头部是只在请求报文中有意义的头部。
用于说明是谁或什么在发送请求、请求源自何处,或者客户端的喜好及能力
Client-IP:提供了运行客户端的机器的IP地址
From:提供了客户端用户的E-mail地址
Host:给出了接收请求的服务器的主机名和端口号
Referer:提供了包含当前请求URI的文档的URL
#表明产生请求的网页来自于哪个URL,用户是从该 Referer页面访问到当前请求的页面。这个属性可以用来跟踪Web请求来自哪个页面,是从什么网站来的等。
#有时候遇到下载某网站图片,需要对应的referer,否则无法下载图片,那是因为人家做了防盗链,原理就是根据referer去判断是否是本网站的地址,如果不是,则拒绝,如果是,就可以下载;
UA-Color:提供了与客户端显示器的显示颜色有关的信息
UA-CPU:给出了客户端CPU的类型或制造商
UA-OS:给出了运行在客户端机器上的操作系统名称及版本
UA-Pixels:提供了客户端显示器的像素信息
User-Agent:将发起请求的应用程序名称告知服务器
Accept:告诉服务器能够发送哪些媒体类型
Accept-Charset:告诉服务器能够发送哪些字符集
Accept-Encoding:告诉服务器能够发送哪些编码方式
Accept-Language:告诉服务器能够发送哪些语言
TE:告诉服务器可以使用那些扩展传输编码
Expect:允许客户端列出某请求所要求的服务器行为
Range:如果服务器支持范围请求,就请求资源的指定范围
If-Match:如果实体标记与文档当前的实体标记相匹配,就获取这份文档
If-Modified-Sinec:除非在某个指定的日期之后资源被修改过,否则就限制这个请求
If-None-Match:如果提供的实体标记与当前文档的实体标记不相符,就获取文档
If-Range:允许对文档的某个范围进行条件请求
If-Unmodified-Since:除非在某个指定日期之后资源没有被修改过,否则就限制这个请求
Authorization:包含了客户端提供给服务器,以便对其自身进行认证的数据
Cookie:客户端用它向服务器传送数据
Cookie2:用来说明请求端支持的cookie版本
Max-Forward:在通往源端服务器的路径上,将请求转发给其他代理或网关的最大次数
Proxy-Authorization:这个首部在与代理进行认证时使用的
Proxy-Connection:这个首部是在与代理建立连接时使用的
Upgrade-Insecure-Requests (升级为HTTPS请求):
#升级不安全的请求,意思是会在加载 http 资源时自动替换成 https 请求,让浏览器不再显示https页面中的http请求警报。
#HTTPS 是以安全为目标的 HTTP 通道,所以在 HTTPS 承载的页面上不允许出现 HTTP 请求,一旦出现就是提示或报错。
x-requested-with : XMLHttpRequest (是Ajax 异步请求)
2.2.3响应头部
响应头部为客户端提供了一些额外信息,
比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令
Age:(从最初创建开始)响应持续时间
Public:服务器为其资源支持的请求方法列表
Retry-After:如果资源不可用的话,在此日期或时间重试
Server:服务器应用程序软件的名称和版本
Title:对HTML文档来说,就是HTML文档的源端给出的标题
Warning:比原因短语更详细一些的警告报文
Accept-Ranges:对此资源来说,服务器可接受的范围类型
Vary:服务器会根据这些首部的内容挑选出最适合的资源版本发送给客户端
Proxy-Authenticate:来自代理的对客户端的质询列表
Set-Cookie:在客户端设置数据,以便服务器对客户端进行标识
Set-Cookie2:与Set-Cookie类似
WWW-Authenticate:来自服务器的对客户端的质询列表
2.2.4实体首部
描述主体的长度和内容,或者资源自身
Allow:列出了可以对此实体执行的请求方法
Location:告知客户端实体实际上位于何处,用于将接收端定向到资源的位置(URL)上去
Content-Base:解析主体中的相对URL时使用的基础URL
Content-Encoding:对主体执行的任意编码方式
Content-Language:理解主体时最适宜使用的自然语言
Content-Length:主体的长度
Content-Location:资源实际所处的位置
Content-MD5:主体的MD5校验和
Content-Range:在整个资源中此实体表示的字节范围
Content-Type:这个主体的对象类型
ETag:与此实体相关的实体标记
Expires:实体不再有效,要从原始的源端再次获取实体的日期和时间
Last-Modified:这个实体最后一次被修改的日期和时间
2.2.5扩展首部
规范中没有定义的新首部,开发者可以自定义一个首部的key/value
2.3实体的主体部分
该部分其实就是HTTP要传输的内容,是可选的。
HTTP报文可以承载很多类型的数字数据,
比如,图片、视频、HTML文档电子邮件、软件应用程序等等。
2.4HTTP方法
并不是每个服务器都实现了所有的方法。
即使服务器实现了所有这些方法,这些方法的使用很可能也是受限的。
例如,支持DELETE方法或PUT方法的服务器可能并不希望任何人都能够删除或存储资源,
这些限制通常都是在服务器的配置中进行设置的。
常用的HTTP方法
GET方法(不包含主体):
通常用于请求服务器发送某个资源。
HEAD方法(不包含主体):
与GET方法类似,但服务器在响应中只返回首部,
使用HEAD方法可以在不获取资源的情况下了解资源的情况(比如,判断其类型);
通过查看响应中的状态码,看看某个对象是否存在;
通过查看首部,测试资源是否被修改了;
POST方法(包含主体):
该方法是用来向服务器发送数据的,常用于HTML表单
PUT方法(包含主体):
该方法的语义就是让服务器用请求的主体部分来创建一个由所请求的URL命名的新文档,
如果那个URL已经存在的话,就用这个主体来替代它。
TRACE方法(不包含主体):
主要用于验证请求是否如愿穿过了请求/响应链
OPTIONS方法(不包含主体):
决定可以在服务器上执行那些方法
DELETE方法(不包含主体):
该方法就是请服务器删除请求URL所指定的资源,
但是客户端应用程序无法保证删除操作一定会被执行,
因为HTTP规范允许服务器在不通知客户端的情况下撤销请求
扩展方法:
指的是没有在HTTP/1.1规范中定义的方法,
这些方法为开发者提供了一种扩展这些HTTP服务能力的手段。
2.5状态码
HTTP状态码被分成了五大类。状态码为客户端提供了一种理解事务处理结果的便捷方式。
1、100~199(信息性状态码):
HTTP/1.1向协议中引入了信息性状态码
2、200~299(成功状态码):
客户端发起请求时,这些请求通常都是成功的。
服务器有一组用来表示成功的状态码,分别对应于不同类型的请求
3、300~399(重定向状态码):
重定向状态码要么告知客户端使用替代位置来访问他们所感兴趣的资源,
要么就提供一个替代的响应而不是资源的内容
4、400~499(客户端错误状态码):
有时客户端会发送一些服务器无法处理的东西。
浏览网页时,我们都看到过臭名昭著的404 Not Found错误码,
这只是服务器在告诉我们,它对我们请求的资源一无所知
5、500~599(服务器错误状态码):
有时客户端发送了一条有效请求,服务器自身却出错了,这些会返回5xx状态码
3.网络通信
3.1常见的网络通信协议
http协议
超文本传输协议方案,除了没有用户名和密码之外,
与通用的URL格式相符,如果省略了端口,就默认为80
基本格式:http://<host>:<port>/<path>?<query>#<frag>
示例:http://www.baidu.com:80/index.html
https协议
该方案与http方案是一对的,唯一的区别在于方案https使用了网景的SSL,
SSL为HTTP连接提供了端到端的加密机制,其语法与HTTP的语法相同,默认端口为443
基本格式:https://<host>:<port>/<path>?<query>#<frag>
示例:https://www.baidu.com:80/index.html
mailto协议
mailto URL指向的是E-mail地址,由于E-mail的行为与其他方案都有所不同,
它并不指向任何可以直接访问的对象
基本格式:mailto:<RFC-822-addr-spec>
示例:mailto:joe@joes-hardware.com
ftp协议
文件传输协议URL可以用来从FTP服务器上下载或向其上载文件,
并获取FTP服务器上的目录结构内容的列表
基本格式:ftp:<user>:<password>@<host>:<port>/<path>;<params>
示例:ftp://anonymous:joe%40joes@prep.an.edu:21/pub/gs
rtsp和rtspu协议
RTSP URL是可以通过实时流传输协议解析的音/视频媒体资源的标示符。
方案respu中的u表示它是使用UDP协议来获取资源的
基本格式:rtsp:<user>:<password>@<host>:<port>/<path>
示例:rtspu://www.baidu.com:554/inte/cto_video
file协议
file方案表示一台指定主机上可直接访问的文件。
各字段都遵循通用格式,如果省略了主机名,就默认为正在使用URL的本地主机
基本格式:file:<user>:<password>@<host>:<port>/<path>
示例:file://OFFICE-FS/poli/cds.doc
telnet协议
telnet方案用于访问交互式业务,它表示的并不是对象自身,
而是可通过telnet协议访问的交互式应用程式(资源)。
基本格式:telnet://<user>:<password>@<host>:<port>/
示例:telnet:csh:webcsh@joes.com:50/
3.2 ip分类 & port详解
每一个IP地址包括两部分:网络地址和主机地址
ip地址.jpg
3.2.1 A类IP地址
一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”,
地址范围1.0.0.1-126.255.255.254
二进制表示为:00000001 00000000 00000000 00000001 - 01111110 11111111 11111111 11111110
可用的A类网络有126个,每个网络能容纳1677214个主机
3.2.2 B类IP地址
一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,
地址范围128.1.0.1-191.255.255.254
二进制表示为:10000000 00000001 00000000 00000001 - 10111111 11111111 11111111 11111110
可用的B类网络有16384个,每个网络能容纳65534主机
3.2.3 C类IP地址
一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”
范围192.0.1.1-223.255.255.254
二进制表示为: 11000000 00000000 00000001 00000001 - 11011111 11111111 11111110 11111110
C类网络可达2097152个,每个网络能容纳254个主机
3.2.4 D类地址用于多点广播
D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。
它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中
多点广播地址用来一次寻址一组计算机 s 地址范围224.0.0.1-239.255.255.254
3.2.5 E类IP地址
以“1111”开始,为将来使用保留
E类地址保留,仅作实验和开发用
3.2.6 私有ip
在这么多网络IP中,国际规定有一部分IP地址是用于我们的局域网使用,也就
是属于私网IP,不在公网中使用的,它们的范围是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
3.2.7 注意
IP地址127.0.0.1~127.255.255.255用于回路测试,
如:127.0.0.1可以代表本机IP地址,用http://127.0.0.1就可以测试本机中配置的Web服务器。
3.2.8 port分类
第一类公认端口(Well Known Ports):
从0到1023,它们紧密绑定(binding)于一些服务。
通常这些端口的通讯明确表明了某种服务的协议,必须要有Root权限才能绑定。
例如:80端口实际上总是HTTP通讯。
第二类注册端口(Registered Ports):
从1024到49151。它们松散地绑定于一些服务。
也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。
例如:许多系统处理动态端口从1024左右开始。
第三类动态和/或私有端口(Dynamic, private or ephemeral ports):从49152到65535。
理论上,不应为服务分配这些端口。
实际上,机器通常从1024起分配动态端口。
但也有例外:SUN的RPC端口从32768开始。
4.cookie, session, token
http协议(包括http2.0)都是无状态协议.
无状态是指协议对于事务处理没有记忆功能。
缺少状态意味着,假如后面的处理需要前面的信息,
则前面的信息必须重传,这样可能导致每次连接传送的数据量增大。
另一方面,在服务器不需要前面信息时,应答就较快。
直观地说,就是每个请求都是独立的,与前面的请求和后面的请求都是没有直接联系的。
实际中的使用情况
在web应用中,我们使用http协议,但是我们需要的web是有状态的,
因此加入了cookie、session等机制用于跟踪用户的状态, 从而实现有状态的web。
4.1cookie
cookie,有时也用其复数形式cookies,
指某些网站为了辨别用户身份, 进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。
cookie是由服务器端生成,发送给浏览器,
浏览器把cookie以kv形式保存到某个目录下的文本文件内,
下一次请求同一网站时会把该cookie发送给服务器。
由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,
同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。
1. cookie是一门客户端缓存技术
2. cookie数据由服务器生成,发送给浏览器保存
3. cookie数据的格式:键值对
4. cookie数据过期机制:设置expire值
4.2session
1.session是一门服务端会话缓存技术。
2.session由服务器端的web容器创建,保存在服务器端。
3.session保存数据:键值对形式
4.session过期:默认30分钟
session 从字面上讲,就是会话。
这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?
对方肯定有某种特征(长相等)表明他就是张三。
session 也是类似的道理,服务器要知道当前发请求给自己的是谁。
为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,
然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,
服务器就知道这个请求来自于谁了。
至于客户端怎么保存这个“身份标识”,可以有很多种方式,
对于浏览器客户端,大家都默认采用 cookie 的方式。
服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。
这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:
如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。
也就是要解决session共享的问题:
可以另外设计一套专门的session的服务service, 专门用于存储登录认证信息, 如redis集群...
但是会有新的问题:
a.每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。
当越来越多的用户发请求时,内存的开销也会不断增加。
b.可扩展性:在服务端的内存中使用Seesion存储登录信息,伴随而来的是可扩展性问题。
4.3token(防CSRF攻击)
基于Token的验证原理
基于Token的身份验证是无状态的,我们不将用户信息存在服务器或Session中。
这种概念解决了在服务端存储信息时的许多问题
NoSession意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录。
基于Token的身份验证的过程如下
生成的token,客户端可将token写入sessionStorage,服务端可将token加密写入redis集群
1.用户通过用户名和密码发送请求。
2.程序验证。
3.程序返回一个签名的token 给客户端。
<!--
为防止有人伪造token, 可以对数据做一个签名,
比如用<HMAC-SHA256>算法,加上一个<密钥>, 对数据做一个签名,
把这个签名和数据一起作为token, 由于密钥别人不知道, 就无法伪造token了。
-->
<!--
服务端不保存token, 当客户端把这个token发过来时, 再用同样的<HMAC-SHA256>算法和同样的<密钥>,
对数据再计算一次签名, 和token中的签名做个比较,
如果相同, 我就知道小F已经登录过了,
如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
-->
<!--
token中的数据是明文保存的(虽然可以用Base64做下编码, 但那不是加密),
还是可以被别人看到的, 所以不能在其中保存像密码这样的敏感信息。
当然token也可以加密保存!!!
-->
4.客户端储存token,并且每次用于每次发送请求时, 在header中携带改token字段。
5.服务器端采用filter过滤器校验。
校验成功则返回请求数据,校验失败则返回错误码验证token并返回数据。
每一次请求都需要token。
token应该在HTTP的头部发送从而保证了Http请求无状态。
我们同样通过设置服务器属性Access-Control-Allow-Origin:* ,让服务器能接受到来自所有域的请求。
需要注意的是,在ACAO头部标明(designating)*时,不得带有向HTTP认证,客户端SSL证书和cookies的证书。
token-1.png
token-2.png
实现思路
app项目为例
一般app项目都会基于一个token做鉴权。
因为此时客户端不是浏览器,因此就没有cookie这一说了。
当用户登录app时,服务器会响应回来一个token信息
(一般都是返回的一串唯一的标识符,比如说uuid或其他)。
服务器端会将登录用户跟token(票据)保存一个映射关系,
一般保存在redis或者表里面,服务器端响应回来的token会缓存在手机的本地缓存里,
后面手机去访问app的其他页面,就会带着这个token去服务器做验证,
如果通过这个token能够从redis找到登录用户信息, 那么就认为你是已经登录了的用户。
Tokens的优势
1.无状态、可扩展
在客户端存储的Tokens是无状态的,并且能够被扩展。
基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。
如果我们将已验证的用户的信息保存在Session中,则每次请求都需要用户向已验证的服务器发送验证信息(称为Session亲和性)。
用户量大时,可能会造成一些拥堵。但是不要着急。
使用tokens之后这些问题都迎刃而解,因为tokens自己hold住了用户的验证信息。
2.安全性(可先验证referer,再验证token)
请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。
即使在客户端使用cookie存储token,cookie也仅仅是一个存储机制而不是用于认证。
不将信息存储在Session中,让我们少了对session操作。
token是有时效的,一段时间之后用户需要重新验证。
我们也不一定需要等到token自动失效,token有撤回的操作,
通过token revocataion可以使一个特定的token或是一组有相同认证的token无效。
4.可扩展性
tokens能够创建与其它程序共享权限的程序。
例如,能将一个随便的社交帐号和自己的大号(Fackbook或是Twitter)联系起来。
当通过服务登录Twitter(我们将这个过程Buffer)时,我们可以将这些Buffer附到Twitter的数据流上。
使用tokens时,可以提供可选的权限给第三方应用程序。
当用户想让另一个应用程序访问它们的数据,我们可以通过建立自己的API,得出特殊权限的tokens。
5.多平台跨域
我们提前先来谈论一下CORS(跨域资源共享),对应用程序和服务进行扩展的时候,需要介入各种各种的设备和应用程序。
<Access-Control-Allow-Origin: *>
4.4cookie与session有什么区别
1)session是服务器端保存用户信息,cookie是在客户端保存用户信息。
2)session中保存的是对象,cookie保存的是字符串。
3)session对象随会话结束而关闭,cookie可以长期保存在客户端
4)cookie通常用于保存不重要的用户信息,重要的信息使用session保存。
一些反对意见
https://www.jianshu.com/p/af8360b83a9f
5.接口幂等性
什么是幂等性
Methods can also have the property of “idempotence” in that
(aside from error or expiration issues)
the side-effects of N > 0 identical requests is the same as for a single request.
#在分布式集群环境中提供对外幂等性的接口:
只要调用接口成功,外部对接口的多次调用得到的结果是相同的。
即执行多次和一次的效果是一样的。
幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次,比如:
>>订单接口, 不能多次创建订单
>>支付接口, 重复支付同一笔订单只能扣一次钱
>>支付宝回调接口, 可能会多次回调, 必须处理重复回调
>>普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次
什么情况下需要保证幂等性
GET、HEAD、OPTIONS和TRACE方法被定义成安全的,它们只是为了获取数据,
多个相同请求时服务器端的表现是相同的,所以它们是天然幂等的。
PUT和DELETE方法被定义为幂等的。需要注意了。
以SQL为例,有下面三种场景,只有第三种场景需要开发人员使用其他策略保证幂等性:
>>SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。
>>UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。
>>UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。
解决方案
###推荐方案: 结合redis与mysql的unique key
要求是支付一个订单,必须插入一条支付流水,order_id建一个唯一键,unique key
所以你在支付一个订单之前,先插入一条支付流水,order_id就已经进去了
你就可以写一个标识到redis里面去,set order_id payed,下一次重复请求过来了,
先查redis的order_id对应的value,如果是payed就说明已经支付过了,你就别重复支付了
你再重复支付这个订单的时候,你写尝试插入一条支付流水,
数据库给你报错了,说unique key冲突了,整个事务回滚就可以了
来保存一个是否处理过的标识也可以,服务的不同实例可以一起操作redis。
### 1.乐观锁
如果只是更新已有的数据,没有必要对业务进行加锁,
设计表结构时使用乐观锁,一般通过version来做乐观锁,
这样既能保证执行效率,又能保证幂等。例如:
UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#
不过,乐观锁存在失效的情况,就是常说的ABA问题,
如果version版本一直是自增的就不会出现ABA的情况。
### 2.防重表
使用订单号orderNo做为去重表的唯一索引,每次请求都根据订单号向去重表中插入一条数据。
第一次请求查询订单支付状态,当然订单没有支付,进行支付操作,无论成功与否,
执行完后更新订单状态为成功或失败,删除去重表中的数据。
后续的订单因为表中唯一索引而插入失败,则返回操作失败,直到第一次的请求完成(成功或失败)。
可以看出防重表作用是加锁的功能。
### 3.分布式锁
这里使用的防重表可以使用分布式锁代替,比如Redis。
订单发起支付请求,支付系统会去Redis缓存中查询是否存在该订单号的Key,
如果不存在,则向Redis增加Key为订单号。
查询订单支付已经支付,如果没有则进行支付,支付完成后删除该订单号的Key。
通过Redis做到了分布式锁,只有这次订单订单支付请求完成,下次请求才能进来。
相比去重表,将放并发做到了缓存中,较为高效。
思路相同,同一时间只能完成一次支付请求。
### 4.token令牌
这种方式分成两个阶段:申请token阶段和支付阶段。
第一阶段,在进入到提交订单页面之前,需要订单系统根据用户信息向支付系统发起一次申请token的请求,
支付系统将token保存到Redis缓存中,为第二阶段支付使用。
第二阶段,订单系统拿着申请到的token发起支付请求,支付系统会检查Redis中是否存在该token,
如果存在,表示第一次发起支付请求,删除缓存中token后开始支付逻辑处理;
如果缓存中不存在,表示非法请求。
实际上这里的token是一个信物,支付系统根据token确认,你是你妈的孩子。
不足是需要系统间交互两次,流程较上述方法复杂。
### 5.支付缓冲区
把订单的支付请求都快速地接下来,一个快速接单的缓冲管道。
后续使用异步任务处理管道中的数据,过滤掉重复的待支付订单。
优点是同步转异步,高吞吐。
不足是不能及时地返回支付结果,需要后续监听支付结果的异步返回。
幂等性接口的不足
增加了额外控制幂等的业务逻辑,复杂化了业务功能;
把并行执行的功能改为串行执行,降低了执行效率。
因此除了业务上的特殊要求外,尽量不提供幂等的接口。
参考资源
https://www.jianshu.com/p/37cad53375db
https://www.jianshu.com/p/3fc3646fad80
网友评论