redis学习笔记(详细)——初级篇

学习资料

狂神redis视频教程视频教程:https://www.bilibili.com/video/BV1S54y1R7SB

Redis - 概念和基础

概述

Redis:REmote DIctionary Server(远程字典服务器)

是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据 库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器

Redis与其他key-value缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份

安装

传送门

关键配置说明

redis.conf配置文件中daemonize守护线程,默认是NO。daemonize是用来指定redis是否要用守护线程的方式启动。

daemonize 设置yes或者no区别

  • daemonize:yes

    redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项 pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。

  • daemonize:no

    当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(putty,xshell等)都会导致redis进程退出

基础知识

redis性能测试

Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能。

redis 性能测试工具可选参数如下所示:

选项 描述 默认值
-h 指定服务器主机名 127.0.0.1
-p 指定服务器端口 6379
-s 指定服务器 socket
-c 指定并发连接数 50
-n 指定请求数 10000
-d 以字节的形式指定 SET/GET 值的数据大小 2
-k 1=keep alive 0=reconnect 1
-r SET/GET/INCR 使用随机 key, SADD 使用随机值
-P 通过管道传输 numreq 请求 1
-q 强制退出 redis。仅显示 query/sec 值
--csv 以 CSV 格式输出
-l 生成循环,永久执行测试
-t 仅运行以逗号分隔的测试命令列表。
-I Idle 模式。仅打开 N 个 idle 连接并等待。
# 测试:100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性能
cd  /usr/local/bin
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

redis常识

  • 默认端口是6379
  • 单进程
    • 单进程模型来处理客户端的请求。对读写等事件的响应 是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率
    • Epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本, 它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
  • 默认16个数据库,类似数组下表从零开始,初始默认使用零号库,可在配置文件配置
  • select命令切换数据库
  • dbsize查看当前数据库的key的数量
  • flushdb:清空当前库
  • flushall;通杀全部库
  • 统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上
  • Redis索引都是从零开始

默认16个数据库,类似数组下标从零开始,初始默认使用零号库

查看 redis.conf ,里面有默认的配置

# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16

Select命令切换数据库

127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]>
# 不同的库可以存不同的数据

Dbsize查看当前数据库的key的数量

127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]> DBSIZE
(integer) 0
127.0.0.1:6379[7]> select 0
OK
127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> keys * # 查看具体的key
1) "counter:__rand_int__"
2) "mylist"
3) "k1"
4) "myset:__rand_int__"
5) "key:__rand_int__"

Flushdb:清空当前库

Flushall:清空全部的库

127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> DBSIZE
(integer) 0

关于redis的单线程

注:6.x版本有多线程,一般用不到,单线程足够应对

我们首先要明白,Redis很快!官方表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就 顺理成章地采用单线程的方案了!

Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!

Redis为什么这么快?

redis 核心就是如果我的数据全都在内存里,我单线程的去操作就是效率最高的,为什么呢,因为 多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况有一个代价,就是上下文的切 换,对于一个内存的系统来说,它没有上下文的切换效率是最高的。redis 用单个CPU 绑定一块内存 的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处 理这个事。在内存的情况下,这个方案就是最佳方案。

因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us, 假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns * 1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不算你每次读一点数据的时间。

keys关键字

img

  • keys *

    查看所有的key

    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379> set name birdy
    OK
    127.0.0.1:6379> keys *
    1) "name"
    
  • exists key

判断某个key是否存在

127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
  • move key db

移动key到别的库

127.0.0.1:6379> set name birdy
OK
127.0.0.1:6379> get name
"birdy"
127.0.0.1:6379> move name 1  #自动到1库
(integer) 1
127.0.0.1:6379> keys *  #在本库查不到name
(empty array)
127.0.0.1:6379> select 1 #选择1库
OK
127.0.0.1:6379[1]> keys * #查询到name
1) "name"
  • del key

    删除key

    127.0.0.1:6379[1]> del name
    (integer) 1
    127.0.0.1:6379[1]> keys *
    (empty array)
    
  • expire key

    为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删 除。

    • ttl key : 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期
    127.0.0.1:6379> set name birdy
    OK
    127.0.0.1:6379> EXPIRE name 10
    (integer) 1
    127.0.0.1:6379> ttl name
    (integer) 4
    127.0.0.1:6379> ttl name
    (integer) 1
    127.0.0.1:6379> ttl name
    (integer) -2
    127.0.0.1:6379> keys *
    (empty list or set)
    
  • type key

    查看你的key是什么类型

    127.0.0.1:6379> set name birdy
    OK
    127.0.0.1:6379> get name
    "birdy"
    127.0.0.1:6379> type name
    string
    

五大数据类型

String

  • string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
  • string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
  • string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

常用命令

- set/get/del/append/strlen 
- Incr/decr/incrby/decrby,一定要是数字才能进行加减
- getrange/setrange
- setex(set with expire)键秒值 / setnx(set if not exist)
- mset/mget/msetnx
- getset(先get再set)

img

