• Redis第一篇之基础入门,可以快速上手进行一些基础的操作


    在看redis之前,先来了解一下NoSQL(非关系型数据库)

    1. NoSQL数据库简介

    1.1 概述

    NoSQL(Not Only SQL),意为"不仅仅是SQL",泛指非关系型数据库.NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大增加了数据库的扩展能力,有以下的特点:

    • 不遵循SQL标准
    • 不支持ACID
    • 远超于SQL的性能

    1.2 NoSQL的适应性场景:

    • 对数据高并发的读写
    • 海量数据的读写
    • 对数据高扩展性的

    1.3 NoSQL不适应的场景:

    • 需要事务的支持
    • 基于sql的结构化查询,处理复杂的关系,需要即时查询
    • 用不着sql和用了sql也不行的情况,请考虑使用NoSQL

    1.4 Memcache

    • 很早出现的NoSQL数据库

    • 数据都存储在内存中,一般不持久化

    • 支持简单的key-value模式,支持类型单一

    • 一般是作为缓存数据库辅助持久化的数据库

    1.5 Redis

    • 几乎覆盖了Memcache的绝大部分功能
    • 数据都在内存中,支持持久化,主要用作备份恢复
    • 除了支持简单的key-value模式,还支持多种数据结构的存储,如list,set,hash,zset等
    • 一般是作为缓存数据库辅助持久化的数据库

    1.6 MongoDB

    • 高性能,开源,模式自由的文档型数据库
    • 数据都存储在内存中,如果内存不足,把不常用的数据保存到硬盘
    • 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
    • 支持二进制数据及大型对象
    • 可以根据数据的特点替代RDBMS,成为独立的数据库.或者配合RDBMS,存储特定的数据

    2. Redis的性能和基础知识

    2.1 测试性能

    redis-benchmark是一个压力测试工具!
    官方自带的性能测试工具!
    redis-benchmark命令参数!
    我们来简单的测试一下

    # 测试: 100个并发连接 每个并发100000个请求
    redis-benchmark -h localhost -p 6379 -c 100 -n 10000
    
    • 1
    • 2

    在这里插入图片描述
    在这里插入图片描述

    可以发现效率非常的快,费时少

    2.2 Redis的基础知识

    2.2.1 Redis默认有16个数据库

    用vim命令进入配置文件中进行查看
    在这里插入图片描述
    在这里插入图片描述

    默认使用的是第0个数据库,可以使用select进行切换数据库

    比如说我们使用第2个,用 select 命令进行切换
    在这里插入图片描述

    127.0.0.1:6379> select 2  # select切换数据库 
    OK
    127.0.0.1:6379[2]> DBSIZE # 查看数据库中k-v个数
    (integer) 0
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    2.2.2 设置键值

    127.0.0.1:6379[2]> set name xiaowang  # 设置键值
    OK
    127.0.0.1:6379[2]> DBSIZE
    (integer) 1
    127.0.0.1:6379[2]> KEYS *         # 查看所有的key
    1) "name"
    127.0.0.1:6379[2]> get name       # 得到当前key对应的value值
    "xiaowang"
    127.0.0.1:6379[2]> FLUSHDB        # 清空当前的DB,FLUSHALL是清空所有的DB 
    OK
    127.0.0.1:6379[2]> DBSIZE
    (integer) 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.2.3 Redis是单线程的

    官方文档: 因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

    详细原因:

    • 不需要各种锁的性能消耗
      Redis 的数据结构并不全是简单的 Key-Value,还有 list,hash 等复杂的结构。这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
      总之,在单线程的情况下,代码更清晰,处理逻辑更简单,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗,不存在多进程或者多线程导致的切换而消耗 CPU。
    • 单线程多进程的集群方案
      单线程的威力实际上非常强大,每核效率也非常高。多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。所以单线程、多进程的集群不失为一个时髦的解决方案。
    • CPU 消耗
      采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。但是如果 CPU 成为 Redis 瓶颈,或者不想让服务器其他 CPU 核闲置,那怎么办?
      可以考虑多起几个 Redis 进程,Redis 是 key-value 数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些 key 放在哪个 Redis 进程上就可以了。

    2.2.4 为什么Redis是单线程的还这么快?

    误区:

    • 高性能的服务器一定是多线程的?
      不一定
    • 多线程一定比多线程效率高?
      不一定,多线程会进行上下文的切换,是一个耗时的操作

    核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程会进行上下文的切换,比较耗时间.对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,内存情况下,这个就是最佳的方案.

    3. Redis中的五大数据类型

    Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库,缓存消息中间件MQ.它支持多种类型的数据结构,如字符串(string),散列(hash),列表(list),集合(set),有序集合(sorted sets),范围查询和地理空间索引半径查询.Redis内置了复制(replication),LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过Redis哨兵和自动分区提高可用性.

    Redis-Key (基础命令)

    127.0.0.1:6379> FLUSHALL   			# 清空所有的数据库
    OK
    127.0.0.1:6379> set name wang 		# 设置key-value
    OK
    127.0.0.1:6379> keys * 				# 查看所有的key
    1) "name"
    127.0.0.1:6379> set age 1 			# 设置key-value
    OK
    127.0.0.1:6379> keys *  			# 查看所有的key
    1) "age"
    2) "name"
    127.0.0.1:6379> get age				# 获取key对应的value
    "1"
    127.0.0.1:6379> EXISTS name			# 查看key是否存在
    (integer) 1
    127.0.0.1:6379> EXISTS age			# 查看key是否存在
    (integer) 1
    127.0.0.1:6379> EXISTS gender		# 查看key是否存在
    (integer) 0
    127.0.0.1:6379> move name 1			# 移动key-value 到 1号数据库
    (integer) 1
    127.0.0.1:6379> SELECT 1			# 切换到 1 号数据库
    OK
    127.0.0.1:6379[1]> keys *			# 查看所有的key
    1) "name"
    127.0.0.1:6379[1]> FLUSHDB			# 清空当前数据库
    OK
    127.0.0.1:6379[1]> SELECT 0			# 切换到 0 号数据库
    OK
    127.0.0.1:6379> keys *				# 查看所有的key
    1) "age"
    127.0.0.1:6379> set name xiaowang	# 设置key-value
    OK
    127.0.0.1:6379> keys *				# 查看所有的key
    1) "age"
    2) "name"
    127.0.0.1:6379> EXPIRE name 5		# 让key在5s后过期
    (integer) 1
    127.0.0.1:6379> ttl name			# 查看剩余过期时间
    (integer) 0
    127.0.0.1:6379> keys *				# 查看所有的key,发现name已经消失了
    1) "age"
    127.0.0.1:6379> set name xiaowang	# 设置key-value
    OK
    127.0.0.1:6379> TYPE name			# 查看当前key的类型
    string
    127.0.0.1:6379> TYPE age			# 查看当前key的类型
    string
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    如果遇到不会的命令可以去官网查看(https://www.redis.net.cn/)

    在这里插入图片描述

    3.1 String (字符串)

    3.1.1 基本操作

    ##############################################################################
    127.0.0.1:6379> set key1 v1			# 设置值
    OK	
    127.0.0.1:6379> get key1			# 获取值
    "v1"
    127.0.0.1:6379> EXISTS key1			# 判断是否存在
    (integer) 1
    127.0.0.1:6379> APPEND key1 hello	# 拼接字符串
    (integer) 7
    127.0.0.1:6379> get key1			# 获取值
    "v1hello"
    127.0.0.1:6379> APPEND key1 "hello"	# 拼接字符串,如果key不存在,相当于set-key,设置值
    (integer) 12
    127.0.0.1:6379> get key1			# 获取值
    "v1hellohello"
    127.0.0.1:6379> STRLEN key1			# 获取字符串的长度
    (integer) 12
    127.0.0.1:6379> APPEND key1 ",xiaowang"		# 拼接字符串
    (integer) 21
    127.0.0.1:6379> STRLEN key1			# 获取长度
    (integer) 21
    127.0.0.1:6379> get key1			# 获取值
    "v1hellohello,xiaowang"
    ##############################################################################
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.1.2 自增(incr,incrby),自减(decr,decrby)

    ##############################################################################
    127.0.0.1:6379> set views 0    
    OK
    127.0.0.1:6379> get views
    "0"
    127.0.0.1:6379> incr views          # +1操作
    (integer) 1
    127.0.0.1:6379> get views
    "1"
    127.0.0.1:6379> incr views			# +1操作
    (integer) 2
    127.0.0.1:6379> get views
    "2"
    127.0.0.1:6379> decr views			# -1操作
    (integer) 1
    127.0.0.1:6379> decr views			# +1操作
    (integer) 0
    127.0.0.1:6379> decr views			# +1操作
    (integer) -1
    127.0.0.1:6379> get views
    "-1"
    127.0.0.1:6379> INCRBY views 10		# +10操作,指定增量
    (integer) 9
    127.0.0.1:6379> DECRBY views 5		# -5操作
    (integer) 4
    127.0.0.1:6379> get views
    "4"
    ##############################################################################
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3.1.3 字符串范围 (getrange)

    ##############################################################################
    127.0.0.1:6379> set key1 "hello"
    OK
    127.0.0.1:6379> get key1
    "hello"
    127.0.0.1:6379> APPEND key1 ",xiaowang"   # 拼接字符串
    (integer) 14
    127.0.0.1:6379> get key1
    "hello,xiaowang"
    127.0.0.1:6379> GETRANGE key1 0 3		  # 截取字符串,是闭区间的[0,3](0.1.2.3)
    "hell"
    127.0.0.1:6379> GETRANGE key1 0 -1		  # [0,-1] 表示截取整个字符串
    "hello,xiaowang"
    ##############################################################################
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.1.4 替换(setrange)

    ##############################################################################
    127.0.0.1:6379> set key2 abcdefg
    OK
    127.0.0.1:6379> get key2
    "abcdefg"
    127.0.0.1:6379> SETRANGE key2 1 xx		  # 替换指定位置开始的字符串
    (integer) 7
    127.0.0.1:6379> get key2
    "axxdefg"
    ##############################################################################
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.1.5 setex和setnx

    ##############################################################################
    # setex: 设置过期时间
    127.0.0.1:6379> setex key3 30 "hello"     # 30s后key3过期
    OK				  
    127.0.0.1:6379> ttl key3				  # 查看当前剩余时间
    (integer) 17
    127.0.0.1:6379> get key3
    "hello"
    # setnx: 不存在时再设置值,存在时不生效(在分布式锁中会常常使用)
    127.0.0.1:6379> setnx mykey "redis"		  # 设置mykey,当前不存在,设置成功
    (integer) 1
    127.0.0.1:6379> keys *					  # 查看当前所有的key,key3已经过了30s失效了
    1) "mykey"
    2) "key2"
    3) "key1"
    127.0.0.1:6379> setnx mykey "MongoDB"	  # mykey存在,所以不做修改,创建失败
    (integer) 0
    127.0.0.1:6379> get mykey
    "redis"
    ##############################################################################
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.1.6 mset和mget

    # mset: 批量设置k-v
    127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 k4 v4
    OK
    127.0.0.1:6379> keys *
    1) "k4"
    2) "k1"
    3) "k3"
    4) "k2"
    # mget: 批量获取k-v
    127.0.0.1:6379> MGET k1 k2 k3 k4
    1) "v1"
    2) "v2"
    3) "v3"
    4) "v4"
    # msetnx 是一个原子操作,要么一起成功,要么一起失败
    127.0.0.1:6379> MSETNX k1 v1 k5 v5   # k1存在,所以不能设置成功,k5为nil
    (integer) 0
    127.0.0.1:6379> get key5
    (nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.1.7 getset

    # getset: 先get再set
    127.0.0.1:6379> getset db redis   #先get发现db不存在,再set db为redis
    (nil)
    127.0.0.1:6379> get db			  # get发现db为redis
    "redis"
    127.0.0.1:6379> getset db mongodb # 先get发现db存在且为redis,再set db为mongodb
    "redis"
    127.0.0.1:6379> get db			  # get发现db已经被set为mongodb了
    "mongodb"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.2 List (列表)

    在redis里面,我们可以把list玩成栈,队列,阻塞队列!

    所有的 list 命令都是用 l 开头的

    3.2.1 lpush,rpush,lrange

    # lpush 将一个值或者多个值,插入到列表的头部(左)
    127.0.0.1:6379> LPUSH list k1
    (integer) 1
    127.0.0.1:6379> LPUSH list k2
    (integer) 2
    127.0.0.1:6379> LPUSH list k3
    (integer) 3
    # LRANGE 获取全部数据
    127.0.0.1:6379> LRANGE list 0 -1	
    1) "k3"
    2) "k2"
    3) "k1"
    127.0.0.1:6379> LRANGE list 0 1		# 通过区间来获取具体的值
    1) "k3"
    2) "k2"
    # rpush 将一个值或者多个值,插入到列表的尾部(右)
    127.0.0.1:6379> RPUSH list right
    (integer) 4
    127.0.0.1:6379> LRANGE list 0 -1	# 获取全部数据,发现right在最后
    1) "k3"
    2) "k2"
    3) "k1"
    4) "right"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3.2.2 lpop,rpop

    127.0.0.1:6379> LRANGE list 0 -1
    1) "k3"
    2) "k2"
    3) "k1"
    4) "right"
    
    # lpop: 移除列表的第一个元素
    127.0.0.1:6379> LPOP list
    "k3"
    127.0.0.1:6379> LRANGE list 0 -1
    1) "k2"
    2) "k1"
    3) "right"
    
    # rpop: 移除列表的最后一个元素
    127.0.0.1:6379> RPOP list
    "right"
    127.0.0.1:6379> LRANGE list 0 -1
    1) "k2"
    2) "k1"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.2.3 lindex和llen

    127.0.0.1:6379> LINDEX list 0   # 获取0号下标元素
    "k2"
    127.0.0.1:6379> LINDEX list 1	# 获取1号下标元素
    "k1"
    #################################################
    127.0.0.1:6379> LLEN list   	# 获取list长度
    (integer) 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.2.4 lrem 移除

    127.0.0.1:6379> LREM list 1 k1       # 将k1移除一个
    (integer) 1
    127.0.0.1:6379> LRANGE list 0 -1
    1) "k2"
    2) "k2"
    3) "k1"
    127.0.0.1:6379> LREM list 2 k2		# 将k2移除两个
    (integer) 2
    127.0.0.1:6379> LRANGE list 0 -1
    1) "k1"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2.5 ltrim 截短

    # 清空当前数据库
    127.0.0.1:6379> FLUSHDB
    OK
    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
    
    # 通过下标截取指定的长度,mylist已经被改变了
    127.0.0.1:6379> LTRIM mylist 1 2		
    OK
    127.0.0.1:6379> LRANGE mylist 0 -1
    1) "hello2"
    2) "hello3"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.2.6 rpoplpush

    # 清空当前数据库
    127.0.0.1:6379> FLUSHDB
    OK
    127.0.0.1:6379> rpush list "hello0"
    (integer) 1
    127.0.0.1:6379> rpush list "hello1"
    (integer) 2
    127.0.0.1:6379> rpush list "hello2"
    (integer) 3
    
    # rpop移除list中的最后一个元素lpush到mylist中,如果mylist不存在则会创建mylist
    127.0.0.1:6379> rpoplpush list mylist	
    "hello2"
    # 查看list中的所有数据
    127.0.0.1:6379> lrange list 0 -1
    1) "hello0"
    2) "hello1"
    # 查看mylist中的所有数据
    127.0.0.1:6379> lrange mylist 0 -1
    1) "hello2"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.2.7 lset 设置,更新

    # 清空当前数据库
    127.0.0.1:6379> FLUSHDB
    OK
    # 判断list是否存在
    127.0.0.1:6379> EXISTS list
    (integer) 0
    # 往0号位置设置一个item的值,list不存在会报错
    127.0.0.1:6379> LSET list 0 item
    (error) ERR no such key
    # 创建list,将value1添加进去
    127.0.0.1:6379> LPUSH list value1
    (integer) 1
    127.0.0.1:6379> LRANGE list 0 0
    1) "value1"
    # lset: 将指定下标的值替换成另外一个值,相当于一个更新操作
    # 如果下标存在,则更新
    127.0.0.1:6379> LSET list 0 item
    OK
    127.0.0.1:6379> LRANGE list 0 0
    1) "item"
    # 1号位置的值不存在,所以不能更新
    127.0.0.1:6379> LSET list 1 other
    (error) ERR index out of range
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3.2.8 lindex

    127.0.0.1:6379> FLUSHDB
    OK
    127.0.0.1:6379> rpush list hello
    (integer) 1
    127.0.0.1:6379> rpush list world
    (integer) 2
    127.0.0.1:6379> LRANGE list 0 -1
    1) "hello"
    2) "world"
    
    # linsert: 将某个具体的value插入到列表中某个元素的前面或者后面
    # 插入到world的前面
    127.0.0.1:6379> LINSERT list before world other
    (integer) 3
    127.0.0.1:6379> LRANGE list 0 -1
    1) "hello"
    2) "other"
    3) "world"
    # 插入到hello的后面
    127.0.0.1:6379> LINSERT list after hello new
    (integer) 4
    127.0.0.1:6379> LRANGE list 0 -1
    1) "hello"
    2) "new"
    3) "other"
    4) "world"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3.2.9 一个小结

    • list实际上是一个链表,可以在某个元素的前面,后面插入,也可以在头尾插入
    • 当我们lpush或者rpush的时候,如果key不存在,则会创建链表
    • 如果key存在的话,新增内容
    • 如果移除了所有的内容,也就是一个空链表,也代表不存在
    • 在两边插入或者删除值效率最高,中间元素相对来说效率会低一点

    3.3 Set(集合)

    set是一个无序不重复集合,命令都是s开头的

    3.3.1 sadd,smembers,sismember

    # sadd: 添加元素,set不存在时创建set,存在时直接添加
    127.0.0.1:6379> sadd myset hello
    (integer) 1
    127.0.0.1:6379> sadd myset xiaowang
    (integer) 1
    127.0.0.1:6379> sadd myset xiaozhang
    (integer) 1
    # smembers: 输出set中的所有元素
    127.0.0.1:6379> SMEMBERS myset
    1) "xiaozhang"
    2) "hello"
    3) "xiaowang"
    # ismember: 判断某个元素是否存在
    127.0.0.1:6379> SISMEMBER myset hello
    (integer) 1
    127.0.0.1:6379> SISMEMBER myset wang
    (integer) 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.3.2 scard,srem,srandmember

    # scard: 获取集合中的元素个数
    127.0.0.1:6379> SCARD myset
    (integer) 3
    
    # srem: 移除某个特定的值
    127.0.0.1:6379> srem myset hello
    (integer) 1
    # 查看集合中的元素
    127.0.0.1:6379> SMEMBERS myset
    1) "xiaozhang"
    2) "xiaowang"
    # 查看集合个数
    127.0.0.1:6379> SCARD myset
    (integer) 2
    
    # srandmember: 随机抽取一个元素
    127.0.0.1:6379> SRANDMEMBER myset
    "xiaowang"
    127.0.0.1:6379> SRANDMEMBER myset
    "hello1"
    127.0.0.1:6379> SRANDMEMBER myset
    "hello1"
    127.0.0.1:6379> SRANDMEMBER myset
    "xiaowang"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3.3.3 spop,smove

    # 查看所有元素
    127.0.0.1:6379> SMEMBERS myset
    1) "hello1"
    2) "xiaozhang"
    3) "hello"
    4) "xiaowang"
    
    # spop: 随机移除一个元素
    127.0.0.1:6379> SPOP myset
    "hello1"
    # spop: 随机移除一个元素
    127.0.0.1:6379> SPOP myset
    "hello"
    
    # 查看所有元素
    127.0.0.1:6379> SMEMBERS myset
    1) "xiaozhang"
    2) "xiaowang"
    # 再创建一个myset2,添加一个set2
    127.0.0.1:6379> SADD myset2 set2
    (integer) 1
    
    # smove: 将指定的值进行移动
    # 将myset中的xiaowang移动到myset2
    127.0.0.1:6379> SMOVE myset myset2 xiaowang
    (integer) 1
    127.0.0.1:6379> SMEMBERS myset2
    1) "set2"
    2) "xiaowang"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3.3.4 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 key1 d
    (integer) 1
    127.0.0.1:6379> sadd key2 b
    (integer) 1
    127.0.0.1:6379> sadd key2 d
    (integer) 1
    127.0.0.1:6379> sadd key2 e
    (integer) 1
    # sdiff: 求差集
    # key1和key2的差集为 a,c
    127.0.0.1:6379> SDIFF key1 key2
    1) "a"
    2) "c"
    127.0.0.1:6379> SDIFF key2 key1
    1) "e"
    
    # sinter: 求交集
    # key1和key2的交集为 b,d
    127.0.0.1:6379> SINTER key1 key2
    1) "d"
    2) "b"
    
    # sunion: 求并集
    127.0.0.1:6379> SUNION key1 key2
    1) "a"
    2) "e"
    3) "c"
    4) "b"
    5) "d"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    3.4 Hash(哈希)

    Map集合,key-map集合,这时候的值是一个map集合.本质和String类型没有太大的区别,还是一个简单的集合

    hash的命令都是以h开头的

    基本操作

    # hset
    127.0.0.1:6379> hset myhash field1 v1
    (integer) 1
    
    # hget
    127.0.0.1:6379> hget myhash field1
    "v1"
    
    # hmset
    127.0.0.1:6379> hmset myhash field1 hello field2 world
    OK
    
    # hmget
    127.0.0.1:6379> hmget myhash field1 field2
    1) "hello"
    2) "world"
    
    # hgetall
    127.0.0.1:6379> hgetall myhash
    1) "field1"
    2) "hello"
    3) "field2"
    4) "world"
    
    # hlen: 求内容大小
    127.0.0.1:6379> hlen myhash
    (integer) 2
    
    # hexists: 判断是否存在
    127.0.0.1:6379> HEXISTS myhash field1
    (integer) 1
    127.0.0.1:6379> HEXISTS myhash field3
    (integer) 0
    
    # hkeys: 获取hash中所有的键
    127.0.0.1:6379> hkeys myhash
    1) "field1"
    2) "field2"
    
    #hvals: 获取hash中所有的值
    127.0.0.1:6379> hvals myhash
    1) "hello"
    2) "world"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    剩下的都和string里面的差不多,就是命令前加了个h,就不过多写了

    3.5 Zset(有序集合)

    zset就是在set的基础上加了一个值

    set k1 v1
    zset k1 score v1
    
    • 1
    • 2

    基本操作

    # zadd  添加一个值
    127.0.0.1:6379> ZADD myzset 1 one	# 
    (integer) 1
    127.0.0.1:6379> ZADD myzset 2 two	
    (integer) 1
    
    #zadd 添加多个值
    127.0.0.1:6379> ZADD myzset 3 three 4 four	
    (integer) 2
    # zrange 查看所有元素
    127.0.0.1:6379> ZRANGE myzset 0 -1  
    1) "one"
    2) "two"
    3) "three"
    4) "four"
    
    ##############################################################################
    
    127.0.0.1:6379> zadd salary 2500 xiaowang
    (integer) 1
    127.0.0.1:6379> zadd salary 5000 xiaozhang
    (integer) 1
    127.0.0.1:6379> zadd salary 500 xiaoli
    (integer) 1
    # zrangebyscore: 排序,-inf是负无穷,+inf是正无穷
    127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
    1) "xiaoli"
    2) "xiaowang"
    3) "xiaozhang"
    # zrangebyscore..........withscores: 在显示的时候带上数据
    127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
    1) "xiaoli"
    2) "500"
    3) "xiaowang"
    4) "2500"
    5) "xiaozhang"
    6) "5000"
    # zrangebyscore: 排序,负无穷-3000之间的进行排序
    127.0.0.1:6379> ZRANGEBYSCORE salary -inf 3000 withscores
    1) "xiaoli"
    2) "500"
    3) "xiaowang"
    4) "2500"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    4. 事务

    Redis 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化

    在事务的执行过程中,会按照顺序执行!

    特性: 一次性,顺序性,排他性 ,执行一系列的命令!

    -------- 队列 set set set  执行 ------------
    
    • 1

    Redis单条命令是保证原子性的

    但是Redis的事务是不保证原子性的,没有隔离级别的概念

    所有的命令在事务,并没有被直接执行,只有发起执行命令的时候才会执行

    redis的事务:

    • 开启事务 (multi)
    • 命令入队 (…)
    • 执行事务 (exec)

    4.1 执行事务

    # multi 开启一个事务
    127.0.0.1:6379> multi			
    OK
    
    # 命令入队
    127.0.0.1:6379(TX)> set k1 v1
    QUEUED
    127.0.0.1:6379(TX)> set k2 v2
    QUEUED
    127.0.0.1:6379(TX)> get k2
    QUEUED
    127.0.0.1:6379(TX)> set k3 v3
    QUEUED
    
    # 执行事务
    127.0.0.1:6379(TX)> exec
    1) OK
    2) OK
    3) "v2"
    4) OK
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注意: exec执行后,这个事务就执行结束了,想要另外操作,需要再次去开启事务

    4.2 放弃事务

    # 开启一个事务
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> set k1 v1
    QUEUED
    127.0.0.1:6379(TX)> set k2 v2
    QUEUED
    127.0.0.1:6379(TX)> set k4 v4
    QUEUED
    
    # discard 放弃一个事务
    127.0.0.1:6379(TX)> discard
    OK
    
    # 放弃一个事务后,事务队列中的命令都不会被执行
    127.0.0.1:6379> get k4
    (nil)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.3 出现的异常

    4.3.1 编译型异常

    代码有问题,命令有错,事务中所有的命令都不会被执行

    # 开启一个事务
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> set k1 v1
    QUEUED
    127.0.0.1:6379(TX)> set k2 v2
    QUEUED
    127.0.0.1:6379(TX)> set k3 v3
    QUEUED
    
    # 错误的命令
    127.0.0.1:6379(TX)> getset k3
    (error) ERR wrong number of arguments for 'getset' command
    127.0.0.1:6379(TX)> set k4 v4
    QUEUED
    127.0.0.1:6379(TX)> set k0 v5
    QUEUED
    
    # 执行时事务报错
    127.0.0.1:6379(TX)> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    # 所有的命令都不会被执行
    127.0.0.1:6379> get k1
    (nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4.3.2 运行时异常

    如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛出异常

    # 清空当前库
    127.0.0.1:6379> flushdb
    OK
    127.0.0.1:6379> set k1 v1
    OK
    # 开启事务
    127.0.0.1:6379> multi
    OK
    # k1 自增1,执行的时候失败
    127.0.0.1:6379(TX)> incr k1
    QUEUED
    127.0.0.1:6379(TX)> set k2 v2
    QUEUED
    127.0.0.1:6379(TX)> set k3 v3
    QUEUED
    127.0.0.1:6379(TX)> get k3
    QUEUED
    
    # 出现异常,但是其他的命令不会收到影响,正常执行
    127.0.0.1:6379(TX)> exec
    1) (error) ERR value is not an integer or out of range
    2) OK
    3) OK
    4) "v3"
    
    # 可以正确的获得到k2和k3的值
    127.0.0.1:6379> get k2
    "v2"
    127.0.0.1:6379> get k3
    "v3"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    4.4 监控 Watch(面试常问)

    • 悲观锁
      很悲观,认为什么时候都会出问题,所以无论做什么都会加锁!比较影响性能.

    • 乐观锁

      • 很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下在此期间是否有人修改过这个数据
      • 获取version
      • 更新的时候比较version
    • redis的监视测试

      正常执行成功:

      127.0.0.1:6379> set money 100
      OK
      127.0.0.1:6379> set out 0
      OK
      # 监视 money
      127.0.0.1:6379> watch money
      OK
      
      # 开启事务
      127.0.0.1:6379> multi
      OK
      127.0.0.1:6379(TX)> decrby money 20
      QUEUED
      127.0.0.1:6379(TX)> incrby out 20
      QUEUED
      # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
      127.0.0.1:6379(TX)> exec
      1) (integer) 80
      2) (integer) 20
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      测试多线程修改值,使用watch可以当做redis的乐观锁操作

      # 线程1 :
      # 对money进行监控
      127.0.0.1:6379> watch money
      OK
      # 开启事务
      127.0.0.1:6379> multi
      OK
      127.0.0.1:6379(TX)> decrby money 10
      QUEUED
      127.0.0.1:6379(TX)> incrby out 10
      QUEUED
      
      # 此时线程1的事务不执行,并且线程2对money进行了修改
      
      # 线程2 :
      127.0.0.1:6379> get money
      "80"
      127.0.0.1:6379> set money 1000
      OK
      
      # 此时线程1的事务进行执行,发现没有返回结果
      # 原因是watch监控到了线程2对money的操作,导致了执行失败
      127.0.0.1:6379(TX)> exec
      (nil)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

      事务执行失败后进行解锁操作(unwatch),再进行监控(watch)

      # 如果发现事务执行失败,先进行解锁
      127.0.0.1:6379> unwatch
      OK
      # 获取最新的值,重新进行监控
      127.0.0.1:6379> watch money
      OK
      127.0.0.1:6379> get money
      "1000"
      127.0.0.1:6379> multi
      OK
      127.0.0.1:6379(TX)> decrby money 10
      QUEUED
      127.0.0.1:6379(TX)> incrby out 10
      QUEUED
      # 对比监视的值是否发生了变化,如果没有发生变化,那么可以执行成功,变化了就执行失败
      127.0.0.1:6379(TX)> exec
      1) (integer) 990
      2) (integer) 30
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
  • 相关阅读:
    Node.js(3)-Buffer对象
    SVN 和 GIT 命令对比
    Kali 基础命令(二)
    stm32Cubemx USB虚拟串口
    巨量引擎AB落地页测试
    2021年06月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试
    基于FPGA的PID控制器开发与实现
    【Java 进阶篇】MySQL 数据控制语言(DCL):管理用户权限
    RT-ThreadXSTM32F407智能车培训报名啦!
    Node.js躬行记(25)——Web自动化测试
  • 原文地址:https://blog.csdn.net/weixin_47278183/article/details/128028068