Redis学习笔记(一)


Redis笔记

【狂神说】

一、NoSql概述

1. 为什么使用NoSql

1、单机Mysql时代

在这里插入图片描述

90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题

  1. 数据量增加到一定程度,单机数据库就放不下了
  2. 数据的索引(B+ Tree),一个机器内存也存放不下
  3. 访问量变大后(读写混合),一台服务器承受不住。

只要出现三种情况之一, 就必须要升级了!

2、Memcached(缓存) + Mysql + 垂直拆分(MySql库读写功能分离)

网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!

在这里插入图片描述

优化过程经历了以下几个过程:

  1. 不是一开始就有缓存的

  2. 优化数据库的数据结构和索引 (难度大)

  3. 文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了

  4. MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。

3、分库分表 + Mysql集群 + 水平拆分 (每个MySql库都兼具读和写的功能,但存储的数据都是部分数据)

技术和业务在发展的同时, 对人的要求也越高

本质: 数据库( 读 + 写)

  • 早年 MyISAM : 表锁
  • 转 Innodb : 行锁
  • 慢慢 : 分库分表

image-20211026161036003

4、如今最近的年代

如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)已无法满足大量数据要求。NoSql数据库就能轻松解决这些问题。

5、目前一个基本的互联网项目

  • 很多服务器
  • 很多数据库

image-20211026162127215

6、所以, 为什么要用NoSQL ?

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!

这时候我们就需要使用NoSQL数据库的,NoSql可以很好的处理以上的情况!

2. 什么是NoSql

  • 非关系型数据库

NoSQL = Not Only SQL(不仅仅是SQL)(好多人一开始包括我也以为是"不要SQL", hahaha)

Not Only Structured Query Language

关系型数据库:列 + 行,同一个表下数据的结构是一样的。

非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。

NoSql 泛指 非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。

3. NoSql特点

解耦 !

  1. 方便扩展(数据之间没有关系,很好扩展!)

  2. 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)

  3. 数据类型是多样型的!(不需要事先设计数据库,随取随用

  4. 传统的 RDBMS 和 NoSQL

    传统的 RDBMS(关系型数据库)

    • 结构化组织
    • SQL
    • 数据和关系都存在单独的表中 row - col
    • 操作,数据定义语言
    • 严格的一致性
    • 基础的事务
    • ...

    NoSql

    • 不仅仅是数据
    • 没有固定的查询语言
    • 键值对存储,列存储,文档存储,图形数据库(社交关系)
    • 最终一致性
    • CAP定理和BASE
    • 高性能,高可用,高扩展
    • ...

了解:大数据时代: 3V + 3高

大数据时代的3V :主要是描述问题

  1. 海量 Velume
  2. 多样 Variety
  3. 实时 Velocity

大数据时代的3高 : 主要是对程序的要求

  1. 高并发
  2. 高可扩
  3. 高性能

真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。

4. 阿里巴巴演进分析

推荐阅读:阿里云这群疯子:https://yq.aliyun.com/articles/653511

1

image-20211026174453715

# 商品信息
名称 .  价格 . 商家信息
- 一般存放在关系型数据库:Mysql, 阿里巴巴使用的Mysql都是经过内部改动的。

# 商品描述、评论(文字居多)
- 文档型数据库:MongoDB

# 图片
- 分布式文件系统 FastDFS
- 淘宝:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里云: oss

# 商品关键字 用于搜索
- 搜索引擎:solr,  elasticsearch
- 阿里:Isearch 多隆

# 商品热门的波段信息
- 内存数据库:Redis,Memcache

# 商品交易,外部支付接口
- 第三方应用

image-20211026175833513

5. NoSql的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + Memcache

文档型数据库(bson数据格式):

  • MongoDB(掌握)
    • 基于分布式文件存储的数据库。是C++编写的,用于处理大量文档。
    • 介于关系型数据库和非关系型数据库之间的产品.
    • MongoDB是RDBMS和NoSQL的中间产品。
    • MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
  • ConthDB

列存储数据库

  • HBase(大数据必学)
  • 分布式文件系统

图关系数据库

用于广告推荐,社交网络

  • Neo4j、InfoGrid
分类 Examples举例 典型应用场景 数据模型 优点 缺点
键值对(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 Key 指向 Value 的键值对,通常用hash table来实现 查找速度快 数据无结构化,通常只被当作字符串或者二进制数据
列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 功能相对局限
文档型数据库 CouchDB, MongoDb Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 查询性能不高,而且缺乏统一的查询语法。
图形(Graph)数据库 Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群

二、Redis入门

1. 概述

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务。

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API

免费和开源! 是当下热门的NoSql技术之一!

也被人们称为结构化数据库!

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

区别的是【redis会周期性的把更新的数据写入磁盘】或者【把修改操作写入追加的记录文件】,

并且在此基础上实现了【master-slave(主从)同步】。

Redis能该干什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)

  2. 高效率、用于高速缓冲

  3. 发布订阅系统( 消息队列 )

  4. 地图信息分析

  5. 计时器、计数器(eg:浏览量 )

    .........

特性

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

其它

  1. 官网: https://redis.io/
  2. 中文网: http://www.redis.cn/
  3. 下载地址: 通过官网
  4. Windows 需要在 Github 上下载
  5. Redus 推荐都是在 Linux 服务器上搭建的, 基于 Linux 学习!

2. 环境搭建

官网:https://redis.io/

推荐使用Linux服务器学习。

windows版本的Redis已经停更很久了…

3. Windows安装

https://github.com/MicrosoftArchive/redis/releases

  1. 解压安装包
    在这里插入图片描述

  2. 开启redis-server.exe

  3. 启动redis-cli.exe测试在这里插入图片描述

  4. 记住: Windows 下使用确实简单, 但 Redis 推荐我们使用 Linux 去开发并使用 !

    image-20211026204801354

4. Linux安装

  1. 下载安装包!redis-5.0.8.tar.gz

  2. 解压Redis的安装包!程序一般放在 /opt 目录下

    在这里插入图片描述

  3. 基本环境安装

    yum install gcc-c++
    # 然后进入redis目录下执行 : 注意! 是进入该环境后执行!
    make
    # 然后执行
    make install
    

在这里插入图片描述

使用初体验

  1. redis默认安装路径 /usr/local/bin

    在这里插入图片描述

  2. 将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig

  3. redis默认不是后台启动的,需要修改配置文件!

  4. 通过制定的配置文件启动redis服务: redis-server kconfig/redis.conf

    • 其实安装成功后,也可以在任何目录直接 redis-server 通过默认的 conf 启动

  5. 使用redis-cli连接指定的端口号测试,Redis的默认端口6379

  6. 查看redis进程是否开启 ps -ef|grep redis

  7. 关闭Redis服务 shutdown

    • 目前是通过客户端进行关闭

    • redis-cli -h 127.0.0.1 -p 6379 shutdown
      

  8. 再次查看进程是否存在

    ps -ef|grep redis

  9. 后面我们会使用单机多Redis启动集群测试

5. 测试性能

redis-benchmark:Redis官方提供的性能测试工具,参数选项如下:

img

简单测试:

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

image-20211027220430552

6. 基础知识

redis默认有16个数据库

默认使用的是第0个

16个数据库为:DB 0 ~ DB 15
默认使用DB 0 ,可以使用select n切换到DB n,dbsize可以查看当前数据库的大小,与key数量相关。

127.0.0.1:6379> config get databases # 命令行查看数据库数量databases
1) "databases"
2) "16"

