Redis应用场景

本文主要介绍Redis的常用场景,这都是由Redis的特性决定的。并尽量从源码角度说明Redis为什么适合于这种场景。

I、积分排行榜

1、积分排行榜是Redis的经典应用,这是得益于Redis提供了zset的有序集合数据结构。

2、针对zset,Redis提供了一系列的命令:

ZADD: 添加新元素;
ZRANGE: 按分数从低到高返回给定排名区间的元素;
ZREVRANGE: 按分数从高到底返回给定区间的元素;
ZRANK: 返回某个元素的排名。

3、实现:下面的例子为用python完成的一个leaderboard

# -*- coding: utf-8 -*-
#!/usr/bin/python
import redis
class Leaderboard:
    def __init__(self,host,port,key,db):
        self.host = host
        self.port = port
        self.key = key
        self.db = db
        self.r = redis.StrictRedis(host=self.host,port=self.port,db=self.db)
    def isRedisValid(self):
        return self.r is None
    def addMember(self,member,score):
        if self.isRedisValid():
            return None
        return self.r.zadd(self.key,score,member)
    def delMember(self,member):
        if self.isRedisValid():
            return None
        return self.r.zrem(self.key,member)
    def incrScore(self,member,increment):
        """increase score on specified member"""
        if self.isRedisValid():
            return None
        return self.r.zincrby(self.key,member,increment)
    def getRankByMember(self,member):
        """Get ranking by specified member."""
        if self.isRedisValid():
            return None
        return self.r.zrank(self.key,member)
    def getLeaderboard(self,start,stop,reverse,with_score):
        """Return the whole leaderboard."""
        if self.isRedisValid():
            return None
        return self.r.zrange(self.key,start,stop,reverse,with_score)
    def getLeaderboardByPage(self,item_per_page,page_num,reverse=False,with_score=False):
        """Return part of leaderboard configurably."""
        # fix parameters
        if item_per_page <= 0:
            item_per_page = 5
        if page_num <= 0:
            page_num = 1
        # note: it is possible that return value is empty list.
        return self.getLeaderboard((page_num-1)*item_per_page,
                                    page_num*item_per_page-1,
                                            reverse,with_score)
    def getWholeLeaderboard(self,reverse=False,with_score=False):
        """Return the whole leaderboard."""
        return self.getLeaderboard(0,-1,reverse,with_score)

II、分布式锁

1、我们通常所提到的锁,一般都是在本地环境下对多线程完成互斥操作,而如果共享资源存在于网络上,本地的锁就不起作用了。
互斥访问某个网络资源的时候,需要有一个在网络上的锁服务器,负责锁的申请与回收,Redis就可以作为这种分布式锁的服务器。

2、因为Redis是单进程单线程的工作模式,所以前来申请锁资源的请求都被排队处理,能保证锁资源的同步访问。

3、实现:可以在Redis服务器设置一个键值对,用以表示一把分布式互斥锁,当申请锁的时候,申请方SET这个键值对,当释放锁的时候,释放方DEL这个键值对:

lock = redis.get("mutex_lock");
if(!lock)
    error("apply the lock error.");
else
    -- 确定可以申请锁
    redis.set("mutex_lock","locking");
    do_something();

4、上述的申请方法,涉及到客户端与Redis服务器的多次交互,当客户端确定可以加锁的时候,可能这时候锁已经被其他客户端申请了,最终导致两个客户端同时持有锁。解决这个问题的方法就是将申请/释放锁的逻辑都放在服务器上。Redis Lua脚本可以完成这个问题:

-- apply for lock
local key = KEYS[1]
local res = redis.call('get', key)

-- 锁被占用,申请失败
if res == '0' then
return -1

-- 锁可以被申请
else
local setres = redis.call('set', key, 0)
if setres['ok'] == 'OK' then
return 0
end
end
return -1

get 命令不成功返回(nil).
实验命令:保存lua 脚本redis-cli script load ”$(cat mutex_lock.lua)”
-- releae lock
local key = KEYS[1]
local setres = redis.call('set', key, 1)
if setres['ok'] == 'OK' then
return 0
return -1

5、死锁问题

分布式锁由于客户端的崩溃很容易造成锁无法释放,从而出现死锁。所以需要使用Redis的TTL功能,设置超时释放:

-- apply for lock
local key = KEYS[1]
local timeout = KEYS[2]

local res = redis.call('get', key)

-- 锁被占用,申请失败
if res == '0' then
return -1
-- 锁可以被申请
else
local setres = redis.call('set', key, 0)
local exp_res = redis.call('pexpire', key, timeout)
if exp_res == 1 then
return 0
end
end
return -1

6、此外如出现服务器宕机,也会出现死锁问题,这就需要slave服务器。

III、消息中间件

1、消息中间件可以理解为分布式的消息队列
消息中间件负责接收生产者的消息,并存储转发给对应的消费者,生产者按照topic发布消息,消费者按topic订阅各种消息。生产者只需要向中间件中推送消息,不用等待消费者回应。

2、也就是说,这种分布式的消息中间件就是网络上的一个服务器,起到一个数据中转的功能。

IV、Web服务器存储session

1、以Redis作为session的存储,可以加速后台的处理速度,如购物车数据,就不必直接存储到磁盘数据库中,在这里Redis的作用就是一个缓存。

【参考】
[1] 《Redis源码日志》

你可能感兴趣的:(Redis应用场景)