Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15896 articles
Browse latest View live

5分钟教程:如何通过UART获得root权限

$
0
0

1.png

写在前面的话

你知道物联网设备以及其他硬件制造商是如何调试和测试自家设备的吗?没错,绝大多数情况下,他们都会留下一个串行接口,这样就可以利用这个接口并通过shell来读取实时的调试日志或与硬件进行交互。现在主要有两种不同的串行接口,但最常见的一种是通用异步收发器(UART)。

在这篇文章中,我们将讨论如何通过UART来与TP-Link WR841N (v9.0)进行连接,整个实际动手操作时间大约在五分钟左右。

UART

在开始之前,我想先跟大家简单介绍一下UART的工作机制,如果你已经了解了的话,可以直接跳过这个部分。

UART指的是通用异步收发器,跟其他串行接口不同的是,它是一种不依赖于时钟的串行通信接口,它可以适用于单向通信、半双工通信或全双工等场景下,通信本身主要是通过数据包实现的:

2.png

寻找UART接口

UART接口在物理设备的电路板上,一般是一个拥有3/4个针脚的面板。在我们的分析场景中(TP-Link WR841N v9.0),端口情况如下图所示:

3.png

当你找到UART端口之后,我们还需要区分每一个针脚的功能(GND, VCC, TX, RX)。此时我们需要按照以下步骤进行操作:

1. 识别GND:关掉设备,把你的万用表调到‘连续模式‘,把黑色探针接地(或接其他金属),然后把红色探针依次与上述四个针脚进行接触,如果发出了“哔哔”声,则说明这个针脚为GND。

2. 识别VCC:把万用表调到“DC Voltage(V-)”,把黑色探头接到GND,然后用红色探头去检查UART针脚。然后接点,如果哪个针脚能够出现恒定的高电压(约3.3V或5V),则说明这个针脚就是VCC。

3. 识别TX:在启动过程中的10-15秒时间里,TX针脚会引起非常大的电压波动,因为启动过程中有很多数据需要传输,方法跟寻找VCC针脚的方法相同。

4. 识别RX:RX针脚在整个过程中电压是最低的,相比不用解释太多了。

识别出了所有针脚之后,你就可以焊接一些连接器上去了(Attify Badge):

4.png

5.png

利用UART与设备交互

在我们真正与UART接口进行通信之前,我们需要先弄清楚波特率(Baud Rate)。所谓波特率,指的是数据在设备之间的传输频率。换句话来说,就是每秒传输几比特的数据。常见的波特率为9600, 38400, 19200, 57600和115200,但是理论上来说,制造商是可以随意设置的。

我们可以使用 devttys0的Python脚本来快速寻找到适当的波特率:

$ git clone https://github.com/devttys0/baudrate
$ cd baudrate   

下载之后,我们将TP-Link路由器接电,然后在开启设备的同时运行Python脚本:

$ sudo python baudrate.py

之后,你就可以用上下键来扫描不同的波特率了,这个过程其实跟你调整无线电台频率是一样的。

在我们的测试场景下,TP-Link路由器使用的波特率为115200。

6.png

为了通过UART来与设备进行交互,我们需要运行下列命令:

screen/dev/ttyUSB0 115200

7.png

Boom!!我们成功拿到了shell的root访问权!没错,就是这么简单!接下来,你可以随意查看或修改设备的文件系统了。

意外情况

下面是我在测试过程中所遇到的一些有意思的情况。

TP-Link的安全性确实不容乐观

近期我还对TP-Link WR841N v.9和v.20进行了测试,而我发现当我通过UART尝试访问v.9路由器的shell时,它会要求我输入用户名和密码。但是v.20路由器却直接给我访问设备shell了。

UART+首次启动=启动失败

如果你连接好UART后首次启动设备失败的话,你可以拔掉TX和RX接头,然后再重启设备时重新连接。此时你将会看到Dropbear rsa/dss密钥的创建信息:

8.png

后续研究

我现在的主要目标是通过UART在这些路由器中创建后门,创建恶意固件,或修改目标设备上的特定文件。除此之外,我还会分析这两个版本路由器之间的区别。希望本文能够给各位同学的Hacking带来一些思路。

* 参考来源: konukoii,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.CO


YC创始人2008年看好30个创业方向,现在来看对了几条?

$
0
0

2008 年 7 月时,Y Combinator 创始人 Paul Graham 曾发表一篇文章,他列出创业公司创业的 30 个创意,这些创意是 Y Combinator 看好的。几年过去了,Paul Graham 给出的创业方向正确吗?今天来回味一下,还真是另有一番风味。

当我们查看 Y Combinator 申请项目时,总是能找到我们渴望看到的创意。以前,我们不会在公开场合告诉大家它们是什么。如果我们说,我们正在寻找X,那么申请者就会提交X。这样一来,要评判项目就会变得更困难:到底这个团队提交X是因为它们真的想做,还是因为他们知道我们想听?

我们并不想固守项目,因为我们真正想找的是做项目的人。于是我们换了一套方法:我们会将自己想看到的某些项目列出来,但是描述时却很宽泛。这份创意清单也许很有用,因为想象力丰富的人会将它引向我们无法预料的方向。

YC 创始人 2008 年看好 30 个创业方向,现在来看对了几条?

请注意,不要有这样的想法:如果你想向 Y Combinator 提交申请,必须选择这些创意类型中的某一个,千万别有这样的感觉。如果问,我们在做 YC 的过程中学到了什么,那就是:我们所知甚少。我们曾资助许多出色的公司,比如 Loopt,但它所要做的事情是我们从未想过的。

1、RIAA 是怎么应对危机的,这也许是一个征兆。

当索尼和环球起诉“孩子”时,有些东西被破坏了。事实上,至少有两样东西被破坏:一是文件分享所使用的软件,二是唱片公司的业务模式。现状并非最终答案。在音乐产业发生的事情,电影行业正在重复。20 年后,当一切尘埃落定之时,世界会是怎样的呢?从现在开始,我们就可以动手鼓捣什么组件呢?想找到答案可能还有些遥远。例如,对于音乐产业,答案可能是这样的:录制音乐不要收费了,一心一意关注授权,关注现场表演。电影产业怎么办?

2、简化浏览

从网络浏览器中割舍一些东西,换取更简洁的体验,这样的例子很多很多。老人和小孩并不想要“完整的网络”,他们只是想沟通,想分享照片,想看点东西。在数字相框与计算机 Firfox 之间,还有没有可行的创意,它们没有被发现?如果你找到了,开发出来了,除了老人和小孩,还会有其它人使用吗?

3、新新闻

Marc Andreessen 曾经说过,报纸已经麻烦缠身。问题并不只是它们接受网络的速度太慢,还有更严重的问题:报纸有着深层的结构性问题,问题已经曝光于竞争对手眼前。如果新闻源只有通讯社和少数大报纸,它们就会不断写一些老套的东西,比如总统见了谁,说一些很传统的以观点,由员工来写。读者不再感兴趣,如果新闻无可替代,读者还是愿意读读的。在竞争更激烈的网络环境中,新闻将会发生很大的变化。我们所说的“博客”就是描绘未来的一个信号。新闻网站(比如 Reddit、Digg)也是一股重要力量。不过一切刚刚才开始。

4、IT 外包

在大多公司的内部,IT 部门是一个瓶颈,企业为此付出了惨重代价。要让它们为你制作一份简单的 WEB 表格,居然要几个月时间。于是 Wufoo 冒了出来。如果营销部门想要一份 WEB 表单,只要 5 分钟就能做好,自己做。如果用户还在依赖 IT 部门做什么事,你可以抽出来,以此作为基础建立一家公司,用户对现状不满,你可以将这种不满变成力量,让自己的企业奔跑起来。

5、企业软件 2.0

企业软件公司卖的软件很糟糕,但是收的钱却不少。它们干了不少坏事,但是还是成功,为什么?原因很多,这些原因连接起来,变成一道保护墙。不过软件世界正在改变。如果你深入研究企业软件业务的各个部分(不只是软件本身,还有销售方式),你就会发现,有些部分可以挑出来,让创业公司做。起步时有一条路可以走:为更小的企业开发产品,因为这些小企业没多少钱,不能像大企业一样购买贵的东西。想将东西卖给小企业也要容易一些。

6、变种 CRM 将会越来越多

它是企业软件的一种,我之所以明确提出来,主要是因为这个领域很有潜力。CRM(客户关系管理)意味着无所不包,但是现在的产品功能有限,似乎只能管理邮件列表。它应该可以帮助企业与客户进行更好的交流。

7、企业需要的东西有些不存在

当某些人在工作时需要一些东西,但是这些东西不存在,于是就自己跑去开发,许多出色的创业公司正是这样诞生的。看起来这套方法很模糊,但它极有效。你知道客户想要什么,跑去开发,因为你就是客户。如果你在工作时需要一些东西,其它人也会需要,他们愿意付费。如果你正在为大企业工作,想自己做点事,这套方法可以帮你寻找创意。请从这句话开始:“如果某人开发了……我愿意付很多钱购买。”接下来,你可能就会找到很棒的产品创意。

8、约会

目前的约会网站并不好,会有更好的冒出来。如果有人想创办一家约会创业公司,有两个问题必须回答:会以何种不同的方式约会?除了要回答这个问题,还有一个问题要回答,这个问题更重要,那就要克服鸡和蛋的问题,这个问题是所有约会网站都要面对的。当 Reddit 这样的网站只有 20 个用户时,它也是很有趣的。但是如果约会网站只有 20 人,那可没有谁会用。所以说,如果你想创办一家约会创业公司,提供约会时不要太注重新奇性,因为这部分工作算是比较容易的。你应该围绕鸡与蛋的问题寻找创新方法。

9、照片/视频分享服务

网络上有许多流行照片分享网站。不过那些划入“社交网络”的网站很大程度上也与照片分享有关。大家很喜欢分享文字(比如 IM、邮件、博客,这些都是文字分享 App),其实大家可能更喜欢分享照片。照片更简单,更有趣。我认为这个市场有很大的增长空间。在图片与视频分享服务方面,最终可能会留下 30 种不同的子类型,其中一半现在还没有被发现。

10、拍卖

网络拍卖市场比大家想像的都要大。因为 EBay 做得很糟糕,大家可能会认为网络拍卖很枯燥,但是 EBay 仍然很强大,甚至成了垄断者。最终结果就是停滞不前。在自己的领地上,我怀疑 EBay 将会遭遇攻击,当入侵者成功之后,市场会变得更有价值——比眼下更有价值。和约会一样,如果有创业公司想进入这块领地,与其关注拍卖网站的运营方式,不如多想想办法打破垄断。

11、WEB Office App

如果有人想与微软桌面软件竞争,我们很感兴趣,愿意提供资金支持。很明显,这是一个利润丰厚的市场,看看微软赚了多少钱就知道了。如果有一家创业公司能拿下十分之一的蛋糕,那肯定会高兴得睡不着觉。如果有哪家创业公司想进入,还会得到微软的帮助,微软文化变得越来越官僚,它想守护自己的桌面营收,当它们自己开发 WEB Office 程序时,可能会做得很糟糕。当你创立公司,准备开发类似产品时,你应该做好准备,向大家介绍一下为什么现有 WEB Office 替代产品无法获得成功,而你能够打败它们。

12、纠正广告产业

如果能让观众满意,广告会变得更好。现在的广告不再好么有用,观众已经知道如何屏蔽糟糕的广告,不管广告以多大的声音叫嚣,都没有关系。

就目前来说,我们只是将印刷、电视广告转移到网络。什么是正确的网络广告?它可能会是完全不同的形式。这些广告可能看起来不像广告,按现有标准衡量,不像广告。所以说,如果想解决问题,可能要重新开始思考:想想广告的目标是什么,如何用新技术实现。可能新的答案已经存在,只是还处在发展初期,晚些时候就会被人们承认,替代传统广告。

如果你能发明一种新的广告形式,它的效果可以评估,尤其是销售方面可以评估,那肯定能加分。

13、线上学习

美国的学校太糟糕了。许多家长都意识到了,如果有办法让自己的孩子多学些东西,他们肯定感兴趣。不久之前,学校仍然是地区的垄断者,就像报纸一样。但是网络改变了一切。如果你可以通过网络接触孩子,你会如何教他知识呢?答案可能极有趣,比单纯将书本放在网上更有趣。

有一种办法可以帮你起步,那就是考试准备服务,孩子们需要这样的服务,起步之后再扩张,教孩子们更多东西,不限于在考试中拿到高分。你也可以通过游戏起步,然后慢慢提升他们的思考能力。对于较小的孩子,你可以让他们看其它人是如何解决问题的,边看边学。

14、评估工具

现在有如此多的电脑连接到网络,有些事情我们可能没有意识到,但是仍然可以评估。如果可以评估更多的东西,也许能解决一些大问题。在所有事情中,最重要的就是帮助大机构查找问题:你可以告诉它们谁的生产力更高。小企业是由市场直接评估的,不过一旦企业变得足够大,企业内部的人就会受到市场力量的保护,政治开始抬头,成为主导力量,而不是绩效。在大企业内,即使评估能力只是稍有提高,也可以对整个世界的经济造成巨大影响,如果有创业公司能帮助大企业,肯定能获得成功。

15、现成的安防解决方案

像 ADT 这样的服务前途光明。到了今天,房屋与屋主几乎每时每刻都与网络连接,创业公司也许可以用便宜现成的硬件和服务将它们缝合起来。

16、依赖于设计的搜索形式

谷歌的弱点并不多,当中最大的一个弱点就是设计。它总是努力开发下一个最好的东西,结果精力分散。如果能开发一种高度依赖设计的搜索,创业公司也许可以在搜索领域打败谷歌。我不知道是否有人在做,如果你在做,我们想听听你的说法。

17、新支付方式

因为没有办法收费,有许多东西不能快速增长。有些人本来可以拿出解决方案,但是它们并不知道需求有多大,由于增长被压制,就更难知道了。所以说,如果能找到新的支付方式,在某些情况下让支付变得更简单,那它可能会变成庞大的市场,比投资者预想的更庞大。看看 PayPla 吧(警告:这是一个受到严格监管的行业)。

18、WebOS

我想说的是转向服务器的客户端 OS,这样写可能不太合适。当应用与服务器渐渐融合,也许会出现一样东西,它成为核心,就像 OS 一样。我们已经投资几家公司,它们可能会成为候选者。这个市场很大,也许会出现多个赢家。

19、应用和(或)数据代管

这个领域与数据处理有关,但并非等同于数据处理。再次强调,在这个领域,我们已经投资几家创业公司,未来,这个市场可能会变得很庞大,可以容纳几个利润丰厚的次市场。关于第4、第 18 和第 19 点,可能会有相同的答案,或者说,会有某个答案能回答这三个问题。如果你想找到总体性解决方案,它可能不是那么直接,而是要从小起步,先解决较小的、较特殊的问题,然后慢慢扩张。

20、导购

和新闻一样,购物受到了地域的限制。你可能会去本地商店,从它们拥有的产品中选择。现在可能性更多了,用户需要引导。如果你知道自己想要什么,Bountii 能帮你找到最好的价格。但是你如何判断自己想要什么呢?暗示一下:答案之一与第 3 条有关。

21、面向个人和小企业的财务软件

凭直觉判断,这是一个不错的行业,困难在于,你要将数据与所有银行连接。对于小型创业公司来说,达到目标有点难。不过,如果你从社区开始起步,然后慢慢向其它地方扩散,也许就能替代它们。

22、基于 WEB 的 Excel/数据库混合产品

许多时候,人们将 Excel 当成轻量数据库。我认为,这可能是一个机会,你可以开发一个程序,因为用户有这样的需求,如果它是基于 WEB 开发的,你也许可以增加一些新东西。例如,你可以让数据输入变得更简单。

千万不要让它变成数据库,那会吓倒用户。有一个问题你要好好思考:如果没有定义架构,我能让人们做多少事?你应该将数据库变成语言一样的东西,它可以让用户以链表的形式轻松保存数据。

23、开发更开放的维基百科替代品

在维基百科中,“删除”统治一切。真是讽刺,维基百科居然受到了“打印时代”的思维限制。如果线上参考信息有很长的“尾巴”,由文章组成的尾巴,只有少数人对它们感兴趣,又能有什么伤害呢?只要每个人仍然可以找到他们想找的东西。维基百科颠覆了大英百科全书,想颠覆维基百科仍然是有可能的。

24、为糟糕的客户服务设立缓冲区

有许多公司的客户服务很糟糕。“请不要挂断,对于我们来说,你的呼叫很重要。”读到这些东西的时候,难道不会让你打起退堂鼓吗?有时,UI 会故意刁难客户,例如,一些航空公司故意设置障碍,当你用里程数换购机票时很困难。如果能开发一样东西,解决糟糕客户服务问题,也许有人愿意付费。

25、挑战 Craigslist

是否应该商业化?Craiglist 的看法很矛盾。这种矛盾有利也有弊。如果你关注某个领域,而 Craiglist 在该领域很弱,你也许会找到更好的解决方案,解决 Craiglist 正在努力解决的某些问题。

26、让视频聊天更好

Skype 和 Tokbox 只是开始,在这个领域,还会有更多的革命到来,尤其是移动设备领域。

27、硬件与软件混合

大多黑客认为硬件难以攻破。你必须与混乱、昂贵的硬件打交道。不过 Meraki 告诉我们,如果你愿意冒险进入硬件领域,还是可以有一番作为的。在硬件领域,有许多果实挂得很低,你可以摘到,有时,你只需要对现有产品进行小小的调整,就可以带来很大的变化。

现在的硬件基本上已经软件化。我所说的硬件软件混合,就是说软件扮演非常重要的角色。如果你有这样的想法,相信你会找到立足之地的,因为大多黑客害怕硬件,而大多硬件企业又写不出好软件(正因如此,你的 iPod 不是索尼制造的,索尼也没法写出 iTunes)。

28、解决邮件超载问题

许多人都觉得邮件太多了,包括我。如果你能找到解决方案,就能找到一个已经存在的市场。不过最好的解决方案可能不像开发新邮件阅读器那么明显。你可以将收件箱变成类似任务清单一样的东西,

29、为特殊市场开发建站工具

如果想建站,可以选择 Weebly ,它是一个出色的通用型建站工具。不过你可以针对许多市场开发专用工具。如果你是房产代理,如果你是餐馆经营者或者律师,怎样建站更好呢?现在还没有很好的答案。

很明显,要解决问题,需要编写一个更灵活的建站工具,然后再在上面搭建各种层,制作不同的“变种”站点。暗示一下:如果想为终端用户开发建站工具,关建是让那些不懂设计的人制作很漂亮的网站——至少要让专业人士能制作出来。

30、面向创业公司的创业公司

创业公司的数量越来越多,这本身就是一个创业机会。我们的公司算一家,TechCrunch 算一个。你能做些什么新东西呢?

清单还没有完,姑且省略吧。我们正在寻找各种创意,这些创意的类型很多,清单无法完全覆盖,更别说市场上还有更多的创业构想。如果你有好的创意,但是它们没有在清单中列出来,不要因此否认。有些很棒的创意之所以被人们忽视,就是因为看起来很疯狂。

盘点这份清单是一件很有趣的事。在创意与创意之间,我看到许多相似点,之前被我忽视。事实上,阅读清单时,你能给创业公司绘制一张清晰的合成图:世界上有许多问题,一些老迈慈祥的解决者正在解决,你要成为无情的掠夺者,爬上他们的肩膀。许多时候,创业公司就是无情的竞争者,但是它们参与的游戏很特殊,只有创造出人们想要的东西,才能成为赢家。

原文链接: http://old.ycombinator.com/ideas.html

编译组出品。编辑:郝鹏程

本文链接

人大发言人谈房地产税立法进展:正加快起草法律草案

$
0
0

十三届全国人大一次会议3月4日上午11时在人民大会堂新闻发布厅举行新闻发布会,由大会发言人就大会议程和人大工作相关的问题回答中外记者的提问。十三届全国人大一次会议发言人张业遂表示,加快房地产税立法是党中央提出的重要任务,由全国人大常委会预算工作委员会和财政部牵头组织起草,目前正在加快进行起草完善法律草案、重要问题的论证、内部征求意见等方面的工作。

以下为会议实录:

新华社记者:

现在距离到2020年全面落实税收法定原则只剩3年时间,但现有18个税种只有6个税法,剩下的税收立法修改任务还很艰巨,请问社会关注的房地产税法进展如何?到2020年全面税收法定的目标能否如期实现?谢谢。

张业遂:

谢谢你的提问。税收与国计民生密切相关,依法治税是全面推进依法治国的重要内容,落实税收法定原则是党中央提出的重要改革任务。

全国人大常委会认真贯彻这项改革任务,研究提出落实税收法定原则的实施意见,经党中央审议通过,明确了改革路线图和时间表。一是今后开征新税的,应当通过全国人大及其常委会制定相应的法律。二是对现行的税收条例修改上升为法律或者废止的时间也作出了安排,那就是力争在2020年前完成改革的任务。

正如你刚才提到的,十二届全国人大常委会已先后制定了环境保护税法、烟叶税法、船舶吨税法,修改了企业所得税法,加上此前制定的个人所得税法和车船税法,已经制定了6部税收法律。今年还将制定耕地占用税法、车辆购置税法、资源税法等,并且将修改税收征收管理法。

剩下的任务确实很重,全国人大常委会将督促有关部门按照实施意见确定的时间节点要求,抓紧相关税收法律的起草工作,尽快将相关税法草案提交全国人大常委会审议,力争按时完成这项改革任务。

房地产税立法是社会普遍关注的一个问题,加快房地产税立法是党中央提出的重要任务,由全国人大常委会预算工作委员会和财政部牵头组织起草,目前正在加快进行起草完善法律草案、重要问题的论证、内部征求意见等方面的工作,争取早日完成提请常委会初次审议的准备工作。谢谢。

spring +mybatis读写分离

$
0
0
一、配置定义数据库连接属性

二、定义bean
<!-- 主数据库连接池 -->
    <bean id="masterDataSource" parent="abstractDataSource">
        <property name="url" value="${master.jdbc.url}"/>
        <property name="username" value="${master.jdbc.username}"/>
        <property name="password" value="${master.jdbc.password}"/>
    </bean>

    <!-- 从数据库连接池1 -->
    <bean id="slave1DataSource" parent="abstractDataSource">
        <property name="url" value="${slave1.jdbc.url}"/>
        <property name="username" value="${slave1.jdbc.username}"/>
        <property name="password" value="${slave1.jdbc.password}"/>
    </bean>

    <!-- 从数据库连接池2 -->
    <bean id="slave2DataSource" parent="abstractDataSource">
        <property name="url" value="${slave2.jdbc.url}"/>
        <property name="username" value="${slave2.jdbc.username}"/>
        <property name="password" value="${slave2.jdbc.password}"/>
    </bean>

    <!-- 从数据库连接池3 -->
    <bean id="slave3DataSource" parent="abstractDataSource">
        <property name="url" value="${slave3.jdbc.url}"/>
        <property name="username" value="${slave3.jdbc.username}"/>
        <property name="password" value="${slave3.jdbc.password}"/>
    </bean>

    <!-- 动态切换数据源 -->
    <bean id="dynamicDataSource"  class="com.common.datasource.DynamicDataSource">
        <property name="writeDataSource"  ref="masterDataSource"></property>
        <property name="readDataSources">
            <list>
                <ref bean="slave1DataSource" />
                <ref bean="slave2DataSource" />
                <ref bean="slave3DataSource" />
            </list>
        </property>
        <!--轮询方式-->
        <property name="readDataSourcePollPattern" value="1" />
        <property name="onlyWrite" value="true" />
    </bean>

