iven_wong

iven_wong 查看完整档案

上海编辑华东师范大学  |  软件工程 编辑  |  填写所在公司/组织 segmentfault.com/blog/xfslove 编辑
编辑

ya, wass up

个人动态

iven_wong 关注了用户 · 3月22日

阿里云视频云 @aliyunshipinyun

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

关注 87

iven_wong 关注了用户 · 2020-09-15

mercyblitz @mercyblitz

小马哥,Java 劝退师,《Spring Boot 编程思想》作者,Apache 和 Spring Cloud 等知名开源架构成员,点击查看详情。(交流QQ群:719291662

最新发布:
Spring Boot 2.0深度实践之核心技术篇
Java 微服务实践系列课堂
「一入 Java 深似海 」系列课程

关注 2870

iven_wong 关注了用户 · 2020-08-14

敖丙 @aobing

关注 5893

iven_wong 赞了文章 · 2020-08-14

秒杀系统设计

背景

我之前写过一个秒杀系统的文章不过有些许瑕疵,所以我准备在之前的基础上进行二次创作,不过让我决心二创秒杀系统的原因是我最近面试了很多读者,动不动就是秒杀系统把我整蒙蔽了,我懵的主要是秒杀系统的细节大家都不知道,甚至不知道电商公司一个秒杀系统的组成部分。

我之前在某电商公司就是做电商活动的,所以这样的场景和很多解决方案我是比较清楚的,那我就从我自身去带着大家看看一个秒杀的设计细节以及中间各种解决方案的利弊,以下就是我设计的秒杀系统,几乎涵盖了市面上所有秒杀的实现细节:

正文

首先设计一个系统之前,我们需要先确认我们的业务场景是怎么样子的,我就带着大家一起假设一个场景好吧。

我们现场要卖1000件下面这个婴儿纸尿裤,然后我们根据以往这样秒杀活动的数据经验来看,目测来抢这100件纸尿裤的人足足有10万人。(南极人打钱!)

你一听,完了呀,这我们的服务器哪里顶得住啊!说真的直接打DB肯定挂,但是别急嘛,有暖男敖丙在,任何系统我们开始设计之前我们都应该去思考会出现哪些问题?这里我罗列了几个非常经典的问题:

问题

高并发:

是的高并发这个是我们想都不用想的一个点,一瞬间这么多人进来这不是高并发什么时候是呢?

是吧,秒杀的特点就是这样时间极短瞬间用户量大

正常的店铺营销都是用极低的价格配合上短信、APP的精准推送,吸引特别多的用户来参与这场秒杀,爽了商家苦了开发呀

秒杀大家都知道如果真的营销到位,价格诱人,几十万的流量我觉得完全不是问题,那单机的Redis我感觉3-4W的QPS还是能顶得住的,但是再高了就没办法了,那这个数据随便搞个热销商品的秒杀可能都不止了。

大量的请求进来,我们需要考虑的点就很多了,缓存雪崩缓存击穿缓存穿透这些我之前提到的点都是有可能发生的,出现问题打挂DB那就很难受了,活动失败用户体验差,活动人气没了,最后背锅的还是开发

超卖:

但凡是个秒杀,都怕超卖,我这里举例的只是尿不湿,要是换成100个MacBook Pro,商家的预算经费卖100个可以赚点还可以造势,结果你写错程序多卖出去200个,你不发货用户投诉你,平台封你店,你发货就血亏,你怎么办? (没事看了敖丙的文章直接不怕)

那最后只能杀个开发祭天解气了,秒杀的价格本来就低了,基本上都是不怎么赚钱的,超卖了就恐怖了呀,所以超卖也是很关键的一个点。

恶意请求:

你这么低的价格,假如我抢到了,我转手卖掉我不是血赚?就算我不卖我也不亏啊,那用户知道,你知道,别的别有用心的人(黑客、黄牛...)肯定也知道的。

那简单啊,我知道你什么时候抢,我搞个几十台机器搞点脚本,我也模拟出来十几万个人左右的请求,那我是不是意味着我基本上有80%的成功率了。

真实情况可能远远不止,因为机器请求的速度比人的手速往往快太多了,在贵州的敖丙我每年回家抢高铁票都是秒光的,我也不知道有没有黄牛的功劳,我要Diss你,黄牛。杰伦演唱会门票抢不到,我也Diss你。

Tip:科普下,小道消息了解到的,黄牛的抢票系统,比国内很多小公司的系统还吊很多,架构设计都是顶级的,我用顶配的服务加上顶配的架构设计,你还想看演唱会?还想回家?

不过不用黄牛我回家都难,我们云贵川跟我一样要回家过年的仔太多了555!

链接暴露:

前面几个问题大家可能都很好理解,一看到这个有的小伙伴可能会比较疑惑,啥是链接暴露呀?

相信是个开发同学都对这个画面一点都不陌生吧,懂点行的仔都可以打开谷歌的开发者模式,然后看看你的网页代码,有的就有URL,但是我写VUE的时候是事件触发然后去调用文件里面的接口看源码看不到,但是我可以点击一下查看你的请求地址啊,不过你好像可以对按钮在秒杀前置灰。

不管怎么样子都有危险,撇开外面的所有的东西你都挡住了,你卖这个东西实在便宜得过分,有诱惑力,你能保证开发不动心?开发知道地址,在秒杀的时候自己提前请求。。。(开发:怎么TM又是我)

数据库:

每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单单是做秒杀的还涉及其他的业务,你没做降级、限流、熔断啥的,别的一起挂,小公司的话可能全站崩溃404

反正不管你秒杀怎么挂,你别把别的搞挂了对吧,搞挂了就不是杀一个程序员能搞定的。

程序员:我TM好难啊!

问题都列出来了,那怎么设计,怎么解决这些问题就是接下去要考虑的了,我们对症下药。

我会从我设计的秒杀系统从上到下去给大家介绍我们正常电商秒杀系统在每一层做了些什么,每一层存在的问题,难点等。

我们从前端开始:

前端

秒杀系统普遍都是商城网页、H5、APP、小程序这几项。

在前端这一层其实我们可以做的事情有很多,如果用node去做,甚至能直接处理掉整个秒杀,但是node其实应该属于后端,所以我不讨论node Service了。

资源静态化:

秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。

秒杀链接加盐:

我们上面说了链接要是提前暴露出去可能有人直接访问url就提前秒杀了,那又有小伙伴要说了我做个时间的校验就好了呀,那我告诉你,知道链接的地址比起页面人工点击的还是有很大优势

我知道url了,那我通过程序不断获取最新的北京时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率大太多了,而且我可以一毫秒发送N次请求,搞不好你卖100个产品我全拿了。

那这种情况怎么避免?

简单,把URL动态化,就连写代码的人都不知道,你就通过MD5之类的摘要算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。

这个只能防止一部分没耐心继续破解下去的黑客,有耐心的人研究出来还是能破解,在电商场景存在很多这样的羊毛党,那怎么做呢?

后面我会说。

限流:

限流这里我觉得应该分为前端限流后端限流

物理控制:

大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。

这是因为怕大家在时间快到的最后几秒秒疯狂请求服务器,然后还没到秒杀的时候基本上服务器就挂了。

这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点再给按钮可用状态。

按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。

你敢说你们秒杀的时候不是这样的?

前端限流:这个很简单,一般秒杀不会让你一直点的,一般都是点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。

后端限流:秒杀的时候肯定是涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖光了,return了一个false,前端直接秒杀结束,然后你后端也关闭后续无效请求的介入了。

Tip:真正的限流还会有限流组件的加入例如:阿里的Sentinel、Hystrix等。我这里就不展开了,就说一下物理的限流。

我们卖1000件商品,请求有10W,我们不需要把十万都放进来,你可以放1W请求进来,然后再进行操作,因为秒杀对于用户本身就是黑盒的,所以你怎么做的他们是没感知的,至于为啥放1W进来,而不是刚好1000,是因为会丢掉一些薅羊毛的用户,至于怎么判断,后面的风控阶段我会说。

Nginx:

Nginx大家想必都不陌生了吧,这玩意是高性能的web服务器,并发也随便顶几万不是梦,但是我们的Tomcat只能顶几百的并发呀,那简单呀负载均衡嘛,一台服务几百,那就多搞点,在秒杀的时候多租点流量机

Tip:据我所知国内某大厂就是在去年春节活动期间租光了亚洲所有的服务器,小公司也很喜欢在双十一期间买流量机来顶住压力。

这样一对比是不是觉得你的集群能顶很多了。

恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在网关那一层就得拦截掉了,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用网络带宽或者把服务器打崩、缓存击穿等等。

风控

我可以明确的告诉大家,前面的所有措施还是拦不住很多羊毛党,因为他们是专业的团队,他们可以注册很多账号来薅你的羊毛,而且不用机器请求,就用群控,操作几乎跟真实用户一模一样。

那怎么办,是不是无解了?

这个时候就需要风控同学的介入了,在请求到达后端之前,风控可以根据账号行为分析出这个账号机器人的概率大不大,我现在负责公司的某些特殊系统,每个用户的行为都是会送到我们大数据团队进行分析处理,给你打上对应标签的。

那黑客其实也有办法:养号

他们去黑市买真实用户有过很多记录的账号,买到了还不闲着,帮他们去购物啥的,让系统无法识别他们是黑号还是真实用户的号。

怎么办?

通杀!是的没有办法,只能通杀了,通杀的意思就是,我们通过风管分析出来这个用户是真实用户的概率没有其他用户概率大,那就认为他是机器了,丢弃他的请求。

之前的限流我们放进来10000个请求,但是我们真正的库存只有1000个,那我们就算出最有可能是真实用户的1000人进行秒杀,丢弃其他请求,因为秒杀本来就是黑盒操作的,用户层面是无感知的,这样设计能让真实的用户买到东西,还可以减少自己被薅羊毛的概率。

风控可以说是流量进入的最后一道门槛了,所以很多公司的风控是很强的,蚂蚁金服的风控大家如果了解过就知道了,你的资金在支付宝被盗了,他们是能做到全款补偿是有原因的。

后端

服务单一职责:

设计个能抗住高并发的系统,我觉得还是得单一职责

什么意思呢,大家都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式

也就是我们下单是有个订单服务,用户登录管理等有个用户服务等等,那为啥我们不给秒杀也开个服务,我们把秒杀的代码业务逻辑放一起。

单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(高可用)

Redis集群:

之前不是说单机的Redis顶不住嘛,那简单多找几个兄弟啊,秒杀本来就是读多写少,那你们是不是瞬间想起来我之前跟你们提到过的,Redis集群主从同步读写分离,我们还搞点哨兵,开启持久化直接无敌高可用!

库存预热:

秒杀的本质,就是对库存的抢夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存,撇开性能因数,你不觉得这样好繁琐,对业务开发人员都不友好,而且数据库顶不住啊。

开发:你tm总算为我着想一次了。

那怎么办?

我们都知道数据库顶不住但是他的兄弟非关系型的数据库Redis能顶啊!

那不简单了,我们要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介绍了,再异步的去修改库存就好了。

但是用了Redis就有一个问题了,我们上面说了我们采用主从,就是我们会去读取库存然后再判断然后有库存才去减库存,正常情况没问题,但是高并发的情况问题就很大了。

**多品几遍!!!**就比如现在库存只剩下1个了,我们高并发嘛,4个服务器一起查询了发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,那结果就变成了-3,是的只有一个是真的抢到了,别的都是超卖的。咋办?

事务:

Redis本身是支持事务的,而且他有很多原子命令的,大家也可以用LUA,还可以用他的管道,乐观锁他也知支持。

限流&降级&熔断&隔离:

这个为啥要做呢,不怕一万就怕万一,万一你真的顶不住了,限流,顶不住就挡一部分出去但是不能说不行,降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就独立的,但是你会调用其他的系统嘛,你快不行了你别拖累兄弟们啊。

消息队列(削峰填谷):

一说到这个名词,很多小伙伴就知道了,对的MQ,你买东西少了你直接100个请求改库我觉得没问题,但是万一秒杀一万个,10万个呢?服务器挂了,程序员又要背锅的

秒杀就是这种瞬间流量很高,但是平时又没有流量的场景,那消息队列完全契合这样的场景了呀,削峰填谷。

Tip:可能小伙伴说我们业务达不到这个量级,没必要。但是我想说我们写代码,就不应该写出有逻辑漏洞的代码,至少以后公司体量上去了,别人一看居然不用改代码,一看代码作者是敖丙?有点东西!

你可以把它放消息队列,然后一点点消费去改库存就好了嘛,不过单个商品其实一次修改就够了,我这里说的是某个点多个商品一起秒杀的场景,像极了双十一零点。

数据库

数据库用MySQL只要连接池设置合理一般问题是不大的,不过一般大公司不缺钱而且秒杀这样的活动十分频繁,我之前所在的公司就是这样秒杀特卖这样的场景一直都是不间断的。

单独给秒杀建立一个数据库,为秒杀服务,表的设计也是竟可能的简单点,现在的互联网架构部署都是分库的。

至于表就看大家怎么设计了,该设置索引的地方还是要设置索引的,建完后记得用explain看看SQL的执行计划。(不了解的小伙伴也没事,MySQL章节去康康)

分布式事务

这为啥我不放在后端而放到最后来讲呢?

因为上面的任何一步都是可能出错的,而且我们是在不同的服务里面出错的,那就涉及分布式事务了,但是分布式事务大家想的是一定要成功什么的那就不对了,还是那句话,几个请求丢了就丢了,要保证时效和服务的可用可靠。

所以TCC最终一致性其实不是很适合,TCC开发成本很大,所有接口都要写三次,因为涉及TCC的三个阶段。

最终一致性基本上都是靠轮训的操作去保证一个操作一定成功,那时效性就大打折扣了。

大家觉得不那么可靠的**两段式(2PC)三段式(3PC)**就派上用场了,他们不一定能保证数据最终一致,但是效率上还算ok。

总结

到这里我想我已经基本上把该考虑的点还有对应的解决方案也都说了一下,不知道还有没有没考虑到的,但是就算没考虑到我想我这个设计,应该也能撑住一个完整的秒杀流程。

最后大家再看看这个秒杀系统或许会有新的感悟,是不是一个系统真的没有大家想的那么简单,而且我还是有漏掉的细节,这是一定的。

秒杀这章我脑细胞死了很多,考虑了很多个点,最后还是出来了,忍不住给自己点赞

总结

我们玩归玩,闹归闹,别拿面试开玩笑。

秒杀不一定是每个同学都会问到的,至少肯定没Redis基础那样常问,但是一旦问到,大家一定要回答到点上。

至少你得说出可能出现的情况需要注意的情况,以及对于的解决思路和方案,因为这才是一个coder的基本素养,这些你不考虑你也很难去进步。

最后就是需要对整个链路比较熟悉,注意是一个完整的链路,前端怎么设计的呀,网关的作用呀,怎么解决Redis的并发竞争啊,数据的同步方式呀,MQ的作用啊等等,相信你会有不错的收获。

不知道这是一次成功还是失败的二创,我里面所有提到的技术细节我都写了对应的文章,大家可以关注我去历史文章看看,天色已晚,我溜了。

查看原文

赞 99 收藏 58 评论 9

iven_wong 关注了用户 · 2020-04-08

日拱一兵 @tanrigongyibing

欢迎关注,公众号「日拱一兵」,以读侦探小说思维趣味轻松学习Java技术

关注 23823

iven_wong 发布了文章 · 2019-10-25

白话CMPP、SGIP

白话CMPP、SGIP

我们都知道在国内,有3家运营商(中国联通,中国移动,中国电信),而这三家运营商也是各自使用的自己的协议,不过却是大同小异,它们都是基于我们前两篇(白话短信协议白话彩信协议)介绍的短彩信协议,不过是扩展了一些运营商特有信息而已

那接下我我们就挑两个介绍一下,移动的CMPP协议,以及联通的SGIP协议

CMPP协议

CMPP也有多种版本,这里我们以CMPP2.0为例来介绍,CMPP协议中定义了多种消息类型,主要包括连接消息(Connect Message)、提交消息(Submit Message)、送达消息(Deliver Message)、终端消息(Terminate Message)、心跳消息(Active Test Message)

这里我们主要介绍两个较复杂的Submit Message以及Deliver Message,因为这两个消息是和我们短彩信内容相关的,其它的消息类型看名称就能看得出,是一些功能性的消息,我们这里就略过了

CMPP协议也有信息头和信息体

信息头里包含两个信息command_id和sequece_id,它们各占4个字节,我们这里特别解释一下command_id,command_id代表了消息类型,比如0x00000004表示是一个Submit Message, 0x00000005表示一个Deliver Message,其它消息也有一个command_id对应,我们就不一一列举了;sequence_id表示流水号,我们可以先不用关心

我们接着说信息体,先给你列出信息体里包含的信息:msg_id, pk_total, pk_number, registered_delivery, msg_level, service_id, fee_user_type, fee_terminal_id, tp_pid, tp_udhi, msg_fmt, msg_src, fee_type, fee_code, val_id_time, at_time, src_id, dest_terminal_id, msg_length, msg_content, reserve

我们开始的时候提到过,CMPP是基于短信协议扩展的,那这是怎么体现的呢?

老样子,我们先来回顾一下短信协议是什么样子的:

PDU-complete.png

那我们上面那么多信息是放到PDU哪个部分的呢?其实我们上面的信息并不是放到PDU中间的,而是放到PDU前面的,那我们一个CMPP协议组成(这里列举的是Submit Message)就是这样的:

CMPP.png

知道了CMPP的样子,我们回过来看一下上面有很多信息,我们先不用关心所有的值,我们来看一下和我们短彩信内容有关的:pk_total, pk_number, tp_pid, tp_udhi, msg_fmt, msg_length, msg_content,其中,msg_content看名称就知道是我们的消息内容了,也就是我们PDU的内容

接着我们来看剩下的值表示什么

pk_total, pk_number:还记得我们讲短信协议的时候有提到,长短信会拆分成多条吗?我们当时讲到,如果是长短信,UDH中会带有短信的分段信息(refNr,totalNumberOfSms,seqNr),我们CMPP协议中也需要指定,其pk_total=totalNumberOfSms,pk_number=seqNr

tp_pid:在短信协议中用来表示上层协议类型,在我们CMPP协议里的的值为0,我们可以先不用关心

tp_udhi:如果我们PDU中UDH有值,该值为1,反之则为0

msg_fmt:我们在讲短信协议时有提到过DCS,用来指示如何处理UD,就是这个值了

msg_length:看名称就知道是PDU的长度

接着我们来看一下Deliver Message

Deliver Message和Submit Message一样,包含的内容少了计费相关的信息:msg_id, dest_id, service_id, tp_pid, tp_udhi, msg_fmt, src_terminal_id, registered_delivery, msg_length, msg_content, reserve,和Submit Message的组成一样,我们这里就不重复了

这里补充一点,我们可以看到,Deliver Message中少了pk_total, pk_number,所以在Deliver Message中,我们的长短信信息就需要从UDH中提取了

好了,Submit Message和Deliver Message中所有和短彩信消息内容相关的信息我们就介绍完了

SGIP协议

SGIP也有多种版本,这里我们以SGIP1.2为例来介绍,SGIP协议中也定义了多种消息类型,主要包括连接消息(Bind Message)、提交消息(Submit Message)、送达消息(Deliver Message)、终端消息(Unbind Message)、报告消息(Report Message)

和CMPP一样,我们也是主要介绍Submit Message以及Deliver Message,那我们来看协议的信息头和信息体

信息头里也包含两个信息command_id和sequece_number,command_id占4个字节,command_id同样代表了消息类型,比如0x00000003表示是一个Submit Message, 0x00000004表示一个Deliver Message,其它消息也有一个command_id对应,我们也就不一一列举了;sequece_number和CMPP不同,sequece_number也表示流水号,不过占用12个字节,我们也先不用关心

我们接着说信息体,同样列出信息体里包含的信息:sp_number, charge_number, user_number, crop_id, service_type, fee_type, fee_value, given_type, agent_flag, more_late_to_mt_flag, priority, expire_time, schedule_time, report_flag, tp_pid, tp_udhi, message_coding, message_type, message_length, message_content, reserve

同样我们还是看一下SGIP协议组成(这里也是Submit Message)是什么样子:

SGIP.png

我们来看一下和我们短彩信内容有关的:tp_pid, tp_udhi, message_coding, message_length, message_content

相比于CMPP,SGIP没有pk_total, pk_number,所以我们长短信信息只在UDH中,tp_pid, tp_udhi同CMPP一样,message_coding同CMPP的msg_fmt一样,message_length, message_content同样与CMPP一样

可以看出,SGIP和CMPP大同小异,当然,它们的编码顺序上会有些先后不同,这里就不详细介绍了

同样,接着我们再来看一下Deliver Message

Deliver Message和Submit Message一样,包含的信息:sp_number, user_number, tp_pid, tp_udhi, message_coding, msg_length, msg_content, reserve,和Submit Message的组成一样,我们这里也就不重复了

这里我们没有解释所有的值,也没没有具体列出每个字段占用的字节和编码方式,因为这些相比于短彩信的编码会简单很多,并且我们主要是目的是介绍CMPP、SGIP包含的信息,以及了解其与短信协议的关系

当然,如果你对详细的编码有兴趣,可以到SMSSP查看

后面的内容我们就会介绍基于netty的短彩信网关SMSSP
查看原文

赞 3 收藏 1 评论 0

iven_wong 发布了文章 · 2019-10-24

白话彩信协议

白话彩信协议

彩信协议相比于上一篇白话短信协议会稍微复杂一些

和介绍短信协议时一样,我们先简单介绍一下手机收到彩信的过程,实际上手机收到彩信有两个步骤,第一步会接收到彩信通知,第二步会去彩信通知中给的地址下载彩信文件

以上第一步和我们普通文本短信一样是是以短信协议的方式发送到手机上(wap-push),第二步则是通过通知中附带的地址去下载彩信文件(wap-wsp)

同样,接下来通过白话的方式向你解释这个协议

通知

第一步既然也是短信协议,我们先回顾一下短信协议的组成,是这个样子的:

包含UDH和UD,那彩信通知中UDH和UD的值是什么呢?

先说UDH,其包含2个端口信息(wap-push:2948,wap-wsp:9200),这两个端口是IANA注册的端口,我们后面说,现在我们只需要知道,要存下这两个端口需要4个字节(每个端口2字节),我们正好有IEI为0x05(Application port addressing scheme, 16 bit address),表示IED包含的是16位的端口信息,那我们通知的UDH是这样的:

mms-udh.png

2948的16进制为0x0B84,9200的16进制为0x23F0,所以最后UDH就是:0x05 0x04 0x0B 0x84 0x23 0xF0

接着我们再来说UD,我们前面提到了,我们通知中会带上彩信文件的地址,当手机收到通知时,会去该地址下载彩信文件,所以,彩信的地址必然在我们UD中,哪还有哪些东西需要包含在UD中呢?

不知道大家有没有注意过彩信在手机中展示是什么样的呢?彩信肯定会有图片、视频这些主要信息,不过还会发送人、主题、彩信大小这些信息。那在我们通知中需要包含上面哪些信息呢,实际上以上就是我们彩信文件所包含的全部信息,不过通知中是不需要图片、视频这些主要信息的,不然还要通知干什么呢,那剩下的就是通知中需要带上的信息了,包括发送人、主题、彩信大小。

好了,我们知道了UD中需要存哪些信息,接下来,我们就来看下UD的具体组成

老规矩,在这之前,介绍个概念

WSP(Wireless Session Protocol): 这是一种基于HTTP1.1的一种协议,有自己的编码方式

我们前面说UDH的时候提到,有2个端口信息(wap-push, wap-wsp),这俩个端口恰好对应我们彩信的两个过程,可以把它们当做目的地和源地,我们UD也正是用WSP编码方式存储的,这里简单的说一下WSP是怎么编码的

WSP编码其实可以看做key-value编码,key的编码又有2种,一种可以当做是内置的key,一种是自定义的key

内置key的有对应的一个字节内容代表,自定义key会用专门的字符串编码方式进行编码,value和自定义key一样,如果是字符串会用专门的字符串编码方式进行编码,数字则会用专门的数字编码方式进行编码

我们UD组成也包含信息头和信息体,这个信息头和信息体就不是短信中的UDH和UD了,而是WSP的信息头和信息体

信息头

WSP信息头很简单,包含一个TID和一个PDU TYPE,各占一个字节,TID不用去关心它,我们只需要关心当pdu_type=6时,就表示这是一个wap-push就OK了

比如,我们假设TID=0那么WSP头就为:0x00 0x06

信息体

这里我们再介绍一个概念

MMSEP(Multimedia Messaging Service Encapsulation Protocol): 彩信封装协议,是基于WSP协议

MMSEP也包含信息头和信息体,还记得我们之前说通知和彩信文件的内容差不多吗?其实彩信文件和通知相同的部分就是MMSEP的信息头,多出来的图片、视频就是MMSEP的信息体了,其实通知就是一个信息体为空的MMSEP

MMSEP信息头会指定一个content_type,content_type也是根据专门的字符串编码方式进行编码,比如彩信通知的content_type为:application/vnd.wap.mms-message,MMSEP的content_type和普通content_type不太一样,MMSEP的content_type能带参数,并一起参与编码,彩信通知的content_type就带有一个参数X-Wap-Application-ID=x-wap-application:mms.ua

这里我们简单看一下content_type结果:0x22 0x61 ... 0x00 0xAF 0x84

我们大概解释一下这串编码,还记得前面说的WSP编码方式吗?X-Wap-Application-ID这个key是内置的,用0xAF表示,x-wap-application:mms.ua同样,用0x84表示,那再来看前面的,因为application/vnd.wap.mms-message不是内置的key,所以得按照特殊字符串编码方式编码,第一位表示长度0x22,后面0x61 ... 0x00 就是编码内容了(这里就省略了)

我们接着看,MMSEP信息头里面除了我们之前提到的发送人、主题、彩信大小外,还会有一些其它信息,我们挨个介绍一下

version: 表示使用的WSP编码版本

message_type: 表示彩信类型

transaction_id: 表示事物ID

message_class_id: 表示彩信分类ID

expiry: 表示彩信有效期

subject: 表示标题

location: 表示彩信文件地址(我们前面提到,WSP是基于HTTP的协议,这里可以是一个HTTP地址)

from: 表示发送人

这里我们特别解释一下message_type,message_type代表了这个是一种什么类型的彩信,比如message_type=2表示这是一个通知,message_type=4表示这是一个彩信文件

那到这里,我们的通知UD剩下的部分也知道了,就是这个样子:

MMSEP-header.png

这里大多数key都是内置的,我举几个例子,比如

0x8D900x80表示这是version,0x90表示值为1.0;

0x8A80表示msg_class_id=personal;

0x8C82表示message_type=2;

0x98表示transaction_id,0x88 表示expiry,0x89表示from,0x8E表示size,0x83表示location,0x96表示subject;

这里没有例出的值的key,它们的值就需要用特殊的字符串或者数字编码了

到这里,我们通知的部分就介绍完了,整个通知大概就是:0x05 0x04 0x0B 0x84 0x23 0xF0 0x00 0x06 0x22 0x61 ... 0x00 0xAF 0x84 0x8D90 0x8A80 0x8C82 0x98 ... 0x88 ... 0x89 ... 0x8E ... 0x83 ... 0x96 ...

这里补充一点,可能你也注意到了,从最终结果来看,既然我们彩信通知和普通文本短信一样,而且我们编码出来的东西很可能超过了短信字节的限制,那同样也会有可能出现一个通知分为多条发送的情况,这种情况下,我们不能省略必要的信息,能做的可能就是将location(uri)缩短了

最后,我们从协议的层面来看下通知是什么样子的:

notification-msg.png

彩信文件

前面我们说通知的时候提到了,彩信文件其实就是比彩信通知多了图片、视频的信息,从协议来看就是比彩信通知(信息部分MMSEP header)多了信息体,其实信息体里就是放的图片、视频,彩信文件就是这个样子:

mms.png

前面我们已经介绍过了MMSEP header,那我们彩信文件里只是部分header的值不同,其它都与通知时一样的,下面我把不同的取值给你列出来

content_type: application/vnd.wap.multipart.relatedapplication/vnd.wap.multipart.mixed

message_type: 之前也提到过,如果是彩信文件message_type=4

date: 表示彩信生成时间,不同于通知的expiry,彩信文件中是date

这里就不例出彩信文件的编码结果了,内容和通知中MMSEP header部分相似,只是多了图片、视频的编码(当然同样包含其content_type);包括我们之前提到编码方式的地方,都只是简单说明或直接给的值,没有说具体的方式,因为WSP编码还是比较复杂的,有兴趣可以到SMSJ查看

SMSJ

smsj是个短彩信协议项目、有完整丰富的doc,smsj能方便的生成短彩信协议内容,具体使用方式可以查看项目地址

下一篇白话CMPP、SGIP
查看原文

赞 3 收藏 3 评论 0

iven_wong 发布了文章 · 2019-10-23

白话短信协议

白话短信协议

我们都知道短信其实也是通过网络传输的,不过走的是核心网,那既然同样走的是网络,那这些数据不外乎01010...,那手机是怎么把这串01010...翻译成我们看到的文字短信的呢?

其实短信协议(gsm)和我们HTTP很相似,是基于TCP/IP的协议,短信协议也包含信息头和信息体,其每个部分都有规定的含义

接下来通过白话的方式向你解释这个协议,在这之前,有两个概念先介绍一下

PDU(Protocol Data Unit): 代表我们一条短信的整个数据,也叫一个包

UDH(User Data Header): 数据头

UD(User Data): 数据体

一个PDU包含一个UDH以及一个UD,那一个PDU就是这个样子:

PDU.png

那接下来我们用个最常见的例子,也就是我们平时看到最多的普通文本短信为例子,挨个解释一下每个部分具体包含了哪些内容

UDH(User Data Header)

这儿也先介绍两个概念

IE(Information Element): 表示一个UDH单元

IED(IE Data): 包含一个IE的内容

IEL(Length of IE): 表示一个IED所占用的字节长度

IEI(IE Identifier): 表示一个IE具体代表的是什么含义

UDHL(Length 0f UDH): 表示整个UDH所占用的字节长度

一个IE包含一个IEI以及一个IED数据,那一个IE就是这个样子:

ie.png

IEI上面解释了,表示这个IE代表的是什么,IEI有许多,我们这里就不展开说了,一个IEI占用一个字节,普通短信的IEI=0x00是最最常用的,表示是一条拼接短信(CONCATENATED),后面我们再解释什么是拼接短信

那IED里面有些什么东西呢,当IEI=0x00时,IED包含3个字节,分别是refNr,totalNumberOfSms,seqNr各一个字节,这里先不用管具体的值是多少,我们只关心它们共占用了3个字节

最后我们来看IEDL的值,IEDL同样占用一个字节,我们之所以需要IEDL的值,是因为不同的IE,IED是不一样的,所以需要指定IED的长度才能正确的读出数据,那当IEI=0x00时,我们知道IED占用了3个字节,所以IEDL=3

如果我们假设refNr,totalNumberOfSms,seqNr为0x01 0x01 0x01,最后我们得到的一个IE的值就为:0x00 0x03 0x01 0x01 0x01

同IEDL一样,也需要UDHL的是来指定整个UDH的长度,UDHL占用1个字节,所以UDH就是这个样子:

UDH.png

那假如我们只有一个IE,那么我们的的UDH值就为:0x05 0x00 0x03 0x01 0x01 0x01

UD(User Data)

这儿也先介绍个概念

UDL(Length of UD): 表示一个PDU数据的所占用的字节长度

那一个完整的PDU就是这个样子:

PDU-complete.png

UD其实就是我们的短信内容了,也就是多个字节,这里同样先不用管内容具体是什么,同样如果我们假设短信内容为4个字节0xfe 0xff 0x00 0x61,那我们加上上面的UDH,其一共所占字节长度为6,UDL占用1个字节,所以UDL=10

最后得到的PDU值就为:0x10 0x05 0x00 0x03 0x01 0x01 0x01 0xfe 0xff 0x00 0x61

上面简单通俗的介绍了一下整个短信数据是怎么组成的,以及每个部分代表的含义,以及以普通文本短信为例子,大概说明了一下每个部分所包含的内容,那接下来我们继续介绍一下UD的内容是如何与文本相互转化的

在这之前,老规矩,我们再介绍一个概念

DCS(Data Coding Scheme): 表示应该以什么方式处理UD的数据

关于DCS,上面说了,表示怎么处理UD的数据,也就是包含了UD怎么转化成文本的信息,其占用一个字节,可以说其中每一位(bit)不同,都有着不同的处理方式,虽然没有2^8这么多,但也不少

一个DCS主要包含2个信息,UD编码方式(Character Set),以及短信类别(Message Class)

编码方式有4种(GSM 7 bit,ISO 8859-1,UCS2,reserved)

短信类别有4种(Class 0,Class 1,Class 2,Class 3)

以我们国内运营商为例,我们普通短信所使用的编码方式为UCS2,短信类别为Class 1,那dcs的值为0x19,当然关于这个值肯定不是仅仅靠编码方式和短信类别得来的,我们前面说到,DCS每一位的值都会影响到UD的处理,编码方式和短信类别只占用了其中4位,还有剩余的4位有其它含义,这里就不展开说了

有了DCS,即有了编码方式,我们就能够将UD的字节和文本进行相互转化了,其实上面例子中UD的值就是英文字母a经过UCS2编码得到的字节

CONCATENATED

接下来我们说一说长文本的问题,我们都知道一条短信长度是有限制的,一般来说是140个字节,因为汉字一般使用UCS2编码,UCS2一个字符占用2个字节,所以一条短信用UCS2编码的短信,如果超过70个字符,是需要拆分成多条短信的,但我们手机上却能显示超长的短信,那我们的手机又是如何处理的呢?

还记得我们前面介绍UDH的时候举的例子吗,当UDH中IEI=0x00,表示是一条拼接短信(CONCATENATED),这个IED有3个值分别是refNr,totalNumberOfSms,seqNr,长文本就是通过这个IE来识别多个短信是否是同一个短信的

解释一下这三个值的含义

refNr: 如果值相同的,则标识为同一条短信

totalNumberOfSms: 由多少条短信拼接

seqNr: 拼接短信的顺序

所以上面例子中,这三个值都为1,就表示的是该条短信是由1条短信构成

这篇我们以白话的方式介绍了短信协议中的一些基本概念,短信协议的组成,以及一些常用的值,我们国内运营商所使用的协议也是基于上面所介绍的协议加入了一些各自特有的内容而形成

SMSJ

smsj是个短彩信协议项目、有完整丰富的doc,smsj能方便的生成短彩信协议内容,具体使用方式可以查看项目地址

下一篇 白话彩信协议
查看原文

赞 3 收藏 3 评论 2

iven_wong 关注了用户 · 2018-10-25

已注销 @javajiagoushi007

关注 208

iven_wong 发布了文章 · 2018-08-09

Shiro的统一认证授权

Shiro的统一认证授权

Shiro是Apache下面的一个简单,易用的Java权限框架,对于单体应用来讲,Shiro完全能够极好的,快速的满足权限的需求,所以一般在做项目的时候,Shiro都会成为开发者的首选。

可是,如果你需要做第二个,第三个,第n个应用,同样需要相同的认证、授权时,可能就需要对Shiro进行一定的扩展或者是集成其它框架,才能很好的满足你的需求了。

Shiro是如何进行认证授权

Shiro本身并没有帮你实现认证、授权,但Shiro很好的定义了权限相关的一些概念,让你完成具体的实现

  • 认证
    在Shiro里,完成认证一般是这样的subject.login(token),Subject代表一个用户,Token代表一个用户请求授权时提交的授权信息,通过AuthenticatingRealm.doGetAuthenticationInfo()获取到当前Subject的一些信息,比如Principals,Credentials,校验提交的token,如果登录成功,保存当前登录用户
  • 授权
    在Shiro里,权限控制一般是这样的@RequiresPermissions,当用户访问受保护资源的时候,Shiro会通过AuthorizingRealm.doGetAuthorizationInfo(),从当前认证通过Subject的Principals里获取用户的权限,判断用户是否能访问该资源

在Shiro里,通过实现Realm来完成上面2件事情,当你时单体应用的时候,非常简单就能完成应用的认证授权。

但是当你有多个应用,需要复用同一套用户以及权限信息时该怎么做呢,可以复用Realm,用户权限在同一个db中,这样的话是可以实现的,但是耦合太高,不同的应用必须要接入同一个数据源才行;或者可以把用户权限相关的DAO剥离出来,作为RPC或Rest调用,也可以实现;但是更好的方式是把认证授权整个剥离出来,单独作为认证授权服务

基于Shiro的统一认证授权

为了实现统一认证授权,Shiro有CasFilter,可以集成CAS,但是CAS又是另外一套框架,较为重,有单独的学习成本,所以这里介绍一种更简单,轻量,易用的,基于Shiro的认证授权服务shiro-uaa

认证授权流程

  1. 用户请求受保护资源Resource Server
  2. Resource Server判断用户是否已经登录
  3. 如果没有登录,Resource Server引导用户到UAA Server进行登录
  4. 用户在UAA Server登录,如果登录成功,UAA Server返回code给用户,并引导用户到之前访问的Resource Server
  5. Resource Server用code到UAA Server获取access-token,token包含用户授权信息
  6. Resource Server验证accessToken是否合法,如果合法,在Resource Server保存用户信息

如下图:

map

使用

  • auth-server

    1. 引用maven
    2. 实现自己的登录
  • resource-server

    1. 引用maven
    2. 和shiro一样,使用相关注解进行权限控制

基本上开箱即用,目前auth-server只是作为jar包提供,需要自己实现登录逻辑,后续会有可部署服务

shiro-uaa具体的相关说明介绍可以查看项目地址

查看原文

赞 2 收藏 1 评论 0

认证与成就

  • 获得 47 次点赞
  • 获得 10 枚徽章 获得 1 枚金徽章, 获得 1 枚银徽章, 获得 8 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-02-13
个人主页被 2.1k 人浏览