Redis
文章目录
nosql
为什么要用nosql
为什么要用nosql?
大数据时代,springboot、springcloud。
现代的计算系统上每天网络上都会产生庞大的数据量。
这些数据有很大一部分是由关系数据库管理系统(RDBMS)来处理。 1970年 E.F.Codd’s提出的关系模型的论文 “A relational model of data for large shared data banks”,这使得数据建模和应用程序编程更加简单。
通过应用实践证明,关系模型是非常适合于客户服务器编程,远远超出预期的利益,今天它是结构化数据存储在网络和商务应用的主导技术。
NoSQL 是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨。NoSQL的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。
NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
1、单机Mysql的年代
当初一个网站的访问量不大,单个数据库 完全 够。服务器没有太大的压力。
出现的问题:
1、数据量太大,一个机器放不下。
2、数据的 索引,一个机器内存也放不下。
3、访问量一个服务器承受不了
2、缓存 Memcahed+mysql+垂直拆分(读写分离)
网站80%都是在读,每次都要去查询数据库十分麻烦!可以用缓存来解决。
3、分库分表+水平拆分
技术和业务发展的同时,对人的要求也越来越高了。
4、如今时代
翻天覆地的变化。
mysql等关系型数据库已经不够用了。
mysql有的使用它来储存一些较大的文件。
目前互联网项目架构
为什么要用Nosql
用户的个人信息,社交网络,用户自己产生的数据,用户日志爆发式增长。
什么是Nosql
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。
非关系型的数据库:随着web2.0时代产生,传统的数据库很难应付,尤其是超大规模的高并发社区。
Nosql在目前环境下发展的非常迅速,redis发展是最快的。
Nosql特点
1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(Nosql的缓存是细密度的)
3、数据类型是多样型的!(不需要事先设计数据库!随取随用。)
4、传统的关系型数据库和Nosql
传统的关系型数据库
-结构化组织
-sql
-数据 和关系存在单独表中
-严格的一致性
Nosql
-不仅仅是数据
-没有固定的查询语句
-键值对,列存储。文档存储,图像数据库
-CAP定理和BASE
-
阿里架构分析
Nosql的四大分类
KV键值对
- 新浪:Redis
- 美团:Redis+Tail
- 阿里百度:Redis +memecache
文档型数据库
-
MngoDB
基于分布式存储数据库。
是一个介于关系型和非关系型数据中间的。
列存储数据库
- HBash
- 分布式数据库
图关系数据库
- 不是存图的,而是存关系的
Redis
概述
Redis是什么
REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统,远程服务字典。
当前Nosql技术之一,也被称为结构化数据库。
Redis能做什么
1、内存存储、持久化,内存是断电即失。
2、用于高速缓存,效率高
3、发布订阅
4、地图信息分析
5、计时器,计数器
Redis特性
1、多样数据类型
2、持久化
3、集群
4、事务
注意什么
Redis都是推荐在Linux上搭建。
安装
windows安装
下载安装包:github下载
解压,完成!双击运行服务即可 默认端口:6379
使用redis客户端连接服务器
Linux安装
**下载地址:**http://redis.io/download,下载最新稳定版本。
1、centos安装
1、下载 https://redis.io/
2、上传服务器
3、解压安装包 程序放在opt目录
2、docker安装
安装的时候可以简化,先不挂载,进入步骤:1、2、5.2、6、7、8
1、这里我们拉取官方的最新版本的镜像:
$ docker pull redis:latest
2、使用以下命令来查看是否已安装了 redis:
$ docker images
3、 创建本机redis挂载目录
mkdir -p /root/redis/data /root/redis/conf
4、在/root/redis/conf目录中创建文件 redis.conf
touch redis.conf
5、安装完成后,我们可以使用以下命令来运行 redis 容器:
1、$ docker run -d -p 6379:6379 -v /root/redis/conf/redis.conf:/redis.conf -v /root/redis/data:/data redis redis-server --appendonly yes
2、docker run -d -p 6379:6379 redis redis-server --appendonly yes
6、查看redis是否运行
docker ps
7、接着我们通过 redis-cli 连接测试使用 redis 服务。
$ docker exec -it 容器ID /bin/bash
8、进入终端
docker exec -it 容器ID redis-cli
性能 测试
redis-benchmark
redis 性能测试工具可选参数如下所示:
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | –csv | 以 CSV 格式输出 | |
12 | -l | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
# 测试100个并发连接,每个并发10万个请求
基础知识
redis 默认有16个数据库
默认使用的是第0个数据库
数据库切换
#设置key
set name cbbgs
#查看key的值
get name
#切换到数据库3
select 3
#查看数据库大小
DBsize
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> DBsize
(integer) 0
#查看数据库所有的key
keys *
清除数据库
#清除当前数据库
flushdb
#清空全部的数据库
flushall
redis是单线程的
redis是很快的,是基于内存操作的。,CUP不是redis的性能瓶颈 ,Redis的瓶颈是根据机器的内存和网络的带宽。
Redis为什么单线程还这么快?
1、误区1:高性能的服务器一定是多线程的?
2、误区2:多线程一定比单线程效率高。
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的。
五大基本数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
Redis-key
#添加
set name hello
#获取
get name
#查看一个key是否存在
exists keyname
#移除key
move keyname#1表示当前数据库
#设置过期时间 10秒
expire keyname
# 查看过期时间
ttl keyname
#查看key的数据类型
type keyname
官方命令查询 http://www.redis.cn/commands.html
string
字符串类型
追加字符串
#在某个key的值后面追加字符串
append keyname 'hello'
127.0.0.1:6379> set name cbbgs
OK
127.0.0.1:6379> get name
"cbbgs"
127.0.0.1:6379> append name 'hello'
(integer) 10
127.0.0.1:6379> get name
"cbbgshello"
查看value的长度
#查看key的值的长度
strlen keyname
127.0.0.1:6379> strlen name
(integer) 10
自增和自减
#自动加一
incr keyname
#自动减一
decr keyname
#设置步长
incrby keyname 10 #自动加10
decrby keyname 10 #自动减10
字符串范围
#字符串范围 getrange
getrange keyname 0 3 # 截取字符串0-3的字符
getrange keyname 0 -1 #查看全部字符串
127.0.0.1:6379> set name hello_cbbgs
OK
127.0.0.1:6379> getrange name 0 3
"hell"
127.0.0.1:6379> getrange name 0 -1
"hello_cbbgs"
替换字符串
# 替换 setrange
setrange name 7 xxx #从第七个字符开始替换
127.0.0.1:6379> setrange name 7 xxx
(integer) 11
127.0.0.1:6379> get name
"hello_cxxxs"
setex
#设置过期时间,过期之前会变成你设置的
setex keyname 30 'hello' #设置keyname的值为hello,10,秒过期
127.0.0.1:6379> setex name 10 "hello"
OK
127.0.0.1:6379> get name
"hello"
setnx
#不存在的设置
setnx keyname 'hello' #如果keyname不存在会创建keyname ,如果存在会创建失败
批量操作
# 批量设置
mset k1 v1 k2 v3 k3 v3
#批量获取
mget k1 k2 k3
#如果不存在就创建,存在就创建失败
msetnx k1 v1 k4 v4 #要么一起成功,要么一起失败
127.0.0.1:6379> mset k1 v1 k2 v3 k3 v3
OK
127.0.0.1:6379> mget k1 k3 k2
1) "v1"
2) "v3"
3) "v3"
对象
#设置一个user:1对象,值为json字符串来保存一个对象
set user:1 {name:zhangsan,age 3}
#这个的key是一个巧妙的设计, user:id:filed
27.0.0.1:6379> mset user:1:name zzhangsan user:1:age 2
OK
127.0.0.1:6379> get user
(nil)
127.0.0.1:6379> mget user:1:name user:1:age
1) "zzhangsan"
2) "2"
getset
#先get再set
getset key value
127.0.0.1:6379> getset db redis #如果不存在,返回null,则去创建
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mm #如果存在则会返回值,再去设置值
"redis"
127.0.0.1:6379> get db
"mm"
string应用场景,value除了可以是字符串还可以是数字。统计数量,计数器,粉丝数。
List
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
在list命令都是l开头的。
添加到列表
#添加到列表的头部(左)
lpush list one
#添加到列表的尾部(右)
rpush list one
#得到部分值
lrange list 0 -1
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lrange list 0 -1 #查看元素
1) "two"
2) "one"
移除一个元素
#移除左边的
lpop list
#移除右边
rpop list
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "one"
127.0.0.1:6379> lpop list
"two"
127.0.0.1:6379> Rpop list
"one"
127.0.0.1:6379> lrange list 0 -1
1) "one"
#移除指定的值
lrem list 1 one #移除一个one
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "one"
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
通过下标得到值
#通过下标的到值
127.0.0.1:6379> lindex list 0
"one"
list长度
llen list
127.0.0.1:6379> llen list
(integer) 4
截取很多元素
#截取很多元素
ltrim list 1 2
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
rpoplpush
#移除最后一个元素,并添加到另一个list中
rpoplpush list list1
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> rpoplpush list list1
"1"
127.0.0.1:6379> lrange list1 0 -1
1) "1"
127.0.0.1:6379> lrange list 0 -1
1) "2"
设置值
#设置值 将list 下标为1 的值改成hello
lset list 1 hello
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "2"
127.0.0.1:6379> lset list 1 hello
OK
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "hello"
插入一个值在某个元素后面或前面
#在list中hello后面插入word
linsert list before hello word
#在list中hello前面插入word
linsert list after hello word
127.0.0.1:6379> linsert list before hello wperld
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "wperld"
3) "hello"
127.0.0.1:6379> linsert list after hello word
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "wperld"
3) "hello"
4) "word"
set
Redis 的 Set 是 string 类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。不能重复。
添加元素
#添加
sadd set1 hello
127.0.0.1:6379> sadd set1 hello
(integer) 1
127.0.0.1:6379> sadd set1 world
(integer) 1
查看set的所有值
smembers setname
127.0.0.1:6379> smembers set1
1) "world"
2) "hello"
sismember
#判断一个元素是不是在集合中
sismember setname hello
127.0.0.1:6379> sismember set1 hello
(integer) 1
获取元素的个数
#得到个数
scard setname
移除元素
#移除指定元素
srme setname hello
127.0.0.1:6379> srem set1 hello
(integer) 1
127.0.0.1:6379> smembers set1
1) "world"
#随机 删除key
spop setname
127.0.0.1:6379> smembers set1
1) "world"
2) "python"
3) "php"
4) "java"
127.0.0.1:6379> spop set1
"php"
127.0.0.1:6379> smembers set1
1) "world"
2) "python"
3) "java"
#将一个指定的元素删除,加到另一个集合
smove set1 set2 java
127.0.0.1:6379> smove set1 set2 java
(integer) 1
127.0.0.1:6379> smembers set2
1) "java"
随机元素
#随机抽取一个元素
srandember setname 1
127.0.0.1:6379> srandmember set1
"world"
127.0.0.1:6379> srandmember set1
"hello"
并集
#两个集合的并集
sunion set1 set2
差集
#两个集合的差集
sdiff set1 set2
交集
#两个集合的交集
sinter set1 set2
Hash(哈希)
map集合,key-map 值是map集合
Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象
添加和获取
#添加
hset hashname key value
#获取
hget hashname key
#同时添加多个值
hmset hashname k1 v1 k2 v2
#同时获得多个值
hmget hashname k1 k2
hgetall hashname
127.0.0.1:6379> hmset hash1 k1 v1 k2 v2
OK
127.0.0.1:6379> hmget hash1 k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> hgetall hash1
1) "name"
2) "cbbgs"
3) "k1"
4) "v1"
5) "k2"
6) "v2"
删除
#删除指定的值
hdel hashname key
获取长度
#长度
hlen hashname
127.0.0.1:6379> hlen hash1
(integer) 3
判断是否存在
#判断hash的key是否存在
hexists hashname key
只获得key
hkeys hashname
股只得value
hvalues hashname
自增自减
#自增
hincrby hashname key 1
#自减
hdecrby hashname key -1
应用:user :name age
Zset
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
添加
#添加
zadd zsetname 1 hello
127.0.0.1:6379> zadd zsetname 1 hello
(integer) 1
127.0.0.1:6379> zadd zsetname 2 woerld 3 java
(integer) 2
127.0.0.1:6379> zrange zsetname 0 -1
1) "hello"
2) "woerld"
3) "java"
排序
#排序,负无穷到正无穷,返回带值
zrangebyscore zsetname -inf +inf withscores
#添加三个用户
127.0.0.1:6379> zadd zset2 money 2000
(error) ERR value is not a valid float
127.0.0.1:6379> zadd zset2 2000 money
(integer) 1
127.0.0.1:6379> zadd zset2 20999 a
(integer) 1
127.0.0.1:6379> zadd zset2 20000 b
(integer) 1
127.0.0.1:6379> zrangebyscore zset2 -inf +inf withscores
1) "money"
2) "2000"
3) "b"
4) "20000"
5) "a"
6) "20999"
#从大到小排序
zrevrange zsetname 0 -1
移除元素
#移除元素
zrem zsetname money
127.0.0.1:6379> zrem zset2 money
(integer) 1
查看数量
#获取有序集合的个数
zcard zzsetname
#查看区间的值有几个
zcount zsetname 1 3 #1 -3之间有几个成员数量
三种特殊数据类型
geospatial
查询经纬度
https://www.bejson.com/convert/map/
添加位置
# 添加位置
#两极无法直接导入,
# 参数 key 值(纬度 经度 名称)
geoadd china:city 116.40 39.90 北京
geoadd china:city 121.47 31.23 上海
geoadd china:city 106.50 29.53 重庆 114.05 22.52 深圳
查询位置的值
# 获取指定位置的经度和纬度
geopos china:city 北京 重庆
127.0.0.1:6379> geopos china:city 北京 重庆
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
两个位置的距离
# m 米
# km 千米
geodist China:city 北京 重庆
127.0.0.1:6379> geodist china:city 北京 重庆 km
"1464.0708"
范围
# 在 110 30 为点,1000为半径的位置 withcoord 带经纬度 withdist 距离 count 2 显示几个
georadius china:city 110 30 1000 km withcoord
以城市名查询
#指定位置查询其他位置
georadiusbymember china:city 北京 1000 km
查看全部城市
#查看全部城市
zrange china :city 0 -1
#删除
zrem china :city beijingchina :city
hyperloglogs
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
网页的UV(一个人访问一个网站多次,只算一次。
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
下表列出了 redis HyperLogLog 的基本命令:
序号 | 命令及描述 |
---|---|
1 | [pfadd ement element …] 添加指定元素到 HyperLogLog 中。 |
2 | [pfcount key …] 返回给定 HyperLogLog 的基数估算值。 |
3 | [ pfmerge destkey sourcekey sourcekey …] |
Bitmaps
统计用户信息,登录、打卡等。两个状态都可以用这个。
Bitmap位图,数据结构,都是二进制进行记录,只有0和1,
setbit
#添加
setbit sign 0 1
setbit sign 1 0
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
get bit
#查看值
getbit sign 2
127.0.0.1:6379> getbit sign 2
(integer) 0
查看打卡的天数
bitcount sign
127.0.0.1:6379> bitcount sign
(integer) 1
事务
Redis 要么同时成功,同时失败!
Redis单条命令保存时原子性的,但是事务不是原子性。
Redis事务的本质:一组命令集合,一个事务中的所有命令都会被序列化,在事务执行的过程中,会按顺序执行!
一次性、顺序性,排他性。
Redis没有隔离级别的概念。并没有被直接执行,只有执行命令的时候才会执行 !
Redis的事务:
- 开启事务(multi)
- 命令 入队(…)
- 执行事务(exec )
正常执行事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v2 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2#命令入队
QUEUED
127.0.0.1:6379> get k1#命令入队
QUEUED
127.0.0.1:6379> get k2#命令入队
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) "v2"
#取消事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
# 事务中的队列都不会被执行
# 如果在事务队列中部分有错误,会跳过错误的执行其他的
编译型异常
所有的执行都不会被执行。直接错误!
运行时异常
执行命令时,其他正确的队列会继续执行,错误指令抛出异常。
监控
悲观锁
很悲观,什么时候都会出问题 ,无论做什么都会加锁!
乐观锁
- 认为什么时候都不会出现问题,所以不会上锁!更新数据的时候判断一下,在此期间是否有人修改过数据。
- 获取version
- 更新的时候对比version
Redis监测
正常情况,单线程执行
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视money对象
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生改动,这个时候正常执行!
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby money 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 100
测试多线程修改值,使用watch可以当Redis的乐观锁!
怎么从新操作呢,要先解锁,unwatch
Jedis
用java来操作redis
开始在 Java 中使用 Redis 前, 我们需要确保已经安装了 redis 服务及 Java redis 驱动,且你的机器上能正常使用 Java。 Java的安装配置可以参考我们的 Java 开发环境配置 接下来让我们安装 Java redis 驱动:
- 首先你需要下载驱动包 下载 jedis.jar,确保下载最新驱动包。
- 在你的 classpath 中包含该驱动包。
使用java操作Redis中间件。
测试
1、导入依赖
<!--redis依赖-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2、测试编码
- 链接数据库
- 操作命令
- 断开命令
public class RedisTest {
public static void main(String[] args) {
/*new 一个jedis对象即可*/
Jedis jedis = new Jedis("47.93.49.117",6379);
/*所有的指令都是我们之前学的*/
System.out.println(jedis.ping());
}
}
常用的api
String
List
Set
Hash
zset
Jedis的事务
public class RedisTest {
public static void main(String[] args) {
/*new 一个jedis对象即可*/
Jedis jedis = new Jedis("47.93.49.117",6379);
/*所有的指令都是我们之前学的*/
/* System.out.println(jedis.ping());*/
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","zhangsan");
/*
* 开启事务*/
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
multi.set("user1",result);
multi.set("user2",result);
/*
* 执行事务
* */
multi.exec();
jedis.close();
}
}
Springboot整合Redis
springboot操作数据.springboot2.x之后,原来使用的jedis被替换成了lettuce
jedis:采用直连,多个线程操作的话,是不安全的,使用jedis pool连接池。
lettuce:采用net同意,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据。
整合项目
1、导入依赖
<dependencies>
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、Redis配置
#Redis配置
spring.redis.host=47.93.49.117
spring.redis.pore=6379
3、测试 在springboot的test包中测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
}
@Test
public void test1(){
//opsForValue 相对于操作字符串
//opsForList 相当于操作List ...
redisTemplate.opsForValue().set("cbbgs","陈斌");
System.out.println(redisTemplate.opsForValue().get("cbbgs"));
}
}
关于对象的保存需要序列化之后用json字符串;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Users implements Serializable {
private String name;
private Integer age;
}
测试里面
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test2() throws JsonProcessingException {
//用json字符串来传递对象
Users user = new Users("陈斌",23);
String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get(user));
}
}
配置自己的RedisTemplate
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
/*
* 编写自己的redistemplate
* */
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)throws UnknownHostException {
//设置具体的序列化方式
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object> ();
template.setConnectionFactory (factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer (Object.class);
ObjectMapper om = new ObjectMapper ();
om.setVisibility (PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping (ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper (om);
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;
}
}
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
我们可以继续编写一个工具类来开发,不使用原生的。
package cbbgs.cn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
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;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire (String key) {
return redisTemplate.getExpire (key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey (String key) {
try {
return redisTemplate.hasKey (key);
} catch (Exception e) {
e.printStackTrace ();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings ("unchecked")
public void del (String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete (key[0]);
} else {
redisTemplate.delete (Arrays.asList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get (String key) {
return key == null ? null : redisTemplate.opsForValue ().get (key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set (String key, Object value) {
try {
redisTemplate.opsForValue ().set (key, value);
return true;
} catch (Exception e) {
e.printStackTrace ();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
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;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr (String key, long delta) {
if (delta < 0) {
throw new RuntimeException ("递增因子必须大于0");
}
return redisTemplate.opsForValue ().increment (key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr (String key, long delta) {
if (delta < 0) {
throw new RuntimeException ("递减因子必须大于0");
}
return redisTemplate.opsForValue ().increment (key, - delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget (String key, String item) {
return redisTemplate.opsForHash ().get (key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget (String key) {
return redisTemplate.opsForHash ().entries (key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset (String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash ().putAll (key, map);
return true;
} catch (Exception e) {
e.printStackTrace ();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
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;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
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;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
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;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel (String key, Object... item) {
redisTemplate.opsForHash ().delete (key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey (String key, String item) {
return redisTemplate.opsForHash ().hasKey (key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr (String key, String item, double by) {
return redisTemplate.opsForHash ().increment (key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr (String key, String item, double by) {
return redisTemplate.opsForHash ().increment (key, item, - by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet (String key) {
try {
return redisTemplate.opsForSet ().members (key);
} catch (Exception e) {
e.printStackTrace ();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey (String key, Object value) {
try {
return redisTemplate.opsForSet ().isMember (key, value);
} catch (Exception e) {
e.printStackTrace ();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet (String key, Object... values) {
try {
return redisTemplate.opsForSet ().add (key, values);
} catch (Exception e) {
e.printStackTrace ();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
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;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize (String key) {
try {
return redisTemplate.opsForSet ().size (key);
} catch (Exception e) {
e.printStackTrace ();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
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=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
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;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize (String key) {
try {
return redisTemplate.opsForList ().size (key);
} catch (Exception e) {
e.printStackTrace ();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
* 时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex (String key, long index) {
try {
return redisTemplate.opsForList ().index (key, index);
} catch (Exception e) {
e.printStackTrace ();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
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 时间(秒)
*/
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;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet (String key, List<Object> value) {
try {
redisTemplate.opsForList ().rightPushAll (key, value);
return true;
} catch (Exception e) {
e.printStackTrace ();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
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;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
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;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
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;
}
}
}
使用方法
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisUtil redisUtil;
@Test
public void test3(){
redisUtil.set("name","cbbgs");
System.out.println(redisUtil.get("name"));
}
}
Redis持久化
RDB
RDB即redis database,它是redis默认采用支持持久化的方式。RDB通过快照实现持久化的支持,当满足一定条件时,RDB将对内存中的所有数据生成快照,并存放到硬盘中,默认存放在当前执行redis服务的根目录的dump.rdb中。
触发机制
1、save 的规则满足的情况下会自动触发rdb规则
2、执行flushall命令也会触发rdb规则
3、退出redis也会触发
恢复rdb
1、只需要将rdb文件放在我们redis启动的目录就可以,redis启动会自动恢复文件中的数据。
2、查看存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/data" #把 文件放在在这里就会自动恢复
优缺点
1、适合大规模数据恢复
2、如果对数据完成性要求不高!
1、需要一定的时间间隔!
2、fork进程的时候会占用一定的内存空间
AOF
AOF持久化(原理是将Reids的操作日志以追加的方式写入文件)
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
默认是不开启 的,需要用要开启!我们只需要appendonly改成yes即可!
优点
(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。(2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
缺点
(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。
Redis发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
下表列出了 redis 发布订阅常用命令:
序号 | 命令及描述 |
---|---|
1 | [PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。 |
2 | [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。 |
3 | PUBLISH channel message 将信息发送到指定的频道。 |
4 | [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。 |
5 | [SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。 |
6 | [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。 |
#订阅端
127.0.0.1:6379> SUBSCRIBE cbbgs #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cbbgs"
3) (integer) 1
1) "message" # 等待消息
2) "cbbgs" #那个频道的信息
3) "hello" #消息的具体内容
1) "message"
2) "cbbgs"
3) "redis"
#发送端
127.0.0.1:6379> publish cbbgs hello #发布者发布信息
(integer) 1
127.0.0.1:6379> publish cbbgs redis
(integer) 1
redis的主从复制
主从复制,读写分离! 80% 的情况下都是在进行读操作!减缓服务器的压力! 架构中经常使用! |
13.1、概念
主从复制,是指将一台Redis
服务器的数据,复制到其他的Redis服务器。前者称为主节点(master
),后者称为从节点(slave
);数据的复制是单向的,只能由主节点到从节点。
- 默认情况下,每台Redis服务器都是主节点;且-个主节点可以有多个从节点(或没有从节点) ,但一个从节点只能有一个主节点。
13.2、主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
一般来说 ,要将
Redis
运用于工程项目中,只使用一台Redis
是万万不能的(宕机) 。