Author: Gideon

关于大型网站技术演进的思考(三)–存储的瓶颈(3)

存储的瓶颈写到现在就要进入到深水区了,如果我们所做的网站已经到了做数据库垂直拆分和水平拆分的阶段,那么此时我们所面临的技术难度的挑战也会大大增强。 这里我们先回顾下数据库的垂直拆分和水平拆分的定义:   垂直拆分:把一个数据库中不同业务单元的数据分到不同的数据库里。   水平拆分:是根据一定的规则把同一业务单元的数据拆分到多个数据库里。 垂直拆分 是一个粗粒度的拆分数据,它主要是将原来在一个数据库下的表拆分到不同的数据库里,水平拆分粒度比垂直拆分要更细点,它是将一张表拆到不同数据库里,粒度 的粗细也会导致实现技术的难度的也不一样,很明显水平拆分的技术难度要远大于垂直拆分的技术难度。难度意味着投入的成本的增加以及我们需要承担的风险的加 大,我们做系统开发一定要有个清晰的认识:能用简单的方案解决问题,就一定要毫不犹豫的舍弃复杂的方案,当系统需要使用高难度技术的时候,我们一定要让自己感受到这是迫不得已的。 我是以 java工程师应聘进了我现在的公司,所以在我转到专职前端前,我也做过不少java的应用开发,当时我在公司的前辈告诉我,我们公司的数据库建模很简 单,怎么个简单法了,数据库的表之间都没有外键,数据库不准写触发器,可以写写存储过程,但是存储过程决不能用于处理生产业务逻辑,而只能是一些辅助工 作,例如导入导出写数据啊,后面听说就算是数据库做到了读写分离,数据之间同步也最好是用java程序做,也不要使用存储过程,除非迫不得已。开始我还不 太理解这些做法,这种不理解不是指我质疑了公司的做法,而是我在想如果一个数据库我们就用了这么一点功能,那还不如让数据库公司为咋们定制个阉割版算了, 不过在我学习了hadoop之后我有点理解这个背后的深意了,其实作为存储数据的数据库,它和我们开发出的程序的本质是一样的那就是:存储和计算,那么当 数据库作为一个业务系统的存储介质时候,那么它的存储对业务系统的重要性要远远大于它所能承担的计算功能,当数据库作为互联网系统的存储介质时候,如果这 个互联网系统成长迅速,那么这个时候我们对数据库存储的要求就会越来越高,最后估计我们都想把数据库的计算特性给阉割掉,当然数据库基本的增删改查我们是 不能舍弃的,因为它们是数据库和外界沟通的入口,我们如果接触过具有海量数据的数据库,我们会发现让数据库运行的单个sql语句都会变得异常简洁和简单, 因为这个时候我们知道数据库已经在存储这块承担了太多的负担,那么我们能帮助数据库的手段只能是尽量降低它运算的压力。 回到关于数据库垂直拆分和水平拆分的问题,假如我们的数据库设计按照我们公司业务数据库为蓝本的话,那么数据库进行了水平拆分我们会碰到什么样的问题了?为了回答这个问题我就要比较下拆分前和拆分后会给调用数据库的程序带来怎样的不同,不同主要是两点:   第一点:被拆出的表和原库的其他表有关联查询即使用join查询的操作需要进行改变;    第二点:某些增删改(注意:一般业务库设计很少使用物理删除,因为这个操作十分危险,这里的删往往是逻辑删除,一般做法就是更新下记录的状态,本质是一 个更新操作)牵涉到拆分的表和原库其他表共同完成,那么该操作的事务性就会被打破,如果处理不好,假如碰到操作失败,业务无法做到回滚,这会对业务操作的 安全性带来极大的风险。 关于解决第一点的问题还是相对比较简单的,方式方法也很多,下面我来讲讲我所知道的一些方法,具体如下: 方法一: 在垂直拆表时候,我们先梳理下使用到join操作sql查询,梳理的维度是以被拆分出的表为原点,如果是弱依赖的join表我们改写下sql查询语句,如 果是强依赖的join表则随拆分表一起拆分,这个方法很简单也很可控,但是这个技术方案存在一个问题,就是让拆分粒度变大,拆分的业务规则被干扰,这么拆 分很容易犯一个问题就是一个数据库里总会存在这样一些表,就是很多数据库都会和它关联,我们很难拆解这些关联关系,当我们无法理清时候就会把该表做冗余, 即不同数据库存在雷同表,随着业务增长,这种表的数据同步就成为了数据库的一个软肋,最终它会演变为整个数据库系统的短板甚至是全系统的短板。 方法二: 我们拆表的准则还是按业务按需求在数据库层面进行,等数据库拆好后,再改写原来受到影响的join查询语句,这里我要说明的是查询语句修改的成本很低,因 为查询操作是个只读操作,它不会改变任何底层的东西,如果数据表跨库,我们可以把join查询拆分为多次查询,最后将查询结果在内存中归纳和合并,其实我 们如果主动拆库,绝不会把换个不同的数据库产品建立新库,肯定是使用相同数据库,同类型的数据库基本都支持跨库查询,不过跨库查询听说效率不咋地,我们可 以有选择的使用。这种方案也有个致命的缺点,我们做数据库垂直拆分绝不可能一次到位,一般都是多次迭代,而该方案的影响面很大,关联方过多,每次拆表几乎 要检查所有相关的sql语句,这会导致系统不断累积不可预知的风险。 以下三段内容是方法三: 不管是方法一还是方法二,都有一个很根本的缺陷就是数据库和上层业务操作耦合度很高,每次数据库的变迁都导致业务开发跟随做大量的同步工作,这样的后果就 是资源浪费,做服务的人不能天天被数据库牵着鼻子走,这样业务系统的日常维护和业务扩展会很存问题,那么我们一定要有一个服务和数据库解耦方案,那么这里 我们就得借鉴ORM技术了。(这里我要说明下,方法一和方法二我都是以修改sql阐述的,在现实开发里很多系统会使用ORM技术,互联网一般用 ibatis和mybatis这种半ORM的产品,因为它们可以直接写sql和数据库最为亲近,如果使用hibernate则就不同了,但是… Read More

关于大型网站技术演进的思考(四)–存储的瓶颈(4)

如果数据库需要进行水平拆分,这其实是一件很开心的事情,因为它代表公司的业务正在迅猛的增长,对于开发人员而言那就是有不尽的项目可以做,虽然会感觉很忙,但是人过的充实,心里也踏实。   数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个整体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储。 当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义需要多张物理表协同完成,因此数据库的表被水平拆分后,那么我们对这张表的操作 已经超出了数据库本身提供给我们现有的手段,换句话说我们对表的操作会超出数据库本身所拥有的处理能力,这个时候我就需要设计相关的方案来弥补数据库缺失 的能力,这就是数据库水平拆分最大的技术难点所在。 数据库的 水平拆分是数据库垂直拆分的升级版,它和垂直拆分更像继承机制里的父子关系,因此水平拆分后,垂直拆分所遇到的join查询的问题以及分布式事务的问题任 然存在,由于表被物理拆解增加了逻辑表的维度,这也给垂直拆分里碰到的两个难题增加了更多的维度,因此水平拆分里join查询的问题和分布式事务会变得更 加复杂。水平拆分除了垂直拆分两个难题外,它还会产生新的技术难题,这些难题具体如下:   难题一:数据库的表被水平拆分后,该表的主键设计会变得十分困难;   难题二:原来单表的查询逻辑会面临挑战。 在准备本篇文章时候,我看到一些资料里还提到了一些难题,这些难题是:   难题三:水平拆分表后,外键的设计也会变得十分困难;   难题四:这个难题是针对数据的新增操作的,大致的意思是,我们到底按什么规则把需要存储的数据存储在拆分出的那个具体的物理数据表里。 难题三的 问题,我在上篇已经给出了解答,这里我进行一定的补充,其实外键问题在垂直拆分就已经存在,不过在讲垂直拆分时候我们没有讲到这个问题,这主要是我设定了 一个前提,就是数据表在最原始的数据建模阶段就要抛弃所有外键的设计,并将外键的逻辑抛给服务层去完成,我们要尽全力减轻数据库承担的运算压力,其实除了 减轻数据库运算压力外,我们还要将作为存储原子的表保持相对的独立性,互不关联,那么要做到这点最直接的办法就是去掉表与表之间关联的象征:外键,这样我 们就可以从根基上为将来数据库做垂直拆分和水平拆分打下坚实的基础。 至于难题 四,其实问题的本质是分库分表后具体的数据在哪里落地的问题,而数据存储在表里的关键障碍其实就是主键,试想一下,我们设计张表,所有字段我们都准许可以 为空,但是表里有个字段是绝对不能为空的,那就是主键,主键是数据在数据库里身份的象征,因此我们在主键设计上是可以体现出该数据的落地规则,那么难题四 也会随之解决。因此下文我会重点讲解前两个水平拆分的难题。 首先是水 平拆分里的主键设计问题,抛开所有主键所能代表的业务含义,数据库里标的主键本质是表达表里的某一条记录的唯一性,在设计数据库的时候我们可以由一个绝对 不可重复的字段表示主键,也可以使用多个字段组合起来表达这种唯一性,使用一个字段表示主键,这已经是很原子级的操作,没法做进一步的修改,但是如果使用 多个字段表示一个主键对于水平拆分而言就会碰到问题了,这个问题主要是体现在数据到底落地于哪个数据库,关于主键对数据落地的影响我会在把相关知识讲解完 毕后再着重阐述,这里要提的是当碰到联合主键时候我们可以设定一个没有任何业务含义的字段来替代,不过这个要看场景了,我倾向于将联合主键各个字段里的值 合并为一个字段来表示主键,如果有的朋友认为这样会导致数据冗余,那么可以干脆去掉原来做联合主键的相关字段就是用一个字段表示,只不过归并字段时候使用 一个分隔符,这样方便服务层进行业务上的拆分。 由上所述,这里我给出水平拆分主键设计的第一个原则:被水平拆分的表的主键设计最好使用一个字段表示。 如果我们 的主键只是表达记录唯一性的话,那么水平拆分时候相对要简单的多,例如在Oracle数据库里有一个sequence机制,这其实就是一个自增数的算法, 自增机制几乎所有关系数据库都有,也是我们平时最喜欢使用的主键字段设计方案,如果我们要拆分的表,使用了自增字段,同时这个自增字段只是用来表达记录唯 一性,那么水平拆分时候处理起来就简单多了,我这里给出两个经典方案,方案如下:   方案一: 自增列都有设定步长的特性,假如我们打算把一张表只拆分为两个物理表,那么我们可以在其中一张表里把主键的自增列的步长设计为2,起始值为1,那么它的自 增规律就是1,3,5,7依次类推,另外一张物理表的步长我们也可以设置为2,如果起始值为2,那么自增规律就是2,4,6,8以此类推,这样两张表的主 键就绝对不会重复了,而且我们也不用另外做两张物理表相应的逻辑关联了。这种方案还有个潜在的好处,那就是步长的大小和水平数据拆分的粒度关联,也是我们 为水平拆分的扩容留有余量,例如我们把步长设计为9,那么理论上水平拆分的物理表可以扩容到9个。   … Read More

关于大型网站技术演进的思考(五)–存储的瓶颈(5)

上文里我遗留了两个问题,一个问题是数据库做了水平拆分以后,如果我们对主键的设计采取一种均匀分布的策略,那么它对于被水平拆分出的表后续的查询操作 将有何种影响,第二个问题就是水平拆分的扩容问题。这两个问题在深入下去,本系列就越来越技术化了,可能最终很多朋友读完后还是没有找到解决实际问题的启 迪,而且我觉得这些问题都是像BAT这样巨型互联网公司才会认真思考的,因此本篇我打算换个角度来阐述本文的后续内容。   这里我们 首先要明确一个问题,到底是什么因素促使我们去做数据库的垂直拆分和水平拆分的呢?答案很简单就是业务发展的需求,前文里的水平拆分技术方案基本都是抛弃 千变万化的业务规则的限制,尽量将水平拆分的问题归为一个简单的技术实现方案,而纯技术手段时常是看起来很美,但是到了面对现实问题时候,常常会变得那么 苍白和无力。   水平拆分 的难题里我还有个难题没有讲述,就是水平拆分后对查询操作的影响,特别是对单表查询的影响,这点估计也是大伙最为关心的问题,今天我不在延着水平拆分的技 术手段演进是阐述上文的遗留问题,而是我要把前面提到的技术手段和一些典型场景结合起来探讨如何解决网站存储的瓶颈问题。   前文中我总结过一个解决存储瓶颈的脉络,具体如下:   单库数据库–>数据库读写分离–>缓存技术–>搜索技术–>数据的垂直拆分–>数据的水平拆分   这个脉络给一些朋友产生了误解,就是认为这个过程应该是个串行的过程,其实在实际的场景下这个过程往往是并行的,但是里面有一个元素应该是串行的或者说思考时候有个先后问题,那就是对数据库层的操作,具体如下:   单库数据库–>数据库读写分离–>数据的垂直拆分–>数据的水平拆分   而缓存技 术和搜索技术在数据库的任意阶段里都可以根据实际的业务需求随时切入其中帮助数据库减轻不必要的压力。例如,当网站的后台数据库还是单库的时候,数据库渐 渐出现了瓶颈问题,而这个瓶颈又没有达到需要采取大张旗鼓做读写分离方案的程度,那么我这个时候可以考虑引入缓存机制。不过要合理的使用缓存我们首先要明 确缓存本身的特点,这些特点如下所示:     特点一:缓存主要是适用于读操作,并且缓存的读操作的效率要远远高于从数据库以及硬盘读取数据的效率。    特点二:缓存的数据是存储在内存当中,因此当系统重启,宕机等等异常场景下,缓存数据就会不可逆的丢失,且无法恢复,因此缓存不能作为可靠存储设备,这 就导致一个问题,缓存里的数据必须首先从数据库里同步到内存中,而使用缓存的目的就是为了解决数据库的读操作效率低下的问题,数据库的数据同步到缓存的操 作会因为数据库的效率低下而在性能上大打折扣,所以缓存适合的场景是那些固定不变的数据以及业务对实时性变化要求不高的数据。   根据缓存的上述两个特点,我们可以把数据库里和上述描述类似操作的相关数据迁移到缓存里,那样我们就从数据库上剥离了那些对数据库价值不高的操作,让数据库专心做有价值的操作,这样也是减轻数据库压力的一种手段。   不过这个 手段局限性很强,局限性主要是一台计算机了用于存储缓存的内存的大小都是远远要低于硬盘,并且内存的价格要远贵于硬盘,如果我们将大规模的数据从硬盘往内 存迁移,从资源成本和利用率角度考虑性价比还是很低的,因此缓存往往都是用于转存那些不会经常变化的数据字典,以及经常会被读,而修改较少的数据,但是这 些数据的规模也是有一定限度的,因此当单库数据库出现了瓶颈时候马上就着手进行读写分离方案的设计性价比还是很高的。   前文我讲 到我们之所以选择数据库读写分离是主要原因是因为数据库的读写比例严重失衡所致,但是做了读写分离必然有个问题不可避免,写库向读库同步数据一定会存在一 定的时间差,如果我们想减小读库和写库数据的时间差,那么任然会导致读库因为写的粒度过细而发生部分性能的损失,但是时间差过大,或许又会无法满足实际的 业务需求,因此这个时间差的设计一定要基于实际的业务需求合理的设计。… Read More

关于大型网站技术演进的思考(六)–存储的瓶颈(6)

在讲数据库水平拆分时候,我列出了水平拆分数据库需要解决的两个难题,它们分别是主键的设计问题和单表查询的问题,主键问题前文已经做了比较详细的讲述了,但是第二个问题我没有讲述,今天我将会讲讲如何解决数据表被垂直拆分后的单表查询问题。   要解决数据表被水平拆分后的单表查询问题,我们首先要回到问题的源头,我们为什么需要将数据库的表进行水平拆分。下面我们来推导下我们最终下定决心做水平拆分表的演进过程,具体如下:     第一个演进过程:进行了读写分离的表在数据增长后需要进行水平拆分吗?回答这个疑问我们首先要想想进行读写分离操作的表真的是因为数据量大吗?答案其实是否定的。最基本的读写分离的目的是为了解决数据库的某张表读写比率严重失衡的问题, 举个例子,有一张表每天会增加1万条数据,也就是说我们的系统每天会向这张表做1万次写的操作,当然也有可能我们还会更新或者删除这张表的某些已有的记 录,这些操作我们把它归并到写操作,那么这张表一天我们随意定义个估值吧2万5千次写操作,其实这种表的数据量并不大,一年下来也就新增的几百万条数据, 一个大型的商业级别的关系数据库,当我们为表建立好索引和分区后,查询几百万条数据它的效率并不低,这么说来查询的效率问题还不一定是读写分离的源头。其 实啊,这张表除了写操作每天还承受的读操作可能会是10万,20万甚至更高,这个时候问题来了,像oracle和mysql这样鼎鼎大名的关系数据库默认 的最大连接数是100,一般上了生产环境我们可能会设置为150或者200,这些连接数已经到了这些关系数据库的最大极限了,如果再加以提升,数据库性能 会严重下降,最终很有可能导致数据库由于压力过大而变成了一个巨锁,最终导致系统发生503的错误,如是我们就会想到采用读写分离方案,将数据库的读操作 迁移到专门的读库里,如果系统的负载指标和我列举的例子相仿,那么迁移的读库甚至不用做什么垂直拆分就能满足实际的业务需求,因为我们的目的只是为了减轻 数据库的连接压力。     第二个演进过程: 随着公司业务的不断增长,系统的运行的压力也越来越大了,我们已经了解了系统的第一个瓶颈是从存储开始了,如是我们开始谈论方案如何解决存储的问题,这时 我们发现我们已经做了读写分离,也使用了缓存,甚至连搜索技术也用上了,那么下个阶段就是垂直拆分了,垂直拆分很简单就是把表从数据库里拆出来,单独建库 建表,但是这种直截了当的方案想想就能感到这样的做法似乎没有打中系统的痛点,那么系统的痛点到底是什么呢?根据数据库本身的特性,我们会发现痛点主要是 三个方面组成:     第一个方面:数据库的连接数的限制。 原库的某些表可能承担数据库80%的连接,极端下甚至可以超过90%的连接,而且这些表的业务操作十分的频繁,当其他小众业务的表需要进行操作时候,搞不 好因为连接数被全部占用而不得不排队等待空闲连接的出现,那么这个时候我们就会考虑把这张表做垂直拆分,这样就减轻了原数据库连接的压力,使得数据库连接 负载变得比较均衡。     第二个方面是数据库的读操作,第三个方面是数据库的写操作, 虽然把读和写分成两个方面,但是这两个方面在我们做垂直拆分时候要结合起来考虑。首先我们要分析下数据库的写操作,单独的写操作效率都是很高的,不管我们 的写是单条记录的写操作,还是批量的写操作,这些写操作的数据量就是我们要去写的数据的大小,因此控制写的数据量的大小是一件很容易很天然的操作,所以这 些操作不会造成数据库太大负担,详细点的话,对于数据库而言,新增操作无非是在原来数据后面追加些记录,而修改操作或者删除操作一般都是通过建立了高效索 引的字段来定位数据后再进行的操作,因此它的性能也是非常高的。而读操作看起来比写操作简单(例如:读操作不存在像事务这些乌七八糟因素的干扰),但是当 读操作面对海量数据时候就严重挑战着数据库和硬盘的极限能力,因此读操作很容易产生瓶颈问题,而且这个瓶颈不管问题表是否读写失衡都会面临的。前文里我详 细列举了一个交易表设计的案例,其中我们可以看到数据库垂直拆分在实际应用里的运用,在例子里我们首先根据业务特点将交易表分成了实时交易表和历史交易 表,这个做法其实就是将原交易表的读和写进行分离,但是这种分离和纯粹的读写分离相比会更加有深意,这个深意就是拆分实时和历史交易表也就是在分拆原表的 读写操作的关联性,换句话说,如果我们不这么做的话,那么交易表的每次写和每次读几乎等价,这样我们没法单独解决读的性能问题,分出了历史交易表后我们再 对历史交易表来做读的优化,那么这也不会影响到写操作,这样把问题的复杂度给降低了。在案例里我们对历史交易表进行了业务级别的水平拆分,但是这个拆分是 以如何提升读的效率进行的,因此前文讲到的水平拆分里主键设计方案基本上派不上用场,因为这两种水平拆分的出发点是不同的,那么使用的手段和达到效果也将 不一样。   由上所述,我们可以把数据库的水平拆分重新定义下,我在这几篇文章里一直讲述的水平拆分本质是从数据库技术来定义的,我把它们称为狭义的水平拆分,与狭义相对的就是广义的水平拆分,例如上文例子里把交易表根据业务特性分为实时交易表和历史交易表,这种行为也是一种水平拆分,但是这个拆分不会遵守我前面讲到主键设计方案,但是它的确达到水平拆分的目的,所以这样的水平拆分就属于广义的水平拆分了。     … Read More

关于大型网站技术演进的思考(七)–存储的瓶颈(7)

本文开篇提个问题给大家,关系数据库的瓶颈有哪些?我想有些朋友看到这个问题肯定会说出自己平时开发中碰到了一个跟数据库有关的什么什么问题,然后如何 解决的等等,这样的答案没问题,但是却没有代表性,如果出现了一个新的存储瓶颈问题,你在那个场景的处理经验可以套用在这个新问题上吗?这个真的很难说。   其实不管什么样的问题场景最后解决它都要落实到数据库的话,那么这个问题场景一定是击中了数据库的某个痛点,那么我前面的六篇文章里那些手段到底是在解决数据库的那些痛点,下面我总结下,具体如下:      痛点一:数据库的连接数不够用了。换句话说就是在同一个时间内,要求和数据库建立连接的请求超出了数据库所允许的最大连接数,如果我们对超出的连接数没 有进行有效的控制让它们直接落到了数据库上,那么就有可能会让数据库不堪重负,那么我们就得要分散这些连接,或者让请求排队。      痛点二:对于数据库表的操作无非两种一种是写操作,一种是读操作,在现实场景下很难出现读写都成问题的事情,往往是其中一种表的操作出现了瓶颈问题所引 起的,由于读和写都是操作同一个介质,这就导致如果我们不对介质进行拆分去单独解决读的问题或者写的问题会让问题变的复杂化,最后很难从根本上解决问题。      痛点三:实时计算和海量数据的矛盾。本系列讲存储瓶颈问题其实有一个范畴的,那就是本系列讲到的手段都是在使用关系数据库来完成实时计算的业务场景,而 现实中,数据库里表的数据都会随着时间推移而不断增长,当表的数据超出了一定规模后,受制于计算机硬盘、内存以及CPU本身的能力,我们很难完成对这些数据的实时处理,因此我们就必须要采取新的手段解决这些问题。   我今天之所以总结下这三个痛点,主要是为了告诉大家当我们面对存储瓶颈问题时候,我们要把问题最终落实到这个问题到底是因为触碰到了数据库的那些痛点,这样回过头来再看我前面说到的技术手段,我就会知道该用什么手段来解决问题了。   好了,多余的话就说到这里,下面开始本篇的主要内容了。首先给大伙看一张有趣的漫画,如下图所示: 身为程序 员的我看到这个漫画感到很沮丧,因为我们被机器打败了。但是这个漫画同时提醒了做软件的程序员,软件的性能其实和硬件有着不可分割的关系,也许我们碰到的 存储问题不一定是由我们的程序产生的,而是因为好的炮弹装进了一个老旧过时的大炮里,最后当然我们会感到炮弹的威力没有达到我们的预期。除此之外了,也有 可能我们的程序设计本身没有有效的利用好已有的资源,所以在前文里我提到如果我们知道存储的瓶颈问题将会是网站首先发生问题的地方,那么在数据库建模时候 我们要尽量减轻数据库的计算功能,只保留数据库最基本的计算功能,而复杂的计算功能交由数据访问层完成,这其实是为解决瓶颈问题打下了一个良好的基础。最 后我想强调一点,作为软件工程师经常会不自觉地忽视硬件对程序性能的影响,因此在设计方案时候考察下硬件和问题场景的关系或许能开拓我们解决问题的思路。   上面的问题按本篇开篇的痛点总结的思路总结下的话,那么就是如下:     痛点四:当数据库所在服务器的硬件有很大提升时候,我们可以优先考虑是否可以通过提升硬件性能的手段来提升数据库的性能。   在本系列 的第一篇里,我讲到根据http无状态的特点,我们可以通过剥离web服务器的状态性主要是session的功能,那么当网站负载增大我们可以通过增加 web服务器的方式扩容网站的并发能力。其实不管是读写分离方案,垂直拆分方案还是水平拆分方案细细体会下,它们也跟水平扩展web服务的方式有类似之 处,这个类似之处也就是通过增加新的服务来扩展整个存储的性能,那么新的问题来了,前面的三种解决存储瓶颈的方案也能做到像web服务那样的水平扩展吗? 换句话说,当方案执行一段时间后,又出现了瓶颈问题,我们可以通过增加服务器就能解决新的问题吗?   要回答清 楚这个问题,我们首先要详细分析下web服务的水平扩展原理,web服务的水平扩展是基于http协议的无状态,http的无状态是指不同的http请求 之间不存在任何关联关系,因此如果后台有多个web服务处理http请求,每个web服务器都部署相同的web服务,那么不管那个web服务处理http 请求,结果都是等价的。这个原理如果平移到数据库,那么就是每个数据库操作落到任意一台数据库服务器都是等价的,那么这个等价就要求每个不同的物理数据库 都得存储相同的数据,这么一来就没法解决读写失衡,解决海量数据的问题了,当然这样做看起来似乎可以解决连接数的问题,但是面对写操作就麻烦了,因为写数 据时候我们必须保证两个数据库的数据同步问题,这就把问题变复杂了,所以web服务的水平扩展是不适用于数据库的。这也变相说明,分库分表的数据库本身就… Read More

关于大型网站技术演进的思考(八)–存储的瓶颈终篇(8)

在开始本篇主要内容前,我们一起看看下面的几张截图,首先是第一张图,如下图所示: 这是一家电商网站的首页,当我们第一次打开这个首页,网站会弹出一个强制性的对话框,让用户选择货物配送的地址,如果是淘宝和京东的话,那么这个选择配货地址的选项是在商品里,如下图是淘宝的选择配送地点: 下图是京东选择配货地点: 那么图一 跟京东和淘宝有什么区别呢?图一的电商强制用户选择地区后,那么我们在查询这个商品时候会因为地区不同,显示的查询结果会不一样,这个就和网站做国际化有 点像,不过网站国际化是切语言和语言相关的静态资源,但是电商这个地域的选择是和业务相关的,不同的地域查询结果是不相同的,这个选择地域的弹出框很像一 个路由器。相比之下,淘宝和京东把商品的配送和商品相关,那么我们在这些网站里查询商品时候,其实是按照全国查询的,全国不同的地方查询同一个条件所获得 到的结果是一致的。从业务角度而言,这说明第一个电商的业务没有全国铺开,就算是铺开了,地域的差异也影响到物流的问题,而淘宝和京东则正是一个全国意义 的大型电商网站了。   回到技术的角度,这两种不同的做法有没有可能还和技术问题有关了?今天我就来探讨下这个问题。   不管网站 大与小,一个网站肯定可以分为客户端、服务端和存储端,勾连这不同的组成部分是网络,网络是一种通讯设施,距离的远近会直接影响到网络传输的效率问题,如 是乎就出现了像CDN这样的技术,很多大型互联网公司还会在不同的城市建立机房,这些手段的目的就是在解决距离对网络传输效率的影响,但是当这种就近解决 问题的方案落到存储层的时候,问题就来了。上篇里我说道web服务的水平扩展问题,这种水平扩展是基于一种无状态性的原理设计的,但是到了存储层我们不管 怎么拆分它,它都很难消除状态的问题,也就是存储层有状态性是它的天然属性。特别是碰到一个竞争性的存储资源时候,这种状态性会变得非常顽固,例如商品的 库存问题,如果我们把库存数据对等的平移到不同地域的数据中心,那么如何保证不同地方的库存信息总是准确的,这就成为了难题。这种问题放在一个小国家不是 什么问题,但是放到地大物博的中国那就很成问题了。所以存储是这种就近方案的短板了。   我曾了解 到中国一家大型信息企业在设计它们第一代系统时候,就考虑到了这种地域性差异对系统设计的影响,它们的第一代系统在存储层这块就设计成了一个双核系统,什 么叫做存储层的双核系统了?它们的做法是在北京和上海分别建立两个数据中心,系统的存储层分别部署在北京的数据中心和上海的数据中心,两个数据中心是等价 的,那么中国北部的交易就走北京数据中心,中国南部的交易就走上海的数据中心。但是系统上线后,发现这种双核设计方案成为了整个系统的梦魇了,这个梦魇的 最核心的问题就是数据的同步问题,因为该企业是一个全国性业务的企业,因此有大量交易需要南北数据中心同步完数据后才能正常完成,但是想从北京和上海同步 数据的效率是异常的低效,我曾经看过一份资料,里面说有机构做了一个测试,当两个数据中心的距离超过了80公里,那么网络的延迟性基本是无法忍受的,当然 不差钱的企业可以专门铺设专线来连接两个数据中心,这种专线的成本高的吓人,我曾听人说就在上海,如果铺专线从浦东到浦西,那么这条专线基本是用人民币铺 就的,更何况是从北京到上海铺专线,就算企业不差这些钱延迟性也严重影响了企业业务的发展。除了延迟性外,通过网络大规模传输数据,数据的可靠性是很难保 证的,也就是网络传输时候经常没有道理的丢包,这就造成了很多重复性传输,使得同步数据的效率更加的低效。   因为存储 层这种双核设计缺陷,该企业马上从事了二代系统的设计和开发,而这个二代系统核心业务就是解决这个存储层的双核问题。那到底该怎么解决了?把双核变成单 核,既然两个数据中心这么麻烦,那我们就搞一个数据中心算了,既省钱有没那么多麻烦事情,这个肯定不是解决问题的正确思路了,双核设计的出发点是非常有现 实意义和价值的,最后该公司使用了一个新的方案替代双核,这个方案称之为主备方案,存储层任然部署到两个数据中心,到了业务运行阶段,一个数据中心为主, 一个数据中心为辅,不过这个主备方案绝不是通常意义的数据备份方案,他其实是吸收了单核和双核方案的优点,同时尽量避免单核和双核的缺点,那么这点上这个 主备方案是如何做到的呢?   首先我们 还是要把系统业务交易分下类,系统有些交易对于实时性啊,数据的正确性啊要求非常高,那么这样的业务场景使用单核存储系统比较合适,一个业务系统不可能全 是这样的实时性交易,也有一些交易对实时性要求比较差,当然我们还是得要考察下这种交易对于延时容忍度,具体就是一般延时多久用户是可以接受的,这点非常… Read More

jQuery实现的测试答题功能

有时在网页中要加入一个在线测试功能,例如在线调查,在线测试各类知识等应用,这类应用需要用到很多前后端技能。今天我给大家分享一个基于jQuery的前端应用——测试答题功能。 查看演示 下载源码 HTML 首先载入jquery库文件和quiz.js以及所需的CSS样式文件styles.css。 <script src="jquery.js"></script> <script src="quiz.js"></script> <link rel="stylesheet" href="styles.css" /> 然后在需要放置测试题的位置加入div#quiz-container。 <div id="quiz-container"></div> jQuery 首先,我们定义题目和答案选项,question是题目,answers是答案选项,correctAnswer是正确答案。可以看出定义的init是一个json数据格式。 var init={'questions':[{'question':'jQuery是什么?','answers':['JavaScript库','CSS库','PHP框架','以上都不是'],'correctAnswer':1},{'question':'找出不同类的一项?','answers':['写字台','沙发','电视','桌布'],'correctAnswer':3},{'question':'国土面积最大的国家是:','answers':['美国','中国','俄罗斯','加拿大'],'correctAnswer':3},{'question':'月亮距离地球多远?','answers':['18万公里','38万公里','100万公里','180万公里'],'correctAnswer':2}]}; 接下来,我们直接调用quiz.js提供的插件方法,然后打开页面是不是可以看到已经在页面上生成了一个在线测试项目。 $(function(){ $('#quiz-container').jquizzy({ questions: init.questions }); }); 那么,要修改定制测试题样式布局,可以到quiz.js和styles.css两文件中做适当修改。 疑问 到这里,细心的朋友就会发现,问题来了: 1、直接将题目的正确答案标记在js代码中,是不是不安全?正规的测试项目答案是不是应该在后台判断,以免有人查看源代码直接获取正确答案。 2、如何与后台交互?比如测试答题前先验证身份,答题完后将结果发送给后台。 我想说的是,这是一个前端代码演示项目,真正的应用答案是不会出现在前端代码中的;quiz.js其实已经有与后台ajax交互的接口,我们会在后面的文章中做详细介绍,文章标题我已经想好了:如何使用jQuery+PHP+MySQL来实现一个在线测试项目,敬请关注。 来源于helloweba.com并保留原文链接:http://www.helloweba.com/view-blog-296.html 下载附件:quiz… Read More

HTML5获取地理位置定位信息

HTML5提供了地理位置定位功能(Geolocation API),能确定用户位置,我们可以借助HTML5的该特性开发基于地理位置信息的应用。本文结合实例给大家分享如何使用HTML5,借助百度、谷歌地图接口来获取用户准确的地理位置信息。 查看演示 下载源码 如何使用HTML5地理位置定位功能 定位功能(Geolocation)是HTML5的新特性,因此只有在支持HTML5的现代浏览器上运行,特别是手持设备如iphone,地理定位 更加精确。首先我们要检测用户设备浏览器是否支持地理定位,如果支持则获取地理信息。注意这个特性可能侵犯用户的隐私,除非用户同意,否则用户位置信息是 不可用的,所以我们在访问该应用时会提示是否允许地理定位,我们当然选择允许即可。 function getLocation(){ if (navigator.geolocation){ navigator.geolocation.getCurrentPosition(showPosition,showError); }else{ alert("浏览器不支持地理定位。"); } } 上面的代码可以知道,如果用户设备支持地理定位,则运行 getCurrentPosition() 方法。如果getCurrentPosition()运行成功,则向参数showPosition中规定的函数返回一个coordinates对 象,getCurrentPosition() 方法的第二个参数showError用于处理错误,它规定当获取用户位置失败时运行的函数。 我们先来看函数showError(),它规定获取用户地理位置失败时的一些错误代码处理方式: function showError(error){ switch(error.code) {

PHP+jQuery+MySql实现红蓝投票功能

这是一个非常实用的投票实例,应用在双方观点对抗投票场景。用户可以选择支持代表自己观点的一方进行投票,本文以红蓝双方投票为例,通过前后台交互,直观展示红蓝双方投票数和所占比例,应用非常广泛。 查看演示 下载源码 本文是一篇综合知识应用类文章,需要您具备PHP、jQuery、MySQL以及html和css方面的基本知识。本文在《PHP+MySql+jQuery实现的“顶”和“踩”投票功能》一文基础上做了适当改进,共用了数据表,您可以先点击了解这篇文章。 HTML 我们需要在页面中展示红蓝双方的观点,以及对应的投票数和比例,以及用于投票交互的手型图片,本例以#red和#blue分别表示红蓝双 方。.redhand和.bluehand用来做手型投票按钮,.redbar和.bluebar展示红蓝双方比例调,#red_num 和#blue_num展示双方投票数。 <div class="vote"> <div class="votetitle">您对Helloweba提供的文章的看法?</div> <div class="votetxt">非常实用<span>完全看不懂</span></div> <div class="red" id="red"> <div class="redhand"></div> <div class="redbar" id="red_bar">

HTML5 Geolocation API : 实时跟踪应用

getCurrentPosition与watchPosition   watchPosition会监视你的移动,并在位置改变时向你报告位置。watchPPosition方法看上去确实与getCurrentPosition方法很像,不过行为稍有不同,每次位置比阿奴啊时会重复调用你的成功处理程序 watchPosition调用步骤: 你的应用watchPosition,传入一个成功处理函数 watchPosition在后台不断监视你的位置 你的位置改变时,watchPostion调用成功处理函数来报告你的新位置 watchPostion继续监视你的位置(并向成功处理程序报告),直至你调用clearWatch将它清除   HTML代码   我们已HTML5COL学院中级课程相关章节的课程为基础,再向HTML增加几个按钮,从而能开始和结束跟踪你的位置;我们设置开始按钮是因为用户不想一直被跟踪,他们通常希望对此有些控制;设置结束按钮,是考虑到对于移动设备来说,不停检查用户的位置是一个相当耗费电的操作,如果一直打开跟踪,会严重影响电池寿命: 注: 实时跟踪用户可能非常耗电。一定要为用户提供信息,指出日前正在跟踪,另外还要提供相应的一些控件 <!DOCTYPE html> <html> <head> <meta='keywords' content='HTML5COL学院,HTML5COL,CSS3,HTML5COL,编码社区'> <script> <!--script代码,见HTML5COL学院的应用课程:Geolocation API应用:计算两地距离---> </script> </head> <body> <form> <input type="button" id="watch" value="Watch%20me"> <input type="button" id="clearWatch" value="Clear%20watch"> </form> <p>position:</p> <div id="location" >… Read More

HTML5 Geolocation API应用:计算两地距离

找出HTML5COL学院的位置   在HTML5COL学院的前面几个章节中我们已经对HTML5 Geolocation API有了一定认识,接下来我们要对位置做些更有意思的处理;看看你与我们HTML5COL学院的办公室秘密位置相距多远。为此我们需要HTML5COL学院的坐标,而且需要知道如何计算两个坐标之间的距离。   增加一个<div>   首先,在HTML中增加一个 <div>: <!DOCTYPE html> <html> <head> <meta='keywords' content='HTML5COL学院,HTML5COL,CSS3,HTML5COL,编码社区'> </head> <body> <div id="location"> Your location will go here. </div> <div id="distance"> Distance from HTML5COL Office will go here. </div> </body> </html> 计算距离  … Read More

利用行动装置GPS定位寻找临近地点

早先已展示过,在网页内嵌Google地图、将地址转换为经纬度座标、在地图上显示自订地标图示等技巧,最后来个综合应用当作期末考。本次的练习题是”依使用者所在位置,找出距离最近的五个台北市消防分队”。 简单整理值得留意的技术细节: 在网页嵌入Google地图并放上自订标示点(Marker)的做法,可参考笔记-网页内嵌Google地图与地理位置模拟一文。 由市政府网站取得台北市消防分队地址,透过地理编码算出经纬度座标,可参考Google Maps API地址转换一文。而在本次范例中,我们预先将查到的经纬度数字一并写入CSV档中,不必每次重新查询。 要计算两个经纬度座标间的直线距离,Haversine公式是最常用的演算法,简单来说,就是把地球当成一个圆球,用球体表面任两点到圆心所形成的夹角,加上一堆Sin , Cos推算沿球体表面连接两点的弧线长度。依据英国学者研究指出,思考过度复杂数学公式可能会对中老年人的神经中枢有负面影响,为求养生保健,在此直接引用公式,对于数学细节就不再深究… HTML5世代的浏览器(IE要IE9+)多能支援地理资讯功能,可整合行动装置(手机、平版)的GPS取得使用者当时所在地理位置(存取前会弹出确认视窗征求使用者同意),如此我们便可依使用者所在位置提供不同资讯,例如:列出临近的商店、餐厅或服务据点…等等。要透过Javascript存取使用者的地理资讯,可使用Geolocation API。 使用者所在位置及各消防分队的经纬度都有了,便可利用Haversine公式算出各分队与目前位置的直线距离作为远近参考。(不考虑路线规划、交通状况等因素,那是导航软体或霹雳车RD该烦恼的事,为了好玩写程式没必要把自己逼上绝路XD至于有心挑战的朋友,Google Maps也有路线规划API,倒是可以参考) Javascript的Array.sort(compareFunction),提供了类似LINQ OrderBy(o => o.prop)的简便做法,让我惊喜了一下。原本以为要花点心思处理,没想到只用两行就搞定依距离远近排序的需求。 前篇文章介绍的动态文字图档权充Marker Icon派上用场,直接用分队名称告示牌当标示图,最近的前五名用鲜红底,其余用暗红底,一目了然,酷!! 程式范例如下,请享用: <!DOCTYPE html> <html> <head runat="server"> <title>Geocoding Test</title> <script type='text/javascript' src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script> <script src="https://maps.google.com/maps/api/js?sensor=true"></script> <script src="DynaMarkerIcon.js" type="text/javascript"></script> <meta name="viewport"

利用HTML5 Canvas动态产生文字图示

前几篇Google Map API文章,一直有用到Google地图加上Marker的做法。(即下图的红色大头针图案) 不过,若全部的标示点用一样的图示,会显得无趣且容易混淆(如下图所示),虽然将滑鼠移到标示图案上方会显示名称,在使用者体验上总觉得还有改善空间。 事实上,Google Maps API在新增地标时,是可以自订图示的。MarkerOptions提供icon参数可指定图档URL,另外有shadow参点可指定阴影图档的URL,以取代预设的黑点红色大头针图示。 只是有个小问题,在消防分队位置显示范例中,台北市共有44个消防分队,即使能自订图示,可能也只是用44台消防车换掉44根大头针,对于识别度并没有太多提升,除非我们能预先制作44个刻有消防分队名称的图档,再一一对应到44个Marker使用… 身为程式魔人,44个图档用手工做是不被允许的,当然要自动产生才不会被人耻笑。我第一个想到是用ASP.NET动态产生图档的技巧,不过再转念一想,何不用HTML5的Canvas来实做,完全在Client解决? (IE6/7/8:那我们怎么办?念你们曾纵横江湖多年,也算时代英雄,你们自尽吧!) HTML5 Canvas要即时产生图档不是问题,再配合Canvas.toDataURL()就可取代图档案URL,作为MarkerOptions.icon参数的设定值,就能达到当场动态产生Marker图示的目的。 我试写了一个小函数,用Canvas作图,再用toDataURL()输出作为<img>的src来源: <!DOCTYPE html> <html> <head runat="server"> <title>動態Canvas圖標</title> <script type='text/javascript' src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script> <style> body,input { font-size: 9pt; }

Google Maps API地址转换

将地址转换成地理座标的程序被称为地理编码 (Geocoding),Google Maps API亦支援地理编码服务(注意:有每天查询次数不可超过2,500次的限制,申请Google Maps API Premier可以提高到100,000次) ,呼叫方法很简单,使用URL “http: //maps.googleapis.com/maps/api/geocode/json?address=要转换的地址&sensor=ture或false”,便可得到一份JSON格式的地址座标资讯,address参数除了完整地址,也可以输入一般性地名或片段地址,另外也能指定传回XML格式、使用语系、检视范围(优先列出该范围内符合结果,最经典的例子是在台湾查询”中正路”、”中山路”之类的菜市场路名,要指定县市范围才能找对目标)…等等,完整参数说明可参见API文件。 先用浏览器来个简单测试,在网址输入http://maps.googleapis.com/maps/api/geocode/json?address=%E5%8F%B0%E5%8C%97%E5%B7%BF %E5%85%89%E5%BE%A9%E5%8D%97%E8%B7%AF100%E8%99%9F&sensor=false (地址中文部分经encodeURI()编码),会得到以下结果: { "results" : [ { "address_components" : [ {