这是我参与更文挑战的第18天,活动详情查看: 更文挑战
一、前言
了解了NoSQL的发展历史,我们接下来开始揭开redis的序幕。
Redis以高超的性能,完美的文档、简洁易懂的原码和丰富的客户端库支持在开源中间件领域广受好评。
深入了解Redis的应用和实践,成为如今中高级后端开发的必备技能。
二、入门概述
2.1 它是什么?
Redis 全称 Remote Dictionary Server,中文名为远程字典服务器。redis被好多人形象的比喻成一把瑞士军刀,因为它的功能非常丰富。
查看官网介绍:
Redis 是一种开源(BSD 许可)、内存中数据结构存储,用作数据库、缓存和消息代理。Redis 提供数据结构,例如字符串、散列、列表、集合、带有范围查询的排序集合、位图、超级日志、地理空间索引和流。Redis 内置复制、Lua 脚本、LRU 驱逐、事务和不同级别的磁盘持久化,并通过 Redis Sentinel 和 Redis Cluster 自动分区提供高可用性。
复制代码
嗯嗯,看完这个,我们知道既它可以是一个数据库,也可以是缓存和消息代理中间件。它也是我们前面说的NoSQL中的键值存储数据库。
2.2 起源
2008年,意大利一家创业公司Merzia的创始人Salvatore Sanfilippo 发现了MYSQL存在的低性能问题,就决定亲自实现一个数据库,并在2009年开发完成了Redis的最初版本。短短几年后,redis便拥有了一大批的用户群体。
Redis之父网名Antirez 于2020年6月发表退役声明,短短十年左右的时间,说Redis 加速了互联网的进程也不为过,而redis的默认端口 6379 也不是随机选的,由手机键盘的 “MERZ” 的位置来决定,缘于意大利广告女郎在节目上说了一堆愚蠢的话而被他不爽,把她名字做为端口号,称为愚蠢的代名词。
2.3 redis能做什么
redis 具有以下性能:
- 1、速度快,性能高 :基于内存操作
- 2、丰富的数据结构: string、list、hash、set、sortedset等不同类型
- 3、多语言支持
- 4、持久化:支持数据的持久化,可以把内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- 5、高可用和分布式:主从复制等技术
基于这些特点,redis可以做什么?
1、缓存系统
缓存热点内容,减少数据库压力。
2、计数器
记录贴子的点赞数、评论数和点击数。
3、消息队列
对消息可靠性要求不高的时候,可以基于redis来实现消息队列。
4、排行榜
对热榜帖子的id放进列表,分为总榜单和分类榜单 。
5、分布式锁
常见于高并发环境,通过setnx设置lock在分布式环境下可以使用,也可以加上加锁时间。
当然,实际中可能用不到这么多需求,但是学一门技术的目的就是解决问题,所以,我们需要学习更多的redis高级特性,这样在做一些看似很难完成的任务到时候,说不定redis就可以派上用场。
2.4 redis安装
三、五大基本类型
这部分内容是 redis 的重中之重,也是入门 redis 的最基础的内容。
reids的命令比较多,下面开始回顾。
3.1 reids-key 基础命令
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"xulei"
127.0.0.1:6379> expire name 3
(integer) 1
127.0.0.1:6379> ttl name # 剩余时间
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> exists name
(integer) 0
127.0.0.1:6379> type age
string
复制代码
3.2 string
3.2.1 内部结构
string 是 redis 中 最简单的数据结构,它的内部是一个字符数组。Redis 中所有的数据结构都通过 唯一的key 来获取相应的 value ,而数据类型value 就是区分 这五大基本类型的地方。
string 也是我们最常用的数据结构,如图,我们常见的用途就是存储用户信息,将JSON序列化成String存储,取出时在反序列化。
redis 中的String 的字符串是动态字符串,并非我们传统的字符串数组,即它可以发生动态变换,内部结构类似于Java 的 ArrayList ,在创建初始的时候就会分配一个预分配内存,从而减少内存的频繁分配,这种字符串简称SDS(simple dynamic stirng)。
其内部定义如下:
struct sdshdr {
// 记录buf数组中已使用字节的数量
// 等于sds 所保存字符串的长度
int len;
// 等于sds所保存字符串的长度
int free;
// 字节数组,用于保存字符串
char buf[];
}
复制代码
该图展示了一个SDS 实例
- free 属性的值为0,表示这个SDS 没有剩余空间分配
- len 表示SDS 保存了一个五字节长的字符串
- buf 属性是一个char 类型的数组,最后一个字节则保存了空字符 ’ \0‘
3.2.2 空间预分配法则和惰性空间释放策略
这两种策略的目的是对未使用空间的一个利用,也就是前面讲的free属性值。
1、空间预分配:
空间预分配用于优化SDS的字符串增长操作,当SDS 的API 对一个SDS 进行修改,并且对SDS进行空间扩展的时候,程序会动态修改已分配空间值和未分配空间值。 类似Java 的ArrayList的扩容。
公式如下:
- 1、赋值时,当len的长度小于1MB,那么len 属性等于实际的字符串长度,free 的属性值=len值,也就是扩容都是加倍的
- 2、赋值时,当len 的长度大于等于1MB,那么程序会分配1MB固定的未使用空间。
- 3、注意:字符串的最大值为512MB
2、惰性空间释放:
惰性空间释放用于优化SDS 的字符串缩短操作,即当字符串个数变少的时候,并不会立即回收多余的空间,而是把空间放在free中。这种避免了缩短字符串所需的内存重分配操作。
当然,SDS也提供相应的API,真正箱释放的时候,也会把这些多余的free释放掉,防止空间占用内存。
3.2.3 常用操作
键值对:
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> keys *
1) "key1"
2) "age"
127.0.0.1:6379> APPEND key1 hello
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1
(integer) 7
复制代码
加减操作, 可以设置步长
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> incrby views 10
(integer) 11
127.0.0.1:6379> decrby views 2
(integer) 9
复制代码
字符串范围:
127.0.0.1:6379> set key1 "hello,xulei"
OK
127.0.0.1:6379> getrange key1 0 4
"hello"
127.0.0.1:6379> getrange key1 0 -1 # 获取所有的字符串
"hello,xulei"
复制代码
替换:
127.0.0.1:6379> set key2 abcd
OK
127.0.0.1:6379> get key2
"abcd"
127.0.0.1:6379> setrange key2 1 xx
(integer) 4
127.0.0.1:6379> get key2
"axxd"
复制代码
setex 和setnx:
#setex(set with expire) 设置过期时间
#setnx(set if not exist) 不存在设置
127.0.0.1:6379> setex key3 20 hualin
OK
127.0.0.1:6379> get key3
"hualin"
127.0.0.1:6379> ttl key3
(integer) 5
127.0.0.1:6379> setnx mykey redis # 如果mykey不存在,创建成功
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
2) "mykey"
3) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey 1 # 如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
复制代码
批量获取和设置:
# mset
# mget
# msetnx 是一个原子性的操作,要么一起成功,要么一起失败
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4
(integer) 0
127.0.0.1:6379> get k4
(nil)
复制代码
对象:
set user:1 {name:zhangson,age:29}
# 设置一个user:1对象,值为json字符来保存一个对象
127.0.0.1:6379> mset user:2:name xulei user:2:age 26
OK
##########
# getset 先get然后set,更新的操作,并返回value
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"
复制代码
3.3 List
redis 的list 实际上是一个链表结构,是双向链表,对应Java 语言中的 linkedList , 这种结构带有表头节点指针、表尾节点指针,以及链表长度等信息。因此,它也具有链表的特点,即查询慢, 增删快。
Redis 的列表结构常用来做异步队列使用,一边读取入队,另一边出队。
常见应用:
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list tow
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "tow"
3) "one"
127.0.0.1:6379> lrange list 0 2
1) "three"
2) "tow"
3) "one"
127.0.0.1:6379> lrange list 0 1 #通过区间获取 从尾部依次往前获取
1) "three"
2) "tow"
127.0.0.1:6379> lrange list 1 2
1) "tow"
2) "one"
127.0.0.1:6379> rpush list zero
(integer) 4
127.0.0.1:6379> lrange list 0 -1 # 插入到头部
1) "three"
2) "tow"
3) "one"
4) "zero"
#############
127.0.0.1:6379> lpop list # 移除尾部元素
"three"
127.0.0.1:6379> rpop list # 移除头部元素
"zero"
127.0.0.1:6379> lrange list 0 -1
1) "tow"
2) "one"
###################
# 通过下标获取某个值 lindex
127.0.0.1:6379> lrange list 0 -1
1) "tow"
2) "one"
127.0.0.1:6379> Lindex list 1
"one"
127.0.0.1:6379> Lindex list 0
"tow"
127.0.0.1:6379> Lindex list 2
(nil)
#######################
Llen: 返回列表的长度
127.0.0.1:6379> llen list
(integer) 3
#####################
# lrem 移除指定的值,移除是从头部开始移除的
取关:uid
127.0.0.1:6379> lrem list 1 1
(integer) 1
127.0.0.1:6379> lrange list 0 -2
1) "2"
2) "2"
3) "1"
4) "2"
5) "2"
6) "3"
127.0.0.1:6379> lrem list 2 2
(integer) 2
127.0.0.1:6379> lrange list 0 -2
1) "1"
2) "2"
3) "2"
4) "3"
###############################
# ltrim 截取list的元素,只剩下剪短后的元素,
# ltrim 1 0 # 是清空整个列表的意思,因为区间长度为负
127.0.0.1:6379> lrange list2 0 -1
1) "hello5"
2) "hello4"
3) "hello3"
4) "hello2"
5) "hello"
127.0.0.1:6379> ltrim list2 2 3
OK
127.0.0.1:6379> lrange list2 0 -1
1) "hello3"
2) "hello2"
127.0.0.1:6379>
#################
rpoplpush # 移除列表的最后一个元素
127.0.0.1:6379> rpush mylist hello1
(integer) 1
127.0.0.1:6379> rpush mylist hello2
(integer) 2
127.0.0.1:6379> rpush mylist hello3
(integer) 3
127.0.0.1:6379> rpush mylist hello4
(integer) 4
127.0.0.1:6379> rpush mylist hello5
(integer) 5
127.0.0.1:6379> rpoplpush mylist othorlist
"hello5"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
4) "hello4"
127.0.0.1:6379> lrange othorlist 0 -1
1) "hello5"
#################
# lset 将列表中指定的下标的值替换为另外一个值 更新操作
127.0.0.1:6379> lset list 0 item # 判断这个列表是否存在,
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
####################
# linsert 在before和after插入 具体的值
127.0.0.1:6379> lrange list 0 -1
1) "intem3"
2) "intem2"
3) "item"
127.0.0.1:6379> linsert list before "item" nimabi
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "intem3"
2) "intem2"
3) "nimabi"
4) "item"
127.0.0.1:6379> linsert list after item hahahhahah
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "intem3"
2) "intem2"
3) "nimabi"
4) "item"
5) "hahahhahah"
复制代码
3.4 set(集合)
redis 的 set 集合相当于 Java 语言里面的 HashSet ,它的内部的键值对是无序的、唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value 都是一个值 NULL ,当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。
set 中的值是不允许重复的,无序的。可以用来存储在某个活动中 中奖的用户ID,可以去重作用,保证一个用户不会中奖两次。
##################################
# sadd 添加元素
# smembers :所有的set集合
# sismember :判断是否有该元素
# scard :获取当前的元素
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset xiaolei
(integer) 1
127.0.0.1:6379> sadd myset xulei love hualin
(integer) 3
127.0.0.1:6379> smembers myset
1) "xulei"
2) "hello"
3) "love"
4) "xiaolei"
5) "hualin"
127.0.0.1:6379> sismember myset hello
(integer) 1
127.0.0.1:6379> scard myset
(integer) 5
########################################
# srem : 移除set集合元素
# SRANDMEMBER:随机抽选出一个元素
127.0.0.1:6379> srem myset hello
(integer) 1
127.0.0.1:6379> SRANDMEMBER myset
"xulei"
127.0.0.1:6379> SRANDMEMBER myset
"hualin"
127.0.0.1:6379> SRANDMEMBER myset
"love"
########################################
# spop :随机删除key
127.0.0.1:6379> spop myset
"hualin"
127.0.0.1:6379> SMEMBERS myset
1) "love"
2) "xulei"
3) "xiaolei"
127.0.0.1:6379> spop myset
"love"
########################################
# 将一个指定的值,移动到另外一个集合中
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset xulei
(integer) 1
127.0.0.1:6379> sadd myset2 hualin
(integer) 1
127.0.0.1:6379> sadd myset2 xiangni
(integer) 1
127.0.0.1:6379> smove myset myset2 xulei
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "xulei"
2) "xiangni"
3) "hualin"
########################################
应用:微博,b站 把所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中。
数字集合类:
差集:SDIFF
交集:SINTER
并集:SUNION
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> SDIFF key1 key2
1) "b"
复制代码
3.5 zset (有序列表)
zset 在set 的基础上增加了一个值,分组。set k1 v1 zset k1 score1 v1
它类似于SortedSet 和HashMap 的结合体,一方面它是一个set,保证了value 的唯一性,另一方面,它可以给每个value 赋予一个 score,代表这个value 的排序权重,它的内部实现用的是跳转列表的数据结构。
应用场景:
- zset 可以用来存储粉丝列表,value 值是粉丝的用户Id ,score 是关注事件,可以按照关注时间排序。
- zset 还可以用来存储学生的成绩,value 是学生的Id ,score 是考试成绩。
实操:
##########################
# ZRANGEBYSCORE 排序 min max
# ZREVRANGE :从大到小排序
# zrange : 从小到大排序
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zadd salary 2500 xiaohon
(integer) 1
127.0.0.1:6379> zadd salary 9000 zhansan
(integer) 1
127.0.0.1:6379> zadd salary 200 xulei
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xulei"
2) "xiaohon"
3) "zhansan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xulei"
2) "200"
3) "xiaohon"
4) "2500"
5) "zhansan"
6) "9000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
1) "xulei"
2) "200"
3) "xiaohon"
4) "2500"
127.0.0.1:6379> ZREVRANGE salary 0 -1
1) "zhansan"
2) "xiaohon"
##########################
# rem:移除rem中的元素:移除有序集合中的元素
# zcard:查看里面的元素个树
# zcount:计算区间内的个树
127.0.0.1:6379> zrange salary 0 -1
1) "xulei"
2) "xiaohon"
3) "zhansan"
127.0.0.1:6379> zrem salary xulei
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohon"
2) "zhansan"
127.0.0.1:6379> zcard salary
(integer) 2
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 xulei
(integer) 2
127.0.0.1:6379> zcount myset 1 3
(integer) 3
复制代码
3.6 Hash (字典)
相当于 Java 语言 里面的HashMap ,也就是数组+链表组合。
hash 结构存储多个key-value ,可以对单独的key做获取,本质上和string 类型没有太大的区别。
hash 对比 string:
-
1、如果存储的都是比较结构化的数据,比如用户数据缓存,特别是如果一个数据中如果字段比较多,但是每次只需要使用其中的一个或者少数的几个,使用hash 是一个好的选择,因为它提供了hget 和hmget,而无需取出所有数据在代码中处理。
-
2、如果数据差异很大,需要把所有数据读取出来再处理,则用string 来完成
-
3、hash缺点:存储消耗高于单个字符串。
基本命令实操:
#################################
# hset:存值
# hget: 取值
# hmset: 存多个值
# hmget: 取多个值
# hgetall: 获取所有的key value
# hdel: 删除指定的key
127.0.0.1:6379> hset myhash field1 xulei
(integer) 1
127.0.0.1:6379> hget myhash field1
"xulei"
127.0.0.1:6379> hset myhash field1 xulei
(integer) 1
127.0.0.1:6379> hget myhash field1
"xulei"
127.0.0.1:6379> hmset myhash field2 2 field3 3
OK
127.0.0.1:6379> hmget myhash field1 field2 field3
1) "xulei"
2) "2"
3) "3"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "xulei"
3) "field2"
4) "2"
5) "field3"
6) "3"
127.0.0.1:6379> hdel myhash field2
(integer) 1
#################################
# hlen:查看hash表的字段数量
# hexists: 判断指定hash是否存在
# hkeys: 查看当前所有的key
# hvals:只获得所有的value
127.0.0.1:6379> hlen myhash
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "xulei"
3) "field3"
4) "3"
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field3"
127.0.0.1:6379> hvals myhash
1) "xulei"
2) "3"
################################
# hincryby :指定增量
# hsetnx : 如果存在则不能设置
127.0.0.1:6379> hset myhash field4 4
(integer) 1
127.0.0.1:6379> hincrby myhash field4 2
(integer) 6
127.0.0.1:6379> hincrby myhash field4 -4
(integer) 2
127.0.0.1:6379>
复制代码
四、小结
这篇文章介绍了redis的功能介绍和五大基本类型的使用,我们也正式开启了nosql 的大门,后续更多高级的redis指南,将在后期奉上,喜欢的点赞,原创不易,你的点赞将是我创作的最好动力。
本文参考:
- 《Redis 深度历险》
- 《Redis 设计与实现》