关系型数据库:Sql Server MySql Oracle…
关系:表与表之间的联系
传统的关系型数据库存储在硬盘上,关系是建立在业务的基础上的。
关系型数据库存在的问题:
量用户高并发访问的情况
缓存:(高速缓存)可以高速访问的,用于临时存储的数据存储区
缓存使用场合
缓存一般用于在较短时间段对相同数据频繁读取的场合,将读取频度较高的数据放入缓存,直接从缓存中取出数据以提高效率
NOSQL(Not only SQL),即"不仅仅是SQL",泛指非关系型数据库。
NOSQL不依赖于业务逻辑方式存储,而以简单的"key-value"模式存储,大大增加了数据库的扩展能力
不遵循SQL标准
不支持ACID(支持事务但是不支持事务的四大特性<原子性,隔离性,一致性,持久性>)
远超SQL性能
NOSQL使用场景
NOSQL数据库
Redis:数据存储在内存中,支持持久化,以key-value模式存储数据,支持多种数据类型,一般作为缓存数据辅助持久化的数据化
MongoDB:高性能,开源的,文档型数6据库,存储在内存中,也可以存储在硬盘中,以Key-value的模式存储数据,value是json的格式存储,提供了丰富的查询功能
Hbase:HBase是Hadoop项目中的数据库,它主要用于对大量数据进行随机,事实的读写场景,使用列式存储的数据模式存储数据
Redis(Remote Diconary Server)是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言API。
Redis本身是一个内存数据库,在应用中可以充当缓存,提高系统数据查询性能的非关系型数据库,统称:NOSQL。
数据类型 | 存储的值 | 对存储的值的操作 | 使用场景 |
string | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; | 把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。例如:token |
hash | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 | 缓存 : 能直观,相比string更节省空间的维护缓存信息,如用户信息,视频信息等 |
list | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素 | 消息队列,任务队列,如秒杀,抢购,抢票等 |
set | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 | 标签(tag) :给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。 点赞,或点踩,收藏等,可以放到set中实现 |
z_set | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射; 元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
排行榜 :有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行 |
https://github.com/microsoftarchive/redis/releases
redis-server
命令,启动redis服务端redis-cli
命令,进入客户端Redis不同版本中所使用的线程处理方式不同,以下就三个版本进行简要说明
Redis4以前的版本使用单线程多路IO复用技术,也就是说Redis 4之前的版本使用的是单线程
Redis4.0开始支持多线程,但仅仅是针对删除操作使用多线程处理,也就是在Redis4.0版本中引入
的惰性删除(异步删除)的方式,将删除操作交给后台线程处理,以解决主线程卡顿的问题
Redis6.0的版本才开始真正的使用多线程进行I/O操作,但Redis的命令依旧是主线程串行执行的,
Redis6.0以后的版本虽然支持多线程,但默认情况下是禁用的,需要开发者在配置文件中开启并指
定所使用的线程数量
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
# threads, if you have a 8 cores, try to use 6 threads. In order to
# enable I/O threads use the following configuration directive:
#
# io-threads 4 设置线程的数量
#
# Setting io-threads to 1 will just use the main thread as usual.
# When I/O threads are enabled, we only use threads for writes, that is
# to thread the write(2) syscall and transfer the client buffers to the
# socket. However it is also possible to enable threading of reads and
# protocol parsing using the following configuration directive, by setting
# it to yes:
#
# io-threads-do-reads no yes为启动多线程
传统的关系型数据库是使用表来存储数据,在Redis中存储数据使用类似于JavaMap集合的方式存储,使 用key-value存储,key不允许重复,值可以重复
具体命令 | 功能 |
---|---|
select index |
切换数据库(默认15个数据库,编号为0-15) |
clear |
清屏命令 |
set key value |
添加一个数据value对应数据类型为string |
get key |
根据key获得value |
lpush key value.... |
添加一个数据value对应数据类型为list |
lrange key 0 -1 |
遍历集合 |
keys pattern keys * keys j* keys *zhong keys ?ava keys u[se]r |
获取当前数据库中的key 获取当前数据库所有key 获取所有以j开头的key 获取所有以中结尾的key 获取前面有一个任意字符,并且以ava结尾的key 获得第一个字符为u,第二个字符为,s或者e,结尾字符为r的key ps:"*"匹配任意字符 "?"匹配一个字符 "[]"匹配其中任意字符 |
exists key |
判断key是否存在 |
type key |
判断key的类型 |
del key |
删除指定 key |
fulshdb |
清除库中所有数据 |
fulshall |
清除所有数据命令 |
dbsize |
查看库中key的数量 |
expire key 1 pexpire key 1 |
设置key的过期时间 以秒为单位 以毫秒为单位 |
ttl key |
查看过期剩余时间(-1表示永不过期,-2表示已过期) |
persist key |
设置永不过期 |
rename key newkey |
修改key的名字 |
renamex key newkey |
修改key的名字,如果newkey不存在才修改,否则修改失败 |
sort key `sort list[asc |
desc] sort list[limit 0 3] [asc |
quit exit ESC键 |
退出命令 |
ping |
检查与服务器连通命令 |
echo message |
控制台打印命令 |
move key db |
数据在不同的库中移动 当前库存在key数据且1号库不存在同名数据 |
info |
或得当前Redis属性值 |
redis自身是一个Map,其中所有的数据都是采用 key : value 的形式存储
key:是一个字符串
value:是具有具体类型的数据
Redis中包含5种基本数据类型和3中特殊类型
string是Redis最基本数据类型,String二进制是安全的,redis的string可以存储任何数据,如图片,对象等
单个数据,一个存储空间保存一个数据,如果其中存储的数字则可以当数字来用
redis中最大可以存储512M大小的字符串,如果是数字,取值范围为java总的lang类型取值范围
命令 | 规则 | 功能 |
set key value | 如果key存在替换原有value,如果不存在则创建新的数据 | 存储单个数据 |
mset key1 value1 key2 value2 ... | 同时存储多个数据 | |
get key | 获取数据时返回值为nil表示空,没有数据 | 获取一个数据 |
mget key1 key2 ... | 同时获取多个数据 | |
strlen | 获得字符串长度 | |
append key value | 如果数据存在追加数据,如果不存在新建数据 | 向value中增加新数据 |
getrange key startIndex endIndex | startIndex:开始截取,索引从0开始 endIndex:终止截取 |
截取字符串 |
setrang key offset value | offset:从下标为多少的字符开始替换 | 字符串某字符替换 |
setnx key value | 如果key存在存值失败,如果不存在则存值 | 存储一个数据 |
incr key | 如果字符串中存储的是数字则可以进行数值运算,但其数据类型还是string 必须确保值为数字格式,否则会报错,在redis中incr具有原子性. |
对key的值自加1,等同于i++ |
incrby key number | 对key的值加上指定的数值(负值进行减法运算),等同于i=i+n | |
incrbyfloat key number | 对key的值加上或减去浮点数 | |
decr key | 对key的值自减1,等同于i++ | |
incrby key number | 对可以的值减去指定的数值 | |
setex key seconds value | 以秒为单位设置存活时间 | |
psetex key millseconds value | 以毫秒为单位设置存活时间 | |
set key value [ex seconds][px milliseconds] | 使用set方式设置key的存活期,如果使用nx选项则表示setnx+过期时间 | |
set key value [ex seconds][px milliseconds][nx] | ||
incrby key number | 对可以的值减去指定的数值 | |
expire key seconds | 单独设置过期时间 | 以秒为单位 |
set key value [ex seconds][px milliseconds][nx] | 以毫秒为单位 |
存储对象
我们可以使用字符串存储一个对象,但在使用时不方便,所以存储对象一般不使用字符串
string中key的命名规范
redis中常用于存储表中的一条数据(对象),所以key命名规则体现唯一原则:
表名:主键名:主键值:字段名
set user:user_id:1001{username:admin,password}
hash类型主要用于存储对象,以key field value 的形式存储
hash中的value只能为string不能在value中嵌套hash或list等
命令 | 规则 | 功能 |
hset key field value | 设置数据 | 设置单个数据 |
hmset key field value | 同时设置多个数据 | |
hget key | 获取数据 | 获得key对应的整体数据 |
hget key filed | 获得hash单个字段数据 | |
hmget key1 filed1 filed2 ... | 同时获取多个数据 | |
hdel key filed | 删除数据 | 删除指定字段 |
hlen key | 获取hash中的字段数量 | |
hexists key filed | 存在返回1,不存在返回0 | 获取hash中是否包含指定字段 |
hexists key filed | 获取hash中的所有字段名和字段值 | 获取所有字段值 |
hkeys key | 获取所有字段名 | |
hincrby key filed number | 设置指定字段数值增加或减少 | 增加或减少整数(负数为减少) |
hincrbyfloat key filed number | 增加或减少浮点数(负数为减少) |
在Redis中可以把list用作栈、队列、阻塞队列
list中允许存放重复数据
list中存储的数据有序(指进入顺序<分左右>)
常用指令
① 向列表添加数据
lpush key value
rpush key value
② 从list中获取元素
lrange key startIndex endIndex
③ 从list中弹出元素
lpop key
rpop key
④ 通过下标获取list中的某个元素
lindex key index
⑤ 获得列表中元素的数量(长度)
llen key
⑥ 根据元素值从列表中移除指定数量的元素
lrem key count value
⑦ 截取子list(截断子元素)
ltrim key start stop
⑧ 将原列表中最后一个元素移到新列表中
source:列表
destination:新列表
rpoplpush source destination
⑨ 根据下标重置list中的一个元素(根据下标修改list中的一个元素)
lset key index value
⑩ 向某个元素前或后插入一个元素
linsert key BEFORE|AFTER pivot value
不保证顺序,集合不能存放重复元素
常用指令
① 向set集合中添加一个元素
sadd key value1 value2...
② 查看set集合中所有元素
smembers key
③判断一个元素是否存在于set集合中
0表示不存在 1表示存在
sismembers key value
④获取set集合元素个数
scard key
⑤ 移除一个元素
srem key value
⑥ 随机抽取一个元素
srandmember key [count]
⑦ 随机删除元素
spop key [count]
⑧将一个特定的值,移动到另一个set集合中
destination:另外一个集合
smove source destination member
⑨ 集合操作
sdiff key [key ...]
sinter key [key...]
sunion key [key..]
该集合是对set集合的改造,在set集合中加入了一个字段值,用于存储排序规则数据,该数据只负责排序不起其他作用
- zset存储结构
常用指令
① 向z_set集合中添加元素
zadd key score1 value1 score2 value2...
② 获取z_set集合中的元素
zrange key start stop [WITHSCORES]
zrevrangekey start end [WITHSCORES]
③ 按条件获取z_set中的元素
zrevrangebyscore key min max [limit] [withscores]
zrevrangebyscore key max min [limit] [withscores]
④ 增加或减少z_set中元素score
zincrby key increment value
⑤ 删除z_set中的元素
zrem key member1 member2...
zremrangebyrank key start stop
zremrangebyscore key min max
⑥ 获得元素在集合中的排名
zrank key member
zrevrank key member
⑦ 获得集合中元素数量
zcard key
zcard key min max
⑧ 集合交集和并集
集合交集操作,将多个集合的交集存 入到newset集合中,相交集合的数量个setcount指定的数量要一致,默认对交集数据进行求和运算, 也可以获得最大值或最小值等运算
zinterstore newset setcount set1 set2 ...
集合并集操作:zunionstore newset setcount set1 set2 ...
1.geospatial :地理位置
城市经纬度查询: 经纬度查询
2.hyperloglog : 基数
数学层面上可以说是:两个数据集中不重复的元素
但是再Redis中,可能会有一定的误差性。 官方给出的误差率是0.81%。
Hyperloglog的优点: 占用的内存是固定的,2^64个元素,相当于只需要12kb的内存即可。效率极高!
3.bitmap 位图
都是操作二进制位来进行记录,只有0 和 1 两个状态
jedis是Java操作redis的一个工具,等同于jdbc,可用使用jedis实现java操作redis的目的。
Spring中操作Redis的技术是Spring Data Redis
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>4.3.1version>
dependency>
/**
* 创建jedis对象并指定服务器ip地址及端口号
* 参数1:Redis服务器地址
* 参数2:Redis服务器端口号
*/
Jedis jedis=new Jedis("127.0.0.1",6379);
jedis.set("num1","0");
String value = jedis.get("num1");//根据key获得对应的value
//关闭连接
jedis.close();
Jedis操作Sting
向redis添加数据
//添加一条数据
jedis.set("num1","0");
//添加多条数据
jedis.mset("num3","2","num4","3");
获得redis中元素
①根据key获得对应的value
//获取一个数据
String value = jedis.get("num1");
System.out.println(value);
//获取多个数据
List<String> mget = jedis.mget("num1", "num2", "num3");
System.out.println(mget);
②获取redis所有元素的key
//获取redis所有元素的key
Set<String> keys = jedis.keys("*");
System.out.println(keys);
设置时效
jedis.expire("num1",1000);
//获得剩余时效
long num11 = jedis.ttl("num1");
System.out.println(num11);
Jedis操作List
public static void jedisForList(){
/**
* 创建jedis对象并指定服务器ip地址及端口号
*/
Jedis jedis = new Jedis("127.0.0.1", 6379);
//向Redis中添加list数据
jedis.lpush("list","a","b","c");
jedis.rpush("list","x");
//从Redis中将key为list1的数据获取,返回java.util.List集合
List<String> list1 = jedis.lrange("list1", 0, -1);
//遍历List集合
for(String str : list1){
System.out.println(str);
}
//关闭Redis的连接
jedis.close();
}
Jedis操作set集合数据
public static void jedisForSet(){
/**
* 创建jedis对象并指定服务器ip地址及端口号
*/
Jedis jedis = new Jedis("127.0.0.1", 6379);
//向Redis中添加set集合数据
jedis.sadd("set1","v1","v2","v3");
//从Redis中获取key为set1的数据,返回值自动封装到java.util.Set集合中
Set<String> set1 = jedis.smembers("set1");
for(String set : set1){
System.out.println(set);
}
//关闭与Redis的连接
jedis.close();
}
Jedis操作Map
public static void jedisForMap(){
/**
* 创建jedis对象并指定服务器ip地址及端口号
*/
Jedis jedis = new Jedis("127.0.0.1", 6379);
//向hash中添加数据
Map valMap = new HashMap<String,Object>();
valMap.put("id","001");
valMap.put("name","小明");
valMap.put("age","20");
/**
* 参数1:key
* 参数2:value对应的是map集合
*/
jedis.hmset("user",valMap);
Map<String,String> user = jedis.hgetAll("user");
System.out.println(user);
//关闭与Redis的连接
jedis.close();
}
redis启动时会读取相应配置文件,我们可以通过修改redis的配置文件对redis进行配置,同时也
可以设置多个不同的配置文件,启动多个redis服务,如redis-6379.conf
,redis-6380.conf
。
① Redis服务端启动方式
默认启动方式: redis-server
读取的配置文件为默认的件redis.windows-service.conf
指定配置文件启动方式:redis-server (xxx/redis-6380.conf)
在redis目录里:
redis-server redis-6380.conf
配置文件在指定文件夹里(此处为redis目录下的config文件夹):
redis-server config/redis-6381.conf
② Redis客户端启动方式
redis-cli
访问默认服务器redis-cli -p 6380
redis-cli -h 127.0.0.1 -p 6381
基本配置
#服务器绑定的ip地址
bind 127.0.0.1
#Redis服务器绑定的服务器端口号
port 6380
#超时时间 0表示不超时
timeout 0
# redis是否使用守护线程,设置为no表示不使用启动时会在控制台上打印启动信息
# 设置为yes表示使用守护线程,后台启动,不打印启动信息(该配置在windows上不支持)
# daemonize no
#日志文件,该文件配置后,redis相关的日志信息会输出到指定的配置文件中,便于查看
logfile "E:/Redis/Redis-x64-3.2.100/config/log/redis-6380.log"
#Redis服务器数据库的数量
databases 16
# 指定存储到本地的数据库文件时是否对数据进行压缩,默认为yes,采用LZF压缩(一般开启)
rdbcompression yes
# 设置是否对rdb文件格式进行校验,该校验在写文件和读文件时均会进行,默认为yes(一般开启,如
果设置为no读写性能会提升但存在数据损坏风险)
rdbchecksum yes
# 当后台保存数据时如果出现错误是否停止保存操作
stop-writes-on-bgsave-error yes
redis是基于内存的数据库,它是存储在内存上,如果突然断电或遇到其他意外情况把内存中的数
据清除,就会造成redis中数据的丢失,为避免这样的情况出现而操作损失,在redis中提供了持久
化机制。
redis持久化指将redis中的数据保存到可以永久存储的存储介质中,在需要时将保存的数据进行恢复。
Redis中提供了两种持久化方案:
(1) RDB方式(快照方式):将当前数据状态以快照的形式进行保存,存储格式简单,关注点在数据
(2) AOF方式(日志方式):将操作数据的过程以日志的方式记录下来,存储格式复杂,关注点在操作
数据的过程。
RDB方式实现持久化,使用
save命令
可以将当前的redis中的数据存储到磁盘上,存储文件的默认名称为dump.rdb
当redis服务启动时,会自动读取rdb文件,将已存储的备份进行恢复
save
指令是指示redis立即将数据存储到磁盘上进行备份,但如果存储的数据量过大,而且同时又
有其他客户端也要访问redis时就会造成堵塞状态,影响性能(不建议使用),redis提供了后台执行命
令bgsave
来解决这个问题
save
和 bgsave
两个命令都会调用 rdbSave
函数将当前Redis中的数据存入到rdb文件中,但两者调
用的方式不同
save
直接调用rdbSave
,会阻塞Redis主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处 理客户端的任何请求。bgsave
则调用fork函数生成一个子进程,子进程负责调用rdbSave
,并在保存完成之后向主进程发送 信号,通知保存已完成。Redis服务器在bgsave
执行期间仍然可以继续处理客户端的请求。
在配置文件里配置rdb的路径和文件名
#指定持久化文件存储路径
dir E:/Redis/Redis-x64-3.2.100/config/data
#指定持久化文件文件名
dbfilename dump-6380.rdb
ps:为什么在配置文件里持久化文件名不在
dir
(持久化文件存储路径)里配置?答:因为持久化方式不止一种
bgsave
指令,自动保存当前数据,我们需要在配置文件里配置自动保存的时间及数据变化量(不配置不会自动保存) #该配置表示开启Redis的自动备份功能
#配置rdb快照方式自动保存时间及数据变化量
#每隔900s内有一个数据发送改变则自动保存
save 900 1
#每隔300s内有10个数据发送改变则自动保存
save 300 10
#每隔60s内有10000个数据发送改变则自动保存
save 60 10000
PS:
根据使用场景进行设置,频率过高和过低都会使性能受到影响
调用
debug reload
指令重启服务器时会自动保存数据服务器关闭时(
shutdown
指令)会自动保存数据
RDB持久化方式的优点
RDB是一个紧凑压缩的二进制文件,存储效率高
RDB内部存储的是Redis在某个时间点的数据快照,非常适用于数据备份,数据整体复制等场景
RDB恢复数据的速度要比AOF快
RDB持久化方式的缺点
RDB方式基于快照思想,每次读写全部数据,当数据量巨大时效率低、IO读写性能低下
RDB方式无法实时备份数据,数据丢失的可能性大
bgsave
指令每次执行要创建子进程,内存会产生额外的消耗
AOF持久化是以独立日志方式记录每次写命令,重启时在重新执行AOF文件中的命令以恢复数据
与RDB相比AOF不是存储所有数据而是存储操作数据的过程,实时性更高
AOF解决了数据持久化的持久性,目前AOF已经是数据持久化的主流方式
AOF持久化方式写数据的过程:
AOF写数据的三种策略:
always(每次写都存入)
每次写都存入,虽然保证了数据存储的实时性,准确性,但是当写入数据过多,每次写入都需要IO操作,就会产生大量IO操作,效率低下(不建议使用)
everysec(每秒写入)
每秒将缓冲区中的写操作同步到AOF文件中,数据实时性,准确性,性能较高(建议使用,Redis默认使用方式)
no(系统写入)
由操作系统控制每次同步到AOF的周期
AOF持久化的使用
AOF默认在Redis中是禁用的,需要在配置文件中打开,设置为yes,开启后服务器会自动生产一个aof文件,用于备份数据
#配置是否使用AOF存储数据,默认为no
appendonly no|yes
在配置文件里指定aof文件名
#配置aof文件名
appendfilename "appendonly-6380.aof"
在配置文件中配置AOF存储所使用的策略,默认为everysec策略
# 配置AOF存储的写入策略
# appendfsync always
appendfsync everysec
# appendfsync no
AOF重写
随着不断向aof写入数据,aof文件会越来越大,且操作中会有大量重复且无效的操作,替换过程,Redis引入了AOF重写机制,将对AOF压缩,将AOF中原有指令进行重组,将无效,多余的指令移除,多条追加指令进行合并,转换为最终数据对应的指令进行记录。以实现压缩aof文件的目的
AOF重写的作用
① 降低磁盘占用量,提高磁盘使用率
② 提高持久化效率,降低持久化时间,提高IO性能
③ 提高数据恢复效率
AOF重写规则
① 超时数据不再写入文件
② 忽略无效指令,重写时使用进程内的数据直接生成,这样新的aof文件只保存最终数据的写入命令
③ 对同一数据的多条命令进行合并
lpush list a
,lpush list b
,lpush list c
转换为l push a,b,c
AOF重写的使用
① 使用bgrewriteaof
手动重写
127.0.0.1:6380> set name xiaowang
OK
127.0.0.1:6380> set age 20
OK
127.0.0.1:6380> set sex 1
OK
127.0.0.1:6380> set num01 10
OK
127.0.0.1:6380> set num02 10
OK
127.0.0.1:6380> delete num01
(error) ERR unknown command 'delete'
127.0.0.1:6380> del num01
(integer) 1
127.0.0.1:6380> set num02 20
OK
#查看aof文件
127.0.0.1:6380> bgrewriteaof
Background append only file rewriting started
127.0.0.1:6380>
#查看重写的aof文件
② 在配置文件里进行配置实现自动重写
Redis中配置好AOF自动重写的触发条件后,当条件满足自动触发,在Redis中有两个触发条件配置,当达到两个条件就会自动触发AOF重写
# AOF自动触发百分比
auto-aof-rewrite-percentage 100
# AOF自动触发最小尺寸
auto-aof-rewrite-min-size 64mb
当Reids中自动触发条件大于触发参考值时,执行AOF重写(参考条件可根据
info
指令查看,Redis中已配置好)
# AOF自动触发参考值
aof_current_size:60 #当AOF触发条件的最小尺寸大于该值自动执行AOF重写
aof_base_size:60 #AOF触发基础尺寸
# 当前使用百分比大于或等于自动触发百分比时会自动执行AOF重写
# 公式:aof_current_size-auto-aof-rewrite-min-size/aof_base_size>=autoaof-rewrite-percentage
传统的事务:事务对应一个sql集,其中包含sql语句,这些sql语句要么全部执行,要么不执行,必须符合事务的(ACID)特性
Redis中的事务:不需要符合所谓(ACID)四大特性。
- Redis中事务是为了保证多条执行的指令在执行过程中不被打断
开启事务:multi
指令,该指令用于设置事务的开始位置,该指令后的所有指令都会加入Redis的事务中,形成一个指令队列
执行事务:exec
指令,该指令用于执行事务,表示事务的结束和指令队列的执行,与multi
成对使用
取消事务:discard
指令,该指令必须在multi
之后,在exec
之前,该指令用于取消当前事务
PS:
Redis事务中如果出现指令语法错误,则会自动取消当前事务
在事务执行过程中,即使出现错误,事务也会继续执行
watch监控锁
当同一个数据需要改变时,可能会出现多个人同时修改该数据,数据可能发生错乱,为了避免发生数据修改错乱,此时加入watch锁,只允许数据修改一次数据,当一个人修改后其他人则不能改变,其他人修改该数据,则终止事务的执行
加入watch锁,执行事务前,如果数据发生变化,则终止事务执行
watch key[key1 key2....]
关闭watch锁
unwatch
分布式锁
当多个线程同时操作一个数据数,可能出现数据错乱,为避免该情况,我们需要将该数据锁起来,当一个线程操作完成,才允许另外一个线程操作该数据,Redis使用分布式锁实现此场景
Redis中没有分布式锁,我们可以使用setnx
设计一个分布式锁
添加一个key,该key为分布式锁,我们知道setnx在设置数据时如果数据存在则返回0
设置该数据为锁,其他客户端要操作数据前先通过该指令的返回值检测如果返回值为0
则表示当前数据已被锁定不能操作,如果返回值为1表示加锁,然后操作。
setnx lock-num 1
对加锁的数据使用后要解锁,通过del lock-num移除数据的方式实现解锁过程
del lock-num
死锁
通过分布式锁的机制可以实现对数据操作的排他性,但数据使用结束后需要解锁(谁加锁,谁解锁),但有时可能会忘记解锁或发送意外无法解锁,就成了死锁。。。
设计分布式锁时不允许出现死锁
在设置分布式锁就需要添加时效,在一定时间后自动解锁
set key value [ex seconds][px milliseconds][nx]
:设置分布式锁
Spring使用Spring Data与Redis进行整合,在SpringData中提供了一个模板类
RedisTemplate
来实现redis的相关操作
添加相关依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.9version>
parent>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-webartifactId>
<version>2.7.9version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>2.7.9version>
dependency>
设置配置
server.context:
port: 80
spring:
redis:
host : 127.0.0.1 #配置ip地址
port : 6380 #配置端口号
jedis:
#配置连接池
pool:
max-active: 10 # 最大活动连接
min-idle: 2 # 最小空闲连接
max-idle: 5 # 最大空闲连接
max-wait: 1000 # 最大等待时间
创建Bean
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userId;
private String username;
private String password;
}
要将程序中的数据进行外部存储,都必须将数据进行序列化。
数据从外部存储到程序中,也需要将数据进行反序列化
SpringDataRedis默认使用JDK方式将程序中的数据进行序列化,要求类需要实现序列化接口
该种方法传到redis中会有富余数据,所以我们使用下列方式设置序列化
private RedisTemplate redisTemplate;
@Autowired
public ApplicationTest(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
/**
* 设置redisTemplate中key和value的序列化
* SpringDateRedis默认使用JDKSerializationRedisSerializer对key和value进行序列化
* GenericJackson2JsonRedisSerializer:将任意类型的数据转化为json串进行序列化到redis中
* Jackson2JsonRedisSerializer(User.class):将指定类型转化json并序列化到redis中
* GenericToStringSerializer(User.class):将任意类型数据转化为字符串
*/
//设置key序列化
//StringRedisSerializer:只支持字符串
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(User.class));
//redisTemplate.setValueSerializer(new GenericToStringSerializer(User.class));
}
操作字符串
@Test
void operateString() {
//获得字符串操作对象
ValueOperations valueOperations = redisTemplate.opsForValue();
// valueOperations.set("user",new User());//添加对象
// valueOperations.set("num",100);
// Integer num = (Integer) valueOperations.get("num");
// Long num = valueOperations.increment("num");//incr命令
// Long num = valueOperations.increment("num", 10);//incrby命令
// User user = (User) valueOperations.get("user");
// valueOperations.set("username","admin",100, TimeUnit.SECONDS);//添加字符串并设置过期时间
// valueOperations.set("age",18, Duration.ofSeconds(10));
// Map map=new HashMap<>();
// map.put("username","admin");
// map.put("age",88);
// valueOperations.multiSet(map);//mset命令
// ArrayList arrayList=new ArrayList();
// Collections.addAll(arrayList,"username","age");
// List list = valueOperations.multiGet(arrayList);//mget命令
// valueOperations.setIfAbsent("age",18);//setnx命令
// valueOperations.setIfAbsent("age1",18);
// System.out.println(user);
}
操作list
@Test
void operateList() {
ListOperations listOperations = redisTemplate.opsForList();
// listOperations.leftPush("list","aaa");//lpush
// listOperations.leftPush("list","bbb");//lpush
// listOperations.rightPush("list","ccc");//rpush
// listOperations.leftPushAll("list","ddd","eee","fff");
// listOperations.leftPop("list");//lpop
// List list = listOperations.range("list", 0, -1); //lrange key start end
// list.forEach(System.out::println );
}
操作hash
@Test
void operateHash() {
HashOperations hashOperations = redisTemplate.opsForHash();
// hashOperations.put("hash","hashKey","value");//hset
Map<String,Object> map=new HashMap<>();
map.put("hashKey01","value01");
map.put("hashKey02","value02");
map.put("hashKey03","value03");
map.put("hashKey04","value04");
hashOperations.putAll("hash",map);//hmset
// System.out.println(hashOperations.get("hash", "hashKey"));//hget
System.out.println(hashOperations.entries("hash"));//hmget
}
操作set
@Test
void operateSet() {
SetOperations setOperations = redisTemplate.opsForSet();
// setOperations.add("key1","value1","value2","value1");//sadd
// System.out.println(setOperations.isMember("key1", "value"));//sismenmbers
setOperations.add("key1",new User());
System.out.println(setOperations.members("key1"));//smembers
}
操作Z_set
@Test
void operateZSet() {
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
// zSetOperations.add("scores","小明",90.9);//zadd
// zSetOperations.add("scores","花花",88.6);
// Set scores = zSetOperations.range("scores", 0, -1);//zrange
// Set scoreSet=new HashSet<>();
// scoreSet.add(ZSetOperations.TypedTuple.of("张三",100d));
// scoreSet.add(ZSetOperations.TypedTuple.of("李四",60.55));
// zSetOperations.add("scores",scoreSet);
/**
* 根据z_set的key获得value和filed,filed和value被封装在TypedTuple中
*/
// Set scores = zSetOperations.reverseRange("scores", 0, -1);//zrevrange
// scores.forEach(System.out::println);
// for (ZSetOperations.TypedTuple typedTuple : scores) {
// System.out.println(typedTuple.getScore() + ":" + typedTuple.getValue());
// }
//获得前三名
// Set scores = zSetOperations.reverseRangeByScoreWithScores("scores", 0, 100, 0, 3);
// for (ZSetOperations.TypedTuple typedTuple:scores){
// System.out.println(typedTuple.getScore()+":"+typedTuple.getValue());
// }
// Long rank = zSetOperations.rank("scores", "小明");//获得该值得排名
// System.out.println(rank+1);
Long scores = zSetOperations.zCard("scores");//获得元素数量
System.out.println(scores);
}
学习来自于西安加中实训