菜鸟的redis学习总结

说明

本文主要整理了非关系型数据库redis的相关知识,本文会持续更新,不断地扩充

本文仅为记录学习轨迹,如有侵权,联系删除

一、Nosql和Mysql

mysql作为目前使用人数比较多的一种数据库属于关系型数据库,有关系型数据库就有非关系型数据库,也就是Nosql(Not Only Sql),在所有的非关系型数据库中,用的最多的就是redis

两者的区别
像平时我们所用到的MSSQL Server、Mysql等是关系型数据库,它们是建立在关系模型基础上的数据库,依靠表、字段等关系模型,结合集合代数等数学方法来处理数据。而非关系型数据库,Nosql,Not only sql,是以Key-Value形式进行存储的,用来解决文档方面数据的存储。

也就是说最直观的区别就是两者之前的数据存储方式不同,关系型数据库存储方式是用的表结构,通过表的一行一行的方式来存储数据,而像redis这种非关系型数据库,存储方式就比较简单粗暴,直接通过键值对的方式存储,它没有行、列的概念,集合就相当于“表”,文档就相当于“行”。下面给出一张在网上看到的一张图
在这里插入图片描述
一句话总结就是:MySQL是一个基于表格设计的关系数据库,而NoSQL本质上是非关系型的基于文档的设计

两者优缺点比较
(1)MySQL中创建数据库之前需要详细的数据库模型,而在NoSQL数据库类型的情况下不需要详细的建模。
(2)MySQL的严格模式限制并不容易扩展,而NoSQL可以通过动态模式特性轻松扩展。
(3)MySQL提供了大量的报告工具,可以帮助应用程序有效,而NoSQL数据库缺少用于分析和性能测试的报告工具。
(4)MySQL是一个关系数据库,其设计约束灵活性较低;而NoSQL本质上是非关系型的,与MySQL相比,它提供了更灵活的设计。
(5)MySQL中使用的标准语言是SQL;而NoSQL中缺乏标准的查询语言。
(6)Mysql在进行CURD操作时,会用到I/O操作,读写效率较慢,而redis数据是存储在缓存中的,读写效率自然比mysql高,尤其是在高并发的情况下。
在这里插入图片描述
在上面这张图中,个人觉得最重要的一点就是高并发时数据的读取,因为像redis数据可以存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库,但是保存时间有限。此外就是redis虽然是存储在缓存中,但也是可以做数据持久化的。

两者的应用场景
这两种数据库的优缺点就决定了它们的使用场景,mysql的使用场景就不多说了,可以说现在的开发基本离不开这种关系型数据,但是考虑到mysql的读写效率,如果是数据量较少的情况下,mysql够用了,但如果涉及到一些大数据的分析处理等,就必须借助redis的力量,可以说它们两个是相辅相成的,关系型数据库为主,非关系型数据库为辅。下面给出redis的常见的使用常景

使用场景 说明
缓存 缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。
排行榜 很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。
计数器 什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。
分布式会话 集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。
分布式锁 在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。
社交网络 点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。
最新列表 Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。
消息系统 消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

上面的这些redis使用场景是整理的网络上的,有些自己也不是完全懂,但是自己想了一下,redis数据既然存在内存中,所以用来做缓存的话是最佳的选择,然后像排行榜,点赞关注等功能,则是因为redis的数据存储类型,所以用来做这些相关的功能会很方便,同时效率也高,但是有一个场景是不建议用redis的,像是经常改动的数据,这种频繁变动的数据就不适合用redis,弄不好可能造成数据的丢失。

总结如下
(mysql)关系型数据库适合存储结构化数据,如用户的帐号、地址等
(1)这些数据通常需要做结构化查询,比如join,这时候,关系型数据库就要胜出一筹
(2)这些数据的规模、增长的速度通常是可以预期的
(3)保证数据的事务性、一致性要求。

(redis)NoSQL适合存储非结构化数据,如发微博、文章、评论
(1)这些数据通常用于模糊处理,如全文搜索、机器学习
(2)这些数据是海量的,而且增长的速度是难以预期的,
(3)根据数据的特点,NoSQL数据库通常具有无限(至少接近)伸缩性
(4)按key获取数据效率很高,但是对join或其他结构化查询的支持就比较差

目前许多大型互联网项目都会选用MySQL(或任何关系型数据库) + NoSQL的组合方案。

二、Nosql常见类型及比较

这里整理了一下网上常见的4种Nosql类型:列式、文档、图形和内存键值。
在这里插入图片描述
对于这4种类型的Nosql,之前在网上看到一张图
在这里插入图片描述
redis就是属于内存键值的Nosql类型。

三、简介

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets)有序集合(sorted sets)等类型,是当下热门的 NoSQL 技术之一!

Redis 能干嘛
(1)内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
(2)效率高,可以用于高速缓存
(3)发布订阅系统
(4)地图信息分析 5、计时器、计数器(浏览量!)
(5)…

特性
(1)多样的数据类型
(2)持久化
(3)集群
(4)事务
(5)…

五大基本数据类型
(1)String
(2)List
(3)Set
(4)Hash
(5)Zset

三种特殊数据类型
(1)geo
(2)hyperloglog
(3)bitmap

四、入门系列

(1)性能测试

按照官方的介绍,redis的性能是非常高的,每秒可以处理超过 10 万次读写操作。对与redis的性能,我们可以用它自带的工具redis-benchmark进行性能测试,测试的方式也简单,具体如下

redis-benchmark 相关命令参数

参数如下表
在这里插入图片描述

测试案例(测试100个并发数,每个请求数10万)
命令:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

用进入redis的安装目录,可以看redis-benchmark测试工具
在这里插入图片描述
先开启redis的服务,用命令提示符的方式进入该目录,并输入上面的测试的命令
在这里插入图片描述

(2)String类型

基本的使用

