MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Redis在新闻网站中的缓存应用与性能优化

2024-10-235.5k 阅读

Redis基础概述

Redis 是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis 支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。这些数据结构使得 Redis 在不同场景下都能发挥强大的作用。

Redis数据结构

  1. 字符串(String):这是最基本的数据结构,一个 key 对应一个 value。字符串类型是二进制安全的,意味着可以包含任何数据,比如图片或者序列化的对象。在新闻网站中,可以用字符串来缓存文章的标题、摘要等简单信息。例如,我们可以将一篇新闻文章的标题缓存起来:
import redis

r = redis.Redis(host='localhost', port=6379, db=0)
article_title = "Redis在新闻网站中的应用"
r.set('article:1:title', article_title)
  1. 哈希(Hash):哈希类型是一个键值对集合,适合用于存储对象。在新闻网站场景下,可以用哈希来存储一篇新闻文章的详细信息,如标题、作者、发布时间、正文等。示例代码如下:
article_info = {
    'title': 'Redis在新闻网站中的应用',
    'author': '张三',
    'publish_time': '2023-10-01 10:00:00',
    'content': '这是一篇关于Redis在新闻网站应用的文章...'
}
r.hmset('article:1', article_info)
  1. 列表(List):列表是简单的字符串列表,按照插入顺序排序。在新闻网站中,可以用列表来存储用户的浏览历史,每一次用户浏览一篇新闻,就将新闻的 ID 或者相关标识添加到列表中。如下代码实现将新闻 ID 添加到用户浏览历史列表:
user_id = 1001
article_id = 'article:1'
r.lpush(f'user:{user_id}:history', article_id)
  1. 集合(Set):集合是字符串的无序集合,且集合中的元素是唯一的。在新闻网站中,可以利用集合来存储文章的标签,标签不会重复。例如:
article_tags = {'redis', '缓存', '新闻网站'}
r.sadd('article:1:tags', *article_tags)
  1. 有序集合(Sorted Set):有序集合和集合一样也是字符串的集合,且不允许重复的成员。不同的是每个元素都会关联一个分数(score),Redis 通过分数来为集合中的成员进行从小到大的排序。在新闻网站中,可以根据文章的热度(如点赞数、评论数等)来排序,热度作为 score。如下代码根据文章点赞数更新有序集合:
article_id = 'article:1'
like_count = 100
r.zadd('article:hot', {article_id: like_count})

Redis在新闻网站中的缓存应用

页面缓存

在新闻网站中,页面缓存是提高性能的重要手段。对于一些静态页面或者变化频率较低的页面,如首页、栏目页等,可以将整个页面缓存到 Redis 中。当用户请求这些页面时,首先检查 Redis 中是否存在对应的缓存页面,如果存在则直接返回,避免了重复的数据库查询和页面渲染。

以 Python Flask 框架为例,假设我们有一个新闻网站的首页视图函数:

from flask import Flask, render_template
import redis

app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, db=0)

@app.route('/')
def index():
    page = r.get('index_page')
    if page:
        return page.decode('utf - 8')
    else:
        # 从数据库获取新闻数据并渲染页面
        news_data = get_news_data_from_db()
        rendered_page = render_template('index.html', news_data=news_data)
        r.set('index_page', rendered_page)
        return rendered_page


def get_news_data_from_db():
    # 模拟从数据库获取新闻数据
    return [
        {'title': '新闻1', 'content': '新闻1的内容'},
        {'title': '新闻2', 'content': '新闻2的内容'}
    ]


if __name__ == '__main__':
    app.run(debug=True)

在上述代码中,当用户访问首页时,先检查 Redis 中是否有缓存的 index_page,如果有则直接返回。如果没有,则从数据库获取新闻数据,渲染页面后再将页面缓存到 Redis 中。

数据缓存

  1. 新闻文章缓存:新闻文章是新闻网站的核心数据。为了减少数据库的压力,可以将新闻文章的内容缓存到 Redis 中。一般可以根据文章的 ID 作为 key,文章的详细信息作为 value 存储在 Redis 的哈希结构中。例如,获取新闻文章的函数可以这样实现:
def get_article(article_id):
    article = r.hgetall(f'article:{article_id}')
    if article:
        article_dict = {k.decode('utf - 8'): v.decode('utf - 8') for k, v in article.items()}
        return article_dict
    else:
        article_data = get_article_from_db(article_id)
        if article_data:
            r.hmset(f'article:{article_id}', article_data)
            return article_data
        return None