127.0.0.1:6379> select 8 # 切换数据库 DB 8
OK

127.0.0.1:6379[8]> dbsize # 查看数据库大小
(integer) 0

# 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
127.0.0.1:6379> set name sakura 
OK

127.0.0.1:6379> SELECT 8
OK

127.0.0.1:6379[8]> get name # db8中并不能获取db0中的键值对。
(nil)

127.0.0.1:6379[8]> DBSIZE
(integer) 0

127.0.0.1:6379[8]> SELECT 0
OK

127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"

127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5

dbsize :根据库中key的个数

exists (keyname) :判断某个键是否存在

keys * :查看当前数据库中所有的key。

flushdb:清空当前数据库中的键值对。

flushall:清空所有数据库的键值对。

  • 题外话: 为什么 Redis 选择端口 6379 ?
    • MRZE : 数字按键
    • 3306 好像也差不多

Redis是单线程的,Redis是基于内存操作的。

所以Redis的性能瓶颈不是CPU, 而是 机器内存网络带宽

然后既然可以用单线程实现, 就没必要上多线程了.

那么为什么Redis的速度如此快呢,性能这么高呢?(QPS达到10W+)

Redis为什么单线程还这么快?

  • 误区1:高性能的服务器一定是多线程的?

  • 误区2:多线程(CPU上下文会切换!)一定比单线程效率高!

  • 核心:

    • Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,
    • 官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。
    • 这个数据不比采用单进程多线程 的同样基于内存的 KV 数据库 Memcached 差!
    • 多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,没有上下文切换的,效率就是最高的,
    • 多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案。
    • 即,该方案的瓶颈是内存,而不是CPU。搞多线程会增加CPU的工作量,得不偿失。

三、五大数据类型

官方介绍:

image-20211028111006820

  • 翻译:
  • Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库高速缓存消息队列代理
  • 它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。
  • 内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

1. Redis-key

在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。

下面学习的命令:

  • exists key:判断键是否存在
  • del key:删除键值对
  • move key dbIndex:将键值对移动到指定数据库
  • expire key second:设置键值对的过期时间
  • ttl key : 查看剩余存活时间
  • type key:查看value的数据类型
127.0.0.1:6379> keys * # 查看当前数据库所有key
(empty list or set)

127.0.0.1:6379> set name qinjiang # set key
OK

127.0.0.1:6379> set age 20
OK

127.0.0.1:6379> keys * 
1) "age"
2) "name"

127.0.0.1:6379> move age 1 # 将键值对移动到指定数据库
(integer) 1

127.0.0.1:6379> EXISTS age # 判断键是否存在
(integer) 0 # 不存在

127.0.0.1:6379> EXISTS name
(integer) 1 # 存在

127.0.0.1:6379> SELECT 1
OK

127.0.0.1:6379[1]> keys *
1) "age"

127.0.0.1:6379[1]> del age # 删除键值对
(integer) 1 # 删除个数

127.0.0.1:6379> set age 20
OK

127.0.0.1:6379> EXPIRE age 15 # 设置键值对的过期时间
(integer) 1 # 设置成功 开始计数

127.0.0.1:6379> ttl age # 查看key的过期剩余时间
(integer) 13

127.0.0.1:6379> ttl age
(integer) 11

127.0.0.1:6379> ttl age
(integer) 9

127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key过期,-1表示key未设置过期时间

127.0.0.1:6379> get age # 过期的key 会被自动delete
(nil)

127.0.0.1:6379> keys *
1) "name"

127.0.0.1:6379> type name # 查看value的数据类型
string

关于TTL命令

Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:

  1. 当前key没有设置过期时间,所以会返回-1.
  2. 当前key有设置过期时间,而且key已经过期,所以会返回-2.
  3. 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.

关于重命名RENAMERENAMENX

  • RENAME key newkey修改 key 的名称

    • 当 newkey 已经存在时,其值会被覆盖掉
    • image-20211208093739703
  • RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。

    • 如果存在,则返回 0 ,不进行操作
    • image-20211208093936544

更多命令学习:https://www.redis.net.cn/order/

2. String(字符串)

会忽略单双引号,且只能出现一对

普通的set、get略。