127.0.0.1:6379> set key1 value1 # 设置值
OK
127.0.0.1:6379> get key1 # 获得key
"value1"
127.0.0.1:6379> del key1 # 删除key
(integer) 1
127.0.0.1:6379> keys * # 查看全部的key
(empty list or set)
127.0.0.1:6379> exists key1 # 确保 key1 不存在
(integer) 0
127.0.0.1:6379> append key1 "hello" # 对不存在的 key进行APPEND,等同于SET key1 "hello"
(integer) 5 # 字符长度
127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND
(integer) 10 # 长度从 5 个字符增加到 10 个字符
127.0.0.1:6379> get key1
"hello-2333"
127.0.0.1:6379> STRLEN key1 # # 获取字符串的长度
(integer) 10

incr、decr 一定要是数字才能进行加减,+1 和 -1。

incrby、decrby 将 key 中储存的数字加上或减去指定的数量。

127.0.0.1:6379> set views 0 # 设置浏览量为0
OK
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 1
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 2
127.0.0.1:6379> decr views # 浏览 - 1
(integer) 1

127.0.0.1:6379> incrby views 10 # +10
(integer) 11
127.0.0.1:6379> decrby views 10 # -10
(integer) 1

range [范围]

getrange 获取指定区间范围内的值,类似between...and,从0到-1表示全部

127.0.0.1:6379> set key2 abcd123456 # 设置key2的值
OK
127.0.0.1:6379> getrange key2 0 -1 # 获得全部的值
"abcd123456"
127.0.0.1:6379> getrange key2 0 2 # 截取部分字符串
"abc"

setrange 设置指定区间范围内的值,格式是setrange key值 具体值

127.0.0.1:6379> get key2
"abcd123456"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换值
(integer) 10
127.0.0.1:6379> get key2
"axxd123456"

setex(set with expire)设置过期时间

setnx(set if not exist)如果key存在则不覆盖值,还是原来的值(分布式中常用)

127.0.0.1:6379> setex key3 60 expire # 设置过期时间
OK
127.0.0.1:6379> ttl key3 # 查看剩余的时间
(integer) 55

127.0.0.1:6379> setnx mykey "redis" # 如果不存在就设置,成功返回1
(integer) 1
127.0.0.1:6379> setnx mykey "mongodb" # 如果值存在则不覆盖值,返回0
(integer) 0
127.0.0.1:6379> get mykey
"redis"

mset:同时设置一个或多个 key-value 对。

mget:返回所有(一个或多个) key 的值。 如果给定的 key 里面,有某个 key 不存在,则此 key 返回特殊值nil

msetnx:当所有 key 都成功设置,返回 1 。如果至少有一个 key 已经存在,那么返回 0 。相当于原子性操作,要么都成功,要么都不成功。

127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12
OK
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k10"

127.0.0.1:6379> mget k10 k11 k12 k13
1) "v10"
2) "v11"
3) "v12"
4) (nil)

127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作!
(integer) 0
127.0.0.1:6379> get key15
(nil)

存储对象

set user:1 value(json数据)

127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

getset:先get再set

127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis # 返回旧值 mongodb
"mongodb"
127.0.0.1:6379> get db
"redis"

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。

常规计数:微博数,粉丝数等。

List

可重复单值多value,list存储类似与栈

常用命令

#push:插入 l(left):栈顶,插入与读取顺序相反 r(right):栈尾,顺序相同 	lrange:查
lpush/rpush/lrange
LPUSH list1 1 2 3 4 5 
LRANGE list1 0 -1	#0和-1是索引值,0~-1表示查全部 结果为 5 4 3 2 1
LPUSH list2 1 2 3 4 5 
LRANGE list2 0 -1 #1 2 3 4 5

#弹出,l:栈顶,后进先出 r:栈尾,先进先出
lpop/rpop
lpop list1 #4 3 2 1
rpop list1 #4 3 2

#按照索引下标获得元素(从上到下)
lindex
lindex list2 2 #3

#获取list长度
llen
llen list2 #5

# 删N个value
lrem key N value
rpush list3 1 1 2 2 2 2
lrem list3 3 2 #删除3个2 结果:1 1 2

#截取指定范围的值后再赋值给key
ltrim key start end
rpush list4 1 2 3 4 5
ltrim list4 0 2
lrange list4 0 -1 #1 2 3

#移除源列表的最后一个元素添加到目的列表
rpoplpush 源列表 目的列表

#通过索引改变列表值
lset key index value

#在目标值前/后插入value
linsert key before/after 目标值 value

img

Lpush:将一个或多个值插入到列表头部。(LeftPush左)

Rpush:将一个或多个值插入到列表尾部。(RightPush右)

lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。

其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。

使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

127.0.0.1:6379> LPUSH list "one"
(integer) 1
127.0.0.1:6379> LPUSH list "two"
(integer) 2
127.0.0.1:6379> RPUSH list "right"
(integer) 3

127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> Lrange list 0 1
1) "two"
2) "one"

lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。

rpop 移除列表的最后一个元素,返回值为移除的元素。

127.0.0.1:6379> Lpop list
"two"
127.0.0.1:6379> Rpop list
"right"
127.0.0.1:6379> Lrange list 0 -1
1) "one"

Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)

