我是如何构建高并发下响应式读服务的

标签: 并发 响应式 服务 | 发表时间:2015-10-08 18:07 | 作者:deyimsf
出处:http://www.iteye.com
一. 该系统的特点
该系统的特点是业务方多,后台依赖系统多。由于各个服务方使用的语言各不相同,为了方便各个系统调用,我们统一使用http协议形式来提供数据。
这就需要我们将后端依赖的各种数据类型进行组装并转换成服务方需要的数据,同时我们也需要解决快速响应的问题

二. 最早的系统架构我们是这样做的
我们用nginx代理后端的多个tomcat实例,tomcat负责从各个系统读取数据并组装成客户需要的数据,同时为了解决快速响应问题,我们将数据缓存redis。

大致的数据访问流程很简单:当请求过来后通过HttpRedis2Module模块直接从redis获取需要的数据,如果redis获取不到就回源到tomcat,tomcat负责将数据返回并向redis写一份数据。

另外为了保证多个机器数据一致性问题,采用redis一主多从来解决数据一致性问题。

架构图:



三. 第一版系统遇到的问题
初期由于系统访问量不大,基本可以满足我们的要求。后续随着业务量的增长,以及每年的618和双十一活动的影响,基本上每个活动的当天的访问量都会高出日常十倍。

遇到的问题主要如下:
1. 主redis需要向多个从redis同步数据,流量过大会造成主的性能下降,比如cpu负载升高,网卡流量打满等。
2. 由于缓存失效导致的回源问题,导致tomcat压力过大
3. 单个redis实例的内存使用量过高
4. 存入redis中单个key-value数据体积过大
5. 由于主redis单点问题,如果挂掉造成数据无法及时同步

四. 针对第一版遇到的问题我们做了哪些事情
1. 为了降低主redis的压力,我们采用了redis树状结构,比如我们在主redis下只设置了两个从实例,然后每个从实例下又可以挂载几个从实例。

2. 对于应对缓存失效导致的回源问题,我们采用了两种方式来解决;一种是让缓存不过期,我们通过定时的跑worker或者mq来更新数据,同时为了节约内存,这种方式只适用于数据量小的业务。 另一种是通过lua-resty-lock的方式对需要回源到后端的相同请求进行无阻塞锁。

lua-resty-lock是一个基于共享内存(ngx.shared.DICT)的非阻塞锁。首先该锁是基于共享内存的,并且锁在创建后还会为自身设置一个超时时间,也就是说在这个指定的超时时间内,不管有没有对其解锁,该锁都会被回收掉。另外在nginx中对于使用DICT缓存的数据它是每个worker都是可见的;说它是非阻塞的是因为它不会阻塞nginx的worker进程,当某个key被锁住之后,后续试图对该key尝试进行锁的操作都会被非阻塞等待在这里,查看lua-resty-lock的源码可以看到,它实际上是用ngx.sleep实现的,而ngx.sleep又是利用了nginx自身的定时器和lua中协程的概念实现的。
下面我们看一下具体业务代码实现步骤:
1. 首先我们检查请求的key是否在dict中有一份数据,有就直接返回,如果没有,那么我们就从redis中获取数据,有则返回,没有则进入第2步
2. 这时候我们需要创建一个锁,创建锁的时候可以为该锁对象设置一些超时时间等参数,然后调用lock方法对其加锁。然后我们判断该锁是否已经超时,如果超时这里有两种策略供我们选择,根据具体业务的不同,一种是直接返回一个错误数据;另一种是直接回源;如果选择的是直接回源,那么在超时时间内积压的请求会全部走后后端,对于后端是个不小的压力;如果选择返回错误数据,对客户体验又不好,这两者需要权衡。如果没有超时,那么进入第3步。
3. 再次从dict中获取数据,因为数据成功回源后不但会向redis写一份数据,还会向dict中写一份数据,如果从dict中获取到数据,那么我们对该key进行unlock操作,这个时候所有在该key上等待的请求会直接从dict中获取数据,这样就避免了因为缓存失效导致后端压力过大的问题。如果从dict中还是获取不到数据,直接进入第4步。
4. 通过回源向后端请求数据,然后解锁。

