数据平台前端缓存技术方案(M/R)

1 缓存Memcached(M)

1.1 M的概念

M是高性能的分布式内存缓存服务器,协议简单。通过缓存数据库的查询结果,减少数据库访问次数,提高动态Web应用的速度和扩展性。

M支持下述语言

wps_clip_image-29334[3][1]

1.2 M的设计

1.2.1 M的机制

守护进程机制:UNIX Daemon;Socket事件处理机制:非阻塞、Libevent异步事件处理、Epoll/Kqueue;内存管理机制:Slab内存分配、LRU对象清除、Hash快速检索Item。

内存管理中,Slab中分成小单位Chunk,Chunk中存有实际数据Item。下述是M的Slab内存结构图和分配实例图。

wps_clip_image-10708[3][1]

Slab内存结构图

wps_clip_image-16413[3][1]

Slab内存分配实例图

wps_clip_image-26154[3][1]

Slab计算占用内存图

wps_clip_image-8825[3][1]

Item数据存储格式图

1.2.2 M的架构设计

M的设计遵从原则是,首次访问从RDBMS中取得数据,按一定规则保存到M后,第二次访问从M中取得数据显示到浏览器页面中。

wps_clip_image-26264[3][1]

M架构层次图

M的服务端并没有分布式功能,各个服务器之间没有互相通信以共享信息。M的分布式完全取决于客户端的实现,如下图所示。

wps_clip_image-25027[3][1]

M使用说明图

如需要在M中保存键名为“tokyo”的数据时,通过客户端应用程序的算法,根据Key来决定选择哪个服务器节点进行存储。服务器选定之后,命令其保存键名“tokyo”和键值“data”。

wps_clip_image-16791[3][1]

Set实例图

如需要在M中获取键名为“tokyo”的数据时。把键名“tokyo”传给客户端函数库,通过和数据保存相同的算法,根据Key选择服务器节点。只要算法一致,就能选对服务器,发送命令取得键值“data”。

wps_clip_image-30287[3][1]

Get实例图

1.3 M的客户端算法

1.3.1 余数分散

根据服务器台数的余数进行分散,求得Key的整数Hash值H,除以服务器的台数N,由余数指向选中服务器,即H%N。

算法的数据分散性比较优秀,但不足的是当添加/移除服务器时(包括服务器Down机事件),缓存重组的代价相当大。因为添加/移除服务器后,余数必定发生改变,客户端在获取数据时,就无法通过算法获取和保存时相同的服务器,严重影响了缓存的命中率。大部分负载会在事件发生的同时,增大数据库的压力。

1.3.2 一致性哈希

一致性哈希算法,Consistent Hashing是一种分布式算法,常用于负载均衡。M 客户端选择这种算法,解决将Key-Value均匀分配到众多M 服务器上的问题。它可以取代传统的取模操作,解决了上述取模操作无法应对增删M 服务器所遇到的问题。

wps_clip_image-17151[3][1]

一致性哈希算法图

wps_clip_image-9755[3][1]

一致性哈希算法增加节点图

普通一致性哈希算法有个潜在的问题是:节点Hash后会不均匀地分布在环上,这样大量的Key在寻找节点时,会存在命中各个节点的概率差别较大,无法实现有效的负载均衡。如有三个节点Node1,Node2,Node3,分布在环上时三个节点挨的很近,落在环上的key寻找节点时,大量key顺时针总是分配给Node2,而其它两个节点被找到的概率都会很小。这种问题的解决方案有: 改善Hash算法,均匀分配各节点到环上;使用虚拟节点的思想,为每个物理服务器节点在圆上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。用户数据映射在虚拟节点上,就表示用户数据真正存储位置是在该虚拟节点对应的实际物理服务器上。

1.4 M的Java客户端实现(集群)

1.4.1 Google的Memcached实现

官方推荐的Java客户端之一Whalin开源实现基础上做再次封装的产物。增加实现了缓存服务接口化,使用配置文件代替代码初始化客户端,集群的实现,LocalCache客户端本地缓存的使用。旧SocketIO代码里面有太多的Synchronized(同步),多多少少会影响性能,改造SocketIO部分,优化Synchronized,添置了读入缓存页。

wps_clip_image-17459[3][1]

接口类图