127.0.0.1:6379> Lindex list 1
(nil)
127.0.0.1:6379> Lindex list 0
"one"
127.0.0.1:6379> Lindex list -1
"one"

llen 用于返回列表的长度。

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> Lpush list "one"
(integer) 1
127.0.0.1:6379> Lpush list "two"
(integer) 2
127.0.0.1:6379> Lpush list "three"
(integer) 3
127.0.0.1:6379> Llen list # 返回列表的长度
(integer) 3

lrem (lrem key count element)根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。

如果有多个一样的lement,则删除列表最前面的的

127.0.0.1:6379> lrem list 1 "two"
(integer) 1
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "one"

Ltrim key 对一个列表进行修剪(trim),只保留指定列表中区间内的元素,不在指定区间之内的元素都将被删除。

127.0.0.1:6379> RPUSH mylist "hello" "hello" "hello2" "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello2"

rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。

127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"

lset key index value ,将列表 key 下标为 index 的元素的值设置为 value 。

127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key

127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 "new" # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
(error) ERR index out of range

linsert key before/after pivot value,用于在列表的元素前或者后插入元素。

将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。

如果pivot有多个,则插入最前面的那个

127.0.0.1:6379> RPUSH mylist "Hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "world"
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "Hello"
2) "world"

127.0.0.1:6379> LINSERT mylist BEFORE "world" "There"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "Hello"
2) "There"
3) "world"

性能总结:

  • 它是一个字符串链表,left,right 都可以插入添加
  • 如果键不存在,创建新的链表
  • 如果键已存在,新增内容
  • 如果值全移除,对应的键也就消失了
  • 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。

list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。

set

无重复单值多value

#添加/查询/是否存在
sadd/smembers/sismember key member

#获取集合里面的元素个数
scard key

#删除集合中元素
srem key value 

#随机返回集合中1个或多个随机数
srandmember key [count] 

#随机出栈
spop key 

#将key1里的某个值赋给key2
smove key1 key2 在key1里某个值
#数学集合类
差集:sdiff 交集:sinter 并集:sunion
sadd set1 1 2 3 4 5
sadd set2 a b c 1 2
sdiff set1 set2 #3 4 5
sinter set1 set2 #1 2
sunion set1 set2 #1 2 3 4 5 a b c 真实情况下这些值是没有顺序的

img

#sadd 将一个或多个成员元素加入到集合中,不能重复
#smembers 返回集合中的所有的成员。
#sismember 判断成员元素是否是集合的成员
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "birdy"
(integer) 1
127.0.0.1:6379> sadd myset "birdy" # 重复值不插入 返回0
(integer) 0
127.0.0.1:6379> SMEMBERS myset #查看集合中所有成员
1) "birdy"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello" #是否是此集合的成员 是反正1
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0

# scard,获取集合里面的元素个数
127.0.0.1:6379> scard myset
(integer) 2
#srem key value 用于移除集合中的一个或多个成员元素
127.0.0.1:6379> srem myset "birdy"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
#srandmember key 用于返回集合中随机元素。后面加上数字,则随机返回对应数量的成员,默认一个
127.0.0.1:6379> SMEMBERS myset
1) "birdy"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "birdy"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "birdy"
2) "hello"
#spop key [count] 用于移除指定 key 集合的随机元素,不填则默认一个。
127.0.0.1:6379> SMEMBERS myset
1) "birdy"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset 2
1) "birdy"
2) "hello"
#smove SOURCE DESTINATION MEMBER, 将指定成员 member 元素从 source 集合移动到 destination 集合
127.0.0.1:6379> sadd myset "hello" #myset 添加元素
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "birdy"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2" #myset2 添加元素
(integer) 1
127.0.0.1:6379> smove myset myset2 "birdy"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "birdy"
2) "set2"

数字集合类

  • 差集: sdiff
  • 交集: sinter
  • 并集: sunion
