美文网首页
redis知识点回顾(1)

redis知识点回顾(1)

作者: 指向远方的灯塔 | 来源:发表于2019-04-08 19:44 被阅读0次

    redis的诞生

    redis的创建者 是一个叫做antriez的意大利男士,个人网站http://invece.org/
    2007年和朋友创建了是一个访客信息追踪网站,网站可以通过JS脚本,将访客的IP地址,所属国家,阅览器信息,被访问页面的地址等数据传送给LLOOGG.com,然后LLOOGG.com将这些数据通过web页面实时地展示给用户,并存储起最新的5至10000条浏览记录以便进行查阅。为了记录每个被追踪的浏览信息,LLOOGG.com需要为每个被追踪的网站创建一个列表,每个列表需要根据用户的设置,存储最新的5至10000条浏览记录。当时使用mysql数据库。

    为了解决负载问题,自己写一个具有列表结构的内存数据库原型,这个数据库原型支持复杂的推入和弹出操作,并且将数据存储在内存而不是硬盘,所以程序的性能不会受到硬盘I/O限制,可以以极快的速度执行针对列表的推入和弹出操作,解决了LLOOGG.com当时的负载问题,于是antriez使用c语言重写了这个内存数据库,并给它加上持久化功能,Redis就诞生!

    Redis的演进

    图片如下:


    redis的演进

    Redis的独特性

    1.独特的键值对模型
    很多数据库只能处理一种数据结构
    Redis也是键值对数据库,但和Memcached不同的是,Redis的值不仅可以是字符串,它还可以其他5种数据结构中的任意一种。通过选用不同的数据结构,用户可以使用Redis解决各式各样的问题。
    它的键是字符串,关联的值可以是字符串,列表,散列,集合,有序集合,哈希;

    2.内存存储,速度极快

    硬盘数据库的工作模式:数据存储在硬盘,储存索引在内存
    内存数据库的工作模式:数据直接存储在内存中。


    两种数据库的工作模式

    3.丰富的附加功能


    丰富的附加功能

    4.良好的支持
    5.广泛的使用


    广泛的使用

    一.字符串键

    redis中最简单的数据结构,可以储存文字(比如:"hello world"),数字(比如整数和浮点数),二进制数据

    字符串基本操作
    1.为字符串键设置值
    001
    set key value
    

    将字符串键key的值设置为value,命令返回OK表示设置成功;如果字符串键key已经存在,那么用新值覆盖原来的旧值。

    set msg "hello world"
    set msg "goodbye"//覆盖原来的值"hello world"
    
    set key value [NX|XX]
    
    set命令还支持可选的NX选项和XX选项:

    如果给定了NX选项,那么命令仅在键key不存在的情况下,才进行设置操作;如果键key存在,set...NX命令不做动作(不会覆盖旧值)
    如果给定了XX选项,那么命令仅在键key已经存在的情况下,才进行设置操作;如果键key不存在,set..XX命令不做动作(一定会覆盖旧值)
    在给定NX选项和XX选项的情况下,set命令在设置成功时返回OK,设置失败时返回nil

    示例:使用Redis来进行缓存

    我们可以使用Redis来缓存一些经常会被用到,或者需要耗费大量资源的内容,通过将这些内容放到Redis里面(也即是内存里面),程序可以以极快的速度取得这些内容。


    redis缓存
    缓存程序的API及其实现
    缓存程序的API及其实现

    cache.py

    class Cache:
    
        def __init__(self, client):
            self.client = client
    
        def put(self, name, content):
            self.client.set(name, content)
    
        def get(self, name):
            return self.client.get(name)
    
    
    仅在键不存在的情况下进行设置

    SETNX key value
    键不存在并且设置成功时,命令返回1,因为键已经存在而导致设置失败时,命令返回0;复杂度为O(1).

    同时设置或获取多个字符串键的值

    示例:设置或获取个人信息


    同时设置或获取多个字符串键的值
    键的命名
    键的命名
    一次设置多个不存在的键

    MSETNX key value[key value...]
    只有在所有给定键不存在的情况下,MSETNX会给所有规定键设置值,效果和执行多个SETNX一样,如果给定的键至少有一个是存在的,那么MSETNX将不执行任何设置任务。
    返回1 表示设置成功,返回0表示设置失败。复杂度为O(N),N为给定的键的个数

    设置新值并返回旧值

    GETSET key new-value
    将字符串键的值设置为new-value,并返回字符串键在设置新值之前储存的旧值(old value).复杂度为O(1).
    set getset-str "i am old value"
    getset getset-str "i am new value"
    get getset-str

    追加内容到字符串末尾

    APPEND key value
    将值value推入到字符串键key已储存内容的末尾
    复杂度为O(N),其中N为被推入值的长度
    set myPhone "nokia"
    append myPhone "-1110"
    get myPhone

    返回值的长度

    strlen key
    返回字符串键key储存的值的长度
    因为redis会记录每个字符串值的长度,所以获取该值的复杂度为O(1)
    set msg "hello"
    strlen msg
    append msg " world"
    strlen msg


    002

    索引


    索引

    范围设值(值的更改)
    setrange key index value
    从索引index开始,用value覆写给定key所储存的字符串值。只接受正数索引,命令返回覆写之后,字符串值的长度,复杂度为O(N),N为value的长度。
    set msg "hello"//ok
    setrange msg 1 "appy"//(integer)5
    get msg //"happy"

    范围取值
    getrange key start end


    范围取值

    数字操作

    设置和获取数字
    数字参照
    增加或者减少数字的值
    01
    02
    增一或减一
    02

    示例:计数器(counter)
    很多网站都使用了计数器来记录页面被访问的次数。
    每当用户访问页面时,程序首先将页面访问计数器的值增一,然后将计数器当前的值返回给用户观看,以便用户通过页面的访问次数来判断页面内容的受关注程度。
    使用字符串键以及INCR,GET等命令,我们也可以实现这样的计数器。

    计数器API及其实现


    计数器API及其实现

    counter.py

    # encoding: utf-8
    
    class Counter:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def incr(self, n=1):
            counter = self.client.incr(self.key, n)
            return int(counter)
    
        def decr(self, n=1):
            counter = self.client.decr(self.key, n)
            return int(counter)
    
        def reset(self, n=0):
            counter = self.client.getset(self.key, n)
            if counter is None:
                counter = 0
            return int(counter)
    
        def get(self):
            counter = self.client.get(self.key)
            if counter is None:
    
    

    示例:id生成器
    很多网站在创建新条目的时候,都会使用id生成器来为条目创建唯一标识符。

    举个例子,对于一个论坛来说,每注册一个新用户,论坛都会为这个新用户创建一个用户id,比如12345,然后访问/user/12345就可以看到这个用户的个人页面。
    又比如说,当论坛里的用户创建一个新帖子的时候,论坛都会为这个新帖子创建一个帖子id,比如10086,然后访问/topic/10086就可以看到这个帖子的内容。

    被创建的id通常都是连续的,比如说,如果最新创建的id为1003,那么下一个生成的id就会是1004,再下一个id就是1005,以此类推。


    03

    id生成器API及其实现


    id生成器API及其实现
    id_generator.py
    class IdGenerator:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def init(self, n):
            self.client.set(self.key, n)
    
        def gen(self):
            new_id = self.client.incr(self.key)
            return int(new_id)
    
    
    浮点数的自增和自减

    incrbyfloat key increment

    为字符串键key储存的值加上浮点数增量increment,命令返回操作执行之后键key的值。
    没有相应的decrbyfloat,但可以通过给定负值来达到decrbyfloat的效果。
    复杂度为O(1).

    set num 10//ok
    incrbyfloat num 3.14//"13.14"
    incrbyfloat num -2.04//"11.1"通过传递负值来达到减法的效果

    注意事项

    即使字符串键储存的是数字值,它也可以执行append、strlen、setrange和getrange.当用户针对一个数字值执行这些命令的时候,Redis会先将数字值转换为字符串,然后再执行命令。
    set number 123 //ok
    strlen number //3 转换为"123",然后计算这个字符串的长度
    append number 456 //6 转换为"123",然后与"456"进行拼接
    get number //123456

    二进制数据操作

    set、get、setnx、append等命令同样可以用于设置二进制数据。

    //因为Redis自带的客户端redis-cli没办法方便的设置二进制数据。所以这里使用python客户端来进行

    import redis
    r = redis.Redis()
    r.set('bits',0b10010100) //True 将字符串键bits的值设置为二进制10010100
    bin(int(r.get('bits'))) //'0b10010100' 获取字符串键bits储存的二进制值(需要进行转换)
    r.append('bits',0b111) //4L 将0b111(也即是十进制的7)推入到bit已有二进制位的末尾
    bin(int(r.get('bit')))  //'0b10111001111' 推入之后的值为0b10111001111=1487
    
    二进制位的索引
    二进制位的索引
    设置二进制位的值

    setbit key index value
    将给定索引上的二进制位的值设置为value,命令返回被设置的位原来储存的旧值。


    04
    获取二进制位的值

    getbit key index 返回给定索引上的二进制位的值。复杂度为O(1)。


    05
    计算值为1的二进制位的数量

    bitcount key [start] [end]
    计算并返回字符串键储存的值中被设置为1的二进制位的数量。

    一般情况下,给定的整个字符串键都会进行计数操作,但通过指定额外的start或end参数,可以让计数只在特定索引范围的位上进行。

    start和end参数的设置和getrange命令类似,都可以使用负数值:比如-1表示最后一个位,而-2表示倒数第二个位,以此类推。
    复杂度为O(N),其中N为被计算二进制位的数量。

    二进制位运算

    bitop operation destkey key[key...]
    对一个或多个保存二进制位的字符串键执行位元操作,并将结果保存到destkey上。operation可以是and、or、not、not、xor这四种操作中的任意一种:


    06
    07

    示例:实现在线人数统计


    08
    在用户id和位索引之间进行关联
    09

    在线用户统计的API及其实现


    10

    online_count.py

    class OnlineCount:
    
        def __init__(self, when, client):
            self.when = when
            self.client = client
    
        def include(self, user_id):
            return self.client.setbit(self.when, user_id, 1)
    
        def result(self):
            return self.client.bitcount(self.when)
    
    

    关于用户在线统计的更多信息


    11

    示例:使用Redis缓存热门图片

    图片网站要储存大量的图片(通常放在硬盘里面),而少部分热门的图片会经常地被访问到。
    为了加快网站获取热门图片的速度,我们可以利用Redis能够储存二进制数据这一特性,使用之前构建
    缓存程序来缓存图片网站中的热门图片。


    12
    储存中文时的注意事项

    一个英文字符只需要使用单个字节来储存,而一个中文字符却需要使用多个字节来储存。
    strlen,setrange和getrange都是为英文设置的,它们只会在字符为单个字节的情况下正常工作,而一旦我们储存的是类似中文这样的多字节字符,那么这三个命令就不在适用了。


    13

    strlen示例


    14
    setrange和getrange的示例
    15
    字符串键的总结
    字符串键的总结

    二.散列键

    一个散列由多个域值对(field-value pair)组成,散列的域和值都可以是文字、整数、浮点数或者二进制数据。
    同一个散列里面的每个域必须是独一无二、各不相同的,而域内的值可以是重复的。
    通过命令,用户可以对散列执行设置域值对、获取域的值、检查域是否存在等操作,也可以让Redis返回散列包含的所有域、所有值或者所有域值对。


    一个散列

    散列的基本操作

    关联域值对

    hset key field value
    在散列键key中关联给定的域值对field和value。
    如果域field之前没有关联值,那么命令返回1;
    如果field已经有关联值,那么命令用新值覆盖旧值,并返回0
    复杂度为O(1).
    HSET message "id" 10086 //1
    HSET message "sender" "peter" //1
    HSET message "receiver" "jack" //1

    获取域关联的值

    hget key field
    返回散列键key中,域field所关联的值。如果域field没有关联值,那么返回
    nil。复杂度为O(1)。


    16
    仅当域不存在时,关联域值对

    HSETNX key field value

    如果散列键key中,域field不存在(也即是,还没有与之相关联的值),那么关联给定的域值对field和value。如果域field已经有与之相关联的值,那么命令不做动作。复杂度为O(1)。

    HSETNX message "content" "Good morning,jack!" //1
    HSETNX message "content" "Good morning,jack!" //0

    检查域是否存在

    HEXISTS key field
    查看散列键key中,给定域field是否存在:存在返回1,不存在返回0.复杂度为O(1)。

    hexists message "id" //1
    hexists message "sender" //1
    hexists message "age" //0
    hexists message "NotExistsField" //0

    删除给定的域值对

    hdel key field[field...]
    删除散列键key中的一个或多个指定域,以及那些域的值。不存在的域将被忽略。命令返回被成功删除的域值对数量。
    复杂度为O(N),N为被删除的域值对数量。

    hdel message "id" //1
    hdel message "receiver" //1
    hdel message "sender" //1

    获取散列包含的键值对数量

    HLEN key
    返回散列键key包含的域值对数量,复杂度为O(1)

    hlen message //4
    hdel message "date" //1
    hlen message //3

    批量操作

    一次对多个域、多个值或者多个域值对进行操作。


    17
    获取散列包含的所有域、值、或者域值对
    18

    数字操作

    和字符串键的值一样,在散列里面,域的值也可以被解释为数字,并执行相应的数字操作。

    对域的值执行自增操作
    19
    20
    使用散列的好处

    (1)将数据放到同一个地方,而并不是直接分散地储存在整个数据库里面,这不仅方便数据管理,还避免误操作的发生。


    21

    (2)避免键名冲突


    22
    23
    (3)减少内存占用

    在一般情况下,保存相同数量的键值对信息,使用散列键比使用字符串键更节约内存。
    因为在数据库里面创建的每一个键都会带有数据库附加的管理信息(比如这个键的类型、最后一次被访问的时间等等),所以数据库里面的键越多,服务器在储存附加管理信息方面耗费的内存就越多,花在管理数据库键上的CPU也会越多。
    除此之外,当散列包含的域值对数量比较少的时候,Redis会自动使用一种占用内存非常少的数据结构来做散列的底层实现,在散列的数量比较多的时候,这一措施对减少内存有很大的帮助。

    结论
    23

    示例:使用散列重新实现计数器


    24
    # 保存所有计数器的散列键
    COUNTER_KEY = 'hash-counter'
    
    class Counter:
    
        def __init__(self, name, client):
            self.name = name
            self.client = client
    
        def incr(self, n=1):
            counter = self.client.hincrby(COUNTER_KEY, self.name, n)
            return int(counter)
    
        def decr(self, n=1):
            minus_n = -n
            counter = self.client.hincrby(COUNTER_KEY, self.name, minus_n)
            return int(counter)
    
        def reset(self, n=0):
            counter = self.client.hget(COUNTER_KEY, self.name)
            if counter is None:
                counter = 0
            self.client.hset(COUNTER_KEY, self.name, n)
            return int(counter)
    
        def get(self):
            counter = self.client.hget(COUNTER_KEY, self.name)
            if counter is None:
                counter = 0
            return int(counter)
    

    列表

    以有序的方式储存多个可重复的值


    01
    1.推入和弹出操作

    了解如何向列表添加项,及如何从列表里面删除项


    02
    03
    04
    05
    06
    07
    2.长度、索引和范围操作
    01
    02
    03
    3.列表示例
    01
    02
    03
    04
    # encoding: utf-8
    
    def create_timeline_key(user_name):
        """
        创建 'user::<name>::timeline' 格式的时间线键名
        举个例子,输入 'huangz' 将返回键名 'user::huangz::timeline'
        """
        return 'user::' + user_name + '::timeline'
    
    
    class Timeline:
    
        def __init__(self, user_name, client):
            self.key = create_timeline_key(user_name)
            self.client = client
    
        def push(self, message_id):
            return self.client.lpush(self.key, message_id)
    
        def fetch_recent(self, n):
            return self.client.lrange(self.key, 0, n-1)
    
        def fetch_from_index(self, start_index, n):
            return self.client.lrange(self.key, start_index, start_index+n-1)
    
    05
    4.插入和删除操作
    01
    02
    03
    04
    05
    06
    07
    08
    # encoding: utf-8
    
    class MessageQueue:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def enqueue(self, item):
            self.client.lpush(self.key, item)
    
        def dequeue(self, timeout):
            result = self.client.brpop(self.key, timeout)
            if result:
                poped_list, poped_item = result
                return poped_item
    
        def length(self):
            return self.client.llen(self.key)
    
        def get_all_items(self):
            return self.client.lrange(self.key, 0, -1)
    
    09
    5.阻塞式弹出操作
    01
    02
    03
    04
    05
    06
    07
    08
    09
    # encoding: utf-8
    
    class FixedFIFO:
    
        def __init__(self, key, max_length, client):
            self.key = key
            self.max_length = max_length
            self.client = client
    
        def enqueue(self,item):
            # 这里存在一个竞争条件:
            # 如果客户端在 LPUSH 成功之后断线
            # 那么队列里将有超过最大长度数量的值存在
            # 等我们学习了事务之后就来修复这个竞争条件
            # 将值推入列表
            self.client.lpush(self.key, item)
            # 如果有必要的话,进行修剪以便让列表保持在最大长度之内
            self.client.ltrim(self.key, 0, self.max_length-1)
            # 返回 1 表示入队成功
            return 1
    
        def dequeue(self):
            return self.client.rpop(self.key)
    
        def get_all_items(self):
    
    a
    b
    6复习
    a

    集合

    储存多个各不相同的元素


    a
    1.元素操作
    a
    b
    c
    d
    e
    f
    g
    h
    image.png
    # encoding: utf-8
    
    class Vote:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def cast(self, user):
            # 因为 SADD 命令会自动忽略已存在的元素
            # 所以我们无须在投票之前检查用户是否已经投票
            self.client.sadd(self.key, user)
    
        def undo(self, user):
            self.client.srem(self.key, user)
    
        def is_voted(self, user):
            if self.client.sismember(self.key, user):
                return True
            else:
                return False
    
        def voted_members(self):
            return self.client.smembers(self.key)
    
    
    image.png
    image.png
    # encoding: utf-8
    
    class Tag:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def add(self, *tags):
            self.client.sadd(self.key, *tags)
    
        def remove(self, *tags):
            self.client.srem(self.key, *tags)
    
        def is_include(self, tag):
            return self.client.sismember(self.key, tag)
    
        def get_all(self):
            return self.client.smembers(self.key)
    
        def count(self):
            return self.client.scard(self.key)
    
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    # encoding: utf-8
    
    class Lottery:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def add_player(self, *users):
            self.client.sadd(self.key, *users)
    
        def get_all_players(self):
            return self.client.smembers(self.key)
    
        def player_count(self):
            return self.client.scard(self.key)
    
        def draw(self, n):
            return self.client.srandmember(self.key, n)
    
    
    image.png
    2.集合运算操作

    计算并集,交集和差集


    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    # encoding: utf-8
    
    class ItemFilter:
    
        def __init__(self, client):
            self.client = client
            self.options = set()
    
        def add_option(self, item_set):
            self.options.add(item_set)
    
        def result(self):
            return self.client.sinter(*self.options)
    
        def store_result(self, key):
            return self.client.sinterstore(key, *self.options)
    
    image.png
    3.复习

    有序集合

    按照元素的分值来有序的存储不同的元素


    image.png
    image.png
    1.有序集合的基本操作
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    2.范围操作

    基于有序集合的排序性质,对处于某种范围之内的多个元素进行操作


    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    # encoding: utf-8
    
    class RankList:
    
        def __init__(self, key, client):
            self.key = key
            self.client = client
    
        def incr(self, item, increment=1):
            # 注意在 redis-py 客户端中
            # zincrby() 方法是先给定元素后给定分值
            # 这和 ZINCRBY 命令规定的顺序正好相反
            self.client.zincrby(self.key, item, increment)
    
        def get_top(self, n, show_score=False):
            return self.client.zrevrange(self.key, 0, n-1, withscores=show_score)
    
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    # encoding: utf-8
    
    class AutoComplete:
    
        def __init__(self, client):
            self.client = client
    
        def feed(self, phrase):
            # 对于输入 u'周杰伦' 来说
            # 这个 for 循环会执行以下命令:
            # ZINCRBY u'auto-complete::周'     u'周杰伦' 1
            # ZINCRBY u'auto-complete::周杰'   u'周杰伦' 1
            # ZINCRBY u'auto-complete::周杰伦' u'周杰伦' 1
            for i in range(len(phrase)+1):
                key = 'auto-complete::' + phrase[:i]
                self.client.zincrby(key, phrase, 1)
    
        def get_hint(self, phrase, n):
            key = 'auto-complete::' + phrase
            result = []
            for hint in self.client.zrevrange(key, 0, n-1):
                result.append(unicode(hint, 'utf-8'))
            return result
    
    image.png
    3.集合运算操作

    计算并存储多个有序集合的交集、并集的运算结果


    image.png
    image.png
    image.png
    image.png
    4.复习
    image.png

    HyperLogLog

    使用常量空间估算大量元素的基数
    问题:如何记录网站每天获得的独立IP的数量


    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    # encoding: utf-8
    
    class UniqueCounter:
    
        def __init__(self, client, key):
            self.client = client
            self.key = key
    
        def include(self, element):
            self.client.pfadd(self.key, element)
    
        def result(self):
            return self.client.pfcount(self.key)
    
    image.png
    image.png
    复习
    image.png

    数据库

    image.png
    image.png
    1.处理数据库中的单个键

    查看键的类型、删除键、检查键是否存在、修改键的名字


    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    2.对键的值进行排序

    SORT命令以及他们的各个参数及选项


    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    3.获取数据库中的键

    随机获取数据库中的某个键,遍历数据库中的所有键


    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png

    相关文章

      网友评论

          本文标题:redis知识点回顾(1)

          本文链接:https://www.haomeiwen.com/subject/adunpqtx.html