目录
架构演进
单机架构
简单来说就是只有一台服务器,这个服务器用来负责所有的工作 ;常用作访问量很少,没有对服务器的性能、安全等提出很⾼的要求,⽽且系统架构简单,⽆需专业的运维团队,所以选择单机架构是合适的。
例:流量很少的一个电商网站
千万不要小瞧这个简单的单机架构,对于绝大部分公司都是这种单机架构;但是随着用户量和业务需求的日渐增长,一个服务器的资源是有限的例如:CPU、内存、硬盘等各种资源;可能在某一时刻我们的服务器收到无法负载的请求时,可能就会导致某个硬件资源不够用了,无论哪方面的资源不够用了,都可能会导致服务器处理请求的时间变长,甚至出错进一步导致用户体验感不好。如果我们真遇上面的情况我们可以进行开源(简单粗暴,增加更多的硬件资源)和节流(软件上进行优化,比较难,对程序员要求比较高);无论怎样进行增加硬件资源,服务器上一个主板上面可以添加的硬件资源也是有限的。一台主机扩展到极限了,但还是不够,我们只能引入第多台主机了;一旦引入多台主机,我们就可以将这个系统成为"分布式系统"。
应用数据分离架构
和之前架构的主要区别在于将数据库服务独⽴部署在同⼀个数据中心的其他服务器上,应⽤服务通过网络访问数据。
由于应用服务器里面可能会包含很多的业务逻辑,可能会非常吃CPU和内存;而数据库服务器需要存储数据需要大量的硬盘空间,更快的访问速度;根据各种需求将整个单机架构系统分为两个协作的单机架构系统;
应用服务集群架构
那么突然有一天我们的系统收到用户的欢迎,并且出现了爆款,单台应⽤服务器已经⽆法满⾜需求了。我们的单机应⽤服务器⾸先遇到了瓶颈,一个服务器由于访问量过大CPU或者内存顶不住了,那么代表整个服务器就顶不住了,我们就可引入多个应用服务器解决上述问题;
但是引入了多个主机确实解决了上述问题,但是我们又有了新的问题如何将用户流量平均分配给这些服务器:这样我们又引入了一个新的组件------负载均衡: 为了解决⽤⼾流量向哪台应⽤服务器分发的问题,需要⼀个专⻔的系统组件做流量分发。实际中负载均衡不仅仅指的是⼯作在应⽤层的,甚⾄可能是其他的⽹络层之中。
常见的负载均衡算法
- Round-Robin 轮询算法。即⾮常公平地将请求依次分给不同的应⽤服务器。
- Weight-Round-Robin 轮询算法。为不同的服务器(⽐如性能不同)赋予不同的权重(weight),能者多劳。
- ⼀致哈希散列算法。通过计算⽤⼾的特征值(⽐如 IP 地址)得到哈希值,根据哈希结果做分发,优点是确保来⾃相同⽤⼾的请求总是被分给指定的服务器。也就是我们平时遇到的专项客⼾经理服务。
但是这样开来负载均衡器不就承担了所有请求了么;他也是个机器啊!虽然要求负载均衡器对于请求的承担能力远超于应用服务器;但是,遇到特别大量的请求负载均衡器也有可能扛不住。因此,对于负载均衡器我们也可以先进行开源和节流,当一个负载均衡器所能处理的请求达到极限时,我们可以引入多个负载均衡器;
读写分离/主从分离架构
上面提到,我们把⽤⼾的请求通过负载均衡分发到不同的应⽤服务器之后,可以并⾏处理了,
并且可以随着业务的增⻓,可以动态扩张服务器的数量来缓解压力。但是现在的架构⾥,⽆论扩展多少台服务器,这些请求最终都会从数据库读写数据,到⼀定程度之后,数据的压⼒称为系统承载能⼒的瓶颈点。我们可以像扩展应⽤服务器⼀样扩展数据库服务器么?答案是否定的,因为数据库服务有其特殊性:如果将数据分散到各台服务器之后,数据的⼀致性将⽆法得到保障。所谓数据的⼀致性,此处是指:针对同⼀个系统,⽆论何时何地,我们都应该看到⼀个始终维持统⼀的数据。想象⼀下,银⾏管理的账⼾⾦额,如果收到⼀笔转账之后,⼀份数据库的数据修改了,但另外的数据库没有修改,则⽤⼾得到的存款金额将是错误的。
实际的应用场景中,读的频率是比写频率要高的;我们采⽤的解决办法是这样的,保留⼀个主要的数据库作为写⼊数据库,其他的数据库作为从属数据库。从库的所有数据全部来⾃主库的数据,经过同步后,从库可以维护着与主库⼀致的数据。然后为了分担数据库的压⼒,我们可以将写数据请求全部交给主库处理,但读请求分散到各个从库中。由于⼤部分的系统中,读写请求都是不成⽐例的,例如 100 次读 1 次写,所以只要将读请求由各个从库分担之后,数据库的压⼒就没有那么⼤了。当然这个过程不是⽆代价的,主库到从库的数据同步其实是由时间成本的,但这个问题我们暂时不做进⼀步探讨。
冷热分离架构(引入缓存)
上面我们因为数据库的读写频率区别我们将读写分开,但是数据库天然有个问题,响应速度是慢的;随着访问量继续增加,发现业务中⼀些数据的读取频率远⼤于其他数据的读取频率。我们把这部分数据称为热点数据,与之相对应的是冷数据。针对热数据,为了提升其读取的响应时间,可以增加本地缓存,并在外部增加分布式缓存,缓存热⻔商品信息或热⻔商品的 html 页面等。通过缓存能把绝⼤多数请求在读写数据库前拦截掉,⼤⼤降低数据库压⼒。其中涉及的技术包括:使⽤memcached作为本地缓存,使⽤ Redis 作为分布式缓存,还会涉及缓存⼀致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。
垂直分库
引入分布式系统,不光要能够去应对更高的请求量(并发量),同时也要能够应对更大的数据量,因为一定会出现一个服务器不能够存储所有的数据;虽然一个服务器,存储数据量可以道道几十个TB,即使这样也可能会存不下;例如我们的短视频平台。一台主机存不下,我们就需要引入多台主机进行存储;使用数据库进行存取数据,我们就要对数据库进一步的拆分------分库或者分表;
- 分库:本来是一个数据库 服务器,这个数据库服务器上有多个数据库,现在引入多个数据库服务器,每个数据库服务器存储一个或一部分数据库;
- 分表:如果一个表特别大,大到一台主机都存不下,我们也可以对表进行拆分;
对于具体的分库或者分表,我们还是要结合具体的业务场景进行展开;
微服务架构
随着⼈员增加,业务发展,我们将业务分给不同的开发团队去维护,每个团队独⽴实现⾃⼰的微
服务,然后互相之间对数据的直接访问进⾏隔离,可以利⽤ Gateway、消息总线等技术,实现相互之间的调⽤关联。甚⾄可以把⼀些类似用户管理、安全管理、数据采集等业务提成公共服务。
认识Redis
The open source,in-memory data store used by millions of developers as a database , cache streaming engine, and message breker
这是官方的介绍,翻译过来为:开源的内存数据存储,被数百万开发人员用作数据库、缓存流引擎和消息中断器
我们可以将Redis理解为使用内存进行数据的存取;由于这种存储方式的优点被广泛用来当作数据库,因为MySQL最大的问题在于访问数据比较慢,但是很多互联网产品中,对于性能的要求是比较高的;而在内存中直接存储数据是非常快的,但是缺点是内存的存储空间是有限的。我们也可把Redis和MySQL结合起来使用,将经常需要访问的热数据存储在Redis中,将其他数据存储在MySQL中。
总结:Redis是在内存中存储数据的中间件,用作数据库进行数据缓存;主要通过"键值对"的方式来存储组织数据的("非关系型数据库"),在分布式系统中能够大展拳脚。
Redis的特性
速度快
正常情况下,Redis 执⾏命令的速度⾮常快,官⽅给出的数字是读写性能可以达到 10 万 / 秒,当
然这也取决于机器的性能,但这⾥先不讨论机器性能上的差异,只分析⼀下是什么造就了 Redis 如此之快,可以⼤概归纳为以下四点:
- Redis 的所有数据都是存放在内存中的,就比访问磁盘的数据库要快很多;
- Redis核心功能都是比较简单的逻辑
- Redis 使⽤了单线程,预防了多线程可能产⽣的竞争问题,减少了不必要线程之间的的竞争开销;
- 从网络角度上,Redis使用了IO多路服用的方式(epoll)
基于键值对的数据结构服务器
⼏乎所有的编程语⾔都提供了类似字典的功能,例如 C++ ⾥的 map、Java ⾥的 map、Python ⾥
的 dict 等,类似于这种组织数据的⽅式叫做基于键值对的⽅式,与很多键值对数据库不同的是,
Redis 中的值不仅可以是字符串,⽽且还可以是具体的数据结构,这样不仅能便于在许多应⽤场景的开发,同时也能提⾼开发效率。Redis 的全程是 REmote Dictionary Server,它主要提供了 5 种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(ordered set /zet),同时在字符串的基础之上演变出了位图(Bitmaps)和 HyperLogLog 两种神奇的 "数据结构",并且随着 LBS(Location Based Service,基于位置服务)的不断发展,Redis 3.2. 版本种加⼊有关 GEO(地理信息定位)的功能,总之在这些数据结构的帮助下,开发者可以开发出各种 "有意思" 的应⽤。
丰富的功能
除了 5 种数据结构,Redis 还提供了许多额外的功能:
- 提供了键过期功能,可以⽤来实现缓存。
- 提供了发布订阅功能,可以⽤来实现消息系统。
- ⽀持 Lua 脚本功能,可以利⽤ Lua 创造出新的 Redis 命令。
- 提供了简单的事务功能,能在⼀定程度上保证事务特性。
- 提供了流⽔线(Pipeline)功能,这样客⼾端能将⼀批命令⼀次性传到 Redis,减少了⽹络的开销。
简单稳定
Redis 的简单主要表现在三个⽅⾯。⾸先,Redis 的源码很少,早期版本的代码只有 2 万⾏左右,
3.0 版本以后由于添加了集群特性,代码增⾄ 5 万⾏左右,相对于很多 NoSQL 数据库来说代码量相对要少很多,也就意味着普通的开发和运维⼈员完全可以 "吃透" 它。其次,Redis 使⽤单线程模型,这样不仅使得 Redis 服务端处理模型变得简单,⽽且也使得客⼾端开发变得简单。最后,Redis 不需要依赖于操作系统中的类库(例如 Memcache 需要依赖 libevent 这样的系统类库),Redis ⾃⼰实现了事件处理的相关功能。
但与简单相对的是 Redis 具备相当的稳定性,在⼤量使⽤过程中,很少出现因为 Redis ⾃⾝ BUG
⽽导致宕掉的情况。
客户端语言多
Redis 提供了简单的 TCP 通信协议,很多编程语⾔可以很⽅便地接⼊到 Redis,并且由于 Redis 受到社区和各⼤公司的⼴泛认可,所以⽀持 Redis 的客⼾端语⾔也⾮常多,⼏乎涵盖了主流的编程语⾔,例如 C、C++、Java、PHP、Python、NodeJS 等,后续我们会对 Redis 的客⼾端使⽤做详细说明。
持久化
通常看,将数据放在内存中是不安全的,⼀旦发⽣断电或者机器故障,重要的数据可能就会丢
失,因此 Redis 提供了两种持久化⽅式:RDB 和 AOF,即可以⽤两种策略将内存的数据保存到硬盘中这样就保证了数据的可持久性,后续我们将对 Redis 的持久化进⾏详细说明。
主从复制
Redis 提供了复制功能,实现了多个相同数据的 Redis 副本(Replica),复制功能是分布式 Redis 的基础。后续我们会对 Redis 的复制功能进⾏详细演示;
高可用和分布式
Redis 提供了⾼可⽤实现的 Redis 哨兵(Redis Sentinel),能够保证 Redis 结点的故障发现和故
障⾃动转移。也提供了 Redis 集群(Redis Cluster),是真正的分布式实现,提供了⾼可⽤、读写和容量的扩展性。