redis 127.0.0.1:6379> keys *     #获取所有的key
(empty list or set)
redis 127.0.0.1:6379> set name Tony  #设置值
OK
redis 127.0.0.1:6379> keys *
1) "name"
redis 127.0.0.1:6379> get name  #获取值
"Tony"
redis 127.0.0.1:6379> exists name  #判断某一个key是否存在,存在就返回1,否则就返回0
(integer) 1
redis 127.0.0.1:6379> exists name1
(integer) 0
redis 127.0.0.1:6379> append name " is boy" #往一个key追加值,追加字符串
(integer) 11
redis 127.0.0.1:6379> get name   
"Tony is boy"
redis 127.0.0.1:6379> strlen name  #获取一个key对应值的字符串长度
(integer) 11
redis 127.0.0.1:6379> get name
"Tony is boy"
redis 127.0.0.1:6379> getrange name 0 2  #截取key对应的值范围,下标范围[0,2]
"Ton"
redis 127.0.0.1:6379> getrange name 0 -1  #截取key对应的值范围,下标范围[0,-1]-1表示截取到最后一个字符串
"Tony is boy"
redis 127.0.0.1:6379> setrange name 8 student   #替换指定位置开始的字符串! 
(integer) 15
redis 127.0.0.1:6379> get name
"Tony is student"
redis 127.0.0.1:6379> setex k1 10  v1  #设置k1 的值为 v1,10秒后过期 
OK
redis 127.0.0.1:6379> ttl k1  #查看key还有多久过期
(integer) 4
redis 127.0.0.1:6379> get k1
"v1"
redis 127.0.0.1:6379> get k1
(nil)
redis 127.0.0.1:6379> setnx mykey "redis"   #如果mykey 不存在,则创建mykey 
(integer) 1
redis 127.0.0.1:6379> keys *
1) "mykey"
2) "username"
redis 127.0.0.1:6379> setnx mykey "mysql"  #如果mykey存在则创建失败
(integer) 0
redis 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  #批量创建键值对
OK
redis 127.0.0.1:6379> keys *
1) "mykey"
2) "username"
3) "k2"
4) "k3"
5) "k1"
redis 127.0.0.1:6379> mget k1 k2 j3  #批量获取键值对
1) "v1"
2) "v2"
3) (nil)
redis 127.0.0.1:6379> msetnx k1 v1 k4 v4  # msetnx 是一个原子性的操作,要么一起成功,要么一起 失败! 
(integer) 0

注意区分创建值的其中两种方式,set和setnx
(1)set创建值时,如果key不存在就创建值,如果存在则会覆盖掉之前的值
(2)setnx创建值时,如果key不存在就会创建值,如果存在就创建失败

对象操作
在用java进行开发的时候,对象是接触得最多的,对象经常用来封装数据,然后存储,在redis里面存储对象,有两种方式,一种是把对象转成json,再用String类型存储;另一种是用user:{id}:{filed} 的方式存储,这是redis支持的语法

假设要存储一个对象user

public class user{
	private Integer id;
	private String username;
	private Integer age;
	private String password;
}

jsond的方式存储

redis 127.0.0.1:6379> set user:2 {username:zs,age:19,password:123}
OK
redis 127.0.0.1:6379> get user:2
"{username:zs,age:19,password:123}"

user:{id}:{filed} 的方式存储
存储:mset user : {id} : username 值 user : {id} : age 值 user : {id} : password 值
获取:mget user : {id} : username user : {id} :age user : {id} : password

redis 127.0.0.1:6379> mset user:1:username Mike user:1:age 18 user:1:password 123
OK
redis 127.0.0.1:6379> mget user:1:username  user:1:age  user:1:password
1) "Mike"
2) "18"
3) "123"

高级使用,统计浏览量

redis 127.0.0.1:6379> set views 0   #初始化浏览量为0
OK
redis 127.0.0.1:6379> keys *
1) "name"
2) "views"
redis 127.0.0.1:6379> incr views  #浏览量自增1
(integer) 1
redis 127.0.0.1:6379> incr views
(integer) 2
redis 127.0.0.1:6379> get views
"2"
redis 127.0.0.1:6379> incr views
(integer) 3
redis 127.0.0.1:6379> get views
"3"
redis 127.0.0.1:6379> decr views  #浏览量自减1
(integer) 2
redis 127.0.0.1:6379> get views
"2"
redis 127.0.0.1:6379> incrby views 10  #浏览量增加10
(integer) 12
redis 127.0.0.1:6379> get views
"12"
redis 127.0.0.1:6379> decrby views 5 #浏览量减少5
(integer) 7
redis 127.0.0.1:6379> get views
"7"
redis 127.0.0.1:6379>

(3)List类型

在redis里面,list类型可以呗当成 栈、队列、阻塞队列等结构来使用

基本使用

redis 127.0.0.1:6379> lpush list1 one    # 将一个值或者多个值,插入到列表头部 (左)
(integer) 1
redis 127.0.0.1:6379> lpush list1 two
(integer) 2
redis 127.0.0.1:6379> lpush list1 three
(integer) 3
redis 127.0.0.1:6379> lrange list1 0 -1   #通过区间的方式获取list1的值
1) "three"
2) "two"
3) "one"
redis 127.0.0.1:6379> rpush list2 one   # 将一个值或者多个值,插入到列表位部 (右) 
(integer) 1
redis 127.0.0.1:6379> rpush list2 two
(integer) 2
redis 127.0.0.1:6379> rpush list2 three
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> lpop list2  #移除最左边的元素
"one"
redis 127.0.0.1:6379> rpop list2 #移除最右边的元素
"three"
redis 127.0.0.1:6379> lrange list2 0 -1
1) "two"
redis 127.0.0.1:6379> lpush list2 one
(integer) 2
redis 127.0.0.1:6379> rpush list2 three
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> lindex list2 0
"one"
redis 127.0.0.1:6379> lindex list2 2
"three"
redis 127.0.0.1:6379> llen list2   #获取列表的元素个数
(integer) 3
redis 127.0.0.1:6379> rpush list2 three
(integer) 4
redis 127.0.0.1:6379> rpush list2 three
(integer) 5
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "three"
5) "three"
redis 127.0.0.1:6379> lrem list2 3 three  #移除3个值等于three的元素
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
redis 127.0.0.1:6379> rpush list2 three
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> rpush list2 four
(integer) 4
redis 127.0.0.1:6379> rpush list2 five
(integer) 5
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
redis 127.0.0.1:6379> ltrim list2 1 3  # 通过下标截取指定的长度,这个list已经被改变了,截断了 只剩下截取的元素! 
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1) "two"
2) "three"
3) "four"
redis 127.0.0.1:6379> lpush list2 one
(integer) 4
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
redis 127.0.0.1:6379> rpoplpush list2 list3   # 移除列表的后一个元素,将他移动到新的列表中!