三、事务定义
<!-- 事务管理器 -->
    <bean id="dynamicTransactionManager" class="com.common.datasource.DynamicDataSourceTransactionManager">
        <property name="dataSource" ref="dynamicDataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="dynamicTransactionManager"/>

四、配置插件
<!-- 配置分页插件 -->
    <plugins>
        <plugin interceptor="com.common.datasource.DynamicPlugin" />
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库 -->
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

五、编写动态数据源类
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final Long MAX_POOL = Long.MAX_VALUE;
    private final Lock lock = new ReentrantLock();
    private Object writeDataSource; //写数据源
    private List<Object> readDataSources; //多个读数据源
    private int readDataSourceSize; //读数据源个数
    private int readDataSourcePollPattern = 0; //获取读数据源方式,0:随机,1:轮询
    private AtomicLong counter = new AtomicLong(0);
    private boolean onlyWrite=false;

    @Override
    public void afterPropertiesSet() {
        if (this.writeDataSource == null) {
            throw new IllegalArgumentException("Property 'writeDataSource' is required");
        }
        setDefaultTargetDataSource(writeDataSource);
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DynamicDataSourceGlobal.WRITE.name(), writeDataSource);
        if (readDataSources != null) {
            for (int i = 0; i < readDataSources.size(); i++) {
                targetDataSources.put(DynamicDataSourceGlobal.READ.name() + i, readDataSources.get(i));
            }
            readDataSourceSize = readDataSources.size();
        } else {
            readDataSourceSize = 0;
        }
        setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        if (onlyWrite){
            return DynamicDataSourceGlobal.WRITE.name();
        }
        DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource();
        if (dynamicDataSourceGlobal == null || dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE || readDataSourceSize <= 0) {
            return DynamicDataSourceGlobal.WRITE.name();
        }
        //index表示选择第几个从数据库
        int index;
        if(readDataSourcePollPattern == 1) {
            //轮询方式
            long currValue = counter.incrementAndGet();
            if((currValue + 1) >= MAX_POOL) {
                try {
                    lock.lock();
                    if((currValue + 1) >= MAX_POOL) {
                        counter.set(0);
                    }
                } finally {
                    lock.unlock();
                }
            }
            index = (int) (currValue % readDataSourceSize);
        } else {
            //随机方式
            index = ThreadLocalRandom.current().nextInt(0, readDataSourceSize);
        }
        return dynamicDataSourceGlobal.name() + index;
    }
    public void setWriteDataSource(Object writeDataSource) {
        this.writeDataSource = writeDataSource;
    }

    public List<Object> getReadDataSources() {
        return readDataSources;
    }

    public void setReadDataSources(List<Object> readDataSources) {
        this.readDataSources = readDataSources;
    }

    public Object getWriteDataSource() {
        return writeDataSource;
    }

    public int getReadDataSourceSize() {
        return readDataSourceSize;
    }

    public void setReadDataSourceSize(int readDataSourceSize) {
        this.readDataSourceSize = readDataSourceSize;
    }

    public int getReadDataSourcePollPattern() {
        return readDataSourcePollPattern;
    }

    public void setReadDataSourcePollPattern(int readDataSourcePollPattern) {
        this.readDataSourcePollPattern = readDataSourcePollPattern;
    }

    public boolean isOnlyWrite() {
        return onlyWrite;
    }

    public void setOnlyWrite(boolean onlyWrite) {
        this.onlyWrite = onlyWrite;
    }
}


public class DynamicDataSourceHolder {
    private static final ThreadLocal<DynamicDataSourceGlobal> holder = new ThreadLocal<DynamicDataSourceGlobal>();

    private DynamicDataSourceHolder() {
        //
    }

    public static void putDataSource(DynamicDataSourceGlobal dataSource) {
        holder.set(dataSource);
    }

    public static DynamicDataSourceGlobal getDataSource() {
        return holder.get();
    }

    public static void clearDataSource() {
        holder.remove();
    }
}

public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {

    /**
     * 只读事务到读库,读写事务到写库
     *
     * @param transaction
     * @param definition
     */
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {

        //设置数据源
        boolean readOnly = definition.isReadOnly();
        if (readOnly) {
            DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ);
        } else {
            DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE);
        }
        super.doBegin(transaction, definition);
    }

    /**
     * 清理本地线程的数据源
     *
     * @param transaction
     */
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        super.doCleanupAfterCompletion(transaction);
        DynamicDataSourceHolder.clearDataSource();
    }
}

/**
* Desc: Mybatis动态数据库切换插件
*/
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {
                MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {
                MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class})})
public class DynamicPlugin implements Interceptor {

    protected static final Logger logger = LoggerFactory.getLogger(DynamicPlugin.class);

    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    private static final Map<String, DynamicDataSourceGlobal> cacheMap = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        if (!synchronizationActive) {
            Object[] objects = invocation.getArgs();
            MappedStatement ms = (MappedStatement) objects[0];
            DynamicDataSourceGlobal dynamicDataSourceGlobal;

            if ((dynamicDataSourceGlobal = cacheMap.get(ms.getId())) == null) {
                //读方法
                if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    //!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库
                    if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                        dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
                    } else {
                        BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                        if (sql.matches(REGEX)) {
                            dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
                        } else {
                            dynamicDataSourceGlobal = DynamicDataSourceGlobal.READ;
                        }
                    }
                } else {
                    dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
                }
                logger.warn("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..", ms.getId(), dynamicDataSourceGlobal.name(), ms.getSqlCommandType().name());
                cacheMap.put(ms.getId(), dynamicDataSourceGlobal);
            }
            DynamicDataSourceHolder.putDataSource(dynamicDataSourceGlobal);
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        //
    }

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



机票行业基础知识:中航信GDS

$
0
0

什么是GDS?

1953年,美国航空公司CEO C.R.Smith和IBM高级销售代表Blair Smith在飞机上巧遇,这次会面促成了世界上第一家航空公司航班控制系统(Inventory Control System–ICS),发展到后来的CRS(面向代理人使用),最后到今日的GDS(Global Distribution System)全球分销系统。这次相遇的结果影响了航空旅游业70年,使得航空业在信息化和全球化上成为第一个吃螃蟹的人,从单一的机票服务发展成为综合旅游产品分销服务的种立分销平台。如今GDS是全球航空业和旅游业都离不开的大型计算机网络的信息服务系统平台。一手是客户,一手是航空公司,起到中间连接的作用。

像多数大型IT系统一样,GDS也是拼拼补补的产物,由航空公司航班控制系统(ICS,Inventory Control System)、计算机订座系统(CRS,Computer Reservation System)等合体演变而来。一个典型的GDS系统包含下面几个部分:

  • 机票分销系统(CRS)
  • 航班控制系统(ICS)
  • 值机配载系统
  • 结算和清算系统

GDS实质上是CRS在分销广度、分销深度、信息质量及分销形式等方面的一次飞跃。

  • 从分销广度,GDS能够在世界范围内,提供交通、住宿、餐饮、娱乐以及支付等”一站式”旅行分销服务;
  • 从分销深度,GDS给旅客提供专业的旅行建议,给供应商提供信息管理咨询服务,这些增值服务为客户和GDS自身都带来了巨大利益;
  • 从信息质量,IT技术的飞速发展,客户服务理念的不断增强,促使GDS提供的信息更加及时、准确、全面和透明,系统响应更为迅速,增加了客户的时间价值;
  • 从分销形式,GDS可以通过电话、互联网、电子客票、自动售货亭、电子商务等多种方式为客户提供服务。

目前,世界上主要有GDS四巨头–北美的Sabre和Worldspan,欧洲的Amadeus和Cendant-Galileo,以及一些服务于特定国家或地区的中小GDS,如我国的Travelsky(中航信)、东南亚的Abacus、韩国的Topas、日本的Axess和Infini、南太平洋的Fantasia以及SITA的Sahara等。

中航信GDS

中国民航信息网络股份有限公司(简称中航信)成立于2000年10月,是中国航空旅游业信息技术解决方案的主导供应商。前身是中国民航计算机信息管理中心,中航信所运营的民航信息系统是国务院监管的八大重点系统之一,是目前中国处于垄断地位的 GDS。作为全球第四大GDS旅游分销系统提供商,中国航信拥有全球最大的BSP数据处理中心为国内航空公司和300余家外国及地区航空公司提供电子旅游分销(ETD),包括航班控制系统服务(ICS)、计算机分销系统服务(CRS)和机场旅客处理(APP),满足所有行业参与者(从商营航空公司、机场、航空旅游产品和服务供应商到旅游分销代理人、机构客户、旅客及货运商)进行电子交易及管理与行程相关信息的需求。

中航信GDS主要包含三大组成模块:

  • CRS全称是Computer Reservation System,即代理人机票售票系统。CRS主要功能是为代理人提供航班可利用情况查询、航段销售、订座记录、电子客票预订,旅游产品等服务。
  • ICS全称是Inventory Control System,即航空公司人员使用的航空公司订座系统。ICS是一个集中式、多航空公司的系统。每个航空公司享有自己独立的数据库、独立的用户群、独立的控制和管理方式,各种操作均可以加以个性化,包括航班班期、座位控制、运价及收益管理、航空联盟、销售控制参数等信息和一整套完备的订座功能引擎。
  • DCS全称是Departure Control System,即机场人员使用的离港控制系统。DCS是为机场提供旅客值机、配载平衡、航班数据控制、登机控制联程值机等信息服务,可以满足值机控制、装载控制、登机控制以及信息交换等机场旅客服务所需的全部功能。

旅客在代理处购买机票。机票代理处首先要做的就是在CRS系统为旅客查询航班信息。CRS系统航班信息是由ICS系统提供,ICS系统的主要功能就是建立,控制和销售航班,所以航空公司就会把所建立好的航班信息传送到CRS以便代理人查询销售航班。旅客要购票,代理处需要在CRS系统为其建立旅客订座信息,当我们建立好记录后,旅客的订座信息会传送到ICS系统,告知航空公司有旅客订取了某某航班的某某舱位。如果订座正常,代理处可以为旅客出票了。DCS系统会在飞机起飞48小时之内对航班进行初始化,ICS系统这时会对DCS系统拍发一份PNL报(旅客名单报),PNL报是指ICS系统把这个航班上所有旅客订座过的记录信息传送到DCS系统以便进行旅客值机。但在航班初始化完后到航班起飞这段期间内,ICS系统还会向DCS系统拍发一份ADL报(旅客增减报),ADL报指把在航班初始化完后到航班起飞这段时间,如果有新的旅客订座和原有旅客取消座位的信息,也会传送到DCS系统以便对上次初始化信息进行修改。最后,当旅客正常登机并且飞机正常起飞之后。这是DCS系统会向ICS系统拍发一份PFS报(最终销售报),PFS报是指把最后所有正常登机的旅客订座信息传送给航空公司系统,那么航空公司系统就可凭这些数据就可以进行结算了。

中航信的相关业务

航空信息技术服务

该服务主要是协助用户购买航空公司的机票,性质和visa有相似之处,你买的每一张机票中航信都有抽成。下面这个图可能表达的更加清晰(ICS,CRS都是它的系统):

不论你采用哪种方式订票,都无法避开中航信的系统(春秋航空除外)。2016年通过公司该系统处理的国内外航空公司的航班订票量高达5.24亿人次,国家统计局显示在2016年中国有4.9亿人次乘飞机出行,公司处于绝对垄断地位。

常见收费规则:

  • GDS 向航司按照在 GDS 预订的每个航段收取费用,称为 Segment Fee。
  • GDS 对下游(包括代理人、OTA 等)按照查询收取流量费,以查定比作为依据,查定比超过一定阈值就额外收费。

春秋航空不加入中航信的最主要原因还是出于成本考虑。作为国内第一家低成本航空公司,其低成本的策略是全方面的。春秋的低成本策略改变还体现在多个方面,包括:

  • 单一机型带来的规模效应和统一规格下的低管理运营成本。
  • 单一仓位。去掉了商务和头等舱提高了客座率。
  • 机票直销(包括网页和app)取消代理使用摆渡车取代廊桥等。

按照成本结算,纳入中航信的系统后,机票销售的成本将占机票总成本的9%左右,而利用春秋航空网站直销的模式,机票销售的成本仅占机票总成本的约3%。

结算及清算服务

清算业务主要是为在中国境内注册的航空公司、机场、航站、货运代理公司、航空服务供应商以及其他民航组织与机构提供涉及航空客、货联运费,UATP 卡及杂项费等十几种类别的国际、国内清算业务服务。航空公司涉及到旅客运输,货物运输,不同航空公司联航运输等等比较复杂的情况,公司通过自身系统整合BSP数据(一种通用的电子票据),出具统一的收付费清单,大大节约了航空公司联运收入的在途时间,提高了资金的利用效率。总而言之就是航空公司,机场所有与钱有关的事项都需要中航信处理。

机场信息技术服务

主要提供机场进出港航班信息,旅客中转以及行李信息等一系列和机场相关的服务。

  • 从航空角度——涉及到着陆、进港滑行、进港站坪保障、出港站坪保障、出港滑行等流程;
  • 从旅客角度——涉及旅客进港、进港边检、楼内摆渡、出港旅客值机、旅客分流、APM(自动捷运)摆渡、出港边检、旅客安检、登机、出港远机位摆渡等流程;
  • 从行李角度——涉及进港行李机下保障、进港行李运输、行李提取、出港行李下传、出港行李分拣、运输、出港行李机下保障等环节。

航空货运物流信息技术服务

很明显了,就是所有有关航空货运的信息整合处理,包括货站信息,货物信息,货物的安检,货物的电子报关等等。

中航信面临的挑战

来自航空公司的挑战

南方航空、东方航空等也都在自建系统开展机票直销。

来自国外GDS的挑战

从2012年10月开始,民航总局开始允许外国航空公司使用国外GDS服务向中国的旅行社销售航空机票。 Amadeus和Abacus是目前两家主要的新竞争者。在2011年Amadeus占了全球旅行社机票预定市场38%的份额,而另一个Abacus是亚太地区最大的GDS服务商。

来自OTA的挑战

互联网时代,流量和入口都在从传统GDS的线下代理人入口向携程这样的互联网OTA转移(尽管OTA最后连接的还是GDS系统),剧情就有了变化了,掌握了流量和客户又是的OTA话语权逐渐开始变强。2015年携程用海外收购的方式控股了TravelFusion(一家欧美低成本航空公司的在线“GDS”服务商),2016年末又收购了具备GDS航班搜索和比价属性的搜索公司SkyScanner,2016年12月基于XML的Direct Connect(直连)外航模式又与汉莎航空开始联姻(这次是彻头彻尾地绕过传统GDS)。

OTA拥有对C端用户的掌控力和影响力,传统GDS做的多是to B生意,很少想到服务终端客户;OTA所代表的互联网技术更为敏捷和高效。通过对数据的分析和运营,实现精准营销或开发更符合用户出行习惯的产品。

The post 机票行业基础知识:中航信GDS appeared first on 标点符.

如何解决微服务架构中的雪崩问题?

$
0
0

记得在三年前公司因为业务发展需要,就曾经将单体应用迁移到分布式框架上来。当时就遇到了这样一个问题:系统仅有一个控制单元,它会调用多个运算单元,如果某个运算单元(作为服务提供者)不可用,将导致控制单元(作为服务调用者)被阻塞,最终导致控制单元崩溃,进而导致整个系统都面临着瘫痪的风险。

那个时候还不知道这其实就是服务的雪崩效应,雪崩效应好比就是蝴蝶效应,说的都是一个小因素的变化,却往往有着无比强大的力量,以至于最后改变整体结构、产生意想不到的结果。雪崩效应也是我们目前研发的产品直面的一道坎,下面我们来看有哪些场景会引发雪崩,又如何避免?对于无法避免的雪崩效应,我们又有哪些应对措施?

1. 星火燎原
1.1农民眼中的微服务

近年来,微服务就象一把燎原的大火,窜了出来并在整个技术社区烧了起来,微服务架构被认为是IT软件服务化架构演进的目标。为什么微服务这么火,微服务能给企业带来什么价值?

1.1.1 以种植农作物的思想来理解微服务

我们以耕种为例来看如何充分利用一块田地的:

  • 先在地里种植了一排排玉米;

  • 后来发现玉米脚下空地可以利用,再间隔一段距离再种上豆角,豆角长大后顺着玉米杆往上爬,最后紧紧地缠绕在玉米杆上;

  • 再后来发现每排玉米之间的空隙地还可以再种些土豆,土豆蔓藤以后会交织在一起,肆虐在玉米脚下吞食营养物质;

表面看来一块土地得到了充分利用,实际上各农作物得不到充分的光照和适宜的营养,如此一来加大了后期除草、松土、施肥、灌溉及收割的成本。

下面的耕植思路是不是更好点呢? 一整块地根据需要分配为若干大小土地块,每块地之间清晰分界,这样就有了玉米地、土豆地、豆角地,再想种什么划块地再耕作就可以了。

这样种植好处很多,比如玉米、豆角和土豆需要的营养物质是不一样的,可由专业技术人员施肥;玉米,豆角和土豆分离,避免豆角藤爬上玉米,缠绕玉米不能自由生长。土豆又汲取玉米需要的营养物质等等问题。

软件系统实现与农作物的种植方式其实也很类似,传统的应用在扩展性,可靠性,维护成本上表现都不尽人意。如何充分利用大量系统资源,管理和监控服务生命周期都是头疼的事情,软件系统设计迫切需要上述的“土地分割种植法”。微服务架构应运而生:在微服务系统中,各个业务系统间通过对消息(字符序列)的处理都非常友好的RestAPI进行消息交互。如此一来,各个业务系统根据Restful架构风格统一成一个有机系统。

1.2 微服务架构下的冰山

泰坦尼克号曾经是世界最大的客轮,在当时被称为是”永不沉没“的,但却在北大西洋撞上冰山而沉没。我们往往只看到它浮出水面的绚丽多彩,水下的基础设施如资源规划、服务注册发现、部署升级,灰度发布等都是需要考虑的因素。

1.2.1 优势
  • 复杂应用分解:复杂的业务场景可被分解为多个业务系统,每个业务系统的每个服务都有一个用消息驱动API定义清楚的边界。

  • 契约驱动:每个业务系统可自由选择技术,组建技术团队利用Mock服务提供者和消费者,并行开发,最终实现依赖解耦。

  • 自由扩展:每个系统可根据业务需要独自进行扩展。

  • 独立部署:每个业务系统互相独立,可根据实际需要部署到合适的硬件机器上。

  • 良好隔离:一个业务系统资源泄漏不会导致整个系统宕掉,容错性较好。

1.2.2 面临的挑战
  • 服务管理:敏捷迭代后的微服务可能越来越多,各个业务系统之间的交互也越来越多,如何做高效集群通信方案也是问题。

  • 应用管理: 每个业务系统部署后对应着一个进程,进程可以启停。如果机器掉电或者宕机了,如何做无缝切换都需要强大的部署管理机制。

  • 负载均衡:为应对大流量场景及提供系统可靠性,同一个业务系统也会做分布式部署即一个业务实例部署在多台机器上。如果某个业务系统挂掉了,如何按需做自动伸缩分布式方案方案也需要考虑。

  • 问题定位:单体应用的日志集中在一起,出现问题定位很方便,而分布式环境的问题定界定位,日志分析都较为困难。

  • 雪崩问题:分布式系统都存在这样一个问题,由于网络的不稳定性,决定了任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩效应。

Michael T. Nygard 在精彩的《Release It!》一书中总结了很多提高系统可用性的模式,其中非常重要的两条是:使用超时策略和使用熔断器机制。

  • 超时策略:如果一个服务会被系统中的其它部分频繁调用,一个部分的故障可能会导致级联故障。例如,调用服务的操作可以配置为执行超时,如果服务未能在这个时间内响应,将回复一个失败消息。然而,这种策略可能会导致许多并发请求到同一个操作被阻塞,直到超时期限届满。这些阻塞的请求可能会存储关键的系统资源,如内存、线程、数据库连接等。因此,这些资源可能会枯竭,导致需要使用相同的资源系统的故障。在这种情况下,它将是优选的操作立即失败。设置较短的超时可能有助于解决这个问题,但是一个操作请求从发出到收到成功或者失败的消息需要的时间是不确定的。

  • 熔断器模式:熔断器的模式使用断路器来检测故障是否已得到解决,防止请求反复尝试执行一个可能会失败的操作,从而减少等待纠正故障的时间,相对与超时策略更加灵活。

一年一度的双十一已经悄然来临,下面将介绍某购物网站一个Tomcat容器在高并发场景下的雪崩效应来探讨Hystrix的线程池隔离技术和熔断器机制。

2. 从雪崩看应用防护
2.1 雪崩问题的本质:Servlet Container在高并发下崩溃

我们先来看一个分布式系统中常见的简化的模型。Web服务器中的Servlet Container,容器启动时后台初始化一个调度线程,负责处理Http请求,然后每个请求过来调度线程从线程池中取出一个工作者线程来处理该请求,从而实现并发控制的目的。

Servlet Container是我们的容器,如Tomcat。一个用户请求有可能依赖其它多个外部服务。考虑到应用容器的线程数目基本都是固定的(比如Tomcat的线程池默认200),当在高并发的情况下,如果某一外部依赖的服务(第三方系统或者自研系统出现故障)超时阻塞,就有可能使得整个主线程池被占满,增加内存消耗,这是长请求拥塞反模式(一种单次请求时延变长而导致系统性能恶化甚至崩溃的恶化模式)。

更进一步,如果线程池被占满,那么整个服务将不可用,就又可能会重复产生上述问题。因此整个系统就像雪崩一样,最终崩塌掉。

2.2 雪崩效应产生的几种场景
  • 流量激增:比如异常流量、用户重试导致系统负载升高;

  • 缓存刷新:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;

  • 程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;

  • 硬件故障:比如宕机,机房断电,光纤被挖断等。

  • 线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;

2.3 雪崩效应的常见解决方案

针对上述雪崩情景,有很多应对方案,但没有一个万能的模式能够应对所有场景。

  • 针对流量激增,采用自动扩缩容以应对突发流量,或在负载均衡器上安装限流模块。

  • 针对缓存刷新,参考Cache应用中的服务过载案例研究

  • 针对硬件故障,多机房容灾,跨机房路由,异地多活等。

  • 针对同步等待,使用Hystrix做故障隔离,熔断器机制等可以解决依赖服务不可用的问题。

通过实践发现,线程同步等待是最常见引发的雪崩效应的场景,本文将重点介绍使用Hystrix技术解决服务的雪崩问题。后续再分享流量激增和缓存刷新等应对方案。

3. 隔离和熔断

Hystrix 是由Netflix发布,旨在应对复杂分布式系统中的延时和故障容错,基于Apache License 2.0协议的开源的程序库,目前托管在GitHub上。

Hystrix采用了命令模式,客户端需要继承抽象类HystrixCommand并实现其特定方法。为什么使用命令模式呢?使用过RPC框架都应该知道一个远程接口所定义的方法可能不止一个,为了更加细粒度的保护单个方法调用,命令模式就非常适合这种场景。

