Redis初级
一.Redis初级 知识点介绍
nosql 概述
阿里巴巴的数据架构演进
nosql 四大分类
Redis 入门及安装(Window & Linux服务器)
五大基本数据类型
String
List
Set
Hash
Zset
三种特殊数据类型
geospatial
hyperloglog
bitmap
Redis 事务操作
基础API 之 Jedis 详解
二.NoSQL概述
2.1 数据存储的发展
1 、单机MySQL的年代
用户通过应用访问 —> 数据库的访问层 —> 再进入mysql的实例。
90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够。此时更多的是使用静态网页Html,服务器根本没有太大的压力。
思考一下,这种情况下:整个网站的瓶颈是什么?
1 、数据量如果太大、一个机器放不下了。
2 、当mysql数据超过300万条,需要建立索引 (B+ Tree),一个机器内存也放不下。
3 、访问量(读写混合),一个服务器承受不了。
只要你开始出现以上的三种情况之一,那么你就必须要晋级!
2 、Memcached(缓存) + MySQL + 垂直拆分 (读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦。所以说我们希望减轻数据的压力,我们可以使用缓存来保证效率。
发展过程: 优化数据结构和索引--> 文件缓存(IO)---> Memcached(当时最热门的技术)
3 、分库分表 + 水平拆分 + MySQL集群
技术和业务在发展的同时,对人的要求也越来越高。
本质:数据库(读,写)
早些年MyISAM: 表锁(例如user表有100万条数据,当有用户通过密码查询信息,会将整张表锁起来,其它进程若要进行查询就必须等待,将会影响效率),高并发下就会出现严重的锁问题。
转战Innodb:行锁(比表锁效率高,查询时只锁当前行)。
MySQL推出了表分区,使用分库分表来解决写的压力,这个并没有多少公司使用!后来推出了MySQL的集群,很好满足了所有需求!
4 、如今的年代
2010--2020 十年之间,世界已经发生了翻天覆地的变化;其中例如定位功能,音乐抖音热榜等也是一种数据。此时,MySQL等关系型数据库就不够用了,数据量很多,变化很快。 有的使用MySQL来存储一些比较大的文件,例如文字量较大的博客,大量的图片等,导致数据库表很大,效率就低了。如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题)。大数据的IO压力下,表几乎没法更改。
2.2 为什么要用NoSQL?
例如用户的个人信息,社交网络,地理位置等,用户自己产生的数据、用户日志等等爆发式增长。这时候我们就需要使用NoSQL数据库,NoSQL可以很好的处理以上的情况。
2.3 什么是NoSQL?
关系型数据库:表格 ,行 ,列。
NoSQL = Not Only SQL (不仅仅是SQL)。泛指非关系型数据库的,随着web2.0互联网的诞生,传统的关系型数据库很难应付web2.0时代,尤其是超大规模的高并发的社区, 暴露出来很多难以克服的问题。NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!
例如很多的数据类型用户的个人信息,社交网络(大量文本),地理位置(拓扑图),流媒体(视频和音频)等,这些数据类型的存储不需要一个固定的格式(即表格数据存储进行列中),不需要多余的操作就可以横向扩展(即允许多台机器来控制的,例如集群)的 ,类似于 Map
2.4 NoSQL 特点
1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(Redis 一秒写 8 万次,读取 11 万次,NoSQL的缓存记录级,是一种细粒度的缓存,性 能会比较高)
3 、数据类型是多样型的(不需要事先设计数据库,随取随用;如果是数据量十分大的表,很多人就无法设计了)
4、传统关系型数据库RDBMS 和 非关系型数据库NoSQL
传统关系型数据库RDBMS
- 结构化组织,例如:表、行、列
- SQL
- 数据和关系都存在单独的表中
- 数据定义语言、数据操作语言、数据查询语言、事务控制语言、数据控制语言
- 严格的ACID
- 基础的事务操作
非关系型数据库NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储、列存储、文档存储、图形数据库(例如社交关系的拓扑图)
- 最终一致性,允许过程中有误差,结果一致即可
- CAP定理和BASE理论(例如异地多活,保证整个服务器不会宕机)
- 高性能、高可用、高可扩
了解:3V+3高
大数据时代的3V:主要是描述问题的
1. 海量数据Volume
2. 多样性Variety
3. 实时性Velocity
大数据时代的3高:主要是对程序的要求
1. 高并发(支持大量用户访问)
2. 高可扩(随时搭建集群,进行水平拆分,拓展机器来解决)
3. 高性能(保证用户体验和性能)
真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的
三.阿里巴巴的数据架构演进
思考问题:页面中大量的数据都是在一个数据库中的吗?
敏捷开发、极限编程、协同开发 、开源等
随着这样的竞争,业务是越来越完善,然后对于开发者的要求也是越来越高
没有什么是加一层解决不了的
例如:商城案例
# 1、商品的基本信息
名称、价格、商家信息,这些信息在关系型数据库中存储即可。
例如: MySQL / Oracle
# 2、商品的描述、评论
文字比较多,使用文档型数据库,例如:MongoDB
# 3、图片
分布式文件系统 FastDFS
- 淘宝自己的 TFS
- Gooale的 GFS
- Hadoop HDFS
- 阿里云的 oss
- 七牛云
# 4、商品的关键字 (搜索)
- 搜索引擎 solr ElasticSearch
- ISerach:多隆(淘宝)
# 5、商品热门的波段信息(秒杀)
- 内存数据库 Redis Tair、memecache...
# 6、商品的交易,外部的支付接口
- 三方应用 支付宝支付、银行支付接口、微信支付...
大型互联网应用问题:
- 数据类型太多了
- 数据源繁多,经常重构
- 数据要改造,大面积改造
四.NoSQL的四大分类
KV键值对:
新浪:Redis
美团:Redis + Tair
阿里、百度:Redis + memecache
文档型数据库(bson格式和json一样):
MongoDB
- MongoDB 是一个基于分布式文件存储的数据库,C++ 编写,主要用来处理大量的文档
- MongoDB 是一个介于关系型数据库和非关系型数据中中间的产品
- MongoDB 是非关系型数据库中功能最丰富,最像关系型数据库的产品
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入门
5.1 概述
1. Redis 概念
Redis(**Re**mote **Di**ctionary **S**erver ),即远程字典服务。是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
**免费和开源**,是当下最热门的 NoSQL 技术之一,也被人们称之为**结构化数据库**。
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步。
2. Redis 作用
1 、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
2 、效率高,可以用于高速缓存
3 、发布订阅系统
4 、地图信息分析
5 、计时器、计数器(例如微博的浏览量)
3. 特性
1 、多样的数据类型
2 、持久化
3 、集群
4 、事务
4.准备工作
1 、官网:https://redis.io/
2 、中文网:http://www.redis.cn/
3 、下载地址:通过官网下载即可
注意:Window在 Github上下载(停更很久了)
Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习
5.2 安装
1. Windows安装
1 、下载安装包:https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
2 、下载完毕得到压缩包
3 、解压到自己电脑上的环境目录下的就可以的,Redis 十分的小,只有5M
4 、window开启Redis,双击运行服务redis-server.exe即可
(mac开启Redis服务端,在命令行里输入命令redis-server即可)
5 、window使用redis客户单,双击redis-cli.exe来连接redis
(mac开启Redis客户端,在命令行里输入命令redis-cli即可)
通过ping命令测试是否连接成功,设置值set key value,获取值get key。
记住一句话,Window下使用确实简单,但是Redis 推荐我们使用Linux去开发使用!
2. Linux安装
mac 连接服务器 ssh root@ip
#连接指定ip的服务器
ssh root@ip地址
1 、官网下载安装包 redis-6.0.6.tar.gz,上传至服务器
Mac版,先切换至该资源所在的盘符下,通过命令 scp 上传的文件名称 用户名@主机名:上传到的路径 ,然后按回车输密码。
例如:
scp redis-6.0.6.tar.gz root@106.14.159.179:/software/
Linux,安装包 redis-6.0.6.tar.gz,上传至服务器
[root@localhost local]# rz -y
2 、解压Redis的安装包
#解压命令:
tar -zxvf redis-6.0.6.tar.gz
#删除命令:
rm -rf redis-6.0.6.tar.gz
3 、进入解压后的文件,可以看到我们redis的配置文件
cd redis-6.0.6
4 、基本的环境安装
#安装c++的编译器:
yum install gcc-c++
注意若此时安装的redis是6版本,则需要升级gcc:
#升级到 5.3及以上版本
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
#注意:scl命令启用只是临时的,退出xshell或者重启就会恢复到原来的gcc版本。
#如果要长期生效的话,执行如下:
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
可以通过以下命令查看gcc版本:
#查看gcc版本
gcc -v
可以通过以下命令,将所有配置的文件配置上:
#将所有配置的文件配置上
make
再通过以下命令,查看是否安装成功:
#查看是否安装成功
make install
5 、redis的默认安装路径 /usr/local/bin
6 、将redis配置文件,复制到我们当前目录下
#新建文件夹
mkdir myconf
#拷贝文件至指定文件夹下
cp /software/redis-6.0.6/redis.conf myconf
注意:之后使用 redis.conf 配置文件,进行启动
7 、redis默认不是后台启动的,修改配置文件
#修改 redis.conf 配置文件
vim redis.conf
#进入配置文件,按i进入修改模式,按esc,输入:wq保存并退出
daemonize yes
8 、启动Redis服务
9 、使用redis-cli 进行连接测试
#通过cd命令,返回上一级bin目录
cd ../
#通过pwd命令,查看当前目录 /usr/local/bin
pwd
#启动redis服务端
redis-server myconf/redis.conf
#启动redis客户端 redis-cli -h 主机ip -p 端口号
redis-cli -p 6379
#输入ping命令,查看是否能拼通
ping
10 、查看redis的进程是否开启
#查看redis进程是否开启
ps -ef|grep redis
11 、关闭Redis服务
#关闭redis服务
shutdown
#此时状态为无连接,直接退出即可
exit
12 、再次查看进程是否存在
#查看redis进程是否开启
ps -ef|grep redis
13 、后续会使用单机多Redis启动集群测试
5.3 测试性能
redis-benchmark 是一个压力测试工具,官方自带的性能测试工具。
# 使用时:redis-benchmark 命令参数
redis-benchmark [option] [option value]
测试:
#测试:本机6379端口,100个并发,连接100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
如何查看这些分析呢?
5.4 基础的知识
1、redis默认有 16 个数据库,默认使用的是第 0 个
# 切换至redis配置文件所在的目录下
cd usr/local/bin
# 进入redis配置文件的编辑状态
vim myconf/redis.conf
2、可以使用 select 进行切换数据库
# 启动redis客户端
[root@iZuf67rdcsn46d295zbgomZ bin]# redis-cli -p 6379
# 测试能否ping通
127.0.0.1:6379> ping
PONG
# 切换至第3个数据库 数据库0~15,默认有16个数据库
127.0.0.1:6379> select 2
OK
# 查看数据库DB大小
127.0.0.1:6379[2]> dbsize
(integer) 0
# 给3号数据库中设置值
127.0.0.1:6379[2]> set name zs
OK
# 再次查看数据库DB大小
127.0.0.1:6379[2]> dbsize
(integer) 1
# 切换至第6个数据库
127.0.0.1:6379[2]> select 5
OK
# 查看数据库DB大小
127.0.0.1:6379[5]> dbsize
(integer) 0
# 通过key获取值
127.0.0.1:6379[5]> get name
(nil)
# 切换至第3个数据库
127.0.0.1:6379[5]> select 2
OK
# 通过key获取值
127.0.0.1:6379[2]> get name
"zs"
# 通过keys * 命令查看当前数据库中所有的key
127.0.0.1:6379[2]> keys *
1) "name"
注意:不同的数据库可以存储不同的值
3、清除数据库
# 清除当前数据库的内容
flushdb
# 清除所有数据库的内容
flushall
# 清除当前数据库的内容
127.0.0.1:6379[2]> flushdb
OK
# 查看当前数据库中所有的key
127.0.0.1:6379[2]> keys *
(empty array)
# 切换至第1个数据库
127.0.0.1:6379[2]> select 0
OK
# 查看当前数据库中所有的key
127.0.0.1:6379> keys *
1) "counter:{tag}:__rand_int__"
2) "myset:{tag}"
3) "key:{tag}:__rand_int__"
4) "name"
# 切换至第3个数据库
127.0.0.1:6379> select 2
OK
# 在任意数据库中清除所有数据,将清空所有数据库中的数据
127.0.0.1:6379[2]> flushall
OK
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> select 0
OK
127.0.0.1:6379> keys *
(empty array)
4、Redis 单线程
明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。
Redis 是C 语言写的,官方提供的数据为 100000+ 的QPS(每秒查询率),完全不比同样是使用 key-value的Memecache差。
Redis 为什么单线程还这么快?
误区 1 :高性能的服务器一定是多线程的
误区 2 :多线程(CPU上下文会切换)一定比单线程效率高
(先去了解CPU>内存>硬盘的速度)
核心:Redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的。多线程(CPU上下文会切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的。多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案。
六.五大数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)即集群搭建。
6.1 Redis-Key
1、常用命令
# 连接redis客户端
[root@iZuf67rdcsn46d295zbgomZ ~]# redis-cli -p 6379
# 测试是否能拼通
127.0.0.1:6379> ping
PONG
# 清空所有数据
127.0.0.1:6379> flushall
OK
# 查看当前数据库中的所有的key
127.0.0.1:6379> keys *
(empty array)
2、set 设置key
############################### set 设置key值 ###################################
# 设置key
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> keys *
1 ) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1 ) "age"
2 ) "name"
3、EXISTS 判断当前的key是否存在
########################## EXISTS 判断当前的key是否存在 ############################
# 判断当前的key是否存在
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
4、move 移动key
########################### move 移动key ##################################
# 移动当前的key至其它数据库
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> EXISTS name
(integer) 0
# 切换数据库
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
# 清除屏幕
127.0.0.1:6379> clear
# 切换数据库
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
1) "age"
# 设置值
127.0.0.1:6379> set name lisi
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"lisi"
5、EXPIRE 设置key的过期时间
########################### EXPIRE 设置key的过期时间 ##############################
# 设置key的过期时间,单位是秒(设置热点数据,一定时间内过期,例如单点登录)
127.0.0.1:6379> EXPIRE name 10
# 设置成功
(integer) 1
# 查看当前key的剩余时间
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
# key已过期
(integer) -2
# 获取key
127.0.0.1:6379> get name
# 数据库中没有此key
(nil)
6、del 移除key
############################### del 移除key #################################
# 查看所有的key
127.0.0.1:6379> keys *
1) "age"
# 移除 key
127.0.0.1:6379> del age
# 移除成功
(integer) 1
127.0.0.1:6379> keys *
(empty array)
7、type 查看当前key的类型
############################## type 查看当前key的类型 #################################
# 查看所有的key
127.0.0.1:6379> keys *
(empty array)
# 设置值
127.0.0.1:6379> set name zs
OK
127.0.0.1:6379> set age 1
OK
# 查看当前key的类型
127.0.0.1:6379> type name
# 字符串类型
string
127.0.0.1:6379> type age
string
可以参考官网上的常用命令:
6.2 String 字符串类型
1、set设置值 get获取值 EXISTS判断key是否存在
################################### set 设置值 #######################################
# 设置值
127.0.0.1:6379> set key1 v1
OK
################################### get 设置值 ####################################
# 获得值
127.0.0.1:6379> get key1
"v1"
############################### keys 获得所有的key #################################
# 获得所有的key
127.0.0.1:6379> keys *
1 ) "key1"
############################### EXISTS 判断key是否存在 ##############################
# 判断某一个key是否存在
127.0.0.1:6379> EXISTS key1
(integer) 1
2、APPEND追加字符串 STRLEN获取字符串的长度
############################# APPEND 追加字符串 #################################
# 追加字符串,如果当前key不存在,就相当于set key
127.0.0.1:6379> APPEND key1 "hello"
(integer) 7
# 获取值
127.0.0.1:6379> get key1
"v1hello"
############################# STRLEN 获取字符串的长度 ###############################
# 获取字符串的长度
127.0.0.1:6379> STRLEN key1
(integer) 7
# 追加字符串
127.0.0.1:6379> APPEND key1 ",world"
(integer) 13
# 获取值
127.0.0.1:6379> get key1
"v1hello,world"
# 获取字符串的长度
127.0.0.1:6379> strlen key1
(integer) 13
3、incr值自增 decr值自减 INCRBY/DECRBY 设置步长,指定增减量
################################# 设置值 #######################################
# 初始浏览量为 0
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
################################## incr 值自增 ##################################
# 类似于 i++ , 步长 i+=
# 自增 1 浏览量变为 1
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
################################## decr 值自减 ####################################
# 自减 1 浏览量-1
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
########################### INCRBY/DECRBY 设置步长,指定增减量 ##########################
# 可以设置步长,指定增量
127.0.0.1:6379> INCRBY views 10
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
# 可以设置步长,指定减量
127.0.0.1:6379> DECRBY views 5
(integer) 14
4、GETRANGE 获取指定范围的字符串
##############################GETRANGE 获取指定范围的字符串#################################
# 获取所有key
127.0.0.1:6379> keys *
(empty array)
# 设置值
127.0.0.1:6379> set key1 "hello world"
OK
# 获取值
127.0.0.1:6379> get key1
"hello world"
# 查看字符串的长度
127.0.0.1:6379> strlen key1
(integer) 11
# 截取指定范围的字符串( 闭区间[0,3] )
127.0.0.1:6379> getrange key1 0 3
"hell"
# 截取指定范围的字符串( 闭区间[0,-1] )当第二个参数设置为-1时,即截取全部字符串,与get key是一样效果
127.0.0.1:6379> getrange key1 0 -1
"hello world"
5、SETRANGE 字符串替换
############################# SETRANGE 字符串替换 #################################
# 设置值
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
# 替换指定位置开始的字符串
127.0.0.1:6379> SETRANGE key2 1 xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
6、setex (set with expire) 设置过期时间
setnx (set if not exist) 不存在再设置(在分布式锁中会常常使用)
####################### 设置过期时间 setex (set with expire) ##########################
# 获取所有key
127.0.0.1:6379> keys *
(empty array)
# 设置key1 的值为 hello,30秒后过期
127.0.0.1:6379> setex key1 30 "hello"
OK
# 查看当前key的剩余时间
127.0.0.1:6379> ttl key1
(integer) 25
# 获取key1的值
127.0.0.1:6379> get key1
"hello"
####################### 不存在再设置 setnx (set if not exist) ########################
# 如果key2不存在,创建key2
127.0.0.1:6379> setnx key2 "world"
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
# 如果key2存在,创建失败
127.0.0.1:6379> setnx key2 "redis"
(integer) 0
127.0.0.1:6379> get key2
"world"
7、mset批量设置值 mget批量获取值
############################## mset和mget 批量操作 ####################################
# 同时设置多个值
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
# 同时获取多个值
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
######################### msetnx 批量为不存在的元素设置值 #################################
# 同时为不存在的再设置值,msetnx是一个原子性的操作,要么一起成功,要么一起失败
127.0.0.1:6379> msetnx k1 v1 k4 v4
# k1和k4皆设置失败
(integer) 0
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
8、设置对象
############################## 对象 ###################################
# 第一种方式:用json字符串保存一个对象信息 user:{id} {field:value,field:value}
# 设置user:1对象的值 , 若设置同名的对象,则会发生覆盖
127.0.0.1:6379> set user:1 {name:zs,age:10}
OK
# 获取user:1对象的值
127.0.0.1:6379> get user:1
"{name:zs,age:10}"
# 第二种方式:批量分别设置对象的值的方式 user:{id}:{field} {value}
# 同时设置user:2:name 和 user:2:age
127.0.0.1:6379> mset user:2:name lisi user:2:age 20
OK
# 同时获取user:2:name 和 user:2:age
127.0.0.1:6379> mget user:2:name user:2:age
1) "lisi"
2) "20"
9、getset 获取旧值并设置新值
######################### getset 获取旧值并设置新值 ############################
# 如果不存在值,则返回 nil
127.0.0.1:6379> getset key1 hello
(nil)
# 如果存在值,获取原来的值,并设置新的值
127.0.0.1:6379> getset key1 world
"hello"
# 获取值,已经是最新的值
127.0.0.1:6379> get key1
"world"
10、使用场景
value除了是我们的字符串还可以是我们的数字
计数器 例如:INCRBY
统计多单位的数量 例如:批量对象属性的值存储 user:{id}:{field} {value}
粉丝数
对象缓存存储
6.3 List 列表
在redis里面,我们可以把list当作栈、队列、阻塞队列。所有的list命令都是用 L 开头的,Redis不区分大小命令。
1、LPUSH左边添加值 RPUSH右边添加值
# 清空数据库
127.0.0.1:6379> flushdb
OK
# 查看所有key
127.0.0.1:6379> keys *
(empty array)
############################# LPUSH 左边添加值 ##########################
# 将一个值或者多个值,插入到列表头部 (左)
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
############################# LRANGE 闭区间获取值 ##########################
# 通过区间获取list具体的值,设置为-1代表取到最后
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
# 通过区间获取list具体的值 [0,1]闭区间
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"
############################# RPUSH 右边添加值 ##########################
# 将一个值或者多个值,插入到列表位部 (右)
127.0.0.1:6379> RPUSH list right
(integer) 4
# 通过区间获取具体的值,设置为-1代表取到最后
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
2、LPOP移除并返回最左边元素 RPOP移除并返回最右边元素
# 查询list列表中的所有元素
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
############################ LPOP 移除并返回最左边元素 ############################
# 移除list的第一个元素
127.0.0.1:6379> LPOP list
"three"
############################ RPOP 移除并返回最右边元素 ############################
# 移除list的最后一个元素
127.0.0.1:6379> RPOP list
"right"
# 查询list列表中的所有元素
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3、Lindex 通过下标获取值
############################# Lindex 通过下标获取值 ##############################
# 查询list列表中的所有元素
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
# 通过下标获得 list 中的某一个值
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379> lindex list 1
"one"
4、Llen 返回列表的长度
############################## Llen 返回列表的长度 #################################
# 清空当前数据库
127.0.0.1:6379> flushdb
OK
# 给list列表传入值
127.0.0.1:6379> LPUSH list 111
(integer) 1
127.0.0.1:6379> LPUSH list 222
(integer) 2
127.0.0.1:6379> LPUSH list 333
(integer) 3
# 返回list列表的长度
127.0.0.1:6379> LLEN list
(integer) 3
5、Lrem 移除指定的值
############################## Lrem 移除指定的值 ###############################
# 给list列表中存入重复值
127.0.0.1:6379> LPUSH list 333
(integer) 4
# 查看list列表中所有值,列表中允许存放同一值
127.0.0.1:6379> LRANGE list 0 -1
1) "333"
2) "333"
3) "222"
4) "111"
# 移除list集合中指定 个数 的 value,精确匹配
# 移除一个"111"的值
127.0.0.1:6379> LREM list 1 111
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "333"
2) "333"
3) "222"
# 移除两个"333"的值
127.0.0.1:6379> LREM list 2 333
(integer) 2
# 查看list列表中所有值
127.0.0.1:6379> LRANGE list 0 -1
1) "222"
6、Ltrim 截取
################################ Ltrim 截取 ######################################
# 清空当前数据库
127.0.0.1:6379> flushdb
OK
# 查询所有key
127.0.0.1:6379> keys *
(empty array)
# 向list列表右边添加值
127.0.0.1:6379> RPUSH list aaa
(integer) 1
127.0.0.1:6379> RPUSH list bbb
(integer) 2
127.0.0.1:6379> RPUSH list ccc
(integer) 3
127.0.0.1:6379> RPUSH list ddd
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
# 通过下标截取指定的位置(闭区间),这个list已经被改变了,截断了只剩下截取的元素
127.0.0.1:6379> LTRIM list 1 2
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "bbb"
2) "ccc"
7、 rpoplpush 移除原列表的最右边元素,并添加到新列表的最左边
######################### rpoplpush 原列表 目标列表 #########################
# 清空当前数据库
127.0.0.1:6379> flushdb
OK
# 给mylist列表最右边添加值
127.0.0.1:6379> RPUSH mylist aaa
(integer) 1
127.0.0.1:6379> RPUSH mylist bbb
(integer) 2
127.0.0.1:6379> RPUSH mylist ccc
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
# 移除列表的最右边元素,将他移动到新列表的最左边
127.0.0.1:6379> RPOPLPUSH mylist myother
"ccc"
# 查看原列表中的元素
127.0.0.1:6379> LRANGE mylist 0 -1
1) "aaa"
2) "bbb"
# 查看目标列表中的元素
127.0.0.1:6379> LRANGE myother 0 -1
1) "ccc"
# 移除列表的最右边元素,将他移动到新列表的最左边
127.0.0.1:6379> RPOPLPUSH mylist myother
"bbb"
# 查看原列表中的元素
127.0.0.1:6379> LRANGE mylist 0 -1
1) "aaa"
# 查看原列表中的元素
127.0.0.1:6379> LRANGE myother 0 -1
1) "bbb"
2) "ccc"
8、Lset 更新当前下标的值,若当前列表不存在更新则会报错
################ Lset 将列表中指定下标的值替换为另外一个值,更新操作 ####################
# 判断这个列表是否存在
127.0.0.1:6379> EXISTS list
(integer) 0
# 如果列表不存在,更新会报错
127.0.0.1:6379> lset list 0 aaa
(error) ERR no such key
# 先给列表中存入元素
127.0.0.1:6379> lpush list aaa
(integer) 1
# 查看列表中所有元素
127.0.0.1:6379> LRANGE list 0 -1
1) "aaa"
# 如果列表存在,更新当前下标的值
127.0.0.1:6379> lset list 0 bbb
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "bbb"
# 如果列表对应下标不存在值,则会报错
127.0.0.1:6379> lset list 1 ccc
(error) ERR index out of range
9、Linsert 将某个具体的value插入到列表中某个元素的前面或者后面
############## linsert 将某个具体的value插入到列表中某个元素的前面或者后面 ##############
# 清空数据库
127.0.0.1:6379> flushdb
OK
# 查看所有key
127.0.0.1:6379> keys *
(empty array)
# 在列表的最右边添加元素
127.0.0.1:6379> rpush list aaa
(integer) 1
127.0.0.1:6379> rpush list bbb
(integer) 2
127.0.0.1:6379> rpush list bbb
(integer) 3
# 查看列表中的所有元素
127.0.0.1:6379> LRANGE list 0 -1
1) "aaa"
2) "bbb"
3) "bbb"
# 在指定的列表元素前面添加值
127.0.0.1:6379> LINSERT list before bbb hello
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "aaa"
2) "hello"
3) "bbb"
4) "bbb"
# 在指定的列表元素后面添加值
127.0.0.1:6379> LINSERT list after bbb world
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "aaa"
2) "hello"
3) "bbb"
4) "world"
5) "bbb"
10、使用场景
1).List实际上是一个链表,before Node after , left,right 都可以插入值
2).如果key不存在,创建新的链表;如果key存在,新增内容
3).如果移除了所有值,则是空链表,说明不存在了
5).在两边插入或者改动值,效率最高;若是中间元素,相对来说效率会低一点
6).可以使用List实现消息排队、消息队列 (Lpush Rpop)、 栈( Lpush Lpop)
6.4 Set 无序不重复集合
1、sadd 添加元素
#############################sadd 给set集合添加元素 ########################################
# 清空数据库
127.0.0.1:6379> flushall
OK
# 添加元素
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset redis
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
# 添加重复元素,返回0代表添加失败
127.0.0.1:6379> sadd myset hello
(integer) 0
########################## SMEMBERS 查看指定set集合的所有值 ###########################
# 查看集合中所有值
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
3) "redis"
2、SISMEMBER 判断某一个值是不是在set集合中
##################### SISMEMBER 判断某一个值是不是在set集合中 ########################
# 判断集合中是否存在某个值,返回1则存在,返回0则不存在
127.0.0.1:6379> SISMEMBER myset hello
(integer) 1
127.0.0.1:6379> SISMEMBER myset aaa
(integer) 0
4、scard 获取集合中的内容元素个数
######################### scard 获取集合中的内容元素个数 ############################
127.0.0.1:6379> SCARD myset
(integer) 3
127.0.0.1:6379> SADD myset aaa
(integer) 1
127.0.0.1:6379> SCARD myset
(integer) 4
5、srem 移除set集合中的指定元素
######################### srem 移除set集合中的指定元素 #############################
# 查看集合中所有元素
127.0.0.1:6379> SMEMBERS myset
1) "aaa"
2) "hello"
3) "world"
4) "redis"
# 移除set集合中的指定元素
127.0.0.1:6379> srem myset aaa
(integer) 1
# 查看集合中所有元素
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
3) "redis"
6、SRANDMEMBER 随机抽选出一个元素
########################### SRANDMEMBER 随机抽选出一个元素 #########################
# 查看集合中所有元素
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
3) "redis"
# 随机抽选出一个元素
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"redis"
# 随机抽选出指定个数的元素
127.0.0.1:6379> SRANDMEMBER myset 2
1) "redis"
2) "world"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hello"
2) "world"
7、spop 删除key,随机删除key
############################# spop 随机删除key #########################
# 查看集合中所有元素
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
3) "redis"
# 随机弹出key并返回
127.0.0.1:6379> SPOP myset
"hello"
127.0.0.1:6379> SPOP myset
"world"
# 查看集合中所有元素
127.0.0.1:6379> SMEMBERS myset
1) "redis"
8、smove 将一个指定的值,移动到另外一个set集
######################## smove 将一个指定的值,移动到另外一个set集 #####################
# 清空数据库
127.0.0.1:6379> flushdb
OK
# 给myset集合添加元素
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset redis
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
# 查看myset集合中的所有元素
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
3) "redis"
# 给myother集合添加元素
127.0.0.1:6379> sadd myother other
(integer) 1
# 查看myother集合中的所有元素
127.0.0.1:6379> SMEMBERS myother
1) "other"
# 将myset集合中的元素,移动至myother中
127.0.0.1:6379> SMOVE myset myother redis
(integer) 1
# 此时myset集合元素少了redis元素
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
# 此时myother集合元素多了redis元素
127.0.0.1:6379> SMEMBERS myother
1) "redis"
2) "other"
9、使用场景
例如:微博、B站、抖音都有共同关注这个功能,实际上是数学上交集的概念。
数字集合类:
- 差集 SDIFF
- 交集 SINTER
- 并集 SUNION
# 清空屏幕
127.0.0.1:6379> clear
# 清空数据库
127.0.0.1:6379> flushdb
OK
# set1集合 添加元素
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
127.0.0.1:6379> sadd set1 c
(integer) 1
# set2集合 添加元素
127.0.0.1:6379> sadd set2 c
(integer) 1
127.0.0.1:6379> sadd set2 d
(integer) 1
127.0.0.1:6379> sadd set2 e
(integer) 1
# 查看set1集合元素
127.0.0.1:6379> SMEMBERS set1
1) "c"
2) "a"
3) "b"
# 查看set2集合元素
127.0.0.1:6379> SMEMBERS set2
1) "d"
2) "e"
3) "c"
# 差集:以set1为主,查看与set2集合不同的元素
127.0.0.1:6379> SDIFF set1 set2
1) "a"
2) "b"
# 交集:查看集合set1和set2的共同元素,例如共同好友就可以这样实现
127.0.0.1:6379> SINTER set1 set2
1) "c"
# 并集:查看集合set1和set2的所有元素
127.0.0.1:6379> SUNION set1 set2
1) "a"
2) "c"
3) "b"
4) "d"
5) "e"
使用场景:例如微博,A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个set集合中。其中共同关注,共同爱好,二度好友,推荐好友(六度分割理论)
6.5 Hash 哈希
类似于map集合,本质和String类型没有太大区别,还是一个简单的key-value。
1、hset设置值 hget获取值
############################## hset设置值 hget获取值 ###########################
# hset设置值
127.0.0.1:6379> hset myhash f1 hello
(integer) 1
127.0.0.1:6379> hset myhash f2 world
(integer) 1
127.0.0.1:6379> hset myhash f3 redis
(integer) 1
# hget获取值
127.0.0.1:6379> hget myhash f2
"world"
2、hmset批量设置值 hmget批量获取值
########################## hmset批量设置值 hmget批量获取值 ########################
# hmset批量设置值
127.0.0.1:6379> hmset myhash f1 aaa f2 bbb f3 ccc
OK
# hmget批量获取值
127.0.0.1:6379> hmget myhash f1 f2 f3
1) "aaa"
2) "bbb"
3) "ccc"
3、hgetall 获取全部的数据
############################ hgetall 获取全部的数据 ###############################
# hset设置值,若key存在则发生覆盖
127.0.0.1:6379> hset myhash f1 zhangsan
(integer) 0
# 获取全部数据
127.0.0.1:6379> hgetall myhash
1) "f1"
2) "zhangsan"
3) "f2"
4) "bbb"
5) "f3"
6) "ccc"
# hset设置值,若key不存在则直接存入
127.0.0.1:6379> hset myhash f4 ddd
(integer) 1
# 获取全部数据
127.0.0.1:6379> hgetall myhash
1) "f1"
2) "zhangsan"
3) "f2"
4) "bbb"
5) "f3"
6) "ccc"
7) "f4"
8) "ddd"
4、hdel 删除hash指定key字段,对应的value值也就消失
#################### hdel 删除hash指定key字段,对应的value值也就消失 #################
# 删除hash的指定key,值也一并删除
127.0.0.1:6379> hdel myhash f1
(integer) 1
# 获取全部数据
127.0.0.1:6379> hgetall myhash
1) "f2"
2) "bbb"
3) "f3"
4) "ccc"
5) "f4"
6) "ddd"
5、hlen 获取hash表的字段数量
############################ hlen 获取hash表的字段数量 ###############################
# 查看hash中的键值对数量
127.0.0.1:6379> hlen myhash
(integer) 3
6、HEXISTS 判断hash中指定字段是否存在
######################### HEXISTS 判断hash中指定字段是否存在 ##########################
# HEXISTS判断hash中是否存在指定的key,不存在则返回0
127.0.0.1:6379> HEXISTS myhash f1
(integer) 0
# HEXISTS判断hash中是否存在指定的key,存在则返回1
127.0.0.1:6379> HEXISTS myhash f2
(integer) 1
7、hkeys只获得所有field,hvals只获得所有value
############################## hkeys 只获得所有键key ###############################
127.0.0.1:6379> hkeys myhash
1) "f2"
2) "f3"
3) "f4"
############################## hvals 只获得所有值value ################################
127.0.0.1:6379> hvals myhash
1) "bbb"
2) "ccc"
3) "ddd"
8、hincrby 设置增量,若是正数则增加,若是负数则减少
# 设置值
127.0.0.1:6379> hset myhash f5 5
(integer) 1
# 获取值
127.0.0.1:6379> hget myhash f5
"5"
# 设置正数,给指定属性自增值
127.0.0.1:6379> HINCRBY myhash f5 2
(integer) 7
127.0.0.1:6379> hget myhash f5
"7"
# 设置负数,给指定属性自减值
127.0.0.1:6379> HINCRBY myhash f5 -2
(integer) 5
127.0.0.1:6379> hget myhash f5
"5"
9、hsetnx 设置值时,若key已存在则失败返回0,不存在则成功返回1
# 设置值时,若key不存在则成功返回1
127.0.0.1:6379> hsetnx myhash f6 aaa
(integer) 1
127.0.0.1:6379> hget myhash f6
"aaa"
# 设置值时,若key已存在则失败返回0
127.0.0.1:6379> HSETNX myhash f6 bbb
(integer) 0
127.0.0.1:6379> hget myhash f6
"aaa"
10、使用场景
hash最多的应用是存储变更的数据,例如将user作为键,name age作为值进行存储,尤其是是用户信息之类的,经常变动的信息。**hash 更适合于对象的存储,String更加适合字符串存储。**
# 清空数据库
127.0.0.1:6379> flushdb
OK
# 设置user对象的单个属性的值
127.0.0.1:6379> hset user:1 name zs
(integer) 1
# 获取user对象的单个属性的值
127.0.0.1:6379> hget user:1 name
"zs"
# 批量设置user对象的多个属性的值
127.0.0.1:6379> hmset user:2 name lisi age 10
OK
# 批量获取user对象的多个属性的值
127.0.0.1:6379> hmget user:2 name age
1) "lisi"
2) "10"
6.6 Zset 有序集合
在set的基础上,增加了一个标志,此标志可以让值进行排序 ,语法: zadd key score member
1、zadd 添加 zrange 查看
# 添加单个元素
127.0.0.1:6379> zadd myzset 1 one
(integer) 1
127.0.0.1:6379> zadd myzset 2 two
(integer) 1
127.0.0.1:6379> zadd myzset 3 three
(integer) 1
# 添加多个元素
127.0.0.1:6379> zadd myzset 4 four 5 five
(integer) 2
# 查看指定下标范围的元素
127.0.0.1:6379> zrange myzset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
2、ZRANGEBYSCORE 实现升序 ZREVRANGEBYSCORE 实现降序
# 清空数据库
127.0.0.1:6379> flushdb
OK
# 添加元素
127.0.0.1:6379> zadd salary 2000 zs
(integer) 1
127.0.0.1:6379> zadd salary 3000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 1500 wang
(integer) 1
# 根据salary字段升序排列
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "wang"
2) "zs"
3) "lisi"
# 根据salary字段降序排列
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf
1) "lisi"
2) "zs"
3) "wang"
# 根据salary字段升序排列,显示全部信息(包括名称、薪资)
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "wang"
2) "1500"
3) "zs"
4) "2000"
5) "lisi"
6) "3000"
# 根据salary字段降序排列,显示全部信息(包括名称、薪资)
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores
1) "lisi"
2) "3000"
3) "zs"
4) "2000"
5) "wang"
6) "1500"
# 根据salary字段升序排列,按照指定范围,显示全部信息(包括名称、薪资)
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
1) "wang"
2) "1500"
3) "zs"
4) "2000"
3、zrem 移除
# 查看指定下标范围的元素
127.0.0.1:6379> ZRANGE salary 0 -1
1) "wang"
2) "zs"
3) "lisi"
# 移除元素
127.0.0.1:6379> zrem salary zs
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "wang"
2) "lisi"
4、zcard 获取有序集合中元素的个数
# 获取有序集合中元素的个数
127.0.0.1:6379> zcard salary
(integer) 2
5、zcount 获取集合中指定区间的元素个数
# 获取指定区间的元素个数
127.0.0.1:6379> zcount salary 500 1500
(integer) 1
127.0.0.1:6379> zcount salary 500 3000
(integer) 2
6、使用场景
zset 即是对set排序
1). 可以存储班级成绩表、工资表排序等
2). 带权重进行判断:普通消息 1 ,重要消息 2
3). 实现排行榜应用:将播放量或评分放进有序集合中进行遍历, 或者取TopN测试
七.三种特殊数据类型
7.1 Geospatial 地理位置
思考:朋友的定位,附近的人,打车距离计算如何实现?
解决:Redis 的 Geo 在Redis3.2 版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人。
参考:城市经度纬度查询网站,可以查询一些测试数据:http://www.jsons.cn/lngcodeinfo/0706D99C19A781A3/
相关六个命令:
- GEOADD
- GEODIST
- GEOHASH
- GEOPOS
- GEORADIUS
- GEORADIUSBYMEMBER
参考文档:https://www.redis.net.cn/order/3685.html
1、GEOADD 添加地理位置
语法:
# 将指定的地理空间位置(经度、纬度、名称)添加到指定的key中
GEOADD key longitude经度 latitude纬度 member地理名 [longitude latitude member ...]
规则:
# 两级无法直接添加,一般开发过程中会下载城市数据,直接通过java程序一次性导入。
# 有效的经度从-180度到180度,有效的纬度从-85.05112878度到85.05112878度,当坐标位置超出上述指定范围时,该命令将会返回一个错误。
#如下:
127.0.0.1:6379> geoadd china:city 39.90 116.40 beijing
(error) ERR invalid longitude,latitude pair 39 .900000,116.400000
案例:
# GEOADD 添加地理位置(经度、纬度、名称)
127.0.0.1:6379> geoadd china:city 116.405285 39.90498 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.298572 30.584355 wuhan
(integer) 1
127.0.0.1:6379> geoadd china:city 118.11022 24.490474 xiamen
(integer) 1
127.0.0.1:6379> geoadd china:city 118.767413 32.04154 nanjing
(integer) 1
127.0.0.1:6379> geoadd china:city 106.504962 29.533155 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.177314 22.266416 xianggang 113.549134 22.19875 aomen
(integer) 2
2、GEOPOS 获得当前定位,即坐标值
语法:
# 从key里返回所有给定位置元素的位置(经度和纬度)
GEOPOS key member [member ...]
规则:
# GEOPOS 命令返回一个数组,数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。
# 当给定的位置元素不存在时,对应的数组项为空值。
案例:
# GEOPOS 获得指定城市的经度和纬度
127.0.0.1:6379> geopos china:city wuhan
1) 1) "114.29857403039932251"
2) "30.58435486605102227"
127.0.0.1:6379> geopos china:city nanjing aomen
1) 1) "118.76741319894790649"
2) "32.04154071334338738"
2) 1) "113.54913264513015747"
2) "22.19874952821576386"
3、GEODIST 返回两个指定位置之间的距离
使用场景:
# 查看距离朋友有多远,即两人之间的距离
语法:
# 返回两个给定位置之间的距离
GEODIST key member1 member2 [unit]
规则:
# 如果两个位置之间的其中一个不存在, 那么命令返回空值。
# 指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
# 如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
案例:
# 北京距离南京的直线距离
127.0.0.1:6379> geodist china:city beijing nanjing
"899992.6369"
# 武汉距离重庆的直线距离,单位是km千米
127.0.0.1:6379> geodist china:city wuhan chongqing km
"759.1648"
4、georadius 以给定的经纬度为中心, 找出某一半径内的元素
使用场景:
# 附近的人 (第一步打开定位,第二步获得所有附近的人的地址,第三步通过半径来查询)
# 附近的50人(另指定人的数量)
语法:
# 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
GEORADIUS key longitude latitude radius m|km|ft|mi
[WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
规则:
# 范围可以使用以下其中一个单位:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
# 在给定以下可选项时, 命令会返回额外的信息:
- WITHDIST:在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致。
- WITHCOORD:将位置元素的经度和维度也一并返回。
- WITHHASH:以52位有符号整数的形式,返回位置元素经过原始geohash编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大。
# 使用 COUNT <count> 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。
# 命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
- ASC:根据中心的位置,按照从近到远的方式返回位置元素。
- DESC:根据中心的位置,按照从远到近的方式返回位置元素。
案例:
# 以 110 , 30 这个经纬度为中心,寻找方圆1000km内的城市
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
2) "aomen"
3) "xianggang"
4) "wuhan"
5) "nanjing"
# 以 110 , 30 这个经纬度为中心,寻找方圆500km内的城市
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
2) "wuhan"
# 显示距离中心500km内的城市的位置信息(经纬度)
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord
1) 1) "chongqing"
2) 1) "106.50495976209640503"
2) "29.53315530684997015"
2) 1) "wuhan"
2) 1) "114.29857403039932251"
2) "30.58435486605102227"
# 显示距离中心500km内的城市的直线距离
127.0.0.1:6379> georadius china:city 110 30 500 km withdist
1) 1) "chongqing"
2) "341.4052"
2) 1) "wuhan"
2) "417.8920"
# 显示距离中心500km内的城市的位置信息、直线距离、第一项
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord withdist count 1
1) 1) "chongqing"
2) "341.4052"
3) 1) "106.50495976209640503"
2) "29.53315530684997015"
# 显示距离中心500km内的城市,由远到近
127.0.0.1:6379> georadius china:city 110 30 1000 km desc
1) "xianggang"
2) "aomen"
3) "nanjing"
4) "wuhan"
5) "chongqing"
5、GEORADIUSBYMEMBER 根据位置元素找出位于指定范围内的元素
使用场景:
# 城市与城市之间的距离、导航距离等
语法:
# 这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点指定成员的位置被用作查询的中心。
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
案例:
# 根据位置元素,查询指定范围内的元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "nanjing"
2) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city wuhan 500 km
1) "wuhan"
2) "nanjing"
6、GEOHASH 返回一个或多个位置元素的 Geohash 表示
语法:
# 返回一个或多个位置元素的Geohash表示,该命令将返回11个字符的Geohash字符串
GEOHASH key member [member ...]
案例:
# 将位置元素的经纬度转换成11位的hash值显示
# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近
127.0.0.1:6379> GEOHASH china:city beijing nanjing
1) "wx4g0b7xre0"
2) "wtsqq8gz920"
7、GEO 底层的实现原理其实就是 Zset
可以使用Zset命令来操作Geospatial地理位置
# 查看地图中全部的元素
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "aomen"
3) "xianggang"
4) "xiamen"
5) "wuhan"
6) "nanjing"
7) "beijing"
# 移除指定元素
127.0.0.1:6379> zrem china:city aomen
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xianggang"
3) "xiamen"
4) "wuhan"
5) "nanjing"
6) "beijing"
7.2 Hyperloglog 基数统计
1、什么是基数?
一个数据集内不重复的元素,且可以接受误差。
2、简介
Redis 2.8.9 版本就更新了 Hyperloglog 数据结构,Redis Hyperloglog 基数统计的算法。
3、使用场景
网页的 UV 即页面访问量(例如,一个人访问一个网站多次,但是还是算作一个人)
传统的方式, set(无序不重复)保存用户的id,然后就可以统计 set 中的元素数量作为标准判断。这个方式如果保存大量的用户id,就会比较麻烦,占内存, 我们的目的是为了计数,而不是保存用户id。
现在的方式,可以使用 Hyperloglog进行存储,占用的内存是固定,2^64 不同元素的基数,只需要12KB内存。如果要从内存角度来比较的话Hyperloglog首选。可能会出现0.81%的 错误率,统计UV任务时,是可以忽略不计的。
4、案例
PFADD 创建元素 PFCOUNT 基数统计(不统计重复数据) PFMERGE 合并元素
# 创建第一组元素 my1
127.0.0.1:6379> PFADD my1 a b c d e f g h i j k
(integer) 1
# 统计 my1 元素的基数数量
127.0.0.1:6379> PFCOUNT my1
(integer) 11
# 创建第二组元素 my2
127.0.0.1:6379> PFADD my2 j k l m n a d e o p
(integer) 1
# 统计 my2 元素的基数数量
127.0.0.1:6379> PFCOUNT my2
(integer) 10
# 合并两组 my1 my2 => my3 并集
127.0.0.1:6379> PFMERGE my3 my1 my2
OK
# 统计 my3 元素的基数数量,即并集的数量
127.0.0.1:6379> PFCOUNT my3
(integer) 16
5、测试使用
如果允许容错,那么一定可以使用 Hyperloglog。
如果不允许容错,就使用 set 或者自己的数据类型即可。
7.3 Bitmap 位存储
1、使用场景
统计用户信息,活跃 或 不活跃、 登录 或 未登录、 打卡 或 未打卡。涉及 两个状态的,都可以使用Bitmap位存储。
Bitmap 位图也是一种数据结构,都是操作二进制位来进行记录,就只有 0 和 1 两个状态。
等价于 365 天 = 365 bit 1字节 = 8bit 46 个字节左右,即若记录某个学生的一年打卡情况,则只需耗费46个字节左右。
2、案例
# 使用Bitmap来记录周一到周日的打卡,例如,周一: 1 周二: 1 周三: 1 周四:0 ......
127.0.0.1:6379> setbit ding 0 1
(integer) 0
127.0.0.1:6379> setbit ding 1 1
(integer) 0
127.0.0.1:6379> setbit ding 2 1
(integer) 0
127.0.0.1:6379> setbit ding 3 0
(integer) 0
127.0.0.1:6379> setbit ding 4 1
(integer) 0
127.0.0.1:6379> setbit ding 5 1
(integer) 0
127.0.0.1:6379> setbit ding 6 0
(integer) 0
127.0.0.1:6379> setbit ding 7 0
(integer) 0
# 查看某一天是否有打卡,返回1则打卡,返回0则未打卡
127.0.0.1:6379> getbit ding 1
(integer) 1
127.0.0.1:6379> getbit ding 3
(integer) 0
# 统计操作,统计打卡的天数,即统计这周的打卡记录,就可以看到是否有全勤
127.0.0.1:6379> bitcount ding
(integer) 5
八.事务
8.1 Redis 事务本质
一组命令的集合。 一个事务中的所有命令都会被顺序化,在事务执行过程的中,会按照顺序执行。
例如,正常执行事务:
------ 队列 set set set 执行 ------
8.2 事务的特性
一次性、顺序性、排他性,执行一系列的命令。
一次性:开启事务后,会在一个队列中一次性执行。
顺序性:会在队列中按照入队的顺序执行。
排他性:事务执行过程中,不允许其它线程干扰。
注意:
1.Redis单条命令是保存原子性的,但是事务不保证原子性。
2.Redis事务没有隔离级别的概念
3.所有的命令在事务中,并没有直接被执行,只有发起执行命令Exec的时候才会执行。
8.3 redis的事务
redis的事务分为三个阶段:
开启事务(multi)
命令入队(输入其它命令)
执行事务(exec)
注意:每次事务执行exec后,再次使用事务时需要重新开启事务multi。
8.4 正常执行事务 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 k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
# 按一次性、顺序性、排他性特性,执行事务
127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
4) OK
注意:每次事务执行exec后,再次使用事务时需要重新开启事务multi。
8.5 放弃事务 DISCARD
# 开启事务
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> set k4 v4
QUEUED
# 一旦取消事务,事务队列中的命令都不会被执行
127.0.0.1:6379> DISCARD
OK
# 此时获取k4则没有值
127.0.0.1:6379> get k4
(nil)
注意:每次事务执行DISCARD后,再次使用事务时需要重新开启事务multi。
8.6 出现异常
1、编译型异常
# 编译型异常(代码有问题, 命令有错),事务中所有的命令都不会被执行。
# 开启事务
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> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
# 执行事务时报错
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 获取值,返回为null
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k4
(nil)
2、运行时异常
# 运行时异常(1/0),若事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常。
# 开启事务
127.0.0.1:6379> multi
OK
# 命令入队
127.0.0.1:6379> set k1 "v1"
QUEUED
# 正常命令入队,但出现语法性错误
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
# 执行事务,发生语法错误的会抛出异常,其它命令正常执行
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"
8.7 redis实现乐观锁
1、监控 Watch
悲观锁
很悲观,认为什么时候都会出问题,无论做什么都会加锁,非常影响性能。
乐观锁
很乐观,认为什么时候都不会出问题,所以不会加锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据。 性能相对好点。
原理:获取version,更新的时候比较version
2、Redis测监视测试
1). 正常执行成功
# 清空数据库
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> clear
# 设置值
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
# 监控money对象
127.0.0.1:6379> watch money
OK
# 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
# 开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
# 执行事务
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
2). 测试多线程修改值 , 使用watch可以当做redis的乐观锁操作
# 第一个客户端:开启监视money,开启事务,修改值
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
# 监视:使用watch可以当做redis的乐观锁操作
127.0.0.1:6379> watch money
OK
# 开启事务
127.0.0.1:6379> multi
OK
# 修改值
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
# 执行事务之前,另外一个线程修改了值,会导致事务执行失败,返回nil
127.0.0.1:6379> exec
(nil)
# 第二个客户端:修改监视的值
[root@iZuf67rdcsn46d295zbgomZ ~]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> set money 1000
OK
因此,实现乐观锁的原理是:
执行监视watch某个值时,即获取当前值;在执行事务exec时需要更新值,此时需要与之前获取的值做比较。
3). 若事务执行失败,则先解锁,再重新监视,即获取最新的值
# 若事务执行失败,先解锁,放弃监视
127.0.0.1:6379> unwatch
OK
# 再开启监视,即获取最新的值select version
127.0.0.1:6379> watch money
OK
# 开启事务
127.0.0.1:6379> multi
OK
# 操作值
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
# 执行事务,比对监视的值是否发生变化,若没有变化则可以执行成功,若变化了则执行失败
127.0.0.1:6379> exec
1) (integer) 980
2) (integer) 20
3、redis乐观锁实现机制
我们知道大多数是基于**数据版本(version)**的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个**version字段**来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。
Redis中可以使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了==**exec,discard,unwatch命令都会清除连接中的所有监视**==。
4、总结
==Redis中的事务(transaction)是一组命令的集合。==事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。
Redis的事务是下面4个命令来实现 :
1).multi 开启Redis的事务,置客户端为事务态。
2).exec 提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
3).discard 取消事务,置客户端为非事务态。
4).watch 监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。
5).unwatch 取消监视
九.Jedis
9.1 什么是Jedis ?
它是 Redis 官方推荐的 java连接开发工具, 使用Java 操作Redis 中间件。如果你要使用Java操作Redis,那么一定要对Jedis 十分的熟悉。
9.2 使用Jedis
1 、Maven导入对应的依赖
<dependencies>
<!-- 导入jedis的包 -->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- json选择1:导入fastjson的包 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- json选择2:导入jackson的包 -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
2 、编码测试
连接数据库
操作命令
断开连接
import redis.clients.jedis.Jedis;
public class testPing {
//开启本地redis服务器,测试是否ping通
public static void main(String[] args) {
// 1、 new Jedis 对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2、测试ping方法,若输出pong,代表成功
System.out.println(jedis.ping());
}
}
/*
* 此处若不是连接本地redis,而是连接服务器则可直接输入服务器IP(公有)地址。
* Jedis jedis = new Jedis("服务器IP(公有)", 6379);
*
* 若阿里云连接不上,则需要:
* 第一步:先去阿里云服务器添加安全组规则放行Jedis对应的端口
firewall-cmd --permanent --zone=public --add-port=6379/tcp
firewall-cmd --reload
firewall-cmd --zone=public --query-port=6379/tcp
* 第二步:通过vim redis.conf命令进入,将# bind 127.0.0.1注释掉,protected-mode no关闭保护模式
* 第三步:redis重启
*/
3、常用的API
import redis.clients.jedis.Jedis;
import java.util.Set;
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("新增键值对:"+jedis.set("username", "zhangsan"));
System.out.println("新增键值对:"+jedis.set("password", "123"));
System.out.println("系统中所有键如下:");
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("随机返回键空间的一个:"+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
新增键值对:OK
新增键值对:OK
系统中所有键如下:
[password, username]
删除键password:1
判断password键是否存在:false
查看键username所存储的值的类型:string
随机返回键空间的一个:username
重命名key:OK
取出改名后的name键的值:zhangsan
按索引切换数据库:OK
删除当前选择数据库中的所有key:OK
返回当前数据库中的key的数目:0
删除所有数据库中的所有key:OK
String
import redis.clients.jedis.Jedis;
/**
* 测试字符串命令操作
*/
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("k1", "v1"));
System.out.println(jedis.set("k2", "v2"));
System.out.println(jedis.set("k3", "v3"));
System.out.println("删除键k2:"+jedis.del("k2"));
System.out.println("获取键k2:"+jedis.get("k2"));
System.out.println("修改k1:"+jedis.set("k1", "v111"));
System.out.println("获取k1的值:"+jedis.get("k1"));
System.out.println("在k3的value值后追加:"+jedis.append("k3", "end"));
System.out.println("获取k3的值:"+jedis.get("k3"));
System.out.println("========增加多个键值对========");
System.out.println(jedis.mset("key1", "aaa", "key2", "bbb", "key3", "ccc"));
System.out.println(jedis.mget("key1", "key2", "key3"));
System.out.println(jedis.mget("key1", "key2", "key3", "key4"));
System.out.println(jedis.del("key1", "key2"));
System.out.println(jedis.mget("key1", "key2", "key3"));
jedis.flushDB();
System.out.println("========新增键值对,不存在再插入,存在则插入不成功=========");
System.out.println(jedis.setnx("k1", "v1"));
System.out.println(jedis.setnx("k2", "v2"));
System.out.println(jedis.setnx("k2", "v2-new"));
System.out.println(jedis.get("k1"));
System.out.println(jedis.get("k2"));
System.out.println("=========新增键值对并设置有效时间==========");
System.out.println(jedis.setex("k3", 2, "v3"));
System.out.println(jedis.get("k3"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(jedis.get("k3"));
System.out.println("==========获取原值,并更新为新值==========");
System.out.println(jedis.getSet("k2", "v2getSet"));
System.out.println(jedis.get("k2"));
System.out.println("获得k2的值的字符串:"+jedis.getrange("k2", 2, 4));
}
}
// 输出结果
========增加数据=========
OK
OK
OK
删除键k2:1
获取键k2:null
修改k1:OK
获取k1的值:v111
在k3后面加入值:5
获取k3的值:v3end
========增加多个键值对========
OK
[aaa, bbb, ccc]
[aaa, bbb, ccc, null]
2
[null, null, ccc]
========新增键值对,防止覆盖原先值==========
1
1
0
v1
v2
=========新增键值对并设置有效时间==========
OK
v3
null
==========获取原值,更新为新值==========
v2
v2getSet
获得k2的值的字符串:get
List
/**
* 测试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","LinkedList","Vector","HashMap","HashSet");
jedis.lpush("collections", "TreeSet");
jedis.lpush("collections", "TreeMap");
jedis.lpush("collections", "HashMap");
//第一个参数:集合名 第二个参数:起始位置下标 第二个参数:终止位置下标,-1代表截取到最后
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("collections的0-3区间的元素:"+jedis.lrange("collections", 0, 3));
System.out.println("=========lrem 删除指定元素并指定个数==========");
//第一个参数:删除指定列表 第二个参数:删除的个数(有重复时),后添加进去的值先被删,类似于出栈
System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2, "HashMap"));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("=========ltrim 截取指定区间的元素==========");
System.out.println("截取下标0-3区间之间的元素:"+jedis.ltrim("collections", 0, 3));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("=========lpop 出栈(左端)==========");
System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("=========rpush 添加(右端)==========");
System.out.println("collections列表添加元素(右端):"+jedis.rpush("collections","xxx"));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("=========rpop 出栈(右端)==========");
System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("=========lpush 添加(左端)==========");
System.out.println("collections列表添加元素(左端):"+jedis.lpush("collections","xxx"));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("=========lset 修改指定下标的元素=========");
System.out.println("collections修改指定下标的元素:"+jedis.lset("collections", 1, "AAA"));
System.out.println("collections的所有元素:"+jedis.lrange("collections", 0, -1));
System.out.println("========================");
System.out.println("collections的长度:"+jedis.llen("collections"));
System.out.println("collections获取指定下标的元素:"+jedis.lindex("collections", 2));
System.out.println("========================");
jedis.flushDB();
jedis.rpush("sortList","3","6","4","8","7");
System.out.println("sortList排序前:"+jedis.lrange("sortList", 0, -1));
System.out.println(jedis.sort("sortList"));
System.out.println("sortList排序后:"+jedis.lrange("sortList", 0, -1));
}
}
// 输出结果
=========增加一个list=========
collections的所有元素:[HashMap, TreeMap, TreeSet, HashSet, HashMap, Vector, LinkedList, ArrayList]
collections的0-3区间的元素:[HashMap, TreeMap, TreeSet, HashSet]
=========lrem 删除指定元素并指定个数==========
删除指定元素个数:2
collections的所有元素:[TreeMap, TreeSet, HashSet, Vector, LinkedList, ArrayList]
=========ltrim 截取指定区间的元素==========
截取下标0-3区间之间的元素:OK
collections的所有元素:[TreeMap, TreeSet, HashSet, Vector]
=========lpop 出栈(左端)==========
collections列表出栈(左端):TreeMap
collections的所有元素:[TreeSet, HashSet, Vector]
=========rpush 添加(右端)==========
collections列表添加元素(右端):4
collections的所有元素:[TreeSet, HashSet, Vector, xxx]
=========rpop 出栈(右端)==========
collections列表出栈(右端):xxx
collections的所有元素:[TreeSet, HashSet, Vector]
=========lpush 添加(左端)==========
collections列表添加元素(左端):4
collections的所有元素:[xxx, TreeSet, HashSet, Vector]
=========lset 修改指定下标的元素=========
collections修改指定下标的元素:OK
collections的所有元素:[xxx, AAA, HashSet, Vector]
========================
collections的长度:4
collections获取指定下标的元素:HashSet
========================
sortList排序前:[3, 6, 4, 8, 7]
[3, 4, 6, 7, 8]
sortList排序后:[3, 6, 4, 8, 7]
Set
import redis.clients.jedis.Jedis;
/**
* 测试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("myset", "s1","s2","s5","s0","s8","s3","s4"));
System.out.println(jedis.sadd("myset", "s6"));
System.out.println(jedis.sadd("myset", "s6"));
System.out.println("查看set集合中所有元素:"+jedis.smembers("myset"));
System.out.println("===========删除元素============");
System.out.println("删除一个元素:"+jedis.srem("myset", "s0"));
System.out.println("查看set集合中所有元素:"+jedis.smembers("myset"));
System.out.println("删除两个元素:"+jedis.srem("myset", "s3","s5"));
System.out.println("查看set集合中所有元素:"+jedis.smembers("myset"));
System.out.println("随机移除元素:"+jedis.spop("myset"));
System.out.println("查看set集合中所有元素:"+jedis.smembers("myset"));
System.out.println("=====================");
System.out.println("集合set中元素的个数:"+jedis.scard("myset"));
System.out.println("查看指定元素是否在myset中:"+jedis.sismember("myset", "s8"));
System.out.println("查看指定元素是否在myset中:"+jedis.sismember("myset", "s5"));
System.out.println("=====================");
System.out.println(jedis.sadd("myset1", "a","b","d","e","c","f"));
System.out.println(jedis.sadd("myset2", "c","f","h","d","k"));
System.out.println("将myset1集合中指定元素移入myset3:"+jedis.smove("myset1", "myset3", "a"));
System.out.println("将myset2集合中指定元素移入myset3:"+jedis.smove("myset2", "myset3", "f"));
System.out.println("查看myset1集合中所有元素:"+jedis.smembers("myset1"));
System.out.println("查看myset2集合中所有元素:"+jedis.smembers("myset2"));
System.out.println("查看myset3集合中所有元素:"+jedis.smembers("myset3"));
System.out.println("==========集合运算===========");
System.out.println("myset1和myset2的交集:"+jedis.sinter("myset1","myset2"));
System.out.println("myset1和myset2的并集:"+jedis.sunion("myset1","myset2"));
System.out.println("myset1和myset2的差集:"+jedis.sdiff("myset1","myset2"));
System.out.println("===========将并集保存到新集合中============");
jedis.sinterstore("newSet", "myset1","myset2");
System.out.println("查看newSet集合中所有元素:"+jedis.smembers("newSet"));
}
}
// 输出结果
=========向集合中添加元素(不重复)=========
7
1
0
查看set集合中所有元素:[s5, s3, s4, s8, s2, s6, s1, s0]
===========删除元素============
删除一个元素:1
查看set集合中所有元素:[s4, s8, s2, s6, s1, s5, s3]
删除两个元素:2
查看set集合中所有元素:[s6, s1, s2, s8, s4]
随机移除元素:s2
查看set集合中所有元素:[s1, s6, s8, s4]
=====================
集合set中元素的个数:4
查看指定元素是否在myset中:true
查看指定元素是否在myset中:false
=====================
6
5
将myset1集合中指定元素移入myset3:1
将myset2集合中指定元素移入myset3:1
查看myset1集合中所有元素:[b, c, e, f, d]
查看myset2集合中所有元素:[d, h, c, k]
查看myset3集合中所有元素:[f, a]
==========集合运算===========
myset1和myset2的交集:[d, c]
myset1和myset2的并集:[c, e, b, h, f, d, k]
myset1和myset2的差集:[e, f, b]
===========将并集保存到新集合中============
查看newSet集合中所有元素:[c, d]
Hash
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
/**
* 测试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<String,String>();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
map.put("k4", "v4");
//添加名称为hash的键,将map集合作为值添加
jedis.hmset("hash", map);
//添加元素
jedis.hset("hash", "k5","v5");
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));
System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));
System.out.println("将k6保存的值加上一个整数,如果k6不存在则添加k6:"
+jedis.hincrBy("hash", "k6", 100));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("将k6保存的值加上一个整数,如果k6不存在则添加k6:"
+jedis.hincrBy("hash", "k6", 300));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("删除一个或多个键值对:"+jedis.hdel("hash", "k2"));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
System.out.println("判断是否存在指定的键:"+jedis.hexists("hash", "k2"));
System.out.println("判断是否存在指定的键:"+jedis.hexists("hash", "k3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash", "k3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash", "k3","k4"));
}
}
// 输出结果
散列hash的所有键值对为:{k3=v3, k4=v4, k5=v5, k1=v1, k2=v2}
散列hash的所有键为:[k3, k4, k5, k1, k2]
散列hash的所有值为:[v1, v3, v2, v4, v5]
将k6保存的值加上一个整数,如果k6不存在则添加k6:100
散列hash的所有键值对为:{k3=v3, k4=v4, k5=v5, k6=100, k1=v1, k2=v2}
将k6保存的值加上一个整数,如果k6不存在则添加k6:400
散列hash的所有键值对为:{k3=v3, k4=v4, k5=v5, k6=400, k1=v1, k2=v2}
删除一个或多个键值对:1
散列hash的所有键值对为:{k3=v3, k4=v4, k5=v5, k6=400, k1=v1}
散列hash中键值对的个数:5
判断是否存在指定的键:false
判断是否存在指定的键:true
获取hash中的值:[v3]
获取hash中的值:[v3, v4]
Zset
import redis.clients.jedis.Jedis;
/**
* 测试Zset命令操作
*/
public class TestZset {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
jedis.zadd("myzset", 1, "one");
jedis.zadd("myzset", 2, "two");
jedis.zadd("myzset", 3, "three");
System.out.println(jedis.zrange("myzset", 0, -1));
System.out.println(jedis.zrangeWithScores("myzset", 0, -1));
jedis.flushDB();
jedis.zadd("salary", 2000, "lisi");
jedis.zadd("salary", 3000, "zs");
jedis.zadd("salary", 1500, "wanger");
System.out.println("按照score字段值排序:"+jedis.zrangeByScore("salary", 1000, 3500));
System.out.println("按照score字段值逆序:"+jedis.zrevrangeByScore("salary", 3500, 1000));
System.out.println("有序集合中元素的个数:"+jedis.zcard("salary"));
System.out.println("移除:"+jedis.zrem("salary", "lisi"));
System.out.println("有序集合中元素的个数:"+jedis.zcard("salary"));
System.out.println("集合中的所有元素:"+jedis.zrange("salary", 0, -1));
System.out.println("获取集合中指定区间的元素个数:"+jedis.zcount("salary", 1500, 2500));
}
}
// 输出结果
[one, two, three]
[[one,1.0], [two,2.0], [three,3.0]]
按照score字段值排序:[wanger, lisi, zs]
按照score字段值逆序:[zs, lisi, wanger]
有序集合中元素的个数:3
移除:1
有序集合中元素的个数:2
集合中的所有元素:[wanger, zs]
获取集合中指定区间的元素个数:1
4、事务
正常执行
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* 测试Jedis进行事务管理
*/
public class TestTrans {
public static void main(String[] args) {
//创建Jedis对象
Jedis jedis = new Jedis("106.14.159.179", 6379);
//测试连接
System.out.println(jedis.ping());
//创建json对象
JSONObject obj = new JSONObject();
//给json对象存储值
obj.put("hello", "world");
obj.put("name", "zhangsan");
obj.put("age", "10");
String str = obj.toJSONString();
//开启事务
Transaction multi = jedis.multi();
try {
//命令入队
multi.set("user1", str);
multi.set("user2", str);
//执行事务
multi.exec();
} catch (Exception e) {
//放弃事务
multi.discard();
e.printStackTrace();
} finally {
//输出信息
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
//关闭连接
jedis.close();
}
}
}
// 运行结果
PONG
{"name":"zhangsan","hello":"world","age":"10"}
{"name":"zhangsan","hello":"world","age":"10"}
出现异常
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* 测试Jedis进行事务管理
*/
public class TestTrans {
public static void main(String[] args) {
//创建Jedis对象
Jedis jedis = new Jedis("106.14.159.179", 6379);
//测试连接
System.out.println(jedis.ping());
jedis.flushDB();
//创建json对象
JSONObject obj = new JSONObject();
//给json对象存储值
obj.put("hello", "world");
obj.put("name", "zhangsan");
obj.put("age", "10");
String str = obj.toJSONString();
//开启事务
Transaction multi = jedis.multi();
try {
//命令入队
multi.set("user1", str);
multi.set("user2", str);
//异常代码块,抛出异常,事务执行失败
int i = 10/0;
//执行事务
multi.exec();
} catch (Exception e) {
//放弃事务
multi.discard();
e.printStackTrace();
} finally {
//输出信息
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
//关闭连接
jedis.close();
}
}
}
// 运行结果
PONG
java.lang.ArithmeticException: / by zero
at com.igeek.test.TestTrans.main(TestTrans.java:37)
null
null