基于REIDS缓存框架的分析与实践

时间:2022-10-24 11:33:38

基于REIDS缓存框架的分析与实践

【摘 要】Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言API。针对WEB应用中,经常被查询的数据,做redis缓存,使得第一次之后的每次相同查询都通过缓存中获取,极大的提高WEB应用的性能和健壮性。本文将重点研究和分析基于redis的数据缓存与MySQL存储性能的比较。

【关键词】缓存;REDIS;WEB;性能优化

1 特性

Redis是一个高性能的key-value数据库。redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便[1]。它有以下几个特点:

1.1 速度快

Redis使用标准C编写实现,而且数据加载到内存上,所以读写速度非常快。官方提供的数据表明,普通的Linux机器上,Redis的读写速度分别达到81000/s 和 110000/s。

Redis使用单线程的IO复用模型。自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll,kqueue和select,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大。

1.2 持久化

由于所有数据保存于内存中,所以对数据的更新将异步地保存到磁盘上。

1.3 数据结构

相比于其他内存型数据库,Redis不只支持字符串类型,而是支持多种数据结构。目前支持5种。分别为 string(字符串)、hash(hash表)、list(双向链表)、set(无序集合)、zset(有序集合)。

1.4 自动操作

Redis对不同数据类型操作时自动的,因此设置或增加key,从一个集合中增加或删除一个元素都能安全的操作。

1.5 支持多种语言

Redis支持多种语言,诸如Ruby,Python,PHP,ErLang,Perl,Lua,Java,Scala等。

1.6 主-从复制

Redis支持简单而快速的主从复制。官方提供数据,Slave在21秒内即完成了Amazon网站10G key Set的复制。

1.7 Sharding

目前只支持PHP,Ruby,Scala语言的Sharding功能。

2 适应场景

1)小数据量,高速读写访问。

2)大数据量,有明显热点数据。

3)大数据量,无热点数据。

4)所有数据in-memory。

3 源码分析

RedisObject基本数据结构:

TypedefstructredisObject {

Unsigned type: 4;

Unsigned notused: 2; /* Not used*/

Unsigned encoding: 4;

Unsigned lru:22; /*lru time (relative to server.lruclock)*/

intrefcount;

/*VM fields are only allocated if VM is active,otherwise the

*object allocation function will just allocate

*sizeof(redisObject) minus sizeof(redisObjectVM),so using

Redis without VM active will not have any overhead*/

} robj;

基本value数据结构

structsdshdr {

intlen;

int free;

charbuf[];

};

ZipList

Typedefstructzlentry {

Unsigned intprevrowlensize,prevrowlen;

Unsigned intlensize, len;

Unsigned intheadersize;

Unsigned char encoding;

Unsigned char *p;

} zlentry;

4 比较MySQL与Redis存取效率比较

(代码都是用java语言完成,数据库操作采用MyBatis框架,Redis操作使用Spring提供的方法)

4.1 存储性能比较

MYSQL数据库存储10000条数据

@Test

public void testMySqlInsert10000(){

longbeginTimestamp = System.currentTimeMillis();

for(inti=1; i< 10000; i++){

redisMapper.insert(getAcl(i));

}

longendTimestamp = System.currentTimeMillis();

logger.info("MySQL数据库插入10000次所需时间为:"+

(endTimestamp-beginTimestamp));

}

Redis存储10000条数据

@Test

public void testRedisInsert10000(){

finalValueOperations<String, Object> ops =

stringRedisTemplate.opsForValue();

longbeginTimestamp = System.currentTimeMillis();

for(inti=0; i< 10000; i++){

ops.set(""+i, getAcl(i));

}

longendTimestamp = System.currentTimeMillis();

logger.info("Redis插入10000次所需时间为:"+

(endTimestamp- beginTimestamp));

}

4.2 查询性能比较

Mysql数据库查询10000次

@Test

public void testMySQLSelect10000(){

longbeginTimestamp = System.currentTimeMillis();

for(inti=1; i< 10000; i++){

RbacAclacl = (RbacAcl)redisMapper.get(i);

System.out.println(acl.getSnId());

}

longendTimestamp = System.currentTimeMillis();

logger.info("MySQL数据库读取10000次所需时间为:"+

(endTimestamp-beginTimestamp));

}

Redis查询10000次

