上一篇文章讨论了分布式系统中的服务调用和异步消息,今天想在这两个的基础上讨论下分布式系统需要解决的问题,以及常见的解决方案。
其实分布式系统的出现和分布式系统面临的挑战归结到最终都是因为用户数量的急剧增长导致的,随着用户数量和活跃用户数的,应用服务和数据库服务的并发访问压力越来越大,我们不得不进行服务拆分,做数据库的拆分和读写分离。服务拆分之后可以让我们能对每个服务进行针对性的部署,核心服务得到更好的隔离。
对于服务的拆分,我们可以根据领域驱动设计的思想,将整个系统进行域的划分,找出我们的核心子域、支撑子域;服务拆分之后面临的一个问题是服务之间的调用,此时RPC就出现了(当然此前还有webservice等技术),而随着RPC的出现,又带来了新的问题,就是服务的治理,例如新的服务上线之后如何让其他服务发现,服务下线之后如何剔除,服务调用超时之后如何进行处理,还有就是服务的性能监控和可用率监控以及相应的报警措施。
同时,有些场景下可能我们不需要同步的调用一些服务,只需要异步通知就可以了,虽然现在的RPC框架也可以实现异步调用,但是当异步调用失败后还是会影响到通知的准确性,于是就有了消息队列。消息队列可以让我们实现服务之间的异步调用,当需要做某一个通知时,我们只需要往消息队列的服务方发送一条消息,用一个消费者去消费这条消息然后调用其他服务即可。同样每一项技术的引入都会给我们带来新的问题,消息队列的使用使得我们必须得保证消息的发送和消费都成功进行,不然就会丢失消息,造成系统数据的不一致。而如何确保消息的准确发送,上篇文章已经说过,要同时保证客户端发送和消费消息都是正常的,服务端接收消息和持久化消息也是正常的。
保证消息不丢之后,我们可能还需要注意消息的重复消费,为了保证消息不丢,就一定会出现消息的重发,这个时候就需要在消费消息的时候进行幂等处理。同时,我们可能还需要对消息进行顺序消费,对于消息的顺序消费,在发送消息的时候可以根据消息的某个特征进行路由,将这一类消息路由到同一个broker上,保证这类消息发送的顺序性,而对于消息消费的顺序性则通过控制消费者数量来实现,一个主题只让一个消费者消费即可实现。很显然这样的模式让我们的并发能力显著降低,总是这么的矛盾。我们还得保证消息队列服务的高可用,针对不同系统的重要性,我们还得设计相应的降级方案,保证在消息系统不可用时整个系统的可用。
对于查询服务,可能每次查询到的结果都是一样的,在这种场景下我们不需要每次都去查询数据库,而是可以将结果缓存起来,每次查询从缓存获取,减轻数据库的压力。缓存的实现可以是jvm缓存,或者专业的缓存数据库,例如redis;由于直接操作内存,加上自身良好的设计,redis可以支撑很大的并发,如果搭建redis集群,则可以扛住更大的并发。同样缓存数据库的引入也给我们带来了问题,例如常见的缓存穿透、缓存击穿、缓存雪崩、热点key、大key等。热点key的问题,微博就经常遇到,在某个时间点,某个热点话题关联的key的访问量剧增,导致缓存服务器的连接打满,使得其他的请求只能去查询数据库,数据库扛不住并发的压力直接宕机,导致服务不可用。对于热点key的问题,不可能常年备着很多机器,就为了那一个不知道什么时候发生的点。我们可以通过大数据统计的技术提前预知热点key,然后提前触发服务的降级策略,最暴力的就是直接拒绝访问,或者温柔点,提前加上jvm缓存,利用双缓存来进行处理。
而对于大key,我们都知道redis在处理客户端的请求时时单线程的,基于IO多路复用技术,实现了高性能的并发访问。但是这样也存在一个很严重的问题,当某个客户端的操作耗时很久时,此时会有大量的请求阻塞,严重可能导致缓存不可用,服务雪崩。对于大key,我们的基本思想是进行拆分,让每个key尽量的小,而且将key进行拆分之后还能进行分流,提高性能。
除了上述问题之外,redis本身也得实现一个高可用的架构,例如redis的哨兵和集群等。
对于写服务,多个服务之间的调用又会涉及到分布式事务的问题。
用户数量带来的另一个问题就是海量数据的处理,例如电商平台,随着用户数量的剧增,每天的订单量都会很大,基本都会有几百万到千万的订单产生。如果我们使用mysql进行存储,我们知道mysql单表的数据量最好是控制在200-500万左右,这样基本一天就会写满一张表,这对数据库来说压力是非常大的。对于海量数据的存储,我们的基本思想就是进行拆分,一张表放不下,就用多张表,一个库放不下就用多个库,一个机器放不下,就用多台机器。也就是我们常说的分库分表的技术,假设我们现在数据每日的增长在1000万左右,我们可以使用16个数据库,每个库设置32张表,,这样落到每张表上的单日数据增长也才两万左右,单表达到性能瓶颈也需要将近一年的时间。当达到瓶颈后我们还可以再扩库,继续分担数据库压力。而为了保证数据不丢失、防止单点故障,我们还会做数据库的主从架构,防止一台数据库服务器宕机后,部分数据的丢失导致的服务不可用。
上面只是说了我们常见的关系型数据,一般关系型数据库只是用来作为持久化存储。对于各种条件查询,我们通常使用ES作为搜索引擎,ES的架构也和分库分表类型,每个索引类型都会分配多个分片,每个分片都会有副本进行备份,而且主分片和副本通常分布在不同的服务器节点上,当主分片节点宕机之后,副本分片会被提升为主分片继续保证服务的可用性。
来源:https://blog.csdn.net/qq_29919983/article/details/99689473