127.0.0.1:6379> sadd key1 "a" # key1
(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" # key2
(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) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

hash

KV模式不变,但V是一个键值对

#添加一个/查询一个/添加多个/查询多个/删除一个或多个
hset/hget/hmset/hmget/hgetall/hdel key field value
hset user id 1
hmset user name birdy age 18
hmget user id	#1
hmget user #1 birdy 18
hgetall user #id 1 name birdy age 18

#获取哈希表key的字段数量
hlen key
hlen user #3

#哈希表key是否存在field属性
hexists key field

#获取哈希表key的所有属性/值
hkeys/hvals key

#为哈希表key执行字段的整数值/浮点值增加increment
hincrby/hincrbyfloat key filed increment

#添加并判断是否已存在,若存在则添加失败
hsetnx key field value

img

#hset、hget 命令用于为哈希表中的字段赋值
#hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段
	#Redis 4.0.0开始弃用HMSET,请使用HSET
#hgetall 用于返回哈希表中,所有的字段和值。
#hdel 用于删除哈希表 key 中的一个或多个指定字段
127.0.0.1:6379>  hset myhash field1 "birdy"
(integer) 1
127.0.0.1:6379> hget myhash field1
"birdy"

127.0.0.1:6379> HSET myhash field1 "Hello" field2 "World"
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"

127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
#hlen 获取哈希表中字段的数量
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HSET myhash field1 "Hello" field2 "World"
OK
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 获取哈希表中的所有域(field)
#hvals 返回哈希表所有域(field)的值
127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello
#hincrby 为哈希表中的字段值加上指定增量值
127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
#hsetnx 为哈希表中不存在的的字段赋值 ,存在则不赋值
127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1 # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0 # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。

zset

在set基础上,加一个score值。 之前set是k1 v1 v2 v3, 现在zset是k1 score1 v1 score2 v2

#向有序集合中添加或更新成员/查询成员
zadd key score1 member/zrange key 索引start 索引end [withscores]
ZADD score 60 d 70 c 80 b 90 a
ZCARD score 0 -1 #d c b a 只有member
ZCARD score 0 -1 withscores #60 d 70 c 80 b 90 a 

#返回满足分数区间的值
zrangebyscore key min max
ZRANGEBYSCORE score 60 80 #d c b
选项:
	withscores 是否带分数返回
	( 不包含
	limit 开始索引 结束索引
#删除有序结合成员
zrem key 某分数对应下member
ZREM score d
ZRANGE score 0 -1  #c b a
#计算有序区间的成员数
zcard/zcount key min max
#获得索引值
zrank key member
zrank score a #2,a的索引为2
#获得成员对应分数
zscore key member
zscore score a #90
#逆序获得下标值
zrevrank key member
zrevrank score a #0

#逆序查询成员
zrevrange key start end 
#查询区间成员并逆序显示
zrevrangebyscore key min max

img

#zadd 将一个或多个成员元素及其分数值加入到有序集当中。
#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"
#zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大) 次序排列。

#ZREVRANGE 从大到小
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1

# Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "birdy"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
1) "birdy"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
1) "xiaohong"
2) "5000"
3) "xiaoming"
4) "2500"
5) "birdy"
6) "500"
# 显示工资 <=2500的所有成员
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES 
1) "birdy"
2) "500"
3) "xiaoming"
4) "2500"

三种特殊数据类型

GEO地理位置

Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。geo的数据类型为 zset。

GEO 的数据结构总共有六个常用命令:

geoadd、geopos、geodist、georadius、 georadiusbymember、gethash

官方文档:https://www.redis.net.cn/order/3685.html

1、geoadd

语法:geoadd key longitude latitude member ...

将给定的空间元素(经度、纬度、名字)添加到指定的键里面。 这些数据会以有序集的形式被储存在键里面,从而使得georadius和georadiusbymember这样的 命令可以在之后通过位置查询取得这些元素。 geoadd命令以标准的x,y格式接受参数,所以用户必须先输入经度,然后再输入纬度。 geoadd能够记录的坐标是有限的:非常接近两极的区域无法被索引。 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间 当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。

测试:百度搜索经纬度查询,模拟真实数据

127.0.0.1:6379> geoadd china:city 116.23 40.22 北京
(integer) 1
127.0.0.1:6379> geoadd china:city 121.48 31.40 上海 113.88 22.55 深圳 120.21 30.20 杭州
(integer) 3
127.0.0.1:6379> geoadd china:city 106.54 29.40 重庆 108.93 34.23 西安 114.02 30.58 武汉
(integer) 3

2、geopos

语法:geopos key member [member...]

从key里返回所有给定位置元素的位置(经度和纬度)

127.0.0.1:6379> geopos china:city 北京
1) 1) "116.23000055551528931"
2) "40.2200010338739844"
127.0.0.1:6379> geopos china:city 上海 重庆
1) 1) "121.48000091314315796"
2) "31.40000025319353938"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
127.0.0.1:6379> geopos china:city 新疆
1) (nil)

3、geodist

语法:geodist key member1 member2 [unit]

返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值

指定单位的参数unit必须是以下单位的其中一个:

  • m表示单位为米
  • km表示单位为千米
  • mi表示单位为英里
  • ft表示单位为英尺

如果用户没有显式地指定单位参数,那么geodist默认使用 米 作为单位。

geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误差。

127.0.0.1:6379> geodist china:city 北京 上海
"1088785.4302"
127.0.0.1:6379> geodist china:city 北京 上海 km
"1088.7854"
127.0.0.1:6379> geodist china:city 重庆 北京 km
"1491.6716"

4、georadius

语法:

georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count]

以给定的经纬度为中心, 找出某一半径内的元素

重新连接 redis-cli,增加参数 --raw ,可以强制输出中文,不然会乱码

127.0.0.1:6379> georadius china:city 100 30 1000 km #乱码
1) "\xe9\x87\x8d\xe5\xba\x86"
2) "\xe8\xa5\xbf\xe5\xae\x89"
127.0.0.1:6379> exit
[root@localhost bin]#  redis-cli --raw -p 6379
# 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379> georadius china:city 100 30 1000 km
重庆
西安

withdist 返回位置名称和中心距离

127.0.0.1:6379> georadius china:city 100 30 1000 km withdist
重庆
635.2850
西安
963.3171

withcoord 返回位置名称、经纬度

