@[toc]
概述
随着业务的不断丰富,高并发和海量数据的处理日益成为影响系统性能的重要问题。下面将提供一些针对并发问题和海量数据处理的解决方案。
海量数据的解决方案:
- 缓存
- 页面静态化
- 数据库优化
- 分离活跃数据
- 批量读取和延迟修改
- 读写分离
- 分布式数据库
- NoSQL和Hadoop
高并发的解决方案:
- 应用和静态资源分离
- 页面缓存
- 集群与分布式
- 反向代理
- CDN
- 底层的优化
海量数据的解决方案
缓存
数据量大最直接的解决方案就是使用缓存,缓存就是将从数据库获取的结果暂时保存起来,在下次使用的时候无需从数据库获取,这样可以大大降低数据库的压力。
缓存的使用方式有两种,一是通过程序直接保存到内存中,二是使用缓存框架。
程序直接操作缓存主要使用Map,尤其是CurrentHashMap。
常用的缓存框架有Ehcache、Memcache、Redis,等等。
缓存的适用情况
缓存主要用于数据变化不是很频繁的情况,如果缓存数据的实时性要求较高,那么会造成缓存数据与真实数据不一致的情况。
页面静态化
页面静态化是将程序最后生成的页面保存起来,页面静态化后就不需要再次调用生成页面了。
这样不仅不需要再查询数据库了,连程序处理都省了。
- 模板技术
页面静态化可以在程序中使用模板技术生成。常用的模板引擎有:FreeMarker,Thymeleaf,Velocity,等等。 - 缓存服务器
可以使用缓存服务器,在应用服务器的上一层缓存生成的页面,可以使用代理缓存服务器Squid,Nginx也提供了相应的功能。
数据库优化
数据库优化可以在不增加硬件成本的前提下提高处理效率,常用的数据库优化方法有:表结构优化、SQL语句优化、分区和分表、索引优化、使用存储过程代替直接操作等,另外有时候合理使用冗余也能获得非常好的效果。
表结构优化
表结构优化是数据库中最基础也是最重要的,如果表结构优化得不合理,就可能导致严重的性能问题,具体怎么设计更合理也没有固定不变的准则,需要根据实际情况具体处理。
表结构优化建议
-
数据类型选择
数据库操作中最为耗时的操作就是IO处理,大部分数据库操作90%以上的时间都花在了IO读写上面。所以尽可能减少IO 读写量,可以在很大程度上提高数据库操作的性能。
我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题:
-
(1)数字类型:
非万不得已不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。对于整数的存储,在数据量较大的情况下,建议区分开 TINYINT / INT / BIGINT 的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。
-
(2)字符类型:
-
非万不得已不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。
-
(3)时间类型:
尽量使用TIMESTAMP类型,因为其存储空间只需要 DATETIME 类型的一半。对于只需要精确到某一天的数据类型,建议使用DATE类型,因为他的存储空间只需要3个字节,比TIMESTAMP还少。不建议通过INT类型类存储一个unix timestamp 的值,因为这太不直观,会给维护带来不必要的麻烦,同时还不会带来任何好处。
-
4.ENUM & SET:
对于状态字段,可以尝试使用 ENUM 来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。SET是一个字符串对象,可以有零或多个值,其值来自表创建时规定的允许的一列值。指定包括多个SET成员的SET列值时各成员之间用逗号(‘,’)间隔开。所以SET成员值本身不能包含逗号。
SET最多可以有64个不同的成员。当创建表时,SET成员值的尾部空格将自动被删除。当检索时,保存在SET列的值使用列定义中所使用的大小写来显示。请注意可以为SET列分配字符集和校对规则。对于二进制或大小写敏感的校对规则,当为列分配值时应考虑大小写。
MySQL用数字保存SET值,所保存值的低阶位对应第1个SET成员。如果在数值上下文中检索一个SET值,检索值的位置对应组成列值的SET成员。例如,你可以这样从一个SET列检索数值值:
mysql> SELECT set_col+0 FROM tbl_name;
使用实例:[sql]
CREATE TABLE `TestSet` (
`Id` int(4) NOT NULL AUTO_INCREMENT,
`set1` set('ABC','1111','2222','XXX') DEFAULT NULL,
PRIMARY KEY (`Id`)
)ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `testset` VALUES ('1','a');
INSERT INTO `testset` VALUES ('2', 'ABC');
INSERT INTO `testset` VALUES ('3', 'ABCD');
#ABC可以存入,a和ABCD无法存入set1字段。
ENUM是一个字符串对象,其值来自表创建时在列规定中显式枚举的一列值。
在某些情况下,ENUM值也可以为空字符串('')或NULL。如果你将一个非法值插入ENUM(也就是说,允许的值列之外的字符串),将插入空字符串以作为特殊错误值。该字符串与“普通”空字符串不同,该字符串有数值值0。如果将ENUM列声明为允许NULL,NULL值则为该列的一个有效值,并且默认值为NULL。如果ENUM列被声明为NOT NULL,其默认值为允许的值列的第1个元素。每个枚举值有一个索引,来自列规定的允许的值列中的值从1开始编号。空字符串错误值的索引值是0。 NULL值的索引是NULL。这说明你可以使用下面的SELECT语句来找出分配了非法ENUM值的行
· mysql> SELECT * FROM tbl_name WHERE enum_col=0;
枚举最多可以有65,535个元素。
CREATE TABLE `TestEnum` (
`Id` INT(4) NOT NULL AUTO_INCREMENT,
`enum` ENUM('ABC','1111','2222','XXX') DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `TestEnum` VALUES ('1','a');
INSERT INTO `TestEnum` VALUES ('2', 'ABC');
INSERT INTO `TestEnum` VALUES ('3', 'ABCD');
-
(5)BLOB类型:
强烈反对在数据库中存放 BLOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。
BLOB (binary large object),二进制大对象,是一个可以存储二进制文件的容器。在计算机中,BLOB常常是数据库中用来存储二进制文件的字段类型。BLOB是一个大文件,典型的BLOB是一张图片或一个声音文件,由于它们的尺寸,必须使用特殊的方式来处理(例如:上传、下载或者存放到一个数据库)。
-
字符编码
字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。
1.纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间
2.如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费
3.MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率
-
适当拆分
有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。当我们的表中存在类似于 TEXT 或者是很大的VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理IO次数,也能大大提高内存中的缓存命中率。
-
适度冗余
为什么我们要冗余?这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?
确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做:
被频繁引用且只能通过 Join 2张(或者更多)大表的方式才能得到的独立小字段
这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新
-
尽量使用NOT NULL
NULL 类型比较特殊,SQL难优化。
虽然 MySQL NULL类型和 Oracle 的NULL 有差异,会进入索引中,但如果是一个组合索引,那么这个NULL 类型的字段会极大影响整个索引的效率。此外,NULL 在索引中的处理也是特殊的,也会占用额外的存放空间。
很多人觉得 NULL会节省一些空间,所以尽量让NULL来达到节省IO的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。所以尽量确保DEFAULT值不是 NULL,也是一个很好的表结构设计优化习惯。
SQL语句优化
SQL语句优化也是非常重要的,基础的SQL优化是语法层面的优化,不过更重要的是处理逻辑的优化,这也需要根据实际情况具体处理,而且要和索引缓存等配合使用。
不过SQL优化有一个通用的做法就是,首先要将涉及大数据的业务的SQL语句执行时间详细记录下来,其次通过仔细分析日志(同一条语句对不同条件的执行时间也可能不同,这点也需要仔细分析)找出需要优化的语句和其中的问题,然后再有的放矢地优化,而不是不分重点对每条语句都花同样的时间和精力优化。
优化举例:
- 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
最好不要给数据库留NULL,尽可能的使用NOT NULL填充数据库.
备注、描述、评论之类的可以设置为NULL,其他的,最好不要使用NULL。
不要以为NULL不需要空间,比如:char(100)型,在字段建立时,空间就固定了,不管是否插入值(NULL也包含在内),都是占用100个字符的空间的,如果是varchar这样的变长字段,null不占用空间。
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num = 0
- 应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
- 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。
如:
SELECT * FROM b2buser u WHERE u.userId
= '1' OR u.username
='test';
可以这样查询:
SELECT * FROM b2buser u WHERE u.userId
= '1'
UNION ALL
SELECT * FROM b2buser u WHERE u.username
='test';
UNION 和 UNION ALL 操作符
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。UNION ALL 命令和 UNION 命令几乎是等效的,不过 UNION ALL 命令会列出所有的值。
- in 和 not in 也要慎用,否则会导致全表扫描。
如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
exists : 强调的是是否返回结果集,不要求知道返回什么;exists引导的子句有结果集返回,那么exists这个条件就算成立了,注意返回的字段始终为1,如果改成“select 2 from a where ...”,那么返回的字段就是2,这个数字没有意义。
而 exists 与 in 最大的区别在于in引导的子句只能返回一个字段,exists子句是允许f返回多个字段的。
如:
SELECT c.chainCode
FROMchain
c WHERE c.chainId
IN (SELECT ci.chainId
FROMchain_i18n
ci);SELECT c.
chainCode
FROMchain
c WHERE EXISTS (SELECT 1,2,3 FROMchain_i18n
ci WHERE ci.chainId
=c.chainId
AND ci.chainName
= c.chainCode
);
其中EXISTS后面的查询返回的1,2,3没有实际意义。
- 下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%’
若要提高效率,可以考虑全文检索。
- 如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。
如下面语句将进行全表扫描:
select id from t where num = @num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num = @num
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。
如:
select id from t where num/2 = 100
应改为:
select id from t where num = 100*2
- 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3) = ’abc’ -–name以abc开头的id
select id from t where datediff(day,createdate,’2005-11-30′) = 0 -–‘2005-11-30’ --生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'
- 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
- 不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
- Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
- 对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。
- select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
- 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
- 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
- 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
- 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
- 任何地方都不要使用 select from t ,用具体的字段列表代替“”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
- 添加索引
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。
1.添加PRIMARY KEY(主键索引)
mysql>ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
2.添加UNIQUE(唯一索引)
mysql>ALTER TABLE `table_name` ADD UNIQUE (
`column`
)
3.添加INDEX(普通索引)
mysql>ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
4.添加FULLTEXT(全文索引)
mysql>ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
5.添加多列索引
mysql>ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
- 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
- explain显示了MySQL如何使用索引来处理select语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。
如:
EXPLAIN
SELECT * FROM b2buser u WHERE u.userId
= '1'
UNION ALL
SELECT * FROM b2buser u WHERE u.username
='test';
查询结果中的possible_keys提示使用哪个索引会在该表中找到行,keys 是MYSQL使用的索引,key_len是MYSQL使用的索引长度。
分区和分表
当数据量变多的时候,如果可以分区或者分表,那将起到非常好的效果。当一张表中的数据量变多的时候操作速度就慢了,所以很容易想到的就是将数据分到多个表中保存,但是这么做之后操作起来比较麻烦,想操作(增删改查)一个数据还需要先找到对应的表,如果涉及多个表还得跨表操作。
其实在常用的数据库中可以不分表而达到跟分表类似的效果,那就是分区。分区就是将一张表中的数据按照一定的规则分到不同的区来保存,这样在查询数据时如果数据的范围在同一个区内那么可以只对一个区的数据进行操作,这样操作的数据量更少,速度更快,而且这种方法对程序是透明的,程序不需要做任何改动。
索引优化
索引的大致原理是在数据发生变化(增删改)的时候就预先按指定字段的顺序排列后保存到一个类似表的结构中,这样在查找索引字段为条件的记录时就可以很快地从索引中找到对应记录的指针并从表中获取到记录,这样速度就快多了。
不过索引也是一把双刃剑,它在提高查询速度的同时也降低了增删改的速度,因为每次数据的变化都需要更新相应的索引。不过合理使用索引对提升查询速度的效果非常明显,所以对哪些字段使用索引、使用什么类型的索引都需要仔细琢磨,并且最好
再做一些测试。
使用存储过程代替直接操作
在操作过程复杂而且调用频率高的业务中,可以通过使用存储过程代替直接操作来提高效率,因为存储过程只需要编译一次,而且可以在一个存储过程里面做一些复杂的操作。
分离活跃数据
虽然有些数据总数据量非常大,但是活跃数据并不多,这种情况就可以将活跃数据单独保存起来从而提高处理效率。比如,对网站来说,用户很多时候就是这种数据,注册用户很多,但是活跃用户却不多,而不活跃的用户中有的偶尔也会登录网站,因此还不能删除。这时就可以通过一个定期处理的任务将不活跃的用户转移
到别的数据表中,在主要操作的数据表中只保存活跃用户,查询时先从默认表中查找,如果找不到再从不活跃用户表中查找,这样就可以提高查询的效率。判断活跃用户可以通过最近登录时间,也可以通过指定时间段内登录次数。除了用户外还有很多这种类型的数据,如一个网站上的文章(特别是新闻类的)、企业业务系统中按时间记录的数据等。
批量读取和延迟修改
批量读取和延迟修改的原理是通过减少操作的次数来提高效率,如果使用得恰当,效率将会呈数量级提升。
批量读取是将多次查询合并到一次中进行,比如,在一个业务系统中需要批量导入工人信息,在导入前需要检查工人的编码是否已经在数据库中、工人对应的部门信息是否正确(在部门表中是否存在)、工人的工种信息在工种表中是否存在等,如果每保存一条记录都查询一次数据库,那么对每个需要检查的字段,都需要查询与要保存的记录条数相同次数的数据库,这时可以先将所有要保存的数据的相应字段读取到一个变量中,然后使用in语句统一查询一次数据库,这样就可以将n(要保存记录的条数)次查询变为一次查询了。
除了这种对同一个请求中的数据批量读取,在高并发的情况下还可以将多个请求的查询合并到一次进行,如将3秒或5秒内的所有请求合并到一起统一查询一次数据库,这样就可以有效减少查询数据库的次数,这种类型可以用异步请求来处理。
延迟修改主要针对高并发且频繁修改(包括新增)的数据。
读写分离
读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。
分布式数据库
分布式数据库是将不同的表存放到不同的数据库中然后再放到不同的服务器。这样在处理请求时,如果需要调用多个表,则可以让多台服务器同时处理,从而提高处理速度。
NoSQL和Hadoop
NoSQL是近年来发展非常迅速的一项技术,它的核心就是非结构化。我们一般使用的数据库(SQL数据库)都是需要先将表的结构定义出来,一个表有几个字段,每个字段各是什么类型,然后才能往里面按照相应的类型保存数据,而且按照数据库范式的规定,一个字段只能保存单一的信息,不可以包括多层内容,这就对使用的灵活性带来了很大的制约,NoSQL就是突破了这些条条框框,可以非常灵活地进行操作,另外因为NoSQL通过多个块存储数据的特点,其操作大数据的速度也非常快,这些特性正是现在的互联网程序最需要的,所以NoSQL发展得非常快。现在NoSQL主要使用在互联网的程序中,在企业业务系统中使用的还不多,而且现在NoSQL还不是很成熟,但由于灵活和高效的特性,NoSQL发展的前景是非常好的。
Hadoop是专门针对大数据处理的一套框架,Hadoop是一个开发和运行处理大规模数据的软件平台,是Appach的一个用java语言实现开源软件框架,实现在大量计算机组成的集群中对海量数据进行分布式计算.
高并发的解决方案
应用和静态资源分离
刚开始的时候应用和静态资源是保存在一起的,当并发量达到一定程度时就需要将静态资源保存到专门的服务器中,静态资源主要包括图片、视频、js、css和一些资源文件等,这些文件因为没有状态,所以分离比较简单,直接存放到相应的服务器就可以了,一般会通过专门的域名去访问。
页面缓存
页面缓存是将应用生成的页面缓存起来,这样就不需要每次都重新生成页面了,从而可以节省大量CPU资源,如果将缓存的页面放到内存中速度就更快了。如果使用了Nginx服务器就可以使用它自带的缓存功能,当然也可以使用专门的Squid服务器。页面缓存的默认失效机制一般是按缓存时间处理的,当然也可以在修改数据之后手动让相应缓存失效。
有部分经常变化的数据的页面怎么使用页面缓存呢?可以先缓存页面,再用ajax修改变化的部分。
集群与分布式
集群和分布式处理都是使用多台服务器进行处理的,集群是每台服务器都具有相同的功能,处理请求时调用哪台服务器都可以,主要起分流的作用,分布式是将不同的业务放到不同的服务器中,处理一个请求可能需要用到多台服务器,这样就可以提高一个请求的处理速度,而且集群和分布式也可以同时使用。
集群有两个方式:一种是静态资源集群。另一种是应用程序集群。
静态资源集群比较简单,而应用程序集群就有点复杂了。因为应用程序在处理过程中可能会使用到一些缓存的数据,如果集群就需要同步这些数据,其中最重要的就是Session,Session同步也是应用程序集群中非常核心的一个问题。
Session同步有两种处理方式:一种是在Session发生变化后自动同步到其他服务器,另外一种方式是用一个程序统一管理Session。所有集群的服务器都使用同一个Session,Tomcat默认使用的就是第一种方式,通过简单的配置就可以实现,第二种方式可以使用专门的缓存程序来管理缓存如Memcached、Redis等。
反向代理
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
反向代理服务器和代理服务器的区别
代理服务器的作用是代我们获取想要的资源然后将结果返回给我们,所要获取的资源是我们主动告诉代理服务器的,比如,我们想访问Facebook,但是直接访问不了,这时就可以让代理服务器访问,然后将结果返回给我们。
反向代理服务器是我们正常访问一台服务器的时候,服务器自己调用了别的服务器资源返回结果给我们,我们自己并不知道。
代理服务器是我们主动使用的,是为我们服务的,它不需要有自己的域名;反向代理服务器是服务器自己使用的,我们并不知道,它有自己的域名,我们访问它跟访问正常的网址没有任何区别。
反向代理服务器可以和实际处理请求的服务器在同一台主机上,而且一台反向代理服务器也可以访问多台实际处理请求的服务器。反向代理服务器主要有三个作用:
①可以作为前端服务器跟实际处理请求的服务器(如Tomcat)集成;
②可以用做负载均衡;
③转发请求,比如,可以将不同类型的资源请求转发到不同的服务器去处理,可以将动态资源转发到Tomcat、Php等动态程序而将图片等静态资源的请求转发到静态资源的服务器,另外也可以在url地址结构发生变化后将新地址转发到原来的旧地址上。
CDN
CDN其实是一种特殊的集群页面缓存服务器,它和普通集群的多台页面缓存服务器比主要是它存放的位置和分配请求的方式有点特殊。
CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。
底层的优化
前面讲到的所有架构都是建立在最前面介绍的基础架构之上的,而且很多地方都需要通过网络传输数据,如果可以加快网络传输的速度,那将会让整个系统从根本上得到改善。网络传输数据都是按照各种协议进行的,不过协议并不是不可以改变,Google就迈出了这一步,它制定了Quic、Spdy等协议来传输数据,Quic比TCP效率高而且比UDP安全,Spdy协议在现有HTTP协议的基础上增加了很多新特性,提高了传输的效率,不过有些特性已经包含到了HTTP/2协议中,而且Google也已经放弃了Spdy而使用HTTP/2了。