下周受邀参加iOS小组的技术分享,为客户端同学介绍服务端技术。因此本文的目标是帮助不熟悉服务端技术的同学了解服务端架构的全貌,以及设计服务端系统需要认真考虑的各方面问题。
什么是客户端和服务端
一般在我们的认知里,"客户端"就是运行在手机、平板和个人电脑里的程序,用户能看得见,摸得着;"服务端"则是运行在机房里的程序,用户不能直观的感知它的存在。因此,客户端又被称为前端,服务端又被称为后端。这种按设备类型划分的说法不是完全准确的。我们定义客户端和服务端可以根据职责来划分。客户端的职责是想要完成一件事(请求);服务端的职责是帮助前者完成那件事(响应)。也可以从数据的维度来划分。客户端负责产生数据和提交数据;服务端负责保存数据和查询数据。下图这个银行柜台的例子很好的说明了客户端和服务端的关系。服务端系统目标
既然服务端的职责是帮助客户端完成请求,那么服务端系统目标自然就是如何为客户端提供"优质"服务。什么样的服务算优质呢?还是以银行为例:
- 小王半夜想取钱,于是来到银行,发现银行柜台人员虽然下班了,但旁边有24小时自助服务的ATM机可以取钱。ATM机满足了用户"随时享受服务"的需求,算是一个优质服务。
- 小王去银行办业务,刚进大厅,大堂经理说请先取号排队。小王取了号,显示前面排队有100人,小王很沮丧,以为要等很久,但由于这家银行有几十个柜台同时工作,实际等待时间很短。减少客户的等待时间,也算一个优质服务。
- 小王去银行存钱,因为他知道把钱放在银行永远不会丢。获得安全保障,也算一个优质服务。
以上是服务端系统最重要的三个目标:高可用,高性能,安全。
具体要求是:
- 高可用:保证服务运行长期稳定,具体指标是SLA服务可用性。一般互联网公司至少都要保证4个9的可用性,即99.99%,全年停机不超过52.6分钟;如果要达到5个9的标准,则全年停机不超过5.26分钟。
- 高性能:保证客户端请求能被快速响应,最重要的指标是吞吐量。吞吐量是指固定时间间隔内,处理完毕的事务个数。通常是1秒内处理完毕的请求个数,单位:事务/秒(tps)。
- 安全:保证用户数据永不丢失和被恶意篡改,用户数据不可被无权限者获取。
技术方案
提升可用性的方案
一支球队如何保证球员在赛场上受伤后比赛不会处于劣势?我们都知道那是因为球队里有替补球员。对于服务端系统来讲也是这样,高可用的原理在于"冗余"。
系统多活
对于每一个服务端程序,都要保证同时部署至少2份,并且部署在不同的服务器上,通过负载均衡将请求的流量分配到每台服务器上。这样可以保证当一台服务器挂掉以后,总有另一台服务器能继续提供服务。另一个好处是可以实现不停机系统升级,避免在发布服务端程序时由于项目重启造成的服务短时间不可用。假设有服务S部署在服务器A和服务器B,现在服务S升级的具体做法是:
负载均衡切断服务S在服务器A上的流量;
在服务器A上升级服务S;
负载均衡恢复服务S在服务器A上的流量;
负载均衡切断服务S在服务器B上的流量;
在服务器B上升级服务S;
-
负载均衡恢复服务S在服务器B上的流量。
系统拆分
将业务众多的大项目拆分成职责单纯的多个小项目也可以提升系统的可用性。原理是,如果未拆分的项目出现阻塞性bug,由于所有业务都在同一个系统中,因此一挂全挂,所有业务都将不可用。而拆分以后,对每个小项目而言,即使有问题,也只影响自己的那一部分业务,不会导致整个系统瘫痪。
提升性能的方案
提升服务性能应该围绕两个方面:提升单服务实例性能和弹性扩容。
优化数据库性能
提高系统性能首先考虑的是数据库的优化,因为就我工作经历中遇到的大多数性能问题都和数据库有关。如果你使用的是mysql这样的关系型数据库,一定要把慢sql日志打开,及时从中找出慢查询语句,想办法优化它们,这个办法通常会提升几十倍甚至上百倍的性能。
使用缓存
使用缓存是提升服务性能最常用的方案。redis是一个基于内存hash结构的缓存型db。其优势在于速读写能力碾压mysql。但由于内存资源对服务器来说是非常宝贵的,因此redis只适合保存查询频率高的数据。另外需要维护缓存数据和数据库中数据的一致性,这是个非常值得关注的问题,但不是本文重点。
使用消息队列
使用消息队列(MQ)是提升系统性能的另一大杀器,MQ能起到对流量的削峰填谷的作用。系统之间进行数据交互的时候,在时效性和稳定性之间我们都需要进行选择。基于线程的异步处理,能确保用户体验,但是极端情况下可能会出现异常,影响系统的稳定性,而同步调用很多时候无法保证理想的性能,那么我们就可以用MQ来进行处理。上游系统将数据投递到MQ,下游系统取MQ的数据进行消费,投递和消费可以用同步的方式处理,因为MQ接收数据的性能是非常高的,不会影响上游系统的性能。下游系统一直在监听MQ的数据,如果MQ有数据,下游系统则会按照先进先出 这样的规则,逐条进行消费,而上游系统只需要将数据存入MQ里,这样就既降低了不同系统之间的耦合度,同时也确保了消息通知的及时性,而且也不影响上游系统的性能。下图说明了使用消息队列如何加速响应时间。集群
以上方案都是提升单实例服务性能的方案,提升效果都会有上限。而集群是横向扩展的方案,从理论上讲可以支持无上限的性能提升。这就像银行扩张柜台数量一样,只要柜台数量扩充一倍,性能就能提升一倍。目前大多数数据库和中间件都支持集群部署。如mongodb,elasticsearch,redis,kafka等等。
提升安全性的方案
服务端需要承担整个系统安全保障的职责。具体来说,需要考虑3方面的安全问题:网络安全、接口安全和数据安全。
网络安全
在整个内网当中,根据用途可以将计算机划分为三类:内部使用的工作站与终端、对外提供服务的应用服务器,以及重要数据服务器。这三类计算机的作用不同,重要程度不同,安全需求也不同:
- 重点保护各种应用服务器,特别是要保证数据库服务的代理服务器的绝对安全,不能允许用户直接访问。对应用服务器,则要保证用户的访问是受到控制的,要能够限制能够访问该服务器的用户范围,使其只能通过指定的方式进行访问。
- 数据服务器的安全性要大于对外提供多种服务的WWW服务器、E-mail服务器等应用服务器。所以数据库服务器在防火墙定义的规则上要严于其他服务器。
- 内部网络有可能会对各种服务器和应用系统的直接的网络攻击,所以内部办公网络也需要和代理服务器、对外服务器等隔离开。
- 不能允许外网用户直接访问内部网络。
- 远程登录服务器必须经过堡垒机。
接口安全
接口安全最重要的目标是保证请求来源合法,用户身份和权限经过验证。有以下几个常见的注意要点:
- 永远不要相信客户端给你的数据。服务端必须对每个参数做合法性校验。
- 要有"限制"意识。要考虑接口调用频率是否正常,若不正常则应该视为恶意攻击,要直接报错。尤其要注意可能被薅羊毛的接口和可能对用户安全造成威胁的接口,如发送短信的接口,验证用户名密码的接口和输入验证码的接口等。
- 要设计完善的身份认证机制。最常见的方式是token模式,尽可能减少请求中带密码的机会。
- 敏感数据要经过加密传输。如用户名密码验证,聊天消息发送,支付转账等都应该对数据内容加密。
- 签名校验。签名校验的目的是防止请求数据在客户端到服务端的中间过程被篡改。签名校验的原理是,客户端和服务端分别保存着一对只有双方知道的密钥,客户端和服务端按照约定好的格式将请求内容和密钥拼接起来,再使用相同的摘要加密算法进行加密。服务端根据客户端传入的请求参数计算签名,并和客户端计算的签名进行比较,如果双方签名不一致,则数据内容可能被篡改。
- 文件上传。服务端如非必要尽量不要提供文件上传接口,但如果一定要提供,应该注意以下几点:1.文件上传目录必须设置为不可执行命令。这一点非常重要,能避免恶意文件上传到服务器被执行后造成服务器中木马病毒。2.服务端必须判断文件类型,而且必须用白名单过滤而不是黑名单。3.上传后使用随机数改写文件名和文件路径。
数据安全
数据安全要注意考虑以下方面:
- 数据库权限划分要清晰。程序访问、开发人员、审计人员的权限要严格控制。
- 数据库密码禁止使用弱口令,而且应该设置密码过期时间。
- 数据库要定时做全量备份,最好是跨地域备份。如果是mysql数据库,要打开binlog。全量备份+binlog结合可以让数据恢复到秒级。
- 服务器用户权限管理要清晰。不同用户要有不同的目录权限和操作权限。重点要控制执行命令、删除、上传、下载等操作的权限。
总结
本文介绍了服务端系统在架构上的目标:高可用、高性能、安全。以及如何达到目标的思路和需要考虑的问题。只要你能在以上方面认真思考,你设计出的服务端系统一定可以为客户端提供"优质"的服务。