127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord
重庆
106.54000014066696167
29.39999880018641676
西安
108.92999857664108276
34.23000121926852302

withdist withcoord 返回位置名称、距离、经纬度,count 限定寻找个数

127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count 1
重庆
635.2850
106.54000014066696167
29.39999880018641676
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count 2
重庆
635.2850
106.54000014066696167
29.39999880018641676
西安
963.3171
108.92999857664108276
34.23000121926852302

5、georadiusbymember

语法:

georadiusbymember key member radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count]

找出位于指定范围内的元素,中心点是由给定的位置元素决定

127.0.0.1:6379> GEORADIUSBYMEMBER china:city 北京 1000 km
北京
西安
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 400 km
杭州
上海

6、geohash

语法:geohash key member [member...]

Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似表示距离越近。

127.0.0.1:6379> geohash china:city 北京 重庆
wx4sucu47r0
wm5z22h53v0
127.0.0.1:6379> geohash china:city 北京 上海
wx4sucu47r0
wtw6sk5n300

zrem

GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除。

127.0.0.1:6379> geoadd china:city 116.23 40.22 beijing
1
127.0.0.1:6379> zrange china:city 0 -1 # 查看全部的元素
重庆
西安
深圳
武汉
杭州
上海
beijing
北京
127.0.0.1:6379> zrem china:city beijing # 移除元素
1
127.0.0.1:6379> zrem china:city 北京 # 移除元素
1
127.0.0.1:6379> zrange china:city 0 -1
重庆
西安
深圳
武汉
杭州
上海

HyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

HyperLogLog则是一种算法,它提供了不精确的去重计数方案。

举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素的数量)为5。

基数估计就是在误差可接受的范围内,快速计算基数。

基本命令

序号 命令及描述
1 PFADD key element [element ...](opens new window) 添加指定元素到 HyperLogLog 中。
2 PFCOUNT key [key ...](opens new window) 返回给定 HyperLogLog 的基数估算值。
3 PFMERGE destkey sourcekey [sourcekey ...](opens new window) 将多个 HyperLogLog 合并为一个 HyperLogLog,并集计算
127.0.0.1:6379> PFADD mykey a b c d e f g h i j
1
127.0.0.1:6379> PFCOUNT mykey
10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
1
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
15

BitMap

在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111...........................,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。

BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。

1、setbit 设置操作

SETBIT key offset value : 设置 key 的第 offset 位为value (1或0)

使用 bitmap 来记录上述事例中一周的打卡记录如下所示:

周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)

127.0.0.1:6379> setbit sign 0 1
0
127.0.0.1:6379> setbit sign 1 0
0
127.0.0.1:6379> setbit sign 2 0
0
127.0.0.1:6379> setbit sign 3 1
0
127.0.0.1:6379> setbit sign 4 1
0
127.0.0.1:6379> setbit sign 5 0
0
127.0.0.1:6379> setbit sign 6 0
0

2、getbit 获取操作

GETBIT key offset 获取offset设置的值,未设置过默认返回0

127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡
1
127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡
0

3、bitcount 统计操作

bitcount key [start, end] 统计 key 上位为1的个数

统计这周打卡的记录,可以看到只有3天是打卡的状态:

127.0.0.1:6379> bitcount sign
3

事务

传送门:官方文档

简单理解,可以认为redis事务是一系列redis命令的集合,Redis事务允许在一次单独的步骤中执行一组命令,并且可以保证如下两个重要事项:

  • Redis会将一个事务中的所有命令序列化,然后按顺序执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。这样便能保证Redis将这些命令作为一个单独的隔离操作执行。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

事务的性质ACID

一般来说,事务有四个性质称为ACID,分别是原子性,一致性,隔离性和持久性。

  • 原子性Atomicity:redis事务保证事务中的命令要么全部执行要不全部不执行。有些文章认为redis事务对于执行错误不回滚违背了原子性,是偏颇的。
  • 一致性Consistency:redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结。
  • 隔离性Isolation:redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式,可以保证命令执行过程中不会被其他客户端命令打断。
  • 持久性Durability:redis事务是不保证持久性的,这是因为redis持久化策略中不管是RDB还是AOF都是异步执行的,不保证持久性是出于对性能的考虑。

事务的使用

相关命令

1-MULTI

用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原子化地执行这个命令序列。

2-EXEC

在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。

  • EXEC命令的返回值是一个数组,其中的每个元素都分别是事务中的每个命令的返回值,返回值的顺序和命令的发出顺序是相同的。
  • 当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。
  • 当使用WATCH命令时,如果事务执行中止,那么EXEC命令就会返回一个Null值。

3-DISCARD

清除所有先前在一个事务中放入队列的命令,结束事务状态,然后恢复正常的连接状态。

如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。

4-WATCH

当某个事务需要按条件执行时,就要使用这个命令将给定的一个或多个键设置为受监控的。

如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断,需要重新获取最新的数据,类似git拉取代码

WATCH key [key ...]

5-UNWATCH

清除所有先前为一个事务监控的键。

如果你调用了EXEC或DISCARD命令,那么就不需要手动调用UNWATCH命令。

UNWATCH

