微信搜索公众号“码路印记”,点关注不迷路!git
从本文开始我将开启Redis专题,逐步整理一些Redis的知识要点并分享出来,目前整理了如下要点。今天先分享第一篇,主要是Redis的一些基本知识点,也是经过这篇文章来挖掘须要完善Redis的知识体系。 github
Redis是如今最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具有以下特性:基于内存运行,性能高效;支持分布式,理论上能够无限扩展;key-value存储系统;开源的使用ANSI C语言编写、遵照BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。面试
相比于其余数据库类型,Redis具有的特色是:C/S通信模型、单进程单线程模型、丰富的数据类型、操做具备原子性、持久化、高并发读写、支持lua脚本。redis
因为Redis出色的性能表现,全世界各大厂都在拥抱与使用,如github、twitter、阿里巴巴、百度,而且一些大厂在开源版本的基础上继续深度开发与定制。固然,Redis使用如此普遍,它也成为了咱们开发者的必备技能,平常工做与面试已经没法无视它的存在。数据库
Redis经常使用的数据类型有String、Hash、List、Set、SortedSet,通常来说熟练使用这些数据类型及操做指令能够知足咱们绝大多数的工做场景。除此以外,Redis还支持一些高级的类型,好比HyperLogLog、Geo、Pub/Sub等。安全
本文对经常使用的五种数据类型及指令进行简单总结,如有未列出的指令,你们能够到redis.io/commands#进行查询。服务器
它是一个二进制安全的字符串,意味着它不只可以存储字符串、还能存储图片、视频等多种类型, 最大长度支持512M。如下列举了String类型经常使用的指令及使用示例。微信
指令 | 用途 |
---|---|
SET key value | 设置指定 key 的值 |
GET key | 获取指定 key 的值。 |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
MGET key1 [key2..] | 获取全部(一个或多个)给定 key 的值。 |
SETEX key seconds value | 将值 value 关联到 key ,并将 key 的过时时间设为 seconds (以秒为单位)。 |
SETNX key value | 只有在 key 不存在时设置 key 的值。 |
MSET key value [key value...] | 同时设置一个或多个 key-value 对。 |
INCR key | 将 key 中储存的数字值增一。 |
INCRBY key increment | 将 key 所储存的值加上给定的增量值(increment) 。 |
DECR key | 将 key 中储存的数字值减一。 |
DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) 。 |
DEL key [key1..] | 删除给定的key,返回被删除的数量 |
String类型是简洁的key-value结构,也是咱们开发中最经常使用的一种类型,使用方式相似于Java中的Map类型。并且,Redis提供了对数值类型的自增、自减指令,可使用它来实现计数器、发号器等功能。markdown
该类型是由field和关联的value组成的map,其中,field和value都是字符串类型的。Hash类型与关系型数据库的表记录相似,key为主键,field为列名,value为列对应的值,经过Hash咱们能够描述一个对象的多个特征。Hash的操做命令以下:网络
指令 | 用途 |
---|---|
HDEL key field1 [field2] | 删除一个或多个哈希表字段 |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在。 |
HGET key field | 获取存储在哈希表中指定字段的值。 |
HGETALL key | 获取在哈希表中指定 key 的全部字段和值 |
HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
HINCRBYFLOAT key field increment | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
HKEYS key | 获取全部哈希表中的字段 |
HLEN key | 获取哈希表中字段的数量 |
HMGET key field1 [field2] | 获取全部给定字段的值 |
HMSET key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value 。 |
HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 |
HVALS key | 获取哈希表中全部值。 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对。 |
Hash类型更加贴近于面向对象的概念,经过key肯定存储的对象,而后以“field-value”键值对从多个维度来描述对象,每一个Hash能够存储2^32-1个键值对,大约有40多亿个。
该类型是一个插入顺序排序的字符串元素集合, 基于双链表实现。一个列表最多能够包含2^32-1个元素。List的操做命令以下:
指令 | 用途 |
---|---|
BLPOP key1 [key2 ] timeout | 移出并获取列表的第一个元素, 若是列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
RPUSH key value1 [value2] | 在列表中添加一个或多个值 |
RPUSHX key value | 为已存在的列表添加值 |
BRPOP key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 若是列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
LINDEX key index | 经过索引获取列表中的元素 |
LLEN key | 获取列表长度 |
LPOP key | 移出并获取列表的第一个元素 |
LINSERT key BEFORE | AFTER pivot value |
LPUSH key value1 [value2] | 将一个或多个值插入到列表头部 |
LPUSHX key value | 将一个值插入到已存在的列表头部 |
LRANGE key start stop | 获取列表指定范围内的元素 |
LREM key count value | 移除列表元素 |
LSET key index value | 经过索引设置列表元素的值 |
LTRIM key start stop | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间以内的元素都将被删除。 |
RPOP key | 移除列表的最后一个元素,返回值为移除的元素。 |
RPOPLPUSH source destination | 移除列表的最后一个元素,并将该元素添加到另外一个列表并返回 |
BRPOPLPUSH source destination timeout | 从列表中弹出一个值,将弹出的元素插入到另一个列表中并返回它; 若是列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
List使用双向链表来实现,相似队列结构,队头在左边,队尾在右边。因此,RPUSH至关于在队尾添加元素,LPOP是在对头取出元素。基于此特性,咱们可使用List做为异步队列。以下图所示:
Set类型是一种无顺序集合, 它和List类型最大的区别是:集合中的元素没有顺序, 且元素是惟一的。Set类型的底层是经过哈希表实现的,其操做命令为:
指令 | 用途 |
---|---|
SADD key member1 [member2] | 向集合添加一个或多个成员 |
SCARD key | 获取集合的成员数 |
SDIFF key1 [key2] | 返回第一个集合与其余集合之间的差别。 |
SDIFFSTORE destination key1 [key2] | 返回给定全部集合的差集并存储在 destination 中 |
SINTER key1 [key2] | 返回给定全部集合的交集 |
SINTERSTORE destination key1 [key2] | 返回给定全部集合的交集并存储在 destination 中 |
SISMEMBER key member | 判断 member 元素是不是集合 key 的成员 |
SMEMBERS key | 返回集合中的全部成员 |
SMOVE source destination member | 将 member 元素从 source 集合移动到 destination 集合 |
SPOP key | 移除并返回集合中的一个随机元素 |
SRANDMEMBER key [count] | 返回集合中一个或多个随机数 |
SREM key member1 [member2] | 移除集合中一个或多个成员 |
SUNION key1 [key2] | 返回全部给定集合的并集 |
SUNIONSTORE destination key1 [key2] | 全部给定集合的并集存储在 destination 集合中 |
SSCAN key cursor [MATCH pattern] [COUNT count] | 迭代集合中的元素 |
Set类型主要应用在某些场景,如社交场景中,经过交集、并集和差集运算,经过Set类型能够很是方便地查找共同好友、共同关注和共同偏好等社交关系。
SortedSet是一种有序集合类型,每一个元素都会关联一个double类型的分数权值,经过这个权值来为集合中的成员进行从小到大的排序。与Set类型同样,其底层也是经过哈希表实现的。Sorted Set的经常使用指令及说明见下表:
指令 | 用途 |
---|---|
ZADD key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员数 |
ZCOUNT key min max | 计算在有序集合中指定区间分数的成员数 |
ZINCRBY key increment member | 有序集合中对指定成员的分数加上增量 increment |
ZRANGE key start stop [WITHSCORES] | 经过索引区间返回有序集合指定区间内的成员 |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 经过分数返回有序集合指定区间内的成员 |
ZSCORE key member | 返回有序集中,成员的分数值 |
SortedSet的特色是内部元素惟1、有序,经过以上指令对数据进行操做能够保持这一特性,咱们可使用它来实现排行榜、延迟队列等功能。
Redis 发布订阅 (pub/sub) 是一种消息通讯模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息,能够实现1:N的消息队列。以下图所示: 可是若是订阅者所有下线了,发布者发布的全部消息都会丢失,Pub/Sub并无提供消息的持久化能力(Redis 5.0提供了Redis Stream弥补了这一缺点)。经常使用的指令及说明以下:
指令 | 做用 |
---|---|
PUBLISH channel message | 将信息发送到指定的频道。 |
SUBSCRIBE channel [channel ...] | 订阅给定的一个或多个频道的信息。 |
UNSUBSCRIBE [channel [channel ...]] | 指退订给定的频道。 |
这是一个常见的面试题,回答它以前咱们须要先知道Redis到底有多块。Redis官方文章《How fast is Redis?》给出了基准测试数据,由此可知Redis的性能受网络带宽、CPU、物理机/虚拟机、数据大小、客户端链接数等因素的影响。下图来自该文章,数据显示了在高端配置下Redis QPS受链接数影响的状况(横轴为链接数,纵轴为QPS),在链接数小于5000时,QPS能够达到10万+。 再来看下Redis为何这么快:
先来看下Redis官方是如何回答的:
It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.
简单翻译下:CPU成为Redis瓶颈的状况并不常见,Redis一般是受内存或网络限制的。例如,在通常的Linux系统上使用Pipeline,Redis每秒能够处理100万个请求,若是你的应用主要使用O(N)或者O(log(N))的命令,那么它几乎不会占用太多的CPU。
总结一下就是:单线程已经够快了,为啥还要用多线程呢!哈哈哈。。。
其实说Redis是单线程并不严谨,所谓单线程只是Redis在处理网络请求时使用了单线程。因为使用了非阻塞、多路复用的I/O技术,几乎纯内存无延迟的操做,使用单个线程足以知足使用需求。可是,这并非说Redis内只有一个线程,它还有其余线程来完成辅助工做,好比:数据过时策略、内存淘汰机制、主从复制等都有相关职责的线程。
“单线程机制”为Redis的快提供了强有力的支撑,这是它的一大优点。可是如今咱们的服务器动不动就8核、16核、32核,Redis只使用一个线程就没有办法充分利用硬件资源了。为了提供硬件资源利用率,咱们能够在一台机器部署多个Redis实例,或者使用虚拟化技术/Docker技术对物理资源再分配。
Redis具备出色的性能,很大一部分缘由是它使用内存做为数据存储,以及Redis开发者对其内部数据结构的精益设计。Redis可用内存的大小除了受物理内存空间限制外,还受系统参数的影响。以下maxmemory
表明了Redis最大占用内存字节数:
# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
maxmemory 268435456
复制代码
Redis可用的内存资源是有限的,为了充分利用内存资源,同时提供高性能的服务,Redis制定了一套完善的数据过时处理策略及内存淘汰机制。数据过时处理策略的目标是那些设置了过时时间且已通过期的数据;内存淘汰机制是指当Redis内存不够用时,为了保证可用性而采起的保障机制。
针对过时数据,Redis并无简单粗暴的使用定时删除方式,而是采用了“按期清理+惰性删除策略”。来看下缘由:
仔细想下,“按期清理+惰性删除策略”并非万无一失的。若是按期删除没删除key,而后也没及时去请求key,也就是说惰性删除也没生效。这样就会致使Redis的内存会愈来愈高,这时就到内存淘汰机制发挥做用了。Redis提供了如下六种内存淘汰机制:
从上面的描述能够知道:volatile-*的策略针对的是那些设置了过时时间的数据集,若是全部写入的数据都没有设置过时时间,即便设置了这些策略没有什么用;all-*的策略针对的是全部的数据集,若是删除了未过时的数据可能会影响生产安全;no-enviction则会致使Redis没法对外提供新的写入服务,很是不友好。
因此,咱们在开发中须要仔细评估是否为数据设置过时时间,并选择合适的内存淘汰策略。在咱们公司对Redis使用有着严格的规范:全部数据必须设置过时时间,不能把Redis当成持久化数据库来使用,Redis内全部数据必须能够重建。
咱们能够经过配置文件为Redis设置合适的内存淘汰机制:
# 淘汰策略:已设置过时中最近最少使用
maxmemory-policy volatile-lru
复制代码
本文内容可能是理论性的基础知识,也有一些面试中的高频问题,其实Redis的基础远远不止这些,经过后面的文章慢慢补充!
若是以为对你有用,请分享给须要的朋友!点关注不迷路,微信搜索公众号“码路印记”!