分布式cache系统的应用

来源:互联网 发布:软件企业退税 编辑:程序博客网 时间:2024/05/26 07:28

分布式cache系统的应用

 

1. 问题背景 
 
   在商城实践中,我们为了改善用户浏览图片的速度,但同时减少图片存储服务器的访问压力,采用了memcached来作cache集群,达到分担来自前端的请求,实际运营效果不错。但是后来在运营的过程中发现,随着memcached实例的不断增加以下问题逐渐开始困扰起我们:
    1. n台分布的memcached做实际数据的存储,每台memcached存放数据总量的一个子集(分布算法:Key映射整数值,再根据已有server数取模),随着memcached实例数量的增加,管理、维护的难度也相应增加;
    2. 新增x台memcached进行扩容,已有数据需要作大规模迁移,且同时一定时间内cache命中较低;
    3. 如有memcached服务异常退出,如不能迅速拉起,会影响前端访问的体验,或者可能导致数据分布的重计算,使得memcached的SLA级别较低;
    4. 因为缺乏统一分配、管理机制,可能会存在数据在多个memcached存在实例的情况,导致数据不一致或不同版本数据冲突的情况。
为了解决这种些问题,我们设计出使用统一集中式索引的分布式缓存管理系统,开发语言java1.6,关键技术 nio、jgroups、memcached java。
2. 关键设计和特性

    在memcached群集前端建立索引服务器,统一数据访问入口的分配和管理:
    1. 索引服务器以8bytes整数为Key,分配、记录应用数据到具体memcached位置的索引信息,并作为应用,memcached间的Proxy角色进行数据的传输;
    2. 可将多个索引服务器配置在一起构成一个索引集群。在同一个索引集群中,索引信息互为拷贝,保证索引表的一致性,这一特性使用索引集群具有高可用性;
    3. 索引服务器后端的memcached构成实际的存储信集群,每个memcached保存一部分数据,索引服务器对已有的存储资源进行分配,并实时的将索引信息同步到索引集群中其他索引服务器上去;
    4. 数据的实际存储位置的分配,不再按照Key取模的方式。而是根据后端memcached个数及容量、内存空闲程度进行负载均衡分配;
    5. 对memcached进行扩容时,不会对已存储信息造成影响,不存在扩容时的数据迁移问题;同时memcached的失效不会影响群集的可用性;
    6. 索引信息以HASHMap方式进行保存,并以FIFO方式进行淘汰,保证最老数据优先淘汰;索引信息具备类型、版本号、失效时间等属性;
    7. 同一索引Key的数据根据版本号进行区分,不存在低版本数据覆盖高版本数据的问题;
    8. memcached实例的SLA级别得到较大的提升;
3. 系统部署图

图表 1 系统部署

    1. 应用通过索引服务器(主)进行数据Set/Get,不直接对memcached进行访问。
    2. 按照上图的部署架构,索引服务器(从)不进行实际Set/Get操作,只是对索引服务器(主)的一份热备。当索引服务器(主)出现异常时,可承担起从责任处理应用应用的请求,而原来已经异常的索引服务器(主)变为从,把对应用产生的影响降至最小。索引服务器(主)出现异常时,通过商城配置中心进行服务路由切换,那么服务器(从)由从变为主,所有的Set/Get请求落在索引服务器(从)上,待主索引应用重新启动,会向集群中指定IP的索引服务请求当前最新的状态信息,这些信息包括:memcached服务列表信息,索引数据的全量信息。
    3. 可在这种拓扑结构上进行扩充,在索引服务器(从)的基础上,再多加一台索引服务器(组织),组织索引服务也是当前主的索引服务器的一个热备,组织和从的区别在于,组织不会处理任何Set/Get请求,只是专门用来处理索引服务器异常重启时状态信息获取的请求。根据测试500w条索引信息,大概会消耗150mb的内存空间,并且在传递初始信息的时候,机器会出现短时cpu负载偏高的情况,所以最好经由组织为新增结点提供初始化索引数据的服务。
