Redis6(一)五大数据类型

发布时间 2023-05-16 19:35:00作者: Tod4

1 NoSQl数据库

1.1 技术的发展

技术的分类:

①解决功能性问题:javase

②解决扩展性问题:框架

③解决性能问题:redis

1.2 NoSQL数据库概述

NoSQL(Not Only SQL),不仅仅是SQL ,泛指非关系型数据库。不依赖业务逻辑存储,而是以简单的key-value键值对存储,大大增加了数据库的扩展能力。

  • 不遵循SQL标注

  • 不支持ACID(原子性、隔离性、持久性、一致性)(支持事务但是不支持ACID)

  • 远超SQL的性能

1.3 NoSQL适用场景

  • 对数据高并发地读写

  • 海量数据的读写

  • 对数据要求高可扩展性

1.4 NoSQL不适用的场景

  • 需要事务支持的

  • 基于sql的结构化查询,处理复杂的关系,即需要即席查询

1.5 几种常见的NoSQL数据库

① Memcache
② Redis 支持持久化
③ MongoDB 文档型数据库

1.6 常见的关系型数据库

① 行式数据库
② 列式数据库

2 Redis

2.1 概述

  • redis是一个开源的key-value存储系统。

  • 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)list(链表)set(集合)zset(sorted-set有序集合)hash(哈希类型)

  • 这些数据类型都支持push\popadd\remove以及取交集、并集和差集及更丰富的操作,而且这些操作都是原子性的。

  • 在此基础上,Redis支持各种不同方式的排序

  • 与memcached一样,为了保证效率,数据都是缓存在内存中的。

  • 区别是Redis会周期性地把更新的数据写入磁盘或者把修改操作写入追加的记录文件。

2.2 安装

正常安装需要下载压缩包解压到虚拟机安装,还需要c的编译器运行环境,我这里直接用之前docker里面安装的redis了,解压安装详细可以参考视频:https://www.bilibili.com/video/BV1Rv41177Af?p=4&spm_id_from=pageDriver&vd_source=7f52e70d44280156501483f0968c6c3e

docker安装redis可以参考我之前的博客:【谷粒商城】(一)docker搭建以及项目的创建 - Tod4 - 博客园

2.3 redis的启动

① 前台启动

执行命令redis-server,前台命令执行后窗口不能关闭,否则服务器会停止,类似于Nacos服务器。

② 后台启动
  1. 备份redis.conf,拷贝一份配置文件到其他目录

  2. 在配置文件中设置daemonize no 为 yes

  3. 执行redis-server + 拷贝的配置文件

后台启动即是命令窗口关闭也不会停止redis服务器

③ docker启动redis

首先安装redis镜像

docker pull redis

创建实例并运行

mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

-v /mydata/redis/data:/data 表示将redis数据文件挂载到/mydata/redis/data

-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf 表示将配置文件挂载到/mydata/redis/conf/redis.conf

-d redis redis-server /etc/redis/redis.conf 表示在启动的时候配置/etc/redis/redis.conf

最新版本的redis配置文件挂载到了/usr/local/etc/redis,所以应该修改为:

mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf

docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-d redis redis-server /usr/local/etc/redis/redis.conf

