B/S 类项目改善的一些建议

 

要分享的议题

  1. 性能提升:在访问量逐渐增大的同时,如何增大单台服务器的 PV2 上限,增加 TPS3 ?
  2. RESTful:相较于传统的 SOAP1,RESTful 风格架构有哪些优点?做法有哪些区别?
  3. 微服务:随着企业越来越大,系统会越来越大,越来越难维护,如何在保证“稳”的同时,还保证有小企业的“灵活”?

简要的介绍

性能提升

最常用的性能提高方式可以通过使用服务器的集群来解决,简单粗暴的理解就是增加银行柜员的数量。但是,一味的只考虑从服务端提供性能,并不是聪明的做法 —— 应该讲求性价比。当然,核心必须是提高服务器的 TPS,即在最短的时间内给最多的客户提供服务。服务器集群可以大幅提升整体的性能,但是我们要讨论的是如何提升单台服务器的性能。

  1. 服务器的压力主要来源于三个方面:CPU、网络和磁盘 IO。磁盘作为最容易达到瓶颈的一方,必须想办法减少 IO 操作。数据库作为数据持久性存储、磁盘开销的大户,这里主要就是要减少或合并数据库操作。
  2. 系统的流畅性取决于服务端和客户端的良好配合。网站类的项目,充分利用浏览器资源,不仅能降低服务器压力,还能提供更好地客户体验。现代化的浏览器,一般都符合 RFC26164 规范。其中很重要的报头有:ETag 和 Last-Modified 报头 —— 浏览器的缓存设置开关,可以最大限度的利用客户端资源。

RESTful 架构风格

好比面向过程编程和面向对象编程,这两者并没有明确的界限。在适当的地方用适当风格的架构,重点是物尽其用。但 RESTful 作为新兴的风格,必然有其优势:

  1. 在 RESTful 架构中,关注点在于资源。每个都有一个地址,资源本身就是方法调用的目标。方法列表对所有资源都是一样的。这些方法都是标准方法,包括 HTTP GET、POST、PUT、DELETE,还可能包括 PATCH、HEADER 和 OPTIONS。其指导思想是远端提供了一系列资源,客户端需要下载、展现、编辑和提交更改,重点放在本地。
  2. 在 RPC 架构中,关注点在于方法。在客户端看来,就是在客户端组合条件,然后在服务器中执行,最终再反馈给客户端。其指导思想是隐藏实现细节,或者关联其它 RPC 服务运算,重点放在了服务器。

微服务和单体式应用

现代化的单体式应用,通常采用模块化的方式,围绕核心模块并行开发。最终他们需要联合测试,部署成一个单体式的应用:C# 会部署成 IIS 的一个网站,Java 会打包成 War 格式部署 Tomcat 上。

随着时间的推移,单体式在应对越来越多的新需求后,会变得越来越大。更不幸的是,因为公司资源和需求的不对等,许多仓促应对的代码会添加到应用中。这些代码在短期内不会出现问题,但是修正 Bug 和正常的新功能添加会变得越来越困难,因为通常会涉及到多个模块,牵一发而动全身。此时就是单体式应用的瓶颈期,会考虑拆分成多个子系统。当然,这将会再维持一段时间,直到再出现相似的问题。

许多公司,比如 Amazon、eBay 和 NetFlix,通过采用微处理结构模式解决了上述问题。其思路不是开发一个巨大的单体式的应用,而是将应用分解为小的、互相连接的微服务。

一个微服务一般完成某个特定的功能,比如下单管理、客户管理等等。每一个微服务都有自己的业务逻辑和适配器。一些微服务还会发布 Api 给其它微服务和应用客户端使用。其它微服务完成一个 Web UI。

性能提升

  1. 数据库静态化:数据库不包含运算逻辑,所有运算逻辑在程序内完成。
  2. 减少外部 IO:使用数据缓存、合并数据库操作、读写分离。
  3. 异步化:对于非必须的方法,异步执行使其不影响当前逻辑。
  4. 子系统拆分:拆分长时间运行的逻辑为 Windows 服务或 Job 。

一个栗子:电子商务系统,下单操作起始涉及到了对多个模块的调用。但是用户下单的时候,并不关心这些,只要得到一个下单成功的结果就可以了。我们可以分析一下:系统首先要对用户提交的信息有效性校验,再就是业务数据准确性校验,最后提交到数据库。一个成功的电商系统,前两者必须能在很短的时间内完成,并且在秒杀特卖这种场景时不会造成数据库的崩溃。

