redis学习

NoSQL数据库

NOt only sql 不仅仅是sql,非关系型数据库

NoSQL数据库特点

1、方便扩展(数据之间没有关系,好扩展)
2、大数据量高i性能
3、数据类型是多样性的(不需要事先设计数据库!随取随用)
4、传统RDBMS和NOSQL

传统的RDBMS

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

NoSQL

  • 不仅仅·是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文档存储,图形数据库
  • 最终一次性
  • CAP定理和BASE
  • 高性能,高可见,高科扩

3v+3高

3v

  • 海量
  • 多样
  • 实时

3高

  • 高性能
  • 高并发
  • 高可扩

NOSQL的四大分类

  • kv键值对
  • 文档型数据库(bson格式和json一样)
  •   	MongoDB
    
  •   			是一个基于分布式文件存储的数据库,C++编写,主要处理大量的文档
    
  •   			是介于关系型数据库与非关新型数据库之间的产品!
    
  • 列存储数据库
  • 图关系数据库
  • 在这里插入图片描述

Redis入门

概述

redis是什么

Redis (Remote Dictionary Server)远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言的
redis会周期性的把更新的数据写入磁盘或者把修改操作西而入追加的记录文件,并且在此基础上实现主从同步。

redis能干吗

1、内存存储、持久化、内存中是断电即失
2、效率高、可以用于高速缓存
3、发布订阅信息
4、地图信息分析
5、计时器、计数器(浏览量)
6…

特性

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

redise的安装

我是使用docker进行安装的

docker pull redis   #下载镜像
# 启动redis容器
docker run -itd --name redis-test -p 6379:6379 redis
# 进入redis容器
[root@iZf8zeb4pvftjcmv58dmy8Z ~]# docker exec -ti 1ee93b679ab1 redis-cli
127.0.0.1:6379> 

在这里插入图片描述
存入key=“name”, value="painye"的键值对

127.0.0.1:6379> set name painye
OK
127.0.0.1:6379> get name
"painye"

在这里插入图片描述
查看redis的进程是否开启

[root@iZf8zeb4pvftjcmv58dmy8Z ~]# ps -ef|grep redis
polkitd   7020  6998  0 15:37 ?        00:00:13 redis-server *:6379
root     21420  5606  0 20:00 pts/0    00:00:00 docker exec -it 1ee93b679ab1 redis-cli
root     21436  6998  0 20:00 pts/0    00:00:00 redis-cli
root     21646 21544  0 20:02 pts/1    00:00:00 grep --color=auto redis

关闭redis服务

127.0.0.1:6379> shutdown
[root@iZf8zeb4pvftjcmv58dmy8Z ~]# 
# 查看进程
[root@iZf8zeb4pvftjcmv58dmy8Z ~]# ps -ef|grep redis
root     21793 21544  0 20:04 pts/1    00:00:00 grep --color=auto redis

性能测试


root@1ee93b679ab1:/data# redis-benchmark -h localhost -p 6379 -c 100 -n 100000^C

在这里插入图片描述

基础的知识

redis有16个数据库,默认用第0个,使用select切换数据库。但是docker中的数据库似乎不足16个
在这里插入图片描述

常用基础命令

DBSIZE 查看数据库内容大小
在这里插入图片描述
keys * 查看所有的key

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

flushdb 清除当前数据库

127.0.0.1:6379> flushDB
OK
127.0.0.1:6379> 
root@1ee93b679ab1:/usr/local/bin# redis-cli                   
127.0.0.1:6379> DBSIZE
(integer) 0
127.0.0.1:6379> 

清空全部数据库 FLUSHALL

127.0.0.1:6379> DBSIZE
(integer) 0
127.0.0.1:6379> set name 22
OK
127.0.0.1:6379> DBSIZE
(integer) 1
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> FLUSHALL
OK
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> DBSIZE
(integer) 0

Redis是单线程的

Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是机器的内存和网络带宽

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

误区:
- 1、高性能的服务器一定是多线程的?
- 2、多线程(上下文切换)一定比单线程效率高?cpu频繁的上下文切换也十分的耗时,所以多线程 不一定比单线程快

redis的五种数据类型

redis-key

判断key是否存在: EXISTS

127.0.0.1:6379> set name painye
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> EXISt name
(error) ERR unknown command `EXISt`, with args beginning with: `name`, 
127.0.0.1:6379> EXISTS name
(integer) 1

