Redis是什么
redis是一个高性能的key-value数据库,它是彻底开源免费的,并且redis是一个NOSQL类型数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库。可是,它也是不能替代关系型数据库,只能做为特定环境下的扩充。redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合能够对关系数据库起到很好的补充做用。redis
Redis的特色:
1.速度快数据库
- Redis的全部数据都是存放在内存中的,因此把数据放在内存中是Redis速度快的最主要缘由。
- Redis是用C语言实现的,通常来讲C语言实现的程序“距离”操做系统更近,执行速度相对会更快。
- Redis使用了单线程架构,预防了多线程可能产生的竞争问题。
2.redis提供了丰富的数据结构编程
它主要提供了5种数据结构:string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。数组
3.丰富的功能缓存
除了5种数据结构,Redis还提供了许多额外的功能:tomcat
- 提供了键过时功能,能够用来实现缓存。
- 提供了发布订阅功能,能够用来实现消息系统。
- 支持Lua脚本功能,能够利用Lua创造出新的Redis命令。
- 提供了简单的事务功能,能在必定程度上保证事务特性。
- 提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到Redis,减小了网络的开销。
4.简单稳定安全
Redis的简单主要表如今三个方面。服务器
- Redis的源码不多。
- Redis使用单线程模型,这样不只使得Redis服务端处理模型变得简单,并且也使得客户端开发变得简单。
- Redis不须要依赖于操做系统中的类库(例如Memcache须要依赖libevent这样的系统类库),Redis本身实现了事件处理的相关功能。
Redis虽然很简单,可是不表明它不稳定。维护的上千个Redis为例,没有出现过由于Redis自身bug而宕掉的状况。
5.客户端语言多网络
Redis提供了简单的TCP通讯协议,不少编程语言能够很方便地接入到Redis,而且因为Redis受到社区和各大公司的普遍承认,因此支持Redis的客户端语言也很是多,几乎涵盖了主流的编程语言,例如Java、PHP、Python、C、C++、Nodejs等。
6.持久化
一般看,将数据放在内存中是不安全的,一旦发生断电或者机器故障,重要的数据可能就会丢失,所以Redis提供了两种持久化方式:RDB和AOF,便可以用两种策略将内存的数据保存到硬盘中(如图所示)这样就保证了数据的可持久性。
7.主从复制
Redis提供了复制功能,实现了多个相同数据的Redis副本(如图所示),复制功能是分布式Redis的基础。
Redis的工做流程
从图上能够看出,当一个客户端访问服务器的时候,客户端请求会先到达Nginx,由Nginx负责对数据进行分发,上传到多个服务器,当用户访问到tomcat1的时候,会进行登录验证并将session放入session管理中,使用Redis管理session的好处就是当第二次客户端登录后再进行操做,这时极可能到达tomcat2服务器,这时候tomcat2会从Redis中寻找session,从而避免了session只在一个服务器中形成第二次读取session发现为空的问题。
在shiro中,shiro也提供了一个分布式session的管理功能,但使用Redis更能集中管理。
Redis数据类型及使用场景
前面也提到了Redis支持五种数据类型,下面详细介绍一下这5种数据类型级使用场景
一、string
简单介绍一下,Strings数据类型是最经常使用、简单的key-value类型,普通的key/ value 存储均可以归为此类。value不只能够是字符串,也能够是数字。由于是二进制安全的,因此你彻底能够把一个图片文件的内容做为string来存储。Redis的string能够彻底实现目前memcached的功能,而且效率更高。除了提供与 Memcached 同样的get、set、incr、decr 等操做外,Redis还额外提供了下面一些操做:
- 获取字符串长度
- 往字符串append内容
- 设置和获取字符串的某一段内容
- 设置及获取字符串的某一位(bit)
- 批量设置一系列字符串的内容
经常使用命令:
set,get,decr,incr,mget 等。
应用场景:
- 应用 Memcached和CKV的全部场景。字符串和数字直接存取。结构化数据须要先序列化,再set到value;相应的,get到value后须要反序列化。
- 能够利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果。便可以用来实现业务上的统计计数需求。也可用于实现idmaker,即生成全局惟一的id。
- 存放session key,实现一个分布式session系统。Redis的key能够方便地设置过时时间,用于实现session key的自动过时。验证skey时先根据uid路由到对应的redis,如取不到skey,则表示skey已过时,须要从新登陆;如取到skey且校验经过则升级此skey的过时时间便可。
- Set nx或SetNx,仅当key不存在时才Set。能够用来选举Master或实现分布式锁:全部Client不断尝试使用SetNx master myName抢注Master,成功的那位不断使用Expire刷新它的过时时间。若是Master挂掉了key就会失效,剩下的节点又会发生新一轮抢夺。
- 借助redis2.6开始支持的lua脚本,能够实现更安全的2种分布式锁:一种适用于各进程竞争但老是单个进程获取锁并处理的场景。除非原处理进程挂掉于是锁过时才会被其它进程获取到锁。无须主动解锁。经过get、expire/pexpire、setnx ex| px的lua脚本实现;一种适用于各进程竞争获取锁并处理的场景。经过set nx ex| px获取锁,用完须要经过先get判断再del释放锁,不然在锁过时以前不能获取到锁。
- GetSet, 设置新值,返回旧值。好比实现一个计数器,能够用GetSet获取计数并重置为0。
- GetBit/SetBit/BitOp/BitCount, BitMap的玩法,好比统计今天的独立访问用户数时,每一个注册用户都有一个offset,他今天进来的话就把他那个位设为1,用BitCount就能够得出今天的总人数。
- Append/SetRange/GetRange/StrLen,对文本进行扩展、替换、截取和求长度,对特定数据格式很是有用。
实现方式:
String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操做时会转成数值型进行计算,此时redisObject的encoding字段为int。
二、List
简单介绍一下,List是一个双向链表,支持双向的Pop/Push,江湖规矩通常从左端Push,右端Pop——LPush/RPop,并且还有Blocking的版本BLPop/BRPop,客户端能够阻塞在那直到有消息到来。还有RPopLPush/ BRPopLPush,弹出来返回给client的同时,把本身又推入另外一个list,LLen获取列表的长度。还有按值进行的操做:LRem(按值删除元素)、LInsert(插在某个值的元素的先后),复杂度是O(N),N是List长度,由于List的值不惟一,因此要遍历所有元素,而Set只要O(log(N))。
按下标进行的操做:下标从0开始,队列从左到右算,下标为负数时则从右到左。LSet ,按下标设置元素值。LIndex,按下标返回元素。LRange,不一样于POP直接弹走元素,只是返回列表内一段下标的元素,是分页的最爱。LTrim,限制List的大小,好比只保留最新的20条消息。复杂度也是O(N),其中LSet的N是List长度,LIndex的N是下标的值,LRange的N是start的值+列出元素的个数,由于是链表而不是数组,因此按下标访问其实要遍历链表,除非下标正好是队头和队尾。LTrim的N是移除元素的个数。
经常使用命令:
lpush,rpush,lpop,rpop,lrange等。
应用场景:
- 各类列表,好比twitter的关注列表、粉丝列表等,最新消息排行、每篇文章的评论等也能够用Redis的list结构来实现。
- 消息队列,能够利用Lists的PUSH操做,将任务存在Lists中,而后工做线程再用POP操做将任务取出执行。这里的消息队列并无ack机制,若是消费者把任务给Pop走了又没处理完就死机了怎么办?解决方法之一是加多一个sorted set,分发的时候同时发到list与sorted set,以分发时间为score,用户把任务作完了以后要用ZREM消掉sorted set里的job,而且定时从sorted set中取出超时没有完成的任务,从新放回list。另外一个作法是为每一个worker多加一个的list,弹出任务时改用RPopLPush,将job同时放到worker本身的list中,完成时用LREM消掉。若是集群管理(如zookeeper)发现worker已经挂掉,就将worker的list内容从新放回主list。
- 利用LRANGE能够很方便的实现list内容分页的功能。
- 取最新N个数据的操做:LPUSH用来插入一个内容ID,做为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。若是用户须要的检索的数据量超越这个缓存容量,这时才须要把请求发送到数据库。
实现方式:
Redis list的实现为一个双向链表,便可以支持反向查找和遍历,更方便操做,不过带来了部分额外的内存开销,Redis内部的不少实现,包括发送缓冲队列等也都是用的这个数据结构。
三、Set
简单介绍一下,set是一种无序的集合,集合中的元素没有前后顺序,不重复。将重复的元素放入Set会自动去重。
经常使用命令:
sadd,spop,smembers,sunion等。
应用场景:
- 某些须要去重的列表,而且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
- 能够存储一些集合性的数据,好比在微博应用中,能够将一个用户全部的关注人存在一个集合中,将其全部粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操做,能够很是方便的实现如共同关注、共同喜爱、二度好友等功能,对上面的全部集合操做,你还可使用不一样的命令选择将结果返回给客户端仍是存集到一个新的集合中。又好比QQ有一个社交功能叫作“好友标签”,你们能够给你的好友贴标签,好比“大美女”、“土豪”、“欧巴”等等,这里也能够把每个用户的标签都存储在一个集合之中。
- 想要知道某些特定的注册用户或IP地址,他们到底有多少访问了某个页面,能够这样实现:SADD page:day1:。想知道特定用户的数量,使用SCARD page:day1:。 须要测试某个特定用户是否访问了这个页面?SISMEMBER page:day1:。
实现方式:
set 的内部实现是一个 value永远为null的HashMap,实际就是经过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的缘由。
四、zest
简单介绍一下,有序集合,相比set,元素放入集合时还要提供该元素的分数,可根据分数自动排序。
经常使用命令:
zadd,zrange,zrem,zcard等
使用场景:
- 存放一个有序的而且不重复的集合列表,好比twitter 的public timeline能够以发表时间做为score来存储,这样获取时就是自动按时间排好序的。
- 能够作带权重的队列,好比普通消息的score为1,重要消息的score为2,而后工做线程能够选择按score的倒序来获取工做任务。让重要的任务优先执行。
- 排行榜相关:ZADD leaderboard。 获得前100名高分用户很简单:ZREVRANGE leaderboard 0 99。用户的全球排名也类似,只须要执行:ZRANK leaderboard。
- 新闻按照用户投票和时间排序,ZADD时的score = points / time^alpha, 这样用户的投票会相应的把新闻挖出来,但时间会按照必定的指数将新闻埋下去。
- 过时项目处理:使用unix时间做为关键字,用来保持列表可以按时间排序。对current_time和time_to_live进行检索,完成查找过时项目的艰巨任务。另外一项后台任务使用ZRANGE...WITHSCORES进行查询,删除过时的条目。
实现方式:
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是全部的成员,排序依据是HashMap里存的score,使用跳跃表的结构能够得到比较高的查找效率,而且在实现上比较简单。
五、Hash
简单介绍一下,Hash存的是字符串和字符串值之间的映射。Hash将对象的各个属性存入Map里,能够只读取/更新对象的某些属性。这样有些属性超长就让它一边呆着不动,另外不一样的模块能够只更新本身关心的属性而不会互相并发致使覆盖冲突。
经常使用命令:
hget,hset,hgetall 等。
应用场景:
- 存放结构化数据,好比用户信息。在Memcached或CKV中,对于用户信息好比用户的昵称、年龄、性别、积分等,咱们须要先序列化后存储为一个字符串的值,这时候在须要修改其中某一项时,一般须要将全部值取出反序列化后,修改某一项的值,再序列化存储回去。这样不只增大了开销,也不适用于一些可能并发操做的场合(好比两个并发的操做都须要修改积分)。而Redis的Hash结构可使你像在数据库中Update一个属性同样只修改某一项属性值
- Key是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取均可以直接经过其内部Map的Key(Redis里称内部Map的key为field), 也就是经过key(用户ID) + field(属性标签) 就能够操做对应属性数据了,既不须要重复存储数据,也不会带来序列化和并发修改控制的问题。3. 不过这里须要注意,Redis提供了接口(hgetall)
- 能够直接取到所有的属性数据,可是若是内部Map的成员不少,那么涉及到遍历整个内部Map的操做,因为Redis单线程模型的缘故,这个遍历操做可能会比较耗时,而对其它客户端的请求彻底不响应,这点须要格外注意。
- 可用来建索引。好比User对象,除了id有时还要按name来查询,能够建一个Key为user:name:id的Hash,在插入User对象时(set user:101{"id":101,"name":"calvin"}), 顺便往这个hash插入一条(hset user:name:id calvin 101),这时calvin做为hash里的一个key,值为101。按name查询的时候,用hgetuser:name:id calvin 就能从名为calvin的key里取出id。假如须要使用多种索引来查找某条数据时可使用,一个hash key搞定,避免使用多个string key存放索引值。
- HINCRBY一样可用于实现idmaker。相对string类型的idmaker每个类型须要一个key,hash类型的用一个key便可。
实现方式:
Redis Hash对应Value内部实际就是一个HashMap,这里会有2种不一样实现,这个Hash的成员比较少时Redis为了节省内存会采用相似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
今日就分享到这啦,若是任何问题或者建议,欢迎留言交流。