命令模式的本质就是分离方法调用和方法实现,在这里我们通过将接口方法抽象成HystricCommand的子类,从而获得安全防护能力,并使得的控制力度下沉到方法级别。

Hystrix核心设计理念基于命令模式,命令模式UML如下图:

可见,Command是在Receiver和Invoker之间添加的中间层,Command实现了对Receiver的封装。那么Hystrix的应用场景如何与上图对应呢?

API既可以是Invoker又可以是Reciever,通过继承Hystrix核心类HystrixCommand来封装这些API(例如,远程接口调用,数据库的CRUD操作可能会产生延时),就可以为API提供弹性保护了。

3.1 资源隔离模式

Hystrix之所以能够防止雪崩的本质原因,是其运用了资源隔离模式,我们可以用蓄水池做比喻来解释什么是资源隔离。生活中一个大的蓄水池由一个一个小的池子隔离开来,这样如果某一个水池的水被污染,也不会波及到其它蓄水池,如果只有一个蓄水池,水池被污染,整池水都不可用了。软件资源隔离如出一辙,如果采用资源隔离模式,将对远程服务的调用隔离到一个单独的线程池后,若服务提供者不可用,那么受到影响的只会是这个独立的线程池。

(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)。这个大家都比较熟悉,参考Java自带的ThreadPoolExecutor线程池及队列实现。线程池隔离参考下图:

线程隔离的优点:

  • 请求线程与依赖代码的执行线程可以完全隔离第三方代码;

  • 当一个依赖线程由失败变成可用时,线程池将清理后并立即恢复可用;

  • 线程池可设置大小以控制并发量,线程池饱和后可以拒绝服务,防止依赖问题扩散。

线程隔离的缺点:

  • 增加了处理器的消耗,每个命令的执行涉及到排队(默认使用SynchronousQueue避免排队)和调度;

  • 增加了使用ThreadLocal等依赖线程状态的代码复杂性,需要手动传递和清理线程状态。

对于一些技术这里我找朋友一起录了些视频讲解,如果有兴趣大家可以加群 318261748 免费领取 群里还有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。 

还有更多视频学习资料可以获取,进群请备注领取资料。



 

 

(2)信号量隔离模式:使用一个原子计数器来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务),参考Java的信号量的用法。

Hystrix默认采用线程池隔离机制,当然用户也可以配置 HystrixCommandProperties为隔离策略为ExecutionIsolationStrategy.SEMAPHORE。

信号隔离的特点:

  • 信号隔离与线程隔离最大不同在于执行依赖代码的线程依然是请求线程,该线程需要通过信号申请;

  • 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。

线程池隔离和信号隔离的区别见下图,使用线程池隔离,用户请求了15条线程,10条线程依赖于A线程池,5条线程依赖于B线程池;如果使用信号量隔离,请求到C客户端的信号量若设置了15,那么图中左侧用户请求的10个信号与右边的5个信号量需要与设置阈值进行比较,小于等于阈值则执行,否则直接返回。

建议使用的场景:根据请求服务级别划分不同等级业务线程池,甚至可以将核心业务部署在独立的服务器上。

3.2 熔断器机制

熔断器与家里面的保险丝有些类似,当电流过大时,保险丝自动熔断以保护我们的电器。假设在没有熔断器机制保护下,我们可能会无数次的重试,势必持续加大服务端压力,造成恶性循环;如果直接关闭重试功能,当服务端又可用的时候,我们如何恢复?

熔断器正好适合这种场景:当请求失败比率(失败/总数)达到一定阈值后,熔断器开启,并休眠一段时间,这段休眠期过后熔断器将处与半开状态(half-open),在此状态下将试探性的放过一部分流量(Hystrix只支持single request),如果这部分流量调用成功后,再次将熔断器闭合,否则熔断器继续保持开启并进入下一轮休眠周期。

建议使用场景:Client端直接调用远程的Server端(server端由于某种原因不可用,从client端发出请求到server端超时响应之间占用了系统资源,如内存,数据库连接等)或共享资源。

不建议的场景如下:

  • 应用程序直接访问如内存中的数据,若使用熔断器模式只会增加系统额外开销。

  • 作为业务逻辑的异常处理替代品。

总结思考

本文从自己曾经开发的项目应用的分布式架构引出服务的雪崩效应,进而引出Hystrix(当然了,Hystrix还有很多优秀的特性,如缓存,批量处理请求,主从分担等,本文主要介绍了资源隔离和熔断)。主要分三部分进行说明:

第一部分:以耕种田地的思想引出软件领域设计的微服务架构, 简单的介绍了其优点,着重介绍面临的挑战:雪崩问题。

第二部分:以Tomcat Container在高并发下崩溃为例揭示了雪崩产生的过程,进而总结了几种诱发雪崩的场景及各种场景的应对解决方案,针对同步等待引出了Hystrix框架。

第三部分:介绍了Hystrix背景,资源隔离(总结了线程池和信号量特点)和熔断机制工作过程,并总结各自使用场景。

如Martin Fowler 在其文中所说,尽管微服务架构未来需要经历时间的检验,但我们已经走在了微服务架构转型的道路上,对此我们可以保持谨慎的乐观,这条路依然值得去探索。

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



想要做”架构师“,一定要会画设计图

$
0
0

什么是系统架构师? 

系统架构师是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。一个架构师得需要足够的想像力,能把各种目标需求进行不同维度的扩展,为目标客户提供更为全面的需求清单。

架构师在软件开发的整个过程中起着很重要的作用。

如何才能成为系统架构师?

1. 首先必须具有丰富的软件设计与开发经验,这有助于理解并解释所进行的设计是如何映射到实现中去。

2. 其次要具有领导能力与团队协作技能,软件架构师必须是一个得到承认的技术领导,能在关键时候对技术的选择作出及时、有效的决定。

3. 第三是具有很强的沟通能力,其实这一点好像什么角色都最好具备,软件架构师需要与各路人马经常打交道,客户、市场人员、开发人员、测试人员、项目经理、网络管理员、数据库工程师等等,而且在很多角色之间还要起沟通者的作用。 

而设计图,它不是简单的供你欣赏,他其实是架构师,产品经理,开发工程师,测试工程师等各种角色之间进行沟通的语言,沟通的一个桥梁,让整个团队更能有效的协调工作。

设计图不单单是架构师要掌握的,在一个产品的开发过程中,任何一个环节,任何一个角色都可以通过掌握不同的设计图来完成沟通的。

流程图

流程是一系列的逻辑关系(包含因果关系、时间先后、必要条件、输入输出)产品经理做需求前一定要先把这些逻辑关系理清楚,如果非要用一句话概括的话“流程就是在特定的情境下满足用户特定需要的总结”。

图就是将你头脑中的逻辑关系以图形化的形式呈现出来,具有图形化、可视化的特点,因为是图,你可以像你的版本迭代一样,当你的逻辑需要修改的时候拿出来迭代一下,同时因为有图,你还可以更好的给项目成员进行宣讲。

产品中设计的流程图主要有三种,业务流程图、任务流程图、页面流程图,下面我们来一一介绍。

业务流程图

业务流程图又称为泳道图,就是描述那些个体在什么条件下做了什么事情,他们之间有何关联。主要分三个方面:

1. 涉及到哪些主体?

2. 每个主体都有哪些任务?

3. 各个主体之间怎么联系的?一般涉及到多个主体,每个主体之间有联系。

  任务流程图 

泳道图一般是从战略上分析整个业务流程,让你对公司所做的业务有个大概的了解,而任务流程图就是在你的产品操作上,用户通过什么样的操作来完成它的目标,比如你去银行ATM机器上取钱,你是如何一步步操作把钱取出来的。

页面流程图

如果说业务流程图帮助你梳理战略,任务流程图帮助你梳理用户操作行为(主要给程序员看)、页面跳转流程在帮助你梳理各个页面之间的跳转关系(主要给UI和前端程序员看)这是一个逐步从整体到局部,从后端到前端的过程。

 

 

所有的产品都是由页面组成的,不论是APP、PC、H5都是由一个个页面组成的,页面流程图描述完成一个任务需要经过哪些步骤,你在画图的时候只需要清晰的表现出用户点击页面的什么地方,然后跳转到那个页面。主要由页面、行动点、连接线组成。

UI设计图标注

对于APP的页面,UI设计师会给出UI设计标注图,这样APP客户端开发人员,直接按照标注图进行页面的开发了。

 

产品设计完成后,架构师需要对产品进行软件的架构设计。包括技术的选型,模块的划分,开发人员的任务分配,工作量的评估等等.....

系统架构设计图

构架将在一次又一次迭代中不断演化、改进、精炼。

 

序列图

架构师一般在做详细设计的时候,会把程序模块之间的每一步调用过程很详细的画出来,这样开发人员拿到设计文档,就能直接开发。

类图

 

设计图有很多种,还包括用例图,状态图,活动图...... 不再一一介绍。画什么样的设计图,不是绝对的,不同公司,不同项目,需要画的设计图也是不同的,有些项目需要画原型图,有些项目只是对外提供服务,没有页面也就不需要画原型图。另外还要根据项目的工期,预算等等因素考虑。如果一个项目的工期也就一个月甚至更短,那基本上就是怎么简单怎么快就怎么做。

画图工具

‘工欲善其事,必先利其器’,下面就为大家介绍几款常用设计图绘制工具。以下软件都可以在 微信公众号,回复“ 设计”,获取破解版本。

Visio

是微软推出的一款流程图绘制工具,它有很多组件库,可以方便快捷的完成流程图、泳道图、结构图的绘制,但是不支持mac电脑。

 

OmniGraffle

Mac下没有Visio很多人就用这个,这个一般流程图都能绘制,但是效率感觉没有Visio高,优点就是画出来的图形比较美,同时支持外部插件,缺点就是没有比较好的泳道流程图插件,画起泳道图来不是太方便,但也可以画,可以自己组装泳道。

另外一个缺点是收费的,只能免费试用15天,不过我已经为大家准备好了一个最新的破解版本。 

 

ProcessOn

是一款网页版的在线作图工具,优点是无需下载安装、破解这些破事,同时支持在线协作,可以多人同时对一个文件协作编辑,而且上手比较容易,它提供很多流程图模版,可以方便的画出流程图、思维导图、原型图、UML图,缺点就是在绘制泳道图需要增加泳道的时候,只能在最后一列加入,不能在中间加入这一点有点麻烦,还有要吐槽的就是由于是在线的,有时候导出图片,导出来的并不太好,流程图画的大的时候也无法截图。

在线地址:https://www.processon.com

 

Axure RP

这是一款产品经理经常用来画原型的工具,它可以在页面里定义各种按钮点击事件,进行页面的跳转,模拟提交的过程,所以非常方便使用。画人物流程图的时候也可以用,但是要画泳道图、UML图的时候,没有对应的模版,需要自己画,效率不高,如果你觉得画原型,制作文档都在Axure里,不想来回切换软件的画,可以在里面自己制作一个组件,下次直接调用。

Axure RP是可以画出这样效果的原型图

 

PxCook

一款还不错的标注工具.

优点:

1. 成熟:跨平台——支持Windows和Mac

2. 成熟2:支持PS和Sketch。

3. 交互特别智能,也方便,一拖一放就标注完了。

4. 相当需要说的一点:对于PSD文件或者Sketch进行了修改之后,PxCook里的标注会自动进行更新,免除了手动操作的过程。这是后面很多软件没有的。

5. 支持移动设备的多单位切换。

缺点:

1. 不能支持多个文件同时进行标注。

2. 对于图层样式等信息,不能进行详细查看。 

iThoughtsX

一款优秀的思维导图工具

 

OmniPlan

最NB的项目管理流程软件,OmniPlan旨在帮助您可视化,维护和简化您的项目。分解任务,优化所需的资源,控制成本,并监控您的整个计划,都一目了然。协作与您的同事和分享每一个细节,更新日历与你的天关,或混搭。接受和拒绝一次过改变一个接一个或所有。

 OmniPlan提供了像甘特图,时间表,摘要,里程碑和关键路径的功能突出显示,让您管理您的所有活动。从自定义的视图来快速输入数据, OmniPlan帮助您管理,因为你需要他们,简单或复杂的项目是 - 不需要复杂。

 

以上软件都可以在 微信公众号,回复“ 设计”,获取破解版本。

 

推荐阅读:

技术: HTTP状态码大全

技术:SpringBoot 如何在一分钟内整合SSM?

技术: CentOS7下Nginx服务器安装与使用教程

技术: Java9逆天的十大新特性

技术: http2.0的时代真的来了...

工具: 如何通过技术手段 “干掉” 视频APP里讨厌的广告?

工具: 通过技术手段 “干掉” 视频APP里讨厌的广告之(腾讯视频)

工具: 抓包神器之Charles,常用功能都在这里了

干货分享:

分享: 1T 软件开发视频资源分享

分享: 深度机器学习56G视频资源分享

<audio controls="controls" style="display: none;"></audio>

<audio controls="controls" style="display: none;"></audio>



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



基于redis分布式锁实现“秒杀”

$
0
0

转载 

来自于  http://blog.5ibc.net/p/28883.html

业务场景

所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀场景里是商品;将业务抽象,技术角度看,秒杀就是多个线程对资源进行操作,所以实现秒杀,就必须控制线程对资源的争抢,既要保证高效并发,也要保证操作的正确。

一些可能的实现

刚才提到过,实现秒杀的关键点是控制线程对资源的争抢,根据基本的线程知识,可以不加思索的想到下面的一些方法: 
1、秒杀在技术层面的抽象应该就是一个方法,在这个方法里可能的操作是将商品库存-1,将商品加入用户的购物车等等,在不考虑缓存的情况下应该是要操作数据库的。那么最简单直接的实现就是在这个方法上加上 synchronized关键字,通俗的讲就是锁住整个方法; 
2、锁住整个方法这个策略简单方便,但是似乎有点粗暴。可以稍微优化一下,只锁住秒杀的代码块,比如写数据库的部分; 
3、既然有并发问题,那我就让他“不并发”,将所有的线程用一个队列管理起来,使之变成串行操作,自然不会有并发问题。

上面所述的方法都是有效的,但是都不好。为什么?第一和第二种方法本质上是“加锁”,但是锁粒度依然比较高。什么意思?试想一下,如果两个线程同时执行秒杀方法,这两个线程操作的是不同的商品,从业务上讲应该是可以同时进行的,但是如果采用第一二种方法,这两个线程也会去争抢同一个锁,这其实是不必要的。第三种方法也没有解决上面说的问题。

<iframe id="aswift_1" style="left: 0px; position: absolute; top: 0px; width: 580px; height: 145px;" name="aswift_1" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="580" height="145"></iframe>

那么如何将锁控制在更细的粒度上呢?可以考虑为每个商品设置一个互斥锁,以和商品ID相关的字符串为唯一标识,这样就可以做到只有争抢同一件商品的线程互斥,不会导致所有的线程互斥。分布式锁恰好可以帮助我们解决这个问题。

何为分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

我们来假设一个最简单的秒杀场景:数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1。现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,500个线程秒杀第二个商品。我们来根据这个简单的业务场景来解释一下分布式锁。 
通常具有秒杀场景的业务系统都比较复杂,承载的业务量非常巨大,并发量也很高。这样的系统往往采用分布式的架构来均衡负载。那么这1000个并发就会是从不同的地方过来,商品库存就是共享的资源,也是这1000个并发争抢的资源,这个时候我们需要将并发互斥管理起来。这就是分布式锁的应用。 
而key-value存储系统,如 redis,因为其一些特性,是实现分布式锁的重要工具。

具体的实现

先来看看一些redis的基本命令: 
SETNX key value 
如果key不存在,就设置key对应字符串value。在这种情况下,该命令和SET一样。当key已经存在时,就不做任何操作。SETNX是”SET if Not eXists”。 
expire KEY seconds 
设置key的过期时间。如果key已过期,将会被自动删除。 
del KEY 
删除key 
由于笔者的实现只用到这三个命令,就只介绍这三个命令,更多的命令以及redis的特性和使用,可以参考 redis官网

需要考虑的问题

1、用什么操作redis?幸亏redis已经提供了jedis客户端用于java应用程序,直接调用jedis API即可。 
2、怎么实现加锁?“锁”其实是一个抽象的概念,将这个抽象概念变为具体的东西,就是一个存储在redis里的key-value对,key是于商品ID相关的字符串来唯一标识,value其实并不重要,因为只要这个唯一的key-value存在,就表示这个商品已经上锁。 
3、如何释放锁?既然key-value对存在就表示上锁,那么释放锁就自然是在redis里删除key-value对。 
4、阻塞还是非阻塞?笔者采用了阻塞式的实现,若线程发现已经上锁,会在特定时间内轮询锁。 
5、如何处理异常情况?比如一个线程把一个商品上了锁,但是由于各种原因,没有完成操作(在上面的业务场景里就是没有将库存-1写入数据库),自然没有释放锁,这个情况笔者加入了锁超时机制,利用redis的expire命令为key设置超时时长,过了超时时间redis就会将这个key自动删除,即强制释放锁(可以认为超时释放锁是一个异步操作,由redis完成,应用程序只需要根据系统特点设置超时时间即可)。

talk is cheap,show me the code

在代码实现层面,注解有并发的方法和参数,通过动态代理获取注解的方法和参数,在代理中加锁,执行完被代理的方法后释放锁。

几个注解定义: 
cachelock是方法级的注解,用于注解会产生并发问题的方法:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheLock {
    String lockedPrefix() default "";//redis 锁key的前缀
    long timeOut() default 2000;//轮询锁的时间
    int expireTime() default 1000;//key在redis里存在的时间,1000S
}

lockedObject是参数级的注解,用于注解商品ID等基本类型的参数:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockedObject {
    //不需要值
}

LockedComplexObject也是参数级的注解,用于注解自定义类型的参数:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockedComplexObject {
    String field() default "";//含有成员变量的复杂对象中需要加锁的成员变量,如一个商品对象的商品ID

}

CacheLockInterceptor实现 InvocationHandler接口,在invoke方法中获取注解的方法和参数,在执行注解的方法前加锁,执行被注解的方法后释放锁:

public class CacheLockInterceptor implements InvocationHandler{
    public static int ERROR_COUNT  = 0;
    private Object proxied;

    public CacheLockInterceptor(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        CacheLock cacheLock = method.getAnnotation(CacheLock.class);
        //没有cacheLock注解,pass
        if(null == cacheLock){
            System.out.println("no cacheLock annotation");          
            return method.invoke(proxied, args);
        }
        //获得方法中参数的注解
        Annotation[][] annotations = method.getParameterAnnotations();
        //根据获取到的参数注解和参数列表获得加锁的参数
        Object lockedObject = getLockedObject(annotations,args);
        String objectValue = lockedObject.toString();
        //新建一个锁
        RedisLock lock = new RedisLock(cacheLock.lockedPrefix(), objectValue);
        //加锁
        boolean result = lock.lock(cacheLock.timeOut(), cacheLock.expireTime());
        if(!result){//取锁失败
            ERROR_COUNT += 1;
            throw new CacheLockException("get lock fail");

        }
        try{
            //加锁成功,执行方法
            return method.invoke(proxied, args);
        }finally{
            lock.unlock();//释放锁
        }

    }
    /**
     * 
     * @param annotations
     * @param args
     * @return
     * @throws CacheLockException
     */
    private Object getLockedObject(Annotation[][] annotations,Object[] args) throws CacheLockException{
        if(null == args || args.length == 0){
            throw new CacheLockException("方法参数为空,没有被锁定的对象");
        }

        if(null == annotations || annotations.length == 0){
            throw new CacheLockException("没有被注解的参数");
        }
        //不支持多个参数加锁,只支持第一个注解为lockedObject或者lockedComplexObject的参数
        int index = -1;//标记参数的位置指针
        for(int i = 0;i < annotations.length;i++){
            for(int j = 0;j < annotations[i].length;j++){
                if(annotations[i][j] instanceof LockedComplexObject){//注解为LockedComplexObject
                    index = i;
                    try {
                        return args[i].getClass().getField(((LockedComplexObject)annotations[i][j]).field());
                    } catch (NoSuchFieldException | SecurityException e) {
                        throw new CacheLockException("注解对象中没有该属性" + ((LockedComplexObject)annotations[i][j]).field());
                    }
                }

                if(annotations[i][j] instanceof LockedObject){
                    index = i;
                    break;
                }
            }
            //找到第一个后直接break,不支持多参数加锁
            if(index != -1){
                break;
            }
        }

        if(index == -1){
            throw new CacheLockException("请指定被锁定参数");
        }

        return args[index];
    }
}

最关键的RedisLock类中的lock方法和unlock方法:

/**
     * 加锁
     * 使用方式为:
     * lock();
     * try{
     *    executeMethod();
     * }finally{
     *   unlock();
     * }
     * @param timeout timeout的时间范围内轮询锁
     * @param expire 设置锁超时时间
     * @return 成功 or 失败
     */
    public boolean lock(long timeout,int expire){
        long nanoTime = System.nanoTime();
        timeout *= MILLI_NANO_TIME;
        try {
            //在timeout的时间范围内不断轮询锁
            while (System.nanoTime() - nanoTime < timeout) {
                //锁不存在的话,设置锁并设置锁过期时间,即加锁
                if (this.redisClient.setnx(this.key, LOCKED) == 1) {
                    this.redisClient.expire(key, expire);//设置锁过期时间是为了在没有释放
                    //锁的情况下锁过期后消失,不会造成永久阻塞
                    this.lock = true;
                    return this.lock;
                }
                System.out.println("出现锁等待");
                //短暂休眠,避免可能的活锁
                Thread.sleep(3, RANDOM.nextInt(30));
            } 
        } catch (Exception e) {
            throw new RuntimeException("locking error",e);
        }
        return false;
    }

    public  void unlock() {
        try {
            if(this.lock){
                redisClient.delKey(key);//直接删除
            }
        } catch (Throwable e) {

        }
    }

上述的代码是框架性的代码,现在来讲解如何使用上面的简单框架来写一个秒杀函数。 
先定义一个接口,接口里定义了一个秒杀方法:

public interface SeckillInterface {
/**
*现在暂时只支持在接口方法上注解
*/
    //cacheLock注解可能产生并发的方法
    @CacheLock(lockedPrefix="TEST_PREFIX")
    public void secKill(String userID,@LockedObject Long commidityID);//最简单的秒杀方法,参数是用户ID和商品ID。可能有多个线程争抢一个商品,所以商品ID加上LockedObject注解
}

上述 SeckillInterface接口的实现类,即秒杀的具体实现:

public class SecKillImpl implements SeckillInterface{
    static Map<Long, Long> inventory ;
    static{
        inventory = new HashMap<>();
        inventory.put(10000001L, 10000l);
        inventory.put(10000002L, 10000l);
    }

    @Override
    public void secKill(String arg1, Long arg2) {
        //最简单的秒杀,这里仅作为demo示例
        reduceInventory(arg2);
    }
    //模拟秒杀操作,姑且认为一个秒杀就是将库存减一,实际情景要复杂的多
    public Long reduceInventory(Long commodityId){
        inventory.put(commodityId,inventory.get(commodityId) - 1);
        return inventory.get(commodityId);
    }

}

模拟秒杀场景,1000个线程来争抢两个商品:

@Test
    public void testSecKill(){
        int threadCount = 1000;
        int splitPoint = 500;
        CountDownLatch endCount = new CountDownLatch(threadCount);
        CountDownLatch beginCount = new CountDownLatch(1);
        SecKillImpl testClass = new SecKillImpl();

        Thread[] threads = new Thread[threadCount];
        //起500个线程,秒杀第一个商品
        for(int i= 0;i < splitPoint;i++){
            threads[i] = new Thread(new  Runnable() {
                public void run() {
                    try {
                        //等待在一个信号量上,挂起
                        beginCount.await();
                        //用动态代理的方式调用secKill方法
                        SeckillInterface proxy = (SeckillInterface) Proxy.newProxyInstance(SeckillInterface.class.getClassLoader(), 
                            new Class[]{SeckillInterface.class}, new CacheLockInterceptor(testClass));
                        proxy.secKill("test", commidityId1);
                        endCount.countDown();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
            threads[i].start();

        }
        //再起500个线程,秒杀第二件商品
        for(int i= splitPoint;i < threadCount;i++){
            threads[i] = new Thread(new  Runnable() {
                public void run() {
                    try {
                        //等待在一个信号量上,挂起
                        beginCount.await();
                        //用动态代理的方式调用secKill方法
                        SeckillInterface proxy = (SeckillInterface) Proxy.newProxyInstance(SeckillInterface.class.getClassLoader(), 
                            new Class[]{SeckillInterface.class}, new CacheLockInterceptor(testClass));
                        proxy.secKill("test", commidityId2);
                        //testClass.testFunc("test", 10000001L);
                        endCount.countDown();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
            threads[i].start();

        }


        long startTime = System.currentTimeMillis();
        //主线程释放开始信号量,并等待结束信号量,这样做保证1000个线程做到完全同时执行,保证测试的正确性
        beginCount.countDown();

        try {
            //主线程等待结束信号量
            endCount.await();
            //观察秒杀结果是否正确
            System.out.println(SecKillImpl.inventory.get(commidityId1));
            System.out.println(SecKillImpl.inventory.get(commidityId2));
            System.out.println("error count" + CacheLockInterceptor.ERROR_COUNT);
            System.out.println("total cost " + (System.currentTimeMillis() - startTime));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

在正确的预想下,应该每个商品的库存都减少了500,在多次试验后,实际情况符合预想。如果不采用锁机制,会出现库存减少499,498的情况。 
这里采用了动态代理的方法,利用注解和反射机制得到分布式锁ID,进行加锁和释放锁操作。当然也可以直接在方法进行这些操作,采用动态代理也是为了能够将锁操作代码集中在代理中,便于维护。 
通常秒杀场景发生在web项目中,可以考虑利用spring的AOP特性将锁操作代码置于切面中,当然AOP本质上也是动态代理。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




开源的BI交互式多维报表设计和分析工具cboard

$
0
0

Introduction

An open BI Dashboard platform that supports interactive multi-dimensional report design and data analysis. Server side framework is Spring+MyBatis and front-end is based on AngularJS1 and Bootstrap. The whole architecture graphic is as below:

Screenshot

Near Realtime data refresh

Be attention, refresh level is cube level rather than whole dashboard

realtime_demo

Features Of CBoard

  • Simple and beautiful interface and layout
  • Lightweight architecture and concise source code, the entire project does not rely on any third-party multi-dimensional analysis tools
    • Front page style and layout of CBoard is based on  AdminLTE2
    • The chart plugin uses  ECharts
    • Javascript uses MVVM AngularJS 1.X framework
  • Interactive, drag-and-drop OLAP classisc report development experience
  • One dataset, multiple report widgets. Maximize reuse query resoult
  • Supports OLAP slice filter operation
  • Supports sort multiple columns/rows at the sametime
  • Global query cache, to avoid repeated query requests for data
  • Support common charts and cross tables
    • Columnar/Stacked vertical and horizontal bar and line mixed chart with dual axis view
    • Pie chart
    • Radar Chart
    • Sanky Chart
    • Funnel Chart
    • KPI Widget
    • Cross-tabulation (Support Drill Down/Roll Up)
    • China Map
    • Bubble Chart
  • Support JDBC data connection
  • Support Native Elasticsearch connection for version 1.x, 2.x, 5.x
  • Support Native Kylin connection for version 1.6
  • Support to connect one of the most popular open source multi-dimensional analysis of products Saiku2, and will be able to selectively create data and graphics
  • Cube level data refresh / realtime chart for quick query
  • Easy to implement your own DataProvider to connect any data source. Even for expensive commercial BI suite, it's not possible to connect all the NOSQL and NewSQL data source in the era of big data. Due to the schema of NOSQL are various, such as hash tables, arrays, trees, maps, etc., different people using the same NoSQL products designed Schema may vary widely. The user who most familiar with their storage schema are the user themselves. And it's easy to find a Java programmers to code a Class to connect their own data source. So we leave this part of work to the end user with an easy extended data connection framework in CBoard

Demo

Load Data from query or DataSet

case 0-switchdataload

Base Operation

case 1-

Switch Chart

case2

Type Calculated Measure

case 3-calculatedmeasures 1

Add Dashboard Parameters

case4-addboardparam

Use Parameters

case4-useparam

Access Control

RBAC (Role Based Access Control), easy admin and view your users' role and roles' access resource list in one page.

  • Grant roles to user by left Grant button.
  • Grant access resource to a role by right Grant button.
  • Resource can only be granted to role. A user can act as more than one roles.

image

Road Map

All tasks are listed in  Issue Page group by milestone. Also you can get our development status from  Project Page

数据可视化的开源方案: Superset vs Redash vs Metabase

$
0
0

人是视觉动物,要用数据把一个故事讲活,图表是必不可少的。如果你经常看到做数据分析同事,在SQL客户端里执行完查询,把结果复制/粘贴到Excel里再做成图表,那说明你的公司缺少一个可靠的数据可视化平台。数据可视化是Business Intelligence(简称BI)中的核心功能,有许多成熟的商用解决方案,如老牌的Tableau, Qilk,新生代的Looker,国内的FineBI等等。不过对于许多小公司来说,这些服务的License费用是一笔不小的开销,且有一种“杀鸡用牛刀”的感觉。那在开源软件如此发达的今天,在数据可视化方面,有什么靠谱的方案可以选择呢?今天给大家介绍三个比较知名的项目,分别是Superset, Redash和Metabase。前两个我都在产生环境中实际使用过,在本文中会重点介绍。Metabase我只是试玩了一下,但我觉得这是一个非常有想法的项目,所以也会和大家聊聊我对它的看法。

选择一个称手的工具,功能上能满足我的需求肯定是首要的。就先从功能需求讲起,我们的数据仓库用的是 Amazon Redshift(如果你没听过Redshift,就把它看作是为大数据优化过的PostgreSQL),所以大部分的实际用例都是要将一个SQL查询的结果可视化。我们所需的图表类型也就是常用的那几种,包括折线图,柱形图,饼图等。有了图表之后,接下去就是把相关的图表排版,生成报表页面(Dashboard)。从数据安全性角度,我不希望每个员工都能自由访问所有的Dashboard,所以每个Dashboard需要设置不同的访问级别。另外,我会看重它是否有REST API,能否通过API来创建与管理报表,这部分我们放在以后的文章中再讲。

除了满足功能性需求,易用性与文档在评判一个工具时也是非常重要的。谁不想要一个简单好用,文档清晰的产品呢?

下面我们就从功能性、易用性与文档等方面,来看看这三个开源项目的实际表现吧

Superset

Superset Demo

Superset最初是由Airbnb的数据团队开源的,目前已进入Apache Incubator,算是明星级的开源项目。老实讲,我也是被Airbnb与Apache两块金字招牌吸引才入了坑。目前公司绝大部分报表都在Superset上,大大小小有50个Dashboard,包含了近900个图表。在使用Superset之前我们用的是Looker(很不错的商用BI工具,可惜太贵),一年半前把Looker上所有的Dashboard迁移到Superset上,整个过程也很顺利。用了一年多,虽然在不少小地方有些不满意,但总体来说Superset很好地满足了公司现阶段在数据可视化与业务报表方面的需求。

当你把一个数据库连接到Superset上以后,你定义你要用的每一张表。Superset里表的定义不但包括字段,还需要定义指标(Metric)。指标是对字段的某种统计结果,比如字段上值的求和、平均值、最大值、最小值等。是不是有点糊涂了?但请回想一下,BI工具通常是用来做商业分析的。假想一个电商数据库,虽然在数据表我们存储每笔订单的交易额,但在商业分析时上我们不关心单笔交易,我们关心的可能是一个时间段内的总交额,或是平均交易额。当你画交易月报表时,你不会把每笔交易画在图上,而是把每天的总交易额用一个柱形在图上表示。这就是为什么Superset要引入“指标”这个概念。

对于数据分析人员来说,由于在Superset上他们不是直接写SQL,而是通过选择指标(Metric), 分组条件(Group)和过滤条件(Filter)来画图表,所以在构建复杂查询时可能会有些不适应。另一个难题是Superset里的表不支持join,如果一个图表里的数据要从多个数据表里取,那只能通过建视图来实现。Superset在0.11版本之后加入SQL Lab功能,支持从SQL查询结果直接生成图表。可惜,由于这个功能与Superset的核心设计格格不入,所以实现得比较差,没什么实用价值。

客观地讲,Superset里引入自己的表与指标的概念,在逻辑上是合理的,在统一各种异型的数据源时也是必要的。但实际操作中仍会让人觉得有些麻烦,不够直接了当。

Superset在可视化方面做得很出色,不但是开源领域中的佼佼者,也把很多商用BI工具甩在身后。在0.20版本中支持的图表类型已经达到了36种,而且在选择图表类型时,你可以看到每一种图表的缩略图,下面这张截图大家可以感受一下 
Superset Chart Types

Superset的另一个亮点是可以在多个时间维度上观察,因为商业分析中的很多问题都是与时间密切相关的。Superset有4种专门针对时间序列的图表,使用这些图表时,你需要指定一个字段为时间维度,之后就可以对时间维度做丰富的操作

  • 从不同时间粒度去查看你关心的指标(小时/日/周/月/季度/年)
  • 对时间序列做rolling average,比如看一个指标的7日平均线
  • 可以对时间序列做偏移,再做对比,比如把本周的销售业绩与上周同期放在一张图表中对比
  • 不在图表上显示指标的绝对值,而是显示它随着时间变化的增长速度

以上这些都是在数据分析中非常实用的功能。

说完优点,再说说Superset的槽点,最大的槽点是当图表与报表多了以后,管理不方便。这个问题其实很好解决,只要在图表和报表管理时,加上分组或是文件夹的概念就可以了,但至今未见类似的功能。现在公司900多个图表都在一个大列表下,虽然Superset支持Search, Filter或是Favorite,但查找起来还是太麻烦。

Superset的文档也比较糟糕,虽然在安装与快速入门方面提供了很完整的文档,但在具体功能的介绍方面文档严重缺失。就算有些功能有文档,文档的结构也很混乱,所以大部分功能只能自己去尝试,好在这个工具本身并不难用,自己去摸索各个功能也不太困难。

Redash

Redash Demo

如果说Superset是构建一个BI平台,那Redash目标就是更纯粹地做好数据查询结果的可视化。Redash支持很多种数据源,除了最常用的SQL数据库,也支持MongoDB, Elasticsearch, Google Spreadsheet甚至是一个JSON文件。Redash的官方文档里列出了它所支持的 所有数据源

它不需要像Superset那样在创建图表前先定义表和指标,而是可以非常直观地将一个SQL查询的结果可视化,这使得它上手很简易。或者说Redash仅仅实现了Superset中SQL Lab的功能,但却把这个功能做到了极致。

Redash有两个非常实用的功能,Query Snippet与Query Parameters。

Query Snippet很好地解决了查询片段的复用问题。做数据报表时经常要用到十分复杂的SQL语句,这些语句是肯定有一些片段是可以在多个Query中复用的。在Redash中我们可以将这些片段定义成Snippet,之后方便地复用。

Query Parameters可以为查询添加可定制参数,让这个图表变得更灵活。比如一个App的日活指标,我可能有时要按iOS/Android切分,有时要按地域切分,或是按新老用户切分。在Superset的Dashboard上我要做三个表图。Redash里我可以把Query的groupby做为一个参数,这样就可以在一张图上搞定。用的时候,运营人员可以图表上方的一个下拉框里选择切分的方式,非常直观好用。

Parameterized Query

Redash的Dashboard可以通过命名来进行分组,Dashboard的名字可以有一个前缀并以冒号结尾,前缀相同的Dashboard就会自动被分为一组。例如“Growth: Daily”,“Growth: Weekly”这两个Dashboard都会被分到“Growth”组下。

相比Superset,Redash在文档方面做得更好,除了快速入门教程以外,每一个功能模块都有文档且条理清晰。

当然Redash也有自己的不足之处,它的可视化种类比Superset逊色不少(不过其实也够用了)。另外,由于它只是纯粹地把数据查询结果可视化,所以也没有Superset里那些对时间维度上的聚合与对比的操作。

Metabase

Metabase Demo

由于我并没有在生产环境下使用过Metabase,只在自己本本上试用过这个工具。所以我只能说一下对它的第一印象。

刚开始用的就觉得这个工具的界面好漂亮,明显是经过UI设计师仔细调校过的。相对的,Superset与Redash一看就是程序员充当设计师的产物。

用了一会儿之后,我觉得Metabase与Superset虽然都想要打造一个完整的BI平台,但在理念上是不同的。Metabase非常注重非技术人员(如产品经理、市场运营人员)在使用这个工具时的体验,让他们能自由地探索数据,回答自己的问题。而在Superset或是Redash里,非技术人员基本上只能看预先建好的Dashboard,不懂SQL或是数据库结构的他们,很难自己去摸索。我非常喜欢Metabase的理念,它更接近一款成熟的商业化产品。当然要把这个理念变为现实是很有挑战的,目前我不知道在面临复杂的真实业务环境中,Metabase是否有想像中那样美好。

另外值得一提的是,Metabase的文档也是三个项目中写得最好最完整的,内容非常丰富。

将来若是有机会,我很愿意更深入地去体验这个产品。

小结

本文简单地介绍了三个开源的数据可视化工具Superset, Redash和Metabase,三者各有所长,我觉得并不存在绝对的最强者。对于刚刚开始搭建BI平台的公司,我相信它们都可以满足大部分报表与业务分析的需求。

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



解密 Airbnb 自助 BI 神器:Superset 颠覆 Tableau

$
0
0


概述

我非常认同前百度数据工程师、现 神策分析创始人桑老师最近谈到的数据分析三重境界:

  1. 统计计数

  2. 多维分析

  3. 机器学习

数据分析的统计计数和多维分析,我们通常称之为数据探索式分析,这个步骤旨在了解数据的特性,有助于我们进一步挖掘数据的价值。而基于我们对数据的理解,再引入机器学习的算法对数据做出预测就变得水到渠成了。

现实世界里,大部分的公司更多时间其实没有这个精力去搭建复杂的数据分析平台,面对快速变化的业务需求,很多数据工程师都把自己的青春埋葬在SQL里了。(其实我也是埋葬在SQL里的)

这几年,所谓的无埋点技术、自助式分析等等概念开始兴起,得益于数据领域的快速发展,国内外也涌现了大量基于数据分析平台的start-ups,而随着技术的发展,许多创业公司也是抱团取暖组成一个更大的团体。

考虑到国内数据安全性的问题,即使我们使用大厂比如百度的网站分析服务也很难保证数据安全性,私有化部署才是很多企业级解决方案的王道。

随着 Superset 被Airbnb的数据科学部门开源了,我看到的是有许多数据分析平台的创业公司或许要转变方向了,自助式分析将不再依赖于各大厂商!

什么是Superset

Superset的中文翻译是超集(过去叫做Caravel),而Superset其实是一个 自助式数据分析工具同时拥有 字段级别的权限控制系统,它的主要目标是简化我们的数据探索分析操作,解放BI产品经理和Web开发工程师,它的强大之处在于整个过程一气呵成,几乎不用片刻的等待即可完成现代化的BI报表开发,实现分析师BI产品赋能。

Superset 的特性

Superset通过让用户创建并且分享仪表盘的方式为数据分析人员提供一个快速的数据可视化功能。
在你用这种丰富的数据可视化方案来分析你的数据的同时,Superset还可以兼顾数据格式的拓展性、数据模型的高粒度保证、快速的复杂规则查询、兼容主流鉴权模式(数据库、OpenID、LDAP、OAuth或者基于Flask AppBuilder的REMOTE_USER)
通过一个定义字段、下拉聚合规则的简单的语法层操作就让我们可以将数据源在U上丰富地呈现。Superset还深度整合了Druid以保证我们在操作超大、实时数据的分片和切分都能行云流水。

数据库支持

Superset 是基于 Druid.io 设计的,但是又支持横向到像 SQLAlchemy 这样的常见Python ORM框架上面。

那Druid又是什么呢?

Druid是一个基于分布式的快速列式存储,也是一个为BI设计的开源数据存储查询工具。Druid提供了一种实时数据低延迟的插入、灵活的数据探索和快速数据聚合。现有的Druid已经可以支持扩展到TB级别的事件和PB级的数据了,Druid是BI应用的最佳搭档。

想必,你已经受够了Hive那个龟速查询,迫不及待想体验一下这种酣畅淋漓的快感了吧!

实战

既然,要行云流水,没有Docker是不行的,想要了解一下Docker可以参考之前的文章: 海纳百川 有容乃大:SparkR与Docker的机器学习实战

这里我默认你已经具备了使用Daocloud加速Docker的知识。

本地跑Docker

下载镜像:

docker pull index.tenxcloud.com/7harryprince/Superset

跑容器

docker run -p 8088:8088 -d index.tenxcloud.com/7harryprince/Superset

查询一下你的docekr ip

docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
default   -        virtualbox   Running   tcp://192.168.99.100:2376           v1.9.1    
dev       -        virtualbox   Saved                                         Unknown

这里可以看到我的默认Docker的IP是 192.168.99.100

所以需要在浏览器中访问 192.168.99.100:8088

这样我们又是三行代码搞定了一个大数据分析神器。

需要注意到,这个 Superset 容器里的默认鉴权配置是:

username: admin
password: Superset_admin

在线Demo

下面我提供了Superset的一个在线Demo:

http://52.33.104.157:8088/login/

下面是仪表盘的交互式分析页面:

我们可以导出JSON、CSV文件、直接得到SQL语句甚至分享页面链接。

下面是全球人口的一个分析仪表盘,感觉再改动一下就可以做信息图了,大数据分析也不在话下。

下面是我最喜欢的sankey chart:

架构

看到如此惊艳的数据产品,想必你也很想自己动手做一个吧!让我们一起看看整体的架构。

后端

整个项目的后端是基于Python的,用到了Flask、Pandas、SqlAlchemy。

  • Flask AppBuilder(鉴权、CRUD、规则)

  • Pandas(分析)

  • SqlAlchemy(数据库ORM)

此外,也关注到Superset的缓存机制值得我们学习:

  • 采用memcache和Redis作为缓存

  • 级联超时配置

  • UI具有时效性控制

  • 允许强制刷新

前端

自然前端是JS的天下,用到了npm、react、webpack,这意味着你可以在手机也可以流畅使用。

  • d3 (数据可视化)

  • nvd3.org(可重用图表)

局限性

  • Superset的可视化,目前只支持每次可视化一张表,对于多表join的情况还无能为力,需要依赖presto。

  • 依赖于数据库的快速响应,如果数据库本身太慢Superset也没什么办法

  • 语义层的封装还需要完善,因为druid原生只支持部分sql。

  • Superset 的使用依赖于数据工程师对底层数据OLAP的处理。

参考资料

推荐产品

作为分享主义者(sharism),本人所有互联网发布的图文均遵从CC版权,转载请保留作者信息并注明作者 Harry Zhu 的 FinanceR专栏: https://segmentfault.com/blog/harryprince,如果涉及源代码请注明GitHub地址:https://github.com/harryprince。微信号: harryzhustudio
商业使用请联系作者。

HTTP 连接管理进化论

$
0
0
连接管理是一个 HTTP 的关键话题:打开和保持连接在很大程度上影响着网站和 Web 应用程序的性能。在 HTTP/1.x 里有好些个模型: 短连接(short-lived connections), 持久连接(persistent connections), 和 HTTP 管道(HTTP pipelining)

HTTP 的传输协议主要依赖于 TCP 来提供从客户端到服务器端之间的连接。在早期,HTTP 使用一个简单的模型来处理这样的连接—— 短连接。这些连接的生命周期是短暂的:每发起一个请求时都会创建一个新的连接,并在收到应答时立即关闭。

这个简单的模型对性能有先天的限制: 打开每一个 TCP 连接都是相当耗费资源的操作。客户端和服务器端之间需要交换好些个消息。当请求发起时,网络延迟和带宽都会对性能造成影响。现代浏览器往往要发起很多次请求(十几个或者更多)才能拿到所需的完整信息,证明了这个早期模型的效率低下。

有两个新的模型在 HTTP/1.1 诞生了。首先是 长连接模型,它会保持连接去完成多次连续的请求,减少了不断重新打开连接的时间。然后是 HTTP Pipelining,它还要更先进一些,多个连续的请求甚至都不用等待立即返回就可以被发送,这样就减少了耗费在网络延迟上的时间。

image

要注意的一个重点是 HTTP 的连接管理适用于两个连续节点之间的连接,如 hop-by-hop,而不是 end-to-end。当模型用于从客户端到第一个代理服务器的连接和从代理服务器到目标服务器之间的连接时(或者任意中间代理)效果可能是不一样的。HTTP 协议头受不同连接模型的影响,比如 ConnectionKeep-Alive,就是 hop-by-hop协议头,它们的值是可以被中间节点修改的。

短连接(short-lived connections)

HTTP 最早期的模型,也是 HTTP/1.0 的默认模型,是短连接。每一个 HTTP 请求都由它自己独立的连接完成;这意味着发起每一个 HTTP 请求之前都会有一次 TCP 握手,而且是连续不断的。

TCP 协议握手本身就是耗费时间的,所以 TCP 可以保持更多的热连接来适应负载。短连接破坏了 TCP 具备的能力,新的冷连接降低了其性能。

这是 HTTP/1.0 的默认模型(如果没有指定 Connection 协议头,或者是值被设置为 close)。而在 HTTP/1.1 中,只有当 Connection 被设置为 close 时才会用到这个模型。

持久连接(persistent connections)

短连接有两个比较大的问题:创建新连接耗费的时间尤为明显,另外 TCP 连接的性能只有在该连接被使用一段时间后(热连接)才能得到改善。为了缓解这些问题, 持久连接(persistent connections)的概念便被设计出来了,甚至在 HTTP/1.1 之前。或者这被称之为一个 keep-alive连接。

HTTP/1.1(以及 HTTP/1.0 的各种增强版本)允许 HTTP 设备在事务处理结束之后将 TCP 连接保持在打开状态,以便为未来的 HTTP 请求重用现存的连接。 在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。非持久连接会在每个事务结束之后关闭。持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。

一个 持久连接 会保持一段时间,重复用于发送一系列请求,节省了新建 TCP 连接握手的时间,还可以利用 TCP 的性能增强能力。当然这个连接也不会一直保留着:连接在空闲一段时间后会被关闭(服务器可以使用 Keep-Alive 协议头来指定一个最小的连接保持时间)。

重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且, 已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输。

持久连接也还是有缺点的;就算是在空闲状态,它还是会消耗服务器资源,而且在重负载时,还有可能遭受 DoS attacks 攻击。这种场景下,可以使用非持久连接,即尽快关闭那些空闲的连接,也能对性能有所提升。

HTTP/1.0 里默认并不适用 持久连接。把 Connection设置成 close 以外的其它参数都可以让其保持 持久连接,通常会设置为 retry-after

在 HTTP/1.1 里,默认就是持久连接的,协议头都不用再去声明它(但我们还是会把它加上,万一某个时候因为某种原因要退回到 HTTP/1.0 呢)。

持久连接与并行连接配合使用可能是最高效的方式。现在,很多 Web 应用程序都会打开少量的并行连接,其中的每一个都是持久连接。

盲中继(blind relay)

那些不理解 Connection 首部,而且不知道在沿着转发链路将其发送出去之前,应该将该首部删除的代理。很多老的或简单的代理都 是 盲中继(blind relay),它们只是将字节从一个连接转发到另一个连接中去,不对 Connection 首部进行特殊的处理。

image

HTTP Pipelining

默认情况下,HTTP 请求是按顺序发出的。下一个请求只有在当前请求收到应答过后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。

流水线是在同一条长连接上发出连续的请求,而不用等待应答返回。这样可以避免连接延迟。理论上讲,性能还会因为两个 HTTP 请求有可能被打包到一个 TCP 消息包中而得到提升。就算 HTTP 请求不断的继续,尺寸会增加,但设置 TCP 的 最大分段大小 MSS (Maximum Segment Size)选项,任然足够包含一系列简单的请求。

并不是所有类型的 HTTP 请求都能用到流水线:只有 idempotent 方式,比如 GET、HEAD、PUT 和 DELETE 能够被安全的重试:如果有故障发生时,流水线的内容要能被轻易的重试。

今天,所有遵循 HTTP/1.1 的代理和服务器都应该支持流水线,虽然实际情况中还是有很多限制:一个很重要的原因是,任然没有现代浏览器去默认支持这个功能。

HTTP 流水线在现代浏览器中并不是默认被启用的:

  • Web 开发者并不能轻易的遇见和判断那些搞怪的 代理服务器的各种莫名其妙的行为。
  • 正确的实现流水线式复杂的:传输中的资源大小,多少有效的 往返时延 RTT(Round-Trip Time)会被用到,还有有效带宽,流水线带来的改善有多大的影响范围。不知道这些的话,重要的消息可能被延迟到不重要的消息后面。这个重要性的概念甚至会演变为影响到页面布局!因此 HTTP 流水线在大多数情况下带来的改善并不明显。
  • 流水线受制于 队头阻塞 Head-of-line blocking (HOL blocking)问题。

由于这些原因,流水线已经被更好的算法给代替,如 multiplexing,已经用在 HTTP/2。

HTTP/2 的长连接与多路复用(multiplexing)

长连接

在HTTP/2中,客户端向某个域名的服务器请求页面的过程中,只会创建一条TCP连接,即使这页面可能包含上百个资源。而之前的HTTP/1.x一般会创建6-8条TCP连接来请求这100多个资源。单一的连接应该是HTTP2的主要优势,单一的连接能减少TCP握手带来的时延(如果是建立在SSL/TLS上面,HTTP2能减少很多不必要的SSL握手,大家都知道SSL握手很慢)。

另外我们知道,TCP协议有个滑动窗口,有慢启动这回事,就是说每次建立新连接后,数据先是慢慢地传,然后滑动窗口慢慢变大,才能较高速度地传,这下倒好,这条连接的滑动窗口刚刚变大,http1.x就创个新连接传数据(这就好比人家HTTP2一直在高速上一直开着,你HTTP1.x是一辆公交车走走停停)。由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。

所以,HTTP2中用一条单一的长连接,避免了创建多个TCP连接带来的网络开销,提高了吞吐量。

帧(frame)

HTTP/2 是基于帧(frame)的协议。采用分帧是为了将重要信息都封装起来, 让协议的解析方可以轻松阅读、解析并还原信息。 帧(frame)是HTTP/2中数据传输的最小单位,因此帧不仅要细分表达HTTP/1.x中的各个部份,也优化了HTTP/1.x表达得不好的地方,同时还增加了HTTP/1.x表达不了的方式。

HTTP/2 帧结构如下:
image

流(Stream)

HTTP/2 规范对流(stream)的定义是: HTTP/2 连接上独立的、双向的帧序列交换。你可以将流看作在连接上的一系列帧,它们构成了单独的 HTTP 请求和响应。如果客户端想要发出请求,它会开启一个新的流。然后,服务器将在这个流上回复。这与 h1 的请求 / 响应流程类似,重要的区别在于,因为有分帧,所以多个请求和响应可以交错,而不会互相阻塞。流 ID(帧首部的第 6~9 字节)用来标识帧所属的流。

特点如下:

  • 一个HTTP/2连接可同时保持多个打开的流,任一端点交换帧
  • 流可被客户端或服务器单独或共享创建和使用
  • 流可被任一端关闭
  • 在流内发送和接收数据都要按照顺序
  • 流的标识符自然数表示,1~2^31-1区间,有创建流的终端分配
  • 流与流之间逻辑上是并行、独立存在

image

多路复用(multiplexing)

就是说在一个TCP连接上,我们可以向对方不断发送一个个的消息,这里每一个消息看成是一帧,而每一帧有个 stream identifier的字段标明这一帧属于哪个 ,然后在对方接收时,根据 stream identifier拼接每个 的所有帧组成一整块数据。我们把 HTTP/1.x 每个请求都当作一个 ,那么请求化成多个流,请求响应数据切成多个帧,不同流中的帧交错地发送给对方,这就是HTTP/2中的 多路复用

image

从上图我们可以留意到:

  • 不同的流在交错发送;
  • HEADERS 帧在 DATA 帧前面;
  • 流的ID都是奇数,说明是由客户端发起的,这是标准规定的,那么服务端发起的就是偶数了。

多路复用让HTTP连接变得很廉价,只需要创建一个新流即可,这不需要多少时间,而在 HTTP/1.x 时代却要经历三次握手时间或者队首阻塞等问题。而且创建新流默认是无限制的,也就是可以无限制的并行请求下载。不过,HTTP/2 还是提供了 SETTINGS_MAX_CONCURRENT_STREAMS字段在 SETTINGS 帧上设置,可以限制并发流数目,标准上建议不要低于 100 以保证性能。

实际的传输可能是这样的:
image

只看到 帧(Frame),没有 流(Stream)嘛。

需要抽象化一些,就好理解了:

  • 每一个帧可看做是一个学生,流可以认为是组(流标识符为帧的属性值),一个班级(一个连接)内学生被分为若干个小组,每一个小组分配不同的具体任务。
  • HTTP/1.x一次请求-响应,建立一个连接,用完关闭;每一个小组任务都需要建立一个班级,多个小组任务多个班级,1:1比例
  • HTTP/1.1 Pipeling 解决方式为,若干个小组任务排队串行化单线程处理,后面小组任务等待前面小组任务完成才能获得执行机会,一旦有任务处理超时等,后续任务只能被阻塞,毫无办法,也就是人们常说的线头阻塞
  • HTTP/2多个小组任务可同时并行(严格意义上是并发)在班级内执行。一旦某个小组任务耗时严重,但不会影响到其它小组任务正常执行
  • 针对一个班级资源维护要比多个班级资源维护经济多了,这也是多路复用出现的原因。

参考

重要!你的工龄与养老金有这种关系 不看后悔

$
0
0

重要!你的工龄与养老金有这种关系 不看后悔

北京时间 昨天09:58

t01deebbc93531bd589.jpg?size=500x332

从菜鸟到老手,从入职到退休,习惯了干一个月领一个月工资的人们,可能并不怎么重视“工龄”这个词。

但实际上,工龄与员工的待遇息息相关,除了在职期间的工资收入、带薪年假天数、医疗待遇等之外,还影响着员工退休之后的养老金水平。

特别是1992年以前参加工作的员工,你知道你的工龄与养老金有什么关系吗?

名词:什么是工龄?工龄有啥用?

工龄,即指了工从事脑力或体力劳动,以工资收入为其全部或主要生活资料来源的工作年限。每工作一年,就形成一年工龄。

t016e2b8349688a537c.jpg?size=500x304

工龄分为两种,一种是“连续工龄”,即累计工作的时间年限;另一种是“本企业工龄”。

职工工龄长短,是衡量一个职工为社会所作贡献大小的重要标志之一,也是确定职工享受劳保福利待遇的主要依据。在不少工资和津贴的处理上也与工龄密切相关。

在很多单位,尤其是机关、国企、事业单位等,工龄是跟工资和津贴等待遇直接相关的,工资里面还有工龄工资这一项,一些待遇也是工龄越长,标准越高。(工人日报)

提醒:“视同缴费年限”关系养老金

还有一个跟工龄相关的词汇叫做“视同缴费年限”,这个名词在计算将来的养老金待遇时可是至关重要的。1992年之前就参加工作的人,在退休时都会涉及一个视同缴费年限认定的事情。

对于这个视同缴费年限,很多人在工作期间并未重视,等到办理退休时却要为这个东西跑断腿,愁断肠。

因为职工究竟是从哪年到哪年在哪个单位工作,这并不是由自己说了算的,这个年限的认定一般要依靠职工档案中记载的工作经历来证明。那么,人事档案就显得尤为重要。

t01224a73b69521eb1c.jpg?size=500x375

以下几种情况,经过认定,都可作为“视同缴费年限”,并且可以与实际“缴费年限”合并计发养老保险金。

1、机关事业单位正式职工调入企业后,应参加企业职工基本养老保险,其原有的工作年限视同缴费年限;

2、复员退伍军人被招为合同制工人,且参加了基本养老保险的,其军龄按国家规定计算为连续工龄的年限,可视同缴费年限;

3、城镇下乡知识青年被招为合同制工人,且参加了基本养老保险的,下乡期间按国家规定计算为连续工龄的年限,可视同缴费年限。

需要提醒大家的是,有视同缴费年限的参保人员,一定要注意自己的档案问题,尤其是换工作或者失业期间,处理好自己的人事档案。

重视工龄,以下几点需牢记:

首先,要保存好有关工作经历的记录,如劳动合同。

其次,重视人事档案。这个档案一般都是由工作单位或者人才、职介等部门保存。但在工作有变动的时候,员工也需注意,有必要时可以跟单位的人事专员确认一下,以免将来遇到麻烦。

最后,别断社保。因为工作变动、灵活就业等原因,一些员工可能一段时间没有“工龄”。如果不想影响到社保缴费年限的计算,可以将档案存到人才或职介机构,以灵活就业人员的身份通过这些机构继续参加职工社保。等找到新工作时,再将社保关系转移过去。这样,社保就不会中断了。(工人日报)

注意:有这两种情况领不到养老金

大部分人都在缴纳社保,有些人交了15年了但是还是领不到养老金,这是为什么呢?

职工退休时的社保养老金由两部分组成:养老金=基础养老金+个人账户养老金。

其中:

个人账户养老金=个人账户储存额÷计发月数(50岁为195、55岁为170、60岁为139,不再统一是120了)

基础养老金=(全省上年度在岗职工月平均工资+本人指数化月平均缴费工资)÷2×缴费年限×1%=全省上年度在岗职工月平均工资(1+本人平均缴费指数)÷2×缴费年限×1%

(公式中:本人指数化月平均缴费工资=全省上年度在岗职工月平均工资×本人平均缴费指数)

这两种情况下领不到养老金:①未及时参加养老金领取资格核验,要等核验之后才发放之前没发放的养老金 ;②被判刑、拘役、劳动教育的退休人员暂时不发放养老金,释放后可以继续领取。

t01bf302446db8111ad.jpg?size=500x332

养老金领取所需材料:①养老金领取办理凭证;②本政策实施时年满60周岁人员:本人户口簿、身份证原件和复印件;《60周岁以上城乡居民社会养老保险登记表》;复员退伍军人还需填写《复员退伍军人军龄认定表》,并提供退伍证或复员退伍登记表等相关材料原件和复印件;③参保缴费年满60周岁人员:本人户口簿、身份证原件和复印件;填写《城乡居民社会养老保险待遇核定申请表》;复员退伍军人还需填写《复员退伍军人军龄认定表》,并提供退伍证或复员退伍登记表等相关材料原件和复印件。

病退人员年龄不到法定退休年龄,其个人账户累计存储额的计发月数要比正常退休的多;正常退休的基础养老金计算公式全国是一样的,但病退各省、市、区有一定区别,要到当地社保经办机构咨询,以当地政策为准。(中财网)

关切:养老金领多少年才能回本?

2017年8月,一篇文章《五险一金退休你能拿回多少?养老保险五年就能拿回来》引起网友关注。该文假设工资为6504元不变,从22岁缴到65岁,自65岁起,每月领5455元,只需4.1年(6504×8%×12×43÷5455 ÷12)就可领回养老金。

不过,该文其实玩了一个花招:只计算了养老金个人缴纳的部分(工资的8%),而不计算企业为员工缴纳的部分。眼尖的网友一眼看出了猫腻,“公司缴纳的部分就不是我创造的价值了?”

t013f73d87fd62b5e45.jpg?size=500x332

先来看下那篇文章说“5年就能回本”是怎么算出来的?

人一生要缴纳的五险一金费用可超百万元:以2017年上海职工的平均月工资(6504元)为例,计算一下每个月缴纳的五险一金和个税。

以2017年上海职工的平均月工资(6504元)为例,计算一下每个月缴纳的五险一金和个税。

假设你从22岁开始工作,到65岁退休,假定工资水平不变。

【个人】社会保险与公积金总额:

6504元X10.5%(社保)+6504元X7%(公积金)=个人缴费合计1138.2元

需缴纳个税:81.58元

【单位】社会保险与公积金总额:

6504元X31.5%(社保)+6504元X7%(公积金)=单位缴费合计:2504元

(工伤费率按行业类别及工伤事故发生率等因素实行浮动费率)

每个月需要缴纳的五险一金+个税是:

1138.2(五险一金)+81.58(个税) = 1219.78元

1年就要交:

1219.78 × 12 = 14637.36元

43年需要缴纳:

14637.36 × 43 = 62.9万元

这么算下来60多万也不是很多,但别忘了,这只是你自己缴纳的部分。其实公司每个月帮我们缴纳的五险一金比例和金额更高!

43年下来,要交:2504× 12 × 43 = 129.2万元

个人+单位合计:62.9万 + 129.2万 = 192.1万元

192万,就是我们43年工作中要缴纳的五险一金,这在大部分看来可不是一个小数目。

【公积金提取】

公积金可以用来买房或者全部取出来

那么第一笔钱可以拿这么多:

6504 × 14% × 12 × 43 = 约47万元

【养老保险领取】

退休时每月能领取的养老金数额是:

每月领取总额即基本养老金= 基础养老金 + 个人账户养老金 + 过渡性养老金

从目前大家每月能够领取的个人账户中养老金的标准来看,根据退休年龄的不同,“计发月数”也不一样,到65岁退休是按照101个计发月数来算。

举个例子:

老王2017年在上海办理退休手续,退休前的月平均缴费工资为6504元(由于退休员工平均月缴费工资指数时,包括视同缴费年限的缴费工资指数,为便于统计,我们假设月平均缴费工资指数恒定为1),老王退休时上年度上海全市职工月平均工资为6504元,且不需要计算过渡养老金。

基础养老金 = (参保人员办理申领基本养老金手续时上年度全市职工月平均工资 + 本人指数化月平均缴费工资)÷2 × (1%×缴费年限)=

(6504+6504)÷ 2 ×(1%*43)= 约2797元

个人养老账户储存额 =

6504× 8% × 12 × 43 = 约26.8万元

个人账户养老金 =

退休时个人账户储存额(个人缴纳总额)÷计发月份=约2658元

所以每个月可以到手养老金:

2797 + 2658= 5455元

如果这样的话,我们用4.1年(6504× 8% × 12 ×43 ÷ 5455 ÷ 12 )就可以将缴纳的养老保险费用拿回来。(央视财经)

(北京时间综合工人日报、中财网、央视财经)

三年做到百亿美金,华为瞄准了智能家居

$
0
0

今年是华为第三次参加全国最大的家电展会 AWE。作为全球最大的通讯厂商和最大的手机品牌之一,华为 2016 年第一次亮相就被冠以“狼来了”的名号,当然深层次的原因是华为的企业文化,以及它开疆拓土一观的野性和速度。

AWE 2018 华为展台

实际上,华为是为了 HiLink 而来。这是华为的智能家居战略。华为消费者业务首席战略官邵洋在上海接受了包括 PingWest 品玩在内的媒体采访,他透露了华为在该领域的中长期目标:

三年内 HiLink 家电设备销售额超过 100 亿美金。

对于今年的具体目标,华为消费者业务智能家庭产品总经理闪罡表示,2018 年 HiLink 设备的注册量达到 1000 万~1500 万台,而且 HiLink 设备的硬件激活率超过 70%,是目前国内最高的。

不同于小米的生态链投资、自有品牌模式,华为希望与合作伙伴建立开放的,有约束的体系,筛选家电家居行业内的领先品牌合作,从会场背景板上密密麻麻的合作品牌就能看出。

与亚马逊等国际领先的智能家居行业的巨头相比,华为对智能家居的理解有着明显的区别,智能手机依然担负着中枢的角色,音箱、电视、平板电脑,甚至智能手表都是辅助中心。

不过,“华为还是会做智能音箱的。”邵洋对 PingWest 品玩表示,“我们 3 月份会开八家店,然后八家店开完就会摆,摆的主要目的是让大家能够感受到所谓的智能家居是怎么回事。”

作为华为 HiLink 合作伙伴,科沃斯机器人国际事业部总裁钱程表示,今年科沃斯的扫地机器人将会进入超过 1000 家的华为线下门店中。

邵洋最后透露,今年是华为智能家居发展的关键一年,会有更多的惊喜,内部制定了 1+X 计划,让消费者可以从更多渠道购买到华为的 HiLink 智能家居产品,华为自有的销售渠道,加上国美、苏宁这样的家电卖场,中粮地产、鲁能这类房地产商,合作厂家自有的销售门店,以及天猫京东上的设立 HiLink 专区。

题图来自凤凰科技/刘正伟。后附对话原文,有删减。

受访人:

华为消费者业务首席战略官 邵洋

华为消费者业务智能家庭产品总经理 闪罡

华为消费者业务战略与业务发展部部长 姜羿山

邵洋:今天是家博会开场第一天,华为也会在家博会推出“百亿计划”。不知道大家对于HiLink是否熟悉?华为最早是做通讯,慢慢开始做手机业务,手机业务在华为占比也是越来越高,去年达到2/5,今年会达到一半。我们手机市场上也成为比较有竞争力的厂家,全球前三,在中国从销售量是第一,销售额还需要努力。我们在手机发展趋势上看,手机处于很有规律变化趋势,每12年为一个周期进行一个大变革。83年摩托拉发明了模拟手机,95年诺基亚发明数字手机,07年苹果发明智能手机,按照这个规律2019年或者是2020年就会出现下一代手机,对于我们来说我们作为希望不断向前的厂家,我想抓住这个大机遇成为手机领域强者。

发展趋势来看,手机发展历史是不断深入大家生活方方面面历史最早手机是通话,功能机是进入数字多媒体时代,成为MP3、照相机等。智能机时代全面互联网化,开始随时随地解决大家各种联网数字需求。下一阶段手机进入下一个时代,依然会像过去一样会全面进一步进入大家生活当中。下一代手机可以更多改变大家生活方面主要三个方面:

第一智能家居、第二汽车,第三机器人。

这三个领域会跟手机发生比较大交集,这是手机发展趋势看到这个方向。另外一方面家居市场也是一个历史悠久市场。90年代末微软提出了维纳斯计划,就开始提出了智能家居化,还有未来之家,也考虑家庭程序化、智能化、自动化这样的东西。经过十年来发展,智能家居实际上一直在兜兜转转但是没有很大的向前。大部分停留在少数的即刻使用,没有深入到千家万户。靠家店自己的力量,是很难完成真正产生生命的过程,而跨界正好是这样一个驱动力,家电市场和手机市场如果两个可以结合,我们会发现有三个会产生改变:

第一,使连接变得更加容易。大多数家店没有屏幕,没有屏幕情况下想联网本身是不太容易的事情。

第二,使控制变得更加容易。家电希望能够通过手机APP触控改变,或者是通过语音控制改变,或者通过视觉方式就可以改变。这样一些方式实际上以手机为核心这些智能化设备其实包括平板、穿戴、音响这样的设备,都会进一步强化控制能力。

第三,支持能力。我家电饭煲想学习一下陈太太煲汤,或者是了解一下有什么新的清洁扫地程序,这个知识能力可以通过手机或者是移动互联网赋能实现,这是我们进入智能家居主要的考虑。

2015年12月12日到现在已经是两年多,我觉得我们这条路上也在不断学习,也在和伙伴一起来探讨如何做得更好,现在来看已经到了这个时间点,我相信在今年加上明年智能家居就会成为现实。这个过程当中这是HiLink重要的特色,我们要打造真正开放、有约束的体系。开放意思是所有品牌都可以连入这个系统,这个系统当中可以互相联动,不能被品牌约束,比如我们看到过去智能家居有很多是被品牌约束。第二是一定要清晰自己的边界,我们做这个过程当中,我第一次参加AWE的时候,2016年第一次来AWE,AWE当时主持人介绍,现在有一个厂家在业界被誉为“一匹狼”来到会场,狼性十足,大家就担心华为是干什么来了,你是做电视机还是扫地机器人,你是做什么来的,你是看中这个市场吗?我觉得我们要约束自己,我们要做的是中间像连接一样的东西,比如路由器、手机、平板这样的东西,实际上是为家电做增持,而不是抢生意,这是我们和伙伴非常重要的约定,也是大家可以深入进行开放合作的前提。

华为 HiLink 今年会不断有一些惊喜带给大家。

提问:有没有数字化一些目标?2018年、2019年发展到什么规模?

邵洋:对于我们来说HiLink到底靠什么衡量,我们认为是两个东西,一个是设备连接数,一个是流水,设备连接数意思是有多少设备被连接到网络上和云上,这个流水要求是真正HiLink到底卖了多少钱。我们希望三年之内,把第三年支持HiLink家电设备销售额超过100亿美元,这是额上要求。关于量上要求我们还在探讨,但是拿100亿美金销售额来约束。

闪罡:今年我们开始智能家居和华为我们都在做,今年我们注册设备数是可以1000-1500万之间。

提问:之前阿里智能说他是全国销售最大的连接设备,我们今天有很多企业加入,这些企业会生产很多不同设备,对于用户来说选择是否造成困扰?我们怎么给用户创造一个更加良好购买体验或者是线下场景互通体验?

邵洋:这是非常重要的问题,第一,这是全链条,厂家要生产出支持这个方式的设备。第二,有销售渠道真正卖掉。比如说美国或者是苏宁或者是各种各样网点。第三,设备要被用户用起来,真正连接云,日活月活数据,所以这是分层的。我们考核一个是设备连接数,一个是销售额,就是真正厂家卖了多少设备。尤其第二个数据对于厂家来说非常重要的数据,他如果生产多少台设备最后发现开发多少型号,发现没有产生销量,但是就会对这个产生动摇。所以华为要把真正销售通路打开,这是第一步。这个销售通路我们有一个“1+X计划”,1这是一条线,从线上到线下然后到荣耀各种网络展示馆,都会做,所以华为全面开放自己的销售通路,来做拉动。同时X是四边,我们开发另外四个通路,第一个通路是家电卖场,像国美、苏宁,成立HiLink专区,这个设备以智能家居方式部署在卖场,大家可以直接体验。另外一个是房地产商,中粮地产、鲁能等等,这些房地产商一直希望,现在大家看到精装修变成了一种趋势,所以也确实可以创造出更大溢价。一平米多两千块钱没有问题,所以他们希望找到智能家居解决方案。目前可以看到最开放智能家居体系就是华为,这是X另外一边。再有一边,是厂家自己的门店,有很多门店会主力推销HiLink设备。还有一边,天猫和京东我们也会开设HiLink专区。通过“1+4”四条通路我们希望尽可能多HiLink设备送到用户家里。这当中有一个问题要解决,要让用户用起来,很可能我这个设备支持HiLink,但是可能用户不用,这个时候就要通过改进,我们在路由器改进、手机改进、APP改进,人工智能操控体验上改进。通过这样的方式使得这个设备进入家可以真正联网,真正月活日活,这是我们对于这个体系需要去做的工作。

提问:我有一个问题,相比亚马逊和谷歌,他们做智能家居互联,HiLink最大优势是在什么地方?它如何做到商用后短期内可以迅速获得大量家电厂商支持?

邵洋:这个问题很好,亚马逊是人工智能加智能家居的领头羊,这两个厂家和华为合作很多,谷歌目前并不是很强智能家居产品,这个大家可以看到谷歌方向,本身来说智能家居在这方面投入比较弱,这是这两个厂家基本情况。

这两个厂家面临一个问题,他们在中国部署开展云业务目前不行,所以这是限制他们一个重要原因。美国亚马逊已经进入超过两千万家庭还是不错的,在中国这一部分还是要花较大跨越才可以做到。所以这如果在中国销售家电厂家,无论中国还是国际需要考虑的问题。而华为在这方面是很强的。

另外讲一下行业上趋势,亚马逊实际上是非常聪明厂家,他选择音箱这样的入口,而且基本上是一个以前没有的入口,他是这样做的。音箱入口做完以后取得较达成共,也看到了较大天花板。相比声音更好输出手段是视觉,就是直接显示在你面前是更好输出手段,所以亚马逊现在有一个非常关键任务,希望占据手机入口。但是这个入口是非常难占据,本身来说这是大家自己的地,凭什么种你的种子。所以这就是未来我们说智能音箱第一步驱动智能家居市场,真正驱动家居市场第二步是旅行社。现在我们看到智能音箱有一个趋势,他推出了有屏设备,但是实际上手机是现成海量可以完成学习类操作的有屏设备。华为和他们区别一个是地理维度,一个是方向,我们更看重是视觉角度。

提问:小米今年公布他们激活数量八千万,你们跟他还是有一定差距你们这种开发性模式跟小米这种生态链模式,你们有什么优势?你们对于家电合作伙伴吸引他们来合作,是否有什么门槛和标准?

邵洋:小米八千万不是智能家居,包括手环、充电宝,我们说的1500万是家电设备。这个问题是很多人想我到底加入哪,我们看看实实在在结果,在小米的生态链当中它加入厂家是一些小厂家,这些厂家是一些新进入这个领域的厂家。而他们需要去依托小米品牌,依托小米一些光环效应帮助他们进入这个市场。而华为合作伙伴可以看到基本上是业界前三。在各个领域可以看到真正的各个巨头,做冰箱、洗衣机、空调、扫地机器人,各类家电真正成熟大品牌,都选择华为,而不可能选择小米。这里面一个重要原因在于,我们是一个自我约束的机制。而小米是控股投资的机制。你跟我合作前提是让我拥有你,只有在我拥有你情况下才可以帮助你。对于很多厂家来说,大家渴望得到手机的能力,但是不希望被占有,所以这是未来在这一端不断分化。我们以一种比较开放自由的方式,帮助大家实现智能化这一步,不会通过资本控制方式来做。

其实这里面也存在一个开放性问题,实际上我们每一个领域内都是开放多个同类品牌,小米一般来说每一个领域是控制一家公司来做这个事情。在这个领域内你只有一个选择,我买电饭煲就只有一个选择必须是这个品牌。要理解用户本身有他自己品牌选择的偏好,我可能喜欢国内品牌,可能喜欢进口品牌,这样一个方式上还是让用户有一个真正的自主权。包括我们跟房地产厂商开发商沟通,他们也是一样,他们希望给大家选择是用户可以自己定制的选择,我喜欢这样的油烟机,我喜欢这样的冰箱。

提问:对于合作伙伴选择这个门槛,你们是否会对公司大小规模有一定要求?

邵洋:是有要求,会从业界前三起步,你做这个领域,我希望这个伙伴已经是这个领域前三,本身大家对于HiLink能够作为品质标识,不会让大家感觉到一上来这个设备是不好用。所以会从前三起步,然后再逐步的在这个标准建立情况下会逐步扩大领域。每一个领域,我们现在大概有50个领域,每一个领域必须是前三。

姜羿山:我们对于前三要求并不是指只是体量大的伙伴,我们有很多不同类型的领域。因为现在家电这边细分领域越来越多,我们主要还是每一个子领域里面都会找一些排名TOP的伙伴。最核心还是要保证产品品质,从而最终保证用户体验。

提问:CES 之前我们公布了HiLink数据,想问一下虽然过去两个月左右,虽然有春节时间点在HiLink上面取得一些成绩能否分享?

闪罡:CES 中间数据还没有统计过来。进展主要是生态产品和解决方案上进展。一个是我们在CES 发布了路由这个产品,我们还会再做一次发布。这个时候会带有一些功能在里。智能家居融合也会放进去,华为在智能家居线上,我们跟纯粹互联网云的解决方案还是有一些不一样。纯粹云解决方案是放在云端,华为是做设备出身公司,我们更推崇是云的智能加上边缘计算智能方式在里面,所以华为路由器在整个家庭里面起比较关键作用。我们还有一个物由器概念,就是我们不仅仅是设备与设备连接,智能物语,物与物通话的时候,家里面需要一个协议转换,能够让很多智能设备不需要连接到云端就可以完成对话,这样时延会更低。华为路由Q2这个产品,整个户内时延不会超过10毫秒,对于快速控制,实时控制比上云端就响应快多了,上云端最高时延会达到3秒,对实时控制是有问题。对于海量家庭实时控制的时候,云端处理能力是来不及的,如果现在只有1000万人、2000万人,偶尔每天有几次云端这种需求的话,那云端响应是没有问题的,当我们大面积使用智能设备的时候,大面积家庭内部的调节,比如半小时之内户内光线变化是非常快,灯对于光线调节对于眼睛保护需要一个实时调节,如果一千万用户或者是更多用户在使用实时调节功能的时候,云端是响应不了,所以华为推崇云端智能加边缘计算解决方案。这一部分我们下一个月会正是在国内,把华为路由Q2推出来以后,把这个事情完成。

HiLink是开放式解决平台,合作伙伴选择是TOP3选择,但是协议标准上是开放的,任何厂家都可以使用这个标准规范。我们开放协议与协议之间转化的平台,这个平台下一个月会直接上线很多协议直接转化成功的。消费者也不是完全通过HiLink协议买东西,有可能在京东、天猫看到不错设备,可能买的时候不是HiLink,到家里能否用手机统一管理起来,我们也是可以的。这一部分是CS到这一段时间,我们解决方案快速发展一部分。

提问:我们现在看华为HiLink更多是基于手机构建,手机作为操作中心,但是我们更多是亚马逊是基于智能音箱,华为这一块是怎么考虑的,语音,还是手机?以及华为未来对于音箱有什么规划?

邵洋:我们认为离人越近和人待的越长的东西,就可能成为最高频被客户使用。所以手机优势一定大于其他设备。手机会成为智能家居主控中心,智能家居设备配制、管理、状态,人无论在任何地方。举一个例子我们有智能门锁,它有一个重要功能,我随时可以知道家里有几个人,我孩子是否回家,我人是在外面但是我希望家里情况,类似于这样的功能。所以手机任何地点任何时间这一个功能,这是不可绕过的中心,主中心建立起来以后驱动几个辅中心,音箱是辅中心,平板也是辅中心,华为平板音质上已经很强了,实际上要做成一个带频的音箱,将来很多东西就会语音加视觉一块升级。电视可能成为一个辅中心,手表可能成为一个辅中心。所以辅中心是多元化的,这不是因为我们做手机我们说这个话,我觉得这是人一个要求。就是要在任何地方要有这些体验,到具体场景要有增强性体验。所以我们会通过这样的一个以手机为中心驱动多个辅中心,同时提升能力。

提问:我们会做音箱吗?

邵洋:当然会,但并不是做了音箱就把智能家居这个事情搞起来了,不是这个问题。有没有音箱智能家居都会起来,只有音箱智能家居是不够的。

提问:我们会不会在未来在华为专卖店里放一些合作伙伴一些产品?

邵洋:我们3月份会开八家店,然后八家店开完就会摆,摆的主要目的是让大家能够感受到所谓的智能家居是怎么回事。你有了对这个了解,你可以通过线上线下拉动方式产生更多客户。

提问:现在更多用户对于智能家居安全性越来越关注,去年有很多这方面报道,关于HiLink华为安全性方面是否有自己的解决方案?

闪罡:安全这个事情是不断升级的,我们一直在这个地方在做研究,HiLink一开始就在做。我们用的系统之前是给大运营商一起做运营商级安全系统。我们是一个云加计算加室内方案,我们做了两级安全枷锁,从路由到云端枷锁,从路由到本地枷锁,又满足物与物互联互通。我们今年还会和两到三家包括一些组织联手一起加强云端建设,去年我们和中国最大一家合作,今年还会选择新加坡,我们会不断加强云端安全。

邵洋:安全智能家居是非常关键的,优先级甚至是高过手机。你家很多家电突然自动开始工作,对人惊吓性很高。所以这种机制一定要做得很强。家里很多设备是属于,比如水、气自己突然开始工作是有危险性,所以这个安全性是非常高。华为一直强调我们安全体系是“芯端云”,华为指纹各种攻破难度是最高,因为是做到芯片级。华为视频芯片占到中国市场90%,海康、大华他们用的网络摄像头都是华为视频芯片。华为今年就会做我们的NB-IOT芯片,华为是现在目前最强。我们还做WIFIIOT芯片,今年已经开始了。有了这些芯片以后,实际上会进一步,这个芯片是给家电厂商用的,能够更好的高效来联网,包括一些计算能力。这些东西会结合起来形成一个保障安全性。

提问:之前做智能家居联盟厂商很多,海尔、腾讯都做过也开过类似发布会,主要是在2014年左右,到现在其实都没有后文。华为如何避免这种发布会声音很大,后面声音很小就没有后文情况,华为有什么独家的比较强的地方?华为发起的联盟用HiLink情况下,如何保证旗下产品可以体验一致?我注意到很多买小米生态链产品他可能是买一个之后不断买,因为虽然是很封闭的,但是可能封闭好处就是体验会比较一致。主要是这两个问题。

邵洋:一个是持续性问题,持续性问题我先讲一下我在华为工作20年,我们在华为这么多年有一个特点,没有什么领域做着做着不做了,可以看一下华为历史,我们开始做固定后来做移动,开始做光盘,开始做云计算,没有不做的。我们做手机的时候,我以前碰到很多媒体朋友,他说你会不会做着做着不做了,你做手机是真的吗?你做PC和平板是真的吗?大家会觉得这个市场有难度,会不会做不下去了。我这么多年在华为实际看到,这次我们发布PC也是很好,大家相信我们是做PC,我们不是做着完。这是一方面,另外一方面为什么出现这种现象,因为这些领域,每一个领域越扎下去所需要攻克真正的问题就越来越多,这种不断深下去能力是华为所擅长的。如果这个是很浅的矿,谁打都一样,只有这个矿埋得比较深,我们觉得打下去很难别人再打。

刚才讲到我们在这个领域内,为什么会持续投入。因为我觉得从某种程度来说,我们是必须投入,我们必须投入原因是什么,手机一定会和家居产生联系,我们作为手机厂家不得不做这个事情,要想保证你家居在手机上有好的体验,必须做这个事情。与此同时伙伴也要求华为必须做这个事情,因为家电厂商也看到手机会和家电有这个联动。中国谁来做,三星来做吗?三星已经很小了,苹果做吗,苹果太慢了,华为是最合适的,华为在中国市场范围是23%,未来是到40%,大家都说手机下滑了,实际上没有下滑,而且更加集中。所以这种情况下,我觉得我们会持续的在这个地方加大投入,来帮助大家迈过这个坎儿。

你说到一致性问题,一致性问题是有利也有弊,利就是确实ID比较一致,包装比较一致,体验看起来一致。但是一致性实际上会使得厂家丧失自己创新的可能性。我们看原来手机三个操作系统,一个是苹果IOS很一致,但是没有办法别人选择不了,只有苹果可以自己用。剩下两个大家都可以用,一个是谷歌安卓,一个是微软windows,安卓实际上一开始是落后苹果很多,从APP数量来看大幅度落后,但是就依托于开放性,所有厂家都可以安卓上开发自己的东西,安卓迅速就在全球占到80%手机市场份额,苹果只有10%几,windows就是封闭的,安卓很多可以改的window很多是不可以改的,这个时候就很难做出价值来。所以我觉得我们会在手机上尽可能保证大家操控体验一致性。都是通过语音或者是APP来操控,但是我想还是要让厂家有自己的自主权。他的电饭煲有15种模式,他的有20种模式,这个是要让大家有自己的选择权利。

提问:我们看到有很多华为选择品牌是前三,华为是如何平衡我们HiLink和他们自身品牌关系?他们为什么加入我们最大原因是什么?我们有很多国际品牌加入我们HiLink联盟,因为是国际是全球化的进程,我们会不会把这个HiLink有一个国际化战略在里面?

邵洋:智能家居十多年前就开始了,海尔、美的等都有自己的标准,这种标准作为每一个厂家自己的体系是很难放弃的。所以我们尊重他们的标准,我们也支持大家这种标准。路由器上有协议转换,你的设备接入HiLink有困难,那没有关系你就用你的,我在路由器上帮助你转。另外他们这种协议存在一个最大障碍就是跨品牌,所有家电厂商他们同行是竞争的,我怎么可能用你的标准,你的标准万一不更新怎么办,所以这是为什么我们认为这种中立的标准是可以成功的。

国际化方面,智能家居是高度的云安全相关领域,短期内全面铺开并不现实,还是尽可能打磨一下,打磨成熟再往海外走比较好,一定会往海外走。前几年我们荣耀就是在国内打磨,打磨差不多就往海外走。所以这个是时间上的选择。

闪罡:技术方面回答一下,为什么很多合作伙伴愿意和华为做HiLink,华为HiLink优势在什么地方,一个是数据 CES 谈到过的,目前HiLink生态里面硬件激活率超过70%,目前是国内最高。我们在过去两年多时间里面,一个家电自行通路销售下去,很多家电是智能的,但是第一步连接都不能完成,华为本身是连接公司,我们这方面是有优势,华为手机整个市场占有率非常高。消费者买了一个硬件回去电接上了,过一段时间手机上弹出一个信息发现一个新的HiLink设备请问是否配制,这是解决了入门问题。这是回答很多人问题,就是体验一致性事情。我们讲体验是分三级,一个是购买体验开机体验,还有用机体验,用机体验上能否达到一致性,小米体验一致性主要是购机体验上,就是包装、ID风格上。华为规范包括HiLink协议,尤其是品类协议,是与各个大型家电厂商一起来协商制定,并不是华为自己拍脑袋想出来,因为我们不是专业家电厂家,他们是有更强创造力,我们购机体验上也做了升级,所以看上去虽然来自于不同厂家,但是给消费者视觉感观非常一致。包括开机体验。

我们谈到智能、音箱、手机入口,有一些场景是需要无线发展,也有可能是一个动作并不需要下多次指令。我们这么多年来讨论观影模式,智能家居APP里提供一个观影模式,能否打开电视一下的时候,我们所谓观影模式动作可以自动执行,可能你的窗帘会关上,可能灯是什么样,可能空调怎么样,我们举了很多这样的例子。这样的一些沟通方式,智能物语在家庭内部就可以操作,不涉及用户隐私,不需要这个动作上传云端,这个只需要通过一个消息打开,剩下可以你自己去做,这是HiLink打造的物语环境。当协议开放,当进入更多协议,这个智能场景可以自动生成,我们不希望智能场景是一个个手动编辑出来的。智能家居一定是不断往前走,华为不擅长一个家电领域这些功能。过去两年跟家电品牌合作发现他们非常棒和优秀,他们在自己领域里面耕耘几十年,他们有深厚积累,他们有很强创造能力。华为擅长是与消费者沟通,我们一直讲C2M,你如何把C的需求带给生产厂家,大家一起来制定新的智能性创新,再提供给消费者,这是华为帮助大家解决后面的一段路程我们可以给产业带来贡献。所以大家之间合作非常好,邵总谈到我们有1+X计划,他不是只卖某一类产品,他希望产品可以产生新的协议,以前是他只有自己的协议,但是他自己不做其他品类,我们有一些厂家原来有一个电视机后来由于家电竞争没有办法做了一些周边的,包括智能茶几一些新硬件,只能自己玩,但是没有人知道他这些设备,他未来销售是很困难,他希望可以跟我们选择生态领域优秀伙伴,大家能够在一起把这个协议通起来,他可以围绕他的东西。欧普可以围绕灯的场景,可以打造灯相关联的产品。新零售不是一个企业事情是一个产业事情,所以我觉得HiLink定下来是开放那一刻,就决定了这个路程往下走会受产业和消费者欢迎。

使用OAUTH2+Zuul实现认证和授权 GitHub - wiselyman/uaa-zuul:

$
0
0

Spring Cloud需要使用 OAUTH2来实现多个微服务的统一认证授权,通过向 OAUTH服务发送某个类型的 grant type进行集中认证和授权,从而获得 access_token,而这个token是受其他微服务信任的,我们在后续的访问可以通过 access_token来进行,从而实现了微服务的统一认证授权。

本示例提供了四大部分:

  • discovery-service:服务注册和发现的基本模块
  • auth-server:OAUTH2认证授权中心
  • order-service:普通微服务,用来验证认证和授权
  • api-gateway:边界网关(所有微服务都在它之后)

OAUTH2中的角色:

  • Resource Server:被授权访问的资源
  • Authotization Server:OAUTH2认证授权中心
  • Resource Owner: 用户
  • Client:使用API的客户端(如Android 、IOS、web app)

Grant Type:

  • Authorization Code:用在服务端应用之间
  • Implicit:用在移动app或者web app(这些app是在用户的设备上的,如在手机上调起微信来进行认证授权)
  • Resource Owner Password Credentials(password):应用直接都是受信任的(都是由一家公司开发的,本例子使用)
  • Client Credentials:用在应用API访问。

1.基础环境

使用 Postgres作为账户存储, Redis作为 Token存储,使用 docker-compose在服务器上启动 PostgresRedis

Redis:
  image: sameersbn/redis:latest
  ports:
    - "6379:6379"
  volumes:
    - /srv/docker/redis:/var/lib/redis:Z
  restart: always

PostgreSQL:
  restart: always
  image: sameersbn/postgresql:9.6-2
  ports:
    - "5432:5432"
  environment:
    - DEBUG=false

    - DB_USER=wang
    - DB_PASS=yunfei
    - DB_NAME=order
  volumes:
    - /srv/docker/postgresql:/var/lib/postgresql:Z

2.auth-server

2.1 OAuth2服务配置

Redis用来存储 token,服务重启后,无需重新获取 token.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisConnectionFactory connectionFactory;


    @Bean
    public RedisTokenStore tokenStore() {
        return new RedisTokenStore(connectionFactory);
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)//若无,refresh_token会有UserDetailsService is required错误
                .tokenStore(tokenStore());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("android")
                .scopes("xx") //此处的scopes是无用的,可以随意设置
                .secret("android")
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
            .and()
                .withClient("webapp")
                .scopes("xx")
                .authorizedGrantTypes("implicit");
    }
}

2.2 Resource服务配置

auth-server提供user信息,所以 auth-server也是一个 Resource Server

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
                .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .httpBasic();
    }
}
@RestController
public class UserController {

    @GetMapping("/user")
    public Principal user(Principal user){
        return user;
    }
}

2.3 安全配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {



    @Bean
    public UserDetailsService userDetailsService(){
        return new DomainUserDetailsService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }

    //不定义没有password grant_type
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    


}

2.4 权限设计

采用 用户(SysUser)<-> 角色(SysRole)<-> 权限(SysAuthotity)设置,彼此之间的关系是 多对多。通过 DomainUserDetailsService 加载用户和权限。

2.5 配置

spring:
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:dev}
  application:
      name: auth-server

  jpa:
    open-in-view: true
    database: POSTGRESQL
    show-sql: true
    hibernate:
      ddl-auto: update
  datasource:
    platform: postgres
    url: jdbc:postgresql://192.168.1.140:5432/auth
    username: wang
    password: yunfei
    driver-class-name: org.postgresql.Driver
  redis:
    host: 192.168.1.140

server:
  port: 9999


eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/



logging.level.org.springframework.security: DEBUG

logging.leve.org.springframework: DEBUG

##很重要
security:
  oauth2:
    resource:
      filter-order: 3

2.6 测试数据

data.sql里初始化了两个用户 admin-> ROLE_ADMIN-> query_demo, wyf-> ROLE_USER

3.order-service

3.1 Resource服务配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig  extends ResourceServerConfigurerAdapter{

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
                .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .httpBasic();
    }
}

3.2 用户信息配置

order-service是一个简单的微服务,使用 auth-server进行认证授权,在它的配置文件指定用户信息在 auth-server的地址即可:

security:
  oauth2:
    resource:
      id: order-service
      user-info-uri: http://localhost:8080/uaa/user
      prefer-token-info: false

3.3 权限测试控制器

具备 authorityquery-demo的才能访问,即为 admin用户

@RestController
public class DemoController {
    @GetMapping("/demo")
    @PreAuthorize("hasAuthority('query-demo')")
    public String getDemo(){
        return "good";
    }
}

4 api-gateway

api-gateway在本例中有2个作用:

  • 本身作为一个client,使用 implicit

  • 作为外部app访问的方向代理

4.1 关闭csrf并开启Oauth2 client支持

@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();

    }

}

4.2 配置

zuul:
  routes:
    uaa:
      path: /uaa/**
      sensitiveHeaders:
      serviceId: auth-server
    order:
      path: /order/**
      sensitiveHeaders:
      serviceId: order-service
  add-proxy-headers: true

security:
  oauth2:
    client:
      access-token-uri: http://localhost:8080/uaa/oauth/token
      user-authorization-uri: http://localhost:8080/uaa/oauth/authorize
      client-id: webapp
    resource:
      user-info-uri: http://localhost:8080/uaa/user
      prefer-token-info: false

5 演示

5.1 客户端调用

使用 Postmanhttp://localhost:8080/uaa/oauth/token发送请求获得 access_token(admin用户的如 7f9b54d4-fd25-4a2c-a848-ddf8f119230b)

  • admin用户




  • wyf用户



5.2 api-gateway中的webapp调用

暂时没有做测试,下次补充。

6 注销oauth2

6.1 增加自定义注销 Endpoint

所谓注销只需将 access_tokenrefresh_token失效即可,我们模仿 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint写一个使 access_tokenrefresh_token失效的 Endpoint:

@FrameworkEndpoint
public class RevokeTokenEndpoint {

    @Autowired
    @Qualifier("consumerTokenServices")
    ConsumerTokenServices consumerTokenServices;

    @RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
    @ResponseBody
    public String revokeToken(String access_token) {
        if (consumerTokenServices.revokeToken(access_token)){
            return "注销成功";
        }else{
            return "注销失败";
        }
    }
}

6.2 注销请求方式


你有什么相见恨晚的知识想推荐给年轻人? - 知乎

$
0
0
作者:王盐
链接:https://www.zhihu.com/question/22238159/answer/46072753
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  1. 对普通人来说,金钱非常重要。
  2. 99%的人适合马斯洛需求层次理论,所以弄清楚对方的需求很重要,你和重名声的人谈利益、重利的人谈感情、重情的人谈利益,就不会有好的结果。关键在于对症下药。
  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. 迷茫不知道自己想要什么的时候,不妨从想象十年后你一天的生活开始:想象几种不同的人生轨迹,比如留在大城市/回到家乡、成为医生/导演、一夜暴富/背负贷款,在截然不同的人生走向中,想象你那时每天的生活细节,最终找出这几个不同故事的共性,那往往就是你希望的生活方式。



人际关系


  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. 在双方决定结婚之前,两个人应该开诚布公的谈谈,确定一些共同生活后有可能因为价值观产生的分歧。(著名的《纽约时报》婚前15问,是个很好的范本)
  32. 婚前可以约定,如果婚后一方出轨,另一方以此为由想要离婚的,共同财产一人一半,对外则宣称因感情变淡而和平分手,好聚好散。别觉得这似乎在宽恕出轨的一方,大多数人在结婚时,都坚信自己不会出轨,而一门心思想要约束对方。


职场类


  1. 作为新人刚入职时,要主动对带你的人示好,因为他是潜在的盟友或敌人,更重要的是他了解你。
  2. 无论你有多大的野心多高的才华,都要学会尊重并服从你的上司,在上司面前你只有建议权没有决定权,这是基本的职业素养。
  3. 真正意义上的团队合作,是每个团队成员都做好自己该做的那个部分,而不是对着别人的部分指手画脚。
  4. 学会服从是学习团队合作的开始。
  5. 想要有个良好的办公室人际关系,就给同事们多买点零食。
  6. 不要和同事八卦自己的私生活,偶尔聊聊增进感情,多说无益。
  7. 写邮件重要的意义是留下记录,而记录意味着证据。
  8. 写文档和邮件时,注意排版,简单清晰易于阅读。
  9. 一定要学会及时反馈,不要等着别人追问你工作进度,你自己不说没人知道你在做什么,这也是区分职场新人和资深人士重要的区别之一。
  10. 工作中遇到阻碍和困难,不要产生挫败感,因为这些阻碍和困难本身就是工作的一部分。
  11. 工作中没有真正的对事不对人,先做人在做事。
  12. 尽量不要产生办公室恋情,尤其是和前台或者人事。
  13. 每天开始工作前,给自己列个任务清单,然后一项项的完成掉,今日任务没完成不下班。
  14. 番茄工作法是简单好用的时间管理方法,推荐学习。
  15. 每次离开工位时,随手将电脑、手机锁屏。
  16. 常用软件尽量熟悉其快捷键,能明显提高工作效率。
  17. 如果公司配的办公电脑配置很低,可以考虑用自己的电脑办公,这看起来是额外的付出,但节省下来的时间都是你自己的。
  18. 每次和别人沟通之前,要先思考一下沟通的目的是什么,想要达到什么样的结果,如果对方反对该怎么办。
  19. 和人沟通时,你自己表达清楚,不代表对方理解清楚,所以如果你不确定对方是否真的理解,可以让他把你表达的意思换个方式讲给你听。
  20. 笔非常容易丢,平时多备几支。
  21. 公司对你的重视程度会直接反映在你的薪资上,无论话说的多好听,在对方心目中,你就只值这么多钱。
  22. 绝大多数情况下,入职时薪水高的涨薪速度与程度都要高于入职时薪水低的。
  23. 发文档类附件时,最好附带一个PDF格式的文件,这样可以保证在任何系统的任何软件任何版本中,文档都能保持你希望给大家看到的样子,而不是一堆乱码。
  24. 统计数据时,要注明数据的统计时间。
  25. 使用Excel要学会数据透视表和函数vlookup,简单实用,可以帮你节省大量的时间。
  26. 报告中要杜绝“差不多”、“大概”、“好像”这样的词汇。
  27. 需要打印的文档,window系统下字体选择微软雅黑。
  28. 如果是用来演示的PPT文件,又不想别人修改内容,可将文件扩展名改为.ppsx,这样双击打开就直接开始演示了。

有买房打算的年轻人,这些事得先想清楚

$
0
0

在上海,我遇到很多上海小孩名下是无房的,因为“恨铁不成钢”、“小孩自己不赚钱”、“在外被骗、背债”,所以,父母们的想法不如先放在自己名下,待日后小孩成熟以后,再做打算。这种情况,父母往往会找一个专职律师顾问—— “财富管理律师”

所以我处理过很多小年轻离婚案件的时候,双方名下基本上没什么财产,两条光杆儿,案子也简单好办。

除了以上情况,就是很多外地打拼的年轻人,没有上海户口,努力工作,加上父母的积蓄,凑够首付,和自己的另一半在上海买一套属于自己的房子。

上海限购政策:
1.非本市籍居民家庭(限购 1 套)
(1)在上海市范围内家庭名下无任何住宅类房屋
(2)符合网签日期前 63 个月内累计正常缴满 60 个月的社会保险或个人所得税
2.本市户籍
(1)已婚家庭,限购 2 套
(2)单身无房,限购 1 套

北上广深房子动则千万,即便年薪百万,还是要慎重。

如果是婚前,是否买房,可以参考我另一个回答 吴声威:女生婚前是否应该买房?

如果是小情侣们婚后一起买房,给以下几点法律方面的意见吧。

1.买房登记在谁名下?

既然选择结婚,我建议是登记在双方名下,选择相互信任。若登记在双方名下且没有写明各占多少份额,则属于一人一半。一般来说,签订购房合同(和开发商签订商品房购房合同,和房东签订二手房购买合同)的乙方是夫妻双方的名字,不动产权证才可以登记在双方名下,如果是一个人的名字,那就只能登记在一个人名下。

2.买新房子注意什么?

【烂尾楼】由于我国是商品房预售制度,在很多小城市,房子刚正负零的时候开发商就可以拿到预售许可证,开始对外销售了,所以新房子最大的风险就在于,开发商资金链断裂,发生烂尾楼风险,一旦遇到烂尾楼的情况,情形会很焦灼,在未来持续很长一段时间,业主会处在维权和等待中。

为了避免买到烂尾楼,由于大部分购房人无法深入做尽职调查,所以可以选择大的开发商,风险相对要小一些。

【沉降】大部分购房人都喜欢河景房,海景房。我的老师 @徐斌 之前在上海处理过一个案件,“莲花河畔景苑”楼倒倒案件,该楼盘就是建在河边,由于当时挖地基的泥土直接堆放在工程旁,导致泥土流动,桩机倾斜,发生整座楼倒塌的事故,所幸小区业主尚未入住,没有发生人员伤亡。

总的来说,就是由于海边河边土质比较疏松,常年土质流动,如果房屋地基工程没有做好,可能会发生严重的沉降,导致整座楼的倾斜,甚至倒塌的危险,所以我们购房人可以尽量不要选择购买河景、海景房。

【销售欺诈】很多人都会相信开发商在销售时候的所谓“学区房”、“赠送面积”,这些都只是开发商的一个销售手段,到了交房的时候都无法兑现,或者此学区非彼学区,因为该小区是否学区房是无法提前预知的,是每年由教委重划片区。

而所谓的“赠送面积”其实就是开发商的一个偷面积惯用的伎俩,通过改天井、封闭阳台、加隔层修改规划等方式来将非法面积赠送给购房人,而实际上购房人拿到的面积都是违章建筑,严重些可能会被住房保障部门限制交易,城管部门上门直接拆除。而销售欺诈的诉讼官司尤为难打,毕竟大部分开发商都是口头承诺的方式,且大部分宣传手册都无法被认定为购房合同的一部分,所以不涉及到开发商违约,诉讼会尤为艰难。

【车位】现在生活水平提高,家家户户都会买车,有车了都会考虑车位的问题。而车位到底是租还是买呢?

我在这里给大家一个思路,我们首先要鉴别车位的性质,确定是属于产权车位,还是人防工程车位,还是业主共有的绿化车位,因为性质不同,开发商可以处理车位的方式也不一样。

  • 产权车位,开发商是可以向业主出售的;
  • 人防车位,实际上是只能出租,大部分开发商会采取出售“20 年使用权”的方式将人防车位出售给购房人,为什么是 20 年,因为我国《合同法》规定,租赁合同的最长期限为 20 年;
  • 绿化改建的车位,本身就不能出售,且出租的收益应当归所有业主共有。

【签订购房合同】由于在我国主要还是开发商占优势地位,近两年房价上涨,开发商多少采取各种各样的销售策略,来假装房源不够,通过摇号、抽签等方式刺激购房人抢购,而在这种方式下的购房,大部分业主都是不会认真细看购房合同,均在销售经理的恍惚下,签下了合同。

双方一旦发生纠纷,又追悔莫及。

这里提几点,我作为律师购房时去签订购房合同,大部分条款我也是不看的,但是有几个地方需要注意:土地使用年限(50 年还是 70 年),房屋性质(住宅还是办公),购房款的总额,购房款的支付方式(全款、按揭),违约责任的多少,办理产证的时间。

如果有耐心,可以把补充协议的条款细看,因为合同正文属于房管局的格式文本,开发商多半无法做什么手脚,全凭在补充协议时做文章,所以给业主挖坑也多半在补充协议上,补充协议对哪一块约定的越多,以后交房的时候房子也更多的会在哪一块出现问题。

再补充一点,如果业主采取按揭贷款的方式购房,由于按揭贷款的不确定性,所以不能在协议中约定放款时间,如果对此约定过于明确,业主将承担高概率违约的风险。

3.买旧的二手房要注意什么?

这一块,我有专门写过一片文章,有兴趣的朋友可以看看 二手房交易你需要了解什么?

简述一下,主要还是要考察: 房东,房子,购房合同

房东是否有诸多债务,是否征信有问题,是否涉诉。

房子的好坏,由于旧房子多少都住了些年,肉眼是可以观察出来房子质量的好坏,是否有裂缝,是否漏水,我再给几点其他方面的小建议。

看这个小区的物业,看小区是否有业委会,看小区的电梯维护的如何,看小区保安是否让你进去,看小区的井盖是否按照一定的次序排列,因为这些都会涉及到这个小区未来的升值空间。好的小区,一般物业管理较好,小区会成立业委会,电梯维护较好,保安在一般情况下不允许外人进入,井盖牵涉到小区给排水。这些方面可以反应一个小区品质的好坏,如果这些方面做不好,小区在未来某个时间节点,会比同地段其他小区的房价差一个档次,当然住起来,也会烦恼多多。

购房合同,作为购房人,主要还是注意自己需要履行的义务,购房款的多少,如何支付,支付的时间节点。同样的,如果采取按揭的方式,则不能约定放款的时间,因为放款时间的不定,容易产生己方违约的情形。

最后,祝大家都能买到个好房子!

树莓派做路由器 2018.03 (Part 3) Google 硬件(Home/Wear/Chromecast...)上网配置

$
0
0

前言:写于 2018.03,分成三篇blog,介绍为了在家里使用 Google Home Mini 而进行的一系列努力;
本文为第三部分,进阶路由配置,github 官方页面上相应的 iptables 配置已经介绍的很全面了;本文只描述几个特定的坑
第一部分: 树莓派做路由器 2018.03 (Part 1) 最基础的Hotspot配置知识
第二部分: 树莓派做路由器 2018.03 (Part 2) hostapd 和 RTL8188CUS 网卡,如何在 Raspbian Stretch/Kernel 4.9.x 下工作?

从源码编译 https://github.com/shadowsocks/shadowsocks-libev#linuxhttps://github.com/shadowsocks/simple-obfs非常简单,毕竟树莓派系统是一个很完整的 Debian Stretch 变种,首先安装依赖的包,然后从源码 clone 下来按说明编译和配置即可

  • sudo apt install --no-install-recommends gettext build-essential autoconf libtool libpcre3-dev asciidoc xmlto libev-dev libc-ares-dev automake libmbedtls-dev libsodium-dev
  • 。。。略去官网里的步骤

因为这个树莓派目的很简单,只是为了服务 Google Home Mini,不需要复杂的国内国际流量区分,所有流量统统全局路由出去;为了避免投毒只是简单的自己搭了一个 DNS 转发,然后在 dnsmasq 里把 第一部分所描述的 "dhcp-option=option:dns-server,114.114.114.114" 改成自己的 IP 即可

  • ss-tunnel -c /etc/shadowsocks-libev/crown2.json -b 192.168.x.1 -l 53 -L 8.8.8.8:53 -u -f /var/run/ss-tunnel.pid
  • 如果是在 rc.local 里启动 ss-tunnel 可能出现启动不了的情况。参考 https://www.v2ex.com/t/348171加上 "sleep 15 && " 解决问题

但是后来发现这样做是不够的,因为 Google 的硬件不理会 DHCP 的 DNS-Server 配置,固执的使用自己的 DNS Server 进行解析

附录:在解决问题中发现的其它可能有帮助的链接

Topic: 

OAuth 2.0 开放授权的那些事儿

$
0
0

OAuth 2.0协议是一种三方授权协议,目前大部分的第三方登录与开放授权都是基于该协议的标准或改进实现。 OAuth 1.0版本于 2007 年发布,2.0 版本则在 2011 年发布,其中 2.0 版本取消了所有 token 的加密过程,并简化了授权流程,但因强制使用 HTTPS 协议,被认为安全性高于之前的版本。

项目地址: https://github.com/procyon-lo...

一. 小场景带你感受 OAuth 2.0 的交互过程

为了让你对 OAuth 2.0 协议有一个整体上的感知,这里先设置一个小场景对协议的交互过程进行模拟。话说阿冰在花果山上有几亩果园,种了各种各样的水果,有苹果、荔枝、西瓜等等,并由管理员老王进行看管。

夏天到了,果园里的的水果涨势喜人,阿冰的好朋友小桂子想去果园摘一些西瓜和荔枝解解馋,于是小桂子提着果篮吭哧吭哧就跑到了花果山,结果被管理员老王一把拦住,呵斥道:“要摘水果,必须经过阿冰的同意,快出示相关凭证”,小桂子当然没有,还纳闷去哪搞什么鬼凭证。这时候老王拎着小桂子来到了一个茅草房前,只见上面写着“花果山街道办事处”。老王告诉小桂子这里可以开具凭证。

小桂子来到柜台前,业务人员询问了其姓名,并要求出示身份证件。核实身份之后,业务人员询问小桂子要去谁的果园,采摘什么水果?小桂子如实答复,不一会只见业务人员打印出了一张凭证,并将其丢入一个“时光通道”,凭证上写着:

小桂子请求在您的果园采摘以下水果:
- 采摘您的西瓜 2 个
- 采摘您的荔枝 3 斤

通道那头的阿冰收到凭证之后盖上自己的印章以示同意,然后将其投回了“时光通道”。最终小桂子拿到了经过阿冰授权的凭证,跳着奔向果园。管理员老王验证了小桂子出示的凭证,并摘了 2 个西瓜和 3 斤荔枝交到了小桂子手上。

一个星期后,小桂子嘴又馋了,拿着上次的凭证直奔花果山,到了果园门口又被老王拦住了,老王说:“你这凭证已经过期了,必须再次请求阿冰授权”。小桂子满脸委屈,嘟囔着:“不就想吃俩西瓜嘛,怎么就管的这么严”。

小编不才,实在编不下去了,就这样结束吧。。。

在这个故事中,凭证限制了小桂子是否可以获得水果,以及获得哪些水果,每种水果多少斤。此外凭证还具备生命周期,超出凭证范围的请求都会被老王拒绝。通过凭证,小桂子可以获取到自己想要的水果,阿冰也不需要亲临花果山,在做好果园管理的同时,又不影响阿冰的生活。

回到正题,对于 OAuth 2.0 协议(以下简称 OAuth 协议),我相信大部分读者都有所接触,最常见的就是使用微信登录第三方应用。的确,OAuth 协议被广泛应用于第三方授权登录中,借助第三方登录可以让用户免于再次注册之苦,支持第三方登录也对这些网站、APP 起到了积极的作用,免去了复杂的注册过程,用户体验更佳。这样在提高留存率的同时,也更加易于收集用户的一些非敏感信息等,另外还可以借助一些社交类的第三方帐号进行站点推广。

二. OAuth 2.0 中的基本概念与授权流程

作为第三方登录服务提供方,其核心矛盾点是 既要让用户在对接服务的 APP 上完成登录,同时还不能让该 APP 拿到用户的密码凭证。解决这一矛盾的利器就是 token(中文译为令牌),而 OAuth 协议的最终目的就是给第三方应用下发 token,它记录了用户的登录或授权状态。通过将 token 下发给第三方应用,既能让 APP 登录并拿到用户许可的数据,也可以将用户的密码凭证牢牢拽在服务自己手里。

上面的论述可能侧重了第三方登录,实际上登录只是一个授权的过程,OAuth 2.0 协设计的目的在于开放授权。对于一个应用,其最终目的还是在于拿到用户存储在资源服务器上的用户数据,所以登录授权还只是第一步,后续 APP 还需要携带 token 去资源服务器请求用户数据,这个时候是一个鉴权的过程,OAuth 协议的主要目的在于授权,至于鉴权,实现上主要是还是对请求传递过来的 token 进行解析和验证,这一块相对要简单一些,所以本文主要讲解 OAuth 授权的过程。

2.1 OAuth 2.0 定义的 4 种角色

  • 资源所有者(resource owner)

受保护资源所属的实体,比如资源的所有人,下文的用户即为资源所有者。

  • 资源服务器(resource server)

托管受保护资源的服务器,能够响应持有访问令牌的资源访问请求,可以与授权服务器是同一台服务器,也可以分开。

  • 客户端(client)

客户端是 OAuth 服务的接入方,其目的是希望请求用户存储在资源服务器上的受保护资源。

  • 授权服务器(authorization server)

授权服务器的主要职责是验证资源所有者的身份,并依据资源所有者的许可向客户端下发访问令牌。

在之前的故事中,果园中的水果就是资源,而资源所有者是阿冰,果园可以看做是资源服务器,小桂子就是客户端,而街道办事处是整个流程的授权中心,也就是上面的授权服务器。

2.2 基本概念

2.2.1 访问令牌(access token)

还记得故事中老王问小桂子要的凭证吗?凭证限制了小桂子只能摘 2 个西瓜和 3 斤荔枝,并且凭证还是具备生命周期的,一个星期之后小桂子再拿着过期的凭证老王也不认了。

实际上故事中的凭证对应的是 OAuth 2.0 中的访问令牌,访问令牌是在用户授权许可下,授权服务器下发给客户端的一个授权凭证,该令牌所要表达的意思是“ 用户授予该 APP 在多少时间范围内允许访问哪些与自己相关的服务或数据”,所以访问令牌主要在 时间范围权限范围两个维度进行控制,此外访问令牌对于客户端来说是非透明的,外在表现就是一个字符串,客户端无法知晓字符串背后所隐藏的用户信息,因此不用担心用户的密码凭证会因此泄露。

2.2.2 刷新令牌(refresh token)

故事中小桂子最后之所以觉得委屈是因为意识到自己需要再重复走一次授权过程,这让小桂子觉得很麻烦,专业点说就是用户体验太差,解决之道就是引入刷新令牌。

刷新令牌的作用在于更新访问令牌,访问令牌的生命周期一般较短,这样可以保证在发生访问令牌泄露时,不至于造成太坏的影响,但是访问令牌有效期设置太短导致的副作用就是用户需要频繁授权,虽然可以通过一定的机制进行静默授权,但是频繁的调用授权接口之于授权服务器也是一种压力。此时可以在下发访问令牌的同时下发一个刷新令牌,刷新令牌的生命周期明显长于访问令牌,这样在访问令牌失效时,可以利用刷新令牌去授权服务器换取新的访问令牌,不过协议对于刷新令牌没有强制规定,是否需要该令牌客户端可以自行选择。

2.2.3 回调地址(redirect uri)

OAuth 2.0 是一类基于回调的授权协议,以授权码模式为例,整个授权需要分为两步进行,第一步下发授权码,第二步根据授权码请求授权服务器下发访问令牌。OAuth 在第一步下发授权码时,是将授权码以参数的形式添加到回调地址后面,并以 302 跳转的形式进行下发,这样简化了客户端的操作,不需要再主动去触发一次请求,即可进入下一步流程。

回调的设计存在一定的安全隐患,坏人可以利用该机制引导用户到一个恶意站点,继而对用户发起攻击。对于授权服务器而言,也存在一定的危害,坏人可以利用该机制让授权服务器变成“肉鸡”,以授权服务器为代理请求目标地址,这样在消耗授权服务器资源的同时,也对目标地址服务器产生 DDOS 攻击。

为了避免上述安全隐患,OAuth 协议强制要求客户端在注册时填写自己的回调地址,其目的是为了让回调请求能够到达客户端自己的服务器,从而可以走获取访问令牌的流程。客户端可以同时配置多个回调地址,并在请求授权时携带一个地址,服务器会验证客户端传递上来的回调地址是否与之前注册的回调地址相同,或者前者是后者集合的一个元素,只有在满足这一条件下才允许下发授权码,同时协议还要求两步请求客户端携带的回调地址必须一致,通过这些措施来保证回调过程能够正常到达客户端自己的服务器,并继续后面拿授权码换取访问令牌的过程。

2.2.4 权限范围(scope)

访问令牌自带过期时间,可以在时间维度上对授权进行控制,而在权限范围上,OAuth 引入了一个 scope 的概念。Scope 可以看做是一个对象,包含一个权限的 ID,名称,以及描述信息等,比如 “获取您的基本资料(头像、昵称)”。应该在接入 OAuth 服务时必须向服务提供方申请相应的 scope,并在请求授权时指明该参数,这些权限在用户确认授权时,必须毫无保留的展示给用户,以让用户知道本次请求需要访问用户的哪些数据或服务。

在之前的故事中凭证允许小桂子只能摘取 2 个西瓜和 3 斤荔枝,这里就对应两个 scope,这些信息是写入到凭证(访问令牌)中的,从而限制凭证的权限范围。

2.3 基本授权流程

OAuth 2.0 协议定义了 4 种授权模式,其中最具代表性的是授权码模式,我们将在 3.1 节中详细介绍,这里先简单体会一下 OAuth 2.0 的授权流程,交互时序图如下:

基本交互流程

假设整个流程开始之前,用户已经登录,那么整个授权流程如下:

  1. 客户端请求资源所有者(用户)授权,一般都是由授权服务器进行引导
  2. 资源所有者实施授权(采用 4 种授权模式中的一种),客户端拿到用户的授权凭证
  3. 客户端携带用户授权凭证请求授权服务器下发访问令牌
  4. 授权服务器验证客户端出示的授权凭证,并下发访问令牌
  5. 客户端携带访问令牌请求存储在资源服务器上的用户受保护资源
  6. 资源服务器验证客户端出示的访问令牌,通过则响应客户端的请求

整个过程中,客户端都无法接触到用户的密码凭证信息,客户端通过访问令牌请求受保护资源,用户可以通过对授权操作的控制来间接控制客户端对于受保护资源的访问权限范围和周期。

三. 四种授权模式

OAuth 2.0 相对于 1.0 版本在授权模式上做了更多的细化,已定义的授权模式分为四种:

  1. 授权码模式(Authorization Code Grant)
  2. 隐式授权模式(Implicit Grant)
  3. 资源所有者密码凭证模式(Resource Owner Password Credentials Grant)
  4. 客户端凭证模式(Client Credentials Grant)

3.1 授权码授权模式(Authorization Code Grant)

授权码模式在整个授权流程上与 1.0 版本最为贴近,但是流程上还是要简化许多,也是 OAuth 2.0 中最标准,应用最为广泛的授权模式。这类授权模式非常适用于具备服务端的应用,当然现在大多数 APP 都有自己的服务端,所以授权码模式拥有最广泛的应用场景,下图为授权码各个角色之间的交互时序:

授权码模式

整个授权流程说明如下(具体参数释义见下文):

  1. 客户端携带 client_id, scope, redirect_uri, state 等信息请求授权服务器下发 code
  2. 授权服务器验证客户端身份,通过则询问用户是否同意授权(此时会跳转到用户能够直观看到的授权页面,等待用户点击确认授权)
  3. 假设用户同意授权,此时授权服务器会将 code 和 state 拼接在 redirect_uri 后面,并以 302 形式下发 code
  4. 客户端携带 code, redirect_uri, 以及 client_secret 请求授权服务器下发 access_token
  5. 授权服务器验证客户端身份,同时验证 code,以及 redirect_uri 是否与第一步相同,通过则下发 access_token,并选择性下发 refresh_token
3.1.1 获取授权码

授权码是授权流程的一个中间临时凭证,是对用户确认授权这一操作的一个短暂性表征,其生命周期一般较短,协议建议最大不要超过 10 分钟,在这一有效时间内,客户端可以通过授权码去授权服务器请求换取访问令牌,授权码应该采取防重放措施。

请求参数说明:

名称是否必须描述信息
response_type必须对于授权码模式来说 response_type=code
client_id必须客户端 ID,用于标识一个客户端,在注册应用时生成
redirect_uri可选授权回调地址,具体参见 2.2.3 小节
scope可选权限范围,用于对客户端的权限进行控制,如果客户端没有传递该参数,那么服务器则以该应用被许可的所有权限代替
state推荐用于维持请求和回调过程中的状态,防止 CSRF攻击,服务器不对该参数做任何处理,如果客户端携带了该参数,则服务器在响应时原封不动的进行返回

请求参数示例:

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1  
Host: server.example.com

客户端携带上述参数请求授权服务器,授权服务器会验证客户端的身份以及相关参数,并在确认用户已登录的前提下弹出授权页询问用户是否同意授权,如果用户同意则会将授权码(code)和 state 信息添加到回调地址后面,并以 302 的形式下发。

成功响应参数说明:

名称是否必须描述信息
code必须授权码,授权码代表用户确认授权的暂时性凭证,推荐最大生命周期不超过 10 分钟
state可选如果客户端传递了该参数,则必须原封不动返回

成功响应示例:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

如果请求参数错误,或者服务器端响应错误,那么需要将错误信息添加在回调地址后面,同样以 302 形式下发(回调地址错误,或客户端标识无效除外)。

错误响应参数说明:

名称是否必须描述信息
error必须错误代码
error_description可选具备可读性的错误描述信息
error_uri可选错误描述信息页面地址
state可选如果客户端传递了该参数,则必须原封不动返回

错误响应示例:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz
3.1.2 下发访问令牌

授权服务器的授权端点在以 302 形式下发 code 之后,用户 User-Agent,比如浏览器,将携带对应的 code 回调请求用户指定的 redirect_url,这个地址应该能够保证请求打到应用服务器的对应接口,该接口可以由此拿到 code,并附加相应参数请求授权服务器的令牌端点,授权端点验证 code 和相关参数,验证通过则下发 access_token。

请求参数说明:

名称是否必须描述信息
grant_type必须对于授权码模式 grant_type=authorization_code
code必须上一步骤获取的授权码
redirect_uri必须授权回调地址,具体参见 2.2.3 小节,如果上一步有设置,则必须相同
client_id必须客户端 ID,用于标识一个客户端,在注册应用时生成

如果在注册应用时有下发客户端凭证信息(client_secret),那么客户端必须携带该参数以让授权服务器验证客户端的真实性。针对客户端凭证需要多说的一点就是不能将其存储或传递到客户端,客户端无法保证 client_secret 的安全,应该始终将其存储在应用的服务器端,当下发授权码回调请求到应用服务器时,在服务器端携带上 client_secret 继续请求下发令牌。

请求参数示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

授权服务器需要验证客户端的真实性,以及是否与之前请求授权码的客户端属同一个(请求授权时的信息可以记录在授权码中,或以授权码为 key 建立缓存),授权服务器还要保证授权码处于生命周期内,且只能被使用一次。验证通过之后,授权服务器生成 access_token,并选择性下发 refresh_token,OAuth 2.0 协议明确了 token 的下发策略,对于 token 的生成策略没有做太多说明,不过相关 RFC 补充文档为生成 token 提供了指导,目前主要的 token 有 BEARER、MAC 等类型。

成功响应参数说明:

名称是否必须描述信息
access_token必须访问令牌
token_type必须访问令牌类型,比如 BEARER、MAC 等
expires_in推荐访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过期,如果没有指定该项,则使用默认值
refresh_token可选刷新令牌,选择性下发,参见 2.2.2
scope可选权限范围,如果最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应示例:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","example_parameter":"example_value"
}

错误响应参数说明:

名称是否必须描述信息
error必须错误代码
error_description可选具备可读性的错误描述信息
error_uri可选错误描述信息页面地址

错误响应示例:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "error":"invalid_request"
}
3.1.3 对于授权码模式的一点小感悟

授权码授权模式是 OAuth 2.0 协议已定义 4 种模式中最严谨的模式,其余 3 中模式都是建立在一些特殊场景下,并对这些场景做了一些妥协和优化。授权码授权流程分为两步走,将用户授权与下发访问令牌分开,这给授权带来了更多的灵活性,正常授权过程中必须经过用户登录这一步骤,在用户已登录的前提下,可以直接询问用户是否同意授权,但是在一些场景下,比如内部走 SSO(单点登录)的应用集成了基于 OAuth 授权的第三方应用,这个时候在 OAuth 授权登录第三方应用时,用户体验较好的流程是不需要用户再次输入用户名和密码的,这就需要将外围 APP 的登录态传递给嵌套的应用,但是这样是存在安全问题的,用户的登录态必须把握在走 SSO 登录流程的应用手上,这样的场景下授权码授权模式的两步走流程就可以满足在不交出用户登录态的情况下,无需再次登录即可授权。

内部应用可以拿着第三方应用的 client_id 等信息代替第三方应用去请求获取 code,因为自己持有用户的登录态,所以过程中无需用户再次输入用户名和密码,拿到 code 之后将其交给第三方应用,第三方应用利用 code 和自己的 client_secret 信息去请求授权服务器下发 token,整个流程内部应用不需要交出自己持有的用户登录态,第三方应用也无需交出自己的 client_secret 信息,最终却能够实现在保护用户密码凭证的前提下无需再次登录即可完成整个授权流程。

3.2 隐式授权模式(Implicit Grant)

对于一些纯客户端应用,往往无法妥善的保管其客户端凭证,同时因为没有服务器端,所以无法向授权服务器传递凭证信息,并且纯客户端应用在请求交互上要弱于有服务器的应用,这时候减少交互可以让应用的稳定性和用户体验更好,隐式授权模式是对这一应用场景的优化。

隐式授权模式在安全性上要弱于授权码模式,因为无法对当前客户端的真实性进行验证,同时对于下发的 access_token 存在被同设备上其它应用窃取的风险,为了降低这类风险,隐式授权模式强制要求不能下发 refresh_token,这一强制要求的另外一个考量是因为 refresh_token 的生命周期较长,而客户端无法安全的对其进行存储和保护。下图为授权码各个角色之间的交互时序:

隐式授权

整个授权流程说明如下:

  1. 客户端携带 client_id, scope, redirect_uri, state 等信息请求授权服务器下发 access_token
  2. 授权服务器验证客户端身份,通过则询问用户是否同意授权(此时会跳转到用户能够直观看到的授权页面,等待用户点击确认授权)
  3. 假设用户同意授权,此时授权服务器会将 access_token 和 state 等信息以 URI Fragment 形式拼接在 redirect_uri 后面,并以 302 形式下发
  4. 客户端利用脚本解析获取 access_token
3.2.1 请求获取访问令牌

不同于授权码模式的分两步走,隐式授权码模式一步即可拿到访问令牌。

请求参数说明:

名称是否必须描述信息
response_type必须对于授权码模式 response_type=token
client_id必须客户端 ID,用于标识一个客户端,在注册应用时生成
redirect_uri可选授权回调地址,具体参见 2.2.3 小节
scope可选权限范围,用于对客户端的权限进行控制,如果客户端没有传递该参数,那么服务器则以该应用被许可的所有权限代替
state推荐用于维持请求和回调过程中的状态,防止 CSRF攻击,服务器不对该参数做任何处理,如果客户端携带了该参数,则服务器在响应时原封不动的返回

请求参数示例:

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

成功响应参数说明:

名称是否必须描述信息
access_token必须访问令牌
token_type必须访问令牌类型,比如 BEARER,MAC 等
expires_in推荐访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过期,如果没有指定该项,则使用默认值
scope可选权限范围,如果最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明
state可选如果客户端传递了该参数,则必须原封不动返回

隐式授权模式不下发刷新令牌,访问令牌以 URI Fragment 的形式拼接在授权回调地址后面以 302 形式下发,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应示例:

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600

错误响应参数说明:

名称是否必须描述信息
error必须错误代码
error_description可选具备可读性的错误描述信息
error_uri可选错误描述信息页面地址
state可选如果客户端传递了该参数,则必须原封不动返回

授权服务器将上述元素以 URI Fragment 形式拼接在授权回调地址后面以 302 形式下发(redirect_uri 或 client_id 错误除外)。

错误响应参数示例:

HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz

3.3 资源所有者密码凭证授权模式(Resource Owner Password Credentials Grant)

资源所有者密码凭证授权模式建立在资源所有者充分信任客户端的前提下,因为该模式客户端可以拿到用的登录凭证,从而在用户无感知的情况下完成整个授权流程,毕竟都有用户的登录凭证了,再弹窗让用户确认授权也是多此一举。

这里可能有一个比较疑惑的地方是既然已经拿到了用户的登录凭证,为什么还需要绕一大圈子走 OAuth 授权,拿到令牌再去请求用户的受保护资源呢?实际中事情可能并不会这么简单,拿到用户登录凭证的不一定是用户本身,而且这里协议指的用户登录凭证是用户的用户名和密码,实际中还可以是走 SSO 登录下发的 token,token 在持有权限上要小于等于用户的用户名和密码,这是从客户端角度出发。对于资源服务器来说,有些敏感数据需要在用户级别做权限控制,对于服务级别的控制粒度太粗,所以这些服务往往需要服务携带 access_token 来请求某一个用户的敏感数据。

举个例子来说,比如有一个服务是获取某个用户的通讯录,这是用户十分敏感的数据,且一般只能授予内部应用,如果是在服务级别进行控制,那么只要拿到服务权限,该应用可以请求获取任何一个用户的通讯录数据,这是一件十分危险的事情。如果基于 access_token 做鉴权,那么就可以将粒度控制在用户级别,前面讲的两种授权方式在这里应用时都有一个共同的缺点,需要弹出授权页让用户确认授权,要知道这样的场景往往是发生在内部应用里面,内部应用是可以持有用户登录态的,这里的确认授权对于一个用户体验好的 APP 来说就应该发生在用户登录时,通过用户协议等方式直接告诉用户,从而让用户在一次登录过程中可以让应用拿到用户的登录态和访问令牌。资源所有者密码凭证授权模式的交互时序如下:

资源所有者凭证

整个授权流程说明如下:

  1. 用于授予客户端登录凭证(比如用户名和密码信息,亦或是 token)
  2. 客户端携带用户的登录凭证和 scope 等信息请求授权服务器下发 access_token
  3. 授权服务器验证用户的登录凭证和客户端信息的真实性,通过则下发 access_token,并选择性下发 refresh_token
3.3.1 用户授予登录凭证

用于登录凭证如何传递给客户端这一块协议未做说明,实际中该类授权一般用于内部应用,这类应用的特点就是为用户提供登录功能,当用户登录之后,这类应用也就持有了用户的登录态,可以是用户登录的 session 标识,也可以是走 SSO 下发的 token 信息。

3.3.2 请求获取访问令牌

请求参数说明:

名称是否必须描述信息
grant_type必须对于本模式 grant_type=password
username必须用户名
password必须用户密码
scope可选权限范围,如果最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

如果在注册应用时有下发客户端凭证信息(client_secret),那么客户端必须携带该参数以让授权服务器验证客户端的真实性。

请求参数示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

成功响应参数说明:

名称是否必须描述信息
access_token必须访问令牌
token_type必须访问令牌类型,比如 BEARER、MAC 等
expires_in推荐访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过期,如果没有指定该项,则使用默认值
refresh_token可选刷新令牌,选择性下发,参见 2.2.2
scope可选权限范围,如果最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应参数示例:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","example_parameter":"example_value"
}

错误响应参数说明:

名称是否必须描述信息
error必须错误代码
error_description可选具备可读性的错误描述信息
error_uri可选错误描述信息页面地址

错误响应示例:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "error":"invalid_request"
}

3.4 客户端凭证授权模式(Client Credentials Grant)

客户端凭证授权模式基于客户端持有的证书去请求用户的受保护资源,如果把这里的受保护资源定义得更加宽泛一点,比如说是对一个内网接口权限的调用,那么这类授权方式可以被改造为内网权限验证服务。客户端凭证授权模式的交互时序如下:

客户端凭证

整个授权流程说明如下:

  1. 客户端携带客户端凭证和 scope 等信息请求授权服务器下发 access_token
  2. 授权服务器验证客户端真实性,通过则下发 access_token
3.4.1 请求获取访问令牌:

请求参数说明:

名称是否必须描述信息
grant_type必须对于本模式 grant_type=client_credentials
scope可选权限范围,如果最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

请求参数示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

成功响应参数说明:

名称是否必须描述信息
access_token必须访问令牌
token_type必须访问令牌类型,比如 BEARER、MAC 等等
expires_in推荐访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过期,如果没有指定该项,则使用默认值
scope可选权限范围,如果最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应参数示例:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"example_parameter":"example_value"
}

错误响应参数说明:

名称是否必须描述信息
error必须错误代码
error_description可选具备可读性的错误描述信息
error_uri可选错误描述信息页面地址

错误响应示例:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "error":"invalid_request"
}

四. 本篇小结

本文介绍了 OAuth 2.0 授权协议的理论知识,OAuth 2.0 被广泛应用于第三方授权登录,很多其它的协议都可以基于该协议进行改造,比如前面提到的 SSO,作为开发人员,还是建议对该协议或多或少有些了解。如果要自己实现一个授权和鉴权服务,该协议为我们绘制指明了思路,但是还有很多细节实现需要我们再去查阅各种资料和实践。

关于 token 的生成最后再补充一点,OAuth 2.0 协议只是一笔带过的说它是一个字符串,用于表示特定的权限、生命周期等,却没有明确阐述 token 的生成策略,以及如何去验证一个 token。协议不去详细阐述,个人觉得是因为这一块是与具体业务绑定的,无法完全做到抽象,并且在这一块去做详细的规定,意义也不大。

Token 本质上就是对用户授权这一操作在时间和权限范围两个维度上的一个表征,协议可以对 token 的传递和基本验证做相应规定,但是具体的一个 token 包含哪些元素,采用什么样的生成算法还是需要由自己去把握。一些文档,比如参考文献 3 和 4 都为 token 的生成进行了扩展说明,鉴于篇幅,不再展开。

欢迎扫码关注指间生活

参考文献

  1. RFC5849 - The OAuth 1.0 Protocol
  2. RFC6749 - The OAuth 2.0 Authorization Framework
  3. RFC6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
  4. HTTP Authentication: MAC Authentication (draft-hammer-oauth-v2-mac-token-02)

树莓派驱动LCD12864显示屏

$
0
0
适用于ST7920控制器的LCD12864显示屏,使用wiringPi GPIO库,C语言编写.


代码在文章结尾处

硬件连接
采用LCD12864的8位并行驱动方式,与树莓派GPIO连接已在程序开头处定义

驱动原理
使用wiringPi控制GPIO,按通讯协议向LCD发送指令以及要显示的数据即可显示
主要由进制转换、字符编码转换、发送数据构成。

代码
/*运行:sudo ./12864 [字符]
	编译:gcc 12864.c -o 12864 -L lib -l wiringPi (需已安装wiringPi)
	by:WuSiYu
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <wiringPi.h>
#include <iconv.h> 
#define LCD_RS 4 //显示屏控制线
#define LCD_RW 5
#define LCD_EN 1
 
#define D1 30 //显示屏数据线
#define D2 21
#define D3 22
#define D4 23
#define D5 24
#define D6 25
#define D7 26
#define D8 27
 
char u2g_out[255]; 
 
/*===================================================================
功能:编码转换
输入:UTF8
输出:GB2312
====================================================================*/
int code_convert(char *from_charset,char *to_charset,char *inbuf,int inlen,char *outbuf,int outlen){
	iconv_t cd;
	int rc;
	char **pin = &inbuf;
	char **pout = &outbuf;
	cd = iconv_open(to_charset,from_charset);
	if (cd==0) return -1;
	memset(outbuf,0,outlen);
	if (iconv(cd,pin,&inlen,pout,&outlen)==-1) return -1;
	iconv_close(cd);
	return 0;
}
int u2g(char *inbuf,int inlen,char *outbuf,int outlen){ 
	return code_convert("utf-8","gb2312",inbuf,inlen,outbuf,outlen); 
} 
/*===================================================================
功能:总线写入
输入:十六进制数据
输出:无
====================================================================*/
void bus_write(unsigned char data){
	int t[10];
	int f=0,i=0,d=data;
 
	//进制转换
	for(i=0;i<8;i++){
		t[i]=data%2;
		data=data/2;
	}
	//输出
	digitalWrite(D1,t[0]);
	digitalWrite(D2,t[1]);
	digitalWrite(D3,t[2]);
	digitalWrite(D4,t[3]);
	digitalWrite(D5,t[4]);
	digitalWrite(D6,t[5]);
	digitalWrite(D7,t[6]);
	digitalWrite(D8,t[7]);
}
/*===================================================================
功能:检查LCD忙状态                                                    
输入:无
输出:lcd_busy为1时,忙,等待。lcd-busy为0时,闲,可写指令与数据。      
====================================================================*/
void chk_busy(){//检查忙位
	digitalWrite(LCD_RS,0);
	digitalWrite(LCD_RW,1);
	digitalWrite(LCD_EN,1);
	bus_write(0xff);
	pinMode(D8, INPUT);
	while(digitalRead(D8));
	pinMode(D8, OUTPUT);
	digitalWrite(LCD_EN,0);
}
/*====================================================================
功能:写命令
输入:8位数据
输出:无
=====================================================================*/
void WriteCmd_LCD12864(unsigned char cmdcode){
	chk_busy();
	digitalWrite(LCD_RS,0);
	digitalWrite(LCD_RW,0);
	digitalWrite(LCD_EN,1);
	delay(5);
	bus_write(cmdcode);
	digitalWrite(LCD_EN,0);
	delay(5);
}
/*====================================================================
功能:写数据
输入:8位数据
输出:无
=====================================================================*/
void WriteData_LCD12864(unsigned char Dispdata){
	chk_busy();
	digitalWrite(LCD_RS,1);
	digitalWrite(LCD_RW,0);
	digitalWrite(LCD_EN,1);
	delay(5);
	bus_write(Dispdata);
	digitalWrite(LCD_EN,0);
	delay(5);
}
/*==========================================================================
功能:发送字符串
输入:地址,字符串
输出:无
===========================================================================*/
void WriteWord_LCD12864(unsigned char a,unsigned char *d){//向LCD指定位置发送一个字符串,长度64字符之内。
	unsigned char *s;
	u2g(d,strlen(d),u2g_out,255);
	s=u2g_out;
	WriteCmd_LCD12864(a);
	while(*s>0){
		WriteData_LCD12864(*s); 
		s++;
	}
}
/*==========================================================================
功能:发送字符串2
输入:字符串
输出:无
===========================================================================*/
void WriteWord_LCD12864_2(unsigned char *d){//向LCD发送一屏字符串,长度64字符之内。
	int i=0;
	unsigned char *s;
	u2g(d,strlen(d),u2g_out,255);
	s=u2g_out;
	WriteCmd_LCD12864(0x80);
	while(*s>0){
		WriteData_LCD12864(*s); 
		s++;
		i++;
		if(i==16){
			WriteCmd_LCD12864(0x90);
		}
		if(i==32){
			WriteCmd_LCD12864(0x88);
		}
		if(i==48){
			WriteCmd_LCD12864(0x98);
		}
	}
}
/*==========================================================================
功能:初始化LCD
输入:无
输出:无
===========================================================================*/
void Init_LCD12864(void){			//初始化LCD屏
	pinMode(D1, OUTPUT);	//设置GPIO
	pinMode(D2, OUTPUT);
	pinMode(D3, OUTPUT);
	pinMode(D4, OUTPUT);
	pinMode(D5, OUTPUT);
	pinMode(D6, OUTPUT);
	pinMode(D7, OUTPUT);
	pinMode(D8, OUTPUT);
 
	pinMode(LCD_RS, OUTPUT);
	pinMode(LCD_RW, OUTPUT);
	pinMode(LCD_EN, OUTPUT);
 
	WriteCmd_LCD12864(0x38);       //选择8bit数据流
	delay(20);
	WriteCmd_LCD12864(0x01);       //清除显示,并且设定地址指针为00H
	delay(20);
	WriteCmd_LCD12864(0x0c);       //开显示(无游标、不反白)
	delay(20);
}
 
int main (int args, char *argv[]){
	wiringPiSetup();
	Init_LCD12864();
 
	WriteCmd_LCD12864(0x01);
	WriteWord_LCD12864(0x80,"Hello LCD12864");
	if(argv[1]){
		WriteCmd_LCD12864(0x01);
		WriteCmd_LCD12864(0x80);
		WriteWord_LCD12864_2(argv[1]);
	}
}


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Viewing all 15896 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>