使用MULTI命令便可以进入一个Redis事务。这个命令的返回值总是OK。此时,用户可以发出多个Redis命令。Redis会将这些命令放入队列,而不是执行这些命令。一旦调用EXEC命令,那么Redis就会执行事务中的所有命令。

相反,调用DISCARD命令将会清除事务队列,然后退出事务。

示例:

image-20210408101936847

事务内部的错误

在一个事务的运行期间,可能会遇到两种类型的命令错误:

  1. 入队错误

一个命令可能会在被放入队列时失败。因此,事务有可能在调用EXEC命令之前就发生错误,例如,这个命令可能会有语法错误(参数的数量错误、命令名称错误,等等),或者可能会有某些临界条件(例如:如果使用maxmemory指令,为Redis服务器配置内存限制,那么就可能会有内存溢出条件)。

类似Java编译异常。Redis会拒绝执行这个事务,在运行EXEC命令之后,便会返回一个错误消息。最后,Redis会自动丢弃这个事务。类似Java编译异常

  1. 执行错误

在调用EXEC命令之后发生的事务错误。Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其他的命令也将会继续执行。

类似Java运行时异常

  1. 放弃事务
image-20210408101955501

watch监控

WATCH 使得 EXEC 命令需要有条件地执行: 事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足,事务就不会被执行。

Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,整个事务队列都不会执行

传送门:乐观锁和悲观锁

WATCH命令可以被调用多次。简单说来,所有的WATCH命令都会在被调用之时立刻对相应的键进行监控,直到EXEC命令被调用之时为止。你可以在单条的WATCH命令之中,使用任意数量的键作为命令参数。

  • 无加塞,先监控再开启事务,
  • 有加塞,监控了key,如果key被修改了,后面一个事务的执行失效

一旦执行了exec,之前加的监控锁都会被取消掉了

Java使用redis

Jedis

Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能 写成漂亮的代码

测试ping

前提打开了redis服务。

1、新建一个普通的Maven项目

2、导入redis的依赖!

<dependencies>
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.75</version>
    </dependency>
</dependencies>

3、编写测试代码

public class Ping {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //查看服务是否运行
        System.out.println(jedis.ping());	//PONG
    }
}

key命令

public class TestKey {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        System.out.println("清空数据:" + jedis.flushDB());
        System.out.println("判断某个键是否存在:" + jedis.exists("username"));
        System.out.println("新增<'username','birdy'>的键值对:" + jedis.set("username", "birdy"));
        System.out.println("新增<'password','password'>的键值对:" + jedis.set("password", "password"));

        System.out.print("系统中所有的键如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);

        System.out.println("删除键password:" + jedis.del("password"));
        System.out.println("判断键password是否存在:" + jedis.exists("password"));
        System.out.println("查看键username所存储的值的类型:" + jedis.type("username"));
        System.out.println("随机返回key空间的一个:" + jedis.randomKey());
        System.out.println("重命名key:" + jedis.rename("username", "name"));
        System.out.println("取出改后的name:" + jedis.get("name"));
        System.out.println("按索引查询:" + jedis.select(0));
        System.out.println("删除当前选择数据库中的所有key:" + jedis.flushDB());
        System.out.println("返回当前数据库中key的数目:" + jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:" + jedis.flushAll());
    }
    
}
/*
清空数据:OK
判断某个键是否存在:false
新增<'username','birdy'>的键值对:OK
新增<'password','password'>的键值对:OK
系统中所有的键如下:[password, username]
删除键password:1
判断键password是否存在:false
查看键username所存储的值的类型:string
随机返回key空间的一个:username
重命名key:OK
取出改后的name:birdy
按索引查询:OK
删除当前选择数据库中的所有key:OK
返回当前数据库中key的数目:0
删除所有数据库中的所有key:OK

* */

String相关操作


public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        jedis.flushDB();
        System.out.println("===========增加数据===========");
        System.out.println(jedis.set("key1", "value1"));
        System.out.println(jedis.set("key2", "value2"));
        System.out.println(jedis.set("key3", "value3"));

        System.out.println("删除键key2:" + jedis.del("key2"));
        System.out.println("获取键key2:" + jedis.get("key2"));
        System.out.println("修改key1:" + jedis.set("key1", "value1Changed"));
        System.out.println("获取key1的值:" + jedis.get("key1"));
        System.out.println("在key3后面加入值:" + jedis.append("key3", "End"));
        System.out.println("key3的值:" + jedis.get("key3"));
        System.out.println("增加多个键值对:" + jedis.mset("key01", "value01", "key02", "value02", "key03", "value03"));
        System.out.println("获取多个键值对:" + jedis.mget("key01", "key02", "key03"));
        System.out.println("获取多个键值对:" + jedis.mget("key01", "key02", "key03", "key04"));
        System.out.println("删除多个键值对:" + jedis.del("key01", "key02"));
        System.out.println("获取多个键值对:" + jedis.mget("key01", "key02", "key03"));

        jedis.flushDB();