这里先创建配置文件的原因是,容器中目录/usr/local/etc/redis/下是没有conf文件的,如果linux也不创建的话redis.cong就会被当做目录(linux是没有文件后缀名的,而且目录也被视作文件

docker设置redis自动启动

docker update redis --restart=always
④ 通过redis-cli客户端访问redis数据库

直接执行redis-cli命令即可,docker则执行:

docker exec -it redis redis-cli

2.4 redis相关知识介绍

端口号6379 来源:总结为程序员追星夹带私货(

数据库:redis默认16个数据库,类似数组下标从0开始,初始默认使用0号库,使用select+下标 访问。

127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> 

单线程+多路IO复用技术:相较于memcached的多线程加锁的方式以及传统的串行方式,redis采用单线程+多路IO复用技术的技术,即使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用selectpoll函数,传入多个文件描述符,如果有一个文件描述符就绪则返回,否则阻塞直到超时。得到就绪状态后真正的操作可以在同一个线程里执行,也可以启动线程执行。

1

2.5 key键操作

keys *:查看当前库的所有key

exists key:判断某个key是否存在

type key:查看key是什么类型

del key:删除指定的key数据

unlink key:根据value选择非阻塞删除

仅将keys从keyspace中删除,真正地删除是在后续的异步操作

expire key 10: 设置key10s后过期

ttl key: 查看多少秒过期,-1表示永不过期,-2表示已经过期

select:命令切换数据库

dbsize:查看当前数据库key的数量

flushdb:清空当前库

flushall:通杀所有库

2.6 redis常用数据类型及其操作

String

String 是Redis最基本的数据类型,一个key对应一个value,value的最大值为512M

String类型是二进制安全的,意味着redis可以包含任何数据,如图片、视频(可以转换为二进制编码)和序列化对象

set 添加键值对
127.0.0.1:6379> set v1 100
OK
127.0.0.1:6379> set v2 100
OK

对相同的key添加会覆盖掉原来的

get 根据键获取值
127.0.0.1:6379> set v1 1100
OK
127.0.0.1:6379> get v1
"1100"
append 在key对应的value后面追加value
127.0.0.1:6379> append v1 abc
(integer) 7
127.0.0.1:6379> get v1
"1100abc"

添加后会返回添加完成的长度

strlen 获取key对应value的字符串长度
127.0.0.1:6379> strlen v1
(integer) 7
setnx 不存在key的时候才能设置键值对
127.0.0.1:6379> setnx v1 acx
(integer) 0
127.0.0.1:6379> setnx v3 100
(integer) 1
incr、decr 将key对应的数字值加、减一
127.0.0.1:6379> incr k4 10
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> incr k4
(integer) 101
127.0.0.1:6379> decr k4
incrby、decrby 将key对应的数字值加、减步长
127.0.0.1:6379> incrby k4 100
(integer) 200
127.0.0.1:6379> decrby k4 200
(integer) 0
mset、mget 设置获取多个
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
msetnx 设置多个且key不存在
127.0.0.1:6379> msetnx k4 v4 k5 v5 k1 v2
(integer) 0

由于原子性,有一个设置失败则全部会设置失败

getrange 获取指定位置的字符串 包前包后
127.0.0.1:6379> set name lucymary
OK
127.0.0.1:6379> getrange name 03
(error) ERR wrong number of arguments for 'getrange' command
127.0.0.1:6379> getrange name 0 3
"lucy"
setrange 设置指定位置的字符串 包前包后
127.0.0.1:6379> setrange name 3 abc
(integer) 8
127.0.0.1:6379> get name
"lucabcry"
setex
127.0.0.1:6379> setex k20 10 val20
OK
127.0.0.1:6379> ttl k20
(integer) 6
127.0.0.1:6379> ttl k20
(integer) 5
127.0.0.1:6379> ttl k20
(integer) 4
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
4) "name"
原子操作

指的是不会被线程机制(如cpu时间片轮换)打断的操作,这种操作一旦开始就会执行到结束,不会切换到其他线程。

  • 在单线程中,能在一条指令内完成的操作都被认为是原子操作

  • 在多线程中,不被其他线程打断的操作都被认为是原子操作

Redis的原子性得益于redis的单线程

java的i++是原子操作吗

不是。比如两个线程分别进行100次i++,得到的结果是2~200

i++操作可以被看做三部分:

① 获取i的取值

② 执行加一

③ 将结果赋值给i

这三步操作是随时能出现切换线程的情况的

数据结构

String的数据结构为简单动态字符串(Simple Dynamic String, SDS)是可以修改的字符串,内部结构实现上类似于Java的ArratList,采取预先分配冗余空间的方式来减少内存的频繁分配。

当字符串小于1M的时候,扩容都是加倍现有的空间,如果超过1M,扩容只会增加1M的空间,需要注意字符串的最大长度为512M。

3 Redis列表

3.1 简介

单键多值,redis列表是简单的字符串列表,按照插入顺序排序,可以选择添加一个元素到列表的表头或者表尾。

底层其实是一个双向链表,对两边的操作性能很高,但是按照索引下标操作中间元素的性能较差。

3.2 常用命令

lpush、rpush 从表尾表头添加字符串
127.0.0.1:6379> lpush k1 v1 v2 v3
(integer) 3
127.0.0.1:6379> keys *
1) "k1"
127.0.0.1:6379> get k1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> lrange k1 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> rpush k2 v1 v2 v3
(integer) 3
127.0.0.1:6379> lrange k2 0 -1
1) "v1"
2) "v2"
3) "v3"

lpush类似于链表的头插法,插入结果为倒序

rpush类似于尾插法,为正序

lrange 根据下标获取列表key

如上,0 -1表示从左边第一个元素到右边最后一个元素

lpop rpop 从左边、右边取出一个值

值在键在,值光键亡

127.0.0.1:6379> rpop k1
"v1"
127.0.0.1:6379> rpop k1
"v2"
127.0.0.1:6379> rpop k1
"v3"
127.0.0.1:6379> rpop k1
(nil)
127.0.0.1:6379> keys *
1) "k2"