"four"
redis 127.0.0.1:6379> lrange list3 0 -1
1) "four"
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> exists list2    #判断列表是否存在
(integer) 1
redis 127.0.0.1:6379> lset list2 1 number2   #将列表中指定下标的值替换为另外一个值,更新操作
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "number2"
3) "three"
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "number2"
3) "three"
redis 127.0.0.1:6379> lset list2 1 two
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> linsert list2 before three number3 # 将某个具体的value插入到列把你中某个元素的前面

(integer) 4
redis 127.0.0.1:6379> linsert list2 after three four    # 将某个具体的value插入到列把你中某个元素的后面!

(integer) 5
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "number3"
4) "three"
5) "four"

注意:list类型的值是可以重复的
小结
(1)他实际上是一个链表,before Node after , left,right 都可以插入值
(2)如果key 不存在,创建新的链表
(3)如果key存在,新增内容
(4)如果移除了所有值,空链表,也代表不存在!
(5)在两边插入或者改动值,效率高! 中间元素,相对来说效率会低一点~

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

(4)Set集合

#######################################################################
127.0.0.1:6379> sadd myset "hello"   # set集合中添加匀速 
(integer) 
1 127.0.0.1:6379> sadd myset "kuangshen" 
(integer) 1 
127.0.0.1:6379> sadd myset "lovekuangshen" 
(integer) 
1 127.0.0.1:6379> SMEMBERS myset     # 查看指定set的所有值 
1) "hello" 
2) "lovekuangshen" 
3) "kuangshen" 
127.0.0.1:6379> SISMEMBER myset hello    # 判断某一个值是不是在set集合中! 
(integer) 1 
127.0.0.1:6379> SISMEMBER myset world 
(integer) 0
#######################################################################
127.0.0.1:6379> scard myset  # 获取set集合中的内容元素个数! 
(integer) 4
#######################################################################
127.0.0.1:6379> srem myset hello  # 移除set集合中的指定元素 
(integer) 
1 127.0.0.1:6379> scard myset 
(integer) 3 
127.0.0.1:6379> SMEMBERS myset 
1) "lovekuangshen2" 
2) "lovekuangshen" 
3) "kuangshen"
#######################################################################
# set 无序不重复集合。抽随机!
127.0.0.1:6379> SMEMBERS myset 
1) "lovekuangshen2" 
2) "lovekuangshen" 
3) "kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset  # 随机抽选出一个元素 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 2  # 随机抽选出指定个数的元素 
1) "lovekuangshen" 
2) "lovekuangshen2" 
127.0.0.1:6379> SRANDMEMBER myset 2 
1) "lovekuangshen" 
2) "lovekuangshen2" 
127.0.0.1:6379> SRANDMEMBER myset      # 随机抽选出一个元素 
"lovekuangshen2"
#######################################################################
# 删除定的key,随机删除key!
127.0.0.1:6379> SMEMBERS myset 
1) "lovekuangshen2" 
2) "lovekuangshen" 
3) "kuangshen" 
127.0.0.1:6379> spop myset  # 随机删除一些set集合中的元素! 
"lovekuangshen2" 
127.0.0.1:6379> spop myset 
"lovekuangshen" 
127.0.0.1:6379> SMEMBERS myset 
1) "kuangshen"
#######################################################################
# 将一个指定的值,移动到另外一个set集合! 
127.0.0.1:6379> sadd myset "hello" 
(integer) 1 
127.0.0.1:6379> sadd myset "world" 
(integer) 1 
127.0.0.1:6379> sadd myset "kuangshen" 
(integer) 1 
127.0.0.1:6379> sadd myset2 "set2" 
(integer) 1 
127.0.0.1:6379> smove myset myset2 "kuangshen" # 将一个指定的值,移动到另外一个set集 合! 
(integer) 1 
127.0.0.1:6379> SMEMBERS myset 
1) "world" 
2) "hello" 
127.0.0.1:6379> SMEMBERS myset2 
1) "kuangshen" 
2) "set2"

高级应用,求共同好友
微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!
共同关注,共同爱好,二度好友,推荐好友!(六度分割理论)

redis 127.0.0.1:6379> smembers k1
1) "a"
2) "d"
3) "b"
4) "c"
redis 127.0.0.1:6379> smembers k2
1) "f"
2) "d"
3) "e"
4) "c"
redis 127.0.0.1:6379> sdiff k1 k2    # 差集 
1) "a"
2) "b"
redis 127.0.0.1:6379> sinter k1 k2   # 交集   共同好友就可以这样实现 
1) "d"
2) "c"
redis 127.0.0.1:6379> sunion k1 k2  # 并集 
1) "a"
2) "f"
3) "b"
4) "c"
5) "e"
6) "d"

(5)Hash类型

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

基本应用

########################################################################## 
127.0.0.1:6379> hset myhash field1 kuangshen  # set一个具体 key-vlaue 
(integer) 1 
127.0.0.1:6379> hget myhash field1  # 获取一个字段值 
"kuangshen" 
127.0.0.1:6379> hmset myhash field1 hello field2 world   # set多个 key-vlaue 
OK 
127.0.0.1:6379> hmget myhash field1 field2   # 获取多个字段值 
1) "hello" 
2) "world" 
127.0.0.1:6379> hgetall myhash   # 获取全部的数据, 
1) "field1" 
2) "hello" 
3) "field2" 
4) "world" 
127.0.0.1:6379> hdel myhash field1  # 删除hash指定key字段!对应的value值也就消失了! 
(integer) 1 127.0.0.1:6379> hgetall myhash 
1) "field2" 
2) "world" #######################################################################
### hlen
127.0.0.1:6379> hmset myhash field1 hello field2 world 
OK 
127.0.0.1:6379> HGETALL myhash 
1) "field2" 
2) "world" 
3) "field1" 
4) "hello" 
127.0.0.1:6379> hlen myhash  # 获取hash表的字段数量! 
(integer) 2
#######################################################################
### 
127.0.0.1:6379> HEXISTS myhash field1  # 判断hash中指定字段是否存在!
 (integer) 1