@Test

public void testRedisSelect10000(){

finalValueOperations<String, Object> ops =

stringRedisTemplate.opsForValue();

longbeginTimestamp = System.currentTimeMillis();

for(inti=0; i< 10000; i++){

RbacAclacl = (RbacAcl)ops.get(i+"");

System.out.println(acl.getSnId());

}

longendTimestamp = System.currentTimeMillis();

logger.info("Redis读取10000次所需时间为:"+

(endTimestamp-beginTimestamp));

}

4.3 最后结果与分析

表1 执行五次来进行比较两者的时间(毫秒)

可见,redis的存取性能要极大程度优于数据库,所以在应用中合适的使用redis,能极大提升产品性能。

Redis存取速度快的原因分析如下:

4.3.1 纯内存操作

Redis所有数据存储于内存中,内存中的操作速度比磁盘快的多。

4.3.2 Redis的存取底层使用异步非阻塞IO

系统I/O 可分为阻塞型, 非阻塞同步型以及非阻塞异步型。

阻塞型I/O意味着控制权只到调用操作结束了才会回到调用者手里. 结果调用者被阻塞了, 这段时间了做不了任何其它事情。

非阻塞同步是会立即返回控制权给调用者的。调用者不需要等等,它从调用的函数获取两种结果:要么此次调用成功进行了;要么系统返回错误标识告诉调用者当前资源不可用,你再等等或者再试度看吧。

在非阻塞异步调用中,稍有不同。调用函数在立即返回时,还告诉调用者,这次请求已经开始了。系统会使用另外的资源或者线程来完成这次调用操作,并在完成的时候知会调用者(比如通过回调函数)[2]。

In a non-blocking asynchronous call, the calling function returns control to the caller immediately, reporting that the requested action was started. The calling system will execute the caller’s request using additional system resources/threads and will notify the caller (by callback for example),when the result is ready for processing。[3]

在以上三种IO形式中,非阻塞异步是性能最高、伸缩性最好的。

4.3.3 采用Epoll接口实现IO复用

与epoll模式相比较,有select模式,select模式是采用轮询的方式,来检测所有socket的状态,当有很大的socket集时,即使有小部分是活跃的,内核也需要把整个集合轮询才能知道所有socket的状态变化,而epoll模式是在创建时即对需要关注的socket注册事件,当某一个监听的socket有状态变化,就将其保存到一个内部数组中,当应用层需要检测时直接返回该数组,不需要对所有socket逐一检查,所以epoll模式的性能优于select模式。

5 Redis实践

5.1 有存活时间的数据存于redis中

需要设置数据的一定存活时间。例如找回密码功能,用户需要填写邮箱,然后发送一个连接到用户邮箱中,该连接是有时间限制,半小时后即失效。此时就可以利用redis存储数据可以设置有效时间的功能。并且不需要存储于数据库,读取redis的效率更高。

public void set(String key, Object value, long seconds) {

finalValueOperations<Object, Object> ops = redisTemplate.

psForValue();

ops.set("redis.global.forgetPass","1",30 * 60);

logger.info("数据30分钟后自动清除");

}

读取时直接在redis中查询key为 "redis.global.forgetPass",如果有值则说明未超时,为空则说明连接失效。

5.2 系统设置(基本不会变化数据存于redis中)

一些全局的系统设置,基本上都是在第一次启动应用的时候设置,以后就不会再修改的数据,可以存于redis中,设置不自动失效。

5.3 业务数据

所有业务数据,可以通过数据库主键ID当成key或者一些层级关系生成redis存储的key,将整个数据对象存储于redis,极大的减少数据库查询次数,提高应用性能。

例如:数据的层级为 商租户用户 则用户可以在redis中存储的数据为

Key: redis.user.partnerId.customerId.userId

Value: user 的数据对象

e.g.redis.user.1.33.21 redis.user.3.42.13。

【参考文献】

[1]百度百科.Redis[EB/OL].http:///view/4595959.htm,2014.

[2]veryDemo.两种高性能I/O设计模式(Reactor/Proactor)的比较[Z].2005.

[3]Artima paring Two High-Performance I/O Design Patterns[Z].2005.

上一篇:杂环双酰胺类衍生物的研究进展 下一篇:绝缘导线弧垂计算的计算机实现