3. 最早我们每个机器上都是用的单个redis实例,当redis单实例占用内存过大并且高并发下,通过slowlog会看到响应速度明显变慢,同时一但redis挂掉,或者因为网络的原因就会导致redis主从之间进行数据全量重同步。这对于线上网卡又会产生一个不小的压力。为了解决这个问题,我们对数据进行了分片处理,就是没太机器上启动多个redis实例,通过hash的方式将数据分散到不同的实例上。在做分片的时候我们需要保证后端的写入分片算法,和nginx读取数据的分片算法一直。

后端采用的是在jedis的基础上扩展出一套简单的取摸算法;nginx这端为了方便和快速,我们用C实现和后端相同的算法,并将其嵌入到lua中,通过ngx_lua模块对其进行调用。

具体算法这里就不做解释了,简单说一下调用过程:
    1)在nginx指定.so动态库路径 lua_package_cpath "/path/?.so;;"; 
2)  location ~ ^/test$ {
content_by_lua_file  /path/get.lua
    }
3 在get.lua文件中调用该方法
 local redis = require “redis”
  local chash = require “chash”
   
  local function read_redis(index)
    local red,ok,err = redis.connect(“127.0.0.1”,index对应的端口号)
    if not ok then
      return nil,nil,err;
    end
    ---- 这里为了方便做了一个简单的封装
    local ok,res,err = redis.excute(red,”get”,key) 
    ---- 释放链接,同样做了一个简单封装
    redis:release(red);
    return ok,res,err;
  end

 local index = chash.mod(key,摸数)
 ---- read data
 local ok,res,err = read_redis(index);
 // 对获取到的数据res进行一些处理
 


     以上就是对于分片的大概处理逻辑。
                 
4. 对于单个key数据量过大的问题,我们目前用两种方式来解决,一种是对业务数据进行简单的字符串替换,比如我们对某个数据项的字段名做精简,用更短的字符替代长的字段名;另一种方式是对整个数据进行gzip压缩;

5. 采用多机房部署,每个机房的都是一个独立的集群,redis之间也不会存在跨机房同步的问题。对于数据一致性问题,根据业务需要采用worker、mq等方式来解决。如果业务对于数据一致性要求不高,我们完全可以使用被动更新缓存和控制缓存时间来解决。

6. 对于某些业务,后端可能要调用很多服务才能完成数据组装,之前我们对于这种业务都是顺序的去调用后端依赖的服务。目前我们的做法是只要这些依赖的服务没有上下逻辑关系,我们都是并行的去调用,然后再统一组装数据。

五 架构图


六 我们后续要做的事情

1. 对单个key数据进行分片处理,这里是对超过一定大小的key值进行平均分割,放到多个redis分片实例中,最后通过ngx.location.capture_multi的方式对其进行并行调用,并将得到的数据进行组装。
2. 目前如果缓存失效我们最后一道防线是后端应用,如果后端一个用挂掉,或者由于后端的某个接口有问题,比如处理速度慢、超时等,会造成系统无法提供正常业务。后续我们考虑使用像ssdb这样的nosql数据库做一个托底方案,这样如果业务允许,当缓存和应用都出现问题我们可以使用托底方案对业务进行降级处理。
3. 后端应用和redis进行隔离部署。




已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [并发 响应式 服务] 推荐:

我是如何构建高并发下响应式读服务的

- - 互联网 - ITeye博客
该系统的特点是业务方多,后台依赖系统多. 由于各个服务方使用的语言各不相同,为了方便各个系统调用,我们统一使用http协议形式来提供数据. 这就需要我们将后端依赖的各种数据类型进行组装并转换成服务方需要的数据,同时我们也需要解决快速响应的问题. 最早的系统架构我们是这样做的. 我们用nginx代理后端的多个tomcat实例,tomcat负责从各个系统读取数据并组装成客户需要的数据,同时为了解决快速响应问题,我们将数据缓存redis.

高并发web服务技术选型