集群、客户端的初始化通过配置文件生成,配置内容如下:

wps_clip_image-27350[3][1]

集群中多节点软负载均衡,当前采用简单的Hash算法加取余来分发数据,数据在多节点上进行异步冗余存储,防止数据丢失,数据在分发到某一失败节点时可以转向到其他可用节点,节点恢复可用后数据Lazy复制,例如,当A,B两台机器作为集群的时候,如果A出现了问题,系统会去B获取数据,当A正常以后,如果应用在A中没有拿到数据可以去B获取数据,并且复制到A上。

1.4.2 Spring+XMemcached实现

M的Java客户端中,XMemcached支持所有的文本协议和二进制协议。支持动态添加和删除M节点。支持客户端统计。支持节点的权重设置,支持nio的连接池,网路实现层是长连接形式,无需重复创建多个Client对象,在高负载环境下提高吞吐量。

利用Spring框架可以直接配置客户端集群,按照规定算法(Ketama等),自动Build出客户端对象,具体如下图。

wps_clip_image-11929[3][1]

集群服务配置图

wps_clip_image-19708[3][1]

客户端对象配置图

2 缓存Redis(R)

2.1 R的简介

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

2.2 R的master-slave(主从)架构

wps_clip_image-17091[3][1]

图2.1 master-slave模式

当设置好slave服务器后,slave会建立和master的连接,然后发送sync命令。无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来。后台进程完成写文件后,master就发送文件给slave,slave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上。接着master就会把缓存的命令转发给slave。而且后续master收到的写命令都会通过开始建立的连接发送给slave。从master到slave的同步数据的命令和从 client发送的命令使用相同的协议格式。当master和slave的连接断开时slave可以自动重新建立连接。如果master同时收到多个 slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave。

流程如下图所示:

wps_clip_image-1231[3][1]

图2.3 redis主从数据复制流程

Redis复制机制的缺陷

从上面的流程可以看出,Slave从库在连接Master主库时,Master会进行内存快照,然后把整个快照文件发给Slave,也就是没有象MySQL那样有复制位置的概念,即无增量复制,这会给整个集群搭建带来非常多的问题。

比如一台线上正在运行的Master主库配置了一台从库进行简单读写分离,这时Slave由于网络或者其它原因与Master断开了连接,那么当Slave进行重新连接时,需要重新获取整个Master的内存快照,Slave所有数据跟着全部清除,然后重新建立整个内存表,一方面Slave恢复的时间会非常慢,另一方面也会给主库带来压力。

所以基于上述原因,如果你的Redis集群需要主从复制,那么最好事先配置好所有的从库,避免中途再去增加从库。

redis 的 master/slave 模式下,master 提供数据读写服务,而 slave 只提供读服务。客服端通过Consistent Hashing算法选择要访问的slave缓存服务节点,运行图如下图所示:

wps_clip_image-20984[3][1]

图2.2  redis运行图

2.3 R的特性

² 可以将缓存数据持久化到磁盘

² 不支持集群,但支持主从模式

² value支持数据类型:string(字符串)、list(列表)、sets(集合)或者是 ordered  sets(被排序的集合)和hash(哈希)。

² 基于客服端来实现分布式(通过Consistent Hashing算法选择

服务器节点,算法详情请参考1.4节)

² 事务支持(MULTI,EXEC 及 DISCARD三个命令让Redis的使用者可以将Redis命令打包进行原子性的操作)

² 支持Publish/subscribe(使用该功能可以很容易实现一个实时消息平台)

2.4 R的Java客户端实现

redis主页上列出的java 客户端有Jedis 、JRedis和 JDBC-Redis三种,面分别介绍三种客户端的优缺点。

支持redis版本

性能

维护

推荐

Jedis

2.0.0 release

fast

actively developed

推荐

JRedis

1.2.n  release
2.0.0 尚未release版本

fast

JDBC-Redis

not good

详情请参考:http://redis.io/clients

(1)、jedis实现

jedis是Redis官方首选的Java客户端开发包,推荐使用jedis,所有对redis操作类的结构非常明确,都通过jedis获取其他的类,且jedis支持和spring集成。

以下是jedis+spring集成步骤:

首先,在项目中引入jeids的jar包:

wps_clip_image-30786[3][1]

其次,在spring配置文件中添加配置:

其次,在spring配置文件中添加配置:

wps_clip_image-17669[3][1]

wps_clip_image-9484[3][1]最后,应用程序调用:

(2)、jredis实现

实现比较复杂,且版本比较旧,不支持redis的很多新特性。

(3)、jdbc-redis实现

jdbc-redis 是用于 redis 这个 NoSQL 数据库的 jdbc 驱动,也就是说你可以使用你所熟悉的 jdbc的方法来访问 Redis 数据。但是jdbc-redis性能较差,不推荐使用。

2.5 R的最新版本2.4改进

² 对小数据量的sorted sets结构的内存使用做很大的优化

² RDB文件的持久化速度也将会大大提高

² 对目前的一些写操作命令进行了改进,支持批量写入功能

² 启用新的内存分配模式 jemalloc

² 通过对copy on write机制使用的优化,数据持久化保存的子进程的内存占用将大大减少

² INFO内容更加丰富

² 新的OBJECT命令,提供对Redis存储value结构描述

² 新的CLIENT命令,提供对Redis客户端连接的信息描述

² 彻底将Slave对Master的连接改成非阻塞,之前connect(2)系统调用是会阻塞的

² Redis-benchmark、Redis-cli都进行了几个方面的改进

² Make改为彩色输出,更易读

² 2.0版本中提供的VM机制被废弃

² 总的来说2.4版本会在各方面有性能上的提升

² Redis测试框架也有非常大的提升

详情请参考:http://sd.csdn.net/a/20111017/305875.html

2.6 R的性能:

根据Redis官方的测试结果:在50个并发的情况下请求10w

次,写的速度是110000次/s,读的速度是81000次/s
测试环境:
1. 50个并发,请求100000次
2.读和写大小为256bytes的字符串
3.Linux2.6 Xeon X3320 2.5GHz的服务器上
4.通过本机的loopback interface接口上执行

详情请参考:http://code.google.com/p/redis/wiki/Benchmarks

2.7 R的短期发展规划

² Lua脚本支持
² Redis集群功能
² Replication功能的改进
² 持久化方案的改进
² 提供更精确的过期时间
² 长数据的读写操作性能改进
² 进行一些内部改造
² 其它小功能

详情请参考:http://blog.nosqlfan.com/html/3405.html

3 M和R的比较

3.1 网络IO模型

M是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描述字pipe 传递给worker线程,进行读写IO, 网络层使用libevent封装的事件库,多线程模型可以发挥多核作用,但是引入了cache coherency和锁的问题,比如,M最常用的stats 命令,实际M所有操作都要对这个全局变量加锁,进行计数等工作,带来了性能损耗。

wps_clip_image-25202[3][1]

网络IO模型图

R使用单线程的IO复用模型,主要实现了Epoll、Kqueue,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是R也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际上会严重影响整体的吞吐量,并且在CPU计算过程中,整个IO调度都是被阻塞住的。

3.2 内存管理方面

M使用预分配的内存池的方式,使用Slab和大小不同的Chunk来管理内存,Item根据大小选择合适的Chunk存储,内存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数据也可能会被剔除。

Redis使用现场申请内存的方式来存储数据,并且很少采取方式优化内存分配,会在一定程度上存在内存碎片,R会根据存储命令的参数,把过期的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,进行Swap,也不会剔除任何非临时数据,这点上R更适合作为存储而不是Cache。

3.3 数据一致性

M提供了CAS命令,可以保证多个并发访问操作同一份数据的一致性问题。

R没有提供CAS命令,但是提供了事务功能,可以保证一串命令的原子性,中间不会被任何操作打断。

3.4 存储方式及其它方面

M基本只支持简单的Key-Value存储,不支持枚举,不支持持久化和复制等功能。

R除Key-Value之外,还支持list,set,sorted set,hash等众多数据结构。R还同时提供了持久化和复制等功能。主从模式(Master-Slave)实现了多台缓存服务器具有相同数据副本,某缓存服务器节点Down机后,不影响应用服务正常访问。

3.5 关于不同语言的客户端支持

M发展的时间更久一些, M有丰富的客户端支持,并且成熟稳定。

R由于其协议本身就比M复杂,并且版本在不断更新,客户端的需要时刻跟进。

4 M+R和M->R

发表评论