def get_article_from_db(article_id):
    # 模拟从数据库获取新闻文章数据
    if article_id == 'article:1':
        return {
            'title': 'Redis在新闻网站中的应用',
            'author': '张三',
            'publish_time': '2023-10-01 10:00:00',
            'content': '这是一篇关于Redis在新闻网站应用的文章...'
        }
    return None
  1. 用户相关数据缓存:新闻网站中用户相关的数据,如用户的基本信息、用户的阅读历史、用户的收藏等也可以缓存到 Redis 中。例如用户的阅读历史,使用 Redis 的列表结构存储。获取用户阅读历史的代码如下:
def get_user_read_history(user_id):
    history = r.lrange(f'user:{user_id}:history', 0, -1)
    return [article_id.decode('utf - 8') for article_id in history]
  1. 热门新闻缓存:热门新闻通常是根据点赞数、评论数等热度指标来确定的。可以利用 Redis 的有序集合来缓存热门新闻。例如,当一篇新闻有新的点赞或者评论时,更新其热度并同步到 Redis 有序集合中。
def update_article_hot(article_id, increment):
    current_score = r.zscore('article:hot', article_id)
    if current_score is None:
        current_score = 0
    new_score = current_score + increment
    r.zadd('article:hot', {article_id: new_score})


def get_hot_articles(count):
    hot_articles = r.zrevrange('article:hot', 0, count - 1, withscores=True)
    return [(article_id.decode('utf - 8'), score) for article_id, score in hot_articles]

Redis性能优化

合理设置缓存过期时间

在新闻网站中,不同类型的数据有不同的更新频率,因此需要合理设置缓存的过期时间。对于首页等更新频率较低的页面缓存,可以设置较长的过期时间,如几个小时甚至一天。而对于一些实时性要求较高的数据,如热门新闻排行榜,过期时间可以设置得较短,比如几分钟。

在 Python 中设置缓存过期时间的示例代码如下:

# 设置首页页面缓存过期时间为1小时
r.setex('index_page', 3600, rendered_page)

# 设置热门新闻排行榜缓存过期时间为5分钟
r.setex('hot_articles', 300, hot_articles_data)

缓存穿透优化

缓存穿透是指查询一个一定不存在的数据,由于缓存中没有,每次都会去数据库查询,从而导致数据库压力增大。在新闻网站中,如果恶意用户频繁请求不存在的新闻文章 ID,就可能引发缓存穿透问题。

常见的解决方法是使用布隆过滤器(Bloom Filter)。布隆过滤器是一种概率型数据结构,它可以高效地判断一个元素是否在集合中。虽然存在一定的误判率,但可以通过调整参数来降低误判率。

以 Python 的 pybloomfiltermmap 库为例,实现防止缓存穿透的代码如下:

from pybloomfiltermmap import BloomFilter

article_id_bloom = BloomFilter(capacity=100000, error_rate=0.001)

# 假设从数据库中获取所有已存在的文章 ID 并添加到布隆过滤器中
article_ids = get_all_article_ids_from_db()
for article_id in article_ids:
    article_id_bloom.add(article_id)


def get_article_with_bloom(article_id):
    if article_id not in article_id_bloom:
        return None
    article = r.hgetall(f'article:{article_id}')
    if article:
        article_dict = {k.decode('utf - 8'): v.decode('utf - 8') for k, v in article.items()}
        return article_dict
    else:
        article_data = get_article_from_db(article_id)
        if article_data:
            r.hmset(f'article:{article_id}', article_data)
            return article_data
        return None


def get_all_article_ids_from_db():
    # 模拟从数据库获取所有文章 ID
    return ['article:1', 'article:2', 'article:3']

缓存雪崩优化

缓存雪崩是指在某一时刻,大量的缓存同时过期,导致大量请求直接落到数据库上,造成数据库压力过大甚至崩溃。在新闻网站中,如果首页、栏目页等重要页面的缓存同时过期,就可能引发缓存雪崩问题。

解决缓存雪崩的方法有多种,一种是设置随机过期时间。例如,对于首页页面缓存,原本设置过期时间为 1 小时,可以改为在 50 分钟到 70 分钟之间随机设置过期时间,这样可以避免大量缓存同时过期。代码示例如下:

import random

expire_time = random.randint(3000, 4200)
r.setex('index_page', expire_time, rendered_page)

另一种方法是使用互斥锁(Mutex)。当缓存过期时,先获取一个互斥锁,只有获取到锁的请求才去查询数据库并更新缓存,其他请求等待。这样可以保证同一时间只有一个请求去查询数据库,避免大量请求同时压到数据库上。以下是使用 Redis 实现互斥锁的示例代码:

import time


def get_page_with_mutex(page_key):
    lock_key = f'{page_key}:lock'
    while True:
        if r.set(lock_key, 'locked', nx=True, ex=10):
            try:
                page = r.get(page_key)
                if not page:
                    # 从数据库获取数据并渲染页面
                    data = get_data_from_db()
                    rendered_page = render_page(data)
                    r.set(page_key, rendered_page)
                    return rendered_page
                return page.decode('utf - 8')
            finally:
                r.delete(lock_key)
        else:
            time.sleep(0.1)


def get_data_from_db():
    # 模拟从数据库获取数据
    return [
        {'title': '新闻1', 'content': '新闻1的内容'},
        {'title': '新闻2', 'content': '新闻2的内容'}
    ]


def render_page(data):
    # 模拟渲染页面
    return f'<html><body>{data}</body></html>'

缓存击穿优化

缓存击穿是指一个热点 key,在某个时间点过期的时候,恰好在这个时间点对这个 key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候对数据库造成很大的压力。在新闻网站中,如果一篇热门文章的缓存过期,同时大量用户请求这篇文章,就可能出现缓存击穿问题。

解决缓存击穿的方法和缓存雪崩类似,可以使用互斥锁,也可以将热点数据设置为永不过期。不过设置为永不过期可能会导致数据一致性问题,所以需要在数据更新时及时更新缓存。例如,当一篇热门文章的内容更新时,不仅要更新数据库,还要同时更新 Redis 中的缓存:

def update_article(article_id, new_data):
    # 更新数据库
    update_article_in_db(article_id, new_data)
    # 更新 Redis 缓存
    r.hmset(f'article:{article_id}', new_data)


def update_article_in_db(article_id, new_data):
    # 模拟更新数据库操作
    pass

Redis集群优化

随着新闻网站业务的增长,单台 Redis 服务器可能无法满足性能和存储需求,这时可以采用 Redis 集群。Redis 集群是一个提供在多个 Redis 节点间共享数据的程序集。

在搭建 Redis 集群时,需要合理分配节点数量和数据分片。一般来说,可以根据数据量和访问频率来确定节点数量。同时,要注意节点之间的网络连接稳定性,避免因为网络问题导致数据同步失败。

以 Redis 官方提供的 redis - trib.rb 工具搭建集群为例,假设我们有 6 个 Redis 节点(3 个主节点,3 个从节点),搭建集群的步骤如下:

  1. 启动 6 个 Redis 实例,分别监听不同的端口,如 7000 - 7005。
  2. 使用 redis - trib.rb 工具创建集群:
redis - trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

上述命令中 --replicas 1 表示每个主节点对应一个从节点。

在应用程序中连接 Redis 集群时,需要使用支持集群模式的客户端。以 Python 的 redis - py 库为例,连接 Redis 集群的代码如下:

from rediscluster import RedisCluster

startup_nodes = [
    {"host": "127.0.0.1", "port": "7000"},
    {"host": "127.0.0.1", "port": "7001"},
    {"host": "127.0.0.1", "port": "7002"}
]
rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)

# 使用 Redis 集群进行操作,例如设置和获取新闻文章
article_id = 'article:1'
article_info = {
    'title': 'Redis在新闻网站中的应用',
    'author': '张三',
    'publish_time': '2023-10-01 10:00:00',
    'content': '这是一篇关于Redis在新闻网站应用的文章...'
}
rc.hmset(article_id, article_info)
article = rc.hgetall(article_id)

通过以上对 Redis 在新闻网站中的缓存应用与性能优化的介绍,我们可以看到 Redis 能够极大地提升新闻网站的性能,减少数据库压力。同时,通过合理的性能优化策略,可以进一步保障系统的稳定性和高效性。在实际应用中,需要根据新闻网站的具体业务需求和规模,灵活调整 Redis 的使用方式和优化策略。