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非常非常的像了。

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

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

  1. 127.0.0.1:6379> set mynum "2"
  2. OK
  3. 127.0.0.1:6379> get mynum
  4. "2"
  5. 127.0.0.1:6379> incr mynum
  6. (integer) 3
  7. 127.0.0.1:6379> get mynum
  8. "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中指定一个范围来提取元素。我们来看几个例子:

  1. //新建一个list叫做mylist,并在列表头部插入元素"1"
  2. 127.0.0.1:6379> lpush mylist "1"
  3. //返回当前mylist中的元素个数
  4. (integer) 1
  5. //在mylist右侧插入元素"2"
  6. 127.0.0.1:6379> rpush mylist "2"
  7. (integer) 2
  8. //在mylist左侧插入元素"0"
  9. 127.0.0.1:6379> lpush mylist "0"
  10. (integer) 3
  11. //列出mylist中从编号0到编号1的元素
  12. 127.0.0.1:6379> lrange mylist 0 1
  13. 1) "0"
  14. 2) "1"
  15. //列出mylist中从编号0到倒数第一个元素
  16. 127.0.0.1:6379> lrange mylist 0 -1
  17. 1) "0"
  18. 2) "1"
  19. 3) "2"

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

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

redis数据结构 – 集合

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

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

  1. //向集合myset中加入一个新元素"one"
  2. 127.0.0.1:6379> sadd myset "one"
  3. (integer) 1
  4. 127.0.0.1:6379> sadd myset "two"
  5. (integer) 1
  6. //列出集合myset中的所有元素
  7. 127.0.0.1:6379> smembers myset
  8. 1) "one"
  9. 2) "two"
  10. //判断元素1是否在集合myset中,返回1表示存在
  11. 127.0.0.1:6379> sismember myset "one"
  12. (integer) 1
  13. //判断元素3是否在集合myset中,返回0表示不存在
  14. 127.0.0.1:6379> sismember myset "three"
  15. (integer) 0
  16. //新建一个新的集合yourset
  17. 127.0.0.1:6379> sadd yourset "1"
  18. (integer) 1
  19. 127.0.0.1:6379> sadd yourset "2"
  20. (integer) 1
  21. 127.0.0.1:6379> smembers yourset
  22. 1) "1"
  23. 2) "2"
  24. //对两个集合求并集
  25. 127.0.0.1:6379> sunion myset yourset
  26. 1) "1"
  27. 2) "one"
  28. 3) "2"
  29. 4) "two"

redis数据结构 – 有序集合

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

  1. 127.0.0.1:6379> zadd myzset 1 baidu.com
  2. (integer) 1
  3. //向myzset中新增一个元素360.com,赋予它的序号是3
  4. 127.0.0.1:6379> zadd myzset 3 360.com
  5. (integer) 1
  6. //向myzset中新增一个元素google.com,赋予它的序号是2
  7. 127.0.0.1:6379> zadd myzset 2 google.com
  8. (integer) 1
  9. //列出myzset的所有元素,同时列出其序号,可以看出myzset已经是有序的了。
  10. 127.0.0.1:6379> zrange myzset 0 -1 with scores
  11. 1) "baidu.com"
  12. 2) "1"
  13. 3) "google.com"
  14. 4) "2"
  15. 5) "360.com"
  16. 6) "3"
  17. //只列出myzset的元素
  18. 127.0.0.1:6379> zrange myzset 0 -1
  19. 1) "baidu.com"
  20. 2) "google.com"
  21. 3) "360.com"

redis数据结构 – 哈希

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

  1. //建立哈希,并赋值
  2. 127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34
  3. OK
  4. //列出哈希的内容
  5. 127.0.0.1:6379> HGETALL user:001
  6. 1) "username"
  7. 2) "antirez"
  8. 3) "password"
  9. 4) "P1pp0"
  10. 5) "age"
  11. 6) "34"
  12. //更改哈希中的某一个值
  13. 127.0.0.1:6379> HSET user:001 password 12345
  14. (integer) 0
  15. //再次列出哈希的内容
  16. 127.0.0.1:6379> HGETALL user:001
  17. 1) "username"
  18. 2) "antirez"
  19. 3) "password"
  20. 4) "12345"
  21. 5) "age"
  22. 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数据后记录如下:

  1. Set login:1:login_times 5
  2. Set login:2:login_times 1
  3. Set login:3:login_times 2
  4. Set login:1:last_login_time 2011-1-1
  5. Set login:2:last_login_time 2011-2-1
  6. Set login:3:last_login_time 2011-3-1
  7. set login:1:name "ken thompson"
  8. set login:2:name "dennis ritchie"
  9. set login:3:name "Joe Armstrong"

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

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

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

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

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

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

  1. select * from login order by last_login_time desc limit N

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

  1. select * from login order by login_times desc limit N

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

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

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

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

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

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

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

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

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

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

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

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

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