127.0.0.1:6379> HEXISTS myhash field3 
(integer) 0
#######################################################################
### # 只获得所有field # 只获得所有value 
127.0.0.1:6379> hkeys myhash  # 只获得所有field 
1) "field2" 
2) "field1"
127.0.0.1:6379> hvals myhash  # 只获得所有value 
1) "world" 
2) "hello" #######################################################################
### incr   decr
127.0.0.1:6379> hset myhash field3 5    #指定增量! 
(integer) 1 
127.0.0.1:6379> HINCRBY myhash field3 1 
(integer) 6 
127.0.0.1:6379> HINCRBY myhash field3 -1 
(integer) 5 
127.0.0.1:6379> hsetnx myhash field4 hello  # 如果不存在则可以设置 
(integer) 1 
127.0.0.1:6379> hsetnx myhash field4 world  # 如果存在则不能设置 
(integer) 0

(6)Zset有序集合

在set的基础上,增加了一个值score,在做排序等操作时需要用到这个score值:zset k1 score1 v1

基本操作

redis 127.0.0.1:6379> zadd salary 8000 xm  #添加用户xm,工资8000
(integer) 1
redis 127.0.0.1:6379> zadd salary 9000 xh
(integer) 1
redis 127.0.0.1:6379> zadd salary 10000 zs
(integer) 1
redis 127.0.0.1:6379> zadd salary 8800 ls
(integer) 1
redis 127.0.0.1:6379> zrangebyscore salary -inf +inf  # 显示全部的用户 从小到大! 
1) "xm"
2) "ls"
3) "xh"
4) "zs"
redis 127.0.0.1:6379> zrevrange salary 0 -1   # 显示全部的用户 从大到小! 
1) "zs"
2) "xh"
3) "ls"
4) "xm"
redis 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores  # 显示全部的用户从小到大并且附带成 绩
1) "xm"
2) "8000"
3) "ls"
4) "8800"
5) "xh"
6) "9000"
7) "zs"
8) "10000"
redis 127.0.0.1:6379> zrangebyscore salary -inf 9000 withscores  # 显示工资小于9000员工的升 序排序! 
1) "xm"
2) "8000"
3) "ls"
4) "8800"
5) "xh"
6) "9000"
redis 127.0.0.1:6379> zrange salary 0 -1  #查询所有用户
1) "xm"
2) "ls"
3) "xh"
4) "zs"
redis 127.0.0.1:6379> zrem salary ls  #移除用户
(integer) 1
redis 127.0.0.1:6379> zcard salary   # 获取有序集合中的个数 
(integer) 3
redis 127.0.0.1:6379> zadd myzset 1 hello 2 world 3 nice  #批量增加用户
(integer) 3
redis 127.0.0.1:6379> zcount myzset 1 2  # 获取指定区间的成员数量! 
(integer) 2

(7)Geospatial 地理位置

朋友的定位,附近的人,打车距离计算?这些功能都可以用reids来实现,Redis 的 Geo 在Redis3.2 版本就推出了! 这个功能可以推算地理位置的信息,两地之间的距离,方圆 几里的人!

常见命令

GEOHASH:该命令将返回11个字符的Geohash字符串!
GEOPOS:获得当前定位:一定是一个座标值!
GEODIST:获取两个城市之间的直线距离
GEORADIUS:以给定的经纬度为中心, 找出某一半径内的元素
GEOADD:添加key的经纬度座标及名称
GEORADIUSBYMEMBER:找出位于指定元素周围的其他元素

GEOADD
添加key的经纬度座标及名称:GEOADD key 经度 纬度 名称

#添加中国几个城市的经纬度座标
127.0.0.1:6379>  geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 113.38 22.52 zhongshan
(integer) 1
127.0.0.1:6379> geoadd china:city 113.28 23.12 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.54 shengzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 108.94 34.26 xianshi
(integer) 1

GEOPOS
获得当前定位,一定是一个座标值:GEOPOS key 名称

#获取上海和北京的定位,前提是这些城市要提前添加到相应的key(china:city)里面
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793564"
   2) "31.229999039757836"
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918"
   2) "39.900000091670925"

GEODIST
获取两个城市之间的直线距离:GEODIST key 城市1 城市2 单位
m 表示单位为米。 km 表示单位为千米。 mi 表示单位为英里。 ft 表示单位为英尺

#获取中山到广州的距离,单位是km
127.0.0.1:6379> GEODIST china:city zhongshan guangzhou km
"67.5185"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"

GEORADIUS
以给定的经纬度为中心, 找出某一半径内的元素:GEORADIUS key 经度 维度 半径 单位

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "xianshi"
2) "zhongshan"
3) "shengzhen"
4) "guangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "xianshi"
127.0.0.1:6379>

GEORADIUSBYMEMBER
找出位于指定元素周围的其他元素! :GEORADIUSBYMEMBER key 城市 距离 单位

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xianshi"

GEOHASH
将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!

# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近! 
127.0.0.1:6379> geohash china:city beijing chongqi 
1) "wx4fbxxfke0" 
2) "wm5xzrybty0"

底层原理
GEO 底层的实现原理其实就是 Zset!我们可以使用Zset命令来操作geo

127.0.0.1:6379> zrange china:city 0 -1  #获取所有的值
1) "xianshi"
2) "zhongshan"
3) "shengzhen"
4) "guangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city xianshi  #移除值
(integer) 1
127.0.0.1:6379> zcard china:city  #查看值的个数
(integer) 5
127.0.0.1:6379>

(8)Hyperloglo基数技术

基数
在数学上,基数(cardinal number)也叫势(cardinality),指集合论中刻画任意集合所含元素数量多少的一个概念。比如:集合A={1,2,3,4,5,5,5,6},集合B={3,4,5,6,7},基数(两个集合不重复的元素) = 7

优点
占用的内存是固定,2^64 不同的元素的技术,只需要花费 12KB内存!如果要从内存角度来比较的 话 Hyperloglog 首选!