基于以上两点,我们分析下如何优化秒杀特卖这种场景下的操作流程。

  1. 服务端对信息有效性的校验,操作频率最密集、速度要最快,所以不应该涉及除内存运算之外的操作,比如:Redis 和数据库读写、TCP/HTTP 远程调用等。
  2. 业务性数据校验,关联模块很多、速度要求较快,所以不应涉及慢速的 IO 操作,比如:数据库读写、HTTP 远程调用等。
  3. 写数据库频繁,在较短的时间内给数据库造成很大的持续压力、速度要求很快,所以这里可以采用立即反馈,稍后写入的方式执行。

数据库静态化

数据库的操作都是有锁的:Select 语句发布共享锁5,Insert、Update 和 Delete 发布排它锁6。所以说在操作同一张表的前提下,数据库操作都是串行7的。

基于以上考虑,让数据库只做存储容器,不负责运算才是正途。正是因为数据库的操作是串行的,在大并发量写入时,任何一点的提升都是要争取的,所以这里要把运算的任务提到程序中执行

  1. 存储过程因为把程序逻辑放在数据库,一般来说肯定包含运算任务,考虑一般开发的水平不能保证先用临时表存储预先计算好的数据(能做到也太繁琐了,很容易出现异常),最后再统一执行,所以首先要摒弃包含数据库写入类的存储过程
  2. 程序内的运算,如果是在开启事务后仍然存在,也要算入数据库的运算任务。因为数据库事务开启后,独占的串行已经开始了,程序的运算时间不仅占用了程序的运算时间,还占用了数据库事务的开启时长,在本质上并没有减少数据库事务的开启时长。严格来算的话,这种做法甚至还不如上一条的做法优化。

所以,真正的数据库静态化是:首先在程序内运算,产生数据库要执行的 SQL 写入语句和参数,持续运算直到产生所有的数据库写入命令;再开启数据库事务,按照先进先出原则顺序连续执行数据库写入命令(此段时间内不能包含其它非数据库运算)。只有这样,才能保证命令的执行都是静态化的写入,并且锁定数据库的时间最短,保证最大化的降低数据库压力。

另外一个,正是因为数据库的操作是串行的,所以在执行数据库写入的情况下,是不能读取的,要避免出现脏读,数据库的读写分离就很有必要了。建立从库,由主库负责写入,从库负责读取,将数据库的压力均分到多台上。

数据库的读写分离要注意:刚写入数据库的数据,同步到从库需要 2 到 3 秒的时间,需要在业务上更改流程,以便于在用户检索时数据已同步。

减少外部 IO

由于磁盘的限制,其读写速度和内存不成比例,所以这里是第二个可能出现瓶颈的地方。可以考虑将配置信息预先读取到缓存的方式解决。

因为数据库是依托于硬盘而存在的,所以数据库的读写相对于有效性验证和业务验证来说,是时间消耗大户。在单纯的考虑数据库写入的情况下,可以从系统内剥离订单的数据库写入业务。一来可以省掉无意义等待数据库写入的时间;二来可以减少 CPU 时间片的占用,将时间

另一种是数据库的读写操作,在一个数据库事务内,是不能有第二个数据库执行相同的操作的。考虑到数据静态化中的介绍,数据库事务开启后,程序内的运算其实也是数据库的运算时间的。此时可以考虑推迟数据库事务的开启时间:首先在程序内运算产生要执行的数据库命令,再开启数据库事务,在连续的时间内执行批量执行数据库事务。即合并数据库操作到一次数据库执行中。

异步化

在开发的过程中,不可避免的要和其关联的模块交互,而这些交互并不会对当前的业务逻辑产生影响。这种操作就应该改成异步的方式。在数据库交互的过程中,如果用户不需要等待数据库的返回值,还可以将数据库执行异步化,在最短的时间内反馈执行结果。

一个栗子:用户下单的过程中,提交订单的操作,其实并不关心提交成功失败,只是在后续跳转到订单详情的时候才会看到订单详情。这个流程就可以将数据库执行异步化,在服务器接收到用户的提交请求时,可以在校验数据后,直接反馈提交成功的响应给用户。接下来,通过异步队列的方式,保存到数据库。客户端在接收到提交成功的反馈后,提示用户提交成功,但是不给出订单的任何信息。用户只有主动点击了查看订单列表,才会执行数据库查询。这个时间差足够系统处理订单的真正提交操作了。