命令 描述 示例
append key content 向指定的key的value后追加字符串 content set msg hello
append msg "world"
返回此时字符串长度:(integer) 11
get msg
“hello world”
decr/incr key 将指定key的value数值进行+1/-1(仅对于数字) set age 20
incr age
返回此时integer的值: (integer) 21
decr age
返回此时integer的值:(integer) 20
incrby/decrby key n 按指定的步长对数值进行加减 INCRBY age 5
(integer) 25
DECRBY age 10
(integer) 15
incrbyfloat key n 为数值加上浮点型数值 INCRBYFLOAT age 5.2
“20.2”
strlen key 获取key保存值的字符串长度 get msg
“hello world”
STRLEN msg
(integer) 11
getrange key start end 按起止位置获取字符串(闭区间,起止位置都取) get msg
“hello world”
GETRANGE msg 3 9
“lo worl”
setrange key offset value 用指定的 value 替换 key中 offset 开始后的值
即其从 offset 开始后的值,全都被value替换掉了
SETRANGE msg 2 hello
(integer) 7
get msg
“hehello”
getset key value 如果不存在值,则返回nil,如果存在值,获取原来的值,并设置新的值 GETSET msg test
“hello world”
GET msg
"test"
setnx key value 仅当key不存在时进行set SETNX msg test
(integer) 0
SETNX name sakura
(integer) 1
setex key seconds value set 键值对并设置过期时间 setex name 10 root OK
get name (nil)
mset key1 value1 [key2 value2..] 批量 set 键值对 MSET k1 v1 k2 v2 k3 v3
OK
msetnx key1 value1 [key2 value2..]` 批量设置键 值对,仅当参数中所有的 key 都不存在时执行 MSETNX k1 v1 k4 v4
(integer) 0
mget key1 [key2..] 批量获取多个key保存的值,值顺序即 key 顺序 MGET k1 k2 k3
1) “v1”
2) “v2”
3) “v3”
psetex key milliseconds value 和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,
# ===================================================
# set、get、del、append、strlen
# ===================================================
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   :value一定要是数字才能进行加减,+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的关系,从零到负一表示全部
# ===================================================
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)
# ===================================================
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   Mset 命令用于同时设置一个或多个 key-value 对。
# mget   Mget 命令返回所有(一个或多个)给定 key 的值。
#      如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
# msetnx  当所有 key 都成功设置,返回 1 。
#      如果所有给定 key 都设置失败(至少有一个 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数据)
# 可以用来缓存对象
mset user:1:name zhangsan user:1:age 2
mget user:1:name user:1:age


# ===================================================
# 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类似的使用场景:value除了是字符串还可以是数字,用途举例:

  • 计数器
  • 统计多单位的数量:uid:123666,follow 0
  • 粉丝数
  • 浏览量
  • 对象存储缓存

3. List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序。

你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 2~232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

  • List 是头插的
  • 测试1
# ===================================================
# Lpush:将一个或多个值插入到列表头部。(左)
# Rpush:将一个或多个值插入到列表尾部。(右)
# ===================================================
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


# ===================================================
# Lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
# 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
# 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
# ===================================================
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,按照索引下标获得元素,从 【0 】开始数的(-1 代表末位)
# 注意:只有 Lindex ,没有 Rindex,如果想倒着用,则用 负数 n (倒数第 n 个)即可
# ===================================================
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


# ===================================================
# 注: list 中是可以存在重复的值的,
#  		Lrem   list    count   value  : 从 list 的左边开始数,移除 count 个 值为 value 的键值对
# 	  	 有多少个就移除多少个,上限为 count ,下限为 0 个(即没有 value 值的键)
# ===================================================
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),就是说,让列表【只保留指定区间内的元素】,不在指定区间之内的元素都将被删除。
# 	Ltrim    key    start    end   : 注意,保留的是闭区间,即   [ start,  end  ] 区间内的元素
# ===================================================
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1

127.0.0.1:6379> RPUSH mylist "hello"
(integer) 2

127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 3

127.0.0.1:6379> RPUSH mylist "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 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
#   rpoplpush    source    destination :元素从 source 右边出队,在 destination 左边入队
# ===================================================
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,否则报错  (error) ERR no such key
(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 的之前或之后。
# ===================================================
redis> RPUSH mylist "Hello"
(integer) 1

redis> RPUSH mylist "World"
(integer) 2

redis> LINSERT mylist BEFORE "World" "There"
(integer) 3

redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"

首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等

正如图Redis中List是可以进行双端操作的,所以命令也就分为了LXXX和RXXX两类,有时候L也表示List例如LLEN

  • 但 Lindex 就没有对应的 Rindex
  • 要是想从右数起,可以用负数如 Lindex -1 ,表示倒数第 1 个位置

表格总结:

命令 描述
LPUSH/RPUSH key value1[value2..] 从L左边/R右边向列表中PUSH值(一个或者多个)。
LRANGE key start end 获取list 起止元素(索引从左往右 递增)
LPUSHX/RPUSHX key value 向已存在的列表名中push值(一个或者多个),如果不存在,则不执行
`LINSERT key BEFORE AFTER pivot value`
LLEN key 查看列表长度
LINDEX key index 通过索引获取列表元素
LSET key index value 通过列表指定索引处的值
LPOP/RPOP key 从最左边/最右边移除值 并返回
RPOPLPUSH source destination 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部
LTRIM key start end 通过下标截取指定范围内的列表
LREM key count value List中是允许value重复的,Lrem ,即 ListRemove
count > 0:从头部开始搜索 然后删除指定的value ,至多删除count个
count < 0:从尾部开始搜索…
count = 0:删除列表中所有的指定value。
BLPOP/BRPOP key1[key2] timout 移出并获取列表的第一个/最后一个元素,
如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOPLPUSH source destination timeout RPOPLPUSH功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

小结

  • list实际上是一个链表,before Node after , left, right 都可以插入值
  • 如果 key 不存在,则会自动创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低

应用:

消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)

4. Set(集合)

Redis的Set是string类型的无序集合。

集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 2~232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

命令 描述
SMEMBERS key 获取集合 key 的所有元素
SADD key member1[member2..] 向集合中无序增加一个/多个成员
SCARD key` 获取集合的成员数
SMEMBERS key` 返回集合中所有的成员
SISMEMBER key member 查询member元素是否是集合的成员,结果是无序的
SRANDMEMBER key [count] 随机返回集合中count个成员,count缺省值为1
SPOP key [count] 随机移除并返回集合中count个成员,count缺省值为1
SMOVE source destination member` 将source集合的成员member移动到destination集合
SREM key member1[member2..] 移除集合中一个/多个成员
SDIFF key1 [key2..] 返回所有集合的差集 key1- key2 - …,结果肯定是 key1 的真子集
SDIFFSTORE destination key1[key2..] 在 SDIFF 的基础上,将结果保存到集合中(覆盖)。不能保存到其他类型 key !
SINTER key1 [key2..] 返回所有集合的交集
SINTERSTORE destination key1[key2..] 在SINTER的基础上,存储结果到集合中。覆盖
SUNION key1 [key2..] 返回所有集合的并集
SUNIONSTORE destination key1 [key2..] 在SUNION的基础上,存储结果到及和张。覆盖
SSCAN key [MATCH pattern] [COUNT count] 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分
---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------
127.0.0.1:6379> sadd myset m1 m2 m3 m4 # 向myset中增加成员 m1~m4
(integer) 4