使用场景
网页的 UV (一个人访问一个网站多次,但是还是算作一个人!),传统的方式, set 保存用户的id,然后就可以统计 set 中的元素数量作为标准判断 ! 这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;根据官方给的数据,Hyperloglo会有0.81% 错误率! 统计UV任务,可以忽略不计的!

127.0.0.1:6379> PFADD H1 A B C  # # 创建第一组元素 H1
(integer) 1
127.0.0.1:6379> PFADD H2 C B A A D E G
(integer) 1
127.0.0.1:6379> PFADD H3 Q W E R
(integer) 1
127.0.0.1:6379> PFCOUNT H1 # 统计 H1元素的基数数量 
(integer) 3
127.0.0.1:6379> PFMERGE H H1 H2 H3  # 合并三组 H1 H2 H3 => H 并集 
OK
127.0.0.1:6379> PFCOUNT H
(integer) 9

(9)Bitmap位存储

Bitmap存储的数据只有0和1,利用0和1可以用来做一些状态的记录,前提是这种状态只有两种情况

应用场景
统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡,365天打卡! 两个状态的,都可以使用 Bitmaps!Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态! 365 天 = 365 bit 1字节 = 8bit 46 个字节左右

应用场景一:一周的打卡记录
0到6表示周一到周天,打卡状态由0和1记录,0代表缺勤,1代表已打卡

127.0.0.1:6379> setbit sign 0 1  #设置打卡状态
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 0  #查看某一天的打卡状态
(integer) 1
127.0.0.1:6379> getbit sign 4
(integer) 0
127.0.0.1:6379> bitcount sign  #统计这一周的打卡情况
(integer) 5

(10)事务

Redis事务的概念
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务没有隔离级别的概念
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务的三个阶段
(1)开始事务(multi)
(2)命令入队
(3)执行事务(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> set k3 v3
QUEUED
127.0.0.1:6379> exec   #执行事务,只有执行了exec命令后,队列里面的命令才会执行
1) OK
2) OK
3) OK
127.0.0.1:6379>

事务的编译时异常
事务在执行的时候有可能会遇到一些异常,比如编译时的异常,运行时的异常,遇到编译时异常,事务中所有的命令都不会被执行!

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 k3 v3
QUEUED
127.0.0.1:6379> getsdfsafsaf   #随便输入不存在的命令,遇到编译时异常
(error) ERR unknown command 'getsdfsafsaf'
127.0.0.1:6379> set k4 v4    #再设置命令
QUEUED
127.0.0.1:6379> exec   #执行事务,发现会失败,事务中所有的命令都不会被执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1   
(nil)
127.0.0.1:6379>

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

127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379> set k1 "hello"  #执行的命令都不会立刻执行,只会先放入队列
QUEUED
127.0.0.1:6379> incr k1     #对字符串进行自增1,遇到运行时异常
QUEUED
127.0.0.1:6379> set k2 v2   #再设置命令
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec   #执行事务,发现除了那一条异常的命令,其余都执行成功了
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
127.0.0.1:6379> keys *
1) "H2"
2) "k3"
3) "k1"
4) "sign"
5) "k2"
6) "china:city"
7) "H1"
8) "H"
9) "H3"
127.0.0.1:6379>

放弃事务

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 k3 v3
QUEUED
127.0.0.1:6379> discard   #放弃事务
OK
127.0.0.1:6379> keys *
1) "H2"
2) "sign"
3) "china:city"
4) "H1"
5) "H"
6) "H3"
127.0.0.1:6379>

(11)watch监控实现乐观锁

悲观锁:顾名思义就是很“悲观”,无论执行什么操作都觉得会出问题,所以在执行任何操作的时候都会加上锁,这样就会影响性能
乐观锁:顾名思义就是很“乐观”,认为什么时候都不会出问题,所以不会上锁! 在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

redis利用watch的监控功能可以实现乐观锁,正常实现乐观锁的流程就是利用版本号比较机制,只是在读数据的时候,将读到的数据的版本号一起读出来,当对数据的操作结束后,准备写数据的时候,再进行一次数据版本号的比较,若版本号没有变化,即认为数据是一致的,没有更改,可以直接写入,若版本号有变化,则认为数据被更新,不能写入,防止脏写

watch监控测试

127.0.0.1:6379> set money 1000   #假设现有1000块钱
OK
127.0.0.1:6379> set out 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 100   #花费了100块钱,剩下900
QUEUED
127.0.0.1:6379> incrby out 100   #消费余额100块钱
QUEUED
127.0.0.1:6379> exec    #执行事务,单线程情况下,watch没有监控到其余线程对money进行操作,正常执行
1) (integer) 900
2) (integer) 100
127.0.0.1:6379>

多线程修改值 , 利用watch 实现redis的乐观锁操作
上面的例子由于是单线程的,watch没有监控到其余线程对money进行操作,所以正常执行,如果watch监控到有其余线程已经对money进行了修改,则会执行失败,这就是redis利用watch的监控功能实现乐观锁操作的原理

在这里插入图片描述

(12)通过Jedis操作redis

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

String类型

    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**设置值*/
        jedis.set("k1", "v1");//设置一个值
        jedis.mset("k2","v2","k3","v3","k4","v4","k5","v5");//设置多个值
        jedis.append("k1","hello");//再k1后面再追加值“hello”
        jedis.setnx("k6","v6");//注意与set的区别,setnx表示如果不存在才创建
        jedis.setnx("k5","v5");//setnx如果存在就创建失败,set的话则会覆盖
        jedis.setex("k7",10,"v7");//设置一个值,10秒后过期


        /**获取值*/
        String k1 = jedis.get("k1");//获取一个值
        List<String> mgetlist = jedis.mget("k2", "k3", "k4","k5","k6","k7");//获取多个值
        System.out.println("k1 = "+k1);
        for (int i = 0; i <mgetlist.size(); i++) {
            System.out.println("k"+(i+2)+" = "+mgetlist.get(i));
        }



        /**删除值*/
        jedis.del("k1");//删除单个值
        jedis.del("k2","k3","k4","k5","k6","k7");//删除多个值

        jedis.close();//关闭连接
    }