        System.out.println("===========新增键值对防止覆盖原先值==============");
        System.out.println(jedis.setnx("key1", "value1"));
        System.out.println(jedis.setnx("key2", "value2"));
        System.out.println(jedis.setnx("key2", "value2-new"));
        System.out.println(jedis.get("key1"));
        System.out.println(jedis.get("key2"));
        System.out.println("===========新增键值对并设置有效时间=============");
        System.out.println(jedis.setex("key3", 2, "value3"));
        System.out.println(jedis.get("key3"));
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(jedis.get("key3"));
        System.out.println("===========获取原值,更新为新值==========");
        System.out.println(jedis.getSet("key2", "key2GetSet"));
        System.out.println(jedis.get("key2"));
        System.out.println("获得key2的值的字串:" + jedis.getrange("key2", 2,4));

    }
}
/*
===========增加数据===========
OK
OK
OK
删除键key2:1
获取键key2:null
修改key1:OK
获取key1的值:value1Changed
在key3后面加入值:9
key3的值:value3End
增加多个键值对:OK
获取多个键值对:[value01, value02, value03]
获取多个键值对:[value01, value02, value03, null]
删除多个键值对:2
获取多个键值对:[null, null, value03]
===========新增键值对防止覆盖原先值==============
1
1
0
value1
value2
===========新增键值对并设置有效时间=============
OK
value3
* */

List相关操作

public class TestList {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("===========添加一个list===========");
        jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
        jedis.lpush("collections", "HashSet");
        jedis.lpush("collections", "TreeSet");
        jedis.lpush("collections", "TreeMap");
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
        System.out.println("collections区间0-3的元素:" + jedis.lrange("collections", 0, 3));
        System.out.println("===============================");
        // 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
        System.out.println("删除指定元素个数:" + jedis.lrem("collections", 2,
                "HashMap"));
        System.out.println("collections的内容:" + jedis.lrange("collections",
                0, -1));
        System.out.println("删除下表0-3区间之外的元素:" + jedis.ltrim("collections", 0, 3));
        System.out.println("collections的内容:" + jedis.lrange("collections",
                0, -1));
        System.out.println("collections列表出栈(左端):" + jedis.lpop("collections"));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("collections添加元素,从列表右端,与lpush相对应:" + jedis.rpush("collections", "EnumMap"));
        System.out.println("collections的内容:" + jedis.lrange("collections",
                0, -1));
        System.out.println("collections列表出栈(右端):" + jedis.rpop("collections"));
        System.out.println("collections的内容:" + jedis.lrange("collections",
                0, -1));
        System.out.println("修改collections指定下标1的内容:" + jedis.lset("collections", 1, "LinkedArrayList"));
        System.out.println("collections的内容:" + jedis.lrange("collections",
                0, -1));
        System.out.println("===============================");
        System.out.println("collections的长度:" + jedis.llen("collections"));
        System.out.println("获取collections下标为2的元素:" + jedis.lindex("collections", 2));
        System.out.println("===============================");
        jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
        System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0,
                -1));
        System.out.println(jedis.sort("sortedList"));
        System.out.println("sortedList排序后:" + jedis.lrange("sortedList", 0, -1));

    }
}
/*
===========添加一个list===========
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, HashMap, Stack, Vector, ArrayList]
collections区间0-3的元素:[TreeMap, TreeSet, HashSet, LinkedHashMap]
===============================
删除指定元素个数:1
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
删除下表0-3区间之外的元素:OK
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap]
collections列表出栈(左端):TreeMap
collections的内容:[TreeSet, HashSet, LinkedHashMap]
collections添加元素,从列表右端,与lpush相对应:4
collections的内容:[TreeSet, HashSet, LinkedHashMap, EnumMap]
collections列表出栈(右端):EnumMap
collections的内容:[TreeSet, HashSet, LinkedHashMap]
修改collections指定下标1的内容:OK
collections的内容:[TreeSet, LinkedArrayList, LinkedHashMap]
===============================
collections的长度:3
获取collections下标为2的元素:LinkedHashMap
===============================
sortedList排序前:[4, 7, 0, 2, 6, 3]
[0, 2, 3, 4, 6, 7]
sortedList排序后:[4, 7, 0, 2, 6, 3]

* */

Set相关操作