127.0.0.1:6379> scard myset # 获取集合的成员数目
(integer) 4

127.0.0.1:6379> smembers myset # 获取集合中所有成员
1) "m4"
2) "m3"
3) "m2"
4) "m1"

127.0.0.1:6379> sismember myset m5 # 查询m5是否是myset的成员
(integer) 0 # 不是,返回0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1 # 是,返回1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1

---------------------SRANDMEMBER--SPOP----------------------------------

127.0.0.1:6379> SRANDMEMBER myset 3 # 随机返回3个成员
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> SRANDMEMBER myset # 随机返回1个成员
"m3"
127.0.0.1:6379> SPOP myset 2 # 随机移除并返回2个成员
1) "m1"
2) "m4"
# 将set还原到{m1,m2,m3,m4}

---------------------SMOVE--SREM----------------------------------------
127.0.0.1:6379> SMOVE myset newset m3 # 将myset中m3成员移动到newset集合
(integer) 1

127.0.0.1:6379> SMEMBERS myset
1) "m4"
2) "m2"
3) "m1"

127.0.0.1:6379> SMEMBERS newset
1) "m3"

127.0.0.1:6379> SREM newset m3 # 从newset中移除m3元素
(integer) 1

127.0.0.1:6379> SMEMBERS newset
(empty list or set)



# 下面开始是多集合操作,多集合操作中若只有一个参数默认和自身进行运算
# setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
-----------------------------SDIFF------------------------------------
127.0.0.1:6379> SDIFF setx sety setz # 等价于setx-sety-setz
1) "m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
1) "m4"
2) "m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
1) "m5"

-------------------------SINTER---------------------------------------
#	交集     (用途: 共同关注)

127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
1) "m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
1) "m2"
2) "m6"

-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集
1) "m4"
2) "m6"
3) "m3"
4) "m2"
5) "m1"
6) "m5"

127.0.0.1:6379> SUNION setx sety # setx sety 并集
1) "m4"
2) "m6"
3) "m2"
4) "m1"
5) "m5"

5. Hash(哈希)

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

Set 就是一种简化的 Hash,只变动key,而value使用默认值填充。

可以将一个 Hash 表作为一个对象进行存储,表中存放对象的信息。

命令 描述
HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0
HMSET key field1 value1 [field2 value2..] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。
HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
HGET key field 获取存储在哈希表中指定字段的值
HMGET key field1 [field2..] 获取所有给定字段的值
HGETALL key 获取在哈希表key 的所有字段和值
HKEYS key 获取哈希表key中所有的字段
HLEN key 获取哈希表中字段的数量
HVALS key 获取哈希表中所有值
HDEL key field1 [field2..] 删除哈希表key中一个/多个field字段
HINCRBY key field n 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段
HINCRBYFLOAT key field n 为哈希表 key 中的指定字段的浮点数值加上增量 n。
HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。
# ===================================================
# hset、hget 命令用于为哈希表中的字段赋值 。
127.0.0.1:6379> hset myhash field1 "kuangshen"
(integer) 1
127.0.0.1:6379> hget myhash field1
"kuangshen"

# hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"

# hgetall 用于返回哈希表中,所有的字段和值。
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"

# hdel  用于删除哈希表 key 中的一个或多个指定字段
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> HMSET 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 # 1为存在,0为不存在
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 为哈希表中的指定 filed 的 value 加上指定的增量。
# ===================================================
127.0.0.1:6379> HSET myhash field 5  # field :5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1  # field :5+1 = 6
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1 # field :6-1 = 5
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10 # field :5-10 = -5
(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"



----------------Traing-Again--------HSET--HMSET--HSETNX----------------
# studentx 哈希表中, 键值对:name —— sakura
127.0.0.1:6379> HSET studentx name sakura 
(integer) 1

# 重复设置 field 进行覆盖,并返回0 ,键值对:name —— cyh
127.0.0.1:6379> HSET studentx name cyh 
(integer) 0

 # 键值对 age —— 20
127.0.0.1:6379> HSET studentx age 20
(integer) 1

 # 一次设置多组,sex为1,tel为15623667886
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886
OK

 # HSETNX 设置已存在的field
127.0.0.1:6379> HSETNX studentx name gyc
(integer) 0 # 失败
127.0.0.1:6379> HSETNX studentx email 12345@qq.com
(integer) 1 # 成功

----------------------HEXISTS--------------------------------
 # name字段在studentx中是否存在
127.0.0.1:6379> HEXISTS studentx name
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在

-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 获取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 获取studentx中name、age、tel字段的value
1) "gyc"
2) "20"
3) "15623667886"
127.0.0.1:6379> HGETALL studentx # 获取studentx中所有的field及其value
 1) "name"
 2) "gyc"
 3) "age"
 4) "20"
 5) "sex"
 6) "1"
 7) "tel"
 8) "15623667886"
 9) "email"
