
关注高并发、分布式系统架构、中间件、领域建模等。 InfoQ特约作者,CSDN博客专家。
缓存一致性问题 当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策略。一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存。 缓存并发问题 缓存过期后将尝试从后端数据库获取数据,这是一个看似合理的流程。但是,在高并发场景下,有可能多个请求并发的去从数据库获取数据,对后端数据库造成极大的冲击,甚至导致 “雪崩”现象。此外,当某个缓存key在被更新时,同时也可能被大量请求在获取,这也会导致一致性的问题。那如何避免类似问题呢?我们会想到类似“锁”的机制,在缓存更新或者过期的情况下,先尝试获取到锁,当更新或者从数据库获取完成后再释放锁,其他的请求只需要牺牲一定的等待时间,即可直接从缓存中继续获取数据。 缓存穿透问题 缓存穿透在有些地方也称为“击穿”。很多朋友对缓存穿透的理解是:由于缓存故障或者缓存过期导致大量请求穿透到后端数据库服务器,从而对数据库造成巨大冲击。 这其实是一种误解。真正的缓存穿透应该是这样的: 在高并发场景下,如果某一个key被高并发访问,没有被命中,出于对容错性考虑,会尝试去从后端数据库中获取,从而导致了大量请求达到数据库,而当该key对应的数据本身就是空的情况下,这就导致数据库中并发的去执行了很多不必要的查询操作,从而导致巨大冲击和压力。 可以通过下面的几种常用方式来避免缓存传统问题: 缓存空对象 对查询结果为空的对象也进行缓存,如果是集合,可以缓存一个空的集合(非null),如果是缓存单个对象,可以通过字段标识来区分。这样避免请求穿透到后端数据库。同时,也需要保证缓存数据的时效性。这种方式实现起来成本较低,比较适合命中不高,但可能被频繁更新的数据。 单独过滤处理 对所有可能对应数据为空的key进行统一的存放,并在请求前做拦截,这样避免请求穿透到后端数据库。这种方式实现起来相对复杂,比较适合命中不高,但是更新不频繁的数据。 缓存颠簸问题 缓存的颠簸问题,有些地方可能被成为“缓存抖动”,可以看做是一种比“雪崩”更轻微的故障,但是也会在一段时间内对系统造成冲击和性能影响。一般是由于缓存节点故障导致。业内推荐的做法是通过一致性Hash算法来解决。这里不做过多阐述,可以参照其他章节 缓存的雪崩现象 缓存雪崩就是指由于缓存的原因,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。导致这种现象的原因有很多种,上面提到的“缓存并发”,“缓存穿透”,“缓存颠簸”等问题,其实都可能会导致缓存雪崩现象发生。这些问题也可能会被恶意攻击者所利用。还有一种情况,例如某个时间点内,系统预加载的缓存周期性集中失效了,也可能会导致雪崩。为了避免这种周期性失效,可以通过设置不同的过期时间,来错开缓存过期,从而避免缓存集中失效。 从应用架构角度,我们可以通过限流、降级、熔断等手段来降低影响,也可以通过多级缓存来避免这种灾难。 此外,从整个研发体系流程的角度,应该加强压力测试,尽量模拟真实场景,尽早的暴露问题从而防范。 缓存无底洞现象 该问题由 facebook 的工作人员提出的, facebook 在 2010 年左右,memcached 节点就已经达3000 个,缓存数千 G 内容。 他们发现了一个问题---memcached 连接频率,效率下降了,于是加 memcached 节点, 添加了后,发现因为连接频率导致的问题,仍然存在,并没有好转,称之为”无底洞现象”。 目前主流的数据库、缓存、Nosql、搜索中间件等技术栈中,都支持“分片”技术,来满足“高性能、高并发、高可用、可扩展”等要求。有些是在client端通过Hash取模(或一致性Hash)将值映射到不同的实例上,有些是在client端通过范围取值的方式映射的。当然,也有些是在服务端进行的。但是,每一次操作都可能需要和不同节点进行网络通信来完成,实例节点越多,则开销会越大,对性能影响就越大。 主要可以从如下几个方面避免和优化: 数据分布方式 有些业务数据可能适合Hash分布,而有些业务适合采用范围分布,这样能够从一定程度避免网络IO的开销。 IO优化 可以充分利用连接池,NIO等技术来尽可能降低连接开销,增强并发连接能力。 数据访问方式 一次性获取大的数据集,会比分多次去获取小数据集的网络IO开销更小。 当然,缓存无底洞现象并不常见。在绝大多数的公司里可能根本不会遇到。
RPC概念及分类 RPC全称为Remote Procedure Call,翻译过来为“远程过程调用”。目前,主流的平台中都支持各种远程调用技术,以满足分布式系统架构中不同的系统之间的远程通信和相互调用。远程调用的应用场景极其广泛,实现的方式也各式各样。 从通信协议的层面,大致可以分为: 基于HTTP协议的(例如基于文本的SOAP(XML)、Rest(JSON),基于二进制Hessian(Binary)) 基于TCP协议的(通常会借助Mina、Netty等高性能网络框架) 从不同的开发语言和平台层面,分为: 单种语言或平台特定支持的通信技术(例如Java平台的RMI、.NET平台Remoting) 支持跨平台通信的技术(例如HTTP Rest、Thrift等) 从调用过程来看,分为: 同步通信调用(同步RPC) 异步通信调用(MQ、异步RPC) 常见的几种通信方式 1. 远程数据共享(例如:共享远程文件,共享数据库等实现不同系统通信) 2. 消息队列 3. RPC(远程过程调用) 序列化/反序列化 只有二进制数据才能在网络中传输,序列化和反序列化的定义是: 将对象转换成二进制流的过程叫做序列化, 将二进制流转换成对象的过程叫做反序列化。 Java和.NET平台中常见的通信技术 Java中支持的包括: 技术 简介 是否支持跨平台 Corbra 90年代产物,已被淘汰 不支持 RMI EJB时代产物,已逐渐被淘汰 不支持 WebService 基于Http SOAP,效率低,逐渐被淘汰 支持 Hessain 基于Http,二进制序列化,效率高,使用广泛 支持 Rest(spring mvc等) 支持Http Rest,广泛应用于无线API,开放平台等 支持 JMS、开源MQ Java消息服务(消息中间件),使用广泛 支持 Socket 基于Mina、Netty(NIO、AIO高效通信) 理论上支持 .NET中包括: 技术 简介 是否支持跨平台 WebService 基于Http SOAP,效率低,逐渐被WCF整合淘汰 支持 .NET Remoting 通信效率尚可,使用复杂,逐渐被WCF整合淘汰 不支持 WCF SOAP 整合了原有的WebService,通信效率低 支持 WCF NET.TCP 通信效率高,部分.NET项目内部服务在使用 不支持 WCF Rest 使用较少,已经被Web Api逐渐取代 支持 Web Api 支持Http Rest,广泛应用于无线API,开放平台等 支持 MSMQ、开源MQ 微软自己的消息中间件或者其他开源MQ 支持(MSMQ除外) Hessain .NET 基于Http,二进制序列化,效率高,使用较少 支持 Socket 通过Socket网络编程方式实现系统通信 理论上支持 互联网时代常见的RPC技术和框架 应用级的服务框架: Dubbo/Dubbox ZeroICE GRpc Spring Boot/Spring Cloud 基础通信框架: Protocol Buffers Thrift 远程通信协议: RMI Socket SOAP(HTTP XML) REST(HTTP JSON) RPC的注意事项 性能 影响RPC性能的主要在几个方面: 1.序列化/反序列化的框架 2.网络协议,网络模型,线程模型等 安全 RPC安全的主要在于服务接口的鉴权和访问控制支持。 跨平台 跨不同的操作系统,不同的编程语言和平台。 跨平台RPC技术和常见框架介绍 SOAP WebService Hessian HTTP Rest Thrift GRpc(Protobuffer) Zero ICE 消息中间件
缓存命中率的介绍 命中:可以直接通过缓存获取到需要的数据。 不命中:无法直接通过缓存获取到想要的数据,需要再次查询数据库或者执行其它的操作。原因可能是由于缓存中根本不存在,或者缓存已经过期。 通常来讲,缓存的命中率越高则表示使用缓存的收益越高,应用的性能越好(响应时间越短、吞吐量越高),抗并发的能力越强。 由此可见,在高并发的互联网系统中,缓存的命中率是至关重要的指标。 如何监控缓存的命中率 在memcached中,运行state命令可以查看memcached服务的状态信息,其中cmd_get表示总的get次数,get_hits表示get的总命中次数,命中率 = get_hits/cmd_get。 当然,我们也可以通过一些开源的第三方工具对整个memcached集群进行监控,显示会更直观。比较典型的包括:zabbix、MemAdmin等。 如图:MemAdmin对memcached服务的命中率情况的监控统计 同理,在redis中可以运行info命令查看redis服务的状态信息,其中keyspace_hits为总的命中中次数,keyspace_misses为总的miss次数,命中率=keyspace_hits/(keyspace_hits+keyspace_misses)。 开源工具Redis-star能以图表方式直观redis服务相关信息,同时,zabbix也提供了相关的插件对redis服务进行监控。 影响缓存命中率的几个因素 之前的章节中我们提到了缓存命中率的重要性,下面分析下影响缓存命中率的几个因素。 业务场景和业务需求 缓存适合“读多写少”的业务场景,反之,使用缓存的意义其实并不大,命中率会很低。 业务需求决定了对时效性的要求,直接影响到缓存的过期时间和更新策略。时效性要求越低,就越适合缓存。在相同key和相同请求数的情况下,缓存时间越长,命中率会越高。 互联网应用的大多数业务场景下都是很适合使用缓存的。 缓存的设计(粒度和策略) 通常情况下,缓存的粒度越小,命中率会越高。举个实际的例子说明: 当缓存单个对象的时候(例如:单个用户信息),只有当该对象对应的数据发生变化时,我们才需要更新缓存或者让移除缓存。而当缓存一个集合的时候(例如:所有用户数据),其中任何一个对象对应的数据发生变化时,都需要更新或移除缓存。 还有另一种情况,假设其他地方也需要获取该对象对应的数据时(比如其他地方也需要获取单个用户信息),如果缓存的是单个对象,则可以直接命中缓存,反之,则无法直接命中。这样更加灵活,缓存命中率会更高。 此外,缓存的更新/过期策略也直接影响到缓存的命中率。当数据发生变化时,直接更新缓存的值会比移除缓存(或者让缓存过期)的命中率更高,当然,系统复杂度也会更高。 缓存容量和基础设施 缓存的容量有限,则容易引起缓存失效和被淘汰(目前多数的缓存框架或中间件都采用了LRU算法)。同时,缓存的技术选型也是至关重要的,比如采用应用内置的本地缓存就比较容易出现单机瓶颈,而采用分布式缓存则毕竟容易扩展。所以需要做好系统容量规划,并考虑是否可扩展。此外,不同的缓存框架或中间件,其效率和稳定性也是存在差异的。 其他因素 当缓存节点发生故障时,需要避免缓存失效并最大程度降低影响,这种特殊情况也是架构师需要考虑的。业内比较典型的做法就是通过一致性Hash算法,或者通过节点冗余的方式。 有些朋友可能会有这样的理解误区:既然业务需求对数据时效性要求很高,而缓存时间又会影响到缓存命中率,那么系统就别使用缓存了。其实这忽略了一个重要因素--并发。通常来讲,在相同缓存时间和key的情况下,并发越高,缓存的收益会越高,即便缓存时间很短。 提高缓存命中率的方法 从架构师的角度,需要应用尽可能的通过缓存直接获取数据,并避免缓存失效。这也是比较考验架构师能力的,需要在业务需求,缓存粒度,缓存策略,技术选型等各个方面去通盘考虑并做权衡。尽可能的聚焦在高频访问且时效性要求不高的热点业务上,通过缓存预加载(预热)、增加存储容量、调整缓存粒度、更新缓存等手段来提高命中率。 对于时效性很高(或缓存空间有限),内容跨度很大(或访问很随机),并且访问量不高的应用来说缓存命中率可能长期很低,可能预热后的缓存还没来得被访问就已经过期了。 缓存的失效和更新策略也是非常重要的,下篇继续...
缓存的基本知识 在整个计算机体系构造中(无论是硬件层面还是软件层面),缓存都是无处不在的。 在计算机硬件构造中,由于两种介质的速度不匹配,高速介质在和低速介质交互时速度趋向低速方,这就导致了高速介质的资源闲置。而通过引入第三种介质(速度和成本介于两者中间),将低速方读写的部分内容数据保存在该介质中,高速方大多数情况下则无需和低速方直接交互,这样就能整体提升了交互的性能。这就是计算机体系中缓存的由来。比较典型的就是CPU缓存(CPU寄存器=>L1 cahce =>L2 cache =>内存=>硬盘),如图: 在计算机系统和应用软件层面,缓存更是无处不在。我们在使用浏览器上网时,很多静态资源会被缓存到本地。我们在手机上采用微信聊天时,很多好友的头像等数据会被缓存到手机中。在操作系统层面,I/O操作也会被内核缓存(一般将数据缓存在文件系统的缓存页中),当然,这个可能相比前两个场景更加抽象,但缓存的目的都是一致的,为了提升读写性能。 缓存在狭义上解决介质读写速度不匹配问题,广义上包括任何利用中间媒介提高速度的方法,包括:空间换时间,动态操作变为静态操作。 缓存(CACHE)和缓冲(BUFFER) 缓存:可以共享,多种数据,大小不固定,可以重复使用,已知数据,用于提高IO效率。 缓冲:不可以共享,单一数据,大小固定,读取后失效,命中100%,未知数据,用于减少IO次数。 缓存的属性 命中率:从缓存中返回正确数据的次数/总请求次数。 容量:超过这个值启用一定的策略:转移到磁盘;转移到远端;清空部分。 存储介质:内存、磁盘。 成本:开发成本、部署成本、硬件成本。 效率:SET效率、GET效率、序列化、哈希算法、分布式算法。 缓存的限制 由于价格的因素,缓存实现依赖的存储往往有大小限制——保存什么,舍弃什么,命中率。 缓存往往是从无到有的——在最初阶段不能发挥作用,在不命中的时候性能颠簸。 缓存的分类 按照存储介质来分 : 内存(网站进程内、同服务器独立进程、独立服务器、分布式服务器组)。 磁盘(本地文件和数据库,独立服务器、分布式服务器组)。 缓存可以使用磁盘而不仅仅是内存。 按照存储的数据来分 : 直接用于输出的整页(HTML、脚本样式、图片)。 片段页(可供多个客户端使用的HTML、脚本样式等)。 索引和聚合数据(空间换时间)。 耗时查询的结果数据。 和业务相关的大块数据(列表数据,引用数据)。 和业务相关的小级数据(行级数据,资源数据)。 和上下文(用户)相关的数据(活动数据)。 按照实现方式来分 : 框架或引擎内置的缓存(比如ORM缓存和SQL SERVER缓存)。 安装特定的组件根据规则自动实现缓存(比如反向代理和输出缓存)。 需要由开发以编程方式实现的缓存(比如业务数据缓存)。 按照作用来分 : 用于数据的读取(之后介绍的大部分内容都是基于此类缓存) 用于(允许丢失)数据的写入——写到缓存的队列中,再由工作线程提交处理(写入存储) 网站架构中的缓存 浏览器缓存(HTTP缓存头) 代理缓存(Squid Vanish CDN) Web服务器缓存(内核缓存、应用缓存) 页面输出缓存(片段缓存、整页缓存) 业务数据缓存(本地缓存,分布式缓存) 其它缓存(ORM、数据库、搜索引擎等缓存) 缓存的常见模式和策略(过期、更新、清除) 缓存的常见模式: 缓存的策略: 缓存的更新策略 A 由获取数据请求触发的被动更新 B 由更新数据请求触发的主动更新(双写) C 使用独立线程主动定时更新缓存 D 回调方式更新(过期或依赖) E 永远不更新? 缓存的过期(失效)策略 F 绝对的过期时间 G 平滑过期(有人使用就不会过期) H 依赖方式(依赖数据库、依赖文件) I 永远不过期? 缓存的清除(替换)策略: RAND 删除随机数据,不能反映局部性。 SIZE 删除最大的数据。 FIFO,First In First Out 删除最先进入缓存的数据,不能反映局部性。 LFU,Least Frequently Used 删除一直以来最少被使用的数据。 LRU,Least Recently Used 删除最近最少使用的数据。 常见模式 延迟加载方式:A+F 预加载方式:B/C/E+I
在之前的文章中,我介绍了分库分表的几种表现形式和玩法,也重点介绍了垂直分库所带来的问题和解决方法。本篇中,我们将继续聊聊水平分库分表的一些技巧。 分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量、连接数、处理能力等都很有限,数据库本身的“有状态性”导致了它并不像Web和应用服务器那么容易扩展。在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding、分片)。同时,流行的分布式系统中间件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小异的。 分布式全局唯一ID 在很多中小项目中,我们往往直接使用数据库自增特性来生成主键ID,这样确实比较简单。而在分库分表的环境中,数据分布在不同的分片上,不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种ID生成算法。 Twitter的Snowflake(又名“雪花算法”) UUID/GUID(一般应用程序和数据库均支持) MongoDB ObjectID(类似UUID的方式) Ticket Server(数据库生存方式,Flickr采用的就是这种方式) 其中,Twitter 的Snowflake算法是笔者近几年在分布式系统项目中使用最多的,未发现重复或并发的问题。该算法生成的是64位唯一Id(由41位的timestamp+ 10位自定义的机器码+ 13位累加计数器组成)。这里不做过多介绍,感兴趣的读者可自行查阅相关资料。 常见分片规则和策略 分片字段该如何选择 在开始分片之前,我们首先要确定分片字段(也可称为“片键”)。很多常见的例子和场景中是采用ID或者时间字段进行拆分。这也并不绝对的,我的建议是结合实际业务,通过对系统中执行的sql语句进行统计分析,选择出需要分片的那个表中最频繁被使用,或者最重要的字段来作为分片字段。 常见分片规则 常见的分片策略有随机分片和连续分片这两种,如下图所示: 当需要使用分片字段进行范围查找时,连续分片可以快速定位分片进行高效查询,大多数情况下可以有效避免跨分片查询的问题。后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移。但是,连续分片也有可能存在数据热点的问题,就像图中按时间字段分片的例子,有些节点可能会被频繁查询压力较大,热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据,很少需要被查询到。 随机分片其实并不是随机的,也遵循一定规则。通常,我们会采用Hash取模的方式进行分片拆分,所以有些时候也被称为离散分片。随机分片的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,后期分片集群扩容起来需要迁移旧的数据。使用一致性Hash算法能够很大程度的避免这个问题,所以很多中间件的分片集群都会采用一致性Hash算法。离散分片也很容易面临跨分片查询的复杂问题。 数据迁移,容量规划,扩容等问题 很少有项目会在初期就开始考虑分片设计的,一般都是在业务高速发展面临性能和存储的瓶颈时才会提前准备。因此,不可避免的就需要考虑历史数据迁移的问题。一般做法就是通过程序先读出历史数据,然后按照指定的分片规则再将数据写入到各个分片节点中。 此外,我们需要根据当前的数据量和QPS等进行容量规划,综合成本因素,推算出大概需要多少分片(一般建议单个分片上的单表数据量不要超过1000W)。 如果是采用随机分片,则需要考虑后期的扩容问题,相对会比较麻烦。如果是采用的范围分片,只需要添加节点就可以自动扩容。 跨分片技术问题 跨分片的排序分页 一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示: 上面图中所描述的只是最简单的一种情况(取第一页数据),看起来对性能的影响并不大。但是,如果想取出第10页数据,情况又将变得复杂很多,如下图所示: 有些读者可能并不太理解,为什么不能像获取第一页数据那样简单处理(排序取出前10条再合并、排序)。其实并不难理解,因为各分片节点中的数据可能是随机的,为了排序的准确性,必须把所有分片节点的前N页数据都排序好后做合并,最后再进行整体的排序。很显然,这样的操作是比较消耗资源的,用户越往后翻页,系统性能将会越差。 跨分片的函数处理 在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候,需要先在每个分片数据源上执行相应的函数处理,然后再将各个结果集进行二次处理,最终再将处理结果返回。如下图所示: 跨分片join Join是关系型数据库中最常用的特性,但是在分片集群中,join也变得非常复杂。应该尽量避免跨分片的join查询(这种场景,比上面的跨分片分页更加复杂,而且对性能的影响很大)。通常有以下几种方式来避免: 全局表 全局表的概念之前在“垂直分库”时提过。基本思想一致,就是把一些类似数据字典又可能会产生join查询的表信息放到各分片中,从而避免跨分片的join。 ER分片 在关系型数据库中,表之间往往存在一些关联的关系。如果我们可以先确定好关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能很好的避免跨分片join问题。在一对多关系的情况下,我们通常会选择按照数据较多的那一方进行拆分。如下图所示: 这样一来,Data Node1上面的订单表与订单详细表就可以直接关联,进行局部的join查询了,Data Node2上也一样。基于ER分片的这种方式,能够有效避免大多数业务场景中的跨分片join问题。 内存计算 随着spark内存计算的兴起,理论上来讲,很多跨数据源的操作问题看起来似乎都能够得到解决。可以将数据丢给spark集群进行内存计算,最后将计算结果返回。 跨分片事务问题 跨分片事务也分布式事务,想要了解分布式事务,就需要了解“XA接口”和“两阶段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在问题的,会导致主从数据不一致。直到5.7x版本中才得到修复。Java应用程序可以采用Atomikos框架来实现XA事务(J2EE中JTA)。感兴趣的读者可以自行参考《分布式事务一致性解决方案》,链接地址: http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency 我们的系统真的需要分库分表吗 读完上面内容,不禁引起有些读者的思考,我们的系统是否需要分库分表吗? 其实这点没有明确的判断标准,比较依赖实际业务情况和经验判断。依照笔者个人的经验,一般MySQL单表1000W左右的数据是没有问题的(前提是应用系统和数据库等层面设计和优化的比较好)。当然,除了考虑当前的数据量和性能情况时,作为架构师,我们需要提前考虑系统半年到一年左右的业务增长情况,对数据库服务器的QPS、连接数、容量等做合理评估和规划,并提前做好相应的准备工作。如果单机无法满足,且很难再从其他方面优化,那么说明是需要考虑分片的。这种情况可以先去掉数据库中自增ID,为分片和后面的数据迁移工作提前做准备。 很多人觉得“分库分表”是宜早不宜迟,应该尽早进行,因为担心越往后公司业务发展越快、系统越来越复杂、系统重构和扩展越困难…这种话听起来是有那么一点道理,但我的观点恰好相反,对于关系型数据库来讲,我认为“能不分片就别分片”,除非是系统真正需要,因为数据库分片并非低成本或者免费的。 这里笔者推荐一个比较靠谱的过渡技术–“表分区”。主流的关系型数据库中基本都支持。不同的分区在逻辑上仍是一张表,但是物理上却是分开的,能在一定程度上提高查询性能,而且对应用程序透明,无需修改任何代码。笔者曾经负责优化过一个系统,主业务表有大约8000W左右的数据,考虑到成本问题,当时就是采用“表分区”来做的,效果比较明显,且系统运行的很稳定。 小结 最后,有很多读者都想了解当前社区中有没有开源免费的分库分表解决方案,毕竟站在巨人的肩膀上能省力很多。当前主要有两类解决方案: 基于应用程序层面的DDAL(分布式数据库访问层) 比较典型的就是淘宝半开源的TDDL,当当网开源的Sharding-JDBC等。分布式数据访问层无需硬件投入,技术能力较强的大公司通常会选择自研或参照开源框架进行二次开发和定制。对应用程序的侵入性一般较大,会增加技术成本和复杂度。通常仅支持特定编程语言平台(Java平台的居多),或者仅支持特定的数据库和特定数据访问框架技术(一般支持MySQL数据库,JDBC、MyBatis、Hibernate等框架技术)。 数据库中间件,比较典型的像mycat(在阿里开源的cobar基础上做了很多优化和改进,属于后起之秀,也支持很多新特性),基于Go语言实现kingSharding,比较老牌的Atlas(由360开源)等。这些中间件在互联网企业中大量被使用。另外,MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术,不过国内使用的企业较少。 中间件也可以称为“透明网关”,大名鼎鼎的mysql_proxy大概是该领域的鼻祖(由MySQL官方提供,仅限于实现“读写分离”)。中间件一般实现了特定数据库的网络通信协议,模拟一个真实的数据库服务,屏蔽了后端真实的Server,应用程序通常直接连接中间件即可。而在执行SQL操作时,中间件会按照预先定义分片规则,对SQL语句进行解析、路由,并对结果集做二次计算再最终返回。引入数据库中间件的技术成本更低,对应用程序来讲侵入性几乎没有,可以满足大部分的业务。增加了额外的硬件投入和运维成本,同时,中间件自身也存在性能瓶颈和单点故障问题,需要能够保证中间件自身的高可用、可扩展。 总之,不管是使用分布式数据访问层还是数据库中间件,都会带来一定的成本和复杂度,也会有一定的性能影响。所以,还需读者根据实际情况和业务发展需要慎重考虑和选择。 作者介绍 丁浪,技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等方面有深入研究和丰富实践经验。热衷于技术研究和分享。
在谈论数据库架构和数据库优化的时候,我们经常会听到“分库分表”、“分片”、“Sharding”…这样的关键词。让人感到高兴的是,这些朋友所服务的公司业务量正在(或者即将面临)高速增长,技术方面也面临着一些挑战。让人感到担忧的是,他们系统真的就需要“分库分表”了吗?“分库分表”有那么容易实践吗?为此,笔者整理了分库分表中可能遇到的一些问题,并结合以往经验介绍了对应的解决思路和建议。 垂直分表 垂直分表在日常开发和设计中比较常见,通俗的说法叫做“大表拆小表”,拆分是基于关系型数据库中的“列”(字段)进行的。通常情况,某个表中的字段比较多,可以新建立一张“扩展表”,将不经常使用或者长度较大的字段拆分出去放到“扩展表”中,如下图所示: 小结 在字段很多的情况下,拆分开确实更便于开发和维护(笔者曾见过某个遗留系统中,一个大表中包含100多列的)。某种意义上也能避免“跨页”的问题(MySQL、MSSQL底层都是通过“数据页”来存储的,“跨页”问题可能会造成额外的性能开销,这里不展开,感兴趣的朋友可以自行查阅相关资料进行研究)。 拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分,则需要改写以前的查询语句,会额外带来一定的成本和风险,建议谨慎。 垂直分库 垂直分库在“微服务”盛行的今天已经非常普及了。基本的思路就是按照业务模块来划分出不同的数据库,而不是像早期一样将所有的数据表都放到同一个数据库中。如下图: 小结 系统层面的“服务化”拆分操作,能够解决业务系统层面的耦合和性能瓶颈,有利于系统的扩展维护。而数据库层面的拆分,道理也是相通的。与服务的“治理”和“降级”机制类似,我们也能对不同业务类型的数据进行“分级”管理、维护、监控、扩展等。 众所周知,数据库往往最容易成为应用系统的瓶颈,而数据库本身属于“有状态”的,相对于Web和应用服务器来讲,是比较难实现“横向扩展”的。数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段。 然后,很多人并没有从根本上搞清楚为什么要拆分,也没有掌握拆分的原则和技巧,只是一味的模仿大厂的做法。导致拆分后遇到很多问题(例如:跨库join,分布式事务等)。 水平分表 水平分表也称为横向分表,比较容易理解,就是将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分。如下图所示: 小结 水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有IO瓶颈。所以,一般不建议采用这种做法。 水平分库分表 水平分库分表与上面讲到的水平分表的思想相同,唯一不同的就是将这些拆分出来的表保存在不同的数据中。这也是很多大型互联网公司所选择的做法。如下图: 某种意义上来讲,有些系统中使用的“冷热数据分离”(将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上,通常默认只提供热点数据的查询),也是类似的实践。在高并发和海量数据的场景下,分库分表能够有效缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源的瓶颈。当然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等) 分库分表的难点 垂直分库带来的问题和解决思路: 跨库join的问题 在拆分之前,系统中很多列表和详情页所需的数据是可以通过sql join来完成的。而拆分后,数据库可能是分布式在不同实例和不同的主机上,join将变得非常麻烦。而且基于架构规范,性能,安全性等方面考虑,一般是禁止跨库join的。那该怎么办呢?首先要考虑下垂直分库的设计问题,如果可以调整,那就优先调整。如果无法调整的情况,下面笔者将结合以往的实际经验,总结几种常见的解决思路,并分析其适用场景。 跨库Join的几种解决思路 全局表 所谓全局表,就是有可能系统中所有模块都可能会依赖到的一些表。比较类似我们理解的“数据字典”。为了避免跨库join查询,我们可以将这类表在其他每个数据库中均保存一份。同时,这类数据通常也很少发生修改(甚至几乎不会),所以也不用太担心“一致性”问题。 字段冗余 这是一种典型的反范式设计,在互联网行业中比较常见,通常是为了性能来避免join查询。 举个电商业务中很简单的场景: “订单表”中保存“卖家Id”的同时,将卖家的“Name”字段也冗余,这样查询订单详情的时候就不需要再去查询“卖家用户表”。 字段冗余能带来便利,是一种“空间换时间”的体现。但其适用场景也比较有限,比较适合依赖字段较少的情况。最复杂的还是数据一致性问题,这点很难保证,可以借助数据库中的触发器或者在业务代码层面去保证。当然,也需要结合实际业务场景来看一致性的要求。就像上面例子,如果卖家修改了Name之后,是否需要在订单信息中同步更新呢? 数据同步 定时A库中的tab_a表和B库中tbl_b有关联,可以定时将指定的表做同步。当然,同步本来会对数据库带来一定的影响,需要性能影响和数据时效性中取得一个平衡。这样来避免复杂的跨库查询。笔者曾经在项目中是通过ETL工具来实施的。 系统层组装 在系统层面,通过调用不同模块的组件或者服务,获取到数据并进行字段拼装。说起来很容易,但实践起来可真没有这么简单,尤其是数据库设计上存在问题但又无法轻易调整的时候。 具体情况通常会比较复杂。下面笔者结合以往实际经验,并通过伪代码方式来描述。 简单的列表查询的情况 伪代码很容易理解,先获取“我的提问列表”数据,然后再根据列表中的UserId去循环调用依赖的用户服务获取到用户的RealName,拼装结果并返回。 有经验的读者一眼就能看出上诉伪代码存在效率问题。循环调用服务,可能会有循环RPC,循环查询数据库…不推荐使用。再看看改进后的: 这种实现方式,看起来要优雅一点,其实就是把循环调用改成一次调用。当然,用户服务的数据库查询中很可能是In查询,效率方面比上一种方式更高。(坊间流传In查询会全表扫描,存在性能问题,传闻不可全信。其实查询优化器都是基本成本估算的,经过测试,在In语句中条件字段有索引的时候,条件较少的情况是会走索引的。这里不细展开说明,感兴趣的朋友请自行测试)。 小结 简单字段组装的情况下,我们只需要先获取“主表”数据,然后再根据关联关系,调用其他模块的组件或服务来获取依赖的其他字段(如例中依赖的用户信息),最后将数据进行组装。 通常,我们都会通过缓存来避免频繁RPC通信和数据库查询的开销。 列表查询带条件过滤的情况 在上述例子中,都是简单的字段组装,而不存在条件过滤。看拆分前的SQL: 这种连接查询并且还带条件过滤的情况,想在代码层面组装数据其实是非常复杂的(尤其是左表和右表都带条件过滤的情况会更复杂),不能像之前例子中那样简单的进行组装了。试想一下,如果像上面那样简单的进行组装,造成的结果就是返回的数据不完整,不准确。 有如下几种解决思路: 查出所有的问答数据,然后调用用户服务进行拼装数据,再根据过滤字段state字段进行过滤,最后进行排序和分页并返回。 这种方式能够保证数据的准确性和完整性,但是性能影响非常大,不建议使用。 查询出state字段符合/不符合的UserId,在查询问答数据的时候使用in/not in进行过滤,排序,分页等。过滤出有效的问答数据后,再调用用户服务获取数据进行组装。 这种方式明显更优雅点。笔者之前在某个项目的特殊场景中就是采用过这种方式实现。 跨库事务(分布式事务)的问题 按业务拆分数据库之后,不可避免的就是“分布式事务”的问题。以往在代码中通过spring注解简单配置就能实现事务的,现在则需要花很大的成本去保证一致性。这里不展开介绍, 感兴趣的读者可以自行参考《分布式事务一致性解决方案》,链接地址: http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency 垂直分库总结和实践建议 本篇中主要描述了几种常见的拆分方式,并着重介绍了垂直分库带来的一些问题和解决思路。读者朋友可能还有些问题和疑惑。 1. 我们目前的数据库是否需要进行垂直分库? 根据系统架构和公司实际情况来,如果你们的系统还是个简单的单体应用,并且没有什么访问量和数据量,那就别着急折腾“垂直分库”了,否则没有任何收益,也很难有好结果。 切记,“过度设计”和“过早优化”是很多架构师和技术人员常犯的毛病。 2. 垂直拆分有没有原则或者技巧? 没有什么黄金法则和标准答案。一般是参考系统的业务模块拆分来进行数据库的拆分。比如“用户服务”,对应的可能就是“用户数据库”。但是也不一定严格一一对应。有些情况下,数据库拆分的粒度可能会比系统拆分的粒度更粗。笔者也确实见过有些系统中的某些表原本应该放A库中的,却放在了B库中。有些库和表原本是可以合并的,却单独保存着。还有些表,看起来放在A库中也OK,放在B库中也合理。 如何设计和权衡,这个就看实际情况和架构师/开发人员的水平了。 3. 上面举例的都太简单了,我们的后台报表系统中join的表都有n个了, 分库后该怎么查? 有很多朋友跟我提过类似的问题。其实互联网的业务系统中,本来就应该尽量避免join的,如果有多个join的,要么是设计不合理,要么是技术选型有误。请自行科普下OLAP和OLTP,报表类的系统在传统BI时代都是通过OLAP数据仓库去实现的(现在则更多是借助离线分析、流式计算等手段实现),而不该向上面描述的那样直接在业务库中执行大量join和统计。 由于篇幅关系,下篇中我们再继续细聊“水平分库分表”相关的话题。 作者介绍 丁浪,技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等方面有深入研究和丰富实践经验。热衷于技术研究和分享。
论坛上的数据库爱好者们,对于数据库底层的各种细节,内幕,等待事件,隐藏参数等津津乐道,对于调整好一条SQL语句使之在查询优化器/查询引擎下能高性能运转具有巨大的满足感成功感,仿佛自己掌握了天下最有价值的真理,驾驭了天下最有难度的技术。但对于设计和开发出这个数据库系统的人来说,他们看到此情此景,只好躲在一边偷偷的笑了。那么问题来了,使用别人数据库的人被称为大师(如:OCM),那么自己写出一个数据库来的人又该称为什么呢?到底谁才是真正的高手呢? 数据库系统优化中的一些观点: “系统性能出现问题进行优化,一定要深入了解数据库内部参数、等待事件、Latch、缓冲池、trace文件、查询/优化引擎等底层细节。” 这种观点往往出自数据库“高手”,这部分人以了解数据库底层实现细节而感到非常骄傲。但是从优化角度讲数据库的等待事件、Latch等指标高等等都只是问题的表象,懂得底层细节和内幕固然是好。但是解决问题的关键往往是在应用层进行优化。 “只要系统参数调整了,性能就能提高。系统优化应该调整那些参数…” 这种观点往往出自于一些偏运维和应用层的DBA,迷恋参数配置来调优。 调整系统参数是非常重要的,但不一定能解决性能问题,否则就不会有去IOE了,问题可能性最大的还是应用设计和开发问题。 同理,很多运维人员和系统架构师比较迷恋“Linux系统调优”。认为对“文件句柄数、磁盘子系统…”那些做了优化,就能提升整个应用系统的性能。其实不然。有些场景下,针对业务特点和应用类型做操作系统调优是能取到立竿见影的效果,但是大多数时候往往提升并不明显。所以最关键的还是找出瓶颈所在,对症下药。*/ “系统性能问题需要从架构上解决,与应用开发关系不大。” 系统性能与各个层面都有关,架构很重要,但应用开发也是非常重要的一环。 影响数据库性能的因素 1.业务需求和技术选型 2.应用系统的开发及架构 3.数据库自身 3.1表结构的设计 3.2查询语句 3.3索引设计 3.4Mysql服务(安装、配置等) 3.5操作系统调优 3.6硬件升级(SSD、更强的CPU、更大的内存) 4.数据架构(读写分离、分库分表等) 在很多情况下,数据库可能是互联网应用系统的瓶颈。但是单纯从数据库角度去做优化,可能未必能达到理想的效果。 说点题外话,最近看到很多公司使用中间件或者分布式数据访问层来做数据库分片,说明也许该公司业务发展很快。但另一个方面,也令人担忧,他们的数据库压力真的已经到了必须切分不可的程度了吗?分库分表真的像科普的那么简单吗?他们能搞定分库分表带来的成本和问题吗?有没有更合适的优化方法呢? 当然是有的。其实“过度设计”和“提前优化”就是系统万恶之源。
做过APP产品的技术人员都知道,APP应用属于一种C/S架构的,所以在做多版本兼容,升级等处理则比较麻烦,不像web应用那么容易。下面将带大家分析几种常见的情况: 小改动或者新加功能的 这种情况,数据库结构和API程序一般是可以兼容多版本的,所以不用强制升级,可以坐到多版本共存。 尽量采用数据库层面新增字段和API的方式,应用程序层面就可以兼容了。当然,API层面也可以部署多个版本来同时提供,但这个不是必须的 但最重要的是数据库层面的表结构那些能够兼容到。 或者: 总结: 数据库层面,尽量采用新增字段,而不是修改字段的原则,避免影响以前的业务。 而服务端程序层面,API层尽量设计灵活,接入层可以支持“路由”最佳。主要有几种思路,1. 在API方法中通过新增参数或者直接新增API方法(也可以理解为重载)。 较少的改动可以这样去做处理,但是改动多了会比较麻烦,不利用扩展。 2. 代码分不同分支版本,API部署不同子站点。通过api.xx.com/V1 和api.xx.com/V2方式访问,或者通过apiV1.xxx.com,apiV2.xxx.com等方式区分访问。当然,也可以在APP不同版本中请求时传入标识,服务端通过Nginx或者APIGateWay等来实现服务路由。 这种直观上更加清晰,工作量也会大一些,会增加一定的维护和管理成本。 后端的原子服务,也需要尽可能支持多版本。 如果是大改动,底层数据结构都不兼容,那只能提示强制升级了 如果是强制升级,就不会有多版本共存的问题了, 也不会有多套数据库,也不会存在数据同步的问题。 只需要这样操作: 在苹果提交审核之前,我们事先准备好“新版本的数据结构和对象(view、proc、function等)脚本、迁移脚本或者程序、程序发布文件”等。 1. 部署1个具有新表结构和对象的测试数据库(预发布环境)。 2. 部署1个新的API站点(不同域名,建议域名中使用版本号区分。或者采用不同子站点的方式),配置连接测试库和测试的(API站点、DB、缓存、ES、Nosql …这些单独有一套预发布环境) 3. 等苹果审核通过之后,更新最新的程序,数据库结构等到生产环境,并将API地址的域名的指向切换到生产环境中(网站、admin后台等等可以直接发布了),运营人员在苹果商店点击上架操作,服务端升级完成(APP端会有强制更新的提示)。当然,这个过程中可能会造成短暂的停机时间,所以一般是选择在晚上操作。 4. 提前将安卓最新APK放到我们的下载站或镜像站,在3步服务端程序等都发布完成后,在运营后台开启安卓版本的强制升级提示(从我们的下载站或者镜像站下载APK升级)。 这样,旧版本的安卓用户,会强制升级到新的版本,不会影响到使用。 与此同时,运营人员也需要在各大安卓市场去分发和上架最新的apk包,提供最新的其他渠道下载,避免用户下载到旧程序,然后又提示强制升级影响体验。 如果是大改动,数据库结构和API程序都不兼容, 又不想去做强制升级,就会有多版本共存的问题 那就只能去部署两套(或者更多个版本)数据库,而且对于用户产生内容和时效性要求较高的,需要双向(甚至多向)去做同步。核心问题其实是数据库有状态,这种是很困难的。 这种很容易出问题,容易出现冲突和数据不一致。 而且数据结构不一样的情况下,是很难去兼容的。 所以,对于改动较大的,产品新增了重量级新功能的,业务层面或者底层表结构上都不兼容的,建议是要做强制升级的。 或者: 2.如果是大改动,底层数据结构都不兼容,那只能提示强制升级了 如果是强制升级,就不会有多版本共存的问题了, 也不会有多套数据库,也不会存在数据同步的问题。 只需要这样操作: 在苹果提交审核之前,我们事先准备好“新版本的数据结构和对象(view、proc、function等)脚本、迁移脚本或者程序、程序发布文件”等。 1. 部署1个具有新表结构和对象的测试数据库(预发布环境)。 2. 部署1个新的API站点(不同域名,建议域名中使用版本号区分。或者采用不同子站点的方式。通过api.xx.com/V1 和api.xx.com/V2方式区分),配置连接测试库和测试的(API站点、DB、缓存、ES、Nosql …这些单独有一套预发布环境) 3. 等苹果审核通过之后,更新最新的程序,数据库结构等到生产环境,并将API地址的域名的指向切换到生产环境中(网站、admin后台等等可以直接发布了),运营人员在苹果商店点击上架操作,服务端升级完成(APP端会有强制更新的提示)。当然,这个过程中可能会造成短暂的停机时间,所以一般是选择在晚上操作。 4. 提前将安卓最新APK放到我们的下载站或镜像站,在3步服务端程序等都发布完成后,在运营后台开启安卓版本的强制升级提示(从我们的下载站或者镜像站下载APK升级)。 这样,旧版本的安卓用户,会强制升级到新的版本,不会影响到使用。 与此同时,运营人员也需要在各大安卓市场去分发和上架最新的apk包,提供最新的其他渠道下载,避免用户下载到旧程序,然后又提示强制升级影响体验。 3.如果是大改动,数据库结构和API程序都不兼容, 又不想去做强制升级,就会有多版本共存的问题 那就只能去部署两套(或者更多个版本)数据库,而且对于用户产生内容和时效性要求较高的,需要双向(甚至多向)去做同步。核心问题其实是数据库有状态,这种是很困难的。 这种很容易出问题,容易出现冲突和数据不一致。 而且数据结构不一样的情况下,是很难去兼容的。 所以,对于改动较大的,产品新增了重量级新功能的,业务层面或者底层表结构上都不兼容的,建议是要做强制升级的。
在分布式系统中,同时满足“一致性”、“可用性”和“分区容错性”三者是不可能的。分布式系统的事务一致性是一个技术难题,各种解决方案孰优孰劣? 在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转账的案例。传统的企业开发,系统往往是以单体应用形式存在的,也没有横跨多个数据库。 我们通常只需借助开发平台中特有数据访问技术和框架(例如Spring、JDBC、ADO.NET),结合关系型数据库自带的事务管理机制来实现事务性的需求。关系型数据库通常具有ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。 而大型互联网平台往往是由一系列分布式系统构成的,开发语言平台和技术栈也相对比较杂,尤其是在SOA和微服务架构盛行的今天,一个看起来简单的功能,内部可能需要调用多个“服务”并操作多个数据库或分片来实现,情况往往会复杂很多。单一的技术手段和解决方案,已经无法应对和满足这些复杂的场景了。 分布式系统的特性 对分布式系统有过研究的读者,可能听说过“CAP定律”、“Base理论”等,非常巧的是,化学理论中ACID是酸、Base恰好是碱。这里笔者不对这些概念做过多的解释,有兴趣的读者可以查看相关参考资料。CAP定律如下图: 在分布式系统中,同时满足“CAP定律”中的“一致性”、“可用性”和“分区容错性”三者是不可能的,这比现实中找对象需同时满足“高、富、帅”或“白、富、美”更加困难。在互联网领域的绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。 分布式事务 提到分布式系统,必然要提到分布式事务。要想理解分布式事务,不得不先介绍一下两阶段提交协议。先举个简单但不精准的例子来说明: 第一阶段,张老师作为“协调者”,给小强和小明(参与者、节点)发微信,组织他们俩明天8点在学校门口集合,一起去爬山,然后开始等待小强和小明答复。 第二阶段,如果小强和小明都回答没问题,那么大家如约而至。如果小强或者小明其中一人回答说“明天没空,不行”,那么张老师会立即通知小强和小明“爬山活动取消”。 细心的读者会发现,这个过程中可能有很多问题的。如果小强没看手机,那么张老师会一直等着答复,小明可能在家里把爬山装备都准备好了却一直等着张老师确认信息。更严重的是,如果到明天8点小强还没有答复,那么就算“超时”了,那小明到底去还是不去集合爬山呢? 这就是两阶段提交协议的弊病,所以后来业界又引入了三阶段提交协议来解决该类问题。 两阶段提交协议在主流开发语言平台,数据库产品中都有广泛应用和实现的,下面来介绍一下XOpen组织提供的DTP模型图: XA协议指的是TM(事务管理器)和RM(资源管理器)之间的接口。目前主流的关系型数据库产品都是实现了XA接口的。JTA(Java Transaction API)是符合X/Open DTP模型的,事务管理器和资源管理器之间也使用了XA协议。 本质上也是借助两阶段提交协议来实现分布式事务的,下面分别来看看XA事务成功和失败的模型图: 在JavaEE平台下,WebLogic、Webshare等主流商用的应用服务器提供了JTA的实现和支持。而在Tomcat下是没有实现的(其实笔者并不认为Tomcat能算是JavaEE应用服务器),这就需要借助第三方的框架Jotm、Automikos等来实现,两者均支持spring事务整合。 而在Windows .NET平台中,则可以借助ado.net中的TransactionScop API来编程实现,还必须配置和借助Windows操作系统中的MSDTC服务。如果你的数据库使用的mysql,并且mysql是部署在Linux平台上的,那么是无法支持分布式事务的。 由于篇幅关系,这里不展开,感兴趣的读者可以自行查阅相关资料并实践。 总结:这种方式实现难度不算太高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况。但分布式事务对性能的影响会比较大,不适合高并发和高性能要求的场景。 提供回滚接口 在服务化架构中,功能X,需要去协调后端的A、B甚至更多的原子服务。那么问题来了,假如A和B其中一个调用失败了,那可怎么办呢? 在笔者的工作中经常遇到这类问题,往往提供了一个BFF层来协调调用A、B服务。如果有些是需要同步返回结果的,我会尽量按照“串行”的方式去调用。如果调用A失败,则不会盲目去调用B。如果调用A成功,而调用B失败,会尝试去回滚刚刚对A的调用操作。 当然,有些时候我们不必严格提供单独对应的回滚接口,可以通过传递参数巧妙的实现。 这样的情况,我们会尽量把可提供回滚接口的服务放在前面。举个例子说明: 我们的某个论坛网站,每天登录成功后会奖励用户5个积分,但是积分和用户又是两套独立的子系统服务,对应不同的DB,这控制起来就比较麻烦了。解决思路: 1.把登录和加积分的服务调用放在BFF层一个本地方法中。 2.当用户请求登录接口时,先执行加积分操作,加分成功后再执行登录操作 3.如果登录成功,那当然最好了,积分也加成功了。如果登录失败,则调用加积分对应的回滚接口(执行减积分的操作)。 总结:这种方式缺点比较多,通常在复杂场景下是不推荐使用的,除非是非常简单的场景,非常容易提供回滚,而且依赖的服务也非常少的情况。 这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。 本地消息表 这种实现方式的思路,其实是源于ebay,后来通过支付宝等公司的布道,在业内广泛使用。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。 举个经典的跨行转账的例子来描述。 第一步伪代码如下,扣款1W,通过本地事务保证了凭证消息插入到消息表中。 第二步,通知对方银行账户上加1W了。那问题来了,如何通知到对方呢? 通常采用两种方式: 1. 采用时效性高的MQ,由对方订阅消息并监听,有消息时自动触发事件 2. 采用定时轮询扫描的方式,去检查消息表的数据 两种方式其实各有利弊,仅仅依靠MQ,可能会出现通知失败的问题。而过于频繁的定时轮询,效率也不是最佳的(90%是无用功)。所以,我们一般会把两种方式结合起来使用。 解决了通知的问题,又有新的问题了。万一这消息有重复被消费,往用户帐号上多加了钱,那岂不是后果很严重? 仔细思考,其实我们可以消息消费方,也通过一个“消费状态表”来记录消费状态。在执行“加款”操作之前,检测下该消息(提供标识)是否已经消费过,消费完成后,通过本地事务控制来更新这个“消费状态表”。这样子就避免重复消费的问题。 总结:上诉的方式是一种非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息会给数据库造成压力。所以,在真正的高并发场景下,该方案也会有瓶颈和限制的。 MQ(非事务消息) 通常情况下,在使用非事务消息支持的MQ产品时,我们很难将业务操作与对MQ的操作放在一个本地事务域中管理。通俗点描述,还是以上述提到的“跨行转账”为例,我们很难保证在扣款完成之后对MQ投递消息的操作就一定能成功。这样一致性似乎很难保证。 仔细分析,其实并非完全不可能。先从消息生产者这端来分析,请看伪代码: 根据上述代码及注释,我们来分析下可能的情况: 1. 操作数据库成功,向MQ中投递消息也成功,皆大欢喜 2. 操作数据库失败,不会向MQ中投递消息了 3. 操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚 从上面分析的几种情况来看,貌似问题都不大的。那么我们来分析下消费者端面临的问题: 1. 消息出列后,消费者对应的业务操作要执行成功。如果业务执行失败,消息不能失效或者丢失。需要保证消息与业务操作一致 2. 尽量避免消息重复消费。如果重复消费,也不能因此影响业务结果 如何保证消息与业务操作一致,不丢失? 主流的MQ产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制的(有些MQ可以自定义重试次数) 如何避免消息被重复消费造成的问题? 1. 保证消费者调用业务的服务接口的幂等性 2. 通过消费日志或者类似状态表来记录消费状态,便于判断(建议在业务上自行实现,而不依赖MQ产品提供该特性) 总结:这种方式比较常见,性能和吞吐量是优于使用关系型数据库消息表的方案。如果MQ自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的。不过在没有充分测试的情况下,不建议在交易业务中直接使用。 MQ(事务消息) 举个例子,Bob向Smith转账,那我们到底是先发送消息,还是先执行扣款操作? 好像都可能会出问题。如果先发消息,扣款操作失败,那么Smith的账户里面会多出一笔钱。反过来,如果先执行扣款操作,后发送消息,那有可能扣款成功了但是消息没发出去,Smith收不到钱。除了上面介绍的通过异常捕获和回滚的方式外,还有没有其他的思路呢? 下面以阿里巴巴的RocketMQ中间件为例,分析下其设计和实现思路。 RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。细心的读者可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。如下图: 总结:据笔者的了解,各大知名的电商平台和互联网公司,几乎都是采用类似的设计思路来实现“最终一致性”的。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,所以需二次开发或者新造轮子。比较遗憾的是,RocketMQ事务消息部分的代码也并未开源,需要自己去实现。 其他补偿方式 集成过支付宝交易接口的同学都知道,我们一般会在支付宝的回调页面和接口里,解密参数,然后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当我们回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会停止回调请求。否则,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。 其实这就是一个很典型的补偿例子,跟一些MQ重试补偿机制很类似。 一般成熟的系统中,对于级别较高的服务和接口,整体的可用性通常都会很高。如果有些业务由于瞬时的网络故障或调用超时等问题,那么这种重试机制其实是非常有效的。 当然,考虑个比较极端的场景,假如系统自身有bug或者程序逻辑有问题,那么重试1W次那也是无济于事的。那岂不是就发生了“明明已经付款,却显示未付款不发货”类似的悲剧? 其实为了交易系统更可靠,我们一般会在类似交易这种高级别的服务代码中,加入详细日志记录的,一旦系统内部引发类似致命异常,会有邮件通知。同时,后台会有定时任务扫描和分析此类日志,检查出这种特殊的情况,会尝试通过程序来补偿并邮件通知相关人员。 在某些特殊的情况下,还会有“人工补偿”的,这也是最后一道屏障。 小结 上诉的几种方案中,笔者也大致总结了其设计思路,优势,劣势等,相信读者已经有了一定的理解。其实分布式系统的事务一致性本身是一个技术难题,目前没有一种很简单很完美的方案能够应对所有场景。具体还是要使用者根据不同的业务场景去抉择。 老司机介绍 丁浪,现就职于某垂直电商平台,担任技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等方面有深入研究和丰富实践经验。热衷于技术研究和分享。 本文首发于InfoQ,版权所有,请勿转载。 地址:http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency 如果看完文章您还意犹未尽,可以扫码关注向我提问。
最近在招聘中,发现不少人对BIO、NIO、AIO等理解非常模糊,觉得有必要写文章来纠正下很多人的误解。 在谈这些之前,非常有必要先介绍下Unix 5种IO模型: 阻塞: 阻塞是最常用的IO模型,默认情况下所有的文件操作都是阻塞的。以套接字编程为例。在进程空间中调用recvfrom,其系统调用直到数据报文到达且被拷贝到应用程序进程的缓存区(或者发生错误)后才返回,期间一直在等待。进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。有一张很经典的图: 非阻塞: 调用recvfrom从应用层到内核的过程中,如果该缓冲区没有数据的话,则直接返回一个EWOULDBLOCK的错误,一般会轮询的进行检查状态,看内核空间有没有数据来。直到有数据,最后完成拷贝。如下图: IO多路复用: Linux系统提供的select/poll/epoll,进程将一个或者多个FD(文件描述符)传递给一个或者多个poll/select系统调用,阻塞在select。select和poll可以帮助侦听很多的FD是否准备就绪。但是,select和poll是顺序扫描去检查FD的就绪状态,效率比较低,而且支持的FD数量有限(没记错的话,默认好像是1024还是2048,具体记不清)。而epoll是通过事件驱动的方式,当有FD准备就绪的时候,立即回调函数rollback。如图: 谈到epoll,不得不提一个经典的问题,apache和nginx的对比,为什么nginx比apache效率高很多,这就是根本的原因。 信号驱动: 这种模型在实际应用的非常少,这里不做过多介绍,可以看图: 异步: 告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核空间拷贝到自己的缓冲区)通知。异步IO的主要特点是完成操作后主动通知。如图: 好,上面的可能有点抽象。下面用通俗点的语言来总结一下阻塞,非阻塞,同步,异步 阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待; 同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞; 异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。 再举个网上流传的,非常容易理解的例子: 老张爱喝茶,废话不说,煮开水。 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。 1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻 2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大 4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。 所谓同步异步,只是对于水壶而言。普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。 同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。所谓阻塞非阻塞,仅仅对于老张而言。立等的老张, 阻塞;看电视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。 虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
早期的文章中,曾经提到过性能调优中的DETECT方法论,这里先简单回顾一下DETECT方法论。 Discover the problem :发现问题 Explore the conditions:探究原因 Track down possible approaches:提供可能解决的方式 Execute the most likely approach:执行最好可能的解决方式 Check of success :确认是否成功(如果没有成功,反复执行上面的步骤) Tie up loose ends :完成剩余工作 首先看 Discover ,发现问题 是否已经简洁的描述了整个问题? 用户当前的基线在哪里? 用户期望的是什么? 并不是所以的问题都可以解决的 再看Explore,探究原因 取得证据 SqlProfiler跟踪 /Sql Trace DMV 和 DMF ShowPlan执行计划输出 各种系统的性能计数器 sqlserver特有的性能计数器 检查最明显的问题(探究问题先不要深入) Track down 提供可能的解决方式 第一阶段,建立证明假设的计划 第二阶段,建立解决问题的计划 执行最有可能的解决方案 第一阶段,执行测试计划来证明你的假设 第二阶段,执行解决问题的方案 Check,确认成功与否 第一阶段 你的计划证明了你的假设,还是推翻了他? 第二阶段 你的计划是否改变了现象? 瓶颈是否转移了? 解决方案是否符合你原先的目标? 记住:该过程通常是递归反复执行的 Tie up 完成剩余工作,收尾 性能调优是否达会有无法预计的边际效应? 所做的修改是否真正解决了问题,会不会短时间又碰到相同问题? 还需要做哪些跟踪的工作? 上面说了那么方法论,虽然很枯燥,但是还是有指导意义的。下面来点实际的知识。 瓶颈分析 瓶颈的定义 瓶颈=需求达到的速率>实际处理量 流程: 决定是卡在哪一个点上 决定在队列中等待的状况 减少输入(需求达到的速率)或是增加同时的处理量 决定收益 决定成本 常见瓶颈的监视任务 监视内存占用 监视线程和CPU使用 监视硬盘IO 监视低性能查询 监视存储过程、sql和用户活动 监视当前锁定和用户互动 建立性能调优的计划 性能调优是反复的过程,一而再,再而三的循环,一次又一次趋近的修正,要利用文字记录以说明 理出头绪,突显问题并证明 系统的逼近目标 有共识,知道彼此谈论的标地 能够汇总比较 当系统出现的多个瓶颈,找出最关键的,成本最低的先执行调优 执行性能调优的计划时,要确定对线上生产环境的影响 方法论--缩小 性能调优中常用的工作 windows事件查看器 windows系统监视器 SSMS中的当前活动窗口 T-SQL工具 Sql Profiler 查询分析器 数据库引擎优化顾问 windows事件查看器,主要是查看以下的事件日志 windows应用程序日志 windows系统日志 windows安全日志 windows系统监视器能够跟踪: sql server I/O sql server 内存 sql server用户 sql server 锁 复制活动 SSMS中的活动监视器: 活动用户任务 资源等待 数据文件I/O 耗费大量资源的查询 T-SQL工具: 系统存储过程 全局变量 T-SQL语句 DBCC 跟踪标记 DMF/DMF Sql Profiler,跟踪并捕获sqlserver事件 选择需要跟踪的事件 选择跟踪模版 选择需要捕获的数据 有意义对数据进行分类 查询分析器 显示查询执行计划 显示服务器跟踪 显示服务器端统计信息 显示客户端统计信息 数据库引擎优化顾问 分析瓶颈 给出建议sql语句(索引和统计信息)
接着上篇的继续 在做扩展满足了基本的性能需求后,我们会逐渐关注“可用性”(也就是我们通常听别人吹牛时说的SLA、几个9)。如何保证真正“高可用”,也是个难题。 几乎主流的大中型互联网公司,都会有用到类似的架构,只是节点数不同而已。 还有一招用的比较多的,那就是动静分离。可以需要开发人员配合(把静态资源放独立站点下),也可以不需要开发人员配合(利用7层反向代理来处理,根据后缀名等信息来判断资源类型)。有了单独的静态文件服务器之后,存储也是个问题,也需要扩展。多台服务器的文件怎么保持一致,买不起共享存储怎么办?分布式文件系统也派上用场了。 还有一项目前国内外用的非常普遍的技术CDN加速。目前该领域竞争激烈,也已经比较便宜了。国内南北互联网问题比较严重,使用CDN可以有效解决这个问题。 CDN的基本原理并不复杂,可以理解为智能DNS+Squid反向代理缓存 ,然后需要有很多机房节点提供访问。 截止目前为止,都没有怎么去改动应用程序的架构,或者说通俗点,都不怎么需要大面积的修改代码。 如果上面那些手段都用光了,还是支撑不住怎么办?不停的加机器也不是办法啊? 随着业务越来越复杂,网站的功能越来越多,虽然部署层面是采用的集群,但是应用程序架构层面还是“集中式”的,这样会导致很多耦合,不便于开发、维护,而且容易“一荣俱损”。所以,通常会把网站拆分出不同的子站点来单独宿主。 应用都拆了,由于单个数据库的连接,QPS,TPS,I/O处理能力都非常有限,DB层面也可以去做垂直分库操作 拆分应用和DB之后,其实还是会有很多问题。不同的站点,里面可能会有相同逻辑和功能的代码。当然,对于一些基础的功能我们可以封装DLL或者Jar包去到处提供引用,但是这种强依赖也很容易造成一些问题(版本问题、依赖关系等处理起来非常麻烦)。这样,传说中的SOA的价值就得到体现了。 应用、服务之间还是会出现一些依赖问题,这时候,高吞吐量的解耦利器出现了 最后,还介绍一个大型互联网公司都用的绝技--分库分表。个人经验,不是业务发站和各方面非常迫切,不要轻易走这一步。 因为分库分表谁都会干,关键是拆完之后怎么办。目前,市面上还没有完全开源免费的方案,能让你一劳永逸地解决数据库拆分问题。
互联网上有很多关于网站架构的各种分享,有些主要是从运维和基础架构的角度去分析的(堆机器,做集群),太关注技术细节实现,普通的开发人员基本看不太懂。 本文上篇将主要介绍大型网站基础架构的扩展,下篇则重点从应用程序的角度去介绍网站架构的扩展和演变。 草根时期,快速开发网站并上线。当然,通常只是先试水,用户规模也没有形成,经济能力和投入也非常有限。 有一定的业务量和用户规模了,想提升网站速度,于是,缓存出场了。 市场反响还不错,用户量每天在增长,数据库疯狂读写,逐渐发现一台服务器快撑不住了。于是,决定把DB和APP做分离。 单台数据库也感觉快撑不住了,一般都会尝试做“读写分离”。由于大部分互联网“读多写少”的特性所决定的。Salve的台数,取决于按业务评估的读写比例。 数据库层面是缓解了,但是应用程序层面也出现了瓶颈,由于访问量增大,加上早期程序员水平有限写的代码也很烂,人员流动性也大,很难去维护和优化。所以,很常用的办法还是“堆机器”。 加机器谁都会加,关键是加完之后得有效果,加完之后可能会引发一些问题。例如非常常见的:页面输出缓存和本地缓存的问题,Session保存的问题...... 到这里,已经基本做到了DB层面和应用层面的横向扩展了,可以开始关注一些其它方面,例如:站内搜索的精准度,对DB的依赖,开始引入全文索引。 Java领域用的较多的是Lucene、Solr等,而php领域用的比较多的是sphinx/coreseek。 到目前为止,一个能够承载日均百万级访问量的中型网站架构基本介绍完了。当然,每一步扩展里面都会有很多技术实现的细节,后续有时间会写文章单独去剖析那些细节。 下篇我们继续。
在上文中,主要介绍了SOA的概念,什么叫做“服务”,“服务”应该具备哪些特性。本篇中,我将介绍SOA的一种很常见的设计实践--基于服务总线的设计。 基于服务总线的设计 基于总线的设计,借鉴了计算机内部硬件组成的设计思想(通过总线传输数据)。在分布式系统中,不同子系统之间需要实现相互通信和远程调用,比较直接的方式就是“点对点”的通信方式,但是这样会暴露出一些很明显的问题:系统之间紧密耦合、配置和引用混乱、服务调用关系错综复杂、难以统一管理、异构系统之间存在不兼容等。而基于总线的设计,正是为了解决上述问题。总线则作为中枢系统,提供统一的服务入口,并实现了服务统一管理、服务路由、协议转换、数据格式转换等功能。这样能够将不同系统有效地连接起来,并大大降低了连接数(每个子系统只需要和总线建立连接)和系统间连接拓扑的复杂度。如图所示: 基于服务总线的设计,往往需要ESB(Enterprise Service Bus,企业服务总线)产品来充当基础设施。ESB采用了“总线”这样一种模式来管理和简化应用之间的集成拓扑结构,以广为接受的开放标准为基础来支持应用之间在消息、事件和服务的级别上动态的互连互通。 ESB是一种在松散耦合的服务和应用之间标准的集成方式。 在其内部设计和实现中,通常会应用到一些经典的架构模式,例如:Broker模式、消息总线模式、管道过滤器模式、发布订阅模式等。这里,我们将重点介绍这几种核心的架构模式。 Broker模式 Broker可以看作是服务总线中的一部分,通常应用于同步调用的场景(调用服务后阻塞,等待远程服务响应完成后再返回结果)。Broker可以看作是代理、分发,意味着对服务的调用,可以直接通过Broker来完成。在软件架构设计中,向已经存在(引用或者调用)关系的两者中间加入“第三者”是一种常见的解耦方式,显然,Broker也不例外。如下图所示: 为了进一步加深读者对Broker模式的理解,这里通过举例来说明: 在现实生活中,我们需要租房子(可以看作是一种服务调用),可以通过几种途径来完成。第一,我们可以先在网上了解,然后跑到目标小区去询问和看房,最后再找房东签合同等;第二,也可以直接找附近的地产中介,说出我们的要求和预算,请中介直接帮我们搞定一切。很明显,第一种方式,需要自己对目标有足够的了解而且还要亲自去找房东签合同(依赖具体,可以看作是紧密耦合),而第二种方式,仅仅需要告知中介需要什么即可完成租房,甚至都不需要知道哪个小区有房子、房东到底是谁等这些信息(依赖抽象,并通过第三者来实现解耦)。当然,找中介虽然省事,也会产生额外的经济开销。同理,通过Broker来调用服务也可能会产生一定的开销。 消息总线模式 SOA系统有三种基础组件:消息总线、信息转换/处理引擎和存储库。一般来说,这些组件会集成到ESB中,而在这些基础组件中,消息总线是最重要的。消息总线主要应用于异步通信场景(投递消息后立刻返回结果,而不用等待远程系统返回执行成功),可以大大提升响应速度和系统吞吐量。当然,消息总线也同时支持同步通信模式。基于消息总线模式的设计中,消息中间件屏蔽了系统间底层通信的细节,是比较典型的(异步)松耦合的架构。 在企业中,随着业务的逐渐发展,各大系统之间的调用交互变得非常频繁,关系错综复杂。 想象如果有几十或者上百个系统(在保险、金融领域很常见),将变得难以维护。通过引入消息中间件,便能有效的解决这些问题。不同系统之间,只需要连接到消息总线,能保证成功投递/接收消息即可。对于消息投递者(生产者)而言,根本不用关心消息的接收者(消费者)到底有哪些,也不用关心消费者接收到消息之后该如何处理。对于接收者(消费者)而言,只需要关注与自己有关的消息,接收到消息后并做出相应的处理即可。如下图所示: 比较典型的应用场景:张三在CRM系统中录入了一条客户签约订单的信息并审核通过后,CRM系统会向消息总线投递一条消息(消息中包含必要信息,例如员工ID,订单编号,产品编号和交易金额等,而CRM系统不用关心有哪些系统会接受到该消息)。员工绩效奖金管理系统订阅了该消息主题,收到消息通知后开始处理(给张三执行加奖金操作),同时,进销存系统收到消息通知后也会即时地更新商品库存的数量。这样,便实现异构系统之间的松耦合、异步通信。当然,真实场景可能比这里更复杂,但是实现思路上都大同小异。 在理解了上述实例之后,有必要了解下Java EE中被广泛应用和实现的JMS(Java消息服务)。 JMS是一种关于消息的规范,业界流行的ActiveMQ则是实现了JMS规范的开源消息总线。 JMS支持两种模式:发布/订阅模式和队列模式(点对点模式)。其中,发布/订阅模式借鉴了现实生活中的出版社(发布图书)和读者(订阅图书),消息的消费者(读者)只需要订阅自己感兴趣的消息(图书)即可,消息生产者(出版社)生产(出版)了消费者(读者)感兴趣的新消息(新书)后,会通知消费者(读者)接受处理。在JMS发布/订阅模式中,通常以Topic(主题)来标识消息,是一对多的模式,意味着同一个主题可以同时被多个消费者来订阅和消费。而在JMS 队列模型中,通常以Queue name来标识消息,是一对一的模型,意味着同一条消息只能被一个消费者接收并消费(且只能被消费一次)。在生产者和消费者都是集群的环境中,通常需要将这两种模式结合起来使用,情况会复杂很多,而且需要考虑容错性、负载均衡、消息一致性、消息优先级等复杂的问题。 业界主流的消息总线(消息中间件)产品,普遍支持消息过滤、自动重试、分布式事务、持久化、消息优先级、消息回溯、(生产者/消费者/中间件自身)集群、故障转移等高级特性。 总结 基于总线架构的主要优势在于: l 可扩展性 使用消息架构,可以在不影响现有应用的情况下增加或移除应用。 l 低复杂度 每个应用只需要和总线通信,只有1个连接点,降低了应用程序集成的复杂度。 l 灵活性 对于构成复杂处理的应用程序通信组来说,只需要改变配置和控制路由参数就能满足业务逻辑或者需求变更,而不需要更改服务本身。 l 松耦合 应用程序直接和消息总线通信,不依赖其它应用程序,因此可以替换和修改。 l 可扩展性 可以把多个应用程序附加到总线上,进行并发处理,达到负载均衡的目的。 基于总线的模型,可以面向同构和异构系统(跨平台)。当前主要应用于传统企业应用的整合与系统集成中(例如:电信、保险、金融等行业)。由于基于总线的模型是一种集中式的架构,总线自身容易形成性能瓶颈,而且在实现高可用和容错性方面的复杂度和成本相对较高。所以,该模式并不是很适合于高并发、高性能、高吞吐量的互联网应用。 注:关于消息总线(消息中间件)的知识点很多,在实际应用中还有很多更加深入和复杂的细节问题。由于篇幅问题,笔者就不做过多的细节介绍和展开,感兴趣的读者,可以自行去查阅相关资料或者参考开源产品,做深入的学习和研究。 ESB产品主要包括:开源Mule ESB、ServiceMix、JBoss ESB,商业的Oracle ESB、BEA AquaLogic ServiceBus,.NET领域的NService Bus、MassTransit等。 MQ产品主要包括:ActiveMQ、RabbiteMQ、ZeroMQ、RocketMQ、Kafka、MSMQ等。
曾今SOA的概念犹如今日“云计算、大数据”一样,被炒得火热,不少企业便纷纷响应,并宣称会拥抱和实施SOA。而事实上,业界出现了两种极端:一种是由于各类文章和书籍关于SOA的描述往往太过抽象,再加上各大厂商的呼吁,使得SOA往往显得“高大上”,令不少企业和架构师们望而却步。第二种恰好相反,有部分人却认为SOA无非是“新瓶装旧酒”。 个人理解,SOA在宏观上确实太复杂,因为它涉及到的不仅仅是技术和架构本身。而从技术的视角来看,并非难以落地。 SOA全称“面向服务架构”,它提供的是一种架构风格和理念,而并非是一种技术或者产品。并不是说项目中用了WebService、WCF、Hessian、RMI之类的就是SOA了。 通俗点来讲,SOA提倡将不同应用程序的业务功能封装成“服务”并宿主起来,通常以接口和契约的形式暴露并提供给外界应用访问(通过交换消息),达到不同系统可重用的目的。 流行的WebService等可以看作是实现SOA基础设施的技术方法。当然,实践SOA不仅需要解决服务调用的问题,还包括服务编排、服务治理、服务路由、服务监控等一系列的问题。在大型分布式系统中,SOA被广泛实践,但是在不同的应用场景中,设计方法也大不相同。 SOA是一个组件模型,它能将不同的服务通过定义良好的接口和契约联系起来。服务是SOA的基石,在开始服务设计和SOA实践之前,有必要先了解服务的概念以及服务的常见特性。 何为服务 服务的概念非常宽泛,在宏观上,服务的理解是“为他人做事,满足他人需要,而且通常是不以实物形式提供劳动的…”。在SOA系统中,服务指的是应用程序的功能单元,它通常体现了业务功能。服务是一种抽象,它向服务使用者隐藏了服务内部的实现细节。根据服务设计的基本原则,服务可能会具有以下特性: l 自治(理)性 服务应该是独立部署和运行存在的,且边界清晰,应尽量减少对外部的引用和依赖。 l 粗粒度 服务调用是需要开销的,这也是实现松耦合的分布式系统必须付出的代价。因此,应尽量通过一次服务调用传输所有需要的数据,而不是分多次去调用服务和组装数据。 l 可见性 服务是对外提供的,必须在某公共的地方可搜寻和发现,且服务要有必要的描述。 l 无状态 服务不应该依赖于其他服务的上下文、会话等,尽量减少不必要的状态管理流程所带来的资源消耗。但是,对于业务流程服务而言,状态数据是不可避免的。 l 幂等性 当消费者调用服务后,服务调用可能会有“成功、失败、超时”这三种状态,当服务并没有最终响应完成时,消费者可以尝试反复地调用服务,这样仍不会影响到最终结果。 l 可重用性 服务应该是可以被重用的,相同功能应可以调用相同的服务,这也是软件设计的原则。 l 可组合 服务是可以被当作成一个步骤的,服务也可以调用其它的服务。这样能够灵活的组合。 有关服务的“粗粒度、无状态、幂等性”等特性,一直是饱受争议的话题,可谓见仁见智。这里有必要说明下,这些特性并不是服务不可或缺的,应当在实践中根据需求来取舍。 SOA所面临的问题 SOA架构将公共的业务拆分出来,形成可共用的服务,最大程度的保障了代码和逻辑的复用,避免了系统的重复建设,并且让应用程序的部署找到了一种持续可扩展的方案,给应用抗负载能力带来了质的飞跃。 SOA架构所面临的一大问题就是如何解决集成服务应用普遍存在的一致性问题,举例来说,同时调用多个服务,当其中一个服务调用失败时,其他服务已经处理执行的结果该如何进行回滚,这在单机本地调用的情况下使用事务比较好处理,而分布式环境下的事务将问题复杂化,并且性能开销难以承受,因此,只有在极端情况下才会考虑强一致性,一般情况下更多的关注最终一致性。另外一个就是安全问题,面向企业的平台级的SOA架构,需要对参数传递、响应内容以及各种用户私有信息的交互,有着更严格的且特殊的安全需求,如何构建一个安全的SOA架构体系,也给技术人员带来了很大的挑战。 在讲了很多“大而空”的理论之后,估计很多人要拍砖了。后面文章中将会讲一些干货,更贴合实际应用。先预告一下后面文章: 1.SOA之基于服务总线的设计 (从设计的角度讲述服务总线--比较适合企业级系统集成的设计方式) 2.大型分布式网站的演变历程 (随着网站快速发展,解决业务复杂化、大流量、稳定性等问题的必经之路。正所谓“天下大势,分久必合,合久必分”。 重点不是讲负载均衡这些手段,而是设计层面的“集中式”到“分布式”) 3.SOA架构体系之通信协议和远程调用(RPC) (讲解具体的实现技术,对比各自优缺点) 4.SOA之基于服务框架的应用 (服务化实践--介绍流行的服务框架,重点演示一种服务框架的使用)
在谈论数据库性能优化的时候,通常都会提到“索引”,但很多人其实没有真正理解索引,并没有搞清楚索引为什么能加快检索速度,以至于在实践中并不能很好的应用索引。事实上,索引可以说是最廉价而且十分有效一种优化手段,一般而言,设计优良的索引对查询性能优化确实能起到立竿见影的效果。 相信很多读者,都了解和使用过索引,可能也看过或者听过”新华字典“、”图书馆“之类比较通俗描述,但是对索引的存储结构和本质任然还比较迷茫。 有数据结构和算法基础的读者,应该都听过或者实践过“顺序查找,二分查找(折半)查找,二叉树查找”这几种很常见的查找算法。其中,顺序查找效率是最低的,其算法复杂度为O(n),而二分查找算法复杂度为O(logn)但要求数据是必须为有序的,通常在链表中使用广泛。而二叉树查找的复杂度仅为O(log2n),但要求数据结构为“树”。 在主流的关系型数据库中,使用和支持最广泛的要属B-Tree索引。考虑到大部分读者数据结构知识有限,为了便于理解,读者可以把B-Tree(或者其变种B+Tree) 理解为常见的二叉树。虽然这并不精确,但是相信读者看了之后,已经大致明白了为什么通过索引查找数据会比普通的表扫描会快很多。 sqlserver中的聚集索引 聚集索引的叶子节点(最底下的节点)直接包含了数据页。 sqlserver中的非聚集索引 在有聚集索引的表中,非聚集索引的叶子节点,包含的是聚集索引的键值(可以理解为聚集索引的指针)。 在没有聚集索引的堆表中,非聚集索引包含的是RID(可以理解为数据行的指针)。 在mysql中,通常也有“聚集索引”(针对InnoDB引擎)和“非聚集索引”(针对MyIsam引擎),“主键索引"和”二级索引“。 mysql InnoDB引擎中的索引结构 在主键索引中,叶子节点包含了数据行(数据页),二级索引的叶子界面,存放的是主键索引的键值(指向的主键索引) mysql MyIsam引擎中的索引结构 主键索引与二级索引结构上没有太大的区别,叶子节点都保存的数据行信息(例如row number等)可以直接指向并定位到数据行 相信读者不难看出,B-Tree索引在sqlserver和mysql中的结构、存储方式、原理都是大致相同的。当然,也有很多细节和内部实现上的差异。 限于笔者水平和理解有限,文中全部文字和描述等全凭笔者记忆写出,难免出现错误,敬请热心的读者及时批评和指正。 由于时间有限,大部分图片笔者画的比较粗糙,也请读者谅解。 本文出自http://blog.csdn.net/dinglang_2009,转载请注明出处。
在实际开发工作中,我们经常听到“架构设计”和“架构师”这样的名词,它并不新鲜和神秘,但是却很少有人对“架构”有全面的了解和认识,更谈不上掌握了。事实上,也只有极少数人能成为或者被冠以“架构师”这样的title。为此,笔者总结了实践中对架构的一些理解,希望能够补充很多人对此认识上的不足,纠正一些误解。 架构的分类 对于“架构”来讲,理论上划分了5种架构视图,分别是:逻辑架构、开发架构、运行架构、物理架构、数据架构。根据名字,大家都可能大概能猜到其侧重点和含义。 这里先用通俗的文字简单介绍下,便于大家理解,大家可以不必纠结概念和这些理论。我们通常会重点关注“逻辑架构”和“物理架构”。 逻辑架构:逻辑架构关注的是功能,包含用户直接可见的功能,还有系统中隐含的功能。或者更加通俗来描述,逻辑架构更偏向我们日常所理解的“分层”,把一个项目分为“表示层、业务逻辑层、数据访问层”这样经典的“三层架构”。 开发架构:开发架构则更关注程序包,不仅仅是我们自己写的程序,还包括应用程序依赖的SDK、第三方类库、中间价等。尤其是像目前主流的Java、.NET等依靠虚拟机的语言和平台,以及主流的基于数据库的应用,都会比较关注。和逻辑架构有紧密的关联。 运行架构:顾名思义,更关注的是应用程序运行中可能出现的一些问题。例如并发带来的问题,比较常见的“线程同步”问题、死锁问题、对象创建和销毁(生命周期管理)问题等等。开发架构,更关注的是飞机起飞之前的一些准备工作,在静止状态下就能规划好做好的,而运行架构,更多考虑的是飞机起飞之后可能发生的一些问题。 物理架构:物理架构,更关注的系统、网络、服务器等基础设施。例如:如何通过服务器部署和配置网络环境,来实现应用程序的“可伸缩性、高可用性”。或者举一个实际的例子,如何通过设计基础设施的架构,来保障网站能支持同时10W人在线、7*24小时提供服务,当超过10W人或者低于10W人在线时,可以很方便的调整部署架构来支撑。 数据架构:数据架构,更关注的是数据持久化和存储层面的问题,也可能会包括数据的分布、复制、同步等问题。更贴切来讲,如何选择需要的关系型数据库、流行的NOSQL,如何保障数据存储层面的性能、高可用性、灾备等等。很多时候,和物理架构是有紧密联系的,但它更关注数据存储层面的,物理架构更关注整个基础设施部署层面。 上面讲了那么多,相信国内很少有公司是严格按照这五种视图去分工和设计的。其实在笔者眼中,架构大致分为两种:软件架构、系统架构。前三种视图,可以归纳为软件架构,而后两种架构,则归为系统架构。这也比较符合国内大部分中小型互联网公司的分工和布局。 根据应用特性的不同,关注侧重点可能不同。例如,某些门户类的互联网应用,读多写少而且业务相对比较简单,则更加关注“高性能、可伸缩性、可用性”等方面。对于更加复杂的应用,例如电商这类大规模交易型的应用,对每个层面和每个环节都会比较关注。对于业务型的系统,例如一些生产型企业使用的ERP,或者仅供企业内部使用的一些MIS、OA应用,通常更关注功能和复杂的业务和实现、扩展,而很少会对性能等方面又太多要求,这类应用则更关注纯软件架构层面。这里,不展开做具体讨论。 架构设计到底是什么 在长期的技术招聘面试中,我发现在很多人眼中,架构就是分层,架构设计就是“三层架构”(或者四层、五层...反正分层越多就说明项目越复杂而且架构就越牛), 或许是受到诸如PetShop之类的示例项目的影响,这里暂时不去追究原因了,先纠正很多人的一些误解吧。先说一下笔者的理解: 架构就、是实用而且优雅的设计,它不在于分多少层,或者应用了多少种设计模式/架构模式。它应该是以满足实现用户需求为前提,以开发人员普遍可接受为根本的,而且要符合系统特性和业务发展需要的,从软件设计的角度,能够达到层次和结构清晰、可维护、可重用、可扩展...就非常优秀了,无需刻意去纠结分了多少层,是否使用了什么模式等。以面向对象为例,基本目标是“高内聚、低耦合”,为此我们可能会遵循一些常见的设计原则(例如经典的SOLID设计原则)。最后纠正一点,通常我们所说的模式,其实又分为很多种,并不是仅仅指的是“设计模式”。它通常包括:企业架构模式、设计模式、SOA模式、企业集成模式等等。 强调一下,架构要讲求“实用”,而且开发人员普遍可接受,否则再“优雅”的设计,都会沦为高成本的“花架子”,生搬硬套只会让项目“流产”。 关于Tier和Layer 笔者曾多次在一些技术社区和论坛看到一些关于Tier和Layer的争论,众说纷纭。其实最容易被广泛认可和接受的理解就是:Tier通常指的是物理上的分层,由执行同样功能的一台(或者一组)服务器定义。而Layer通常指的是逻辑上的分层,对于代码的组织,例如:我们通常提到的“业务逻辑层,表现层,数据访问层”等等。 Tier指代码运行的位置,多个Layer可以运行在同一个Tier上的,不同的Layer也可以运行在不同的Tier上,当然,前提是应用程序本身支持这种架构。以J2EE和.NET平台为例,大多数时候,不同的Layer之间都是直接通过DLL或者JAR包引用来完成调用的(例如:业务逻辑层需要引用数据访问层),这样部署的时候,也只能将多个Layer同时部署在一台服务器上。相反,不同的Layer之间如果是通过RPC的方式来实现通信调用的,部署的时候,便可以将不同的Layer部署在不同的服务器上面,这也是很常见的解耦设计。 如下图所示: 逻辑分层和物理分层的好处 逻辑分层的好处: 代码组织更清晰 更易于维护 更好的代码重用性 更好的团队开发体验性 更高的代码清晰度 物理分层的好处: 性能 可伸缩性 容错性 安全性 架构师的分类 架构师往往是很多开发人员向往的职业,它并不神秘,也不是像很多人想象中的那样(画一下PPT或者UML草图,然后交给程序员们去实现,然后自己就自由了)。国内很多公司,是没有架构师这种岗位定义的,通常是由技术优秀和经验比较丰富的开发人员担任,身兼多职的情况也并不少见(很多人是身兼主管、系统分析师、项目经理、架构师、核心开发人员...)。值得纠正的就是,架构师和系统分析师不同,系统分析师更侧重在项目早期的需求分析。而架构师,一般贯穿整个软件开发周期,与项目经理也是相辅相成的。微软对于架构师,又分为:软件架构师、系统架构师、解决方案架构师、企业架构师等。而其它一些厂商,可能又会划分:技术架构师、业务架构师、网络架构师、安全架构师、SOA架构师......大家不必对这些概念太纠结。按照笔者的理解,国内大互联网公司里,架构师一般只会分2-3个方向:软件架构师和系统架构师(有些企业叫运维架构师),有些企业对于比较资深DBA而且懂整个系统架构的,还会分出所谓的“数据架构师”。至于具体Title,大可不必纠结,只需在知晓了大致发展定位,然后朝该方向努力即可。对于程序员而言,先写出高质量代码,在实战中逐步提升自己设计思维即可。
最近在对CDN进行优化,对浏览器缓存深入研究了一下,记录一下,方便后来者 画了一个草图: 每个状态的详细说明如下: 1、Last-Modified 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记(HttpReponse Header)此文件在服务期端最后被修改的时间,格式类似这样: Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT 客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头(HttpRequest Header),询问该时间之后文件是否有被修改过: If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT 如果服务器端的资源没有变化,则自动返回HTTP304(NotChanged.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。 注:如果If-Modified-Since的时间比服务器当前时间(当前的请求时间request_time)还晚,会认为是个非法请求 2、Etag工作原理 HTTP协议规格说明定义ETag为“被请求变量的实体标记”(参见14.19)。简单点即服务器响应时给请求URL标记,并在HTTP响应头中将其传送到客户端,类似服务器端返回的格式: Etag:“5d8c72a5edda8d6a:3239″ 客户端的查询更新格式是这样的: If-None-Match:“5d8c72a5edda8d6a:3239″ 如果ETag没改变,则返回状态304。 即:在客户端发出请求后,HttpReponse Header中包含Etag:“5d8c72a5edda8d6a:3239″ 标识,等于告诉Client端,你拿到的这个的资源有表示ID:5d8c72a5edda8d6a:3239。当下次需要发Request索要同一个URI的时候,浏览器同时发出一个If-None-Match报头(Http RequestHeader)此时包头中信息包含上次访问得到的Etag:“5d8c72a5edda8d6a:3239″标识。 If-None-Match:“5d8c72a5edda8d6a:3239“ ,这样,Client端等于Cache了两份,服务器端就会比对2者的etag。如果If-None-Match为False,不返回200,返回304(Not Modified) Response。 3、Expires 给出的日期/时间后,被响应认为是过时。如Expires:Thu, 02 Apr 2009 05:14:08 GMT 需和Last-Modified结合使用。用于控制请求文件的有效时间,当请求数据在有效期内时客户端浏览器从缓存请求数据而不是服务器端.当缓存中数据失效或过期,才决定从服务器更新数据。 4、Last-Modified和Expires Last-Modified标识能够节省一点带宽,但是还是逃不掉发一个HTTP请求出去,而且要和Expires一起用。而Expires标识却使得浏览器干脆连HTTP请求都不用发,比如当用户F5或者点击Refresh按钮的时候就算对于有Expires的URI,一样也会发一个HTTP请求出去,所以,Last-Modified还是要用的,而且要和Expires一起用。 5、Etag和Expires 如果服务器端同时设置了Etag和Expires时,Etag原理同样,即与Last-Modified/Etag对应的HttpRequestHeader:If-Modified-Since和If-None-Match。我们可以看到这两个Header的值和WebServer发出的Last-Modified,Etag值完全一样;在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304. 6、Last-Modified和Etag 分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败 分布式系统尽量关闭掉Etag(每台机器生成的etag都会不一样) Last-Modified和ETags请求的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和一个空的响应体。 注: 1、Last-Modified和Etag头都是由WebServer发出的HttpReponse Header,WebServer应该同时支持这两种头。 2、WebServer发送完Last-Modified/Etag头给客户端后,客户端会缓存这些头; 3、客户端再次发起相同页面的请求时,将分别发送与Last-Modified/Etag对应的HttpRequestHeader:If-Modified-Since和If-None-Match。我们可以看到这两个Header的值和WebServer发出的Last-Modified,Etag值完全一样; 4、通过上述值到服务器端检查,判断文件是否继续缓存; 7、关于 Cache-Control: max-age=秒 和 Expires Expires = 时间,HTTP 1.0 版本,缓存的载止时间,允许客户端在这个时间之前不去检查(发请求)max-age = 秒,HTTP 1.1版本,资源在本地缓存多少秒。如果max-age和Expires同时存在,则被Cache-Control的max-age覆盖。 Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大,那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。 Expires =max-age + “每次下载时的当前的request时间” 所以一旦重新下载的页面后,expires就重新计算一次,但last-modified不会变化 本文转载自http://blog.csdn.net/eroswang/article/details/8302191
什么是cache? 定义:浏览器缓存(Browser Caching)是为了加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。 cache的作用: 1、减少延迟,让你的网站更快,提高用户体验。2、避免网络拥塞,减少请求量,减少输出带宽。 页面内容Cache策略: 目前我们业务的JS、css、静态页面文件和图片等资源放在imgcache域名机器上,是由运维人员在apache上统一设置缓存策略。前台通过Http请求的回调包来展现,其中关键的两个属性就是Expires和Cache-Control。Expires设置对象的有效期,是HTTP/1.0规范;max-age是Cache-control的一个指令设置对象的年龄(秒数),是HTTP/1.1规范,max-age优先级高于Expires。 Cache-Control中的max-age是实现内容cache的主要手段,共有3种常用策略:max-age和Last-Modified(If-Modified-Since)的组合、仅max-age、max-age和ETag的组合。 I、max-age和Last-Modified(If-Modified-Since)的组合: 我们的业务中用的比较多的是max-age和Last-Modified(If-Modified-Since)的组合,现在详细介绍一下,这个策略是在给出max-age的同时,给出一个资源的验证方式:Last-Modified,标示这个响应资源的最后修改时间,例如Last-Modified: Thu, 08 Apr 2010 15:01:08 GMT,这个属性只有配合Cache-control的时候才有实际价值,利用资源的可校验性,可以实现在cache的资源超过max-age浏览器再次请求时的304响应,令浏览器再次使用之前的cache 第一步: 1、浏览器第一次请求资源http://imgcache.qq.com/paipai/cos/portal/js/spu.js 2、浏览器查询临时文件目录发现无cache内容,于是发出请求到web server 3、Web server接到请求,响应资源,并设定Cache-control:max-age=600,Last-Modified: Wed, 29 Sep 2010 09:59:03 GMT 4、浏览器接到响应,将内容展示的同时,在临时文件目录以“http://imgcache.qq.com/paipai/cos/portal/js/spu.js”为key缓存这个响应的内容 图示:首次请求,没有cache,结果是200,回包里包含max-age、Last-Modified。 第二步: 1、离第一步请求间隔时间不到10分钟 2、浏览器再次请求资源http://imgcache.qq.com/paipai/cos/portal/js/spu.js 3、浏览器查询临时文件目录发现有cache内容,于是检查max-age,还未过期,直接读取,响应给用户。(HTTP状态(Cache)) 图示:在不到10分钟的间隔里,再次请求这个js文件,直接从cache里读取了内容 第三步: 1、离第一步请求间隔时间已超过10分钟 2、浏览器再次请求资源http://imgcache.qq.com/paipai/cos/portal/js/spu.js 3、浏览器查询临时文件目录发现有cache内容,于是检查max-age,已经过期,发现资源带有Last-Modified,于是在请求包中带上If-Modified-Since: Wed, 29 Sep 2010 09:59:03 GMT,发请求给web server 4、Web server收到请求后发现有If-Modified-Since,于是和被请求资源的最后修改时间进行比对,如果修改时间比请求包的时间新,说明资源已经被改过,则带包体响应回复整个资源内容(HTTP状态200);如果修改时间不比请求包的时间新,说明资源在这段时间内都没改过,无需回复整个资源,则仅响应包头,不带包体(HTTP状态304),告诉浏览器继续使用临时目录里的cache内容展示。 图示:不做清除cache的操作,1个小时以后,再次请求这个js文件,max-age已经过期了,但修改时间没变,所以304。 II、仅max-age 最基础的策略,只需要在回包的头字段里加入Cache-control: max-age=[secs]即可 如Cache-control: max-age=1800表明cache的时间是30分钟,只使用这样一个声明就可以使浏览器能够将这个HTTP响应的内容写入临时目录做cache 第一步: 1、浏览器第一次请求资源http://imgcache.qq.com/qqshow_v3/css/index_.css 2、浏览器查询临时文件目录发现无cache内容,于是发出请求到web server 3、Web server接到请求,响应资源,并设定Cache-control:max-age=3600 4、浏览器接到响应,将内容展示的同时,在临时文件目录以“http://imgcache.qq.com/qqshow_v3/css/index_.css”为key缓存这个响应的内容 第二步: 1、离第一步请求间隔时间不到60分钟 2、浏览器再次请求资源http://imgcache.qq.com/qqshow_v3/css/index_.css 3、浏览器查询临时文件目录发现有cache内容,于是检查max-age,还未过期,直接读取,响应给用户。(HTTP状态“(Cache)”) 第三步: 1、离第一步请求间隔时间已超过60分钟 2、浏览器再次请求资源http://imgcache.qq.com/qqshow_v3/css/index_.css 3、浏览器查询临时文件目录发现有cache内容,于是检查max-age,已经过期,发起请求给web server。(HTTP状态“200”) III、max-age和ETag的组合 这个策略是,在给出max-age的同时,给出另一种资源的验证方式:ETag,标示这个响应资源由开发者自己确定的验证标识,例如ETag: "12345678" 同样这个属性也只有配合Cache-control的时候才有实际价值,是声明校验资源的方式,ETag的使用为实现304响应提供了更多的灵活性 第一步: 1、浏览器第一次请求资源http://imgcache.qq.com/qqshow_v3/css/index_.css 2、浏览器查询临时文件目录发现无cache内容,于是发出请求到web server 3、Web server接到请求,响应资源,并设定Cache-control:max-age=3600,ETag: "12345678" 4、浏览器接到响应,将内容展示的同时,在临时文件目录以“http://imgcache.qq.com/qqshow_v3/css/index_.css”为key缓存这个响应的内容 第二步: 1、离第一步请求间隔时间不到60分钟 2、浏览器再次请求资源http://imgcache.qq.com/qqshow_v3/css/index_.css 3、浏览器查询临时文件目录发现有cache内容,于是检查max-age,还未过期,直接读取,响应给用户。(HTTP状态“(Cache)”) 第三步: 1、离第一步请求间隔时间已超过60分钟 2、浏览器再次请求资源http://imgcache.qq.com/qqshow_v3/css/index_.css 3、浏览器查询临时文件目录发现有cache内容,于是检查max-age,已经过期,发现资源带有ETag,于是在请求包中带上If-None-Match: "12345678",发请求给web server 4、Web server收到请求后发现有If-None-Match,于是和被请求资源的验证串进行比对,如果校验串的内容不一致,则返回整个资源包体(HTTP状态200),如果校验串的内容一致,仅返回包头(HTTP状态304),告诉浏览器继续使用临时目录里的cache内容展示。 当前业务运维和开发的Cache策略: 1、 js文件:js文件是整个前台页面的灵魂,具有对于承担着前台页面主要功能,用户操作比较多的JS文件,运维人员设置cache一般为10分钟(max-age=600)。对于统计或者测速功能的JS文件,cache通常为一年(max-age=31536000),见下图所示: 2、 样式文件:与本业务相关,经常会有变动优化类的css文,运维人员设置cache一般为1个小时(max-age=3600),公共类的css文件cache通常为一年(max-age=31536000)。 3、 图片文件:根据图片修改的频率,经常需要优化的图片(本业务需要使用的),运维人员设置cache为1个小时(max-age=3600)。公共图片cache时间s久一些,比如static.paipaiimg.com域名下的某些图片cache达到一年时间。 4、 动静态文件:基本都是与业务相关联的页面文件,运维人员设置cache一般为1个小时(max-age=3600)。 5、 CGI/PHP文件:由开发人员根据CGI/PHP文件在业务中的具体功能,在代码中设置max-age的值,时间一般为1个小时。一般支持页面动态内容的CGI/PHP文件不需要设置cache,如商品的库存信息、商品信息等。 6、 尽量减少304返回码。 本文转载自http://www.51testing.com/html/43/434343-243768.html
在主流的Web站点中,图片往往是不可或缺的页面元素,尤其在大型网站中,几乎都将面临“海量图片资源”的存储、访问等相关技术问题。在针对图片服务器的架构扩展中,也会历经很多曲折甚至是血泪教训(尤其是早期规划不足,造成后期架构上很难兼容和扩展)。 本文将以一个真实垂直门户网站的发展历程,向大家娓娓道来。 构建在Windows平台之上的网站,往往会被业内众多架构师认为很“保守”。很大部分原因,是由于微软技术体系的封闭和部分技术人员的短视造成的。由于长期缺乏开源支持,所以只能“闭门造车”,这样很容易形成思维局限性和短板。就拿图片服务器为例子,如果前期没有容量规划和可扩展的设计,那么随着图片文件的不断增多和访问量的上升,由于在性能、容错/容灾、扩展性等方面的设计不足,后续将会给开发、运维工作带来很多问题,严重时甚至会影响到网站业务正常运作和互联网公司的发展(这绝不是在危言耸听)。 之所以选择Windows平台来构建网站和图片服务器,很大部分由创始团队的技术背景决定的,早期的技术人员可能更熟悉.NET,或者负责人认为Windows/.NET的易用性、“短平快”的开发模式、人才成本等方面都比较符合创业初期的团队,自然就选择了Windows。后期业务发展到一定规模,也很难轻易将整体架构迁移到其它平台上了。当然,对于构建大规模互联网,更建议首选开源架构,因为有很多成熟的案例和开源生态的支持,避免重复造轮子和支出授权费用。对于迁移难度较大的应用,比较推荐Linux、Mono、Mysql、Memcahed……混搭的架构,同样能支撑高并发访问和大数据量。 单机时代的图片服务器架构(集中式) 初创时期由于时间紧迫,开发人员水平也很有限等原因。所以通常就直接在website文件所在的目录下,建立1个upload子目录,用于保存用户上传的图片文件。如果按业务再细分,可以在upload目录下再建立不同的子目录来区分。例如:upload\QA,upload\Face等。 在数据库表中保存的也是”upload/qa/test.jpg”这类相对路径。 用户的访问方式如下: http://www.yourdomain.com/upload/qa/test.jpg 程序上传和写入方式: 程序员A通过在web.config中配置物理目录D:\Web\yourdomain\upload 然后通过stream的方式写入文件; 程序员B通过Server.MapPath等方式,根据相对路径获取物理目录 然后也通过stream的方式写入文件。 优点:实现起来最简单,无需任何复杂技术,就能成功将用户上传的文件写入指定目录。保存数据库记录和访问起来倒是也很方便。 缺点:上传方式混乱,严重不利于网站的扩展。 针对上述最原始的架构,主要面临着如下问题: 1. 随着upload目录中文件越来越多,所在分区(例如D盘)如果出现容量不足,则很难扩容。只能停机后更换更大容量的存储设备,再将旧数据导入。 2. 在部署新版本(部署新版本前通过需要备份)和日常备份website文件的时候,需要同时操作upload目录中的文件,如果考虑到访问量上升,后边部署由多台Web服务器组成的负载均衡集群,集群节点之间如果做好文件实时同步将是个难题。 集群时代的图片服务器架构(实时同步) 在website站点下面,新建一个名为upload的虚拟目录,由于虚拟目录的灵活性,能在一定程度上取代物理目录,并兼容原有的图片上传和访问方式。用户的访问方式依然是: http://www.yourdomain.com/upload/qa/test.jpg 优点:配置更加灵活,也能兼容老版本的上传和访问方式。 因为虚拟目录,可以指向本地任意盘符下的任意目录。这样一来,还可以通过接入外置存储,来进行单机的容量扩展。 缺点:部署成由多台Web服务器组成的集群,各个Web服务器(集群节点)之间(虚拟目录下的)需要实时的去同步文件,由于同步效率和实时性的限制,很难保证某一时刻各节点上文件是完全一致的。 基本架构如下图所示: 从上图可看出,整个Web服务器架构已经具备“可扩展、高可用”了,主要问题和瓶颈都集中在多台服务器之间的文件同步上。 上述架构中只能在这几台Web服务器上互相“增量同步”,这样一来,就不支持文件的“删除、更新”操作的同步了。 早期的想法是,在应用程序层面做控制,当用户请求在web1服务器进行上传写入的同时,也同步去调用其它web服务器上的上传接口,这显然是得不偿失的。所以我们选择使用Rsync类的软件来做定时文件同步的,从而省去了“重复造轮子”的成本,也降低了风险性。 同步操作里面,一般有比较经典的两种模型,即推拉模型:所谓“拉”,就是指轮询地去获取更新,所谓推,就是发生更改后主动的“推”给其它机器。当然,也可以采用加高级的事件通知机制来完成此类动作。 在高并发写入的场景中,同步都会出现效率和实时性问题,而且大量文件同步也是很消耗系统和带宽资源的(跨网段则更明显)。 集群时代的图片服务器架构改进(共享存储) 沿用虚拟目录的方式,通过UNC(网络路径)的方式实现共享存储(将upload虚拟目录指向UNC) 用户的访问方式1: http://www.yourdomain.com/upload/qa/test.jpg 用户的访问方式2(可以配置独立域名): http://img.yourdomain.com/upload/qa/test.jpg 支持UNC所在server上配置独立域名指向,并配置轻量级的web服务器,来实现独立图片服务器。 优点: 通过UNC(网络路径)的方式来进行读写操作,可以避免多服务器之间同步相关的问题。相对来讲很灵活,也支持扩容/扩展。支持配置成独立图片服务器和域名访问,也完整兼容旧版本的访问规则。 缺点 :但是UNC配置有些繁琐,而且会造成一定的(读写和安全)性能损失。可能会出现“单点故障”。如果存储级别没有raid或者更高级的灾备措施,还会造成数据丢失。 基本架构如下图所示: 在早期的很多基于Linux开源架构的网站中,如果不想同步图片,可能会利用NFS来实现。事实证明,NFS在高并发读写和海量存储方面,效率上存在一定问题,并非最佳的选择,所以大部分互联网公司都不会使用NFS来实现此类应用。当然,也可以通过Windows自带的DFS来实现,缺点是“配置复杂,效率未知,而且缺乏资料大量的实际案例”。另外,也有一些公司采用FTP或Samba来实现。 上面提到的几种架构,在上传/下载操作时,都经过了Web服务器(虽然共享存储的这种架构,也可以配置独立域名和站点来提供图片访问,但上传写入仍然得经过Web服务器上的应用程序来处理),这对Web服务器来讲无疑是造成巨大的压力。所以,更建议使用独立的图片服务器和独立的域名,来提供用户图片的上传和访问。 独立图片服务器/独立域名的好处 1. 图片访问是很消耗服务器资源的(因为会涉及到操作系统的上下文切换和磁盘I/O操作)。分离出来后,Web/App服务器可以更专注发挥动态处理的能力。 2. 独立存储,更方便做扩容、容灾和数据迁移。 3. 浏览器(相同域名下的)并发策略限制,性能损失。 4. 访问图片时,请求信息中总带cookie信息,也会造成性能损失。 5. 方便做图片访问请求的负载均衡,方便应用各种缓存策略(HTTP Header、Proxy Cache等),也更加方便迁移到CDN。 ...... 我们可以使用Lighttpd或者Nginx等轻量级的web服务器来架构独立图片服务器。 当前的图片服务器架构(分布式文件系统+CDN) 在构建当前的图片服务器架构之前,可以先彻底撇开web服务器,直接配置单独的图片服务器/域名。但面临如下的问题: 1. 旧图片数据怎么办?能否继续兼容旧图片路径访问规则? 2. 独立的图片服务器上需要提供单独的上传写入的接口(服务API对外发布),安全问题如何保证? 3. 同理,假如有多台独立图片服务器,是使用可扩展的共享存储方案,还是采用实时同步机制? 直到应用级别的(非系统级) DFS(例如FastDFS HDFS MogileFs MooseFS、TFS)的流行,简化了这个问题:执行冗余备份、支持自动同步、支持线性扩展、支持主流语言的客户端api上传/下载/删除等操作,部分支持文件索引,部分支持提供Web的方式来访问。 考虑到各DFS的特点,客户端API语言支持情况(需要支持C#),文档和案例,以及社区的支持度,我们最终选择了FastDFS来部署。 唯一的问题是:可能会不兼容旧版本的访问规则。如果将旧图片一次性导入FastDFS,但由于旧图片访问路径分布存储在不同业务数据库的各个表中,整体更新起来也十分困难,所以必须得兼容旧版本的访问规则。架构升级往往比做全新架构更有难度,就是因为还要兼容之前版本的问题。(给飞机在空中换引擎可比造架飞机难得多) 解决方案如下: 首先,关闭旧版本上传入口(避免继续使用导致数据不一致)。将旧图片数据通过rsync工具一次性迁移到独立的图片服务器上(即下图中描述的Old ImageServer)。在最前端(七层代理,如Haproxy、Nginx)用ACL(访问规则控制),将旧图片对应URL规则的请求(正则)匹配到,然后将请求直接转发指定的web 服务器列表,在该列表中的服务器上配置好提供图片(以Web方式)访问的站点,并加入缓存策略。这样实现旧图片服务器的分离和缓存,兼容了旧图片的访问规则并提升旧图片访问效率,也避免了实时同步所带来的问题。 整体架构如图: 基于FastDFS的独立图片服务器集群架构,虽然已经非常的成熟,但是由于国内“南北互联”和IDC带宽成本等问题(图片是非常消耗流量的),我们最终还是选择了商用的CDN技术,实现起来也非常容易,原理其实也很简单,我这里只做个简单的介绍: 将img域名cname到CDN厂商指定的域名上,用户请求访问图片时,则由CDN厂商提供智能DNS解析,将最近的(当然也可能有其它更复杂的策略,例如负载情况、健康状态等)服务节点地址返回给用户,用户请求到达指定的服务器节点上,该节点上提供了类似Squid/Vanish的代理缓存服务,如果是第一次请求该路径,则会从源站获取图片资源返回客户端浏览器,如果缓存中存在,则直接从缓存中获取并返回给客户端浏览器,完成请求/响应过程。 由于采用了商用CDN服务,所以我们并没有考虑用Squid/Vanish来重复构建前置代理缓存。 上面的整个集群架构,可以很方便的做横向扩展,能满足一般垂直领域大型网站的图片服务需求(当然,像taobao这样超大规模的可能另当别论)。经测试,提供图片访问的单台Nginx服务器(至强E5四核CPU、16G内存、SSD),对小静态页面(压缩后的)可以扛住上万的并发且毫无压力。当然,由于图片本身体积比纯文本的静态页面大很多,提供图片访问的服务器的抗并发能力,往往会受限于磁盘的I/O处理能力和IDC提供的带宽。Nginx的抗并发能力还是非常强的,而且对资源占用很低,尤其是处理静态资源,似乎都不需要有过多担心了。可以根据实际访问量的需求,通过调整Nginx参数,Linux内核调优、缓存策略等手段做更大程度的优化,也可以通过增加服务器或者升级服务器配置来做扩展,最直接的是通过购买更高级的存储设备和更大的带宽,以满足更大访问量的需求。 值得一提的是,在“云计算”流行的当下,也推荐高速发展期间的网站,使用“云存储”这样的方案,既能帮你解决各类存储、扩展、备灾的问题,又能做好CDN加速。最重要的是,价格也不贵。 总结,有关图片服务器架构扩展,大致围绕这些问题展开: 1. 容量规划和扩展问题。 2. 数据的同步、冗余和容灾。 3. 硬件设备的成本和可靠性(是普通机械硬盘,还是SSD,或者更高端的存储设备和方案)。 4. 文件系统的选择。根据文件特性(例如文件大小、读写比例等)选择是用ext3/4或者NFS/GFS/TFS这些开源的(分布式)文件系统。 5. 图片的加速访问。采用商用CDN或者自建的代理缓存、web静态缓存架构。 6. 旧图片路径和访问规则的兼容性,应用程序层面的可扩展,上传和访问的性能和安全性等。 作者介绍 丁浪,技术架构师。擅长大规模(大流量、高并发、高可用、海量数据)互联网架构,专注在“高性能,可扩展/伸缩,稳定,安全”的技术架构。 热衷于技术研究和分享,曾分享和独立撰写过大量技术文章。 本文首发于InfoQ社区,版权所有,转载请注明出处。
前言 ProgrammingPearls(《编程珠玑》)一书的作者Jon Bentley曾经说过类似的话:“90%的程序员无法正确实现二分查找算法...” 言下之意,只有1/10的程序员能够写出“二分查找算法”来。昨天我突然又看到了这句话,于是就随时打开eclipse写下了,还算顺利。 关于“二分查找算法” "二分查找算法",很多地方也被称作是“折半查找”或者“折半搜索”,是比较常用的“搜索/查找”算法。其基本思想是,找到最小、最大和中间值,然后再做比较。查找速度快,而且性能好。但是被查找的表需要是有序的(通俗点就是需要排序好的)。 废话不多说,直接上代码吧。 package com.dylan.algorithm; import javax.sound.sampled.Mixer; public class BinarySearch { public static void main(String[] args) { // TODO Auto-generated method stub int arr[] ={10,24,36,47,68,89,130}; int index = Search(arr, 89); System.out.println(index); } /*实现二分查找算法(折半算法) *要确定数组是排序好的 *最好是里面一定包含该元素 */ public static int Search(int[] arr,int key ) { int min =0; int max=arr.length-1; int mid = (min+max)/2; while(arr[mid]!=key){ if (arr[mid]<key) { min=mid+1; }else if (arr[mid]>key) { max=mid-1; } if(max<min){ return -1;//里面不存在值为key的元素 } //继续折半 mid=(min+max)/2; } return mid; } } 感兴趣的同学,可以按照代码去运行试试,看是否能正确查找,是否会出现死循环等等问题。说实话,这东西我平时在工作中也很少用到,是在几年前为了去面试“突击”过,想不到现在居然还能凭印象写出一点来。近期有时间,也是该温习下基础了。 可能现在大部分的码农,都对“数据结构和算法”方面有些“恐惧”甚至“反感”吧。等改天有时间,我补充用画图的形式,详细的描述下“二分查找算法”的基本思想以及详细的推导步骤。
在前面的一些文章中,从实战的角度,讲解了有关memcached的应用、容灾、监控等等。但是缺乏对理论的讲解和原理性的剖析。本文将从理论的角度去介绍,让大家从宏观上对“分布式缓存、nosql”等技术有所了解,以便进一步学习和使用。在构建大规模的web应用时,缓存技术可以说是必备的,学习的必要性不言而喻。 分布式缓存概述 1.1 分布式缓存的特性 分布式缓存具有如下特性:1) 高性能:当传统数据库面临大规模数据访问时,磁盘I/O 往往成为性能瓶颈,从而导致过高的响应延迟.分布式缓存将高速内存作为数据对象的存储介质,数据以key/value 形式存储,理想情况下可以获得DRAM 级的读写性能;2) 动态扩展性:支持弹性扩展,通过动态增加或减少节点应对变化的数据访问负载,提供可预测的性能与扩展性;同时,最大限度地提高资源利用率;3) 高可用性:可用性包含数据可用性与服务可用性两方面.基于冗余机制实现高可用性,无单点失效(single point of failure),支持故障的自动发现,透明地实施故障切换,不会因服务器故障而导致缓存服务中断或数据丢失.动态扩展时自动均衡数据分区,同时保障缓存服务持续可用;4) 易用性:提供单一的数据与管理视图;API 接口简单,且与拓扑结构无关;动态扩展或失效恢复时无需人工配置;自动选取备份节点;多数缓存系统提供了图形化的管理控制台,便于统一维护;5) 分布式代码执行(distributed code execution):将任务代码转移到各数据节点并行执行,客户端聚合返回结果,从而有效避免了缓存数据的移动与传输.最新的Java 数据网格规范JSR-347中加入了分布式代码执行与Map/reduce 的API 支持,各主流分布式缓存产品,如IBM WebSphere eXtreme Scale,VMware GemFire,GigaSpaces XAP 和Red Hat Infinispan 等也都支持这一新的编程模型.1.2 典型应用场景分布式缓存的典型应用场景可分为以下几类:1) 页面缓存.用来缓存Web 页面的内容片段,包括HTML、CSS 和图片等,多应用于社交网站等;2) 应用对象缓存.缓存系统作为ORM 框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问;3) 状态缓存.缓存包括Session 会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群;4) 并行处理.通常涉及大量中间计算结果需要共享;5) 事件处理.分布式缓存提供了针对事件流的连续查询(continuous query)处理技术,满足实时性需求;6) 极限事务处理.分布式缓存为事务型应用提供高吞吐率、低延时的解决方案,支持高并发事务请求处理,多应用于铁路、金融服务和电信等领域.1.3 分布式缓存的发展分布式缓存经历了多个发展阶段,由最初的本地缓存到弹性缓存平台直至弹性应用平台,目标是朝着构建更好的分布式系统方向发展(如下图所示). 1) 本地缓存:数据存储在应用代码所在内存空间.优点是可以提供快速的数据访问;缺点是数据无法分布式共享,无容错处理.典型的,如Cache4j; 2) 分布式缓存系统:数据在固定数目的集群节点间分布存储.优点是缓存容量可扩展(静态扩展);缺点是扩展过程中需要大量配置,无容错机制.典型的,如Memcached; 3) 弹性缓存平台:数据在集群节点间分布存储,基于冗余机制实现高可用性.优点是可动态扩展,具有容错能力;缺点是复制备份会对系统性能造成一定影响.典型的,如Windows Appfabric Caching; 4) 弹性应用平台:弹性应用平台代表了云环境下分布式缓存系统未来的发展方向.简单地讲,弹性应用平台是弹性缓存与代码执行的组合体,将业务逻辑代码转移到数据所在节点执行,可以极大地降低数据传输开销,提升系统性能.典型的,如GigaSpaces XAP.1.4 分布式缓存与NoSQLNoSQL 又称为Not Only Sql,主要是指非关系型、分布式、支持水平扩展的数据库设计模式.NoSQL 放弃了传统关系型数据库严格的事务一致性和范式约束,采用弱一致性模型.相对于NoSQL 系统,传统数据库难以满足云环境下应用数据的存储需求,具体体现在以下3 个方面:1) 根据CAP 理论,一致性(consistency)、可用性(availability)和分区容错(partition tolerance)这3 个要素最多同时满足两个,不可能三者兼顾.对云平台中部署的大量Web 应用而言,数据可用性与分区容错的优先级通常更高,所以一般会选择适当放松一致性约束.传统数据库的事务一致性需求制约了其横向伸缩与高可用技术的实现;2) 传统数据库难以适应新的数据存储访问模式.Web 2.0 站点以及云平台中存在大量半结构化数据,如用户Session 数据、时间敏感的事务型数据、计算密集型任务数据等,这些状态数据更适合以Key/Value 形式存储,不需要RDBMS 提供的复杂的查询与管理功能;3) NoSQL 提供低延时的读写速度,支持水平扩展,这些特性对拥有海量数据访问请求的云平台而言是至关重要的.传统关系型数据无法提供同样的性能,而内存数据库容量有限且不具备扩展能力.分布式缓存作为NoSQL 的一种重要实现形式,可为云平台提供高可用的状态存储与可伸缩的应用加速服务,与其他NoSQL 系统间并无清晰的界限.平台中应用访问与系统故障均具有不可预知性,为了更好地应对这些挑战,应用软件在架构时通常采用无状态设计,大量状态信息不再由组件、容器或平台来管理,而是直接交付给后端的分布式缓存服务或NoSQL 系统.1.5 分布式缓存与极限事务处理随着云计算与 Web 2.0 的进一步发展,许多企业或组织时常会面对空前的需求:百万级的并发用户访问、每秒数以千计的并发事务处理、灵活的弹性与可伸缩性、低延时及7×24×365 可用性等.传统事务型应用面临极限规模的并发事务处理,出现了极限事务处理型应用,典型的有铁路售票系统.Wikipedia 认为,极限事务处理是每秒多于500 事务或高于10 000 次并发访问的事务处理.Gartner 将极限事务处理(extreme transactionprocessing,简称XTP)定义为一种为事务型应用的开发、部署、管理和维护供支持的应用模式,特点是对性能、可扩展性、可用性、可管理性等方面的极限需求.Gartner 在其报告中预测指出,极限事务处理型应用的规模将由2005 年的10%提升至2010 年的20%,极限事务处理技术是未来5 年~10 年的热点技术.极限事务处理的引入,无疑给传统Web 三层架构带来了新的挑战.即,如何在廉价的、标准化的硬件和软件平台之上,对大容量、业务关键型的事务处理应用提供良好的支撑.分布式缓存作为一种关键的XTP 技术,可为事务型应用提供高吞吐率、低延时的技术解决方案.其延迟写(write-behind)机制可提供更短的响应时间,同时极大地降低数据库的事务处理负载,分阶段事件驱动架构(staged event-driven architecture)可以支持大规模、高并发的事务处理请求.此外,分布式缓存在内存中管理事务并提供数据的一致性保障,采用数据复制技术实现高可用性,具有较优的扩展性与性能组合. 相关文章推荐 关于NoSQL的选型和使用 memcached单点故障与负载均衡 memcached性能监控 在Windows .NET平台下使用Memcached “集群和负载均衡”的通俗解释
相信不少的朋友,无论是做开发、架构的,还是DBA等,都经常听说“调优”这个词。说起“调优”,可能会让很多技术人员心头激情澎湃,也可能会让很多人感觉苦恼。当然,也有很多人对此不屑一顾,因为并不是每个人接触到的项目都很大,也不是每个人做的项目都对性能要求很高。 在主流的企业级开发和互联网应用中,数据库的重要性是不言而喻的,而数据库的性能对于整个系统的性能而言也是至关重要的,这里无庸赘述。 sqlserver的性能调优,其实是个很宽广的话题。坦白讲,想从概念到实践的完全讲清楚并掌握透彻,可能至少需要几本书的内容。本文只是一个概念级的总结,希望读者能对此有新的认识,在调优路上有所帮助。如果感兴趣的朋友很多,后续可能会分享一些实战经验。 首先搞清楚,性能调优的目标 从最直观,最常见的角度来讲,主要包含如下两点: 优化响应时间 何为“优化响应时间” 呢?说的通俗点,就是经过调优后,执行查询、更新等操作的时候,数据库的反应速度更快,花费的时间更少。 比较常见的,以前执行某条sql查询语句,可能需要3秒钟,加了索引后,1秒钟不到就搞定了。加索引,这也是最典型最"廉价"的优化手段。 在做“优化响应时间”时,需要了解:用户环境,程序,环境,用户和数据等方面的知识。 优化吞吐量 说起“吞吐量”,那就要想到“并发”了。其实就是“同时处理请求”的能力。如何提高数据库"抗并发"的能力呢?首先要了解sqlserver是如何访问数据的,如何控制并发访问的(事务隔离级别,锁等),如何与底层操作系统进行交互的,还要了解“多线程、进程”等方面的知识。 比较常见的手段,通过降低事务隔离级别(一定程度地牺牲数据一致性等),这种“软手段”通常会起到很好的效果。其次,单台DB Server达到一定瓶颈后,可以通过“集群”等方式,实现请求的“负载均衡”的,来达到“抗并发”的目的,效果也是立竿见影的。 性能调优的方法论--迭代 基线 通俗点讲,就是用来计算或者比较的标准。通常以当前系统性能为基准,或者以匹配系统性能为基准。指各个组件发挥到最大。 成本 用来升级,更换等提升组件性能时的时间,金钱,劳力等等。 基线的定义,以用户期望值为基础,可能会涉及以下因素 以往的经验,应用程序的基准,业界的标准,以前版本的情况 基线的表示方式,包括:每秒完成的批处理(作业),每秒传输量,每秒数据量,磁盘扫描时间等等 分析影响性能的因素: 数据库设计(是否复合范式,是否合理归档、分区、分表等) 软件系统 (操作系统优化,数据库系统的配置,资源的规划和监控等) 硬件基础架构 (设备规格,硬件性能,负载均衡,容灾等) Sql语句的写法、索引和统计信息,事务和锁,应用程序访问代码(连接过多、频繁开关等) 性能调优的顺序: 从左往右,从技术难度、成本、实效去考虑 DETECT 方法 发现问题、探究原因、提供可能的解决方法、执行最有可能的解决方案、确认是否成功解决(如果没有,重复前面的步骤)、完成其余的工作 DETECT方法论 中的这些工作细分起来,会有很多,这里暂时不做过多描述。具体调优的步骤、性能调优工具的使用,下篇文章继续。 sqlserver性能调优方法论 sqlserver性能调优工具
【内容简介】NoSQL,指的是非关系型的数据库。随着互联网Web 2.0网站的兴起,传统的关系数据库在应付Web 2.0网站时,特别是超大规模和高并发的SNS类型的Web 2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。就像AK47是非洲人的身份证一样,NoSQL技能如今已成为Web开发软件工程师的必备技能之一。【使用NoSQL数据库的理由】1. High performance - 对数据库高并发读写的需求2. Huge Storage - 对海量数据的高效率存储和访问的需求3. High Scalability & High Availability - 对数据库的高可扩展性和高可用性的需求【讨论话题】1. 在开发项目做技术选型时,我(们)是如何选择NoSQL的?2. 我(们)目前已经使用了哪些NoSQL数据库?遇到了哪些令人难忘的困难和麻烦?3. 根据我的经验判断,哪个NoSQL是未来的希望之星? 1.在做NoSQL技术选型的时候,谈谈个人的想法。 首先要选择比较成熟的方案,例如MongoDB Redis 这些耳熟能详的,大互联网公司都在用,更有保障性,开源社区支持也非常完善。原因:国内大部分的互联网公司,还不具备对某个Nosql数据库进行二次开发或者扩展的能力,也不具备这个条件,更不可能短期内公司团队自己开发。使用比较广泛的NoSql,功能和社区技术支持相对完善,拥有更多的学习资料,公司技术团队更容易学习和掌握。其次是要合适自身业务场景和基础架构的。每个Nosql产品都会有其自身的优缺点,选择的时候也需要考虑很多问题:假设 MongoDB 是不能进行持久化存储的,而且在命中率不高的情况下必须去读写后端的关系型数据库,这点能否接受吗?假设Redis 数据库的稳定版本仅仅支持跑在Cent OS 5.5以上,而你公司的服务器全部是windows的,这个我想你多半会放弃Redis了。再比如:Memecahed 默认是不支持数据冗余的,也就是说发生单点故障的情况下,就无法保证数据完整性。这点我们的系统能接受吗?我们是否有能力将其扩展,让他支持数据冗余,防止单点故障呢?2.关于使用了哪些,遇到了哪些问题。要说最早使用的NoSql产品,要属Memcached。严格来讲,Memcached只是一套基于K/V存储的分布式缓存系统,并算不上Nosql产品(这得看对nosql的定义了,业内也有很多争议)。至于后来衍生出来的memcachedb(一个由新浪网的开发人员开放出来的开源项目,给memcached分布式缓存服务器添加了Berkeley DB的持久化存储机制和异步主辅复制机制,让memcached具备了事务恢复能力、持久化能力和分布式复制能力,非常适合于需要超高性能读写速度,但是 不需要严格事务约束,能够被持久化保存的应用场景),Membase等,应该算的上真正意义上的Nosql了。在使用Memcached作为辅助存储的时候,遇到过一些很棘手的问题。例如:命中率不高,被迫读取后端的数据库。维护和扩容的成本比较高,后期才启用监控和邮件报警机制。还有很要命的一点就是,当时公司全部使用的是windows服务器授权,windows平台缺乏良好的Nosql支持(或许是团队自身技术有限),导致在性能和高可用方面都大打折扣。为了解决单点故障问题,不惜牺牲了一定的性能。因为当时比较高效、成熟的冗余方案,只能支持Linux服务器,而我们短期内也无法开发出比较完善的方案。后期在业余项目中,使用了更“高级”的Redis,同样遇到过一些困难。首先就是快速熟悉,掌握Redis,这就考验个人学习能力了。在使用过程中,做读写分离的时候(Master/Slave主从复制,和使用Mysql一样的思路),复制过程中,遇到性能瓶颈。经过研究发现,由于Redis使用单线程服务,如果Master快照文件比较大,这样以来传输则需要花费较长时间,还有就是,Master/Slave服务器最好在同一局域网内,避免网络延时。还有就是比较常见的“单点故障”的问题,虽然说主从复制的方式,也可以解决单点故障,但总觉得这不算是高可用的最佳手段。之前在Memcachedb中可以使用 Proxy的方式,实现数据冗余,来避免“单点故障”。Redis中同样也有解决的思路,这里不做太多介绍,有需要的朋友自己去找相关资料研究。还有点值得说的就是,凡是涉及到“持久化存储到硬盘”的,就必读会有磁盘I/O操作,当然就会有一定的性能牺牲。好在Nosql抛开了传统关系型数据库中的默认事务支持和一系列的复杂的检查等。3.如果真要我选择,我觉得Redis将会是未来nosql之星。出于之前对于Memcached的使用喜好,感觉Redis更像一个加强版的Memcached。但是在网络I/O模型,内存管理,可持久化,存储方式(list,set,sorted set,hash等众多数据结构)等方面都有改进,这里就不一一介绍了。时间有限,说了这么多 希望对大家有帮助 也建议朋友们多去尝试 多去学习 不要做新技术的门外汉(这里不是鼓励大家去“赶时髦”) 以上纯属个人浅见,如有错误或者遗漏之处,请指出,也希望大家一起留言讨论。
当今从纯网站技术上来说,因为开源模式的发展,现在建一个小网站已经很简单也很便宜,所以很多人都把创业方向定位在互联网应用。这些人里大多数不是很懂技术,或者不是那么精通,而网站开发维护方面的知识又很分散,学习成本太高,所以这篇文章将这些知识点结合起来,系统的来说,一个从日几千访问的小小网站,到日访问一两百万的小网站,中间可能会产生什么问题,以及怎么才能在一开始做足工作尽量避免这些问题。 相关厂商内容 大众点评主架构师吴其敏,将会在QCon分享大众点评网监控平台经验 Tumblr平台工程总监 Blake,确认参与QCon,分享Tumblr的架构演进 eBay与PayPal的5位全球专家,确认QCon分享主题:大数据分析和电商云计算 创业者之“翼”选拔大赛9月8日火爆开启! 百度技术沙龙第三十一期:推荐引擎算法与技术(10月20日 周六) 相关赞助商 QCon杭州2012大会10月25~27,最后两周,票量有限,欲购从速,5人以上团购享有更多优惠! 你的网站因为努力经营,访问量逐渐升高,在升高的过程中,问题也可能开始显现了。因为带宽的增加、硬件的扩展、人员的扩张所带来的成本提高是显而易见的,而还有相当大的一部分成本是因为代码重构、架构重构,甚至底层开发语言更换引起的,最坏的情况就是数据丢失,所有努力付之一炬。这类成本支出大多数在一开始就可以避免,先打好基础,往后可以省很多精力,少操很多心。 对于不同的初期投资成本,技术路线的选择是不同的。这里假设网站刚刚只是一个构想,计划第一年服务器硬件带宽投入5万左右。对于这个资金额度,有很多种方案可选择,例如租用虚拟主机、租用单独服务器,或者流行的私有云,或者托管服务器。前两种选择,网站发展到一定规模时需迁移,那时再重做规划显然影响更大。服务器托管因为配置自主、能完全掌握控制权,所以有一定规模的网站基本都是这种模式。采用自己托管服务器的网站,一开始要注意以下几点—— 一、开发语言 一般来说,技术人员(程序员)都是根据自己技术背景选择自己最熟悉的语言,不过不可能永远是一个人写程序,所以在语言的选择上还要是要费些心思。首先明确一点,无论用什么语言,最终代码质量是看管理,因此我们从前期开发成本分析。现在国内流行的适用于网站的语言,大概有java、php、.net、python、ruby这五大阵营。python和ruby因为在国内流行的比较晚,现在人员还是相对难招一些。.net平台的人相对多,但是到后期需要解决性能问题时,对人员技能的要求比较高。剩余的java、php用人可以说是最多的。java和php无法从语言层面做比较,但对于初期,应用几乎都是靠前端支撑的网站来说,php入门简单、编写快速,优势相对大一点。至于后端例如行为分析、银行接口、异步消息处理等,等真正需要时,就要根据不同业务需求来选择不同语言了。 二、代码版本管理 稍微有点规模的网站就需要使用代码版本管理了。代码版本管理两点最大的好处,一是方便协同工作,二是有历史记录可查询比较。代码版本管理软件有很多,vss/cvs/svn/hg等,目前国内都比较流行,其中svn的普及度还是很高的。 假设选了svn,那么有几点考虑。一是采用什么树结构。初期可能只有一条主干,往后就需要建立分支,例如一条开发分支,一条上线分支,再往后,可能要每个小组一个分支。建议一开始人少时选择两条分支,开发和线上,每个功能本地测试无误后提交到开发分支,最后统一测试,可以上线时合并到上线分支。如果每人都建自己的分支,合并时会浪费很大精力,对于几乎每天都要修改几次的WEB应用来说,所费时间太多。 向服务器部署代码,可以手工部署也可以自动部署。手工部署相对简单,一般可直接在服务器上svn update,或者找个新目录svn checkout,再把web root给ln -s过去。应用越复杂,部署越复杂,没有什么统一标准,只是别再用ftp上传那种形式,一是上传时文件引用不一致错误率增加,二是很容易出现开发人员的版本跟线上版本不一致,导致本来想改个错字结果变成回滚。如果有多台服务器还是建议自动部署,更换代码的机器从当前服务池中临时撤出,更新完毕后再重新加入。 三、服务器硬件 在各个机房里,靠一台服务器孤独支撑的网站数不清,但如果资金稍微充足,建议至少三台的标准配置,分别用作web处理、数据库、备份。web服务器至少要8G内存,双sata raid1,如果经济稍微宽松,或静态文件或图片多,则15k sas raid10。数据库至少16G内存,15k sas raid 10。备份服务器最好跟数据库服务器同等配置。硬件可以上整套品牌,也可以兼容机,也可以半品牌半组装,取决于经济能力。当然,这是典型的搭配,有些类型应用的性能瓶颈首先出现在web上,那种情况就要单独分析了。 web服务器可以既跑程序又当内存缓存,数据库服务器则只跑主数据库(假如是MySQL的话),备份服务器所承担就相对多一些,web配置、缓存配置、数据库配置都要跟前两台一致,这样WEB和数据库任意一台出问题,很容易就可以将备份服务器切换过去临时顶替,直到解决完问题。要注意,硬件是随时可能坏掉的,特别是硬盘,所以宁可WEB服务器跟数据库服务器放在一起,也一定不能省掉备份,备份一定要异机,并且有异步,电力故障、误操作都可能导致一台机器上的所有数据丢失。很多的开源备份方案可选择,最简单的就是rsync,写crontab里,定时同步。备份和切换,建议多做测试,选最安全最适合业务的,并且尽可能异地备份。 四、机房 三种机房尽量不要选:联通访问特别慢的电信机房、电信访问特别慢的联通机房、电信联通访问特别慢的移动或铁通机房。机房要尽可能多的实地参观,多测试,找个网络质量好,管理严格的机房。机房可以说是非常重要,直接关系到网站访问速度,网站访问速度直接关系到用户体验,访问速度很慢的网站,很难获得用户青睐。 五、架构 在大方向上,被熟知的架构是web负载均衡+数据库主从+缓存+分布式存储+队列。在一开始,按照可扩展的原则设计和编程就可以。只是要多考虑缓存失效时的雪崩效应、主从同步的数据一致性和时间差、队列的稳定性和失败后的重试策略、文件存储的效率和备份方式等等意外情况。缓存失效、数据库复制中断、队列写入错误、电源损坏,在实际运维中经常发生,如果不注意这些,出现问题时恢复期可能会超出预期很长时间。 六、服务器软件 操作系统Linux很流行。在没有专业运维人员的情况下,应倾向于择使用的人多、社区活跃、配置方便、升级方便的发行版,例如RH系列、debian、ubuntu server等,硬件和操作系统要一起选择,看是否有适合的驱动,如果确定用某种商业软件或解决方案,也要提前知晓其对哪种操作系统支持最佳。web服务器方面,apache、nginx、lighttpd三大系列中,apache占有量还是最大,但是想把性能调教好还是需要很专业的,nginx和lighttpd在不需要太多调整的情况下可以达到一个比较不错的性能。无论选择什么软件,除非改过这些软件或你的程序真的不兼容新版本,否则尽量版本越新越好,版本新,意味着新特性增多、BUG减少、性能增加。一个典型的php网站,基本上大多数人都没改过任何服务器软件源代码,绝大多数情况是能平稳的升级到新版本的。类似于jdk5到 jdk6,python2到python3这类变动比较大的升级还是比较少见的。看看ChangeLog,看看升级说明,结合自己情况评估测试一下,越早升级越好,升级的越晚,所花费的成本越高。对于软件包,尽量使用发行版内置的包管理工具,没有特殊要求时不建议自己编译,那样对将来运维不利。 七、数据库 几乎所有操作最后都要落到数据库身上,它又最难扩展(存储也挺难)。数据库常见的扩展方法有复制、分片,设计时要考虑到每种应用的数据如何复制、分片,当然这种考虑一般会推迟到技术设计时期。在初期进行数据库结构设计时,要根据不同的业务类型和增长量预期来考虑是否要分库、分区,并且尽量不要使用联合查询、不使用自增ID以方便分片。复制延时问题、主从数据库数据一致性问题,可以自己写或者用已有的运维工具进行检测。 用存储过程是比较难扩展的,这种情形多发生于传统C/S,特别是OA系统转换过来的开发人员。低成本网站不是一两台小型机跑一个数据库处理所有业务的模式,是机海作战。方便水平扩展比那点预分析时间和网络传输流量要重要的多的多。 另外,现在流行一种概念叫NoSQL,可以理解为非传统关系型数据库。实际应用中,网站有着越来越多的密集写操作、上亿的简单关系数据读取、热备等,这都不是传统关系数据库所擅长的,于是就产生了很多非关系型数据库,比如Redis/TC&TT/MongoDB/Memcachedb等,在测试中,这些几乎都达到了每秒至少一万次的写操作,内存型的甚至5万以上。在设计时,可根据业务特点和性能要求来选择是否使用这类数据库。例如MongoDB,几句配置就可以组建一个复制+自动分片+failover的环境,文档化的存储也简化了传统设计库结构再开发的模式。但是当你决定采用一项技术时,一定要真正了解其优劣,例如可能你所选择的技术并不能支持你所需要的事务和数据一致性要求。 八、文件存储 存储的分布几乎跟数据库扩展一样困难,不过只有百万的PV的情况下,磁盘IO方面一般不会成大问题,一两台采用SATA做条带RAID的机器可以应付,反而是自己做异步备份比较复杂,因为小文件多。如果只有一台机器做存储,可以做简单的优化,例如放最小缩略图的分区和放中等缩略图的分区,根据平均大小调整一下块大小。存储要规划好目录结构,否则文件增多后维护起来复杂,也不利于扩展。同时还要考虑将来扩容,例如采用LVM,或者把文件根据不同规则散列到不同机器。磁盘IO繁重的情况下更容易出现故障,所以要做好备份,若发现有盘坏掉,要马上行动更换,很多人的硬盘都是坏了一块之后,接二连三的坏下去。 为了将来图片走cdn做准备,一开始最好就将图片的域名分开,且不用主域名。因为很多网站都将cookie设置到了.domain.ltd,如果图片也在这个域名下,很可能因为cookie而造成缓存失效,并且占多余流量,还可能因为浏览器并发线程限制造成访问缓慢。 九、程序 一定硬件条件下,应用能承载多少访问量,很大一部分也取决于程序如何写。程序写的不好,可能一万的访问都承载不了,写的好,可能一两台机器就能承担几百万PV。越是复杂、数据实时性要求越高的应用,优化起来越难,但对普通网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。向前端优化指的是,在不影响功能和体验的情况下,能在浏览器执行的不要在服务端执行,能在缓存服务器上直接返回的不要到应用服务器,程序能直接取得的结果不要到外部取得,本机内能取得的数据不要到远程取,内存能取到的不要到磁盘取,缓存中有的不要去数据库查询。减少数据库操作指减少更新次数、缓存结果减少查询次数、将数据库执行的操作尽可能的让你的程序完成(例如join查询),减少磁盘IO指尽量不使用文件系统作为缓存、减少读写文件次数等。程序优化永远要优化慢的部分,换语法是无法“优化”的。 然而编程时不应该把重点放在优化上,应该关注扩展性。当今的WEB应用,需求变化非常之快,适应多种需求的架构是不存在的,我们的扩展性就要把要点放在跟底层交互的架构上,例如持久化数据的存取规则、缓存的存取规则等,还有一些共用服务,例如用户信息等。先把不变的部分做完善,剩下的部分就很容易将精力放在业务逻辑上面了。 本文摘至 http://www.infoq.com/cn/articles/lzy-million-visits-site-technical-preparations
企业级的数据库应用大多部署在RAID磁盘阵列的服务器上,这样能提高磁盘的访问性能,并能够实现容错/容灾。RAID(冗余磁盘阵列),简单理解,就是拿一些廉价的硬盘来做成阵列。其目的无非是为了扩展存储容量,提升读写性能,实现数据冗余(备份容灾)。就像很早就有老外拿N台旧PC,做成一个强大的“服务器集群”。RAID技术诞生于1987年,由美国加州大学伯克利分校提出。主流的大概可以分为几个级别:RAID 0,RAID 1,RAID 5,RAID 10 。配置起来也不是很复杂,有兴趣的朋友可以找相关的资料,自己动手实践。SQL Server 2005常用的有几个级别0,1,5,10 下面我来简单说说这个几个级别的区别及其应用。 RAID 0 简称磁盘条带化,它可以提供最好的读写性能,如果你把两块磁盘做成了RAID0,那么在写入数据的时候,就可以同时对A磁盘和B磁盘执行写入操作。这里我必须说明的是:“可以同时...写入操作”,并不是意味着将文件的相同内容“在同一时间内完全写入”A磁盘和B磁盘中。打个比方:有一个100M的文件需要写入磁盘中,假设单个磁盘的写入速度是10M/S,那么需要10秒钟才能完成写入工作。但如果在具有A、B两块磁盘的RAID 0阵列环境中,(以秒/S为最小单位的)单时间内,可以将10M内容写入A磁盘中,并同时将紧随的10M内容写入B磁盘中,这样算起来,写入速度变成了20M/S,只需要5秒钟就能搞定了,而且每块磁盘中只需存储50M的文件内容,不会造成硬盘存储压力。当然,上诉例子也许不恰当,也只是指的理论环境下,实际环境中会有很多其他因素,效率肯定不能达到。毋庸置疑的是,这样肯定是能提高读写性能的,但是这样也带来了一个问题就是,如果其中的一部分数据丢失了,你的全部数据都不会找回来的,因为RAID0没有提供冗余恢复数据的策略。所以RAID0可以用在只读的数据库数据表,或者是经过复制过来的数据库上,如果你对数据丢失不敏感的话,也可以使用RAID 0,总之这个level下是高性能、无冗余。 RAID 1 磁盘镜像 它对读没有什么影响,如果有两块磁盘它只对写有影响,因为它采用了一块磁盘做冗余备份的方法,这样如果你有两块50G的磁盘,那么加起来就是100G,但是在RAID 1下,那么你只能使用50G ,这种方法会影响磁盘的空间使用,降低了I/O 写的性能。通俗点来讲:你将一个100M的文件写入RAID 1时,讲内容写入A磁盘的同时,也会将相同的内容写入B磁盘中。这样一来,两块磁盘的内容是完全一致的(这就传说中的”冗余“,并不是什么高深的东西)。本来只需要写入1块硬盘的,可是现在要写入到两块硬盘去,效率肯定会变低。至于“读”操作,RAID 1环境下,读取时候只使用到了一块硬盘,所以和普通的环境下没啥区别(如果两块硬盘都能够同时工作,那么还可以分摊压力的)。只是当第一个硬盘数据损坏或者挂掉了,就启动第二块硬盘。当然,两块硬盘都挂了,那就真的崩溃了。哈哈。值得一提的是,有些书或者文章上讲,RAID 1是在将第一块硬盘写入完成后,才将数据完整复制到第二块磁盘中做为镜像备份的这种说法有待考证,按我的理解,是同时复制写入的。RAID 5 与RAID1 不同之处就是多了奇偶校验,所有的奇偶校验的信息会遍布各个磁盘,性能上要比RAID1高些,但是一旦发生磁盘I/O失败,就会造成性能急剧下降,同时这种方法也在RAID0 与RAID1间折了中,是比较通用的做法。 用简单的语言来表示,至少使用3块硬盘(也可以更多)组建RAID5阵列,当有数据写入硬盘的时候,按照1块硬盘的方式就是直接写入这块硬盘的,如果是RAID5的话这次数据写入会分根据算法分成3部分,然后写入这3块硬盘,写入的同时还会在这3块硬盘上写入校验信息,当读取写入的数据的时候会分别从3块硬盘上读取数据内容,再通过检验信息进行校验。当其中有1块硬盘出现损坏的时候,就从另外2块硬盘上存储的数据可以计算出第3块硬盘的数据内容。也就是说RAID5这种存储方式只允许有一块硬盘出现故障,出现故障时需要尽快更换。当更换故障硬盘后,在故障期间写入的数据会进行重新校验。 如果在未解决故障又坏1块,那就是灾难性的了。RAID 10 (也叫RAID 0+1 )就是RAID0 与 RAID1的组合,它提供了高性能,高可用性,性能上要比RAID5好,特别适合大量写入的应用程序,但是就是成本比较高无论是多少块磁盘你都是将损失一半的磁盘存储。按照我的理解,至少需要4块硬盘才能完成,A和B做数据分割,分别存储一半的数据,C和D分别对应做A和B的镜像备份。这样一来,可真是完美了,也是我理想中的最佳状态。也不需要RAID 5的奇偶校验。很显然,这样子成本也会高一些。当然,这就和我们讲架构时候经常说的“负载均衡、高可用集群,横向扩展,纵向扩展”的目的其实很类似。通俗点来讲,都是为了实现不间断的工作,保证数据完整性和高可用性,而且最重要的,还要能分摊压力。突然让我想起了上初中物理时候学的“串联和并联”,有点像哦。顺便讲个丢人的笑话,也是我的真实经历,N年前,某日和某大师在一起抽烟、吹牛、瞎侃,大师瞎侃说”google光是在中国目前有XX台服务器集群......“,当时我并不理解什么叫“集群”,第一反应就是这么多台服务器之间到底是“串联“的还是”并联”的关系呢? 说到最后,可能某些朋友已经不耐烦了。好像本文只是在不停的解释常见RAID的和其好处,而并没提及到sqlserver或者windows存储相关的内容。大家都知道,I/O往往是数据库性能最大的瓶颈, 有时候其实很简单,只需要将sqlserver所在的db server上部署合适的RAID,并将数据库文件分布到不同的磁盘上,就能很大程度的提升数据库的性能,真可谓是立竿见影。有兴趣的朋友,不妨自己实际动手实践一下,网上这方面的资料也很多。其实RAID成本并不高(和DELL、EMC等等那些高端的存储比起来真是“廉价”)。 甚至自己在家里PC机上,也可以通过阵列卡的方式来实现(效果可能不太好)。目前一般的PC服务器,内置都会有RAID支持,在PC服务器领域使用非常广泛。 本文出自http://blog.csdn.net/dinglang_2009,转载请注明出处。
前言: 随着web 2.0 的兴起(最具代表性的是Ajax技术了),javascript不再是程序员眼中的“玩具语言”。 编程在不断的简化,可是“用户体验、性能、兼容性、可扩展......”要求却在不断提高,随之涌现出Prototype、jQuery、ExtJs、Dojo等优秀的框架(类库),大大简化了web开发。 越来越多的人开始深入研究和使用javascript,当然,企业对开发者的要求也越来越高。就拿自己的经历来讲,零几年的时候,我能拿javascript写一些页面UI效果,做网站的表单验证等操作,当时觉得已经很酷了。但是换到现在,如果连XMLHttpRequest、JSON是什么都不知道,连javascript中的面向对象/基于对象编程都不了解,还敢称自己是优秀的web程序员吗?(关注前沿技术的朋友,一定了解node.js、MongoDB,这都离不开javascript。) javascript的灵活性,让人又爱又恨。典型的入门简单,精通很难。理解javascript OOP/基于对象的编程,是判断程序员javascript水平的分水岭。而javascript 基于对象编程中,最基本的是“创建对象”,往往让很多熟悉其他面向语言(Java、C#、C++)的程序员觉得似懂非懂或者难以适应。所以,本文首先将向大家介绍,javascript 中常见的创建对象的几种方式。 1. 简单对象的创建 使用对象字面量的方式{} 创建一个对象(最简单,好理解,推荐使用) var Cat = {};//JSON Cat.name="kity";//添加属性并赋值 Cat.age=2; Cat.sayHello=function(){ alert("hello "+Cat.name+",今年"+Cat["age"]+"岁了");//可以使用“.”的方式访问属性,也可以使用HashMap的方式访问 } Cat.sayHello();//调用对象的(方法)函数 2.用function(函数)来模拟class (无参构造函数) 2.1 创建一个对象,相当于new一个类的实例 function Person(){ } var personOne=new Person();//定义一个function,如果有new关键字去"实例化",那么该function可以看作是一个类 personOne.name="dylan"; personOne.hobby="coding"; personOne.work=function(){ alert(personOne.name+" is coding now..."); } personOne.work(); 2.2 可以使用有参构造函数来实现,这样定义更方便,扩展性更强(推荐使用) function Pet(name,age,hobby){ this.name=name;//this作用域:当前对象 this.age=age; this.hobby=hobby; this.eat=function(){ alert("我叫"+this.name+",我喜欢"+this.hobby+",也是个吃货"); } } var maidou =new Pet("麦兜",5,"睡觉");//实例化/创建对象 maidou.eat();//调用eat方法(函数) 3.使用工厂方式来创建(Object关键字) var wcDog =new Object(); wcDog.name="旺财"; wcDog.age=3; wcDog.work=function(){ alert("我是"+wcDog.name+",汪汪汪......"); } wcDog.work(); 4.使用原型对象的方式 prototype关键字 function Dog(){ } Dog.prototype.name="旺财"; Dog.prototype.eat=function(){ alert(this.name+"是个吃货"); } var wangcai =new Dog(); wangcai.eat(); 5.混合模式(原型和构造函数) function Car(name,price){ this.name=name; this.price=price; } Car.prototype.sell=function(){ alert("我是"+this.name+",我现在卖"+this.price+"万元"); } var camry =new Car("凯美瑞",27); camry.sell(); 6.动态原型的方式(可以看作是混合模式的一种特例) function Car(name,price){ this.name=name; this.price=price; if(typeof Car.sell=="undefined"){ Car.prototype.sell=function(){ alert("我是"+this.name+",我现在卖"+this.price+"万元"); } Car.sell=true; } } var camry =new Car("凯美瑞",27); camry.sell(); 以上几种,是javascript中最常用的创建对象的方式。初学者看到后,可能会晕掉,甚至会觉得担心。其实完全不用担心,这些种方式,只需要掌握一两种,对其他的几种只需要理解就好了。这正是javascript的灵活性。每种方式必定都有其优缺点,因此没有固定的推荐,选择自己最容易理解和掌握的方式即可。况且,每个人的代码风格可能都不同。将来你可能需要去研究jQuery的源码,或者参照别的插件去改写、去开发属于自己的插件,都需要去理解别人的代码风格。而这些类库、插件,都是建立在面向对象/基于对象的基础之上的。 好了,本文就先介绍到这里。由于笔者表达能力和技术水平确实有限,难免有偏差,望读者谅解! 本文出自http://blog.csdn.net/dinglang_2009,转载请注明出处。
最近一直在忙,也没有太多时间停留下来写博客。晚上遇到一个觉得很有趣的sql题,可能对初学者和我这种菜鸟会有帮助,所以决定分享给大家。 由于笔者天生笨拙,且思维不严谨,也实在不擅长写sql语句,高手请勿见笑,就请直接跳过本文吧。 背景就不多介绍了,先建表,插入测试数据吧。字段那些都有注释 --医生表 CREATE TABLE doctor ( id INT IDENTITY(1, 1) , --ID 自增长 docNumber NVARCHAR(50) NOT NULL , --医生编码 NAME NVARCHAR(50) NOT NULL --医生姓名 ) go --插入测试数据 INSERT INTO doctor VALUES ( '007', 'Tom' ) INSERT INTO doctor VALUES ( '008', 'John' ) INSERT INTO doctor VALUES ( '009', 'Jim' ) --号源表(挂号表) CREATE TABLE Nosource ( id INT IDENTITY(1, 1) , docNumber NVARCHAR(50) NOT NULL , --和医生表中的医生编码对应 workTime DATETIME NOT NULL ) go --插入测试数据 INSERT INTO Nosource VALUES ( '007', '20120819' ) INSERT INTO Nosource VALUES ( '007', '20120820' ) INSERT INTO Nosource VALUES ( '007', '20120821' ) INSERT INTO Nosource VALUES ( '008', '20120821' ) 表建好之后,测试数据也OK。下面开始说需求啦。 1.查出每位医生的相关信息,以及该医生所拥有的号源数量。 这简直太简单了,可能连刚学会helloWorld和一点点数据库基础的朋友都会严重真心BS。不过代码还是写出来。 --简单的分组查询即可搞定 SELECT COUNT(nos.id) AS PersonNumSounceCOUNT , --总数 dct.ID AS docID , dct.NAME , dct.docNumber , nos.workTime FROM doctor AS dct LEFT JOIN Nosource AS nos ON dct.docNumber = nos.docNumber GROUP BY dct.ID , dct.NAME , dct.docNumber , nos.workTime 确实简单啊。一个小小的分组就能搞定的。还卖什么关子呢。 那现在需求改变,需要按条件去匹配:要求号源表的workTime大于当前日期才算有效的,否则就不匹配。如果workTime条件不匹配的医生,对应的PersonNumSounceCOUNT字段的值应为0 ;例如:Jim医生没有匹配和符合条件的号源,其PersonNumSounceCOUNT字段值应为0。抬头仰望天空40度,想想能够用where关键字过滤,然后一次性查询出来吗?试试吧。 SELECT COUNT(nos.id) AS PersonNumSounceCOUNT , --总数 dct.ID , dct.NAME , dct.docNumber , nos.workTime FROM doctor AS dct LEFT JOIN Nosource AS nos ON dct.docNumber = nos.docNumber WHERE DATEDIFF(day, GETDATE(), nos.workTime) > 0 GROUP BY dct.ID , dct.NAME , dct.docNumber , nos.workTime 相信有人会写出上面的代码来。可是执行查询后,发现完全不符合要求啊。连Jim医生的基本信息和表记录也都被过滤掉了,不见了。咋回事啊? 原因很简单嘛。在连接查询的后面使用"where"关键字,会过滤连接查询的结果集中的数据。由于右表(号源表)的条件不匹配,也会导致左表(医生表)的数据被过滤掉。 所以,会出现以上的现象(Jim医生的信息和记录都不见了)。要想一次性查出来可能吗?到底该如何去实现呢? 其实,正确的写法应该是这样的: SELECT COUNT(nos.id) AS PersonNumSounceCOUNT , --总数 dct.ID , dct.NAME , dct.docNumber , nos.workTime FROM doctor AS dct LEFT JOIN ( SELECT * FROM Nosource WHERE DATEDIFF(day, GETDATE(), workTime) > 0 ) AS nos ON dct.docNumber = nos.docNumber GROUP BY dct.ID , dct.NAME , dct.docNumber , nos.workTime 再执行一下,果然OK,是满足要求的结果。思路就是:只需要过滤右表,就将(使用子查询)过滤后的结果集作为连接查询的右表,然后再去连接,分组...... 其实编写简洁而高性能的sql语句,是需要很强的逻辑思维能力(和数学分不开)和经验的。还有种更简单的写法: SELECT sum(case when nos.workTime>getdate then 1 else 0 end) AS PersonNumSounceCOUNT , --总数 dct.ID AS docID , dct.NAME , dct.docNumber FROM doctor AS dct LEFT JOIN Nosource AS nos ON dct.docNumber = nos.docNumber GROUP BY dct.ID , dct.NAME , dct.docNumber 这样去解释,不知道大家是否能够理解,反正大致意思就是这样的。笔者的表达能力和水平确实有限,难免有偏差,望读者谅解! 本文出自http://blog.csdn.net/dinglang_2009,转载请注明出处。
在Microsoft SQL Server 2008系统中,数据库管理员(Database Administration,简称为DBA)是最重要的角色。DBA的工作目标就是确保Microsoft SQL Server 2008系统正常高效地运行。DBA的工作也是最繁忙的工作,无论是性能调整,还是灾难恢复,都离不开DBA的支持。 一般地,作为一个DBA,至少应该做好以下12项任务: 任务一:安装和配置; 任务二:容量规划; 任务三:应用架构设计; 任务四:管理数据库对象; 任务五:存储空间管理; 任务六:安全管理; 任务七:备份和恢复; 任务八:性能监视和调优; 任务九:调度作业; 任务十:网络管理; 任务十一:高可用性和高可伸缩性管理; 任务十二:故障解决; 下面简单描述这些DBA的任务 任务一:安装和配置。 DBA的第一项任务是安装和配置Microsoft SQL Server 2008软件系统,为顺利使用Microsoft SQL Server 2008软件创建良好的环境。无论是安装还是配置,都应该根据实际需要来进行,使得系统满足用户的实际需求。需要注意的是,系统配置不是一劳永逸的,应该随时根据需求的变化和环境的需要,进行监视和适当地调整。 任务二:容量规划。 容量规划是对整个Microsoft SQL Server 2008系统进行一个总体的规划。规划的重点应该放在解决瓶颈问题上。可以从内容和期限两个方面考虑系统的容量规划。 从内容上来看,应该考虑的主要内容包括:硬件容量规划、软件规划、网络规划。硬件容量规划包括磁盘空间、CPU、I/O等规划。软件规划包括操作系统的安装和配置规划、数据库规划、数据库对象内容和数量规划等。网络规划包括网络硬件、网络软件和协议、网络客户数量流量和分布、网络拓扑结构等规划。 从期限上来看,应该考虑短期、中期和长期规划。短期规划的目的是满足当前日常业务的需要。中期规划主要是满足业务发展和扩大的需要。长期规划主要是满足业务极限需要等。例如,如果预测某个系统的当前并发用户数量是1000,3年后的用户可能达到1000万,那么这时既不能按照1000用户的需求来设计,也不能一下子按照1000万用户的需求来设计,一定要采取一个折中的形式。 任务三:应用架构设计。 应用架构设计包括数据库设计、应用程序设计和相应的技术架构设计。 数据库设计应该考虑数据库的逻辑需求、数据库的创建方式和数量、数据库数据文件和日志文件的物理位置等。一般情况下,可以在Microsoft SQL Server 2008系统成功安装之后,根据规划的目标,手工创建数据库。 应用设计应该考虑开发工具的选择、API技术、内部资源和外部资源的结合、应用架构的分布等。需要强调是在应用设计时,DBA应该与开发人员共同工作,确保他们编写出优化的代码,尽可能地使用服务器的资源。 技术架构设计主要包括表示层、逻辑层和数据层的分布。这些分布不应该考虑到硬件资源和用户需求。既不能片面地追求过高的硬件资源,也不能仅仅局限于当前的环境,一定要按照可扩展的观点来综合考虑。 任务四:管理数据库对象。 管理数据库对象是使用数据库的最基本、最重要的工作。这些对象包括表、索引、视图、存储过程、函数、触发器、同义词等。为了完成管理数据库对象的工作,DBA应该能够很好地回答诸如下面的这些问题。 系统应该包括哪些数据? 应该怎样存储这些数据? 应该在系统中创建哪些表? 应该在这些表中创建哪些索引,以便加速检索? 是否应该创建视图?为什么要创建这些视图? 应该创建哪些存储过程、函数、CLR对象? 应该在哪些表上创建触发器?应该针对哪些操作创建触发器? 是否应该创建同义词? 任务五:存储空间管理。 存储空间管理任务就是怎样为数据分配空间、怎样保持空间可以满足数据的不断增长。随着业务量的继续和扩大,数据库中的数据也会逐渐地增加,事务日志也不断地增加。存储空间管理任务主要围绕下面几个问题。 当前的数据库由那些数据文件组成? 事务日志的大小应该如何设置? 数据的增长速度是多大? 如何配置数据文件和日志文件的增长方式? 数据库中的数据何时可以清除或转移到其他地方? 任务六:安全管理。 安全性是DBA重要的日常工作之一。安全管理的主要内容包括账户管理和权限管理。账户管理就是在数据库中应该增加哪些账户、这些账户应该组合成哪些角色等等。权限管理是对象权限和语句权限的管理,应该回答下面这些问题: 这些账户或角色应该使用哪些对象? 这些账户或角色应该对这些对象执行哪些操作? 这些账户或角色应该在数据库中执行哪些操作? 如何设置架构?如何建立架构和对象、架构和用户的关系? 任务七:备份和恢复。 无论系统运行如何,系统的灾难性管理是不可缺少的。天灾、人祸、系统缺陷都有可能造成系统的瘫痪、失败。怎样解决这些灾难性问题呢?办法就是制订和实行备份和恢复策略。备份就是制作数据的副本,恢复就是将数据的副本复原到系统中。备份和恢复工作是DBA的一项持续性的重要工作,其执行频率根据数据的重要程度和系统的稳定程度来确定。 任务八:性能监视和调优。 根据企业的经营效益评价企业的管理水平,根据学生的考试成绩评价学生的学习好坏。作为一个大型软件系统,Microsoft SQL Server 2008系统的运行好坏必须得到正确地监视、评价和相应的调整。这是DBA的一项高级工作。借助一些工具和运行性能指标,DBA应该能够监视系统的运行。如果某些运行指标出现了问题,DBA应该及时地采取补救措施,使得系统始终保持高效运行状态。 任务九:调度作业。 DBA不可能一天24小时不停地盯住系统的运行,及时地执行某些指定的操作。Microsoft SQL Server 2008系统提供了许多工具,DBA应该充分利用这些工具和机制,解决下面一些问题。 调度哪些作业应该由系统执行? 这些作业应该在何时执行? 如何确保这些作业可以正确地执行? 如果自动执行的作业执行失败时,应该如何处理? 如何使得系统可以均衡地执行相应的操作? 任务十:网络管理。 作为一种分布式的网络数据库,网络管理的任务更加的重要。Microsoft SQL Server 2008系统提供了网络管理工具和服务,DBA应该借助这些工具进行服务规划和管理网络操作。 任务十一:高可用性和高可伸缩性管理。 作为一个DBA,必须保持系统具有高可用性和高可伸缩性。可用性是一项度量计算机系统正常运行时间的指标。可伸缩性描述应用程序可以接受的并发用户访问的数量问题。影响系统可用性的主要因素包括:网络可靠性、硬件故障、应用程序失败、操作系统崩溃、自然灾害等。无论是数据库系统管理员,还是应用程序设计人员,都应该最小化系统破坏的几率,最大化系统的可用性。在设计系统的可用性时,应该确定采取什么样的可用性策略来满足可用性的需求。 可用性的需求可以通过3个方面描述,即运行的时间、连接性需求和数据的紧密和松散要求。在确定可用性的需求时,首先考虑系统的运行时间。一般地,数据库应用程序有两种运行时间,即在工作时间是可用的和在任何时间都是可用的。如果只是要求在工作时间是可用的,那么可以把系统的维护等工作安排在周末进行。但是,有许多应用程序要求每天运行24小时、每周运行7天,例如,在线超市等,这时必须采取措施保证系统总是运行的。不同的应用程序有不同的连接性要求。大多数的应用程序和电子商务解决方案要求采用可靠的网络连接。这时,要求永久性的在线连接,必须最小化各种异常现象的发生。有些应用程序允许用户离线使用。这时,系统的可用性要求降低了。大多数应用程序要求数据是同步使用的。用户对数据的请求,系统必须立即做出回应。这是紧密型的数据要求,这种情况必须保证系统的高可用性。有些应用程序不需要数据是同步的,对用户的请求可以延迟回应。这种要求是数据松散型的要求,这时系统的可用性需求比较低。 任务十二:故障解决。 虽然不希望Microsoft SQL Server 2008系统出现故障,但是故障可能是无法避免的。这些故障可能每天都会发生。有些故障是人为不小心造成的,有些故障可能是系统中的缺陷形成的,有些故障可能是莫名其妙的。作为一个DBA,在系统中的其他用户心目中是Microsoft SQL Server系统的权威。无论是大事还是小事,DBA都应该做到迅速诊断、准确判断、快速修复。从这个意义上来说,DBA是一个数据库系统的专业医生。
1. 前言 Internet的高速发展,给人们的工作和生活带来了极大的便利,对Internet的服务品质和访问速度要求越来越高,虽然带宽不断增加,用户数量也在不断增加,受Web服务器的负荷和传输距离等因数的影响,响应速度慢还是经常抱怨和困扰。解决方案就是在网络传输上利用缓存技术使得Web服务数据流能就近访问,是优化网络数据传输非常有效的技术,从而获得高速的体验和品质保证。 网络缓存技术,其目的就是减少网络中冗余数据的重复传输,使之最小化,将广域传输转为本地或就近访问。互联网上传递的内容,大部分为重复的Web/FTP数据,Cache服务器及应用Caching技术的网络设备,可大大优化数据链路性能,消除数据峰值访问造成的结点设备阻塞。Cache服务器具有缓存功能,所以大部分网页对象(Web page object),如html, htm, php等页面文件,gif,tif, png, bmp等图片文件,以及其他格式的文件,在有效期(TTL)内,对于重复的访问,不必从原始网站重新传送文件实体,只需通过简单的认证(Freshness Validation)- 传送几十字节的Header,即可将本地的副本直接传送给访问者。由于缓存服务器通常部署在靠近用户端,所以能获得近似局域网的响应速度,并有效减少广域带宽的消耗。据统计,Internet上超过80%的用户重复访问20%的信息资源,给缓存技术的应用提供了先决的条件。缓存服务器的体系结构与Web服务器不同,缓存服务器能比Web服务器获得更高的性能,缓存服务器不仅能提高响应速度,节约带宽,对于加速Web服务器,有效减轻源服务器的负荷是非常有效的。 高速缓存服务器(Cache Server)是软硬件高度集成的专业功能服务器,主要做高速缓存加速服务,一般部署在网络边缘。根据加速对象不同,分为客户端加速和服务器加速,客户端加速Cache部署在网络出口处,把常访问的内容缓存在本地,提高响应速度和节约带宽;服务器加速,Cache部署在服务器前端,作为Web服务器的前置机,提高Web服务器的性能,加速访问速度。如果多台Cache加速服务器且分布在不同地域,需要通过有效地机制管理Cache网络,引导用户就近访问,全局负载均衡流量,这就是CDN内容传输网络的基本思想。 2.什么是CDN? CDN的全称是Content Delivery Network,即内容分发网络。其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络"边缘",使用户可以就近取得所需的内容,解决Internet网络拥塞状况,提高用户访问网站的响应速度。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等原因,解决用户访问网站的响应速度慢的根本原因。 狭义地讲,内容分发布网络(CDN)是一种新型的网络构建方式,它是为能在传统的IP网发布宽带丰富媒体而特别优化的网络覆盖层;而从广义的角度,CDN代表了一种基于质量与秩序的网络服务模式。简单地说,内容发布网络(CDN)是一个经策略性部署的整体系统,包括分布式存储、负载均衡、网络请求的重定向和内容管理4个要件,而内容管理和全局的网络流量管理(Traffic Management)是CDN的核心所在。通过用户就近性和服务器负载的判断,CDN确保内容以一种极为高效的方式为用户的请求提供服务。总的来说,内容服务基于缓存服务器,也称作代理缓存(Surrogate),它位于网络的边缘,距用户仅有"一跳"(Single Hop)之遥。同时,代理缓存是内容提供商源服务器(通常位于CDN服务提供商的数据中心)的一个透明镜像。这样的架构使得CDN服务提供商能够代表他们客户,即内容供应商,向最终用户提供尽可能好的体验,而这些用户是不能容忍请求响应时间有任何延迟的。据统计,采用CDN技术,能处理整个网站页面的70%~95%的内容访问量,减轻服务器的压力,提升了网站的性能和可扩展性。 与目前现有的内容发布模式相比较,CDN强调了网络在内容发布中的重要性。通过引入主动的内容管理层的和全局负载均衡,CDN从根本上区别于传统的内容发布模式。在传统的内容发布模式中,内容的发布由ICP的应用服务器完成,而网络只表现为一个透明的数据传输通道,这种透明性表现在网络的质量保证仅仅停留在数据包的层面,而不能根据内容对象的不同区分服务质量。此外,由于IP网的"尽力而为"的特性使得其质量保证是依靠在用户和应用服务器之间端到端地提供充分的、远大于实际所需的带宽通量来实现的。在这样的内容发布模式下,不仅大量宝贵的骨干带宽被占用,同时ICP的应用服务器的负载也变得非常重,而且不可预计。当发生一些热点事件和出现浪涌流量时,会产生局部热点效应,从而使应用服务器过载退出服务。这种基于中心的应用服务器的内容发布模式的另外一个缺陷在于个性化服务的缺失和对宽带服务价值链的扭曲,内容提供商承担了他们不该干也干不好的内容发布服务。 纵观整个宽带服务的价值链,内容提供商和用户位于整个价值链的两端,中间依靠网络服务提供商将其串接起来。随着互联网工业的成熟和商业模式的变革,在这条价值链上的角色越来越多也越来越细分。比如内容/应用的运营商、托管服务提供商、骨干网络服务提供商、接入服务提供商等等。在这一条价值链上的每一个角色都要分工合作、各司其职才能为客户提供良好的服务,从而带来多赢的局面。从内容与网络的结合模式上看,内容的发布已经走过了ICP的内容(应用)服务器和IDC这两个阶段。IDC的热潮也催生了托管服务提供商这一角色。但是,IDC并不能解决内容的有效发布问题。内容位于网络的中心并不能解决骨干带宽的占用和建立IP网络上的流量秩序。因此将内容推到网络的边缘,为用户提供就近性的边缘服务,从而保证服务的质量和整个网络上的访问秩序就成了一种显而易见的选择。而这就是内容发布网(CDN)服务模式。CDN的建立解决了困扰内容运营商的内容"集中与分散"的两难选择。无疑对于构建良好的互联网价值链是有价值的,也是不可或缺的。 3.CDN新应用和客户 目前的CDN服务主要应用于证券、金融保险、ISP、ICP、网上交易、门户网站、大中型公司、网络教学等领域。另外在行业专网、互联网中都可以用到,甚至可以对局域网进行网络优化。利用CDN,这些网站无需投资昂贵的各类服务器、设立分站点,特别是流媒体信息的广泛应用、远程教学课件等消耗带宽资源多的媒体信息,应用CDN网络,把内容复制到网络的最边缘,使内容请求点和交付点之间的距离缩至最小,从而促进Web站点性能的提高,具有重要的意义。CDN网络的建设主要有企业建设的CDN网络,为企业服务;IDC的CDN网络,主要服务于IDC和增值服务;网络运营上主建的CDN网络,主要提供内容推送服务;CDN网络服务商,专门建设的CDN用于做服务,用户通过与CDN机构进行合作,CDN负责信息传递工作,保证信息正常传输,维护传送网络,而网站只需要内容维护,不再需要考虑流量问题。 CDN能够为网络的快速、安全、稳定、可扩展等方面提供保障。 IDC建立CDN网络,IDC运营商一般需要有分部各地的多个IDC中心,服务对象是托管在IDC中心的客户,利用现有的网络资源,投资较少,容易建设。例如某IDC全国有10个机房,加入IDC的CDN网络,托管在一个节点的Web服务器,相当于有了10个镜像服务器,就近供客户访问。宽带城域网,域内网络速度很快,出城带宽一般就会瓶颈,为了体现城域网的高速体验,解决方案就是将Internet网上内容高速缓存到本地,将Cache部署在城域网各POP点上,这样形成高效有序的网络,用户仅一跳就能访问大部分的内容,这也是一种加速所有网站CDN的应用。 4.CDN 的工作原理 在描述CDN的实现原理,让我们先看传统的未加缓存服务的访问过程,以便了解CDN缓存访问方式与未加缓存访问方式的差别: 由上图可见,用户访问未使用CDN缓存网站的过程为: 1)、用户向浏览器提供要访问的域名; 2)、浏览器调用域名解析函数库对域名进行解析,以得到此域名对应的IP地址; 3)、浏览器使用所得到的IP地址,域名的服务主机发出数据访问请求; 4)、浏览器根据域名主机返回的数据显示网页的内容。 通过以上四个步骤,浏览器完成从用户处接收用户要访问的域名到从域名服务主机处获取数据的整个过程。CDN网络是在用户和服务器之间增加Cache层,如何将用户的请求引导到Cache上获得源服务器的数据,主要是通过接管DNS实现,下面让我们看看访问使用CDN缓存后的网站的过程: 通过上图,我们可以了解到,使用了CDN缓存后的网站的访问过程变为: 1)、用户向浏览器提供要访问的域名; 2)、浏览器调用域名解析库对域名进行解析,由于CDN对域名解析过程进行了调整,所以解析函数库一般得到的是该域名对应的CNAME记录,为了得到实际IP地址,浏览器需要再次对获得的CNAME域名进行解析以得到实际的IP地址;在此过程中,使用的全局负载均衡DNS解析,如根据地理位置信息解析对应的IP地址,使得用户能就近访问。 3)、此次解析得到CDN缓存服务器的IP地址,浏览器在得到实际的IP地址以后,向缓存服务器发出访问请求; 4)、缓存服务器根据浏览器提供的要访问的域名,通过Cache内部专用DNS解析得到此域名的实际IP地址,再由缓存服务器向此实际IP地址提交访问请求; 5)、缓存服务器从实际IP地址得得到内容以后,一方面在本地进行保存,以备以后使用,二方面把获取的数据返回给客户端,完成数据服务过程; 6)、客户端得到由缓存服务器返回的数据以后显示出来并完成整个浏览的数据请求过程。 通过以上的分析我们可以得到,为了实现既要对普通用户透明(即加入缓存以后用户客户端无需进行任何设置,直接使用被加速网站原有的域名即可访问),又要在为指定的网站提供加速服务的同时降低对ICP的影响,只要修改整个访问过程中的域名解析部分,以实现透明的加速服务,下面是CDN网络实现的具体操作过程。 1)、作为ICP,只需要把域名解释权交给CDN运营商,其他方面不需要进行任何的修改;操作时,ICP修改自己域名的解析记录,一般用cname方式指向CDN网络Cache服务器的地址。 2)、作为CDN运营商,首先需要为ICP的域名提供公开的解析,为了实现sortlist,一般是把ICP的域名解释结果指向一个CNAME记录; 3)、当需要进行sorlist时,CDN运营商可以利用DNS对CNAME指向的域名解析过程进行特殊处理,使DNS服务器在接收到客户端请求时可以根据客户端的IP地址,返回相同域名的不同IP地址; 4)、由于从cname获得的IP地址,并且带有hostname信息,请求到达Cache之后,Cache必须知道源服务器的IP地址,所以在CDN运营商内部维护一个内部DNS服务器,用于解释用户所访问的域名的真实IP地址; 5)、在维护内部DNS服务器时,还需要维护一台授权服务器,控制哪些域名可以进行缓存,而哪些又不进行缓存,以免发生开放代理的情况。 5.CDN的技术手段 实现CDN的主要技术手段是高速缓存、镜像服务器。可工作于DNS解析或HTTP重定向两种方式,通过Cache服务器,或异地的镜像站点完成内容的传送与同步更新。DNS方式用户位置判断准确率大于85%,HTTP方式准确率为99%以上;一般情况下,各Cache服务器群的用户访问流入数据量与Cache服务器到原始网站取内容的数据量之比在2:1到3:1之间,即分担50%到70%的到原始网站重复访问数据量(主要是图片,流媒体文件等内容);对于镜像,除数据同步的流量,其余均在本地完成,不访问原始服务器。 镜像站点(Mirror Site)服务器是我们经常可以看到的,它让内容直截了当地进行分布,适用于静态和准动态的数据同步。但是购买和维护新服务器的费用较高,另外还必须在各个地区设置镜像服务器,配备专业技术人员进行管理与维护。大型网站在随时更新各地服务器的同时,对带宽的需求也会显著增加,因此一般的互联网公司不会建立太多的镜像服务器。 高速缓存手段的成本较低,适用于静态内容。Internet的统计表明,超过80%的用户经常访问的是20%的网站的内容,在这个规律下,缓存服务器可以处理大部分客户的静态请求,而原始的WWW服务器只需处理约20%左右的非缓存请求和动态请求,于是大大加快了客户请求的响应时间,并降低了原始WWW服务器的负载。根据美国IDC公司的调查,作为CDN的一项重要指标 —— 缓存的市场正在以每年近100%的速度增长,全球的营业额在2004年将达到45亿美元。网络流媒体的发展还将剌激这个市场的需求。 6.CDN的网络架构 CDN网络架构主要由两大部分,分为中心和边缘两部分,中心指CDN网管中心和DNS重定向解析中心,负责全局负载均衡,设备系统安装在管理中心机房,边缘主要指异地节点,CDN分发的载体,主要由Cache和负载均衡器等组成。 当用户访问加入CDN服务的网站时,域名解析请求将最终交给全局负载均衡DNS进行处理。全局负载均衡DNS通过一组预先定义好的策略,将当时最接近用户的节点地址提供给用户,使用户能够得到快速的服务。同时,它还与分布在世界各地的所有CDNC节点保持通信,搜集各节点的通信状态,确保不将用户的请求分配到不可用的CDN节点上,实际上是通过DNS做全局负载均衡。 对于普通的Internet用户来讲,每个CDN节点就相当于一个放置在它周围的WEB。通过全局负载均衡DNS的控制,用户的请求被透明地指向离他最近的节点,节点中CDN服务器会像网站的原始服务器一样,响应用户的请求。由于它离用户更近,因而响应时间必然更快。 每个CDN节点由两部分组成:负载均衡设备和高速缓存服务器 负载均衡设备负责每个节点中各个Cache的负载均衡,保证节点的工作效率;同时,负载均衡设备还负责收集节点与周围环境的信息,保持与全局负载DNS的通信,实现整个系统的负载均衡。 高速缓存服务器(Cache)负责存储客户网站的大量信息,就像一个靠近用户的网站服务器一样响应本地用户的访问请求。 CDN的管理系统是整个系统能够正常运转的保证。它不仅能对系统中的各个子系统和设备进行实时监控,对各种故障产生相应的告警,还可以实时监测到系统中总的流量和各节点的流量,并保存在系统的数据库中,使网管人员能够方便地进行进一步分析。通过完善的网管系统,用户可以对系统配置进行修改。 理论上,最简单的CDN网络有一个负责全局负载均衡的DNS和各节点一台Cache,即可运行。DNS支持根据用户源IP地址解析不同的IP,实现就近访问。为了保证高可用性等,需要监视各节点的流量、健康状况等。一个节点的单台Cache承载数量不够时,才需要多台Cache,多台Cache同时工作,才需要负载均衡器,使Cache群协同工作。 7. CDN 示例 商业化的CDN网络是用于服务性质的,高可用性等要求非常高,有专业产品和CDN网络解决方案,本文主要从理论角度,理解CDN的实现过程,并利用已有网络环境和开源软件做实际配置,更深刻理解CDN的具体工作过程。 Linux 是开放源代码的免费操作系统,已经成功应用于许多关键领域。Bind是Unix/FreeBSD/Linux等类Unix平台上非常有名DNS服务程序,Internet上超过60%的DNS运行的是bind。Bind的最新版本是9.x,用的比较多的是8.x,bind 9有很多新特性,其中一项是根据用户端源地址对同一域名解析不同的IP地址,有了这种特性,能把用户对同一域名的访问,引导到不同地域节点的服务器上去访问。Squid是Linux等操作系统上有名的Cache引擎,与商业Cache引擎相比,Squid的性能比较低,基本功能工作原理与商业Cache产品是一致的,作为试验,是非常容易配置运行起来。以下简要介绍CDN的配置流程。 1、要加入CDN服务的网站,需要域名(如www.linuxaid.com.cn, 地址202.99.11.120)解析权提供给CDN运营商,Linuxaid的域名解析记录只要把www主机的A记录改为CNAME并指向cache.cdn.com即可。cache.cdn.com是CDN网络自定义的缓存服务器的标识。在/var/named/linuxaid.com.cn域名解析记录中,由: www IN A 202.99.11.120改为www IN CNAME cache.cdn.com. 2、CDN运营商得到域名解析权以后,得到域名的CNAME记录,指向CDN网络属下缓存服务器的域名,如cache.cdn.com,CDN网络的全局负载均衡DNS,需要把CNAME记录根据策略解析出IP地址,一般是给出就近访问的Cache地址。 Bind 9的基本功能可以根据不同的源IP地址段解析对应的IP,实现根据地域就近访问的负载均衡,一般可以通过Bind 9的sortlist选项实现根据用户端IP地址返回最近的节点IP地址,具体的过程为: 1) 为cache.cdn.com设置多个A记录,/var/named/cdn.com 的内容如下: $TTL 3600@ IN SOA ns.cdn.com. root.ns.cdn.com. ( 2002090201 ;Serial num 10800 ;Refresh after 3 hours 3600 ;Retry 604800 ;Expire 1800 ;Time to live ) IN NS nswww IN A 210.33.21.168ns IN A 202.96.128.68cache IN A 202.93.22.13 ;有多少个CACHE地址cache IN A 210.21.30.90 ;就有多少个CACHE的A记录cache IN A 211.99.13.47 2) /etc/named.conf中的内容为: options { directory "/var/named"; sortlist {#这一段表示当在本地执行查询时#将按照202.93.22.13,210.21.30.90,211.99.13.47的顺序返回地址 { localhost; { localnets; 202.93.22.13; { 210.21.30.90; 211.99.13.47; }; }; };#这一段表示当在202/8地址段进行DNS查询时#将按照202.93.22.13,210.21.30.90,211.99.13.47的顺序返回地址 { 202/8; { 202.93.22.13; { 210.21.30.90; 211.99.13.47; }; }; };#这一段表示当在211/8地址段进行DNS查询时#将按照211.99.13.47,202.93.22.13,210.21.30.90的顺序返回地址,#也就是211.99.13.47是最靠近查询地点的节点 { 211/8; { 211.99.13.47; { 202.93.22.13; 210.21.30.90; }; }; }; { 61/8; { 202.93.22.13; { 210.21.30.90; 211.99.13.47; }; }; }; };};zone "." { type hint; file "root.cache";};zone "localhost" { type master; file "localhost";};zone "cdn.com" { type master; file "cdn.com";}; 3、Cache在CDN网络中如果工作在服务器加速模式,因为配置里已经写明加速服务器的url,所以Cache直接匹配用户请求,到源服务器获得内容并缓存供下次使用;如果Cache工作在客户端加速模式,Cache需要知道源服务器的IP地址,所以CDN网络维护和运行一个供Cache使用的DNS服务器,解析域名的真实IP地址,如202.99.11.120 ,各域名的解析记录与未加入CDN网络之前一样。 4、工作在CDN网络中缓存服务器必须工作在透明方式,对于Squid来说,需要设置以下参数: httpd_accel_host virtualhttpd_accel_port 80httpd_accel_with_proxy onhttpd_accel_uses_host_header on 本文出自http://kb.cnblogs.com/page/121664/,转载请注明。
昨日“拜读”《sqlserver2005高级程序设计》和《SQL Server 2008编程入门经典(第3版)》这两本翻译后的中文版书籍。竟然发现目录结构大致一样,其讲解的内容几乎差不多。有抄袭的嫌疑。看到“事务和锁”那一张中,发现连举的小例子、表格都一模一样。哈哈。。。对这类书籍,真不想做太多评论了。国内那些翻译版的书籍嘛。说真的,大部分翻译得有点生硬。而那些“原创著作”嘛。大多是相互抄袭,空谈。就微软技术体系而言,如果直接从MSDN或者联机丛书中copy一下,再随便贴几页的代码,那样就能出版销售,那可能我也能著书了,因为那确实没啥水平。 当然,也不乏精品之作,只是很少且很难找到罢了。好了,言归正传,开始说说事务和 锁,这大概是数据库中比较难理解的东西了。 一、脏读、不可重复读、幻读 (1)脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。 例如: 张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。 与此同时, 事务B正在读取张三的工资,读取到张三的工资为8000。 随后, 事务A发生异常,而回滚了事务。张三的工资又回滚为5000。 最后, 事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。 (2)不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。 例如: 在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。 与此同时, 事务B把张三的工资改为8000,并提交了事务。 随后, 在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。 (3)幻读: 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。 例如: 目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。 此时, 事务B插入一条工资也为5000的记录。 这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。 不可重复读的重点是修改: 同样的条件,你读取过的数据,再次读取出来发现值不一样了 幻读的重点在于新增或者删除:同样的条件,第 1 次和第 2 次读出来的记录数不一样 二、独占锁、共享锁、更新锁,乐观锁、悲观锁 1、锁的两种分类方式 (1)从数据库系统的角度来看,锁分为以下三种类型: 独占锁(Exclusive Lock) 独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、 UPDATE 或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。 共享锁(Shared Lock) 共享锁锁定的资源可以被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放。 更新锁(Update Lock) 更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server 确定要进行更新数据操作时,它会自动将更新锁换为独占锁。但当对象上有其它锁存在时,无法对其作更新锁锁定。 (2)从程序员的角度看,锁分为以下两种类型: 悲观锁(Pessimistic Lock) 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 乐观锁(Optimistic Lock) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。 2、数据库中如何使用锁 首先从悲观锁开始说。在SqlServer等其余很多数据库中,数据的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任何时间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完以后才能依次插入。带来的后果就是性能的降低,在多用户并发访问的时候,当对一张表进行频繁操作时,会发现响应效率很低,数据库经常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。 注:对于悲观锁是针对并发的可能性比较大,而一般在我们的应用中用乐观锁足以。 Oracle的悲观锁需要利用一条现有的连接,分成两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。比如我们看一个例子。 首先建立测试用的数据库表: CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept 这里我们利用了Oracle的Sample的scott用户的表,把数据copy到我们的test表中。 (1)for update 形式介绍 然后我们看一下for update锁定方式。我们执行如下的select for update语句: select * from test where id = 10 for update 通过这条检索语句锁定以后,再开另外一个sql*plus窗口进行操作,再把上面这条sql语句执行一便,你会发现sqlplus好像死在那里了,好像检索不到数据的样子,但是也不返回任何结果,就属于卡在那里的感觉。这个时候是什么原因呢,就是一开始的第一个Session中的select for update语句把数据锁定住了。由于这里锁定的机制是wait的状态(只要不表示nowait那就是wait),所以第二个Session(也就是卡住的那个sql*plus)中当前这个检索就处于等待状态。当第一个session最后commit或者rollback之后,第二个session中的检索结果就是自动跳出来,并且也把数据锁定住。 不过如果你第二个session中你的检索语句如下所示:select * from test where id = 10,也就是没有for update这种锁定数据的语句的话,就不会造成阻塞了。 (2)for update nowait 形式介绍 另外一种情况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql以后,我们在另外一个session中执行for update nowait后又是什么样呢。 比如如下的sql语句: select * from test where id = 10 for update nowait 由于这条语句中是制定采用nowait方式来进行检索,所以当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源。所以在程序中我们可以采用nowait方式迅速判断当前数据是否被锁定中,如果锁定中的话,就要采取相应的业务措施进行处理。 那这里另外一个问题,就是当我们锁定住数据的时候,我们对数据进行更新和删除的话会是什么样呢。 比如同样,我们让第一个Session锁定住id=10的那条数据,我们在第二个session中执行如下语句: update test set value=2 where id = 10 这个时候我们发现update语句就好像select for update语句一样也停住卡在这里,当你第一个session放开锁定以后update才能正常运行。当你update运行后,数据又被你update 语句锁定住了,这个时候只要你update后还没有commit,别的session照样不能对数据进行锁定更新等等。 总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不小心搞成死锁了就好。而且由于数据的及时锁定,在数据提交时候就不呼出现冲突,可以省去很多恼人的数据冲突处理。缺点就是你必须要始终有一条数据库连接,就是说在整个锁定到最后放开锁的过程中,你的数据库联接要始终保持住。 与悲观锁相对的,我们有了乐观锁。乐观锁一开始也说了,就是一开始假设不会造成数据冲突,在最后提交的时候再进行数据冲突检测。 在乐观锁中,我们有3种常用的做法来实现: 第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。 当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。 第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。 采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。 比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个时候再进行业务处理,比如整个Transaction都Rollback等等操作。 在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用 Trigger。 第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。 在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。 和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。 三、事务五种隔离级别 Isolation 属性一共支持五种事务设置,具体介绍如下: (1)DEFAULT 使用数据库设置的隔离级别(默认),由DBA 默认的设置来决定隔离级别。 (2)READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。 会出现脏读、不可重复读、幻读 (隔离级别最低,并发性能高)。 (3)READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 可以避免脏读,但会出现不可重复读、幻读问题(锁定正在读取的行)。 (4)REPEATABLE_READ 可以防止脏读、不可重复读,但会出幻读(锁定所读取的所有行)。 (5)SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。 保证所有的情况不会发生(锁表)。
随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题。对于一个大型的互联网应用,每天百万级甚至上亿的PV无疑对数据库造成了相当高的负载。对于系统的稳定性和扩展性造成了极大的问题。 一、负载均衡技术负载均衡集群是由一组相互独立的计算机系统构成,通过常规网络或专用网络进行连接,由路由器衔接在一起,各节点相互协作、共同负载、均衡压力,对客户端来说,整个群集可以视为一台具有超高性能的独立服务器。 1、实现原理实现数据库的负载均衡技术,首先要有一个可以控制连接数据库的控制端。在这里,它截断了数据库和程序的直接连接,由所有的程序来访问这个中间层,然后再由中间层来访问数据库。这样,我们就可以具体控制访问某个数据库了,然后还可以根据数据库的当前负载采取有效的均衡策略,来调整每次连接到哪个数据库。2、实现多据库数据同步对于负载均衡,最重要的就是所有服务器的数据都是实时同步的。这是一个集群所必需的,因为,如果数不据实时、不同步,那么用户从一台服务器读出的数据,就有别于从另一台服务器读出的数据,这是不能允许的。所以必须实现数据库的数据同步。这样,在查询的时候就可以有多个资源,实现均衡。比较常用的方法是Moebius for SQL Server集群,Moebius for SQL Server集群采用将核心程序驻留在每个机器的数据库中的办法,这个核心程序称为Moebius for SQL Server 中间件,主要作用是监测数据库内数据的变化并将变化的数据同步到其他数据库中。数据同步完成后客户端才会得到响应,同步过程是并发完成的,所以同步到多个数据库和同步到一个数据库的时间基本相等;另外同步的过程是在事务的环境下完成的,保证了多份数据在任何时刻数据的一致性。正因为Moebius 中间件宿主在数据库中的创新,让中间件不但能知道数据的变化,而且知道引起数据变化的SQL语句,根据SQL语句的类型智能的采取不同的数据同步的策略以保证数据同步成本的最小化。 数据条数很少,数据内容也不大,则直接同步数据数据条数很少,但是里面包含大数据类型,比如文本,二进制数据等,则先对数据进行压缩然后再同步,从而减少网络带宽的占用和传输所用的时间。数据条数很多,此时中间件会拿到造成数据变化的SQL语句, 然后对SQL语句进行解析,分析其执行计划和执行成本,并选择是同步数据还是同步SQL语句到其他的数据库中。此种情况应用在对表结构进行调整或者批量更改数据的时候非常有用。3、优缺点(1) 扩展性强:当系统要更高数据库处理速度时,只要简单地增加数据库服务器就 可以得到扩展。(2) 可维护性:当某节点发生故障时,系统会自动检测故障并转移故障节点的应用,保证数据库的持续工作。(3) 安全性:因为数据会同步的多台服务器上,可以实现数据集的冗余,通过多份数据来保证安全性。另外它成功地将数据库放到了内网之中,更好地保护了数据库的安全性。(4) 易用性:对应用来说完全透明,集群暴露出来的就是一个IP (1) 不能够按照Web服务器的处理能力分配负载。(2) 负载均衡器(控制端)故障,会导致整个数据库系统瘫痪。 二、数据库的读写分离1,实现原理:读写分离简单的说是把对数据库读和写的操作分开对应不同的数据库服务器,这样能有效地减轻数据库压力,也能减轻io压力。主数据库提供写操作,从数据库提供读操作,其实在很多系统中,主要是读的操作。当主数据库进行写操作时,数据要同步到从的数据库,这样才能有效保证数据库完整性。 (ebay的读写比率是260:1,ebay的读写分离) (微软数据库分发) 2,实现方法:在MS Sql server中可以使用发布定义的方式实现数据库复制,实现读写分离,复制是将一组数据从一个数据源拷贝到多个数据源的技术,是将一份数据发布到多个存储站点上的有效方式。使用复制技术,用户可以将一份数据发布到多台服务器上。复制技术可以确保分布在不同地点的数据自动同步更新,从而保证数据的一致性。SQL SERVER复制技术类型有三种,分别是:快照复制、事务复制、合并复制。SQL SERVER 主要采用出版物、订阅的方式来处理复制。源数据所在的服务器是出版服务器,负责发表数据。出版服务器把要发表的数据的所有改变情况的拷贝复制到分发服务器,分发服务器包含有一个分发数据库,可接收数据的所有改变,并保存这些改变,再把这些改变分发给订阅服务器。 3,优缺点(1)数据的实时性差:数据不是实时同步到自读服务器上的,当数据写入主服务器后,要在下次同步后才能查询到。 (2)数据量大时同步效率差:单表数据量过大时插入和更新因索引,磁盘IO等问题,性能会变的很差。 (3)同时连接多个(至少两个)数据库:至少要连接到两个数据数据库,实际的读写操作是在程序代码中完成的,容易引起混乱 (4)读具有高性能高可靠性和可伸缩:只读服务器,因为没有写操作,会大大减轻磁盘IO等性能问题,大大提高效率;只读服务器可以采用负载均衡,主数据库发布到多个只读服务器上实现读操作的可伸缩性。 三、数据库/数据表 拆分(分布式) 通过某种特定的条件,将存放在同一个数据库中的数据分散存放到多个数据库上,实现分布存储,通过路由规则路由访问特定的数据库,这样一来每次访问面对的就不是单台服务器了,而是N台服务器,这样就可以降低单台机器的负载压力。提示:sqlserver 2005版本之后,可以友好的支持“表分区”。 垂直(纵向)拆分:是指按功能模块拆分,比如分为订单库、商品库、用户库...这种方式多个数据库之间的表结构不同。 水平(横向)拆分:将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。 (纵向拆分) (横向拆分) 1,实现原理:使用垂直拆分,主要要看应用类型是否合适这种拆分方式,如系统可以分为,订单系统,商品管理系统,用户管理系统业务系统比较明的,垂直拆分能很好的起到分散数据库压力的作用。业务模块不明晰,耦合(表关联)度比较高的系统不适合使用这种拆分方式。但是垂直拆分方式并不能彻底解决所有压力问题,例如 有一个5000w的订单表,操作起来订单库的压力仍然很大,如我们需要在这个表中增加(insert)一条新的数据,insert完毕后,数据库会针对这张表重新建立索引,5000w行数据建立索引的系统开销还是不容忽视的,反过来,假如我们将这个表分成100个table呢,从table_001一直到table_100,5000w行数据平均下来,每个子表里边就只有50万行数据,这时候我们向一张只有50w行数据的table中insert数据后建立索引的时间就会呈数量级的下降,极大了提高了DB的运行时效率,提高了DB的并发量,这种拆分就是横向拆分 2,实现方法:垂直拆分,拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。横向拆分的规则很多,这里总结前人的几点, (1)顺序拆分:如可以按订单的日前按年份才分,2003年的放在db1中,2004年的db2,以此类推。当然也可以按主键标准拆分。 优点:可部分迁移 缺点:数据分布不均,可能2003年的订单有100W,2008年的有500W。 (2)hash取模分: 对user_id进行hash(或者如果user_id是数值型的话直接使用user_id的值也可),然后用一个特定的数字,比如应用中需要将一个数据库切分成4个数据库的话,我们就用4这个数字对user_id的hash值进行取模运算,也就是user_id%4,这样的话每次运算就有四种可能:结果为1的时候对应DB1;结果为2的时候对应DB2;结果为3的时候对应DB3;结果为0的时候对应DB4,这样一来就非常均匀的将数据分配到4个DB中。优点:数据分布均匀缺点:数据迁移的时候麻烦;不能按照机器性能分摊数据 。(3)在认证库中保存数据库配置就是建立一个DB,这个DB单独保存user_id到DB的映射关系,每次访问数据库的时候都要先查询一次这个数据库,以得到具体的DB信息,然后才能进行我们需要的查询操作。优点:灵活性强,一对一关系缺点:每次查询之前都要多一次查询,会造成一定的性能损失。
Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) values(value1,value2,...)这种形式的在应用程序开发中必不可少。但我们在开发、测试过程中,经常会遇到需要表复制的情况,如将一个table1的数据的部分字段复制到table2中,或者将整个table1复制到table2中,这时候我们就要使用SELECT INTO 和 INSERT INTO SELECT 表复制语句了。 1.INSERT INTO SELECT语句 语句形式为:Insert into Table2(field1,field2,...) select value1,value2,... from Table1 要求目标表Table2必须存在,由于目标表Table2已经存在,所以我们除了插入源表Table1的字段外,还可以插入常量。示例如下: --1.创建测试表 create TABLE Table1 ( a varchar(10), b varchar(10), c varchar(10), CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED ( a ASC ) ) ON [PRIMARY] create TABLE Table2 ( a varchar(10), c varchar(10), d int, CONSTRAINT [PK_Table2] PRIMARY KEY CLUSTERED ( a ASC ) ) ON [PRIMARY] GO --2.创建测试数据 Insert into Table1 values('赵','asds','90') Insert into Table1 values('钱','asds','100') Insert into Table1 values('孙','asds','80') Insert into Table1 values('李','asds',null) GO select * from Table2 --3.INSERT INTO SELECT语句复制表数据 Insert into Table2(a, c, d) select a,c,5 from Table1 GO --4.显示更新后的结果 select * from Table2 GO --5.删除测试表 drop TABLE Table1 drop TABLE Table2 2.SELECT INTO FROM语句 语句形式为:SELECT vale1, value2 into Table2 from Table1 要求目标表Table2不存在,因为在插入时会自动创建表Table2,并将Table1中指定字段数据复制到Table2中。示例如下: --1.创建测试表 create TABLE Table1 ( a varchar(10), b varchar(10), c varchar(10), CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED ( a ASC ) ) ON [PRIMARY] GO --2.创建测试数据 Insert into Table1 values('赵','asds','90') Insert into Table1 values('钱','asds','100') Insert into Table1 values('孙','asds','80') Insert into Table1 values('李','asds',null) GO --3.SELECT INTO FROM语句创建表Table2并复制数据 select a,c INTO Table2 from Table1 GO --4.显示更新后的结果 select * from Table2 GO --5.删除测试表 drop TABLE Table1 drop TABLE Table2
本文为译文,原文地址:http://www.codinghorror.com/blog/2007/08/yslow-yahoos-problems-are-not-your-problems.html YSlow: Yahoo's Problems Are Not Your Problems I first saw Yahoo's 13 Simple Rules for Speeding Up Your Web Site referenced in a post on Rich Skrenta's blog in May. It looks like there were originally 14 rules; one must have fallen off the list somewhere along the way. 我第一次看到雅虎的13个简单的规则,加快您的网站在引用Skrenta丰富的博客后,在 5月。它看起来像本来有14规则,必须有脱落沿途的清单 某处。 少的HTTP请求 使用内容传送网络 添加一个Expires头 Gzip已组件 把在顶部的CSS 将脚本的底部 避免CSS表达式 使JavaScript和CSS外部 减少DNS查找 缩小的JavaScript 避免重定向 删除重复的脚本 配置ETag的 它的坚实的意见,从优秀扑杀雅虎的用户界面博客,即将打包成 一个类似的优秀图书。它也 可作为一个PowerPoint演示文稿在Web 2.0大会上发表。 我在我的职务也包括类似的地面 ,减少您的网站的带宽使用情况。 但在此之前运行和实施所有雅虎坚实的意见,考虑到观众。这些规则是从雅虎,根据Alexa是一个在世界前三名的网页内容。里奇的公司,东证,没有懒散-他们在顶部2000。这是很自然,富深知雅虎意见,就如何将兴趣规模的网站,每天数以百万计的唯一用户。 为了帮助他人实施的规则,雅虎创建一个Firebug的插件,YSlow的。这个插件评估当前页,使用的13条规则,并提供具体的指导,就如何修复它发现的任何问题。和所有最重要的是,该工具一个得分率与页面 - !得分有没有我们的爱超过熬煮的网页和网页复杂的意见,以一个简单的数字得分。这里是我的成绩单昨日后的得分。 要理解的得分,你必须解剖个别规则的比重,像西蒙Chiaretta那样 : 重量11 3,添加Expires头4。GZIP组件13。配置ETag的 重量10 2。使用内容传送网络。放置在顶部的CSS 10 。缩小的JavaScript 11 。避免重定向 重量5 9。减少DNS查找6。移动脚本的底部12。删除重复的脚本 重量4 1。较少的请求(CSS )1 。较少的请求( JS) 重量3 1。较少的请求(CSS背景图像 ) 重量2 7,避免CSS表达式 我的73 YSlow的评分是可敬的,但我已经做了一些改变,以容纳大量需求。为了得到一些常见的网站得分的想法 ,西蒙娜跑的博客数量的YSlow,并录得的结果: 谷歌:一(99) 雅虎开发者网络博客:(66) Yahoo!用户界面博客:(65) 斯科特Watermasysk:(62) 苹果:(61) 戴维Shea的mezzoblue:(60) 除了一个名单:F(58) 史蒂夫哈曼:F(54) 编码恐怖:传真:(52) Haacked由菲尔:女(36) Scott Hanselman的计算机禅:F(29) YSlow的是一个方便的工具,但无论是在网上是非常低效的网页,或者有什么东西错误的,其得分。我以后会得到。 在“统计信息”标签包含一个总结以及浏览器缓存和无的足迹,您下载的页面的总规模。从雅虎的主要结果之一是 40%至60%,每日游客有一个空的缓存。因此,有必要优化大小的一切,而不是靠客户端浏览器缓存保存在通常情况下给你。 YSlow的也被打破了更加详细的通过组件“选项卡上的统计。在这里你可以看到几个关键的判断标准,为每一个页面上的资源... 这个资源是否有一个明确的到期日? 这个资源压缩吗? 这个资源是否有一个ETag吗? ... ... 随着绝对大小。 YSlow会是一个有用的工具,但它可以在不法分子手中的危险。软件开发人员喜爱的优化。有时太多。 这里有一些好的建议,但也有很多的意见,才有意义,如果你运行一个网站,获取数以百万计的独特的用户每天。你运行一个这样的网站吗?如果是这样,你在做什么而不是你的私人飞机飞行奖杯妻子到百慕大度假 ?剩下的我们应该多一点有关的意见,我们按照选择性。避免的诱惑,盲目套用这些“顶(X)的方式(Y)”列出了Digg和其他社交网站上如此受欢迎。相反,阅读的建议,批判和思考实施这一建议的后果 。 如果您无法读取雅虎的意见,看房,你可能使您的网站慢, 菲尔哈克不幸发现。虽然许多这些规则是面包和奶油HTTP优化方案,很不幸,一些雅虎的名单上最高的加权规则是非常危险的,如果不是平面的错误较小的网站。而当你定义为“比雅虎小”的“小”,这是.. 好了,几乎每个人都。因此,让我们在雅虎的名单上最棘手的权重意见,关键看 。 使用内容分发网络(重量:10) 如果你要问 一个正式的内容分发网络将耗资多少,你可以买不起。这是更有效地想到这“繁重”在您的网站-例如,你提供任何媒体或图像大块-外包好得多配备处理的外部网站 。这是我提供的意见,最重要的 位,减少您的网站的带宽使用情况。一个CDN,使用下面的一个合理的雅虎式的交通量,甚至 可以减缓你的网站上下 。 配置ETag的(重量:11) ETag的是一个校验领域的服务与每个服务器上的文件,如果服务器上的资源是客户持有本地缓存版本不同,所以客户端可以告诉。雅虎建议转向ETag的,因为它们会导致服务器上的农场,由于他们与机器特定的标记生成方式的问题。所以,除非你运行一个服务器场,你应该忽略本指导意见。这将不仅使您的网站表现较差,因为客户端将有一个更困难的时候,确定其缓存是陈旧或新鲜。它可以为客户端使用现有的最后修改日期字段,以确定是否是陈旧的缓存,但 最后一次修改是一个薄弱的验证程序, 而实体标记(ETag的)是一个强大的的验证器。为何疲软的贸易实力 ? 添加一个Expires头(重量:11) 这是不坏的意见,本身,而是它可以造成巨大的问题,如果你弄错了。在微软的IIS,例如,将Expires头始终是关闭默认情况下,可能正是由于这个原因, 。HTTP资源设置一个Expires头,你告诉客户端从不检查资源的新版本-至少直到Expires头的到期日。当我说没有,我的意思是-浏览器甚至不会要求一个新的版本,它只是承担其缓存版本好走,直到客户端清除缓存,缓存达到的到期日期 。雅虎指出,他们改变了对这些资源的文件名 ,当他们需要 他们刷新。 你真的节能这里是客户端ping了一个新的版本的服务器和一个不能修改的304头,在通常情况下,资源并没有改变的成本。这不是开销很大.. 除非你是雅虎。当然,如果你有一个图像或脚本,几乎永不 改变的,肯定是利用客户端缓存打开Cache - Control头 。缓存到浏览器的性能是至关重要的;每个Web开发人员都应该有一个HTTP的缓存作品的深刻理解 。但只有一个手术,有限的方式使用这些特定的文件夹或文件,可以受益。为别的,风险大于好处。这当然不是你想变成毯默认为您的整个网站上的东西 .. 除非你想改变文件名 ,每次改动的内容。 我不意味着采取任何雅虎的出色指导。 雅虎的13个简单为加快您的网站规则和同伴 ,YSlow的Firebug的插件,是整个互联网的优秀资源 。通过一切手段,读取它。从中受益。实现它。我一直在 敲打GZip压缩的好处多年。 但也认识到,雅虎的问题不一定是你的问题。有没有这样的东西作为一个放之四海而皆准的指导。力争首先要了解的意见,然后实施意见,使您的具体情况的意识 。
本文不适合javascript初学者看(javascript水平还停留在函数级别的朋友,看了会觉得很晕的)。如果你想让你的javascript代码变得更加优美,性能更加卓越。或者,你想像jQuery的作者一样,写出属于自己优秀的类库(哪怕是基于jquery的插件)。那么,你请务必要学习javascript面向对象,否则你无法更灵活的使用javascript这门语言。 什么事闭包?到底什么是原型?(知道闭包和原型的,就算得上是javascript的高手了。但真正能够理解,并且灵活运用的人并不多)到底该如何学习javascript中的面向对象呢?在javascript这么语言正如日中天,相信不少人正在为此而困惑。 本文中,我讲用代码+详细注释的方式,一行行一步步讲述javascript中的面向对象编程。当然有些只是我个人的理解,不足之处,敬请谅解! 1.下面部分的代码,将是从目前十分流行的JSON数据格式以及javascript数组,来一步步像大家阐述javascript中的面向对象思想。 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>JSON数据格式</title> <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript"></script> <script type="text/javascript"> function jsonTest() { //定义json数据格式 -- 以文本的形式存在,转换为javascript对象或者数组 //对象中可以包含数组,数组中也可以包含对象,可以存在相互嵌套的关系 var json1 = "[1,2,{a:123,b:'str',c:[100,200]}]";//数组形式 var parsejson1 = eval(json1);//将纯文本的内容转换为javascript原生支持的json var json2 = "{name:'dinglang',age:21,hobby:['武术','电影']}";//对象形式 //var parsejson2 = eval(json2); //这样直接转换会报错 //当被eval()转换的纯文本json数据为对象形式时,需要在前后加上"()" var parsejson2 = eval("(" + json2 + ")"); //这样转换就可以了 alert(""); } //探索一下JSON这种数据格式的由来 //1.首先是回顾一下javascript数组的相关知识 function arrTest() { // 1)数组的基本定义与赋值 var arrOne = new Array(); //第一种创建方法 var arrTwo = new Array(0,1,2);//第二种创建方式(创建的时候就给数组赋初始值) var arrThree = []; //第三种方式 --定义一个空数组 var arrFour = [1, 2, 3, 5]; //第四种方式--定义一个数组,并给数组赋初始值 //创建多维数组 var arrFive = new Array(1, new Array(2, 3), 4, 5); //创建一个多维数组(嵌套的) var arrSix = [1, [2, 3], 4];//创建一个多维数组 // 2)数组的基本操作(数组是javascript语言中一种很重要的数据结构) alert(arrSix[1]); //通过数组下标,来获取数组中对应的某个元素 arrSix[0] = 100; //给数组中下标对应的元素赋值(如果该元素还未创建,就创建该元素并赋值) arrSix[99] = 888; //arrSix中会自动创建下标为99的元素,并给其赋值 --javascript中数组的长度是可以随时改变的 // 3)javascript当中数组常用的一些方法 //concat方法的使用 --可以做数组的连接或者合并,原数组的内容不变,将返回一个新的数组对象 var arrFour1 = arrFour.concat(101, 102, 103);//第一种连接方式 var arrFour2 = arrFour.concat([104, 105]);//第二种连接方式 var arrFour3 = arrFour.concat(arrFour1); //将已经定义的数组进行连接 //join方法--将数组中元素,按照指定的分隔符连接成字符串输出,原数组的内容不变 //slice方法--返回当前数组中的子数组,原数组中的内容不会改变 //push/pop 在数组的尾端追加(push)或弹出(pop),将会修改原数组的内容 arrFive.push(107);//在数组尾部追加一个元素 arrFive.pop(); //弹出数组中最后一个元素 //在数组的开头追加(shift)和unshift(弹出)操作 arrFive.reverse(); //反转数组中的元素 arrFive.sort(); //按照字母是顺序,对数组中的元素进行升序排列 arrFive.sort(function (a, b) { return a - b; }); //按照数值大小,进行升序排列。如果返回的是负值,那么a就会出现在b的前面 arrFive.sort(function (a, b) { return b - a; }); //按照降序排列 //splice 可以删除数组中一部分元素,并把部分元素进行返回。也可以在指定位置添加元素 var arrSplice1 = arrSix.splice(3, 2); //从下标为3的元素开始删除,删除2个元素 var arrSplice2 = arrSix.splice(4); //从下标为4的元素开始删除,一直删除到数组的末尾 arrSix.splice(1, 0, 401, 402); //在下标为1的元素前面,插入401,402这两个元素 arrSix.splice(1, 0[188, 189]);//在下标为1的元素前面,插入[188,199] } //2.javascript中的对象的定义、使用 var obj1 = new Object(); //定义一个对象 var obj2 = {}; //使用"{}"也可以定义一个对象 //给对象增加属性 obj1.num = 1; obj1.str = "string"; obj1.sayHello = function () { alert("Hello"); } obj2.srcObj = obj1; //将obj1对象作为obj2对象的属性 //属性的访问 --第一种访问方式 obj1.num; //也可以这么访问 obj2.srcObj.num; obj1.str; //obj2.srcObj.str; obj1.sayHello(); //obj2.srcObj.sayHello(); //属性的访问 --第二种方式 obj1["num"]; //obj2["srcObj"]["num"]; obj1["str"]; //obj2["srcObj"]["str"]; obj1["sayHello"](); //obj2["srcObj"]["sayHello"](); //通过对象直接量的方式,定义和调用对象、属性 var obj3 = { num: 1, str: "string", sayHello: function () { alert('Hello'); } } //访问方式同上,例如 obj3.num; //obj3["num"]; //看清楚了吗?这就是javascript中JSON数据格式的原型 //下面来深入讲解javascript语言的面向对象特性 //javascript中定义类,需要用function来模拟 // function Teacher(){ // // } //建议使用下面这种方法来创建一个类,以便将类和函数区分开来(建议定义类时首字母大写) var Teacher = function () { } //定义一个book类,这里的function还承担了构造函数的工作 //在使用new操作符创建Book对象时,这个funtion里面的代码将会被执行一次 //this关键字代表的是当前对象 function Book(name) { //定义公有的属性 this.name = name; //定义公有的函数 this.getName = function () { return this.name; } this.setName = function (nname) { this.name = nname; } } function ooTest() { var tech = new Teacher(); alert(tech instanceof Teacher); // instanceof函数,表示是否属于某对象类型 var book1 = new Book("C#");//这里的new操作相当于先创建了一个简单对象,调用了类的构造函数 var book2 = new Book("JAVA"); alert(book1.name);//弹出C# alert(book2.name);//弹出JAVA book1.setName(".NET"); alert(book1.name);//弹出.NET alert(book2.name); //弹出JAVA //function上面都有一个原型对象 --prototype var proto = Book.prototype; proto.str = "string"; proto.hello = function () { alert("Hello"); } //给原型定义了属性和方法后,拥有这个原型对象的function模拟出来的类,也具有该属性和方法 alert(book1.str); //弹出string book1.hello(); //弹出hello } </script> </head> <body> <input type="button" value="测试json" onclick="jsonTest()"/> </body> </html> 2.下面部分代码,是从另外一个角度讲解javascript中的面向对象编程。是借鉴EasyJF开源团队的讲解,我个人做了一些补充和说明。 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>javascript面向对象编程</title> <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { // function animal(name) { // this.name = name; // this.age = 0; // } // var a1 = animal; // alert(a1);//弹出整个函数体 // var a2 = animal("dinglang"); // alert(a2); //弹出undefined // var a3 = new animal(); // alert(a3);//弹出object // var a4 = new animal; // alert(a4);//弹出object //求值 //alert(sum(1, 3)); //要求弹出结果为4 // alert(sum(1, 3, 5, 4, 7)); //要求弹出结果为20 //根据java或者C#的编程经验,首先想到的是函数重载。 // function sum(a, b) { // return a + b; // } // function sum(a, b, c, d, e) { // return a + b + c + d + e; // } //不幸的是,javascript并不支持函数重载。如果照上面那么写,只有下面的那个函数才会生效 //javascript支持可变参数的函数 function sum() { var n = 0; for (var i = 0; i < arguments.length; i++) { n += arguments[i]; } return n; } //javascript高级知识 -- 闭包 //函数里面嵌套函数,且在外部被引用了,所以这个对象i不会被垃圾回收机制清除,所以i递增 function f1() { var i = 0; var f2 = function () { i++; alert(i); } return f2; //f2对象指的是整个函数体 } var f3 = f1(); // "f1()"就是指该函数的执行结果或者返回值 --f2 // f3();//1 // f3();//2 // f3();//3 //作用域与this关键字 // var obj = new Object(); // obj.v = "v is a object"; // //相当于这么写 // var obj2 = { v: "v is a object" }; //作用域Scope var b1 = { v: "this is b1" }; var b2 = { v: "this is b2" }; function b(x, y) { // alert(this.v + "," + x + "," + y); } b("ding", "lang"); //undefined,"ding","lang" //调用b函数时,b函数中的this关键字指的是window对象.而Window对象中没有v对象,所以undefined //window.b("ding", "lang"); //undefined,"ding","lang" --与 b("ding", "lang");意义相同 //b.call();//就等于b(); //call函数的第一个参数表示的是函数的上下文 --表示b函数中的this 所以this关键字=b1 // b.call(b1, "ding", "lang"); //this is b1,ding,lang //注意apply函数的用法:第一个参数表示的也是函数中的上下文。不过后面的参数要以数组的形式传递 // b.apply(b2, ["ding", "lang"]); // //this is b1,ding,lang //关于作用域,再补充一点 var b3 = { v: "this is b3", sayHello: function () { alert(this.v); } } // b3.sayHello(); //this is b3 //b3.sayHello.call(b1); //会调用b1对象中的sayHello函数 -- this is b1 // for ... in // var arr = new Array(); //new 一个js数组,与c#、java等编程语言不同,可以不指定长度 // arr[0] = 1; //赋值 // arr[1] = 2; // arr[2] = 3; //javascript支持直接定义赋值 var arr = new Array(1, 2, 3); for (var i = 0; i < arr.length; i++) { // alert(arr[i]); //弹出 1,2 ,3 } //注意:javascript中的for...in ,看起来与C#或者java中的foreach类似,但是不同 for (var key in arr) { // alert(key);// 弹出0,1,2 key指的是键,而不是值。在C#的foreach中,“in”前的变量指的是值 //alert(arr[key]);//可以使用这种方式取值 --key指的是键,也就是某个对象中包含的所有的对象,而不是值 } //假如我没有firebug,想使用IE实现类似与firebug控制台中console.dir的功能,可以这样 for (var key in window) { // 这样就能将window对象中,包含的全部对象迭代显示出来。也就实现了firebug中console.dir的功能 //document.getElementById("key").innerHTML += (key + ":" + window[key] + "</br>"); } //对象的删除(释放内存-- 在extjs组件中常用) // delete b3.v; //删除b3对象中的v成员 // alert(b3.v); // undefined --因为v这个成员已经被删除了 //类的修改,扩展(重点,难点) //1.假如我要实现一个简单的加法计算 // var numOne = 5;//如果直接这么定义,那么下面的numOne.add(8);执行会报错 //如果我这么写,下面调用就不会报错了(因为此时的numOne,是个类.相当于java或C#语言中原始的基本数据类型、包装类型) var numOne = new Number(5); numOne.add = function (numTwo) { return this + numTwo; } //alert(numOne.add); //undefined // alert(numOne.add(8));//这样写看起来没错,但是会报错--numOne.add is not a function var numThree = new Number(100); //如果我现在想要给numThree对象中也加上add这么一个函数 //直接使用prototype这个特殊的属性来实现,给所有的Number类型实例都加入add函数 Number.prototype.add = function (numTwo) { return this + numTwo; } alert(numThree.add(200).add(300)); //弹出600 100+200+300=600 //说明所有的Number类型确实都具有了add这么一个函数 超级延时绑定--类的扩展 //小例子 --扩展String类,给所有的String类加上sayILoveYou // String.prototype.sayILoveYou = function () { // alert(this.toString() + ", I Love You"); // } // var strOne = "dinglang"; // strOne.sayILoveYou(); //javascript中的类的用法 //使用构造函数的方式,定义简单的Person类(javascript函数也是一个类) function Person(name) { this.name = name; this.age = 20; var year = 2010; //定义一个私有的成员sex // this.sayHello = function () { // alert(this.name + ":今年" + this.age + "岁,HelloWold"); //可以直接在这里面定义sayHello成员函数(特权方法),但是每次实例化该类的时候都会重新去定义,所有还是选择用prototype属性的方式 //如果在sayHello函数中要使用year这个私有对象,就可以直接在此定义这个特权方法。这就是典型的“闭包”。 } //如果使用了new关键字,说明Person就是一个类。否则的话,只是一个普通的函数 var p1 = new Person("丁浪"); // new 一个Person类的实例 var p2 = new Person("蔡世友"); //注意:别按照java或者C#的习惯,写成了Person p =new Person("XXX"); //给Person这个自定义的类,添加一个sayHello函数 Person.prototype.sayHello = function () { alert(this.name + ":今年" + this.age + "岁,HelloWold"); } p1.sayHello(); p2.sayHello(); //实现javascript中的继承 function classA(name) { this.name = name; this.showName = function () { alert(this.name); } } function classB(name) { //1)使用newMethod的方式实现继承 // this.newMethod = classA; // this.newMethod(name); // delete this.newMethod; //释放对象 //2)调用claasA这个函数,并把他的上下文(作用域)指向this(也就是classB类的实例) //这样也能实现继承效果(使用call或者apply) classA.call(this, name); //classA.apply(this,[name]); } objA = new classA("操作系统"); objB = new classB("组成原理"); objA.showName(); //弹出“操作系统” objB.showName(); //弹出“组成原理” }) </script> </head> <body> <div id="key"> </div> </body> </html>
本章主要讲解javascript语言中的BOM和DOM的相关技术,以实战为主,不讲解太多的重复的理论知识。重要部分代码都贴出来,并有详细的中文注释,思路非常清晰。如果对DOM和BOM理解掌握还不是非常透彻的同学,请耐心的看完每行代码,并自己亲自动手实践,这样才能真正掌握,加深印象。 首先是对BOM(浏览器对象模型)方面的常用技巧的演示: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>学习浏览器对象模型BOM</title> <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript"></script> <script type="text/javascript"> var o = new Object(); o=123456; // alert(o.toString()); // alert(window); //1.使用最多的window对象 var demo = 799; //所有的对象,都是默认在Window下面的 //alert(demo); //或者写成 alert(window.demo); function move() { window.moveTo(300,200);// 定位为 300,200 // window.moveBy(300, 400); //以现在的位置为参考,偏移300,200 } function test() { // window.resizeBy(300, 400); window.resizeTo(300, 400); //将浏览器窗口变为300*400 } $(function () { $("#showWindow").click(function () { var oneNewWindow = window.open("setTimeOut.htm", "_blank"); //打开一个新的页面 oneNewWindow.resizeTo(400, 300); oneNewWindow.moveTo(300, 400); }); //2.history对象 // window.history.go(index); //浏览器历史记录中跳转。正数往前跳,负数往后跳 // window.history.back(); //往后跳转 // window.history.forword();//往前跳转 // alert(window.history.length);//浏览器总的跳转历史记录条数 //3.document对象(dom的相关操作后续会详细讲解) document.anchors; //获取文档中所有的标签 //alert(document.URL); //获取文档URL document.links; //获取文档中所有的链接 document.forms; //获取文档中所有的表单 document.images; //获取文档中所有的图片 //alert(document.links.length);//获取链接的总个数 //alert(document.links[0].href);//获取第一个链接的href属性 //4.location对象 // location.href; //URL // location.host; //ip地址 // location.port; //端口号 // location.reload(); //刷新页面 //location.replace("setTimeOut.htm");//页面跳转至指定的URL //alert(location.port); //5.navigator对象(浏览器对象) //alert(navigator.appName); //浏览器名称 //alert(navigator.appVersion); //浏览器版本信息 //6.screen对象(屏幕) // alert(screen.height); //屏幕分辨率的 Y--高度 // alert(screen.width); //屏幕分辨率的 X--宽度 //7.frames对象(框架)-- 注意:框架与 HTML dom中的contentWindow经常一起使用 //获取父窗体中ID为ifmOne的框架元素,并调用其move()函数 // parent.frames["ifmOne"].move(); }); </script> </head> <body> <a href="javascript:move()">测试移动浏览器窗口</a> <a href="javascript:test()">测试改变浏览器窗口的大小</a> <input type="button" id="showWindow" value="弹出一个小窗口的广告"/> 版权从2005年至<script type="text/javascript">document.write(new Date().getFullYear().toString())</script>年 </body> </html> 下面则是更加复杂的DOM操作: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>复习Dom操作</title> <style type="text/css"> #newTest { color:Red; } </style> <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript"></script> <script type="text/javascript"> // //jQuery写法--页面加载完成 // $(document).ready(function () { // }); // //可以简写成 // $(function () { // // }); //标准js中的写法 --页面加载完成 window.onload = function () { var root = document.documentElement; //DOM根节点 --文档节点 var farDIV = document.getElementById("farther"); //根据ID获得DOM的“元素节点” //元素节点的一些常用的属性 //document.getElementsByTagName("")//根据元素(标签)名称获得DOM的“元素节点”(可能是单个或一组) //document.getElementsByName("")//根据name属性获得DOM的“元素节点”(可能是单个或一组) var idNode = farDIV.getAttributeNode("id"); //获得属性为id的“属性节点” //注意:不要把 getAttribute("id")和getAttributeNode("id")搞混了,前者获取的是属性的值,后者获取的属性节点 var textNode = farDIV.firstChild; //farDIV的第一个子节点 //DOM中常用的属性 //属性(其实只在属性节点中定义才有效) //farDIV.attributes;//返回该节点的所有属性对象的数组 //节点名称 // alert(root.nodeName + ":" + farDIV.nodeName + ":" + idNode.nodeName + ":" + textNode.nodeName); //nodeType(不支持IE)有五种,分别代表:标签(元素)节点,属性节点,文本节点,根节点,注释节点 //alert(root.nodeType + ":" + farDIV.nodeType + ":" + idNode.nodeType + ":" + textNode.nodeType); //节点值 //alert(root.nodeValue + ":" + root.nodeValue + ":" + idNode.value + ":" + textNode.nodeValue); //根节点的一些常用属性 -- 创建新节点 var newNode = document.createElement("div"); //创建一个div节点 var text = document.createTextNode("这是我手动创建的"); //创建一个文本节点 newNode.appendChild(text); //将文本节点插入div节点中 var attrNode = document.createAttribute("id"); //创建属性节点,属性名为“id” newNode.setAttributeNode(attrNode); //将newNode节点的属性节点设置为attrNode newNode.setAttribute("id", "newTest"); //设置newNode节点的id属性设置为newTest //var comment = document.createComment("我是注释");//创建注释节点 //获取页面中的body标签所在节点 var body = root.lastChild; //获取root的最后一个子节点 body.appendChild(newNode); //将新建的节点插入body中 //如果添加成功,则CSS会生效(字体变红,ID选择器有效) //获取元素的属性 alert(farDIV.id); alert(farDIV.getAttribute("id")); alert(newNode.getAttribute("id")); //HTML中获取节点的属性 -- 在XHTML中的可以简写 :alert(newNode.id); // //动态的创建表格 //1.可以使用与上面类似的方式,创建节点、拼接、插入... 但是这样操作似乎很麻烦 //2.可以使用更加简单的操作方式 }; </script> </head> <body> <div id="farther">我是父div</div> </body> </html> 关于LoadXML部分,不同的浏览器有些差异,下面做了改进: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>javascript对XML的操作</title> <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript"></script> <script type="text/javascript"> function test() { alert(loadXML(true, "TestDom.xml")); alert(loadXML(false,"<xml>123</xml>")); } //IE和Firefox类浏览器中装载同域XML文件或XML字符串的方法 //@param flag true表示装载XML文件 false表示装载XML的字符串 //@param xmldoc flag为true时表示XML文件的路径,flag为false时表示XML字符串文本 function loadXML(flag, xmldoc) { //IE和Firefox类浏览器对XML文档的装载和处理存在差异 if (window.ActiveXObject) { //IE浏览器 var activexName = ["MSXML2.Document", "Miscrosoft.XmlDom"]; var xmlObj; for (var i = 0; i < activexName.length; i++) { try { xmlObj = new ActiveXObject(activexName[i]); break; } catch (e) { } if (xmlObj) { // 装载XML文档的对象创建成功 xmlObj.async = false; // 同步方式 if (flag) { //装载XML文件 xmlObj.load(xmldoc); } else { //装载XML字符串 xmlObj.loadXML(xmldoc); } //return xmlObj; //返回根节点 return xmlObj.documentElement; //返回跟元素 } else { alert("装载XML的对象创建失败"); return null; } } } else if (document.implementation.createDocument) { //针对FireFox类浏览器 var xmlObj; if (flag) { //装载XML文件 xmlObj = document.implementation.createDocument("", "", null); if (xmlObj) { //同步方式进行装载 xmlObj.async = false; // 同步方式 xmlObj.load(xmldoc); return xmlObj.documentElement; } else { alert("装载XML的对象创建失败"); return null; } } else { //装载XML字符串 xmlObj = new DOMParser(); var DocRoot = xmlObj.parseFromString(xmldoc, "text/xml"); return DocRoot.documentElement; } } else { alert("该类浏览器可能不支持"); return null; } } </script> </head> <body> <input type="button" id="testLoad" value="测试XML文档加载" onclick="test()" /> </body> </html>
下面总结了一些Eclipse中常用的快捷键。大家不必去死记硬背,日常开发中,该用的时候就用,用得到了,慢慢就都记住了。其实最常用的也就那么几个。 Ctrl+1 快速修复(最经典的快捷键,就不用多说了) Ctrl+D: 删除当前行 Ctrl+Alt+↓ 复制当前行到下一行(复制增加) Ctrl+Alt+↑ 复制当前行到上一行(复制增加) Alt+↓ 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了) Alt+↑ 当前行和上面一行交互位置(同上) Alt+← 前一个编辑的页面 Alt+→ 下一个编辑的页面(当然是针对上面那条来说了) Alt+Enter 显示当前选择资源(工程,or 文件 or文件)的属性 Shift+Enter 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后) Shift+Ctrl+Enter 在当前行插入空行(原理同上条) Ctrl+Q 定位到最后编辑的地方 Ctrl+L 定位在某行 (对于程序超过100的人就有福音了) Ctrl+M 最大化当前的Edit或View (再按则反之) Ctrl+/ 注释当前行,再按则取消注释 Ctrl+O 快速显示 OutLine Ctrl+T 快速显示当前类的继承结构 Ctrl+W 关闭当前Editer Ctrl+K 参照选中的Word快速定位到下一个 Ctrl+E 快速显示当前Editer的下拉列表(如果当前页面没有显示的用黑体表示) Ctrl+/(小键盘) 折叠当前类中的所有代码 Ctrl+×(小键盘) 展开当前类中的所有代码 Ctrl+Space 代码助手完成一些代码的插入(但一般和输入法有冲突,可以修改输入法的热键,也可以暂用Alt+/来代替) Ctrl+Shift+E 显示管理当前打开的所有的View的管理器(可以选择关闭,激活等操作) Ctrl+J 正向增量查找(按下Ctrl+J后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在stutes line中显示没有找到了,查一个单词时,特别实用,这个功能Idea两年前就有了) Ctrl+Shift+J 反向增量查找(和上条相同,只不过是从后往前查) Ctrl+Shift+F4 关闭所有打开的Editer Ctrl+Shift+X 把当前选中的文本全部变味小写 Ctrl+Shift+Y 把当前选中的文本全部变为小写 Ctrl+Shift+F 格式化当前代码 Ctrl+Shift+P 定位到对于的匹配符(譬如{}) (从前面定位后面时,光标要在匹配符里面,后面到前面,则反之) 下面的快捷键是重构里面常用的,本人就自己喜欢且常用的整理一下(注:一般重构的快捷键都是Alt+Shift开头的了) Alt+Shift+R 重命名 (是我自己最爱用的一个了,尤其是变量和类的Rename,比手工方法能节省很多劳动力) Alt+Shift+M 抽取方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用) Alt+Shift+C 修改函数结构(比较实用,有N个函数调用了这个方法,修改一次搞定) Alt+Shift+L 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候) Alt+Shift+F 把Class中的local变量变为field变量 (比较实用的功能) Alt+Shift+I 合并变量(可能这样说有点不妥Inline) Alt+Shift+V 移动函数和变量(不怎么常用) Alt+Shift+Z 重构的后悔药(Undo) 编辑 作用域 功能 快捷键 全局 查找并替换 Ctrl+F 文本编辑器 查找上一个 Ctrl+Shift+K 文本编辑器 查找下一个 Ctrl+K 全局 撤销 Ctrl+Z 全局 复制 Ctrl+C 全局 恢复上一个选择 Alt+Shift+↓ 全局 剪切 Ctrl+X 全局 快速修正 Ctrl1+1 全局 内容辅助 Alt+/ 全局 全部选中 Ctrl+A 全局 删除 Delete 全局 上下文信息 Alt+? Alt+Shift+? Ctrl+Shift+Space Java编辑器 显示工具提示描述 F2 Java编辑器 选择封装元素 Alt+Shift+↑ Java编辑器 选择上一个元素 Alt+Shift+← Java编辑器 选择下一个元素 Alt+Shift+→ 文本编辑器 增量查找 Ctrl+J 文本编辑器 增量逆向查找 Ctrl+Shift+J 全局 粘贴 Ctrl+V 全局 重做 Ctrl+Y 查看 作用域 功能 快捷键 全局 放大 Ctrl+= 全局 缩小 Ctrl+- 窗口 作用域 功能 快捷键 全局 激活编辑器 F12 全局 切换编辑器 Ctrl+Shift+W 全局 上一个编辑器 Ctrl+Shift+F6 全局 上一个视图 Ctrl+Shift+F7 全局 上一个透视图 Ctrl+Shift+F8 全局 下一个编辑器 Ctrl+F6 全局 下一个视图 Ctrl+F7 全局 下一个透视图 Ctrl+F8 文本编辑器 显示标尺上下文菜单 Ctrl+W 全局 显示视图菜单 Ctrl+F10 全局 显示系统菜单 Alt+- 导航 作用域 功能 快捷键 Java编辑器 打开结构 Ctrl+F3 全局 打开类型 Ctrl+Shift+T 全局 打开类型层次结构 F4 全局 打开声明 F3 全局 打开外部javadoc Shift+F2 全局 打开资源 Ctrl+Shift+R 全局 后退历史记录 Alt+← 全局 前进历史记录 Alt+→ 全局 上一个 Ctrl+, 全局 下一个 Ctrl+. Java编辑器 显示大纲 Ctrl+O 全局 在层次结构中打开类型 Ctrl+Shift+H 全局 转至匹配的括号 Ctrl+Shift+P 全局 转至上一个编辑位置 Ctrl+Q Java编辑器 转至上一个成员 Ctrl+Shift+↑ Java编辑器 转至下一个成员 Ctrl+Shift+↓ 文本编辑器 转至行 Ctrl+L 搜索 作用域 功能 快捷键 全局 出现在文件中 Ctrl+Shift+U 全局 打开搜索对话框 Ctrl+H 全局 工作区中的声明 Ctrl+G 全局 工作区中的引用 Ctrl+Shift+G 文本编辑 作用域 功能 快捷键 文本编辑器 改写切换 Insert 文本编辑器 上滚行 Ctrl+↑ 文本编辑器 下滚行 Ctrl+↓ 文件 作用域 功能 快捷键 全局 保存 Ctrl+X Ctrl+S 全局 打印 Ctrl+P 全局 关闭 Ctrl+F4 全局 全部保存 Ctrl+Shift+S 全局 全部关闭 Ctrl+Shift+F4 全局 属性 Alt+Enter 全局 新建 Ctrl+N 项目 作用域 功能 快捷键 全局 全部构建 Ctrl+B 源代码 作用域 功能 快捷键 Java编辑器 格式化 Ctrl+Shift+F Java编辑器 取消注释 Ctrl+\ Java编辑器 注释 Ctrl+/ Java编辑器 添加导入 Ctrl+Shift+M Java编辑器 组织导入 Ctrl+Shift+O Java编辑器 使用try/catch块来包围 未设置,太常用了,所以在这里列出,建议自己设置。 也可以使用Ctrl+1自动修正。 运行 作用域 功能 快捷键 全局 单步返回 F7 全局 单步跳过 F6 全局 单步跳入 F5 全局 单步跳入选择 Ctrl+F5 全局 调试上次启动 F11 全局 继续 F8 全局 使用过滤器单步执行 Shift+F5 全局 添加/去除断点 Ctrl+Shift+B 全局 显示 Ctrl+D 全局 运行上次启动 Ctrl+F11 全局 运行至行 Ctrl+R 全局 执行 Ctrl+U 重构 作用域 功能 快捷键 全局 撤销重构 Alt+Shift+Z 全局 抽取方法 Alt+Shift+M 全局 抽取局部变量 Alt+Shift+L 全局 内联 Alt+Shift+I 全局 移动 Alt+Shift+V 全局 重命名 Alt+Shift+R 全局 重做 Alt+Shift+Y 另外我告诉大家Eclipse中的模板,非常有用的哦 例如:我们在Eclipse中开发时候,经常需要“捕获异常”,在Eclipse中敲击了“try”三个字母后,按一下Alt+/,能够自动补全try/catch语句块,如图所示: 这就是模板的作用嘛。那如果我想每次写“try”语句块,自动补全的时候,默认给我还加上“Finally”块,怎么办呢? 点击Eclipse中的Windows,Preferences,弹出如下图所示: 选中“try“,按edit,弹出如图所示(当然,你也可以按”new“去重新定义) 保存完成后,再试试。使用模版和快捷键,能大大的提高开发效率。如果你想提高自己的英文水平(记单词),或者想练练打字的速度。不推荐使用。
以下的文章主要向大家讲述的是在实际操作中如何快速对sqlserver中的锁机制进行掌握,各种大型数据库所采用的相关的锁基本理论都是相同的,但在具体实现上各有不同之处。SQL Server更强调由系统来管理锁。 在用户有SQL请求时,系统分析请求,自动在满足锁定条件和系统性能之间为数据库加上适当的锁,同时系统在运行期间常常自动进行优化处理,实行动态加锁。 对于一般的用户而言,通过系统的自动锁定管理机制基本可以满足使用要求,但如果对数据安全、数据库完整性和一致性有特殊要求,就需要了解SQL Server锁机制,掌握数据库锁定方法。 锁是数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性。 我们知道,多个用户能够同时操纵同一个数据库中的数据,会发生数据不一致现象。即如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。这些问题包括:丢失更新、脏读、不可重复读和幻觉读: 1.当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。例如,两个编辑人员制作了同一文档的电子复本。 每个编辑人员独立地更改其复本,然后保存更改后的复本,这样就覆盖了原始文档。最后保存其更改复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改,则可以避免该问题。 2. 脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。 例如,一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该复本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文档。 分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前任何人都不能读取更改的文档,则可以避免该问题。 3.不可重复读是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。 例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。 4.幻觉读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。 例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。 所以,处理多用户并发访问的方法是加锁。锁是防止其他事务访问指定的资源控制、实现并发控制的一种主要手段。当一个用户锁住数据库中的某个对象时,其他用户就不能再访问该对象。加锁对并发访问的影响体现在锁的粒度上。为了控制锁定的资源,应该首先了解系统的空间管理。 在SQL Server 2000系统中,最小的空间管理单位是页,一个页有8K。所有的数据、日志、索引都存放在页上。另外,使用页有一个限制,这就是表中的一行数据必须在同一个页上,不能跨页。页上面的空间管理单位是盘区,一个盘区是8个连续的页。表和索引的最小占用单位是盘区。 数据库是由一个或者多个表或者索引组成,即是由多个盘区组成。放在一个表上的锁限制对整个表的并发访问;放在盘区上的锁限制了对整个盘区的访问;放在数据页上的锁限制了对整个数据页的访问;放在行上的锁只限制对该行的并发访问。 SQL Server 2000 具有多粒度锁定,允许一个事务锁定不同类型的的资源。为了使锁定的成本减至最少,SQL Server锁机制自动将资源锁定在适合任务的级别。锁定在较小的粒度(例如行)可以增加并发但需要较大的开销,因为如果锁定了许多行,则需要控制更多的锁。 锁定在较大的粒度(例如表)就并发而言是相当昂贵的,因为锁定整个表限制了其它事务对表中任意部分进行访问,但要求的开销较低,因为需要维护的锁较少。SQL Server锁机制可以锁定行、页、扩展盘区、表、库等资源。 行是可以锁定的最小空间, 行级锁占用的数据资源最少,所以在事务的处理过程中,允许其他事务继续操纵同一个表或者同一个页的其他数据,大大降低了其他事务等待处理的时间,提高了系统的并发性。 页级锁是指在事务的操纵过程中,无论事务处理数据的多少,每一次都锁定一页,在这个页上的数据不能被其他事务操纵。在SQL Server 7.0以前,使用的是页级锁。页级锁锁定的资源比行级锁锁定的数据资源多。在页级锁中,即使是一个事务只操纵页上的一行数据,那么该页上的其他数据行也不能被其他事务使用。 因此,当使用页级锁时,会出现数据的浪费现象,也就是说,在同一个页上会出现数据被占用却没有使用的现象。在这种现象中,数据的浪费最多不超过一个页上的数据行。 表级锁也是一个非常重要的锁。表级锁是指事务在操纵某一个表的数据时,锁定了这个数据所在的整个表,其他事务不能访问该表中的其他数据。当事务处理的数据量比较大时,一般使用表级锁。表级锁的特点是使用比较少的系统资源,但是却占用比较多的数据资源。 与行级锁和页级锁相比,表级锁占用的系统资源例如内存比较少,但是占用的数据资源却是最大。在表级锁时,有可能出现数据的大量浪费现象,因为表级锁锁定整个表,那么其他的事务都不能操纵表中的其他数据。 盘区锁是一种特殊类型的锁,只能用在一些特殊的情况下。簇级锁就是指事务占用一个盘区,这个盘区不能同时被其他事务占用。例如在创建数据库和创建表时,系统分配物理空间时使用这种类型的锁。 系统是按照盘区分配空间的。当系统分配空间时,使用盘区锁,防止其他事务同时使用同一个盘区。当系统完成分配空间之后,就不再使用这种类型的盘区锁。特别是,当涉及到对数据操作的事务时,不使用盘区锁。 数据库级锁是指锁定整个数据库,防止任何用户或者事务对锁定的数据库进行访问。数据库级锁是一种非常特殊的锁,它只是用于数据库的恢复操作过程中。这种等级的锁是一种最高等级的锁,因为它控制整个数据库的操作。只要对数据库进行恢复操作,那么就需要设置数据库为单用户模式,这样系统就能防止其他用户对该数据库进行各种操作。 行级锁是一种最优锁,因为行级锁不可能出现数据既被占用又没有使用的浪费现象。但是,如果用户事务中频繁对某个表中的多条记录操作,将导致对该表的许多记录行都加上了行级锁,数据库系统中锁的数目会急剧增加,这样就加重了系统负荷,影响系统性能。 因此,在SQL Server锁机制中,还支持锁升级(lock escalation)。所谓锁升级是指调整锁的粒度,将多个低粒度的锁替换成少数的更高粒度的锁,以此来降低系统负荷。在SQL Server中当一个事务中的锁较多,达到锁升级门限时,系统自动将行级锁和页面锁升级为表级锁。特别值得注意的是,在SQL Server中,锁的升级门限以及锁升级是由系统自动来确定的,不需要用户设置。 解析:Microsoft SQL Server中的锁模式 在SQL Server数据库中加锁时,除了可以对不同的资源加锁,还可以使用不同程度的加锁方式,即锁有多种模式,SQL Server中锁模式包括: 1.共享锁 SQL Server锁机制中,共享锁用于所有的只读数据操作。共享锁是非独占的,允许多个并发事务读取其锁定的资源。默认情况下,数据被读取后,SQL Server立即释放共享锁。例如,执行查询“SELECT * FROM AUTHORS”时,首先锁定第一页,读取之后,释放对第一页的锁定,然后锁定第二页。 这样,就允许在读操作过程中,修改未被锁定的第一页。但是,事务隔离级别连接选项设置和SELECT语句中的锁定设置都可以改变SQL Server的这种默认设置。例如,“ SELECT * FROM AUTHORS HOLDLOCK”就要求在整个查询过程中,保持对表的锁定,直到查询完成才释放锁定。 2.更新锁 更新锁在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。因为使用共享锁时,修改数据的操作分为两步,首先获得一个共享锁,读取数据,然后将共享锁升级为排它锁,然后再执行修改操作。 这样如果同时有两个或多个事务同时对一个事务申请了共享锁,在修改数据的时候,这些事务都要将共享锁升级为排它锁。这时,这些事务都不会释放共享锁而是一直等待对方释放,这样就造成了死锁。如果一个数据在修改前直接申请更新锁,在数据修改的时候再升级为排它锁,就可以避免死锁。 3.排它锁 排它锁是为修改数据而保留的。它所锁定的资源,其他事务不能读取也不能修改。 4.结构锁 执行表的数据定义语言 (DDL) 操作(例如添加列或除去表)时使用架构修改 (Sch-M) 锁。当编译查询时,使用架构稳定性 (Sch-S) 锁。架构稳定性 (Sch-S) 锁不阻塞任何事务锁,包括排它锁。因此在编译查询时,其它事务(包括在表上有排它锁的事务)都能继续运行。但不能在表上执行 DDL 操作。 5.意向锁 意向锁说明SQL Server锁机制有在资源的低层获得共享锁或排它锁的意向。例如,表级的共享意向锁说明事务意图将排它锁释放到表中的页或者行。意向锁又可以分为共享意向锁、独占意向锁和共享式独占意向锁。共享意向锁说明事务意图在共享意向锁所锁定的低层资源上放置共享锁来读取数据。 独占意向锁说明事务意图在共享意向锁所锁定的低层资源上放置排它锁来修改数据。共享式排它锁说明事务允许其他事务使用共享锁来读取顶层资源,并意图在该资源低层上放置排它锁。 6.大容量更新锁 当将数据大容量复制到表,且指定了 TABLOCK 提示或者使用 sp_tableoption 设置了 table lock on bulk 表选项时,将使用大容量更新 锁。大容量更新锁允许进程将数据并发地大容量复制到同一表,同时防止其它不进行大容量复制数据的进程访问该表。
在日常开发工作中,我们经常会使用到XML,早已成为了一种标准。它的用途非常的广泛,但这些不是本文所重点讨论的。 相信大家在做开始时候经常碰到过“乱码”的问题,这是中国程序员非常头疼的问题。我一直很想深入研究关于“编码”的原理,无奈水平有限,那些枯燥的理论(二进制,ASCII,Unicode,UTF-8,gb2312,ISO ...光这些就让我看的两眼发黑了),实在看不下去,也很难真正搞懂搞明白。望各位网友多指点...... 我将用工作中遇到的一个“XML文件乱码”的简单问题,解决问题,分析其背后的原理。 首先,我们在本地新建一个文本文件,将后缀名改为".XML”, 然后用用记事本打开,往里面添加一些符合XML文档规范的内容。如图所示: 写好之后,按“ctrl+s”保存,然后使用IE浏览器打开该XML文件,验证该XML文档的规范及正确性。不料,居然解析出错了,如下: 这是咋回事呢?我的XML文档定义的格式好像没问题啊。无效字符?这肯定是典型的“编码”问题了。聪明的我第一就想到了,调整IE浏览器的“编码”嘛。 可是打开“查看”“编码”,发现那些编码格式全是灰色的,好像不能选择哦。这是因为,在定义XML文档的时候,指定了编码格式为"UTF-8",这就相当于告诉了浏览器(XML解析引擎):你必须使用"UTF-8"编码去解析我,所以无法使用其他的编码格式去查看了。 这是因为,我们在使用记事本保存该文档的时候,没有选择编码格式,默认使用的是操作系统编码(中文版的系统),也就是对应的"GB2312”编码。当我们的IE浏览器,再使用我们指定的UTF-8编码去解析该XML文档的时候,出现了乱码,所以造成了上面的错误。(Windows中的文件保存在硬盘上,默认使用操作系统编码。比如我们XML文档中定义的“中国”这两个字,保存好后,假如其对应的GB2312可能是"10001",而在UTF-8编码中的,“10001”对应的就不是“中国”了,要么找不到,要么是乱码,所以IE就拒绝显示了)。那我们应该怎么办呢?有两种办法可以解决。 第一,我们在xml文档定义时,指定其编码为gb2312,如下图所示: 保存之后,我们再使用IE浏览器打开,结果如图: 恭喜,这个问题解决了。但是这种方法不推荐使用。因为我们在定义XML文档时候,为了文档的通用性,我们一般使用UTF-8编码。 第二种方法: 我们再用记事本打开该文档,点击“另存为”,发现下面会有“编码”选项,选择“UTF-8”之后再试。 其实,我们在使用诸如 Eclipse 或者Microsoft Visual Studio之类的开发工具来定义XML文档,并不会碰到上面的问题。原因是这些IDE都非常“聪明”,你的XML文档指定的是那种编码格式,IDE在将XML文档保存到硬盘的时候,就自动使用那种格式。所以,很多局限于使用某种IDE开发的程序员,其实并不明白这些知识及其背后的原理,但他们做开发起来一样很顺手。早年据笔者了解,国内有很多大牛,写代码都是用EditPlus之类的文本编辑器,而那些在Linux/unix上面的大牛,很多都是用VI/VIM来编码。大概这就是差距吧。(呵呵。当然这不是本文讨论的重点) 补充一点理论知识,不晕的就继续读下去吧。 在最初的时候,Internet上只有一种字符集——ANSI的ASCII字符集,它使用7 bits来表示一个字符,总共表示128个字符,其中包括了英文字母、数字、标点符号等常用字符。之后,又进行扩展,使用8 bits表示一个字符,可以表示256个字符,主要在原来的7 bits字符集的基础上加入了一些特殊符号例如制表符。 后来,由于各国语言的加入,ASCII已经不能满足信息交流的需要,因此,为了能够表示其它国家的文字,各国在ASCII的基础上制定了自己的字符集,这些从ANSI标准派生的字符集被习惯的统称为ANSI字符集,它们正式的名称应该是MBCS(Multi-Byte Chactacter System,即多字节字符系统)。这些派生字符集的特点是以ASCII 127 bits为基础,兼容ASCII 127,他们使用大于128的编码作为一个Leading Byte,紧跟在Leading Byte后的第二(甚至第三)个字符与Leading Byte一起作为实际的编码。这样的字符集有很多,我们常见的GB-2312就是其中之一。 例如在GB-2312字符集中,“连通”的编码为C1 AC CD A8,其中C1和CD就是Leading Byte。前127个编码为标准ASCII保留,例如“0”的编码是30H(30H表示十六进制的30)。软件在读取时,如果看到30H,知道它小于128就是标准ASCII,表示“0”,看到C1大于128就知道它后面有一个另外的编码,因此C1 AC一同构成一个整个的编码,在GB-2312字符集中表示“连”。 由于每种语言都制定了自己的字符集,导致最后存在的各种字符集实在太多,在国际交流中要经常转换字符集非常不便。因此,提出了Unicode字符集,它固定使用16 bits(两个字节、一个字)来表示一个字符,共可以表示65536个字符。将世界上几乎所有语言的常用字符收录其中,方便了信息交流。标准的Unicode称为UTF-16。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8,使用类似MBCS的方式对Unicode进行编码。注意UTF-8是编码,它属于Unicode字符集。Unicode字符集有多种编码形式,而ASCII只有一种,大多数MBCS(包括GB-2312)也只有一种。 例如“连通”两个字的Unicode标准编码UTF-16 (big endian)为:DE 8F 1A 90 而其UTF-8编码为:E8 BF 9E E9 80 9A 最后,当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件有三种途径来决定文本的字符集和编码: 最标准的途径是检测文本最开头的几个字节,如下表: 开头字节 Charset/encoding EF BB BF UTF-8 FE FF UTF-16/UCS-2, little endian FF FE UTF-16/UCS-2, big endian FF FE 00 00 UTF-32/UCS-4, little endian. 00 00 FE FF UTF-32/UCS-4, big-endian. 例如插入标记后,连通”两个字的UTF-16 (big endian)和UTF-8码分别为: FF FE DE 8F 1A 90 EF BB BF E8 BF 9E E9 80 9A 但是MBCS文本没有这些位于开头的字符集标记,更不幸的是,一些早期的和一些设计不良的软件在保存Unicode文本时不插入这些位于开头的字符集标记。因此,软件不能依赖于这种途径。这时,软件可以采取一种比较安全的方式来决定字符集及其编码,那就是弹出一个对话框来请示用户,例如将那个“连通”文件拖到MS Word中,Word就会弹出一个对话框。 如果软件不想麻烦用户,或者它不方便向用户请示,那它只能采取自己“猜”的方法,软件可以根据整个文本的特征来猜测它可能属于哪个charset,这就很可能不准了。使用记事本打开那个“连通”文件就属于这种情况。 我们可以证明这一点:在记事本中键入“连通”后,选择“Save As”,会看到最后一个下拉框中显示有“ANSI”,这时保存。当再当打开“连通”文件出现乱码后,再点击“File”->“Save As”,会看到最后一个下拉框中显示有“UTF-8”,这说明记事本认为当前打开的这个文本是一个UTF-8编码的文本。而我们刚才保存时是用ANSI字符集保存的。这说明,记事本猜测了“连通”文件的字符集,认为它更像一个UTF-8编码文本。这是因为“连通”两个字的GB-2312编码看起来更像UTF-8编码导致的,这是一个巧合,不是所有文字都这样。可以使用记事本的打开功能,在打开“连通”文件时在最后一个下拉框中选择ANSI,就能正常显示了。反过来,如果之前保存时保存为UTF-8编码,则直接打开也不会出现问题。 如果将“连通”文件放入MS Word中,Word也会认为它是一个UTF-8编码的文件,但它不能确定,因此会弹出一个对话框询问用户,这时选择“简体中文(GB2312)”,就能正常打开了。记事本在这一点上做得比较简化罢了,这与这个程序的定位是一致的。
今天突然有同事问起,如何在sqlserver中调试存储过程(我们公司使用的是sqlserver 2008 R2),猛地一看,和以前使用sqlserver 2000真的有很大的不同,我真晕了。 于是琢磨了一下。SQLSERVER 2005中不知因何去掉了很重要的DEBUGGER功能,要调试,必须要安装VS2005专业版或者更高版本。非常不方便。 还好,SQLSERVER 2008中这个很重要而且方便的功能又回来了。 不过,SQLSERVER 2008的调试功能和SQL2000的方法差别很大。SQL2000是在查询分析器中的对象浏览器中选中需要调试的存储过程,右键----调试---输入参数开始调试。 sqlserver2008中则完全不同,变成了必须要在SSMS中EXEC [PROCEDURE NAME] @VAR1,@VAR2,然后点绿色三角或者点菜单中的调试---启动调试。然后点工具栏的最右边的单步调试或者跳出等。下面的变量窗口和堆栈窗口等可以查看调试中变量等动态变化值。 sqlserver2008调试的要求和条件:如果在引擎所在的电脑或服务器上调试,则只需要SA或者WINDOWS用户登陆即可。如果是异地调试,则需要设置防火墙例外,增加SSMS和SQLSERVER.EXE为允许,增加135端口允许通过。 总之,SQL2008的调试比2000操作起来麻烦多了,要求也多了。刚开始感觉不如2000的好用,也可能是使用2000习惯了。习惯是可怕的,但是微软是在不断进步的... 一、回顾早期的SQL SERVER版本: 早在SQL Server 2000时代,查询分析器的功能还很简陋,远不如VS那么强大。到SQL Server 2005时代,代码高亮、SQL优化等功能逐渐加强,但是依然无法调试SQL语句。好一点的第三方的SQL语法编辑器似乎也不够完美,这样导致一些人抱怨存储过程不便于维护,开发的时候能不用则不用。 二、SQL Server 2008 Express 智能提示加强: 该功能是SQL2008在SQL Server 2005之后的升级版,我们可以很方便的调用智能提示,和 VS一致:使用快捷键ctrl + J 即可。 截图如下: 该功能是否与VS一样了呢? 毕竟他们都是微软的产品。 三、调试T-SQL语句: 1.Debug普通T-SQL语句: SQL代码如下: use northwind go declare @i int ,@j int,@k int set @i = 1; set @j = 2; set @k=@i + @j select @i; go 非常简单的定义了 三个int 型变量:i、j、k并且对这些变量进行简单的逻辑运算,在Management Studio 中只要轻松的按F11键,即可调试以上代码块。 截图如下: 接着点击F11逐语句debug 或者F10逐过程调试代码。 截图如下: 这个dubug的场面您是否觉得已经和VS相差无几了呢? 四、支持复杂存储过程嵌套debug: 您可能会疑问,在一个庞大的系统中,如果数据库逻辑绝大部分都是存储过程实现的情况下,会出现存储过程嵌套存储过程或者嵌套存储函数这样的代码。 SQL2008是否支持调试功能呢?答案是肯定的。 首先定义一个简单的存储过程(本文使用NorthWind数据库)代码如下: CREATE procedure sp_getOrders @orderID int = null as if (@orderID is null ) begin print 'null' end else begin print 'correct' end select * from Orders whereOrderID = @orderID go 该存储过程在以下批处理内被调用,代码如下: declare @i int ,@j int,@k int set @i = 1; set @j = 2; select @k=@i + @j exec sp_getOrders 10248 select @i; go F11对以上代码进行SQL Debug。 截图如下: 当断点经过exec sp_getOrders 10248 这段代码时,点击F11进入sp_getOrders存储过程进行逐语句debug。 截图如下: 这样可以在嵌套的存储过程或函数内进行debug了,此刻不得不承认: 升级后的SQL2008越来越强大。您还恐惧使用或者调试存储过程么?
原则1 减少HTTP请求数 构造请求、等待响应需要时间,因此请求数量越少越好。减少请求的总体思路就是合并资源,减少显示一个页面需要的文件数。 1. Image Map 通过设置<img>标签的usemap属性与使用<map>标签可以在一幅图片上切分出多个区域,指向不同的链接。比起使用多幅图片分别构造链接减少了请求数。 2. CSS Sprite(CSS贴图整合/贴图拼合/贴图定位) 通过设置元素的background-position样式做到。一般用于界面图标。典型的可以参考TinyMCE编辑器上方的那些小按钮。多个小图实质是从一个统一的大图通过不同的偏移量裁剪而来,这样加载界面上的众多按钮实际上只要请求一次(请求大图一次),从而减少HTTP请求数。 3. Inline Image(内联图片) 在<img>的src中不指定外部图片文件的URL,而是直接将图片信息放入。例如src=”data:image/gif;base64,R0lGODlhDAAMAL...”某些特殊情况下有用(例如一个不大的图片仅在当前页面用到)。 原则2 利用多线路CDN 为你的站点提供多种线路(例如国内电信、联通、移动)、多个地理位置(北方、南方、西部)的访问,使得所有用户都能够快速访问。 原则3 利用HTTP Cache 给不频繁更新的资源(例如静态图)加较长的Expires头信息,这些资源一经缓存,未来很长时间都可以不再重复传输了。 原则4 使用Gzip压缩 使用Gzip压缩HTTP报文,减小体积,减少传输时间。 原则5 将样式表置于页面前部 先加载样式表,这样页面渲染得以较早开始,给用户页面加载较快的感觉。 原则6 将脚本置于页面尾部 原因同5,先处理页面显示,页面渲染较早完成,而脚本逻辑稍后执行,这样给用户页面加载较快的感觉。 原则7 避免使用CSS表达式 过于复杂的JavaScript脚本逻辑、DOM查找、选择操作将会降低页面处理效率。 原则8 将JavaScript与CSS作为外联资源 这似乎与原则1中的合并思想相悖,但其实不然:考虑每个页面都引入了一个公共的JavaScript资源(例如jQuery或是ExtJS这样的JavaScript库),单就一个页面的表现来看,内联(即将JavaScript嵌入HTML)页面将比外联(使用<script>标签引入)页面加载更快(因为其较少的HTTP请求数)。但如果有很多页面都引入了这个公共JavaScript资源,那么内联方案会造成重复传输(因为这个资源内嵌在每个页面中了,所以每次打开一个页面都要将这部分资源传输一遍,从而造成网络传输资源的浪费)。而将这种资源独立出来外联引用可以解决这个问题。由于JavaScript和CSS相对稳定,我们可以对其对应的资源设置较长的失效期(参考原则3)。 原则9 减少DNS查找 作者给出的建议是: 1. 使用Keep-Alive保持连接 如果连接断开,那么下次连接又要执行DNS查找,即使对应的域名-IP映射已被缓存,查找也是要消耗一些时间的 2. 减少域名 每次请求新域名都需要进行通过DNS查找不同的域名,且DNS缓存无法发挥作用。因此应该尽量将站点组织在一个统一域名下,避免使用过多子域名 原则10 压缩你的JavaScript 使用JS压缩工具压缩你的JavaScript吧,很有效哦。看看jQuery的两个不同的发行版本就知道区别了: http://code.jquery.com/jquery-1.6.2.js 阅读版jQuery代码,230KB http://code.jquery.com/jquery-1.6.2.min.js 压缩版jQuery代码(用于实际部署),89.4KB 原则11 尽量避免重定向 一次重定向意味着在你真正访问到想要看到的页面前加入了一轮额外的HTTP请求(客户端发起HTTP请求→HTTP服务器返回重定向响应→客户端对新URL发起请求→HTTP服务器返回内容,下划线部分为额外的请求),因此消耗更多的时间(也就给人反应更慢的感觉)。因此除非必要,不要随意使用重定向。几个“必要”的情况: 1. 避免URL失效 旧站点迁移后,为了避免旧的URL失效,通常将对旧URL的请求重定向至新系统的对应地址。 2. URL美化 在可读性好的URL与实际资源URL之间转换,例如对于Google Toolbar,用户记得住http://toolbar.google.com这个对人类富有语义的地址,却很难记住http://www.google.com/tools/firefox/toolbar/FT3/intl/en/index.html这个真正的资源地址。因此有必要保留前者,并且将对前者的请求重定向至后者。 原则12 移除重复的脚本 不要在一个页面中重复引入相同的脚本。例如脚本B和C都依赖于A,那么在使用了B和C的页面中就有可能存在对A的重复引用。解决方法,对于简单的站点手动检查依赖性,消去重复引入;对于复杂的站点则需要构建自己的依赖管理/版本控制机制。 原则13 小心处理ETag ETag是除Last-Modified之外的另一种HTTP Cache手段。通过hash的办法辨识资源是否被修改。但ETag存在一些问题,例如: 1. 不一致:不同Web服务器(Apache, IIS等)定义的ETag格式不同 2. ETag的计算是不稳定的(由于考虑过多因素),例如: 1) 相同资源在不同服务器上计算出来的ETag不一样,而大型Web应用通常由不止一台服务器提供服务,这就导致客户端在服务器A缓存好的资源明明仍然有效,而在下次请求B时由于ETag不同而被认定为失效,导致相同资源的重复传输。 2) 资源不变,而由于一些其他因素的变化,例如配置文件更改,导致ETag变化。直接后果是系统更新后客户端大规模发生Cache失效,导致传输量大增,站点性能下降。 作者给出的建议是:要么根据你的应用特点改进已有的ETag计算方法,要么干脆就不用ETag,而改用最简单的Last-Modified. 原则14 在Ajax中利用HTTP Cache Ajax是异步请求,异步请求不会阻塞你现在的操作,而且当请求完成时,你马上就可以看到结果。但异步不代表能够瞬时完成,也不代表能够容忍它花无限多的时间完成。因此对于Ajax请求的性能也需要重视。有很多Ajax请求访问的是一些相对稳定的资源,因此别忘了对Ajax请求利用好HTTP Cache机制,具体参见原则3、13.
截至到SQL Server 2008 R2 版本,微软还是没有推出官方的负载均衡组件,只能通过SQL Server的其他技术特性或者利用第三方组件来DIY。 在前面的博客中,总结了一些常见的“sqlserver集群”“sqlserver数据同步”“sqlserver故障转移”...等相关的实现技巧。今天就不仔细讲解了。 今天想推荐大家尝试使用第三方的组件和工具,典型的就是一款ICX数据库路由。当然,我和这些工具的厂商也不熟,所以本文显然不是广告。呵呵。 长期以来,SQL SERVER数据库服务器都只有“热备”的解决方案,而没有“负载均衡”和“集群”的解决方案。这种解决方案固然 提升了系统的可靠性,但也存在一些问题: * 面对大数据量和大量的数据库查询请求,只能采取纵向提升服务器档次的方法,而纵向提升的成本远远高于横向扩展。 * 在热备时,数据库服务器只有一台在工作,另一台处于闲置备份的状态,造成了投资的浪费。 * 非实时切换。 而数据库路由器软件ICX 的出现,为基于MS SQL Server的数据库系统提供了一种更优秀的集群解决方案。它可以真正的实现SQL Server数 据库服务器的动态负载均衡,提高性能和速度;它可以真正的保证SQL Server数据库服务器不间断的提供服务,在服务器发生故障的时候实 时切换到其他服务器上继续提供服务,切换时间为“零”。 数据库路由器是实时并发数据库事务处理同步复制器和负载平衡器。 数据库路由器--ICX(意思是:I SEE X DATABASE SERVERS),也就是说,在ICX后面可以同时连接N个数据库,结构如下图所示: 1.所有的数据库客户都通过ICX访问数据库。当访问、查询SQL Server数据库的时候ICX可以根据实际情况分配服务器来提供服务,大大提 高服务速度和优化性能,完成负载均衡。 2.ICX可以同时连接多台数据库(2-16台,具体连多少台,看客户的具体需求而定),这若干台数据库的内容在任何时刻由ICX保证是完 全一致 的。也就是说,ICX采用了全新的并发事务处理的方式,向连接的N台数据库同步复制事务处理,使得系统在任何时刻具有多个一致的 最新逻辑数据库数据集。当 其中一台数据库服务器发生故障的时候,ICX可以实时的、第一时间切换到其他服务器上来继续提供服务。真正 的实现零时间的服务器切换,大大提高安全性,真 正意义的实现服务器不间断服务。
注意:本文不是http RFC文档 未能那么详细和全面 有理解错误的地方 敬请谅解 一、连接至Web服务器 一个客户端应用(如Web浏览器)打开到Web服务器的HTTP端口的一个套接字(缺省为80)。 例如:http://www.myweb.com:8080/index.html 在Java中,这将等同于代码: Soceet socket=new Socket("www.myweb.com",8080); InputStream in=socket.getInputStream(); OutputStream out=socket.getOutputStream(); 二、发送HTTP请求 通过连接,客户端写一个ASCII文本请求行,后跟0或多个HTTP头标,一个空行和实现请求的任意数据。 一个请求由四个部分组成:请求行、请求头标、空行和请求数据 1.请求行:请求行由三个标记组成:请求方法、请求URI和HTTP版本,它们用空格分隔。 例如:GET /index.html HTTP/1.1 HTTP规范定义了8种可能的请求方法: GET 检索URI中标识资源的一个简单请求 HEAD 与GET方法相同,服务器只返回状态行和头标,并不返回请求文档 POST 服务器接受被写入客户端输出流中的数据的请求 PUT 服务器保存请求数据作为指定URI新内容的请求 DELETE 服务器删除URI中命名的资源的请求 OPTIONS 关于服务器支持的请求方法信息的请求 TRACE Web服务器反馈Http请求和其头标的请求 CONNECT 已文档化但当前未实现的一个方法,预留做隧道处理 2.请求头标:由关键字/值对组成,每行一对,关键字和值用冒号(:)分隔。请求头标通知服务器有关于客户端的功能和标识,典型的请求头标有: User-Agent 客户端厂家和版本 Accept 客户端可识别的内容类型列表 Content-Length 附加到请求的数据字节数 3.空行:最后一个请求头标之后是一个空行,发送回车符和退行,通知服务器以下不再有头标。 4.请求数据:使用POST传送数据,最常使用的是Content-Type和Content-Length头标。 三、服务端接受请求并返回HTTP响应 Web服务器解析请求,定位指定资源。服务器将资源副本写至套接字,在此处由客户端读取。 一个响应由四个部分组成;状态行、响应头标、空行、响应数据 1.状态行:状态行由三个标记组成:HTTP版本、响应代码和响应描述。 HTTP版本:向客户端指明其可理解的最高版本。 响应代码:3位的数字代码,指出请求的成功或失败,如果失败则指出原因。 响应描述:为响应代码的可读性解释。 例如:HTTP/1.1 200 OK HTTP响应码: 1xx:信息,请求收到,继续处理 2xx:成功,行为被成功地接受、理解和采纳 3xx:重定向,为了完成请求,必须进一步执行的动作 4xx:客户端错误: 2.响应头标:像请求头标一样,它们指出服务器的功能,标识出响应数据的细节。 3.空行:最后一个响应头标之后是一个空行,发送回车符和退行,表明服务器以下不再有头标。 4.响应数据:HTML文档和图像等,也就是HTML本身。 四、服务器关闭连接,浏览器解析响应 1.浏览器首先解析状态行,查看表明请求是否成功的状态代码。 2.然后解析每一个响应头标,头标告知以下为若干字节的HTML。 3.读取响应数据HTML,根据HTML的语法和语义对其进行格式化,并在浏览器窗口中显示它。 4.一个HTML文档可能包含其它需要被载入的资源引用,浏览器识别这些引用,对其它的资源再进行额外的请求,此过程循环多次。 五、无状态连接 HTTP模型是无状态的,表明在处理一个请求时,Web服务器并不记住来自同一客户端的请求。 六、实例 1.浏览器发出请求 GET /index.html HTTP/1.1 服务器返回响应: HTTP /1.1 200 OK Date: Apr 11 2006 15:32:08 GMT Server: Apache/2.0.46(win32) Content-Length: 119 Content-Type: text/html <HTML> <HEAD> <LINK REL="stylesheet" HREF="index.css"> </HEAD> <BODY> <IMG SRC="image/logo.png"> </BODY> </HTML> 2.浏览器发出请求 GET /index.css HTTP/1.1 服务器返回响应: HTTP /1.1 200 OK Date: Apr 11 2006 15:32:08 GMT Server: Apache/2.0.46(win32) Connection: Keep-alive, close Content-Length: 70 Content-Type: text/plane h3{ font-size:20px; font-weight:bold; color:#005A9C; } 3.浏览器发出请求 GET image/logo.png HTTP/1.1 服务器返回响应: HTTP /1.1 200 OK Date: Apr 11 2006 15:32:08 GMT Server: Apache/2.0.46(win32) Connection: Keep-alive, close Content-Length: 1280 Content-Type: text/plane {Binary image data follows} (附录) 1.HTTP规范:Internet工程制定组织(IETF)发布的RFC指定Internet标准,这些RFC被Internet研究发展机构广泛接受。因为它们是标准文档,故一般用正规语言编写,如立法文标一样。 2.RFC:RFC一旦被提出,就被编号且不会再改变,当一个标准被修改时,则给出一个新的RFC。作为标准,RFC在Internet上被广泛采用。 3.HTTP的几个重要RFC: RFC1945 HTTP 1.0 描述 RFC2068 HTTP 1.1 初步描述 RFC2616 HTTP 1.1 标准 4.资源标识符URI(Uniform Resource Identifter,URI) HTTP参考 一、HTTP码应码 响应码由三位十进制数字组成,它们出现在由HTTP服务器发送的响应的第一行。 响应码分五种类型,由它们的第一位数字表示: 1.1xx:信息,请求收到,继续处理 2.2xx:成功,行为被成功地接受、理解和采纳 3.3xx:重定向,为了完成请求,必须进一步执行的动作 4.4xx:客户端错误,请求包含语法错误或者请求无法实现 5.5xx:服务器错误,服务器不能实现一种明显无效的请求 下表显示每个响应码及其含义: 100 继续 101 分组交换协 200 OK 201 被创建 202 被采纳 203 非授权信息 204 无内容 205 重置内容 206 部分内容 300 多选项 301 永久地传送 302 找到 303 参见其他 304 未改动 305 使用代理 307 暂时重定向 400 错误请求 401 未授权 402 要求付费 403 禁止 404 未找到 405 不允许的方法 406 不被采纳 407 要求代理授权 408 请求超时 409 冲突 410 过期的 411 要求的长度 412 前提不成立 413 请求实例太大 414 请求URI太大 415 不支持的媒体类型 416 无法满足的请求范围 417 失败的预期 500 内部服务器错误 501 未被使用 502 网关错误 503 不可用的服务 504 网关超时 505 HTTP版本未被支持 二、HTTP头标 头标由主键/值对组成。它们描述客户端或者服务器的属性、被传输的资源以及应该实现连接。 四种不同类型的头标: 1.通用头标:即可用于请求,也可用于响应,是作为一个整体而不是特定资源与事务相关联。 2.请求头标:允许客户端传递关于自身的信息和希望的响应形式。 3.响应头标:服务器和于传递自身信息的响应。 4.实体头标:定义被传送资源的信息。即可用于请求,也可用于响应。 头标格式:<name>:<value><CRLF> 下表描述在HTTP/1.1中用到的头标 Accept 定义客户端可以处理的媒体类型,按优先级排序; 在一个以逗号为分隔的列表中,可以定义多种类型和使用通配符。例如:Accept: image/jpeg,image/png,*/* Accept-Charset 定义客户端可以处理的字符集,按优先级排序; 在一个以逗号为分隔的列表中,可以定义多种类型和使用通配符。例如:Accept-Charset: iso-8859-1,*,utf-8 Accept-Encoding 定义客户端可以理解的编码机制。例如:Accept-Encoding:gzip,compress Accept-Language 定义客户端乐于接受的自然语言列表。例如:Accept-Language: en,de Accept-Ranges 一个响应头标,它允许服务器指明:将在给定的偏移和长度处,为资源组成部分的接受请求。 该头标的值被理解为请求范围的度量单位。例如Accept-Ranges: bytes或Accept-Ranges: nonea Age 允许服务器规定自服务器生成该响应以来所经过的时间长度,以秒为单位。 该头标主要用于缓存响应。例如:Age: 30 Allow 一个响应头标,它定义一个由位于请求URI中的次源所支持的HTTP方法列表。例如:Allow: GET,PUT aUTHORIZATION 一个响应头标,用于定义访问一种资源所必需的授权(域和被编码的用户ID与口令)。 例如:Authorization: Basic YXV0aG9yOnBoaWw= Cache-Control 一个用于定义缓存指令的通用头标。例如:Cache-Control: max-age=30 Connection 一个用于表明是否保存socket连接为开放的通用头标。例如:Connection: close或Connection: keep-alive Content-Base 一种定义基本URI的实体头标,为了在实体范围内解析相对URLs。 如果没有定义Content-Base头标解析相对URLs,使用Content-Location URI(存在且绝对)或使用URI请求。 例如:Content-Base: Http://www.myweb.com Content-Encoding 一种介质类型修饰符,标明一个实体是如何编码的。例如:Content-Encoding: zip Content-Language 用于指定在输入流中数据的自然语言类型。例如:Content-Language: en Content-Length 指定包含于请求或响应中数据的字节长度。例如:Content-Length:382 Content-Location 指定包含于请求或响应中的资源定位(URI)。 如果是一绝。对URL它也作为被解析实体的相对URL的出发点。 例如:Content-Location: http://www.myweb.com/news Content-MD5 实体的一种MD5摘要,用作校验和。 发送方和接受方都计算MD5摘要,接受方将其计算的值与此头标中传递的值进行比较。 例如:Content-MD5: <base64 of 128 MD5 digest> Content-Range 随部分实体一同发送;标明被插入字节的低位与高位字节偏移,也标明此实体的总长度。 例如:Content-Range: 1001-2000/5000 Contern-Type 标明发送或者接收的实体的MIME类型。例如:Content-Type: text/html Date 发送HTTP消息的日期。例如:Date: Mon,10PR 18:42:51 GMT ETag 一种实体头标,它向被发送的资源分派一个唯一的标识符。 对于可以使用多种URL请求的资源,ETag可以用于确定实际被发送的资源是否为同一资源。 例如:ETag: "208f-419e-30f8dc99" Expires 指定实体的有效期。例如:Expires: Mon,05 Dec 2008 12:00:00 GMT Form 一种请求头标,给定控制用户代理的人工用户的电子邮件地址。例如:From: webmaster@myweb.com Host 被请求资源的主机名。对于使用HTTP/1.1的请求而言,此域是强制性的。例如:Host: www.myweb.com If-Modified-Since 如果包含了GET请求,导致该请求条件性地依赖于资源上次修改日期。 如果出现了此头标,并且自指定日期以来,此资源已被修改,应该反回一个304响应代码。 例如:If-Modified-Since: Mon,10PR 18:42:51 GMT If-Match 如果包含于一个请求,指定一个或者多个实体标记。只发送其ETag与列表中标记区配的资源。 例如:If-Match: "208f-419e-308dc99" If-None-Match 如果包含一个请求,指定一个或者多个实体标记。资源的ETag不与列表中的任何一个条件匹配,操作才执行。 例如:If-None-Match: "208f-419e-308dc99" If-Range 指定资源的一个实体标记,客户端已经拥有此资源的一个拷贝。必须与Range头标一同使用。 如果此实体自上次被客户端检索以来,还不曾修改过,那么服务器只发送指定的范围,否则它将发送整个资源。 例如:Range: byte=0-499<CRLF>If-Range:"208f-419e-30f8dc99" If-Unmodified-Since 只有自指定的日期以来,被请求的实体还不曾被修改过,才会返回此实体。 例如:If-Unmodified-Since:Mon,10PR 18:42:51 GMT Last-Modified 指定被请求资源上次被修改的日期和时间。例如:Last-Modified: Mon,10PR 18:42:51 GMT Location 对于一个已经移动的资源,用于重定向请求者至另一个位置。 与状态编码302(暂时移动)或者301(永久性移动)配合使用。 例如:Location: http://www2.myweb.com/index.jsp Max-Forwards 一个用于TRACE方法的请求头标,以指定代理或网关的最大数目,该请求通过网关才得以路由。 在通过请求传递之前,代理或网关应该减少此数目。例如:Max-Forwards: 3 Pragma 一个通用头标,它发送实现相关的信息。例如:Pragma: no-cache Proxy-Authenticate 类似于WWW-Authenticate,便是有意请求只来自请求链(代理)的下一个服务器的认证。 例如:Proxy-Authenticate: Basic realm-admin Proxy-Proxy-Authorization 类似于授权,但并非有意传递任何比在即时服务器链中更进一步的内容。 例如:Proxy-Proxy-Authorization: Basic YXV0aG9yOnBoaWw= Public 列表显示服务器所支持的方法集。例如:Public: OPTIONS,MGET,MHEAD,GET,HEAD Range 指定一种度量单位和一个部分被请求资源的偏移范围。例如:Range: bytes=206-5513 Refener 一种请求头标域,标明产生请求的初始资源。对于HTML表单,它包含此表单的Web页面的地址。 例如:Refener: http://www.myweb.com/news/search.html Retry-After 一种响应头标域,由服务器与状态编码503(无法提供服务)配合发送,以标明再次请求之前应该等待多长时间。 此时间即可以是一种日期,也可以是一种秒单位。例如:Retry-After: 18 Server 一种标明Web服务器软件及其版本号的头标。例如:Server: Apache/2.0.46(Win32) Transfer-Encoding 一种通用头标,标明对应被接受方反向的消息体实施变换的类型。例如:Transfer-Encoding: chunked Upgrade 允许服务器指定一种新的协议或者新的协议版本,与响应编码101(切换协议)配合使用。 例如:Upgrade: HTTP/2.0 User-Agent 定义用于产生请求的软件类型(典型的如Web浏览器)。 例如:User-Agent: Mozilla/4.0(compatible; MSIE 5.5; Windows NT; DigExt) Vary 一个响应头标,用于表示使用服务器驱动的协商从可用的响应表示中选择响应实体。例如:Vary: * Via 一个包含所有中间主机和协议的通用头标,用于满足请求。例如:Via: 1.0 fred.com, 1.1 wilma.com Warning 用于提供关于响应状态补充信息的响应头标。例如:Warning: 99 www.myweb.com Piano needs tuning www-Authenticate 一个提示用户代理提供用户名和口令的响应头标,与状态编码401(未授权)配合使用。响应一个授权头标。 例如:www-Authenticate: Basic realm=zxm.mgmt
排序函数:产生一个新的列,一般作为一个流水号排序函数 OVER( [分组子句] 排序子句[DESC][ASC] )(1)row_number():产生一个新的列流水号列,所有的流水号从1开始,然后累加(2)rank():产生一个新的列流水号列,所有的流水号从1开始,然后累加,如果排序子句内容重复,流水号也跟着重复.而后续的流水号会掉过一个或多个.(3)dense_rank():产生一个新的列流水号列,所有的流水号从1开始,然后累加,如果排序子句内容重复,流水号也跟着重复.而后续的流水号不会跳过用途:分页视图:相当于给查询的结果起别名1、建立视图:create view 视图名称(view_stuinfo_stuscore)as 查询语句注意:(1)视图名称不要和查询的表名有重复(2)SQL Server 2005 允许嵌套视图。但嵌套不得超过 32 层。视图最多可包含1024个字段。(3)视图中不能使用DEFAULT/COMPUTE 子句、COMPUTE BY 子句或 INTO 关键字/ORDER BY,除非ORDER BY所在查询语句中有top子句2、使用视图:select * from 视图名称(view_stuinfo_stuscore)3、删除视图:drop view 视图名称(view_stuinfo_stuscore)目标:集合运算:UNION和UNION ALL:并集UNION:把两张表合为一张表,去掉重复数据UNION ALL:把两张表合为一张表,不去掉重复数据注意:两张是同一个表select * from stumarks where writtenexam >70unionselect * from stuinfo where writtenexam > 60select * from stumarks where writtenexam > 70union allselect * from stumarks where writtenexam > 60INTERSECT:交集把两张表合为一张表,去掉不重复数据,保留重复数据select * from stumarks where writtenexam > 70intersectselect * from stumarks where writtenexam > 60EXCEPT:差集把两张表相减,保留不重复的数据注意:前一个表比后一个表的数据要多才行select * from stumarks where writtenexam > 60exceptselect * from stumarks where writtenexam > 70索引:类似是一个目录,使搜索数据的时候速度更快,大数据量非常庞大的时候,使用索引可以加快增,删,查,改的速度.索引的主要分类(1)聚集索引:一旦使用聚集索引,所有的数据回全部重新排列一遍.一张表中,只能有一个聚集索引一张表中,如果有主键,那么会自动的在主键上添加一个聚集索引(2)非聚集索引 使用非聚集索引的时候,数据不会重新排列.一张表中,可以建立多个非聚集索引一张表中,可以对所有的非主键字段加上非聚集索引创建非聚集索引:CREATE NONCLUSTERED INDEX IX_scoreON stuMarks(score)WITH FILLFACTOR= 30删除索引:DROP INDEX Students.IX_score注意:(1)索引名所在的表sysindexes;IF exists(SELECT NAME FROM dbo.sysindexes WHERE name = 'IX_score')DROP INDEX Students.IX_score(2)在删除索引的时候:数据库名.索引名称(3)使用索引SELECT * FROM stuMarks WITH(INDEX = IX_score)WHERE score between 60 and 90(3)其他索引唯一索引索引视图包含性列索引全文索引XML索引等 事务:一个不可再分的模块,要么模块内容都执行,要么都不执行显示事务创建显示事务:begin TRANSACTION 显示事务名(tran_bank)当事物中的代码有错误的时候,那么就需要回滚事物,使我们的环境回到初使状态ROLLBACK TRANSACTION当事物中的代码没有错误的时候,那么就需要提交事物来运行所有内容COMMIT TRANSACTION自动提交事物每一条sql语句就是一个自动提交事物,sql语句运行失败了数据是不会发生改变的。隐式事务
Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) values(value1,value2,...)这种形式的在应用程序开发中必不可少。但我们在开发、测试过程中,经常会遇到需要表复制的情况,如将一个table1的数据的部分字段复制到table2中,或者将整个table1复制到table2中,这时候我们就要使用SELECT INTO 和 INSERT INTO SELECT 表复制语句了。 1.INSERT INTO SELECT语句 语句形式为:Insert into Table2(field1,field2,...) select value1,value2,... from Table1 要求目标表Table2必须存在,由于目标表Table2已经存在,所以我们除了插入源表Table1的字段外,还可以插入常量。示例如下: --1.创建测试表 create TABLE Table1 ( a varchar(10), b varchar(10), c varchar(10), CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED ( a ASC ) ) ON [PRIMARY] create TABLE Table2 ( a varchar(10), c varchar(10), d int, CONSTRAINT [PK_Table2] PRIMARY KEY CLUSTERED ( a ASC ) ) ON [PRIMARY] GO --2.创建测试数据 Insert into Table1 values('赵','asds','90') Insert into Table1 values('钱','asds','100') Insert into Table1 values('孙','asds','80') Insert into Table1 values('李','asds',null) GO select * from Table2 --3.INSERT INTO SELECT语句复制表数据 Insert into Table2(a, c, d) select a,c,5 from Table1 GO --4.显示更新后的结果 select * from Table2 GO --5.删除测试表 drop TABLE Table1 drop TABLE Table2 2.SELECT INTO FROM语句 语句形式为:SELECT vale1, value2 into Table2 from Table1 要求目标表Table2不存在,因为在插入时会自动创建表Table2,并将Table1中指定字段数据复制到Table2中。示例如下: --1.创建测试表 create TABLE Table1 ( a varchar(10), b varchar(10), c varchar(10), CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED ( a ASC ) ) ON [PRIMARY] GO --2.创建测试数据 Insert into Table1 values('赵','asds','90') Insert into Table1 values('钱','asds','100') Insert into Table1 values('孙','asds','80') Insert into Table1 values('李','asds',null) GO --3.SELECT INTO FROM语句创建表Table2并复制数据 select a,c INTO Table2 from Table1 GO --4.显示更新后的结果 select * from Table2 GO --5.删除测试表 drop TABLE Table1 drop TABLE Table2
问题:如何把具有相同字段的记录删除,只留下一条。 例如:表test里有id,name字段,如果有name相同的记录只留下一条,其余的删除。name的内容不定,相同的记录数不定。 用SQL语句删除重复记录的方法: 1、将重复的记录记入temp1表 select [标志字段id],count(*) into temp1 from [表名] group by [标志字段id] having count(*)>1 2、将不重复的记录记入temp1表 insert temp1 select [标志字段id],count(*) from [表名] group by [标志字段id] having count(*)=1 3、作一个包含所有不重复记录的表 select * into temp2 from [表名] where 标志字段id in(select 标志字段id from temp1) 4、删除重复表:delete [表名] 5、恢复表 insert [表名] select * from temp2 6、删除临时表 drop table temp1 drop table temp2
Code: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>复习Dom操作</title> <style type="text/css"> #newTest { color:Red; } </style> <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript"></script> <script type="text/javascript"> // //jQuery写法--页面加载完成 // $(document).ready(function () { // }); // //可以简写成 // $(function () { // // }); //标准js中的写法 --页面加载完成 window.onload = function () { var root = document.documentElement; //DOM根节点 --文档节点 var farDIV = document.getElementById("farther"); //根据ID获得DOM的“元素节点” //元素节点的一些常用的属性 //document.getElementsByTagName("")//根据元素(标签)名称获得DOM的“元素节点”(可能是单个或一组) //document.getElementsByName("")//根据name属性获得DOM的“元素节点”(可能是单个或一组) var idNode = farDIV.getAttributeNode("id"); //获得属性为id的“属性节点” //注意:不要把 getAttribute("id")和getAttributeNode("id")搞混了,前者获取的是属性的值,后者获取的属性节点 var textNode = farDIV.firstChild; //farDIV的第一个子节点 //DOM中常用的属性 //属性(其实只在属性节点中定义才有效) //farDIV.attributes;//返回该节点的所有属性对象的数组 //节点名称 // alert(root.nodeName + ":" + farDIV.nodeName + ":" + idNode.nodeName + ":" + textNode.nodeName); //nodeType(不支持IE)有五种,分别代表:标签(元素)节点,属性节点,文本节点,根节点,注释节点 //alert(root.nodeType + ":" + farDIV.nodeType + ":" + idNode.nodeType + ":" + textNode.nodeType); //节点值 //alert(root.nodeValue + ":" + root.nodeValue + ":" + idNode.value + ":" + textNode.nodeValue); //根节点的一些常用属性 -- 创建新节点 var newNode = document.createElement("div"); //创建一个div节点 var text = document.createTextNode("这是我手动创建的"); //创建一个文本节点 newNode.appendChild(text); //将文本节点插入div节点中 var attrNode = document.createAttribute("id"); //创建属性节点,属性名为“id” newNode.setAttributeNode(attrNode); //将newNode节点的属性节点设置为attrNode newNode.setAttribute("id", "newTest"); //设置newNode节点的id属性设置为newTest //var comment = document.createComment("我是注释");//创建注释节点 //获取页面中的body标签所在节点 var body = root.lastChild; //获取root的最后一个子节点 body.appendChild(newNode); //将新建的节点插入body中 //如果添加成功,则CSS会生效(字体变红,ID选择器有效) //获取元素的属性 alert(farDIV.id); alert(farDIV.getAttribute("id")); alert(newNode.getAttribute("id")); //HTML中获取节点的属性 -- 在XHTML中的可以简写 :alert(newNode.id); // //动态的创建表格 //1.可以使用与上面类似的方式,创建节点、拼接、插入... 但是这样操作似乎很麻烦 //2.可以使用更加简单的操作方式 }; </script> </head> <body> <div id="farther">我是父div</div> </body> </html>