List类型

    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**设置值*/
        jedis.lpush("list1","java");//设置一个值,左边插入
        jedis.lpush("list1","c","c++","python");//设置多个值,左边插入
        jedis.rpush("list1","php");//设置一个值,右边插入
        jedis.rpush("list1","c#","js","jq");//设置多个值,右边插入



        /**获取值*/
        List<String> list1 = jedis.lrange("list1", 0, -1);//通过区间的方式获取list1的值
        String str = jedis.lindex("list1", 0);//获取指定下标的元素
        Long len = jedis.llen("list1");//获取key的所有元素的长度
        for (int i = 0; i < list1.size(); i++) {
            System.out.println("list1["+i+"] = "+list1.get(i));
        }
        System.out.println("list1[0] = "+str);
        System.out.println("list1长度 = "+len);

        /**修改值*/
        jedis.lset("list1",1,"new Value");//修改某一个key对应下标的值


        /**删除值*/
        jedis.lrem("list1",1,"c");//删除一个list1中名为”c“的值,如果存在重复元素,可以删除多个
        jedis.ltrim("list1",0,2);//截取下标为0-2的元素,其余的删掉
        jedis.lpop("list1");//出栈,左边
        jedis.rpop("list1");//出栈,右边

        jedis.close();

    }

Set类型

    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**设置值*/
        jedis.sadd("set1","a","b","c","c");//设置多个值
        jedis.sadd("set2","a","c","e","u","q");

        /**获取值*/
        jedis.smembers("set1");//获取所有的值
        jedis.scard("set1");//获取元素中包含的个数
        jedis.sismember("set1","a");//判断set1元素中是否包含有a这个元素


        /**删除值*/
        jedis.srem("set1","a");//删除值”a“
        jedis.srem("list1","b","c");//删除多个值



        /**集合运算*/
        jedis.sinter("set1","set2");//set1与set2交集
        jedis.sunion("set1","set2");//set1与set2并集
        jedis.sdiff("set1","set2");//set1与set2差集
        jedis.sinterstore("set3","set1","set2");//求set1与set2交集并赋值给set3


        jedis.close();

    }

Hash类型

    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        Map<String,String> map = new HashMap<>();
        map.put("k1","v1");
        map.put("k2","v2");
        map.put("k3","v3");

        /**添加值*/
        jedis.hmset("hash1",map);//添加map
        jedis.hset("hash1","k4","v5");//向hash1中添加key为k5,value为v5的元素


        /**获取值*/
        Map<String, String> map1 = jedis.hgetAll("hash1");//获取hash1的所有的键值对
        Set<String> keys = jedis.hkeys("hash1");//hash1的所有键
        List<String> values = jedis.hvals("hash1");//hash1的所有值
        List<String> hash1 = jedis.hmget("hash1", "k1");//获取hash1中的值
        Long len = jedis.hlen("hash1");//hash1的长度
        Boolean hexists = jedis.hexists("hash1", "k8");//判断hash1中是否含有k8


        /**删除值*/
        jedis.hdel("hash1","k1","k2","k3");

        jedis.close();
    }

Zset类型

    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**添加值*/
        jedis.zadd("salary",8000,"xm");
        jedis.zadd("salary",10000,"zs");
        jedis.zadd("salary",9000,"ls");
        jedis.zadd("salary",9800,"xh");


        /**获取值*/
        Set<String> salary1 = jedis.zrangeByScore("salary",0 , 10001);//将salary从小到大排序
        Set<String> salary2 = jedis.zrevrange("salary", 0, -1);//倒序输出,因为上面已经排过序了,所以倒序就是从大到小输出!
        Set<String> salary3 = jedis.zrange("salary", 0, -1);//顺序输出,已经排过序了
        Long len = jedis.zcard("salary");//集合的元素个数
        System.out.println("从小到大输出");
        salary1.forEach(System.out::println);
        System.out.println("从大到小输出");
        salary2.forEach(System.out::println);
        System.out.println("顺序输出");
        salary3.forEach(System.out::println);
        System.out.println("长度");
        System.out.println(len);

        /**删除值*/
        jedis.zrem("salary","ls");//移除用户

        jedis.close();
    }

事务(重点)

    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name","zs");
        jsonObject.put("age","12");
        jsonObject.put("sex","雄");
        String user = jsonObject.toString();


        //开启事务
        Transaction multi = jedis.multi();

        try{
            multi.set("user1",user);
            int i = 1/0;//遇到运行时异常
            multi.set("user2",user);
            multi.exec();//执行事务
        }catch (Exception e){
            multi.discard();//放弃事务
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
        
    }

运行结果
在这里插入图片描述

(13)springboot整合redis

在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce? jedis采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接 池! 更像 BIO 模式, lettuce采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据 了,更像 NIO 模式。

说明:关于lettuce本人只是简单了解了一下,也不是很懂

(1)源码分析

在这里插入图片描述
我这边的springboot版本是2.2.6,从上面的图可以看到springboot2.x用的redis的java客户端是lettuce

redis自动配置类(autoconfiguration)
在这里插入图片描述
对应的配置文件RedisProperties,里面的参数就是我们可以配置的
在这里插入图片描述

(2)使用配置好模板RedisTemplate

获取RedisTemplate的bean对象,之后就可以直接像调用redis那样直接调用接口,api就跟正常操作redis的命令一样
在这里插入图片描述
使用上面配置好的RedisTemplate模板来操作redis,但是会出现一个问题,那就是序列化的问题,比如下面的例子,用该模板存储几个值,发现在idea里面可以正常获取,但是一打开redis-cil查看却发现,key前面多了很多转义字符
在这里插入图片描述
原因分析
在查询了一些资料之后,发现任何数据存进redis里面都必须先序列化,用哪种序列化方式可以自己设置,如果不设置的话,默认使用JdkSerializationRedisSerializer进行数据序列化。

所以很明显,那些转义字符都是JdkSerializationRedisSerializer进行序列化时,加上去的,此外还有其他序列化的方式,具体如下
(1)StringRedisSerializer
(2)Jackson2JsonRedisSerialize
(3)JdkSerializationRedisSerializer
(4)GenericToStringSerializer
(5)OxmSerializer