删除指定key: move key 1 (表示将kv从当前数据库移到1数据库)

127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> move name 0
(error) ERR source and destination objects are the same
127.0.0.1:6379> move name 1
(integer) 0
# 给指定key设置过期时间秒为单位
127.0.0.1:6379> EXPIRE name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> keys *

String(字符串)

给特定key的value追加字符串,如果当前key不存在就创建 append key str

127.0.0.1:6379[3]> get name
"painye"
127.0.0.1:6379[3]> append name 222
(integer) 9
127.0.0.1:6379[3]> get name
"painye222"
######################################################################
i++
127.0.0.1:6379[3]> set views 0   		# 给views设值为0 
OK
127.0.0.1:6379[3]> get views			# 自加1
"0"
127.0.0.1:6379[3]> incr views
(integer) 1
127.0.0.1:6379[3]> incr views
(integer) 2
127.0.0.1:6379[3]> incr views
(integer) 3
127.0.0.1:6379[3]> incr views
(integer) 4
127.0.0.1:6379[3]> incr views 3
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379[3]> incrby  views 3		# 自加3,后面的数值是步长
(integer) 7
127.0.0.1:6379[3]> incrby  views 3
(integer) 10
127.0.0.1:6379[3]> decr
(error) ERR wrong number of arguments for 'decr' command
127.0.0.1:6379[3]> decr views			# 自减1
(integer) 9
127.0.0.1:6379[3]> decr views
(integer) 8
127.0.0.1:6379[3]> decryby views 2		# 自减2,后面的2是步长
(error) ERR unknown command `decryby`, with args beginning with: `views`, `2`, 
127.0.0.1:6379[3]> decrby views 2
(integer) 6
127.0.0.1:6379[3]> decrby views 2
(integer) 4
######################################################################################
截取字符串的范围
127.0.0.1:6379[3]> set key 01234567
OK
127.0.0.1:6379[3]> getrange 0 1					# 截取从下标0到下标1的字符串
(error) ERR wrong number of arguments for 'getrange' command
127.0.0.1:6379[3]> getrange key 0 1
"01"
127.0.0.1:6379[3]> getrange key 0 -1			# 截取从0开始到字符串尾
"01234567"
#######################################################################################
替换
127.0.0.1:6379[3]> set k2 123456789
OK
127.0.0.1:6379[3]> SETRANGE k2  3 xxxxx   	# 将k2从下标为3开始替换成"xxxx"
(integer) 9
127.0.0.1:6379[3]> get k2
"123xxxxx9"

#######################################################################################
# setex (set with expire)     # 设值过期时间
# setnx (set if no exist)     # 不存在再设置,在分布式锁中会经常使用
127.0.0.1:6379[3]> setex k3 14 33333     # set key=k3 过期时间=14秒 value=3333
OK
127.0.0.1:6379[3]> ttl k3
(integer) 8
127.0.0.1:6379[3]> setnx k4 4444		# 如果k4不存在,创建k4设值为4444, 成功返回1
(integer) 1
127.0.0.1:6379[3]> keys *
1) "name"
2) "key"
3) "k2"
4) "k4"
5) "views"
127.0.0.1:6379[3]> get k4
"4444"
127.0.0.1:6379[3]> setnx k4 fffff		# 此处k4已存在,所以设值为ffff失败
(integer) 0
127.0.0.1:6379[3]> get k4
"4444"
######################################################################################
mset
mget

127.0.0.1:6379[3]> mset k1 v1 k2 v2 k3 v3				# 同时设置多个键值
OK
127.0.0.1:6379[3]> keys *
1) "k3"
2) "k1"
3) "name"
4) "key"
5) "k2"
6) "k4"
7) "views"
127.0.0.1:6379[3]> mget k1 k2 k3						# 同时拿到多个key的value
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379[3]> msetnx k1 vw k4 v4		# msetnx是原子性操作,要么同时成功,要么同时失败
(integer) 0
#######################################################################################
向redis中存入对象,避免使用复杂的json字符串格式
127.0.0.1:6379[3]> mset user:1:name zhangsan user:1:age 22
OK
127.0.0.1:6379[3]> mget user:1:name user:1:age
1) "zhangsan"
2) "22"

####################################################################################### 

List

所有的·list命令都是由l开头的

