Redis-概述
1、SQL与NOSQL的区别
1.1 SQL
1.1.1 特点
- 行存储,二维
- 结构化
- 表与表之间的关联
- SQL语句
- ADID(事务特性)
1.1.2 缺点
1.扩容难
1.1垂直扩容,增大磁盘,不支持动态扩容、缩容
1.2需要水平扩容需要别的技术,分库分表
2.存储的数据不灵活
3.读写压力大
1.2 NOSQL
1.2.1 特点
- 非结构化的数据
- 数据与数据没有关联
- BASE,最终一致性
- 海量数据存储、高并发读写
- 支持分布式:数据分片、扩缩容简单
1.3 常见类型
KV存储
Redis、memcache
文档存储
MongoDB
列存储
HBase
图存储Graph (数据结构)
Neo4j
对象存储
XML存储
……等。
2、Redis概念
使用C语言编写的、遵循开源(BSD许可)、支持网络、基于内存并且可持久化的key-value数据库,可被用来作为数据库、缓存和消息代理。它支持多种数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。
3、Redis特性
- 速度快:存储在内存中(内存速度最快)、C语言编写(距离操作系统最近)、4.0之前单线程架构(避免CPU的上下文切换导致的竞争问题)
- 持久化AOF和RDB
- 功能丰富:缓存、消息队列、事务、流水线(批量传命令,减少网络开销)、支持Lua脚本(自己开发命令)
- 简单稳定
- 高可用
- 分布式、跨进程
- 多种数据结构
- 客户端支持各种语言
4、数据结构及应用场景
string:字符串是其他数据结构的基础。字符串可以是字符串(如JSON、XML)、数字、二进制,但是最大不能超过512MB。
命令:set key value [ex] [px] [nx] [xx];也可以setex key 10 value 与 set key value ex 10是一样的意思
字符串插入操作"[]"里的是可选项
ex设置秒过期时间
px设置毫秒级过期时间
nx键必须不存在(新增操作),可以用做分布式锁
xx是键必须存在(更新操作)
命令:mset key1 value1 key2 value2:进行插入多个键值对;mget key1 key2获取多个值
get 和 mget的区别:get每次都需要通过网络去访问一次服务器,然后服务器再执行命令,那么n次get的耗时等于n次网络时间+n次命令执行时间;而mget可以一次性传输多条命令,也就是一次mget的耗时等于1次网络时间+n次命令执行时间;mset如果设置过多只可能会造成redis的阻塞或网络拥塞,不能随便使用mset
其他命令查看官网API
内部编码
int
8个字节的长整形,只要是数字
embstr
长度小于等于39的字符串
raw
大于39个字节的字符串
应用场景:
缓存、分布式Session、set NX EX 分布式锁(一定要有过期时间,以防set之后redis挂了而没有del的情况发生,死锁)、incr全局ID、incr计数器、incr限流、位操作、统计等。
list:存储多个有序的字符串,列表是一种比较灵活的数据结构,她可以在列表两端插入(push)或弹出(pop),在一些应用场景中,列表可以被当做队列或者栈来使用。
特点
有序
可以重复
相关操作
从右边插入列表:rpush listkey a b c
查看列表指定范围内的所有元素(从左到右是从0到n-1,从右边是-1到-n,end包含自身):lrange listkey 0 -1
从左边插入:lpush listkey e f g
在指定元素前面插入元素(往listkey这个列表的f元素前面添加h元素):linsert listkey before f h
在指定元素后面插入元素(往listkey这个列表的c元素后面添加i元素):linsert listkey after c i
获取指定索引位置的元素:lindex listkey -1
获取列表的长度:llen listkey
从左侧删除元素:lpop listkey
从右侧删除元素:rpop listkey
删除指定的元素(count>0从左到右删,<0从右到左删,=0全部删除,count是删除的数量):lrem listkey count value
保留范围内的元素(保留star到stop之间的元素,其他的删除):ltrim listkey start stop
修改元素(将第0个元素修改为c):lset listkey 0 c
阻塞
阻塞弹出timeout表示几秒后退出,如果等于0就会一直阻塞:brpop key1 key2 timeout,此时往key1里添加数据的话,这里会立即返回出相关的操作,然后跳出阻塞。(brpop会从左到右遍历key,只要有一个有数据就会立即返回)。
内部编码
ziplist(压缩列表)
元素个数小于hash-max-ziplist-entries同时所有值小于hash-max-ziplist-value时,Redis会使用ziplist实现。(Redis 3.2以后移除了)
linkedlist(链表)
当哈希类型无法满足ziplist时,使用此编码
quicklist(快速列表)
Redis 3.2以后加入的数据结构
原有的压缩列表每次插入或删除一个元素时,都需要进行频繁的调用realloc()函数进行内存的扩展或减小,甚至可能引发连锁更新,造成效率上的损失。
在quicklist表头结构中,有两个成员是fill和compress,其中” : “是位域运算符,表示fill占int类型32位中的16位,compress也占16位。
fill成员对应的配置:list-max-ziplist-size -2
当数字为负数,表示以下含义:
-1 每个quicklistNode节点的ziplist字节大小不能超过4kb。(建议)
-2 每个quicklistNode节点的ziplist字节大小不能超过8kb。(默认配置)
-3 每个quicklistNode节点的ziplist字节大小不能超过16kb。(一般不建议)
-4 每个quicklistNode节点的ziplist字节大小不能超过32kb。(不建议)
-5 每个quicklistNode节点的ziplist字节大小不能超过64kb。(正常工作量不建议)
当数字为正数,表示:ziplist结构所最多包含的entry个数。最大值为 215。
compress成员对应的配置:list-compress-depth 0
后面的数字有以下含义:
0 表示不压缩。(默认)
1 表示quicklist列表的两端各有1个节点不压缩,中间的节点压缩。
2 表示quicklist列表的两端各有2个节点不压缩,中间的节点压缩。
3 表示quicklist列表的两端各有3个节点不压缩,中间的节点压缩。
以此类推,最大为 216。
可以在redis里通过config get list-* 命令查看这两个成员的配置
使用场景
lpush+ltrim=Cappen Collection(有限集合)
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+brpop=Message Queue(消息队列)
hash类型:也叫哈希、字典、关联数组等,在Redis中,哈希类型指的是键值本身又是一个键值对结构,例如:value={{field1,value1},……,{fieldN,valueN}}
相关命令
往user1里添加一个key为name,value为tom的操作:hset user1 name tom
从user1里获取一个key为name的操作:hget user1 name
删除一个元素:hdel user1 name
获取长度:hlen user1
批量插入:hmset user2 name jack age 29 city beijing
批量获取:hmget user2 name age city
获取所有的键:hkeys user2
获取所有的值:hvals user2
判断元素是否存在(存在返回1,不存在返回0):hexists user2 name
获得所有的键值对信息,会以列表的形式展示,一个key一个value:hgetall user2
内部编码
ziplist(压缩列表)
元素个数小于hash-max-ziplist-entries同时所有值小于hash-max-ziplist-value时,Redis会使用ziplist实现。
查看上述两个值的命令(要进入redis才可以):config get hash-*
hashtable(哈希表)
当哈希类型无法满足ziplist时,使用此编码。
应用场景
当我们需要把一系列数据当做一个整体来使用时,就可以使用这种数据结构,比如user信息,每个user都有name,age等字段,我们就可以定义user1,name xxx age xxx ; user2 name xxx age xxx;像这样操作起来也比较方便。
与String相比
- 节省空间
- 减少key冲突
- 减少资源消耗
不适用
- key(field, value)形式,设置过期时间只能对整个集合有效,而不能精细到每一个field
- 没有位操作
- 一个聚合的hash,如果非常大,也无法分散存储到多个redis,导致当个节点的压力非常大
set(集合):用来保存多个字符串元素,集合和列表的区别是集合中不允许有重复元素,并且是无序的,因此我们不能通过下标来获取元素,一个集合可以用2的32次方-1个元素
相关命令
往集合里添加元素:sadd key v1 v2 v3
查看集合元素个数(时间复杂度为O(1)没有遍历,而是直接使用内部的变量):scard key
删除元素(返回删除成功的数量):srem key v1 v2
判断集合是否存在(存在返回1,不存在返回0):exists key
判断一个元素是否在集合中(存在返回1,不存在返回0):sismember key v
随机返回指定个数的元素(count表示返回的个数,默认(不填)是1),不会删除元素:srandmember key count
弹出元素(可选个数),并删除:spop key count
获取集合所有元素(无序),比较重的命令,元素多的话会阻塞:smembers key
获取两个集合的交集:sinter key1:follow key2:follow
并集(重复的只显示一个):sunion key1:follow key2:follow
差集:sdiff key1:follow key2:follow
集合运算之后的结果保存以交集为例(只需要加上store再定义一个保存的集合名称即可,其他两个一样):sinterstore interkey key1:follow key2:follow
内部编码
intset(整数集合)
当集合中元素都是整数且元素个数小于set-max-intset-entries时,使用intset作为内部实现
查看命令:config get set-*
hashtable(哈希表)
当集合中元素无法满足intset的条件时,使用
应用场景
用户标签:社交网站,用户对历史、新闻、动漫等感兴趣可以使用集合,可以通过相同集合找到同一个标签的人
有序集合zset:可以排序,但是有序集合并不是使用索引下标作为排序依据,它会给每个元素设置一个score作为排序依据,根据key的ascii码排序,分值越高越往后,ascii码越小越前面
相关操作
查看api
内部编码
ziplist(压缩列表)
当集合中元素都是整数且个数下雨zip-max-entries时,同时元素值都小于zip-max-ziplist-value,使用ziplist作为内部实现
查看:config get zset-*
skiplist(跳跃表)
当集合中元素无法满足ziplist的条件时,使用
应用场景
排行榜系统,比如一些直播网站,可以按照时间、播放量、点赞数等进行排行
list
有序字符串,可以重复,可以容纳(2^32) - 1的元素
l(left)是头,r(right)是尾,顺序为从左到右
可以实现队列、栈
geo
保存地理位置
hyperloglogs
适用于数据量非常大,而且不需要非常精确地统计工作,如统计网站的访问量
5、常用命令
- select index:选择数据库,默认0号数据库
- set key value : 插入键值对;set key value ex 10:设置过期时间,这里是10秒后过期;可以通过ttl key获取过期时长整数表示剩余时长,-2表示已经过期,-1表示没有设置过期时间
- keys * :获取所有键值对
- dbsize:得到当前数据库使用长度
- exists key:判断当前key是否存在,存在返回1,不存在返回0
- del key1 key2...:删除键值对,可一个可多个
- rpush mylist a b c d e:插入链表型数据
- type key:返回当前键的数据结构,如string list hash set zset,不过这些都只是Redis对外的数据结构
- OBJECT encoding key:返回Redis的内部编码,redis会根据插入值的长度自动选择合适的编码。 key如下表所示:
| key(常用的数据结构的内部编码) | |
| string | raw |
| int | |
| embstr | |
| hash | hashtable |
| ziplist | |
| list | linkedlist |
| ziplist | |
| set | hashtable |
| intset | |
| zset | skiplist |
| ziplist | |
- flushdb:清除当前数据库所有内容
- flushall:清除所有数据库
6、发布订阅
6.1 命令:subscribe channel1 channel2 ....,订阅频道
6.2 命令:psubscribe *xxx 或者 psubscribe xxx*, 可以订阅所有xxx结尾的和xxx开头的频道,相当于订阅了一系列相同模式的频道,按照一定的规则去订阅
6.3 命令:publish channel1 xxx,向channel1发布消息
6.4 命令:unsubscribe channel,取消订阅
7、Lua脚本
7.1 为什么在redis使用Lua脚本
7.1.1 批量执行任务,用lua脚本批量执行redis的命令,减少网络开销,客户端可以将redes的命令放在一个Lua脚本中发送给服务端
7.1.2 原子性, 将Lua脚本里的所有redis命令当做一个整体执行,不会被其他请求打断,可以保证原子性
7.1.3 操作集合的复用,对于复杂的组合命令,可以将它们放到一个Lua脚本中,可以在多个程序之间复用
7.2 语法
eval lua-script key-num [key1 key2 key3 ...] [value1 value2 value3 ...]
- eval代表执行Lua语言的命令
- lua-script代表Lua语言脚本内容
- key-num代表参数中有多少个key,需要注意的是Redis中的key是从1开始的,如果没有key的参数,那么写0
- [key1 key2 key3 ...]是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来
- [value1 value2 value3 ...]这些参数传递给Lua语言,可填可不填
7.3 在Lua脚本中执行redis命令
redis.call(command, key[param1, param2...])
- conmand是命令,包括set、get、del等
- key是被操作的键
- param1,param2...代表给key的参数
- 例:eval “redis.call('set', KEYS[1], ARGV[1])”1 key1 value1
- ./redis-cli --eval xx.lua 0
7.4 可以执行lua文件
缓存Lua脚本,在客户端执行 script.load "XXX",会返回一个摘要值,此时会在服务器端缓存这段Lua脚本,之后使用的时候只要发送摘要值就可以了(evalsha "摘要值")。这样如果当Lua脚本非常大的时候,就不用每次都去传输这样一个Lua脚本,减少网络开销
7.4 执行超时(耗时比较长的操作)
- SCRIPT KILL, 可以杀死没有执行完的脚本,以防止一个线程死循环而导致其他线程无法使用。(redis4.0之前是单线程的)
- SHUTDOWN NOSAVE,因为Lua要保证原子性,所以如果在有set(修改)等操作的情况下出现超时,SCRIPT KILL也会被阻断,此时只能使用SHUTDOWN NOSAVE命令,实现在不保存的情况下直接关闭服务器
8、事务
- multi,开启事务(输入相关命令会加入queue中),不能嵌套事务,输入多个multi和输入一个是一样的
- exec,执行事务(将开启事务后的命令按顺序逐条执行,开启事务后会按照我们输入命令的顺序逐条执行)
- discard,取消事务(命令不会执行)
- watch,乐观锁,可以监视一个或多个key的值,只要有一个key被修改就都会取消如果一个key被多个线程操作,当这个key被修改的时候会先去和刚开启事务时这个key的初始值做比较,如果已经被其他线程修改了,这个事务就会取消执行,只有没有被修改,才会提交成功。
- unwatch,取消监视,
- 异常情况,在exec执行之前发生异常,将不会执行提交操作;在excec执行之后发生异常,错误的语句执行失败,而正确的语句仍会执行,也就是无法保证原子性
8、pipeline
批量发送指令和接收指令,由于redis客户端和服务端是基于TCP协议的,那么每次交互都会有响应时间,那么如果一条一条的发送,就会浪费很多时间。pipeline是将命令先缓存在客户端的缓冲区里,当满足一定条件之后,一次性发送给服务端,服务端接收后,一次性执行,并将返回结果返回,这样能节省大量的访问时间。当然,由于要将命令缓存到客户端,所以会对客户端的内存有一定的资源占用,不能一次性写入太多的命令。服务端返回结果的时候,会将结果缓存到客户端的接收缓冲区,如果客户端的接收缓冲区满了,则会通知服务端,此时服务端就不会再发结果给客户端,而是缓存到服务端的发送缓冲区中。适用于,不需要马上知道结果的场景。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