10) "12345@qq.com"


--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field
1) "name"
2) "age"
3) "sex"
4) "tel"
5) "email"
127.0.0.1:6379> HLEN studentx # 查看studentx中的字段数量
(integer) 5
127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value
1) "gyc"
2) "20"
3) "1"
4) "123123123159"
5) "12345@qq.com"

-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "email"

-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段数值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整数字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"

用途:

Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!

Hash更适合于对象的存储,Sring更适合于字符串存储!

6. Zset(有序集合)

每个元素都会关联一个double类型的分数(score)(当做是【权值】)。

redis 正是通过这个权值,来为集合中的成员进行从小到大的排序。

score 相同:按字典顺序排序

有序集合成员是唯一的,但分数(score)却可以重复


有序集合中的三大顺序:【分数(Score)】、【字典序(Lex)】、【当前排名(Rank)

Tips:默认是按分数排,分数相等的按字典序排,最后得到当前排名。

命令 描述
zAdd key score member1
[score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数
zCard key 获取有序集合的成员数
zCount key min max 计算在有序集合中指定区间 (score in [min, max])的成员数
zIncrBy key n member 有序集合中对指定成员 member 的分数加上增量 n
zScore key members 返回有序集中,成员的分数值
zRank key member 返回有序集合中指定成员的索引
zRange key start end 通过索引区间返回有序集合成指定区间内的成员
zRangeByLex key min max 通过字典区间返回有序集合的成员
zRangeByScore key min max 通过分数返回有序集合指定区间内的成员-inf 和 +inf分别表示最小最大值,只支持开区间()
zLexCount key min max 在有序集合中计算指定字典区间内成员数量
zRem key member1 [member2..] 移除有序集合中一个/多个成员
zRemRangeByLex key min max 移除有序集合中给定的字典区间的所有成员
zRemRangeByRank key start stop 移除有序集合中给定的排名区间的所有成员
zRemRangeByScore key min max 移除有序集合中给定的分数区间的所有成员
zRevRange key start end 返回有序集中指定区间内的成员,通过索引,分数从高到底
zRevRangeByScore key max min 返回有序集中指定分数区间内的成员,分数从高到低排序
zRevRangeByLex key max min 返回有序集中指定字典区间内的成员,按字典顺序倒序
zRevRank key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
zInterStore destination numkeys key1 [key2 ..]` 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination中,
numkeys:表示参与运算的集合数,将score相加作为结果的score
zUnionStore destination numkeys key1 [key2..]` 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
zScan key cursor [MATCH pattern] [COUNT count]` 迭代有序集合中的元素(包括元素成员和元素分值):http://www.redis.cn/commands/scan.html
# ==========================================================================================================
# 数据准备,乱序插入
#     zAdd   集合名  score  成员名  [  score  成员名  ...]
# ==========================================================================================================
127.0.0.1:6379> zadd salary 1000 a1000
(integer) 1
127.0.0.1:6379> zadd salary 1000 b1000
(integer) 1
127.0.0.1:6379> zadd salary 500 c500 800 d800
(integer) 2
127.0.0.1:6379> zadd salary 1200 d1200
(integer) 1

#     当前放入了    500 c500 --   800  d800   --  1000 a1000   --  1000 b1000  --  1200 d1200

# ==========================================================================================================
# zCard  key  获取有序集合 key 的元素个数
# ==========================================================================================================
127.0.0.1:6379> zcard salary
(integer) 5

# ==========================================================================================================
#  zCount  key min  max 获取  score in [min,  max ]  区间中的个数
# ==========================================================================================================
127.0.0.1:6379> zcount salary 0 1000
(integer) 4

# ==========================================================================================================
# ZIncrBy  key   n   member   对 有序集合key 中指定的 member 的 score 值增加 n 
# 返回的是修改后的分数
# ==========================================================================================================
127.0.0.1:6379> zincrby salary 50 c500
"550"
127.0.0.1:6379> zincrby salary -50 c500
"500"


# ==========================================================================================================
# zScore key member  获取有序集合中【指定成员】的分数值
# ==========================================================================================================
127.0.0.1:6379> zscore salary a1000
"1000"


# ==========================================================================================================
#  zRank key member  返回有序集合中【指定成员】的当前排名,也是从 0 开始的
# ==========================================================================================================
127.0.0.1:6379> zRank salary d800
(integer) 1
127.0.0.1:6379> zrank salary c500
(integer) 0
127.0.0.1:6379> zrank salary b1000
(integer) 3

# ==========================================================================================================
# zrange key start end  返回有序集合中,默认排序好的第 start 到 end 个
# 可以看成,是先按分数排,分数相同再按成员名称的字典序排
# ==========================================================================================================
127.0.0.1:6379> zrange salary 0 3
1) "c500"
2) "d800"
3) "a1000"
4) "b1000"

zRangeByLeX 特殊

image-20220418101352090

注意点:

  • 分数必须相同!如果有序集合中的成员分数有不一致的,返回的结果就不准。
  • 成员字符串作为二进制数组的字节数进行比较。
  • 默认是以ASCII字符集的顺序进行排列。如果成员字符串包含utf-8这类字符集的内容,就会影响返回结果,所以建议不要使用。
  • 默认情况下, “max” 和 “min” 参数前必须加 “[” 符号作为开头。”[” 符号与成员之间不能有空格, 返回成员结果集会包含参数 “min” 和 “max” 。
  • “max” 和 “min” 参数前可以加 “(“ 符号作为开头表示小于, “(“ 符号与成员之间不能有空格。返回成员结果集不会包含 “max” 和 “min” 成员。
  • 可以使用 “-“ 和 “+” 表示最小字典序最大字典序
  • “min” 和 “max” 不能反, “max” 放前面 “min”放后面会导致返回结果为空
  • 与【ZRANGEBYLEX】获取顺序相反的指令是【ZREVRANGEBYLEX】。
  • 源码中采用C语言中memcmp()函数,,从字符的第0位到最后一位进行排序,如果前面部分相同,那么较长的字符串比较短的字符串排序靠后
# 先重新准备一个,score 全都相同的 zset (score 不同会出现错误)
127.0.0.1:6379> zadd testlex 0 aabbcc
(integer) 1
127.0.0.1:6379> zadd testlex 0 bbccdd
(integer) 1
127.0.0.1:6379> zadd testlex 0 aabbdd
(integer) 1
127.0.0.1:6379> zadd testlex 0 ccddee
(integer) 1
127.0.0.1:6379> zadd testlex 0 cceedd
(integer) 1
# ==========================================================================================================
# zrangebylex key min max  注意!!!!  此时的 区间是正向的,  min  到  max ,否则返回空
# ==========================================================================================================
# 闭区间字典序
127.0.0.1:6379> zrangebylex testlex [aabbcc [bbccdd
1) "aabbcc"
2) "aabbdd"
3) "bbccdd"
#开区间字典序
127.0.0.1:6379> zrangebylex testlex (aabbcc (bbccdd
1) "aabbdd"
# 使用 - +
127.0.0.1:6379> zrangebylex testlex - [bbccdd
1) "aabbcc"
2) "aabbdd"
3) "bbccdd"
# 从最小排到最大 -   +
127.0.0.1:6379> zrangebylex testlex - +
1) "aabbcc"
2) "aabbdd"
3) "bbccdd"
4) "ccddee"
5) "cceedd"

# ==========================================================================================================
#  zlexcount key min max  字典序中,指定字典区间的元素个数
# ==========================================================================================================
127.0.0.1:6379> zrangebylex testlex - +
1) "aabbcc"
2) "aabbdd"
3) "bbccdd"
4) "ccddee"
5) "cceedd"
127.0.0.1:6379> zlexcount testlex [aabbcc [ccddee
(integer) 4
# 记住,底层是 【C语言中` memcmp() `函数】 ,前面部分字符串相同的话,长的排名靠后
127.0.0.1:6379> zlexcount testlex (aa (cc
(integer) 3    #  aabbcc   aabbdd  bbccdd
127.0.0.1:6379> zlexcount testlex (bb (ccee
(integer) 2    # bbccdd  ccddee

# ==========================================================================================================
# zRevRangeByLex key max min   倒序使用,注意!!!!  此时的 区间是反过来的,  max  到  min 
# ==========================================================================================================
127.0.0.1:6379> zrevrangebylex testlex + -
1) "cceedd"
2) "ccddee"
3) "bbccdd"
4) "aabbdd"
5) "aabbcc"

# ==========================================================================================================
# zRemRangeByLex key min max 移除指定字典区间中的元素
# ==========================================================================================================
127.0.0.1:6379> zrangebylex testlex - +
1) "aabbcc"
2) "aabbdd"
3) "bbccdd"
4) "ccddee"
5) "cceedd"
127.0.0.1:6379> zremrangebylex testlex (aab (ccd
(integer) 3
127.0.0.1:6379> zrangebylex testlex - +
1) "ccddee"
2) "cceedd"

# ==========================================================================================================
# zRangeByScore key min max 获取 score 在 min 到 max 区间的元素
# ==========================================================================================================
127.0.0.1:6379> zrangebyscore salary 500 1000
1) "c500"
2) "d800"
3) "a1000"
4) "b1000"
5) "e1000"


# ==========================================================================================================
# zRem key member [members...]
# 返回移除成功的个数
# ==========================================================================================================
127.0.0.1:6379> zrem salary e1000
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "c500"
2) "d800"
3) "a1000"
4) "b1000"
5) "d1200"


# ==========================================================================================================
# zRemRangByRank key start stop 移除指定排名区间的,即第 start 个到第 stop 个
# ==========================================================================================================
127.0.0.1:6379> zrange salary 0 -1
1) "c500"
2) "d800"
3) "a1000"
4) "b1000"
5) "d1200"
127.0.0.1:6379> zremrangebyrank salary 0 0
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "d800"
2) "a1000"
3) "b1000"
4) "d1200"

# ==========================================================================================================
# zRemRangeByScore key min max 移除指定分数区间的元素,也是闭区间 [min, max]
# ==========================================================================================================
127.0.0.1:6379> zadd salary 500 c500
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "c500"
2) "d800"
3) "a1000"
4) "b1000"
5) "d1200"
127.0.0.1:6379> zremrangebyscore salary 500 900
(integer) 2
127.0.0.1:6379> zrange salary 0 -1
1) "a1000"
2) "b1000"
3) "d1200"


# ==========================================================================================================
# zRevRange , 即 zRange 的倒序 , 第start个 到 第stop个
# ==========================================================================================================
127.0.0.1:6379> zrange salary 0 -1
1) "a1000"
2) "b1000"
3) "d1200"
127.0.0.1:6379> zrevrange salary 0 -1
1) "d1200"
2) "b1000"
3) "a1000"
127.0.0.1:6379> zrevrange salary 1 2
1) "b1000"
2) "a1000"

# ==========================================================================================================
# zInterStore 	计算交集,分数相加
# ==========================================================================================================
127.0.0.1:6379> zadd z1 0 a 1 b 2 c 3 d
(integer) 4
127.0.0.1:6379> zadd z2 3 c 4 d 5 e 6 f
(integer) 4
127.0.0.1:6379> zinterstore z3  2 z1 z2
(integer) 2
127.0.0.1:6379> zrange z3 0 -1
1) "c"
2) "d"

# ==========================================================================================================
# zUnion 	计算并集,名称相同的,求和
# ==========================================================================================================
127.0.0.1:6379> zunionstore z4 2 z1 z2
(integer) 6
127.0.0.1:6379> zrange z4 0 -1
1) "a"
2) "b"
3) "c"
4) "e"
5) "f"
6) "d"

用途

  • 和 set 相比,sorted set 增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,

  • 比如一个存储全班同学成绩的 sorted set,其集合value可以是同学的学号,而 score 就可以是其考试得分,

  • 这样在数据插入集合的时候,就已经进行了天然的排序。

  • 可以用sorted set来做带权重的队列,

  • 比如普通消息的score为1,重要消息的score为2,

  • 然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

  • 排行榜应用,取TOP N操作 !

四、三种特殊数据类型

何为特殊数据类型,

即五大基本数据类型的特化。类似 Java 中的子类

1. Geospatial(地理位置)

使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

命令 描述
geoAdd key longitud(经度) latitude(纬度) member [..] 将具体经纬度的坐标存入一个有序集合
geopos key member [member..] 获取集合中的一个/多个成员坐标
geoDist key member1 member2 [unit] 返回两个给定位置之间的距离。默认以米作为单位。
geoRadius key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] 以【给定的经纬度】为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
geoRadiusByMember key member radius 。。。。↑ 功能与GEORADIUS相同,只是【中心位置】不是具体的经纬度,而是使用集合中【已有的成员】作为中心点。
geoHash key member1 [member2..] 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
# 语法
geoadd key longitude latitude member ...
# 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。
# 这些数据会以有序集he的形式被储存在键里面,从而使得 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

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)

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"

georadius

# 语法
georadius   key   longitude   latitude   radius   m|km|ft|mi   [withcoord][withdist]   [withhash][asc|desc][count count]
# 以给定的经纬度为中心, 找出某一半径内的元素

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

[root@kuangshen 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

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

**GEO 底层的实现原理其实就是 Zset ! **

我们可以使用 Zset 命令来操作 Geo!!!!

zrem

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

127.0.0.1:6379> geoadd china:city 116.23 40.22 beijin
1

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

127.0.0.1:6379> zrem china:city beijin  # 移除元素
1

127.0.0.1:6379> zrem china:city 北京   # 移除元素
1

127.0.0.1:6379> zrange china:city 0 -1
重庆
西安
深圳
武汉
杭州
上海

补充

有效经纬度

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

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

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

关于GEORADIUS的参数

通过georadius就可以完成 附近的人功能

withcoord:带上坐标

withdist:带上距离,单位与半径单位相同

COUNT n :只显示前n个(按距离递增排序)

2. Hyperloglog(基数统计)

什么是基数?

数据集中不重复的元素的个数。

简介

  • Redis HyperLogLog 是用来做基数统计的算法,

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

  • 花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。

  • 因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,

  • 所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

  • 其底层使用 String 数据类型

应用场景:

网页的访问量(UV):一个用户多次访问,也只能算作一个人。

  • 传统实现,存储用户的id,然后每次进行比较看看是否是同一个人。
  • 当用户变多之后这种方式及其浪费空间,但我们的目的只是计数,仅此而已
  • Hyperloglog 就能帮助我们利用最小的空间完成。
  • 虽然有 0.81% 的误差率, 但在对应的功能需求下该误差是可以忽略的.
命令 描述
pfAdd key element1 [elememt2..]` 添加指定元素到 HyperLogLog 中
pfCount key [key]` 返回给定 HyperLogLog 的基数估算值。
pfMerge destkey sourcekey [sourcekey..]` 将多个 HyperLogLog 合并为一个 HyperLogLog(基数也会跟着合并)
----------PFADD--PFCOUNT---------------------
# 创建第一组元素:11个
127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k 
(integer) 1

 # hyperloglog 底层使用的是 String 类型
127.0.0.1:6379> type myelemx
string

 # 统计此时的myelemx的基数
127.0.0.1:6379> PFCOUNT myelemx
(integer) 11

# 创建第二组元素  i j k c b相同,11个
127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s
(integer) 1

 # 统计此时的myelemx的基数
127.0.0.1:6379> PFCOUNT myelemy
(integer) 11

----------------PFMERGE-----------------------
# 合并 myelemx 和 myelemy 成为myelemz
127.0.0.1:6379> PFMERGE myelemz myelemx myelemy 
OK

 # 计算基数
127.0.0.1:6379> PFCOUNT myelemz
(integer) 17

可见,当 Hyperloglog 中遇到相同的值时,其基数不会改变,仍是 1

  • 如果允许容错,那么一定可以使用Hyperloglog !
  • 如果不允许容错,就使用set或者自己的数据类型即可 !(占容量大)

3. BitMaps(位图)

位存储

  • 使用位存储,信息状态只有 0 和 1
  • Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),
  • 在bitmap上可执行AND, OR , XOR , NOT 以及其它位操作。
  • 是一种数据结构, 都是操作二进制位来进行记录, 就只有 0 和 1 两个状态!