- - 崔永键的博客
主要问题集中在单个GB级数据使用何种DFS的问题上,目前还没有得到可靠的结论. 采用:nginx或 lvs: https://github.com/alibaba/LVS. 实施自己的调度策略:学习配置lvs或改造lvs或自己重写. 调研下采用hdfs还是fastdfs还是其他的:Fastdfs,ZFS,Lustre,HadoopHDFS,GlusterFS.

100万并发连接服务器笔记之准备篇

- - BlogJava-首页技术区
测试一个非常简单服务器如何达到100万(1M=1024K连接)的并发连接,并且这些连接一旦连接上服务器,就不会断开,一直连着. 环境受限,没有服务器,刚开始都是在自己的DELL笔记本上测试,凭借16G内存,和优秀的vmware workstation虚拟机配合,另外还得外借别人虚拟机使用,最终还得搭上两台2G内存的台式机(安装centos),最终才完成1M并发连接任务.

高并发服务端分布式系统设计概要

- - 博客 - 伯乐在线
写这篇文章的目的,主要是把今年以来学习的一些东西积淀下来,同时作为之前文章《 高性能分布式计算与存储系统设计概要》的补充与提升,然而本人水平非常有限,回头看之前写的文章也有许多不足,甚至是错误,希望同学们看到了错误多多见谅,更欢迎与我讨论并指正. 我大概是从2010年底起开始进入高并发、高性能服务器和分布式这一块领域的研究,到现在也差不多有三年,但其实很多东西仍然是一知半解,我所提到的许许多多概念,也许任何一个我都不能讲的很清楚,还需要继续钻研.

京东抢购服务高并发实践

- - 企业架构 - ITeye博客
限时抢购又称闪购,英文Flash sale,起源于法国网站Vente Privée. 闪购模式即是以互联网为媒介的B2C电子零售交易活动,以限时特卖的形式,定期定时推出国际知名品牌的商品,一般以原价1-5折的价格供专属会员限时抢购,每次特卖时间持续5-10天不等,先到先买,限时限量,售完即止. 顾客在指定时间内(一般为20分钟)必须付款,否则商品会重新放到待销售商品的行列里.

[转]服务端高并发分布式架构演进之路

- - 鸟窝
原文: 服务端高并发分布式架构演进之路, 作者: huashiou. 作者使用一个商城的例子,演示了架构的演变之路,思路清晰,并且解释了架构的瓶颈以及解决之道. 本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则.

高并发系统-如何做服务化拆分

- - 掘金 架构
一般早期架构是 一体化架构(Monolithic Architecture),简单来说就是所有的业务都在一个后台服务来承载. 例如,一个Java web应用运行在Tomcat之类web容器上,仅包含单个WAR文件;一个Rails应用使用Phusion Passenger部署在Apache/Nginx上,或者使用JRuby部署在Tomcat上,它们都仅包含单个目录结构.

响应式网页设计

- - 前端观察
这个话题最近很火爆,我也一直在关注,响应式网页设计和移动互联网密切相关,并因移动终端的丰富和普及而兴盛,并且是一个颇具争议的话题,我今天将和大家探讨下. 上周写了个简单的ppt在组内大概介绍了下,感兴趣的话可以 先看下这个PPT. 在说到这个话题前,我们先看下网页设计和前端开发的现状:. 全球有超过53亿手机用户(包括传统手机).

响应式Web设计

- - 葵中剑's Blog - SwordAir.com
响应式Web设计( Responsive Web Design – RWD)一般是指那些使用CSS3 Media Query特性制作站点,其可以适应不同视窗尺寸的布局. 虽然很早就已经有了类似RWD的概念,但直到最近一年里才开始变得特别流行,各种文章、例子、工具、模板,不断地从无到有,诸如:. 响应式Web设计50个例子和最佳实践.

响应式网页设计

- - IT技术博客大学习
响应式网页设计最初是由 Ethan Marcotte 提出的一个概念:为什么一定要为每个用户群各自打造一套设计和开发方案. Web设计应该做到根据不同设备环境自动响应及调整. 当然响应式Web设计不仅仅是关于屏幕分辨率自适应以及自动缩放的图片等等,它更像是一种对于设计的全新思维模式;我们应当向下兼容、移动优先.