这样做最直接的好处是提高了网站的响应速度,优化了用户体验,在提升服务器 TPS 的同时,还没有提升数据库的压力。在秒杀特卖时,能够最大限度的避免超卖的情况。

子系统拆分

继续上面的例子,数据库的提交订单操作,和网站并没有多少的关系。此时就可以考虑到将这一部分拆分出来,做成一个 Windows 服务,两者通过消息队列的方式通讯。队列的串行读取正好符合了数据库的串行执行,在高峰时段也没有超过数据库的极限,造成宕机的情况。在超过服务极限的情况下,处理慢比不能处理总是要好的。

从操作系统上来说,系统调度针对每个进程都是平等的。此处将数据库执行操作从网站拆分出来,减少了数据库操作对 CPU 时间片的占用,侧面提升了网站的服务能力。

RESTful 架构风格与 SOAP 架构风格

  1. 属性路由:使用渐进式的 URI 替代传统平板式的方法名称 URI。
  2. 客户端缓存报头:使用 ETag 和 Last-Modified 报头减轻服务器压力。
  3. CRUD:使用 GET、POST、PUT 和 DELETE 方法区分数据库 CRUD 操作。

属性路由

第一个 WebApi 版本使用的是基于公约的路由。在该类型的路由中, 你可以定义一个或多个被参数化字符串的模版。当这个框架接收到一个请求时,它匹配一个 URI 到路由模版。

基于公约的路由的一个优势就是:这个模版被定义在一个单独的地方,路由规则一致的被应用于所有的控制器。不幸的是,基于公约的路由是很难支持确切的URI模式,而这个确切的 URI 模式在 RESTful Api 中是很普遍的。比如,资源经常包含子资源:客户下了订单,电影有演员,书有作者等等,它是很自然的创建这些 URI 来反应这些关系:

/customers/3/orders

这种类型的 URI 在基于公约的路由下是比较难实现的。尽管它能做到,但是如果你有许多控制器或者很多资源类型时,不能很好的被扩展。但对于属性路由,它是很容易的为这个 URI 定义一个路由,你可以简单的添加一个属性到控制器的动作上:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomerId(int customerId) { ... }

方便的 Api 版本控制

有时候我们需要开发一个功能的新版本,但是并不想对现有的功能产生影响,比如:api/v1/products 和 api/v2/products 可以被路由到不同的控制器。在开发阶段就做出较好了区分,并且当新的版本正式商用后,也可以方便的对 V1 版本的控制器过期或停用。

重载 URI 片段

在下面的例子中,12306 表示一个特定的车票,而 notravelled 表示未出行的车票集合。

/tickets/12306
/tickets/notravelled

通过自然语义,人们可以很容易的理解这些 URI 的含义,但是基于公约的方式并不能很方便的解决这个问题。

路由约束

属性路由添加了公约路由时代所没有的约束特性,可以让你在路由模版中限制参数被匹配。常规的语法是 {parameter:constraint},例如:

[Route("users/{id:int}"]
public User GetUserById(int id) { ... } [Route("users/{name}"]
public User GetUserByName(string name) { ... }

如果 URI 的 id 片段是一个 int 类型的,那么第一个路由将会被选择,否则第二个路由将会被选择。属性路由约定特殊规则的路由优先匹配,最后才匹配没有任何约束的路由。注意不要出现两种可能的匹配,否则会出现多匹配的问题,比如:

[Route("{id:int}")]
public string Get(int id) [Route("{id:decimal}")]
public string Get(decimal id)

这里需要注意的是,WebApi 框架有一个 Bug,不支持小数点,比如:/values/v1/8.3 将不会被解析成 decimal 类型。

下面是被支持的约束列表:

约束 描述 用法演示
bool 类型匹配(Boolean 类型)  
datetime 类型匹配(DateTime 类型) {x:datetime8}
decimal 类型匹配(Decimal 类型) {x:decimal9}
double 类型匹配(64 位浮点数) {x:double9}
float 类型匹配(32 位浮点数)  
guid 类型匹配(Guid)  
int 类型匹配(32 位整数)  
long 类型匹配(64 位整数)  
alpha 字符组成(必须由拉丁字母组成)  
regex 字符组成(必须与指定的正则表达式匹配)  
max 值范围(小于或等于指定的最大值)  
min 值范围(大于或等于指定的最小值)  
range 值范围(在指定的最小值和最大值之间)  
maxlength 字符串最大长度(小于或等于指定的长度)  
minlength 字符串最小长度(大于或等于指定的长度)  
length 字符串长度(等于指定的长度或者长度在指定的范围内)  

客户端缓存报头

基础知识

什么是 Last-Modified

在浏览器第一次请求某一个 URL 时,服务器端的返回状态会是 200,内容是你请求的资源,同时有一个 Last-Modified 的属性标记此文件在服务期端最后被修改的时间,格式类似这样:

Last-Modified: Fri, 12 May 2006 18:53:33 GMT

客户端第二次请求此 URL 时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:

If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT

如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Modified)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

什么是 ETag

HTTP 协议规格说明定义 ETag 为被请求变量的实体值;另一种说法是,ETag 是一个可以与 Web 资源关联的记号(Token):典型的 Web 资源可以一个 HTML 页,但也可能是 JSON 或 XML 文档。服务器单独负责判断记号是什么及其含义,并在 HTTP 响应头中将其传送到客户端,以下是服务器端返回的格式:

ETag: W/"9e10cdada3f741f6b0802ee31179837d"

客户端的查询更新格式是这样的:

If-None-Match: W/"9e10cdada3f741f6b0802ee31179837d"

如果 ETag 没改变,则返回状态码 304 内容不返回,这也和 Last-Modified 一样。本人测试 ETag 主要在断点下载时比较有用。

Last-Modified 和 ETag 如何帮助提高性能?

聪明的开发者会把 Last-Modified 和 ETag 跟请求的 HTTP 报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 Last-Modified/ETag 标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个 Last-Modified/ETag
  3. 客户端展现该页面,并将页面连同 Last-Modified/ETag 一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的 Last-Modified/ETag 一起传递给服务器。
  5. 服务器检查该 Last-Modified 或 ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回状态码 304 和一个空的响应体。

这里的客户端一般指浏览器,通过编程方式使用的客户端,一般不会处理这两个 HTTP 请求头。

微服务

目前不做深入讨论。


  1. SOAP(Simple Object Access Protocol)简单对象访问协议,是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。 

  2. PV:(Page View)即页面浏览量,通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标。网页浏览数是评价网站流量最常用的指标之一,简称为 PV。监测网站 PV 的变化趋势和分析其变化原因是很多站长定期要做的工作。Page Views 中的 Page 一般是指普通的 HTML 网页,也包含 PHP、JSP 等动态产生的 HTML 内容。来自浏览器的一次 HTML 内容请求会被看作一个 PV,逐渐累计成为 PV 总数。 

  3. TPS:(Transaction Per Second)每秒钟系统能够处理的交易或事务的数量。它是衡量系统处理能力的重要指标。TPS 是 LoadRunner 中重要的性能参数指标。 

  4. RFC2616:目前该规范已有部分更新。 

  5. 共享锁:类似于读写锁中的读锁。可以多个一起读,但是排斥写锁。只有读锁释放后,才能进入写锁。 

  6. 排它锁:类似于读写锁中的写锁。只能一个写,其余的操作都必须等待,直到当前写锁释放后。 

  7. 串行:同一时间只允许一个线程操作,其余线程只能等待完成后,才能继续执行操作。 

  8. datetime 类型的约束,如果采用 / 做分隔符,必须放在最后一个,并且采用 * 前导:{*x:datetime}。目前,也只有这种写法可以跨多个 URI 段。 

  9. decimal 和 double 两种数字类型,如果包含小数点将不能被正常解析,目前可以算 WebApi 框架的一个 Bug 。 

B/S 类项目改善的更多相关文章

  1. B/S 类项目改善的一些建议

    body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...

  2. 普通企业的规划类项目中,OptaPlanner更适合作为APS的规划优化引擎

    在企业的规划.优化场景中,均需要开发规划类的项目,实现从从种可能方案中找出相对最优方案.如排班.生产计划(包括高层次的供应链优化,到细粒度的车间甚至机台作业指令).车辆调度等.因为这类场景需要解决的问 ...

  3. .Net Core库类项目跨项目读取配置文件

    在项目开始之前我们可以先去了解一下IConfiguration接口,.Net Core Web应用程序类似于一个控制台,当程序运行到Startup时会自动注入IConfiguration,默认读取当前 ...

  4. VisualStudio,用C#写的一个开源移动APP,资产管理类项目SmoSec

    继SmoOne之后,Smobiler团队又推出一款用C#开发的APP开源项目. 这款开源项目名为SmoSec,目前包含资产管理.耗材管理两大类. 并且,未来会不断迭代,持续增加盘点.标签打印和仓库管理 ...

  5. 基于EasyDarwin实现幼儿园监控类项目

    移动互联网越来越普及,幼儿园监控类的项目也越来越多,如何能够以最低的成本.最快的速度搭建一套幼儿园监控类的平台成了许多开发者的需求,那么我们今天就来简单探讨一下如何基于EasyDarwin实现一套幼儿 ...

  6. SOA服务类项目开发模式

    开发模式 以需求用例为基,Cas e&Coding两条线并行,服务(M)&消费(VC)分离,单元.接口.功能.集成四层质量管理,自动化集成.测试.交付全程支持. 3个大阶段(需求分析阶 ...

  7. ASIHTTPRequest开源类项目导入问题及解决方法

    在静态库project中加入ASIHTTPRequest导出lib.a.放到project里编译出一下错: Undefined symbols for architecture armv7: &quo ...

  8. Vue框架H5商城类项目商品详情点击返回弹出推荐商品弹窗的实现方案

    需求场景: 非推荐商品详情页返回的时候弹出弹窗推荐商品,点击弹窗按钮可以直接访问推荐商品: 只有直接进入商品详情页返回才会弹出推荐商品弹窗: 每个用户访问只能弹一次(除非清除缓存). 需求分析: 1. ...

  9. 《区块链100问》第81集:应用类项目Augur

    Augur是基于以太坊区块链打造的去中心化预测平台,于2015年6月正式发布,是以太坊上的第一款应用. Augur采用了一个叫“群体智慧”的概念,它的意思是,一群人的智慧会高于这群人中最聪明的人.所以 ...

随机推荐

  1. CentOS 6 安装Oracle11g

    原创作品.从 "深蓝blog" 博客,欢迎转载,请务必注明转载如下源.否则追究其版权责任. 深蓝的blog:http://blog.csdn.net/huangyanlong/ar ...

  2. 小猪的Android入门之路 Day 4 - part 1

    小猪的Android入门之路 Day 4 - part 1 Android事件处理机制之--基于监听的事件处理机制 本节引言: 在開始本个章节前,我们先回想下,如今我们已经知道了android的一些相 ...

  3. vim代码折叠命令简短

    作者:zhanhailiang 日期:2014-10-18 1. 通过fdm实现代码折叠:set fdm=xxx 有下面6种方式实现折叠: |fold-manual| manual Folds are ...

  4. Jenkins(两)

    官网:https://wiki.jenkins-ci.org/display/JENKINS/Meet+Jenkins 我的这篇文章不过简单的依据上文,介绍Jenkins提供了哪些功能.详细大家还是要 ...

  5. HDOJ 5147 Sequence II 树阵

    树阵: 每个号码的前面维修比其数数少,和大量的这后一种数比他的数字 再枚举每一个位置组合一下 Sequence II Time Limit: 5000/2500 MS (Java/Others)    ...

  6. Eclipse 打JAR包,插件FatJar 安装与使用

    下载fatJar插件,解压缩后是一个.../plugins/(net...)把plugins下面的(net..)文件夹拷贝到eclipse的plugins下,重新启动Eclipse3.1,Window ...

  7. Dom4j分解xml

    package cn.com.guju.util; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingExc ...

  8. TimesTen更改CacheGroup管理用户ORACLE结束和TT结束password【TimesTen操作和维修基地】

    password管理一直操作的一部分的安全管理和维护.CacheGroup管理用户password虽然并不复杂变化.然而,这是用于生产,改不好比较easy导致失败.简介点击这里CacheGroup管理 ...

  9. 玩转html5(一)-----盘点html5新增的那些酷酷的input类型和属性

    今天正式开始学习html5了,相比html以前的版本,html5新增了好多功能,属性,使我们做出来的界面更加的绚丽,而且使用起来超级简单,这篇文章先来说说html增加的那些input类型和属性. 这些 ...

  10. 基于Spring + Spring MVC + Mybatis 高性能web构建

    基于Spring + Spring MVC + Mybatis 高性能web构建 一直想写这篇文章,前段时间 痴迷于JavaScript.NodeJs.AngularJs,做了大量的研究,对前后端交互 ...