应用场景

签到统计、状态统计

命令 描述
setbit key offset value 为指定key的offset位设置值
getbit key offset 获取offset位的值
bitcount key [start end] 统计字符串被设置为1的bit数,也可以指定统计范围。【按字节】
bitop operration destkey key[key..] 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
BITPOS key bit [start] [end] 返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位
------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第 0 位为 1 
(integer) 0
127.0.0.1:6379> setbit sign 2 13 # 设置sign的第 2 位为 0 和 1以外的其他值时,报错
(error) ERR bit is not an integer or out of range
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第 2 位为 1  不设置默认是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign	# 底层类型也还是 String
string

127.0.0.1:6379> getbit sign 2 # 获取第 2 位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3 # 获取第 3 位的数值
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置的,默认是0
(integer) 0

-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4

image-20220418121048911

image-20220418121119607

bitmaps的底层

image-20211028204239971

这样设置以后你能get到的值是:\xA2\x80

所以bitmaps是一串从左到右的二进制串

用途

在开发中,可能会遇到这种情况:

  • 需要统计用户的某些信息,
    • 如活跃或不活跃,登录或者不登录;
    • 又如需要记录用户一年的打卡情况,打卡了是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 相关命令。

五、事务

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

但是redis事务不能保证原子性

Redis事务本质:一组命令的集合。

