软件架构与思考

👉 所有文章
高性能 高性能设计模式
数据库 数据库分库分表指南 MySQL 建表参考 MySQL 初始化数据的一些方案 数据版本号
分布式 ID 分布式 ID 生成方案探讨
缓存 缓存 使用数据版本号保证缓存是最新数据 基于redis的二级缓存
微服务 如何实现远程调用 RPC 协议中的数据签名验签和加解密方案探讨 关于服务间调用循环依赖的一些思考 我所理解的负载均衡 一致性哈希 基于Redis的分布式会话管理系统 如何部署服务 灰度发布 如何区分上游和下游 日志级别
算法与协议 一个可扩展的 MQ 消息设计 Dynamo涉及的算法和协议 写时复制
任务分发 Gearman入门 如何使用redis构建异步任务处理程序
安全 关于对账的一些理解 一个简单可靠的 Dubbo 请求/响应数据签名方案
其他 使用卫语句减少 if else 嵌套

基于redis的二级缓存


2014-11-28

编译redis


当前redis的稳定版本是2.8.17,可以在官网下载。下载后解压,进入解压后的目录,直接make,之后可以在src目录下找到编译好的二进制文件redis-serverredis-cli等。

配置


理论上来说,第1级缓存的访问速度要比第2级缓存快,第1级缓存的容量要比第2级缓存小,第1级缓存在价钱上要比第2级缓存高很多。另外,第1级缓存上的数据的访问频率也应该高于第2级缓存,第1级缓存上的数据的热门程度大于第2级缓存。

对于新闻网站,当天的新闻可以缓存到第1级缓存中,之前的新闻可以缓存到第2级缓存。

第1级缓存配置如下(6301.conf):

port 6301
maxmemory 2mb
maxmemory-policy allkeys-lru
maxmemory-samples 20
pidfile ./6301.pid  
timeout 300
logfile ./6301.log  

第1级缓存占用6301端口,使用2 metabytes内存。在内存使用达到2 metabytes时,若需要存储新的内容,使用LRU算法置换已经存储的内容,以腾出内存空间。进程id信息保存在文件6301.pid中,日志信息保存到6301.log文件中。

关于maxmemory-policymaxmemory-samples,可以参考Using Redis as an LRU cache

第2级缓存配置如下(6302.conf):

port 6302
maxmemory 100mb
maxmemory-policy allkeys-lru
maxmemory-samples 20
pidfile ./6302.pid  
timeout 300
logfile ./6302.log

第2级缓存占用6302端口,使用100 metabytes内存。

python2.7 安装相关的模块:

$ sudo pip install redis

缓存的数据格式


假设每条数据的key和value大小都是相同的。key是长度为7的字符串,value是长度为20000的字符串。

在本机上启动第1级缓存:

$ redis-server ./6301.conf

运行下面的python程序:

# coding: UTF-8
import redis

redis1 = redis.StrictRedis(host='localhost', port=6301, db=0)

for x in xrange(100):  #设的大一些
    key = 'url%4d'% x
    value = [str(x%10)] * 20 * 1000
    redis1.set(key, ' '.join(value))

print redis1.dbsize()  # 键值对数量
print redis1.flushall()  # 清空

输出:

36
True

可见第1级缓存可存储36个这样的键值对。

类似的,第2级缓存可以存储2540个这样的键值对。

在单机上测试


在本机上启动第1级缓存:

$ redis-server ./6301.conf

在本机上启动第2级缓存:

$ redis-server ./6302.conf

第2级缓存单独作为一个缓存系统,名为Cache1;第1级和第2级缓存共同作为一个缓存系统,名为Cache2。

在第2级缓存中添加数据:

# coding: UTF-8

import redis

redis2 = redis.StrictRedis(host='127.0.0.1', port=6302, db=0)

for x in xrange(2540):
    key = 'url%4d'% x
    value = [str(x%10)] * 20 * 1000
    redis2.set(key, ' '.join(value))