count表示取出的个数,可以不写

rpoplpush 从列表key1右边取出一个值key的左边
127.0.0.1:6379> rpoplpush k1 k2
"v3"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "v2"
127.0.0.1:6379> lrange k2 0 -1
1) "v3"
2) "v1"
3) "v2"
4) "v3"

注意没有其他的命令

127.0.0.1:6379> lpoprpush k1 k2
(error) ERR unknown command `lpoprpush`, with args beginning with: `k1`, `k2`,
lindex 按照下标获取key对应的value
127.0.0.1:6379> lindex k1 1
"v2"
llen
127.0.0.1:6379> llen k1
(integer) 2
linsert before/after 在key列表中的value前、后插入
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "v2"
127.0.0.1:6379> linsert k1 before v2 v
(integer) 3
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "v"
3) "v2"
127.0.0.1:6379> linsert k1 after v2 v
(integer) 4
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "v"
3) "v2"
4) "v"
127.0.0.1:6379> linsert k1 after v vv
(integer) 5
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "v"
3) "vv"
4) "v2"
5) "v"

可以看到上面有多个对应的value只会在第一个匹配的value前后新增

lrem 从key列表的左边开始删除count个value
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "v"
3) "vv"
4) "v2"
5) "v"
127.0.0.1:6379> lrem k1 3 v
(integer) 2
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "vv"
3) "v2"

可以看到上面删除的个数大于已经有的会删除全部的

lset 将列表key下标为index的值替换为value
127.0.0.1:6379> lrange k1 0 -1
1) "v1"
2) "vv"
3) "v2"
127.0.0.1:6379> lset k1 0 v
OK
127.0.0.1:6379> lrange k1 0 -1
1) "v"
2) "vv"
3) "v2"

4 Redis集合

Redis set对外提供的与list类似是一个列表的功能,特殊之处在于list可以自动排重。并且set提供了一个能够判断成员是否存在的重要接口,这也是列表不能提供的。

redis的set是String类型的无序集合,底层其实是一个value为null的hash表,所以添加删除修改的复杂度都是O(1)

4.1 常用命令

sadd ... 添加
127.0.0.1:6379> sadd k1 v1 v2 v2 v3
(integer) 3
127.0.0.1:6379> 

smembers 取出集合的所有值

127.0.0.1:6379> smember k1
(error) ERR unknown command `smember`, with args beginning with: `k1`, 
127.0.0.1:6379> smembers k1
1) "v3"
2) "v2"
3) "v1"

注意s

sismember 判断一个值是否为集合中的值
127.0.0.1:6379> sismember k1 v1
(integer) 1
127.0.0.1:6379> sismember k1 v4
(integer) 0
scard 返回集合中的值
127.0.0.1:6379> scard k1
(integer) 3
srem ... 删除集合中的值
127.0.0.1:6379> srem k1 v1
(integer) 1
127.0.0.1:6379> smembers
(error) ERR wrong number of arguments for 'smembers' command
127.0.0.1:6379> smembers k1
1) "v3"
2) "v2"
scard 随机突出一些值
127.0.0.1:6379> spop k1
"v2"
127.0.0.1:6379> scard
(error) ERR wrong number of arguments for 'scard' command
127.0.0.1:6379> scard k1
(integer) 1

count不写默认为1

全部吐出之后则集合销毁

srandmember 随机从集合中取出count个值,集合个数不变
127.0.0.1:6379> srandmember k1 
"v3"

count不写默认为1

smove
127.0.0.1:6379> sadd k1 v1 v2 v3
(integer) 3
127.0.0.1:6379> sadd k2 v4 v2 v3
(integer) 3
127.0.0.1:6379> smove k1 k2 v1
(integer) 1
127.0.0.1:6379> smembers k1
1) "v3"
2) "v2"
127.0.0.1:6379> smembers k2
1) "v3"
2) "v4"
3) "v2"
4) "v1"

从一个集合移动一个值到另一个集合

sinter 返回两集合交集
sunion 返回两集合并集
sdiff 返回两集合差集
127.0.0.1:6379> sinter k1 k2
1) "v3"
2) "v2"
127.0.0.1:6379> sunion k1 k2
1) "v3"
2) "v4"
3) "v2"
4) "v1"
127.0.0.1:6379> sdiff k1 k2
(empty array)
数据结构

Set数据结构是dict字典,字典使用哈希表实现的,java中的HashSet内部也是使用的HashMap,只不过所有的value都指向同一个对象,Redis的set结构也是一样,所有的value指向同一个内部值。