----------------- 队列 set set set 执行 -------------------

此事务中每条命令都会被序列化,执行过程中按顺序执行,不会允许其他命令进行干扰。

  • 一次性
  • 顺序性
  • 排他性

  1. Redis事务 没有隔离级别 的概念

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

  3. Redis单条命令是保证原子性的,但是事务不保证原子性

1. Redis事务操作过程

  • 开启事务(multi
  • 命令入队 ( ... )
  • 中途放弃事务(discurd
  • 执行事务(exec

所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成。

127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2 # ..
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED

127.0.0.1:6379> exec # 此时, 上面的命令组开始事务性执行
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
   2) "k2"
   3) "k1"

取消(放弃)事务(discurd)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD # 放弃事务
OK
127.0.0.1:6379> EXEC 
(error) ERR EXEC without MULTI # 当前未开启事务
127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行
(nil)

2. 事务错误

编译型异常: 代码语法错误(编译时异常)所有的命令都不执行

  • 事务中的命令,按顺序执行
    • 遇到能正确执行的,会正确执行
    • 遇到不能正确执行的,则该条命令后的所有命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> error k1 # 这是一条语法错误命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 执行时报错, 也是都没有执行, 即之前报错之后的 QUEUED 都是无意义的!
127.0.0.1:6379> get k1 
(nil) # 其他命令并没有被执行

运行时异常: 代码逻辑错误 (运行时异常) **其他命令却已经正常执行 **

》》》 所以不保证事务原子性

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行

# 虽然中间有一条命令报错了,但是其他的指令依旧正常执行成功了。
# 所以说:Redis单条指令保证原子性,但是Redis事务不保证原子性。

注意 : 在 MySql 中, 事务是保证原子性的,注意区别 ! ! ! ! ! ! !

3. 监控

  • 即在启动事务处理之前,可以把锁开启,这样能保证数据处理的正常

悲观锁:

  • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁
  • 上锁期间其他人不能修改数据 ! ( 这点比乐观锁不同, 只能按需取了 )
  • 所以非常影响性能 !
  • 加悲观锁后
    • 事务的命令输入时,会锁定当前命令所需要操作数据
    • 在事物的编辑期间,不允许其他任何线程对这些数据进行操作
    • 等待事务的命令输入完毕,并执行事务完成,数据才解锁

乐观锁:

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁!
  • 只在更新数据的时候去判断一下,在此期间是否有人修改过这个数据 ( 有人修改了, 那判断的 version 值就非预期的了 )
  • 原理: 获取 version
  • 更新的时候比较 version
  • 应用: 实现 " 秒杀业务 "
  • 加乐观锁后
    • 事务的命令输入时,会获取所操作数据的当前 version
    • 当事务的命令输入完毕,开始执行事务时
    • 又会查看数据的 version 是否发生变化
    • 如果有变化,说明数据在编辑事务阶段发生了更改

使用watch key监控指定数据,相当于 乐观锁 加锁。

正常执行

127.0.0.1:6379> set money 100 # 设置余额:100
OK
127.0.0.1:6379> set use 0 # 支出使用:0
OK

# ====================================================
127.0.0.1:6379> watch money # 监视money (上锁)
OK
127.0.0.1:6379> multi	# 开启事务
OK
127.0.0.1:6379> DECRBY money 20	#减少余额
QUEUED
127.0.0.1:6379> INCRBY use 20  #增加支出
QUEUED
127.0.0.1:6379> exec # 监视值没有被中途修改,事务将正常执行
1) (integer) 80
2) (integer) 20