测试程序:

# coding: UTF-8

import redis
import time
import random


class Cache1:
    '''
    只有第2级缓存, 100mb
    '''
    def __init__(self):
        self.redis2 = redis.StrictRedis(host='127.0.0.1', port=6302, db=0)

    def get(self, key):
        return self.redis2.get(key)

    def set(self, key, value):
        return self.redis2.set(key, value)


class Cache2:
    '''
    第1级缓存:2mb
    第2级缓存:100mb
    '''
    def __init__(self):
        self.redis1 = redis.StrictRedis(host='127.0.0.1', port=6301, db=0)
        self.redis2 = redis.StrictRedis(host='127.0.0.1', port=6302, db=0)

    def get(self, key):

        value1 = self.redis1.get(key)
        if value1 is not None:
            return value1
        else:
            value2 = self.redis2.get(key)
            if value2 is not None:
                self.redis1.set(key, value2)
            return value2

    def set(self, key, value):

        return self.redis1.set(key, value) or self.redis2.set(key, value)


def test01():
    '''
    测试Cache1
    '''
    c1 = Cache1()
    start_time = time.time()
    for x in xrange(200):
        ids = range(36)
        ids.append(random.randint(100, 1800))
        keys = ['url%4d' % x for x in ids]
        random.shuffle(keys)
        for key in keys:
            if c1.get(key) is None:
                print 'do not have key: %s' % key

    print 'test01耗时:', time.time() - start_time


def test02():
    '''
    测试Cache2,第1级缓存命中率较高
    '''
    c2 = Cache2()
    start_time = time.time()
    for x in xrange(200):
        ids = range(36)
        ids.append(random.randint(100, 1800))
        keys = ['url%4d' % x for x in ids]
        random.shuffle(keys)
        for key in keys:
            if c2.get(key) is None:
                print 'do not have key: %s' % key

    print 'test02耗时:', time.time() - start_time
    c2.redis1.flushall()


def test03():
    '''
    测试Cache2,第1级缓存命中率较低
    '''
    c2 = Cache2()
    start_time = time.time()
    for x in xrange(200):
        ids = range(20)
        ids.extend([random.randint(100, 1800) for _ in xrange(17)])
        keys = ['url%4d' % x for x in ids]
        random.shuffle(keys)
        for key in keys:
            if c2.get(key) is None:
                print 'do not have key: %s' % key

    print 'test03耗时:', time.time() - start_time
    c2.redis1.flushall()


if __name__ == '__main__':
    test01()
    test02()
    test03()

三次运行的结果如下:

test01耗时: 1.01688408852
test02耗时: 2.49854183197
test03耗时: 4.28654408455

test01耗时: 1.41078400612
test02耗时: 2.63620305061
test03耗时: 3.54241800308

test01耗时: 1.5699839592
test02耗时: 2.48805403709
test03耗时: 4.36335802078

本以为test02耗时要低于test01,但事实却不是,可以认为redis本身的算法实现很漂亮。

在双机上测试


通常,网站的缓存系统和网站主程序并不在同一个主机上。将第2级缓存转移到连接同一个交换机的另外一台主机上,假设该主机ip为180.180.180.180,将上述测试程序中的

self.redis2 = redis.StrictRedis(host='127.0.0.1', port=6302, db=0)

修改为

self.redis2 = redis.StrictRedis(host='180.180.180.180', port=6302, db=0)

三次运行的结果如下:

test01耗时: 29.73127985
test02耗时: 8.53076004982
test03耗时: 24.4670870304

test01耗时: 29.7424390316
test02耗时: 8.52891612053
test03耗时: 24.3255221844

test01耗时: 29.9915928841
test02耗时: 8.52997803688
test03耗时: 24.701570034

可以认为,由于网络传输的延迟,第2级缓存的访问速度要低于第1级缓存,两级缓存系统满足需求。


( 本文完 )

文章目录