给出一张表,不同的序列化后的值前后对比
在这里插入图片描述
所以一般不会用这个默认的配置模板,而是会自己写一个模板,并且采用其他序列化的方式,这样就不会有上面的问题。

拓展
这让我想起一件事,就是之前在学习面向接口编程的时候,有一条规则,就是所有的实体类pojo创建完后必须实现序列化接口,如果我们不实现序列化接口,直接就将用户类储存进redis,他会报一个序列化错误的异常,如果实现了序列化接口,可以直接存redis

(3)使用自定义模板RedisTemplate

如果使用自定义的模板RedisTemplate,会出现序列化的问题,所以才需要自己写一个RedisTemplate来供自己操作redis

/**
 * redis配置类
 */
@Configuration
public class RedisConfig {

    /**redis配置模板,可直接copy使用**/
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
            throws UnknownHostException {

        //为了开发的方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);

        //json的序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);

        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);

        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;
    }
}

采用了string和json序列化的方式,成功解决上面的问题
在这里插入图片描述

(4)使用RedisUtil

自定义的模板看起来很好,但是还有一种方式更好,那就是将操作redis的命令封装成一个工具类,让我们能跟直接操作redis的api一样,这样在使用redis的java客户端时就能实现无缝切换,因为封装好的工具类的接口就跟直接操作redis的接口一样

package com.zsc.util;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

/**
 *
 * Redis工具类,一般企业开发常用的一个工具类,不会去用原生的redis配置类
 *
 */