127.0.0.1:6379> keys *
(empty array)
# 将一个值或多个值插入到列表的头部(左边)
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange 0 -1								# 通过lrange取值
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list right						# rpush key value 将元素叉在列表的右边
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
#######################################################################################
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpush list
(error) ERR wrong number of arguments for 'lpush' command
127.0.0.1:6379> lpop list						# 弹出列表最左边的元素
"three"
127.0.0.1:6379> rpop list						# 弹出列表最右边的元素
"right"
#######################################################################################
# 通过索引取值
127.0.0.1:6379> lindex list 0					# lindex key index
"two"
127.0.0.1:6379> lindex list -1
"one"
#######################################################################################
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 3
(integer) 3
127.0.0.1:6379> llen
(error) ERR wrong number of arguments for 'llen' command
127.0.0.1:6379> Llen
(error) ERR wrong number of arguments for 'llen' command
127.0.0.1:6379> llen list						# 获取列长度
(integer) 3
#######################################################################################
移除元素

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> lrem list 1 1				# 移除list元素中的1个“1”
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "3"
3) "2"
127.0.0.1:6379> lrem list 2 3				# 移除list元素中的2个“3”
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "2"
#######################################################################################
截取整个列表的指定下标范围的元素

127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> ltrim list 1 2				# 保留key中的下标为1-2的元素
OK
127.0.0.1:6379> lrange list 0 -1
1) "3" 
2) "2"
#######################################################################################
rpoplpush # 移除列表最后一个元素,并将其压入一个新的列表
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> rpoplpush list list2		# 将list中的最最右边的元素取出然后压入List2中
"1"
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
127.0.0.1:6379> lrange list2 0 -1
1) "1"

#######################################################################################
exist key 					# 判断列表key是否存在
lset key index value   		# 为key的下标为index的元素重新赋值,不能创建新的元素

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> exists list				# list列表是否存在, 1存在, 0 不存在
(integer) 1
127.0.0.1:6379> lset list 3 x			# 下标为3的元素不存在所以赋值失败
(error) ERR index out of range
127.0.0.1:6379> lpush list 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> lset list 3 x			# list中已经有下标为3的元素,所以可以成功为其其重新赋值
OK
127.0.0.1:6379> lrange list 0 -1		# 注意这里的下标每次都是从左往右,pusd后的元素总是为0
1) "4"
2) "3"
3) "2"
4) "x"
127.0.0.1:6379> lset list 0 x
OK
127.0.0.1:6379> lrange list 0 -1
1) "x"
2) "3"
3) "2"
4) "x"

#######################################################################################
linsert 			#向key列表中的value【前插|后插】一个新的元素
127.0.0.1:6379> lrange list 0 -1
1) "x"
2) "3"
3) "2"
4) "x"
127.0.0.1:6379> linsert list before 2 2.5    	#向list列表中的值为2的元素前插入2.5
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "x"
2) "3"
3) "2.5"
4) "2"
5) "x"
127.0.0.1:6379> linsert list after 3 2.9 		#向list列表中的值为3的元素后插入2.9
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "x"
2) "3"
3) "2.9"
4) "2.5"
5) "2"
6) "x"

#######################################################################################

set(集合)

set中的key不能重复

#######################################################################################
127.0.0.1:6379> sadd myset 1 				# 向set中添加元素
(integer) 1
127.0.0.1:6379> sadd myset 2				
(integer) 1
127.0.0.1:6379> sadd myset 3
(integer) 1
127.0.0.1:6379> smembers myset				# 查看set中的所有成员
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> sismember myset 1			# 查看set中是否存在某元素
(integer) 1
127.0.0.1:6379> sismember myset 5
(integer) 0

#######################################################################################
127.0.0.1:6379> scard myset					# 获取set中的个数
(integer) 3
127.0.0.1:6379> srem myset 1				# 删除set中的值为1的元素
(integer) 1 
127.0.0.1:6379> smembers myset
1) "2"
2) "3"