5 Redis 哈希 Hash

5.1简介

Redis是一个键值对集合,RedisHash是一个String类型的 field 和 value 的映射表,hash特别适合于存储对象,类似于java里面的 Map<String, Object>

5.2 为什么要采用这种存储结构?

如上图,存储对象的数据的时候,通常三种存储方式: 第一种是json格式的字符串,但是这种需要修改数据的时候及其困难:需要转换为对象然后修改再转换为字符串进行存储。第二种是类似于yml格式的存储,这种存储方式的缺点是数据存储较为分散,当存储的对象和对象的属性特别多的时候,数据会非常凌乱。

而采用key-field-value的格式,修改和存储都比较简单。

5.3 常用命令
hset 设置对象字段值
hget 获取对象的字段值
127.0.0.1:6379> hset user:101 id 1
(integer) 1
127.0.0.1:6379> hset user:101 name zhangsan
(integer) 1
127.0.0.1:6379> hget user:101 id
"1"
127.0.0.1:6379> hget user:101 name
"zhangsan"
hmset 设置多个字段值
127.0.0.1:6379> hmset user:102 id 2 name lisi
OK
hexists 判断对象是否存在字段field
127.0.0.1:6379> hexists user:102 id
(integer) 1
127.0.0.1:6379> hexists user:102 gender
(integer) 0
hkeys 获取key的所有字段
127.0.0.1:6379> hkeys user:101
1) "id"
2) "name"
hvals 获取key的所有字段值
127.0.0.1:6379> hvals user:101
1) "1"
2) "zhangsan"
hincrby
127.0.0.1:6379> hincrby user:101 id 2
(integer) 3
127.0.0.1:6379> hvals user:101
1) "3"
2) "zhangsan"
hsetnx 当不存在字段才能设置字段值
127.0.0.1:6379> hsetnx user:101 id 2
(integer) 0
127.0.0.1:6379> hsetnx user:101 gender nan
(integer) 1
127.0.0.1:6379> hkeys user:101
1) "id"
2) "name"
3) "gender"
5.4 数据结构

Hash类型对应的数据结构有两种,一种是ziplist(压缩列表),另一种是hashtable(哈希表)。当filed-value长度较短且个数较少的时候,使用ziplist否则使用hsahtable。

6 有序集合 Zset

6.1 简介

Zset与普通集合Set类似,是一个没有重复元素的字符串集合。不同之处在于Zset有序集合的每一个成员都关联了一个评分(score),这个评分被用于从低到高的形式排序有序集合中的成员。集合中的成员是惟一的,但是集合中的评分是可以重复的。

6.2 常用命令
zadd
127.0.0.1:6379> zadd topn 100 java 200 c++ 300 python
(integer) 3
zrange [withscores] 返回有序集合key中下标在 start-end 范围内的成员
127.0.0.1:6379> zrange topn 0 -1 withscores
1) "java"
2) "100"
3) "c++"
4) "200"
5) "python"
6) "300"
zrangebyscore [withscores] [limit offset count] 取出有序集合中评分在200-300范围之内的成员
127.0.0.1:6379> zrangebyscore topn 200 300 withscores
1) "c++"
2) "200"
3) "python"
4) "300"
zrevrangebyscore [withscores] 倒序输出
127.0.0.1:6379> zrevrange topn 0 -1 withscores
1) "python"
2) "300"
3) "c++"
4) "200"
5) "java"
6) "100"
zincrby 对有序集合中key的value增加increament
127.0.0.1:6379> zincrby topn 50 java
"150"
127.0.0.1:6379> zrange topn 0 -1 withscores
1) "java"
2) "150"
3) "c++"
4) "200"
5) "python"
6) "300"
zrem 删除有序集合key中的value
zcount 统计分数在min-max范围之内的成员
127.0.0.1:6379> zcount topn 100 200
(integer) 2
zrank 返回成员value在有序集合key中的排名 从0开始
127.0.0.1:6379> zrank topn java
(integer) 0
6.3 底层数据结构

zset底层使用了两种数据结构

① hash:用于关联元素value和权重score,保障元素value的唯一性,通过value能够查找到对应的score。

② 跳表:跳表的目的在于给元素value排序,根据score的范围获取列表。

6.4 跳表

跳表由表头(维护各个层的首个节点指针),跳表节点(保存元素值和多个层),(保存指向该层的其他节点的指针,高层指针的跨度要大于底层指针的跨度),表尾(全部由空指针组成)。

跳表查找的时候,会从高层先进行访问,然后随着元素范围的缩小慢慢降低层次,时间复杂度为O(log n)。