4. 系统结构 
  
   索引服务使用网络接入自主开发的java nio 框架实现(其本原理和MINA框架类似),下图给出索引服务的系统结构。


图表 2 作为主机角色的索引服务的运行时结构

图表 3 作为备机角色的索引服务的运行时结构

5. 索引数据结构


图表 4 索引数据结构

    索引结点数据中有一个version来标识结点数据的版本信息,当索引服务的使用者在试图设置一个版本值低于当前已经存在高版本结点数据值,索引服务将拒绝给予处理,并记录日志,定时将错误总数至itil。
6. 主要功能序列图

1.1 设置缓存数据的序列图

     流程说明:
    1. 前端应用发起数据Set请求
    2. NetIO模块读取请求包,并判断请求包是否已经读取完整
    3. 请求包读取完整,NetIO模块通过Workpooll,选择一个Worker处理线程,将请求包放入线程对应的缓存队列,并唤醒Woker处理线程进行处理。
    4. Worker处理线程判断当前Set数据是否有效,并判断Key是否已经存在Index记录,且版本信息高于当前请求值,若是,需拒绝当前Set请求。否则,从目前有效的memcached中,随机选择一个作为存储入口。(根据memcached容量信息来随机选择,大容量memcached具有大的当选概率)
    5. 淘汰策略:如果当前选择中memcached的已使用内存容量接近最大值,那么从索引map中找到最老结点,淘汰之。这样作可以比较好的处理索引和内存容量之间的供需矛盾。
    6. Woker将Index更新信息保存在Jgroups通知缓存队列中,待Jgroups-msg-snder发送更新消息,通知到索引集群内其他索引服务器。
    7. Worker处理线程根据获取到的memcached入口信息,获取到此memcached的长连接,保存应用应用数据。若保存失败,需删除相应Index信息。
    8. Worker处理线程拼装Set响应信息,通过NetIO.RespDispatcher找到对应的前端应用连接入口,将响应包缓存到响应缓存队列。
    9. NetIO模块将响应包发送至应用前端。

1.2 读取缓存数据的序列图

    流程说明:
    1. 前端应用发起数据Get请求
    2. NetIO模块读取请求包,并判断请求包是否已经读取完整
    3. 请求包读取完整,NetIO模块通过WokerPool,选择一个Worker处理线程,将请求包放入线程对应的缓存队列,并唤醒Woker处理线程进行处理。
    4. Worker处理线程首先判断Key是否在本地索引表中存在记录,若无则直接通知前端应用 Get失败。另外,需判断索引是否已经超时,或对应的后端memcached是否当前有效。
    5. Worker处理线程根据获取到的memcached入口信息,获取到此memcached的长连接,查询应用应用数据。若查询失败,需删除相应Index信息。
    6. Worker处理线程拼装Get响应信息,通过Netio.RespDispatcher找到对应的前端应用连接入口,将响应包缓存到响应缓存队列。
    7. NetIO模块将响应包发送至应用前端。
7. 性能统计

    目前分布式cache系统已经应用在拍拍商城,为商品中图提供cache服务。商品中图平均大小为13.6k,从运营统计中获取相关性能数据如下:
    • 峰值处理能力达到,每分钟 111531 次(每秒钟 1858)请求其中 99.8% 的请求在 10 毫秒钟以内完成,并且CPU的利用率在 20% 以下; 
    • 目前使用的cached共计48G,共40个memcached进程,缓存图片索引 3517242,命中率稳定在 85%至90%之间。 
    预计峰值处理能力可以达到 5000次/秒。
8. 优化经验 
    • 使用java nio实现实现网络接入,事件驱动处理业务; 
    • 使用suse64位操作系统获得系统线程以及大内存的支援; 
    • 使用jprofile进行服务的性能优化; 
    • 使用share nothing 的理念设计索引结构,尽可能减小同步以及加锁操作。
9. 总结

    实践证明以为索引服务为中心的分布式cache系统具有很高的可用性、扩展性以及较好的性能,而且可以充分利用在线运营服务器上的空闲内存。后续分布式cache系统将会在商城应用到商品详情、店铺详情等业务上,以期发布更大的作用。