#######################################################################################
set 无序不重复集合
127.0.0.1:6379> srandmember myset 3		# srandmember 随机抽取指定set的其中3个元素
1) "3"
2) "67"
3) "8"
127.0.0.1:6379> srandmember myset		# srandmember 随机抽取指定set的其中1个元素
"4"
127.0.0.1:6379> srandmember myset
"4"
127.0.0.1:6379> srandmember myset
"67"
127.0.0.1:6379> 
127.0.0.1:6379> srandmember myset
"4"
127.0.0.1:6379> srandmember myset
"4"
127.0.0.1:6379> 
127.0.0.1:6379> srandmember myset
"4"
127.0.0.1:6379> srandmember myset
"67"
127.0.0.1:6379> srandmember myset
"67"
127.0.0.1:6379> srandmember myset
"3"
127.0.0.1:6379> srandmember myset
"6"
#######################################################################################
127.0.0.1:6379> smembers myset
1) "2"
2) "3"
3) "4"
4) "5"
5) "6"
6) "8"
7) "67"
127.0.0.1:6379> spop myset					#随机删除一个元素
"5"
127.0.0.1:6379> spop myset
"6"
127.0.0.1:6379> smembers myset
1) "2"
2) "3"
3) "4"
4) "8"
5) "67"

#######################################################################################

#######################################################################################

Hash

key-Map(key-value)


127.0.0.1:6379> hset myhash k1 1   				# 添加元素 
(integer) 1
127.0.0.1:6379> hget myhash k1					# 根据key获取value
"1"
127.0.0.1:6379> hmset myhash k1 2 k2 3 k3 4		# 设置多个key value
OK
127.0.0.1:6379> hmget myhash k1  k2 k3			# 同时获取多个key
1) "2"
2) "3"
3) "4"

127.0.0.1:6379> hgetAll myhash					# 获取所有key
1) "k1"
2) "2"
3) "k2"
4) "3"
5) "k3"
6) "4"

127.0.0.1:6379> hdel myhash k1					# 删除特定key
(integer) 1
127.0.0.1:6379> hgetAll myhash
1) "k2"
2) "3"
3) "k3"
4) "4"
127.0.0.1:6379> hlen myhash						# 获取hash的字段数量
(integer) 2



ZSET

可以排序的set

127.0.0.1:6379> zadd salary 1 x   # zset中添加元素
(integer) 1
127.0.0.1:6379> zadd salary 2 y
(integer) 1
127.0.0.1:6379> zadd salary 3 z
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf  # 根据salary作为标准进行升序
1) "x"
2) "y"
3) "z"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 将score也展示出来
1) "x"
2) "1"
3) "y"
4) "2"
5) "z"
6) "3"
######################################################################################
127.0.0.1:6379> zcard salary					# 查看zset集合中的数据个数 
(integer) 3
127.0.0.1:6379> zrange salary 0 -1				# 查看下标下的所有元素
1) "x"
2) "y"
3) "z"
127.0.0.1:6379> zrem salary x					# 删除集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "y"
2) "z"

事务

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务的执行过程中,会按照顺序执行,一次性、顺序性、排他性、
Redis事务吗没有隔离级别的概念!
所有命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行
==Redis单条命令保证了原子性,但是事务不保证原子性!
redis的事务

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)
# 开启事务
127.0.0.1:6379> mulit
(error) ERR unknown command `mulit`, with args beginning with: 
127.0.0.1:6379> multi
OK

# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED

#执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK

放弃事务

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED

# 取消事务
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> get k2
(nil)

编译时异常导致事务取消,事务中的所有命令都不会被执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED

# 故意写错的命令
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec
# 事务已经取消了
(error) EXECABORT Transaction discarded because of previous errors.

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED

# 这里会发生运行是异常, k1是一个字符串,所以会出现运行是异常
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range		# 虽然第二条增加命令出错,但不会事务中的其他命令的执行
3) OK
4) OK

监控

悲观锁
  • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁

乐观锁

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候取判断一下,在此期间是否有人修改过这个锁
  • 获取version
  • 更新的时候比较version

redis的监视测试

事务正常执行

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 20
OK
127.0.0.1:6379> watch money							# 监控money
OK
127.0.0.1:6379> multi								# 开启事务
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec							# 正常执行
1) (integer) 80
2) (integer) 40

测试多线程修改值,使用watch可以当作redis的乐观锁进行操作

线程1监视了money,开启事务在执行命令之前,线程2修改了money值,会导致线程1的事务执行出错
在这里插入图片描述
在这里插入图片描述

jedis

我们要使用java来操作redis

什么是jedis
是redis官方推荐的java连接开发工具! 使用java来操作redis中间件!如果你要使用java来操作redis,那么一定要对jedis十分的熟悉

1、导入对相应的依赖

<!--导入jedis的包-->
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

