zoukankan      html  css  js  c++  java
  • Redis数据库

    Redis是k-v型数据库的典范,设计思想及数据结构实现都值得学习。

    1、数据类型

    value支持五种数据类型:
    1.字符串(strings)
    2.字符串列表(lists)
    3.字符串集合(sets)
    4.有序字符串集合(sorted sets)
    5.哈希(hashes)
    而关于key,有几个点要提醒大家:
    1.key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
    2.key也不要太短,太短的话,key的可读性会降低;
    3.在一个项目中,key最好使用统一的命名模式,例如user:10000:passwd。

    redis数据结构 – strings

    如果只使用redis中的字符串类型,且不使用redis的持久化功能,那么,redis就和memcache非常非常的像了。

    set mystr "hello world!" //设置字符串类型
    get mystr //读取字符串类型

    字符串类型的用法就是这么简单,因为是二进制安全的,所以你完全可以把一个图片文件的内容作为字符串来存储。另外,我们还可以通过字符串类型进行数值操作。

    127.0.0.1:6379> set mynum "2"
    OK
    127.0.0.1:6379> get mynum
    "2"
    127.0.0.1:6379> incr mynum
    (integer) 3
    127.0.0.1:6379> get mynum
    "3"

    看,在遇到数值操作时,redis会将字符串类型转换成数值。

    由于INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果,假如,在某种场景下有3个客户端同时读取了mynum的值(值为2),然后对其同时进行了加1的操作,那么,最后mynum的值一定是5。不少网站都利用redis的这个特性来实现业务上的统计计数需求。

    redis数据结构 – lists

    redis中的lists在底层实现上并不是数组,而是链表,也就是说对于一个具有上百万个元素的lists来说,在头部和尾部插入一个新元素,其时间复杂度是常数级别的,比如用LPUSH在10个元素的lists头部插入新元素,和在上千万元素的lists头部插入新元素的速度应该是相同的。

    虽然lists有这样的优势,但同样有其弊端,那就是,链表型lists的元素定位会比较慢,而数组型lists的元素定位就会快得多。lists的常用操作包括LPUSH、RPUSH、LRANGE等。我们可以用LPUSH在lists的左侧插入一个新元素,用RPUSH在lists的右侧插入一个新元素,用LRANGE命令从lists中指定一个范围来提取元素。我们来看几个例子:

    //新建一个list叫做mylist,并在列表头部插入元素"1"
    127.0.0.1:6379> lpush mylist "1"
    //返回当前mylist中的元素个数
    (integer) 1
    //在mylist右侧插入元素"2"
    127.0.0.1:6379> rpush mylist "2"
    (integer) 2
    //在mylist左侧插入元素"0"
    127.0.0.1:6379> lpush mylist "0"
    (integer) 3
    //列出mylist中从编号0到编号1的元素
    127.0.0.1:6379> lrange mylist 0 1
    1) "0"
    2) "1"
    //列出mylist中从编号0到倒数第一个元素
    127.0.0.1:6379> lrange mylist 0 -1
    1) "0"
    2) "1"
    3) "2"

    lists的应用相当广泛,随便举几个例子:

    1.我们可以利用lists来实现一个消息队列,而且可以确保先后顺序,不必像MySQL那样还需要通过ORDER BY来进行排序。
    2.利用LRANGE还可以很方便的实现分页的功能。
    3.在博客系统中,每片博文的评论也可以存入一个单独的list中。

    redis数据结构 – 集合

    redis的集合,是一种无序的集合,集合中的元素没有先后顺序。

    集合相关的操作也很丰富,如添加新元素、删除已有元素、取交集、取并集、取差集等。我们来看例子:

    //向集合myset中加入一个新元素"one"
    127.0.0.1:6379> sadd myset "one"
    (integer) 1
    127.0.0.1:6379> sadd myset "two"
    (integer) 1
    //列出集合myset中的所有元素
    127.0.0.1:6379> smembers myset
    1) "one"
    2) "two"
    //判断元素1是否在集合myset中,返回1表示存在
    127.0.0.1:6379> sismember myset "one"
    (integer) 1
    //判断元素3是否在集合myset中,返回0表示不存在
    127.0.0.1:6379> sismember myset "three"
    (integer) 0
    //新建一个新的集合yourset
    127.0.0.1:6379> sadd yourset "1"
    (integer) 1
    127.0.0.1:6379> sadd yourset "2"
    (integer) 1
    127.0.0.1:6379> smembers yourset
    1) "1"
    2) "2"
    //对两个集合求并集
    127.0.0.1:6379> sunion myset yourset
    1) "1"
    2) "one"
    3) "2"
    4) "two"

    redis数据结构 – 有序集合

    很多时候,我们都将redis中的有序集合叫做zsets,这是因为在redis中,有序集合相关的操作指令都是以z开头的,比如zrange、zadd、zrevrange、zrangebyscore等等

    127.0.0.1:6379> zadd myzset 1 baidu.com
    (integer) 1
    //向myzset中新增一个元素360.com,赋予它的序号是3
    127.0.0.1:6379> zadd myzset 3 360.com
    (integer) 1
    //向myzset中新增一个元素google.com,赋予它的序号是2
    127.0.0.1:6379> zadd myzset 2 google.com
    (integer) 1
    //列出myzset的所有元素,同时列出其序号,可以看出myzset已经是有序的了。
    127.0.0.1:6379> zrange myzset 0 -1 with scores
    1) "baidu.com"
    2) "1"
    3) "google.com"
    4) "2"
    5) "360.com"
    6) "3"
    //只列出myzset的元素
    127.0.0.1:6379> zrange myzset 0 -1
    1) "baidu.com"
    2) "google.com"
    3) "360.com"

    redis数据结构 – 哈希

    哈希是从redis-2.0.0版本之后才有的数据结构。 hashes存的是字符串和字符串值之间的映射,比如一个用户要存储其全名、姓氏、年龄等等,就很适合使用哈希。

    //建立哈希,并赋值
    127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34
    OK
    //列出哈希的内容
    127.0.0.1:6379> HGETALL user:001
    1) "username"
    2) "antirez"
    3) "password"
    4) "P1pp0"
    5) "age"
    6) "34"
    //更改哈希中的某一个值
    127.0.0.1:6379> HSET user:001 password 12345
    (integer) 0
    //再次列出哈希的内容
    127.0.0.1:6379> HGETALL user:001
    1) "username"
    2) "antirez"
    3) "password"
    4) "12345"
    5) "age"
    6) "34"

    2、设计思想和应用

    丰富的数据结构使redis的设计非常的有趣。不像关系型数据库那样,DEV和DBA需要深度沟通,review每行sql语句,也不像memcached,不需要DBA的参与。redis的DBA需要熟悉数据结构,并能了解使用场景。

    以用户登录系统为例,简化后只保留一张数据表,关系型数据库:

    mysql> select * from login; 
    +---------+----------------+-------------+---------------------+ 
    | user_id | name           | login_times | last_login_time     | 
    +---------+----------------+-------------+---------------------+ 
    |       1 | ken thompson   |           5 | 2011-01-01 00:00:00 | 
    |       2 | dennis ritchie |           1 | 2011-02-01 00:00:00 | 
    |       3 | Joe Armstrong  |           2 | 2011-03-01 00:00:00 | 
    +---------+----------------+-------------+---------------------+

    user_id表的主键,name表示用户名,login_times表示该用户的登录次数,每次用户登录后,login_times会自增,而last_login_time更新为当前时间。

    关系型数据转化为KV数据库:key为表名:主键值:列名 、value为列值。于是以上的关系数据转化成kv数据后记录如下:

    Set login:1:login_times 5 
    Set login:2:login_times 1 
    Set login:3:login_times 2 
    Set login:1:last_login_time 2011-1-1 
    Set login:2:last_login_time 2011-2-1 
    Set login:3:last_login_time 2011-3-1 
    set login:1:name "ken thompson"
    set login:2:name "dennis ritchie"
    set login:3:name "Joe Armstrong"

    这样在已知主键的情况下,通过get、set就可以获得或者修改用户的登录次数和最后登录时间和姓名。一般用户是无法知道自己的id的,只知道自己的用户名,所以还必须有一个从name到id的映射关系,这里的设计与上面的有所不同。

    set "login:ken thompson:id" 1 
    set "login:dennis ritchie:id" 2 
    set "login: Joe Armstrong:id" 3

    这样每次用户登录的时候业务逻辑如下(python版),r是redis对象,name是已经获知的用户名。

    #获得用户的id 
    uid = r.get("login:%s:id" % name) 
    #自增用户的登录次数 
    ret = r.incr("login:%s:login_times" % uid) 
    #更新该用户的最后登录时间 
    ret = r.set("login:%s:last_login_time" % uid, datetime.datetime.now())

    如果需求仅仅是已知id,更新或者获取某个用户的最后登录时间,登录次数,关系型和kv数据库无啥区别。一个通过btree pk,一个通过hash,效果都很好。考虑以下特殊的情况:

    假设有如下需求,查找最近登录的N个用户。开发人员看看,还是比较简单的,一个sql搞定。

    select * from login order by last_login_time desc limit N

    DBA了解需求后,考虑到以后表如果比较大,所以在last_login_time上建个索引。执行计划从索引leafblock 的最右边开始访问N条记录,再回表N次,效果很好。过了两天,又来一个需求,需要知道登录次数最多的人是谁。同样的关系型如何处理?DEV说简单

    select * from login order by login_times desc limit N

    DBA一看,又要在login_time上建立一个索引。有没有觉得有点问题呢,表上每个字段上都有素引。关系型数据库的数据存储的的不灵活是问题的源头,数据仅有一种储存方法,那就是按行排列的堆表。统一的数据结构意味着你必须使用索引来改变sql的访问路径来快速访问某个列的,而访问路径的增加又意味着你必须使用统计信息来辅助,于是一大堆的问题就出现了。

    没有索引,没有统计计划,没有执行计划,这就是kv数据库

    redis里如何满足以上的需求呢? 对于求最新的N条数据的需求,链表的后进后出的特点非常适合。我们在上面的登录代码之后添加一段代码,维护一个登录的链表,控制他的长度,使得里面永远保存的是最近的N个登录用户。

    #把当前登录人添加到链表里 
    ret = r.lpush("login:last_login_times", uid) 
    #保持链表只有N位 
    ret = redis.ltrim("login:last_login_times", 0, N-1)

    这样需要获得最新登录人的id,如下的代码即可

    last_login_list = r.lrange("login:last_login_times", 0, N-1)

    另外,求登录次数最多的人,对于排序,积分榜这类需求,sorted set非常的适合,我们把用户和登录次数统一存储在一个sorted set里。

    zadd login:login_times 5 1 
    zadd login:login_times 1 2 
    zadd login:login_times 2 3

    这样假如某个用户登录,额外维护一个sorted set,代码如此

    #对该用户的登录次数自增1 
    ret = r.zincrby("login:login_times", 1, uid)

    那么如何获得登录次数最多的用户呢,逆序排列取的排名第N的用户即可

    ret = r.zrevrange("login:login_times", 0, N-1)

    可以看出,DEV需要添加2行代码,而DBA不需要考虑索引什么的。

  • 相关阅读:
    windows7 端口查看以及杀死进程释放端口
    字符设备驱动模块与测试代码编写。
    c++项目范例
    较复杂makefile跟lds脚本程序的编写
    S5PV210时钟,看门狗定时器
    S5PV210中断处理
    arm 异常处理结构
    arm指令系统
    arm体系结构
    s5pv210 的启动
  • 原文地址:https://www.cnblogs.com/houkai/p/4690106.html
Copyright © 2011-2022 走看看