public class TestSet {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("============向集合中添加元素(不重复)============");
        System.out.println(jedis.sadd("eleSet", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("删除一个元素e0:" + jedis.srem("eleSet", "e0"));

        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("删除两个元素e7和e6:" + jedis.srem("eleSet", "e7", "e6"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet"));
        System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("eleSet中包含元素的个数:" + jedis.scard("eleSet"));
        System.out.println("e3是否在eleSet中:" + jedis.sismember("eleSet", "e3"));
        System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e1"));
        System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e5"));
        System.out.println("=================================");
        System.out.println(jedis.sadd("eleSet1", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
        System.out.println(jedis.sadd("eleSet2","e1", "e2", "e4", "e3", "e0", "e8"));
        System.out.println("将eleSet1中删除e1并存入eleSet3中:" + jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
        System.out.println("将eleSet1中删除e2并存入eleSet3中:" + jedis.smove("eleSet1", "eleSet3", "e2"));
        System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3"));
        System.out.println("============集合运算=================");
        System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet2中的元素:" + jedis.smembers("eleSet2"));
        System.out.println("eleSet1和eleSet2的交集:" + jedis.sinter("eleSet1", "eleSet2"));
        System.out.println("eleSet1和eleSet2的并集:" + jedis.sunion("eleSet1", "eleSet2"));
        System.out.println("eleSet1和eleSet2的差集:" + jedis.sdiff("eleSet1", "eleSet2"));//eleSet1中有,eleSet2中没有
        jedis.sinterstore("eleSet4", "eleSet1", "eleSet2");//求交集并将交集保存到dstkey的集合
        System.out.println("eleSet4中的元素:" + jedis.smembers("eleSet4"));
    }
}

/*
============向集合中添加元素(不重复)============
8
1
0
eleSet的所有元素为:[e4, e3, e0, e7, e1, e2, e8, e5, e6]
删除一个元素e0:1
eleSet的所有元素为:[e7, e1, e2, e8, e5, e3, e4, e6]
删除两个元素e7和e6:2
eleSet的所有元素为:[e2, e8, e5, e3, e4, e1]
随机的移除集合中的一个元素:e8
随机的移除集合中的一个元素:e5
eleSet的所有元素为:[e3, e4, e1, e2]
eleSet中包含元素的个数:4
e3是否在eleSet中:true
e1是否在eleSet中:true
e1是否在eleSet中:false
=================================
8
6
将eleSet1中删除e1并存入eleSet3中:1
将eleSet1中删除e2并存入eleSet3中:1
eleSet1中的元素:[e5, e4, e3, e0, e7, e8]
eleSet3中的元素:[e1, e2]
============集合运算=================
eleSet1中的元素:[e5, e4, e3, e0, e7, e8]
eleSet2中的元素:[e1, e2, e4, e3, e0, e8]
eleSet1和eleSet2的交集:[e4, e3, e0, e8]
eleSet1和eleSet2的并集:[e1, e2, e8, e5, e3, e4, e0, e7]
eleSet1和eleSet2的差集:[e5, e7]
eleSet4中的元素:[e0, e3, e4, e8]

Process finished with exit code 0

* */

Hash相关操作

public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        map.put("key4", "value4");
        //添加名称为hash(key)的hash元素
        jedis.hmset("hash", map);
        //向名称为hash的hash中添加key为key5,value为value5元素
        jedis.hset("hash", "key5", "value5");
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));//return Map<String,String>

        System.out.println("散列hash的所有键为:" + jedis.hkeys("hash"));//returnSet<String>
        System.out.println("散列hash的所有值为:" + jedis.hvals("hash"));//returnList<String>
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:" + jedis.hincrBy("hash", "key6", 6));
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:" + jedis.hincrBy("hash", "key6", 3));
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("删除一个或者多个键值对:" + jedis.hdel("hash", "key2"));
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("散列hash中键值对的个数:" + jedis.hlen("hash"));
        System.out.println("判断hash中是否存在key2:" + jedis.hexists("hash", "key2"));
        System.out.println("判断hash中是否存在key3:" + jedis.hexists("hash", "key3"));
        System.out.println("获取hash中的值:" + jedis.hmget("hash", "key3"));
        System.out.println("获取hash中的值:" + jedis.hmget("hash", "key3", "key4"));

    }
}
/*
散列hash的所有键值对为:{key1=value1, key2=value2, key5=value5, key3=value3, key4=value4}
散列hash的所有键为:[key1, key2, key5, key3, key4]
散列hash的所有值为:[value1, value2, value4, value3, value5]
将key6保存的值加上一个整数,如果key6不存在则添加key6:6
散列hash的所有键值对为:{key1=value1, key2=value2, key5=value5, key6=6, key3=value3, key4=value4}
将key6保存的值加上一个整数,如果key6不存在则添加key6:9
散列hash的所有键值对为:{key1=value1, key2=value2, key5=value5, key6=9, key3=value3, key4=value4}
删除一个或者多个键值对:1
散列hash的所有键值对为:{key1=value1, key5=value5, key6=9, key3=value3, key4=value4}
散列hash中键值对的个数:5
判断hash中是否存在key2:false
判断hash中是否存在key3:true
获取hash中的值:[value3]
获取hash中的值:[value3, value4]

Process finished with exit code 0

* */

事务

public class TestMulti {
    public static void main(String[] args) {
        //创建客户端连接服务端,redis服务端需要被开启
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "java");
        //开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        try {
            //向redis存入一条数据
            multi.set("json", result);
            //再存入一条数据
            multi.set("json2", result);
            //这里引发了异常,用0作为被除数
            int i = 100 / 0;
            //如果没有引发异常,执行进入队列的命令
            multi.exec();
        } catch (Exception e) {
            e.printStackTrace();
            //如果出现异常,回滚
            multi.discard();
        } finally {
            System.out.println(jedis.get("json"));
            System.out.println(jedis.get("json2"));
            //最终关闭客户端
            jedis.close();
        }
    }
}

springboot整合redis

redis——springboot整合redis - 至安 - 博客园 (cnblogs.com)

posted @ 2021-11-11 09:44  至安  阅读(247)  评论(0编辑  收藏  举报