2、编码测试

  • 连接数据库
  • 操作命令
  • 断开连接

在这里插入图片描述

SpringBoot整合

application.properties

// 我这里是远程连接所以是外网地址,如果是本地2连接就是localhost
spring.redis.host=47.113.xxx.xx
//端口号是Redis默认的端口号
spring.redis.port=6379

测试

简单的String类型测试

@SpringBootTest
class SpringRedisApplicationTests {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Test
    void contextLoads() {
    	//操控Redis中的String基本类型,api与Redis的命令基本一致
        redisTemplate.opsForValue().set("key", "1111111");
        System.out.println(redisTemplate.opsForValue().get("key"));
    }
  }

测试结果

在这里插入图片描述

自定义对象的测试

user对象

@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private String name;
    private Integer age;
}

自定义的配置类,配置自定义的redisTemplate。
key设置了String类型的序列化, value自定义对象使用的是json的序列化

@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 为了开发方便一般泛型<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //json序列化配置
        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);

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

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

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

        return template;
    }
}

测试代码

@Test
    public void test() throws JsonProcessingException{
        User user= new User("胖虎", 23);
        redisTemplate.opsForValue().set("user", user);
        System.out.println(redisTemplate.opsForValue().get("user"));

    }

测试结果
在这里插入图片描述
在这里插入图片描述

Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化的功能

RDB Redis DataBase

什么是RDB
在这里插入图片描述

在指定的时间间隔内将内存中的数据集快照写入磁盘中,也就是SnapShot快照,它恢复时将快照文件直接督导内存中。
Redis会单独创建(fork)以这个紫禁城来进行持久化,会先将数据写入到一个临时的文件中去,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不会进行任何的IO操作的,这确保了高性能。如果需要大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF防水更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

RDB保存的文件
在这里插入图片描述
在这里插入图片描述

触发机制

1、save的规则满足的情况下,会自动触发rdb规则
2、执行flushAll命令,也会触发生成rdb
3、退出redis,也会产生rdb文件

备份的时候会自动生成rdb

如何恢复rdb文件

1、只需要将rdb文件放在我们redis的启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据
2、查看redis的启动目录

root@5c666afc5b50:/data# redis-cli
127.0.0.1:6379> config get dir
1) "dir"
2) "/etc"

优点:

  • 适合多大规模的数据恢复!
  • 对数据的完整性要求不高!

缺点:

  • 需要一定的时间间隔进行操作,如果Redis意外宕机,最后一次修改的数据就没有了
  • fork进程的时候,会占用一定的内存空间

AOF (Append Only File)

将我们所有的文件都记录下来,history,恢复的时候就把这个文件全部再执行一遍
在这里插入图片描述
以日志的形式来记录每个写操作,将redis执行过程中所有的指令都记录下来(读操作不记录),只需追加文件但不可改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容指令从前到后执行一次完成数据恢复工作。

Aof保存的appendonly.aof文件
在这里插入图片描述

默认是no不开启,需要改成yes
重启Redis即可生效
入过这个aof文件有错误,Redis就会无法启动。Redis中的redis-check-aof --fix工具会对.aof文件进行修复,重启Redis就可以恢复
在这里插入图片描述

优点
-1、每一次修改都同步,文件的完整性会更加好
-2、每秒同步一次,可能会丢失一秒的数据
-3、从不同步,效率最高
缺点
-1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
-2、AOF运行的效率也很低

Redis发布订阅

redis发布订阅是一种消息通信模式:发送者发送消息,订阅者接收消息。
Redis客户端可以订阅任意数量的频道

在这里插入图片描述
下图表示了channel,以及订阅者个频道的三个客户端–cli之间的关系
在这里插入图片描述
当有新消息时通过pubulsh命令发送给channel时,这个消息会被发送给订阅它的三个客户端
在这里插入图片描述
在这里插入图片描述
测试
在这里插入图片描述
接收端

127.0.0.1:6379> subscribe painye  
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "painye"
3) (integer) 1
1) "message"
2) "painye"
3) "hello"
1) "message"
2) "painye"
3) "hello world"

发送端

root@ce519f267f73:/data# redis-cli
127.0.0.1:6379> publish pinye hello
(integer) 0
127.0.0.1:6379> publish pinye "hello"
(integer) 0
127.0.0.1:6379> publish painye "hello"
(integer) 1
127.0.0.1:6379> publish painye "hello world"
(integer) 1
127.0.0.1:6379> 