@Component
public final class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 26
     * 指定缓存失效时间
     * 27
     *
     * @param key  键
     *             28
     * @param time 时间(秒)
     *             29
     * @return 30
     */

    public boolean expire(String key, long time) {

        try {

            if (time > 0) {

                redisTemplate.expire(key, time, TimeUnit.SECONDS);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 44
     * 根据key 获取过期时间
     * 45
     *
     * @param key 键 不能为null
     *            46
     * @return 时间(秒) 返回0代表为永久有效
     * 47
     */

    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }

    /**
     * 53
     * 判断key是否存在
     * 54
     *
     * @param key 键
     *            55
     * @return true 存在 false不存在
     * 56
     */

    public boolean hasKey(String key) {

        try {

            return redisTemplate.hasKey(key);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 67
     * 删除缓存
     * 68
     *
     * @param key 可以传一个值 或多个
     *            69
     */

    @SuppressWarnings("unchecked")

    public void del(String... key) {

        if (key != null && key.length > 0) {

            if (key.length == 1) {

                redisTemplate.delete(key[0]);

            } else {

                redisTemplate.delete(CollectionUtils.arrayToList(key));

            }

        }

    }

    // ============================String=============================

    /**
     * 83
     * 普通缓存获取
     * 84
     *
     * @param key 键
     *            85
     * @return 值
     * 86
     */

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);

    }

    /**
     * 92
     * 普通缓存放入
     * 93
     *
     * @param key   键
     *              94
     * @param value 值
     *              95
     * @return true成功 false失败
     * 96
     */

    public boolean set(String key, Object value) {

        try {

            redisTemplate.opsForValue().set(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 109
     * 普通缓存放入并设置时间
     * 110
     *
     * @param key   键
     *              111
     * @param value 值
     *              112
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     *              113
     * @return true成功 false 失败
     * 114
     */

    public boolean set(String key, Object value, long time) {

        try {

            if (time > 0) {

                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

            } else {

                set(key, value);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 130
     * 递增
     * 131
     *
     * @param key   键
     *              132
     * @param delta 要增加几(大于0)
     *              133
     * @return 134
     */

    public long incr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("递增因子必须大于0");

        }

        return redisTemplate.opsForValue().increment(key, delta);

    }

    /**
     * 143
     * 递减
     * 144
     *
     * @param key   键
     *              145
     * @param delta 要减少几(小于0)
     *              146
     * @return 147
     */

    public long decr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("递减因子必须大于0");

        }

        return redisTemplate.opsForValue().increment(key, -delta);

    }

    // ================================Map=================================

    /**
     * 157
     * HashGet
     * 158
     *
     * @param key  键 不能为null
     *             159
     * @param item 项 不能为null
     *             160
     * @return 值
     * 161
     */

    public Object hget(String key, String item) {

        return redisTemplate.opsForHash().get(key, item);

    }

    /**
     * 167
     * 获取hashKey对应的所有键值
     * 168
     *
     * @param key 键
     *            169
     * @return 对应的多个键值
     * 170
     */

    public Map<Object, Object> hmget(String key) {

        return redisTemplate.opsForHash().entries(key);

    }

    /**
     * 176
     * HashSet
     * 177
     *
     * @param key 键
     *            178
     * @param map 对应多个键值
     *            179
     * @return true 成功 false 失败
     * 180
     */

    public boolean hmset(String key, Map<String, Object> map) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 192
     * HashSet 并设置时间
     * 193
     *
     * @param key  键
     *             194
     * @param map  对应多个键值
     *             195
     * @param time 时间(秒)
     *             196
     * @return true成功 false失败
     * 197
     */

    public boolean hmset(String key, Map<String, Object> map, long time) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 212
     * 向一张hash表中放入数据,如果不存在将创建
     * 213
     *
     * @param key   键
     *              214
     * @param item  项
     *              215
     * @param value 值
     *              216
     * @return true 成功 false失败
     * 217
     */

    public boolean hset(String key, String item, Object value) {

        try {

            redisTemplate.opsForHash().put(key, item, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 229
     * 向一张hash表中放入数据,如果不存在将创建
     * 230
     *
     * @param key   键
     *              231
     * @param item  项
     *              232
     * @param value 值
     *              233
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     *              234
     * @return true 成功 false失败
     * 235
     */

    public boolean hset(String key, String item, Object value, long time) {

        try {

            redisTemplate.opsForHash().put(key, item, value);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 250
     * 删除hash表中的值
     * 251
     *
     * @param key  键 不能为null
     *             252
     * @param item 项 可以使多个 不能为null
     *             253
     */

    public void hdel(String key, Object... item) {

        redisTemplate.opsForHash().delete(key, item);

    }

    /**
     * 259
     * 判断hash表中是否有该项的值
     * 260
     *
     * @param key  键 不能为null
     *             261
     * @param item 项 不能为null
     *             262
     * @return true 存在 false不存在
     * 263
     */

    public boolean hHasKey(String key, String item) {

        return redisTemplate.opsForHash().hasKey(key, item);

    }

    /**
     * 269
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * 270
     *
     * @param key  键
     *             271
     * @param item 项
     *             272
     * @param by   要增加几(大于0)
     *             273
     * @return 274
     */

    public double hincr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, by);

    }

    /**
     * 280
     * hash递减
     * 281
     *
     * @param key  键
     *             282
     * @param item 项
     *             283
     * @param by   要减少记(小于0)
     *             284
     * @return 285
     */

    public double hdecr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, -by);

    }

    // ============================set=============================

    /**
     * 292
     * 根据key获取Set中的所有值
     * 293
     *
     * @param key 键
     *            294
     * @return 295
     */

    public Set<Object> sGet(String key) {

        try {

            return redisTemplate.opsForSet().members(key);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

    /**
     * 306
     * 根据value从一个set中查询,是否存在
     * 307
     *
     * @param key   键
     *              308
     * @param value 值
     *              309
     * @return true 存在 false不存在
     * 310
     */

    public boolean sHasKey(String key, Object value) {

        try {

            return redisTemplate.opsForSet().isMember(key, value);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 321
     * 将数据放入set缓存
     * 322
     *
     * @param key    键
     *               323
     * @param values 值 可以是多个
     *               324
     * @return 成功个数
     * 325
     */

    public long sSet(String key, Object... values) {

        try {

            return redisTemplate.opsForSet().add(key, values);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 336
     * 将set数据放入缓存
     * 337
     *
     * @param key    键
     *               338
     * @param time   时间(秒)
     *               339
     * @param values 值 可以是多个
     *               340
     * @return 成功个数
     * 341
     */

    public long sSetAndTime(String key, long time, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().add(key, values);

            if (time > 0)

                expire(key, time);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 355
     * 获取set缓存的长度
     * 356
     *
     * @param key 键
     *            357
     * @return 358
     */

    public long sGetSetSize(String key) {

        try {

            return redisTemplate.opsForSet().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 369
     * 移除值为value的
     * 370
     *
     * @param key    键
     *               371
     * @param values 值 可以是多个
     *               372
     * @return 移除的个数
     * 373
     */

    public long setRemove(String key, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().remove(key, values);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    // ===============================list=================================

    /**
     * 386
     * 获取list缓存的内容
     * 387
     *
     * @param key   键
     *              388
     * @param start 开始
     *              389
     * @param end   结束 0 到 -1代表所有值
     *              390
     * @return 391
     */

    public List<Object> lGet(String key, long start, long end) {

        try {

            return redisTemplate.opsForList().range(key, start, end);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

    /**
     * 402
     * 获取list缓存的长度
     * 403
     *
     * @param key 键
     *            404
     * @return 405
     */

    public long lGetListSize(String key) {

        try {

            return redisTemplate.opsForList().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 416
     * 通过索引 获取list中的值
     * 417
     *
     * @param key   键
     *              418
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     *              419
     * @return 420
     */

    public Object lGetIndex(String key, long index) {

        try {

            return redisTemplate.opsForList().index(key, index);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

    /**
     * 431
     * 将list放入缓存
     * 432
     *
     * @param key   键
     *              433
     * @param value 值
     *              434
     * //@param time  时间(秒)
     *              435
     * @return 436
     */

    public boolean lSet(String key, Object value) {

        try {

            redisTemplate.opsForList().rightPush(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */

    public boolean lSet(String key, Object value, long time) {

        try {

            redisTemplate.opsForList().rightPush(key, value);

            if (time > 0)

                expire(key, time);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 467
     * 将list放入缓存
     * 468
     *
     * @param key   键
     *              469
     * @param value 值
     *              470
     * //@param time  时间(秒)
     *              471
     * @return 472
     */

    public boolean lSet(String key, List<Object> value) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 484
     * 将list放入缓存
     * 485
     * <p>
     * 486
     *
     * @param key   键
     *              487
     * @param value 值
     *              488
     * @param time  时间(秒)
     *              489
     * @return 490
     */

    public boolean lSet(String key, List<Object> value, long time) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            if (time > 0)

                expire(key, time);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 504
     * 根据索引修改list中的某条数据
     * 505
     *
     * @param key   键
     *              506
     * @param index 索引
     *              507
     * @param value 值
     *              508
     * @return 509
     */

    public boolean lUpdateIndex(String key, long index, Object value) {

        try {

            redisTemplate.opsForList().set(key, index, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 521
     * 移除N个值为value
     * 522
     *
     * @param key   键
     *              523
     * @param count 移除多少个
     *              524
     * @param value 值
     *              525
     * @return 移除的个数
     * 526
     */

    public long lRemove(String key, long count, Object value) {

        try {

            Long remove = redisTemplate.opsForList().remove(key, count, value);

            return remove;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }
}

使用的方式就跟直接操作redis一样在这里插入图片描述
真正实现了无缝切换,事实上这个也是使用最多的方式之一。

五、redis配置文件详解

在学习这一部分的内容时,我查询了大量的资料,现将查询到的相关知识记录在下面

配置文件位于redis的安装目录下,本人这里用的win10系统,配置文件如下图
在这里插入图片描述
为了方便解读,我将配置文件的内容复制了一份,并且在idea项目里面新建一个文本,将内容粘贴在该文本,这么做的目的主要是我的idea有安装英语的翻译插件,方便解读英语的注释

首先是一开始的说明
在这里插入图片描述
INCLUDES(包括)
在这里插入图片描述
NETWORK(网络)
在这里插入图片描述
在这里插入图片描述
GENERAL(通用)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
SNAPSHOTTING(快照)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SECURITY(安全)
在这里插入图片描述

LIMITS(限制)
在这里插入图片描述
在这里插入图片描述

APPEND ONLY MODE
在这里插入图片描述
除此之外还有其他配置,上面列的是一些相对重要的配置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章