可扩展架构
对于系统的扩展性,有很多人常常将其与伸缩性相混淆。我们先理清一下这两个概念。
伸缩性(Scalability):系统能够通过增加(减少)自身资源规模的方式增强(减少)自己计算处理事务的能力。如果这种增减是成比例的,就被称作线性伸缩性。在网站架构中,通常指利用集群的方式增加服务器数量、提高系统的整体事务吞吐能力。
扩展性(Extensibility):对现有系统影响最小的情况下,系统功能可持续扩展或提升的能力。表现在系统基础设施稳定不需要经常变更,应用之间较少依赖和耦合,对需求变更可以敏捷响应。它是系统架构设计层面的开闭原则,当系统增加新功能时,不需要对现有系统的结构和代码进行修改。
基本策略
设计可扩展架构的核心思想是模块化,并在此基础之上,降低模块间的耦合性,提高模块的复用性。
分层和分割是模块化设计的重要手段,利用分层和分割的方式将软件分割为若干个低耦合的独立的组件模块,这些组件模块以消息传递及依赖调用的方式聚合成一个完整的系统。
在大型应用中,这些模块通过分布式部署的方式,独立的模块部署在独立的服务器(集群)上,从物理上分离模块之间的耦合关系,进一步降低耦合性提高复用性。模块分布式部署以后具体聚合方式主要有分布式消息队列和分布式服务。
分布式消息队列
如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响最小,这样系统的可扩展性就会更好。
事件驱动架构
事件驱动架构(Event Driven Architecture)就是通过在低耦合的模块之间传输事件消息,以保持模块的松散耦合,并借助事件消息的通信完成模块间合作。典型的EDA架构就是操作系统中常见的生产者消费者模式,具体实现手段有很多,最常用的就是分布式消息队列。
消息队列利用发布—订阅模式工作,消息发送者发布消息,一个或者多个消息接收者订阅消息。消息发送者是消息源,在对消息进行处理后将消息发送至分布式消息队列,消息接受者从分布式消息队列获取该消息后继续进行处理。
消息接受者在对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅处理该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列的流程。
由于消息发送者不需要等待消息接受者处理数据就可以返回,系统具有更好的响应延迟;同时,在网站访问高峰,消息可以暂时存储在消息队列中等待消息接受者根据自身负载处理能力控制消息处理速度,减轻数据库等后端存储的负载压力。
原理
队列是一种先进先出的数据结构,分布式消息队列可以看作将这种数据结构部署到独立的服务器上,应用程序可以通过远程访问接口使用分布式消息队列,进行消息存取操作,进而实现分布式的异步调用。基本原理如图所示。
消息生产者应用程序通过远程访问接口将消息推送给消息队列服务器,消息队列服务器将消息写入本地内存队列后立即返回成功响应给消息生产者。消息队列服务器根据消息订阅列表查找订阅该消息的消息消费者应用程序,将消息队列中的消息按照先进先出(FIFO)的原则将消息通过远程通信接口发送给消息消费者程序。
目前开源的和商业的分布式消息队列产品有很多,比较著名的有RabbitMQ、Kafka等。
在伸缩性方面,由于消息队列服务器上的数据可以看作是被即时处理的,因此类似于无状态的服务器,伸缩性设计比较简单。将新服务器加入分布式消息队列集群中,通知生产者服务器更改消息队列服务器列表即可。
在可用性方面,为了避免消费者进程处理缓慢,分布式消息队列服务器内存空间不足造成的问题,如果内存队列已满,会将消息写入磁盘,消息推送模块在将内存队列消息处理完以后,将磁盘内容加载到内存队列继续处理。
为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器,等消息真正被消息消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中其他的服务器发布消息。
分布式服务
使用分布式服务是降低系统耦合性的另一个重要手段。如果说分布式消息队列通过消息对象分解系统耦合性,不同子系统处理同一个消息;那么分布式服务则通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务调用。
大型单体应用会带来很多问题,例如编译和部署困难、分支管理混乱、扩展功能和维护困难等。解决方案就是拆分,将模块独立部署,降低系统耦合性。拆分可以分为纵向拆分和横向拆分两种。
纵向拆分:将一个大应用拆分为多个小应用,如果新增业务较为独立,那么就直接将其设计部署为一个独立的Web应用系统。
横向拆分:将复用的业务拆分出来,独立部署为分布式服务,新增业务只需要调用这些分布式服务,不需要依赖具体的模块代码,即可快速搭建一个应用系统,而模块内业务逻辑变化的时候,只要接口保持一致就不会影响业务程序和其他模块。如图所示。
纵向拆分相对较为简单,通过梳理业务,将较少相关的业务剥离,使其成为独立的Web应用。而对于横向拆分,不但需要识别可复用的业务,设计服务接口,规范服务依赖关系,还需要一个完善的分布式服务管理框架。
安全架构
从互联网诞生起,安全威胁就一直伴随着应用系统的发展,各种Web攻击和信息泄露也从未停止。
常见web攻击与防御
XSS攻击
XSS攻击即跨站点脚本攻击(Cross Site Script),指黑客通过篡改网页,注入恶意HTML脚本,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。
常见的XSS攻击类型有两种,一种是反射型,攻击者诱使用户点击一个嵌入恶意脚本的链接。另外一种是持久型,黑客提交含有恶意脚本的请求,保存在被攻击的Web站点的数据库中,用户浏览网页时,恶意脚本被包含在正常页面中,达到攻击的目的。
XSS攻击者一般都是通过在请求中嵌入恶意脚本达到攻击的目的,这些脚本是一般用户输入中不使用的,如果进行过滤和消毒处理,即对某些html危险字符转义,如“>”转义为“>”、“<”转义为“<”等,就可以防止大部分攻击。
SQL注入攻击
SQL注入攻击的原理如下:攻击者在HTTP请求中注入恶意SQL命令,服务器用请求参数构造数据库SQL命令时,恶意SQL被一起构造,并在数据库中执行。
防御SQL注入攻击首先要避免被攻击者猜测到表名等数据库的关键信息。此外,对请求参数进行过滤和消毒也是一种比较简单粗暴又有效的手段。最后,目前最好的防SQL注入方法是使用预编译手段绑定参数。
CSRF攻击
CSRF(Cross Site Request Forgery,跨站请求伪造),攻击者通过跨站请求,在用户不知情的情况下,以用户的身份伪造请求。其核心是利用了浏览器Cookie或服务器Session策略,盗取用户身份。
相应地,CSRF的防御手段主要是识别请求者身份。主要有下面几种方法:
- 表单Token:在页面表单中增加一个随机数作为Token,每次响应页面的Token都不相同,从正常页面提交的请求会包含该Token值,而伪造的请求无法获得该值,服务器检查请求参数中Token的值以确定请求提交者是否合法。
- 验证码:请求提交时,需要用户输入验证码,以避免在用户不知情的情况下被攻击者伪造请求。但是输入验证码的用户体验比较差,所以请在必要时使用,如支付交易等关键页面。
- Referer check:HTTP请求头的Referer域中记录着请求来源,可通过检查请求来源,验证其是否合法。
信息加密和密钥安全
为了保护系统的敏感数据,通常需要对这些信息进行加密处理,信息加密技术可分为三类:单向散列加密、对称加密和非对称加密。
单向散列加密
单向散列加密是指通过对不同输入长度的信息进行散列计算,得到固定长度的输出,这个散列计算过程是单向不可逆的。
虽然不能通过算法将单向散列密文反算得到明文,但是由于人们设置密码具有一定的模式,因此通过彩虹表(常用密码和对应的密文关系表)等手段可以进行猜测式破解。为了加强单向散列计算的安全性,还会给散列算法加点salt,salt相当于加密的密钥,增加破解的难度。
常用的单向散列算法有MD5、SHA等。单向散列算法还有一个特点就是输入的任何微小变化都会导致输出的完全不同,这个特性有时也会被用来生成信息摘要、计算具有高离散程度的随机数等用途。
对称加密
所谓对称加密是指加密和解密使用的密钥是同一个密钥(或者可以互相推算),对称加密通常用在信息需要安全交换或存储的场合,如Cookie加密、通信加密等。
对称加密的优点是算法简单,加解密效率高,系统开销小,适合对大量数据加密。缺点是加解密使用同一个密钥,远程通信的情况下如何安全的交换密钥是个难题,如果密钥丢失,那么所有的加密信息也就没有秘密可言了。常用的对称加密算法有DES算发、RC算法等。
非对称加密
不同于对称加密,非对称加密和解密使用的密钥不是同一密钥,其中一个对外界公开,被称作公钥,另一个只有所有者知道,被称作私钥。用公钥加密的信息必须用私钥才能解开,反之,用私钥加密的信息只有用公钥才能解开。理论上说,不可能通过公钥计算获得私钥。非对称加密技术通常用在信息安全传输,数字签名等场合。
信息发送者A通过公开渠道获得信息接收者B的公钥,对提交信息进行加密,然后通过非安全传输通道将密文信息发送给B,B得到密文信息后,用自己的私钥对信息进行解密,获得原始的明文信息。即使密文信息在传输过程中遭到窃取,窃取者没有解密密钥也无法还原明文。
数字签名的过程则相反,签名者用自己的私钥对信息进行加密,然后发送给对方,接收方用签名者的公钥对信息进行解密,获得原始明文信息,由于私钥只有签名者拥有,因此该信息是不可抵赖的,具有签名的性质。
在实际应用中,常常会混合使用对称加密和非对称加密。先使用非对称加密技术对对称密钥进行安全传输,然后使用对称加密技术进行信息加解密与交换。
非对称加密的常用算法有RSA算法等。HTTPS传输中浏览器使用的数字证书实质上是经过权威机构认证的非对称加密的公钥。
密钥安全管理
前面所讲的几种加密技术,能够达到安全保密效果的一个重要前提是密钥的安全。不管是单向散列加密用到的salt、对称加密的密钥、还是非对称加密的私钥,一旦这些密钥泄露出去,那么所有基于这些密钥加密的信息就失去了秘密性。
实践中,改善密钥安全性的手段有两种。
一种方案是把密钥和算法放在一个独立的服务器上,甚至做成一个专用的硬件设施,对外提供加密和解密服务,应用系统通过调用这个服务,实现数据的加解密。由于密钥和算法独立部署,由专人维护,使得密钥泄露的概率大大降低。但是这种方案成本较高,而且每次加密、解密都需要进行一次远程服务调用,开销也较大。
另一种方案是将加解密算法放在应用系统中,密钥则放在独立服务器中,为了提高密钥的安全性,实际存储时,密钥被切分成数片,加密后分别保存在不同存储介质中,兼顾密钥安全性的同时又改善了性能,如图所示。
信息过滤与反垃圾
在如今的互联网上,广告和垃圾信息屡见不鲜、泛滥成灾,因此做好信息过滤和垃圾处理是必不可少的。常用的信息过滤与反垃圾手段有以下几种。
文本匹配
通常,系统会维护一份敏感词列表,如果用户发表的信息含有列表中的敏感词,则进行消毒处理或拒绝发表。那么如何快速地判断用户信息中是否含有敏感词呢?如果敏感词比较少,用户提交信息文本长度也较短,可直接使用正则表达式匹配。但是正则表达式的效率一般较差,当敏感词很多,用户发布的信息也很长,网站并发量较高时,就需要更合适的方法来完成。
这方面公开的算法有很多,基本上都是Trie树的变种,空间和时间复杂度都比较好的有双数组Trie算法等。Trie算法的本质是确定一个有限状态自动机,根据输入数据进行状态转移。双数组Trie算法优化了Trie算法,利用两个稀疏数组存储树结构,base数组存储Trie树的节点,check数组进行状态检查。双数组Trie数需要根据业务场景和经验确定数组大小,避免数组过大或者冲突过多。
另一种更简单的实现是通过构造多级Hash表进行文本匹配。
分类算法
对广告贴、垃圾邮件等内容的识别比较好的方法是采用分类算法。以反垃圾邮件为例说明分类算法的使用,先将批量已分类的邮件样本输入分类算法进行训练,得到一个垃圾邮件分类模型,然后利用分类算法结合分类模型对待处理邮件进行识别。比较简单实用的分类算法有贝叶斯分类算法。
分类算法除了用于反垃圾,还可用于信息自动分类,门户网站可用该算法对采集来的新闻稿件进行自动分类,分发到不同的频道。邮箱服务商根据邮件内容推送的个性化广告也可以使用分类算法提高投送相关度。
黑名单
对于垃圾邮件,除了用分类算法进行内容分类识别,还可以使用黑名单技术,将被报告的垃圾邮箱地址放入黑名单,然后针对邮件的发件人在黑名单列表中查找,如果查找成功,则过滤该邮件。
黑名单可以通过Hash表实现,该方法实现简单,时间复杂度小,满足一般场景使用。但是当黑名单列表非常大时,Hash表需要占据极大的内存空间。在对过滤需求要求不完全精确的场景下,可用布隆过滤器代替Hash表。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。