测试多个 Redis-cli 线程修改值,使用watch可以当做redis的乐观锁操作(相当于getversion)

我们启动另外一个客户端模拟插队线程。

原线程1:

127.0.0.1:6379> watch money # money上锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> 	# 此时事务并没有执行,即还没有输入 exec 指令

模拟线程插队,线程2:

127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
(integer) 600

线程1,执行事务:

127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败
(nil) # 没有结果,说明事务执行失败

# 查看结果 ====================================================
127.0.0.1:6379> get money 	# 线程 2 的修改生效, 因为其提交得快
"600"
127.0.0.1:6379> get use 	 # 线程 1 事务执行失败,数值没有被修改
"0"

如果修改失败, 建议解锁, 获取最新值,然后再加锁进行事务。

unwatch进行解锁。

注意:每次事务的提交执行exec后都会自动释放锁,不管是否成功

六、Jedis

Jedis是Redis官方推荐的Java连接开发工具

要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能写出漂亮的代码

Jedis 对象中,可以 . (点)出基本所有 Redis 指令

一、测试连通

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

  2. 导入 Redis 的相关依赖

    
    
      redis.clients
      jedis
      3.2.0
    
    
      com.alibaba
      fastjson
      1.2.58
    
    
  3. 编写测试代码

    import redis.clients.jedis.Jedis;
    public class Ping {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("127.0.0.1",6379);
            System.out.println("连接成功");
            //查看服务是否运行
            System.out.println("服务正在运行: "+jedis.ping());
        }
    }
    
  4. 启动 redis 服务

  5. 结果

  6. 连接成功
    服务正在运行: PONG
    
  7. jedis 对象的 ping( ) 方法,成功返回:PONG

二、常用API

基本操作

public class TestPassword {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //验证密码,如果没有设置密码这段代码省略
        //    jedis.auth("password");
        jedis.connect(); //连接
        jedis.disconnect(); //断开连接
        jedis.flushAll(); //清空所有的key
    }
}

对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','kuangshen'>的键值对:"+jedis.set("username", "kuangshen"));
        System.out.println("新增<'password','password'>的键值对:"+jedis.set("password", "password"));
        System.out.print("系统中所有的键如下:");
        Set 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());
    }
}

对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));
    }
}

对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));
    }
}

对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"));
    }
}

对Hash的操作命令

public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        Map 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
        System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//returnSet
        System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//returnList
        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"));
    }
}

三、事务

基本操作

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
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();
        }
    }
}