Redis 主从复制

概念

主从复制,是将一台redis服务器的数据,复制到其他的Redis服务器。前者称为主节点,后者称为从节点:数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主
默认情况下,每台Redis服务器都是主节点;且一个主节点可以由多个或零个从节点,但每个从节点只能有一个主节点

主从复制的作用主要包括

1、数据冗余:主从复制实现了数据的热备份,是·持久化之外的一种数据冗余方式
2、故障恢复:当主节点出现问题,可以由从节点提供服务,实现快速的故障恢复;实际上·是一种服务的冗余
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其是在写少读多的情况下,通过多个节点分担读负载,大大提高Redis服务器的并发量
4、高可用基石:主从复制还是烧饼和集群能够实现的基础。因此可以说主从复制是Redis高可用的基础
在这里插入图片描述

root@ce519f267f73:/data# redis-cli
127.0.0.1:6379> info replication						# 查看当前库的信息
# Replication
role:master												# 角色
connected_slaves:0										# 没有从机
master_failover_state:no-failover
master_replid:e41e51a444f08e289422f6f4eb711253a08cf763
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> 

复制原理

Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,master将传送整个数据库文件到slave,并完成一次位安全同步
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存之中
增量复制:master继续将新的所有收集到的修改命令一次传递给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)江北自动执行!我们的数据一定可以在从集中看到

如果此时主机宕机了,可以手动选择一台从机作为主机执行命令:=slaveof no one,当前从机就会变为主机,但是他没有自己的从机,其从机还需手动输入命令 salveof

哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成有一段时间内服务不可用。
哨兵模式类似于自动版的谋朝篡位,能够后台监控主机是否故障,如果故障了根据投票数自动将从库换为主库
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
在这里插入图片描述
哨兵的作用

  • 通过发送命令,让Redis服务器返回其运行状态,包括主从服务器
  • 当哨兵检测到master宕机后,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机
    然而一个哨兵进程对Redis服务器进行监控,可能出现问题,为此我们可以使用多个哨兵进行监控,各个哨兵之间还会进行控制,这样就形成了多哨兵模式。
    在这里插入图片描述
    假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果是由一个哨兵发起,进行failover(故障转移)操作。切换成功之后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称之为客观下线

优点

  • 1、哨兵集群,基于主从复制模式,所有主从配置的优点,全有
  • 2、主从可以切换,故障可以转移,系统的可用性更好
  • 3、哨兵模式就是主从模式的升级,手动到自动,更加雄壮
    缺点
  • 1、Redis不好在线扩容,集群容量一旦上限,在线扩容非常麻烦
  • 2、实现哨兵模式的配置复杂

Redis缓存穿透和雪崩

Redis缓存的使用,极大的提高了应用程序的性能和效率,其中最重要的就是数据一致性问题
另外还有缓存穿透,缓存雪崩和缓存击穿

缓存穿透

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现Redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,于是都去请求持久层数据库,这就给持久层数据库造成很大的压力,这时就出现了缓存穿透。
在这里插入图片描述

解决方案

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储。在控制层先进性校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。

在这里插入图片描述

缓存空对象

当存储层不命中后,即使返回的是空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据会从缓存中获取,保护了后端的数据库
在这里插入图片描述

缺点

1、如果空值被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
2、可能存在缓存与数据库中的数据不一致的可能

缓存击穿

概述

缓存击穿是指一个key非常的火热,在不停的扛着大并发,大并发集中对这一个点进行访问,这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上早开了一个洞

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且会写缓存,会导致数据库瞬间压力过大

解决方法

设置热点数据永不过期

从缓存层面来看,没设置过期时间,所以不会出现热点key过期后产生的问题

加互斥锁

分布式锁:使用分布式锁,保证对每个key同时只有一个线程去查看后端的服务,其他线程没有获得分布式锁的权限,因此只能等待,这种方式将高并发的压力转移到了分布式锁。

缓存雪崩

概念

缓存雪崩:指在某一个时间段,缓存集中过期失效,Redis宕机
在这里插入图片描述

解决办法

Redis高可用

搭建Redis集群

限流降级

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。

数据预热

正式部署之前,将可能的数据预先访问一遍,这部分可能大量访问的数据会加载到缓存之中,在即将发生大并发访问之前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的事件点尽量均匀。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值