Redis数据库的更多相关文章

  1. MySQL、MongoDB、Redis数据库Docker镜像制作

    MySQL.MongoDB.Redis数据库Docker镜像制作 在多台主机上进行数据库部署时,如果使用传统的MySQL的交互式的安装方式将会重复很多遍.如果做成镜像,那么我们只需要make once ...

  2. Spring + Jedis集成Redis(集群redis数据库)

    前段时间说过单例redis数据库的方法,但是生成环境一般不会使用,基本上都是集群redis数据库,所以这里说说集群redis的代码. 1.pom.xml引入jar <!--Redis--> ...

  3. 超强、超详细Redis数据库入门教程

    这篇文章主要介绍了超强.超详细Redis入门教程,本文详细介绍了Redis数据库各个方面的知识,需要的朋友可以参考下 [本教程目录] 1.redis是什么2.redis的作者何许人也3.谁在使用red ...

  4. 深入浅出Redis02 使用Redis数据库(String类型)

    一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set 设置Key对应的值为String 类型的value. 例子:向 Redis数据库中插入一条数据类型为S ...

  5. Redis数据库的使用与介绍

    本周11-15号开始用Redis数据库在现有的平台基础上开发一个独立模块,这是一个边学习.边记录.边交流.边开发.边总结的过程.大部分随笔都是个人的“工作日志”,旨在记录自己学习过程中收集的一些资料, ...

  6. node.js应用Redis数据库

    node.js下使用Redis,首先: 1.有一台安装了Redis的服务器,当然,安装在本机也行 2.本机,也就是客户端,要装node.js 3.项目要安装nodejs_redis模块 注意第 3 点 ...

  7. Ubuntu 安装和配置redis数据库

    Ubuntu 14.04下安装和配置redis数据库 小编现在在写一个分布式爬虫,要用到这个数据库,所以分享一下小编是如何安装和配置的,希望对大家有帮助. 工具/原料   Ubuntu 系统电脑一台 ...

  8. Redis数据库?-Redis的Virtual Memory介绍(转)

    众所周知,Redis是一个内存数据库,和Memcached类似,所有数据存在内存中,当然,Redis有rdb和appendonlyfile两个落地文件,可以对断电停机等故障下的数据恢复做一些保证.但是 ...

  9. php redis数据库操作类

    <?php namespace iphp\db; use iphp\App; /** * redis操作类 * 说明,任何为false的串,存在redis中都是空串. * 只有在key不存在时, ...

  10. Windows下安装Redis数据库并实现C#访问

    1.Redis在Windows下的安装 目前Redis官方并不支持Redis的Windows版本,需要去GitHub下载. GitHub上的Redis分两种,一种是以命令行形式安装的,一种是以Wind ...

随机推荐

  1. mapreduce多文件输出的两方法

    mapreduce多文件输出的两方法   package duogemap;   import java.io.IOException;   import org.apache.hadoop.conf ...

  2. 在离线环境中使用.NET Core

    在离线环境中使用.NET Core 0x00 写在开始 很早开始就对.NET Core比较关注,一改微软之前给人的印象,变得轻量.开源.跨平台.最近打算试着在工作中使用.但工作是在与互联网完全隔离的网 ...

  3. 回首经典的SQL Server 2005

    原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com SQL Server是我使用时间最长的数据库,算起来已经有10年了.上世纪90年代,微软在软件开发的所有领域高歌猛 ...

  4. javascript表单的Ajax 提交插件的使用

    Ajax 提交插件 form.js 表单的下载地址:官方网站:http://malsup.com/jquery/form/ form.js 插件有两个核心方法:ajaxForm()和ajaxSubmi ...

  5. 在centos7中添加一个新用户,并授权

    前言 笔记本装了一个centos,想要让别人也可以登录访问,用自己的账号确实不太好,于是准备新建一个用户给他. 创建新用户 创建一个用户名为:zhangbiao [root@localhost ~]# ...

  6. 走进缓存的世界(三) - Memcache

    系列文章 走进缓存的世界(一) - 开篇 走进缓存的世界(二) - 缓存设计 走进缓存的世界(三) - Memcache 简介 Memcache是一个高性能的分布式内存对象缓存系统,用于动态Web应用 ...

  7. thinkphp数据的查询和截取

    public function NewsList(){ $this->assign('title','news'); $p = I('page',1); $listRows = 6; $News ...

  8. 用Java代码实现拦截区域网数据包

    起因: 吃饭的时间在想如果区域网内都是通过路由器上网,那如何实现拦截整个区域网的数据包,从而实现某种窥探欲. 思路:      正常是通过电脑网卡预先设置或分配的IP+网关对路由器进行通讯,比如访问百 ...

  9. VPN连接常见错误汇总

    提示远程服务器没有响应. 这种情况有两种情况,一种是远程服务器出现故障.另一种是自己的电脑出现问题,具体原因我还没有找到,但是可以肯定的是注册表除了问题,一个终极的解决办法就是把注册表替换了.先将HK ...

  10. [jquery]jquery正则表达式验证(手机号、身份证号、中文名称)

    数字判断方法:isNaN()函数 test()方法 判断字符串中是否匹配到正则表达式内容,返回的是boolean值 ( true / false ) // 验证中文名称 function isChin ...