Redis(Remote Dictionary Server)
NoSQL
-Not only sql
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。
NoSQL用于超大规模数据的存储。
Redis就是一种NoSQl的数据库。
**特点:**方便扩展,适用于大数据高性能,不限制数据类型,不需要事先设计数据库。
四大分类:
-
KV键值对
redis+memecache+Tair
-
文档型数据库
MongoDB是一个基于分布式文件存储的数据库,介于关系型数据库和非关系型数据库中间。
-
列存储数据库
-
图关系数据库
RDBMS vs NoSQL
RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL) (SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
-键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理,BASE理论
- 高性能,高可用性和可伸缩性
CAP定理(CAP theorem)
在计算机科学中, CAP定理(CAP theorem), 又被称作 布鲁尔定理(Brewer’s theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
- 一致性(Consistency) (所有节点在同一时间具有相同的数据)
- 可用性(Availability) (保证每个请求不管成功或者失败都有响应)
- 分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
Redis
Remote Dictionary server
单线程!
Redis将所有数据放在内存里面,所以使用单线程效率最高。多线程是在cpu进行的(Cpu会产生上下文切换),对于内存系统来说没有上下文切换效率最高!
可以做数据库,缓存和消息中间件。
常用命令
-
127.0.0.1:6379> ping #查看当前连接是否正常,正常返回PONG PONG 127.0.0.1:6379> select 0 #0号数据库 OK 127.0.0.1:6379> clear #清楚当前控制台(为了更好的看到下面输入的命令) 127.0.0.1:6379> keys * #查看当前库里所有的key
- “db”
127.0.0.1:6379> FLUSHALL #清空所有库的内容
OK
127.0.0.1:6379> FLUSHDB #清空当前库的内容
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name dingdada #添加一个key为‘name’ value为‘dingdada’的数据
OK
127.0.0.1:6379> get name #查询key为‘name’的value值
“dingdada”
127.0.0.1:6379> keys * - “name”
127.0.0.1:6379> set name1 dingdada2
OK
127.0.0.1:6379> get name1
“dingdada2”
127.0.0.1:6379> keys * #查看当前库里所有的key - “name1”
- “name”
127.0.0.1:6379> EXISTS name #判断当前key是否存在
(integer) 1
127.0.0.1:6379> move name 1 #移除当前库1的key为‘name‘的数据
(integer) 1
127.0.0.1:6379> keys * - “name1”
127.0.0.1:6379> FLUSHALL #再次清空所有库的内容
OK
- “db”
多加几条数据 下面测试设置key的过期时间
```c
127.0.0.1:6379> set name dingdada
OK
127.0.0.1:6379> set name1 dingdada1
OK
127.0.0.1:6379> set name2 dingdada2
OK
127.0.0.1:6379> EXPIRE name 15 #设置key为’name‘的数据过期时间为15秒 单位seconds
(integer) 1
127.0.0.1:6379> ttl name #查看当前key为’name‘的剩余生命周期时间
(integer) 13
127.0.0.1:6379> ttl name
(integer) 12
127.0.0.1:6379> ttl name
(integer) 11
127.0.0.1:6379> ttl name
(integer) 8
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> ttl name #如若返回-2,证明key已过期
(integer) -2
127.0.0.1:6379> get name #再次查询即为空
(nil)
127.0.0.1:6379> type name1
string
127.0.0.1:6379>
五大数据类型
1、String字符串
添加、查询、追加、获取长度,判断是否存在的操作
-
127.0.0.1:6379> set name dingdada #插入一个key为‘name’值为‘dingdada’的数据 OK 127.0.0.1:6379> get name #获取key为‘name’的数据 "dingdada" 127.0.0.1:6379> get key1 "hello world!" 127.0.0.1:6379> keys * #查看当前库的所有数据
- “name”
127.0.0.1:6379> EXISTS name #判断key为‘name’的数据存在不存在,存在返回1
(integer) 1
127.0.0.1:6379> EXISTS name1 #不存在返回0
(integer) 0
127.0.0.1:6379> APPEND name1 dingdada1 #追加到key为‘name’的数据后拼接值为‘dingdada1’,如果key存在类似于java中字符串‘+’,不存在则新增一个,类似于Redis中的set name1 dingdada1 ,并且返回该数据的总长度
(integer) 9
127.0.0.1:6379> get name1
“dingdada1”
127.0.0.1:6379> STRLEN name1 #查看key为‘name1’的字符串长度
(integer) 9
127.0.0.1:6379> APPEND name1 ,dingdada2 #追加,key存在的话,拼接‘+’,返回总长度
(integer) 19
127.0.0.1:6379> STRLEN name1
(integer) 19
127.0.0.1:6379> get name1
“dingdada1,dingdada2”
127.0.0.1:6379> set key1 “hello world!” #注意点:插入的数据中如果有空格的数据,请用“”双引号,否则会报错!
OK
127.0.0.1:6379> set key1 hello world! #报错,因为在Redis中空格就是分隔符,相当于该参数已结束
(error) ERR syntax error
127.0.0.1:6379> set key1 hello,world! #逗号是可以的
OK
127.0.0.1:6379> setex name 10 hello #设置key的值以及过期时间
OK
127.0.0.1:6379> ttl name
(integer) 5
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> set name czb
OK
127.0.0.1:6379> setnx name xkk #如果key不存在则创建,存在则创建失败
(integer) 0
127.0.0.1:6379> setnx name1 xkk
(integer) 1
127.0.0.1:6379> get name1
“xkk”
127.0.0.1:6379>
- “name”
**自增**、**自减**操作
```c
127.0.0.1:6379> set num 0 #插入一个初始值为0的数据
OK
127.0.0.1:6379> get num
"0"
127.0.0.1:6379> incr num #指定key为‘num’的数据自增1,返回结果 相当于java中 i++
(integer) 1
127.0.0.1:6379> get num #一般用来做文章浏览量、点赞数、收藏数等功能
"1"
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379> get num
"3"
127.0.0.1:6379> decr num #指定key为‘num’的数据自减1,返回结果 相当于java中 i--
(integer) 2
127.0.0.1:6379> decr num
(integer) 1
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> decr num #可以一直减为负数~
(integer) -1
127.0.0.1:6379> decr num #一般用来做文章取消点赞、取消收藏等功能
(integer) -2
127.0.0.1:6379> decr num
(integer) -3
127.0.0.1:6379> INCRBY num 10 #后面跟上by 指定key为‘num’的数据自增‘参数(10)’,返回结果
(integer) 7
127.0.0.1:6379> INCRBY num 10
(integer) 17
127.0.0.1:6379> DECRBY num 3 #后面跟上by 指定key为‘num’的数据自减‘参数(3)’,返回结果
(integer) 14
127.0.0.1:6379> DECRBY num 3
(integer) 11
截取、替换字符串操作
#截取
127.0.0.1:6379> set key1 "hello world!"
OK
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 4 #截取字符串,相当于java中的subString,下标从0开始,不会改变原有数据
"hello"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 -1 #0至-1相当于 get key1,效果一致,获取整条数据
"hello world!"
#替换
127.0.0.1:6379> set key2 "hello,,,world!"
OK
127.0.0.1:6379> get key2
"hello,,,world!"
127.0.0.1:6379> SETRANGE key2 5 888 #此语句跟java中replace有点类似,下标也是从0开始,但是有区别:java中是指定替换字符,Redis中是从指定位置开始替换,替换的数据根据你所需替换的长度一致,返回值是替换后的长度
(integer) 14
127.0.0.1:6379> get key2
"hello888world!"
127.0.0.1:6379> SETRANGE key2 5 67 #该处只替换了两位
(integer) 14
127.0.0.1:6379> get key2
"hello678world!"
批量操作
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #批量设置
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3 #批量获取
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx是原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2 #批量设置
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
127.0.0.1:6379> getset db redis #不存在值,返回nil并设置
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #存在值,返回值并替换
"redis"
127.0.0.1:6379> get db
"mongodb"
2、List列表
127.0.0.1:6379> flushdb
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> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #查看列表
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> Rpush list four
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #插入数据到列表尾部
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop list #移除头部元素
"three"
127.0.0.1:6379> rpop list #移除尾部元素
"four"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 #查询元素
"two"
127.0.0.1:6379> llen list #返回列表长度
(integer) 2
127.0.0.1:6379> lrem list 1 one #移除指定个数的指定值
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> lpush list one
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lpush list four
(integer) 4
127.0.0.1:6379> lpush list five
(integer) 5
127.0.0.1:6379> ltrim list 0 2 #通过下标截取指定长度,会改变list
OK
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
127.0.0.1:6379> rpoplpush list list1 #移除当前list中的尾部元素放到另一个列表头部
"three"
127.0.0.1:6379> lrange list1 0 -1
127.0.0.1:6379> exists name1
(integer) 1
127.0.0.1:6379> lset name1 0 k3 #更新列表中指定下标值,前提是列表存在
OK
127.0.0.1:6379> lrange name1 0 -1
1) "k3"
127.0.0.1:6379> linsert name1 before k3 k2 #将数据插入列表中某个元素前面或后面
(integer) 2
127.0.0.1:6379> lrange name1 0 -1
1) "k2"
2) "k3"
list(lpush,lpop)可以当作一个栈来使用,(lpush,rpop)可以当作消息队列
3、Set集合(无序不重复)
127.0.0.1:6379> sadd set1 "hello" #添加元素
(integer) 1
127.0.0.1:6379> sadd set1 ",world"
(integer) 1
127.0.0.1:6379> smembers set1 #查看set元素
1) ",world"
2) "hello"
127.0.0.1:6379> sismember set1 hello #判断数据是否在set中
(integer) 1
127.0.0.1:6379> sadd set1 "hello" #添加元素不能重复
(integer) 0
127.0.0.1:6379> srem set1 "hello" #删除指定元素
(integer) 1
127.0.0.1:6379> scard set1 #set元素个数
(integer) 1
127.0.0.1:6379> srandmember set1 #随机获取一个元素,可以用来抽奖
"k1"
127.0.0.1:6379> srandmember set1
"k1"
127.0.0.1:6379> srandmember set1
"k2"
127.0.0.1:6379> srandmember set1 2 #随机获取几个元素
1) "k3"
2) ",world"
127.0.0.1:6379> srandmember set1 2
1) ",world"
2) "k2"
127.0.0.1:6379> spop set1 #随机删除元素
"k1"
127.0.0.1:6379> spop set1
"k3"
127.0.0.1:6379> smembers set1
1) "k2"
2) ",world"
127.0.0.1:6379> sadd set2 hello
(integer) 1
127.0.0.1:6379> smove set1 set2 ,world #将一个set中的指定元素移动到另一个set
(integer) 1
127.0.0.1:6379> smembers set2
1) ",world"
2) "hello"
127.0.0.1:6379> sadd t1 1
(integer) 1
127.0.0.1:6379> sadd t1 2
(integer) 1
127.0.0.1:6379> sadd t1 3
(integer) 1
127.0.0.1:6379> sadd t2 3
(integer) 1
127.0.0.1:6379> sadd t2 4
(integer) 1
127.0.0.1:6379> sdiff t1 t2 #两个集合的差集
1) "1"
2) "2"
127.0.0.1:6379> sinter t1 t2 #两个集合的交集,可以找共同关注
1) "3"
127.0.0.1:6379> sunion t1 t2 #两个集合的并集
1) "1"
2) "2"
3) "3"
4) "4"
4、Hash
key-map
127.0.0.1:6379> hset myhash field1 czb
(integer) 1
127.0.0.1:6379> hget myhash field1
"czb"
127.0.0.1:6379> hmset myhash field1 xkk field2 iloveyou #批量设置
OK
127.0.0.1:6379> hget myhash field1
"xkk"
127.0.0.1:6379> hget myhash field2
"iloveyou"
127.0.0.1:6379> hmget myhash field1 field2 #批量获取
1) "xkk"
2) "iloveyou"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "xkk"
3) "field2"
4) "iloveyou"
127.0.0.1:6379> hdel myhash field1 #删除某个字段
(integer) 1
127.0.0.1:6379> hget myhash field1
(nil)
127.0.0.1:6379> hlen myhash #获取字段长度
(integer) 1
127.0.0.1:6379> hexists myhash field2 #判断指定字段是否存在
(integer) 1
127.0.0.1:6379> hkeys myhash #获取所有字段
1) "field2"
127.0.0.1:6379> hvals myhash #获取所有值
1) "iloveyou"
127.0.0.1:6379> hset myhash field1 1
(integer) 1
127.0.0.1:6379> hset myhash field2 2
(integer) 1
127.0.0.1:6379> hincrby myhash field1 1 #自增
(integer) 2
127.0.0.1:6379> hget myhash field1
"2"
127.0.0.1:6379> hsetnx myhash field3 3 #字段不存在则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field1 2
(integer) 0
127.0.0.1:6379> hset user:1 name czb #对象存储
(integer) 1
127.0.0.1:6379> hset user:1 age 24
(integer) 1
127.0.0.1:6379> hgetall user:1
1) "name"
2) "czb"
3) "age"
4) "24"
5、Zset有序集合
-
127.0.0.1:6379> zadd myset 1 one #添加 (integer) 1 127.0.0.1:6379> zadd myset 2 two 3 three (integer) 2 127.0.0.1:6379> zrange myset 0 -1 #获取全部元素,从小到大 1) "one" 2) "two" 3) "three" 127.0.0.1:6379> zadd salary 2500 p1 (integer) 1 127.0.0.1:6379> zadd salary 5000 p2 (integer) 1 127.0.0.1:6379> zadd salary 10000 p3 (integer) 1 127.0.0.1:6379> zrangebyscore salary -inf inf #排序 1) "p1" 2) "p2" 3) "p3" 127.0.0.1:6379> zrangebyscore salary -inf inf withscores 1) "p1" 2) "2500" 3) "p2" 4) "5000" 5) "p3" 6) "10000" 127.0.0.1:6379> zrangebyscore salary -inf 5000 1) "p1" 2) "p2" 127.0.0.1:6379> zrem salary p1 #删除指定元素 (integer) 1 127.0.0.1:6379> zcard salary #获取集合长度 (integer) 2 127.0.0.1:6379> zrevrange salary 0 -1 withscores #从大到小获取 1) "p3" 2) "10000" 3) "p2" 4) "5000" 127.0.0.1:6379> zcount salary 2000 5000 #获取指定区间的元素数量 (integer)
三种特殊的数据类型
1、geospatial地理位置
127.0.0.1:6379> geoadd China:city 116.4 39.90 beijing
(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 106.5 29.53 chongqing
(integer) 1
127.0.0.1:6379> geopos China:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> geopos China:city hangzhou
1) (nil)
127.0.0.1:6379> geodist China:city shanghai beijing #获取两个城市距离
"1067378.7564"
127.0.0.1:6379> georadius China:city 110 30 1000 km #给定经纬度为中心,以指定值为半径范围内的值
1) "chongqing"
2、hyperloglog基数统计
127.0.0.1:6379> pfadd mykey a b c d e f g h i h
(integer) 1
127.0.0.1:6379> pfcount mykey #统计元素基数数量
(integer) 9
127.0.0.1:6379> pfadd mykey1 k l m n a d
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey mykey1 #合并
OK
127.0.0.1:6379> pfcount mykey3
(integer) 13
3、bitmaps
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sigh 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> getbit sign 2 #获取某天是否打卡
(integer) 0
127.0.0.1:6379> bitcount sign #统计打卡天数
(integer) 3
redis事务
本质:一组命令的集合。
没有隔离级别的概念,不会出现脏读等。
redis单条命令保存具有原子性,事务不具有原子性!
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED #命令入队
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set v3 k3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get k3
(nil)
编译时异常(代码有问题!),事务中所有命令都不会被执行!
**运行时异常,执行命令时,其他命令是可以正常执行的,错误命令抛异常!**也是redis不具有原子性的体现!
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 "v1"
QUEUED
127.0.0.1:6379> incr 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> get k2
"v2"
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 #监视器
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
#事务正常结束,数据期间没有发生变化
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #执行之前,另一个进程修改money的值,导致事务开启失败,需要重新打开监视器更新money的值UNwatch money
(nil)
Jedis
连接java的开发工具,使用java操作redis中间件!
<!--导入jedis的包-->
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
</dependencies>
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
jedis.flushDB();
jedis.lpush("city","beijing");
jedis.lpush("city","shanghai");
System.out.println(jedis.keys("*"));
List<String> list = jedis.lrange("city",0,-1);
System.out.println(list);
System.out.println(jedis.llen("city"));
}
}
springBoot集成Redis
jedis:采用直连,多个线程操作,是不安全的。
lettuce:底层采用netty,实例可以多个线程共享,可以减少线程数量。
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.2</version>
</dependency>
2、配置properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
3.test
@SpringBootTest
class SpringbootRedisApplicationTests {
@Resource
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
redisTemplate.opsForSet().add("city","shanghai");
System.out.println(redisTemplate.opsForSet().pop("city"));
// redisTemplate.opsForValue().set();
}
}
Redis提供的序列化方式(源码):
RedisTemplate默认采用的是JDK的序列化策略,被序列化的必须实现Serializable接口。
StringRedisTemplate默认采用的是String的序列化策略
一般使用自定义序列化方式。
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> MyRedisTemplate(RedisConnectionFactory factory){
// 为了开发方便,一般都使用<String, Object>类型
RedisTemplate<String, Object> template = new RedisTemplate();
// 连接工厂
template.setConnectionFactory(factory);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 序列化配置
Jackson2JsonRedisSerializer JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
JsonRedisSerializer.setObjectMapper(om);
// value值的序列化采用FastJsonRedisSerializer
template.setValueSerializer(JsonRedisSerializer);
// hash值的序列化采用FastJsonRedisSerializer的方式
template.setHashValueSerializer(JsonRedisSerializer);
// key的序列化采用StringRedisSerializer
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
// hash的key的序列化采用StringRedisSerializer的方式
template.setHashKeySerializer(stringRedisSerializer);
return template;
}
}
配置文件 redis.windows-service.conf
bind 127.0.0.1 #绑定的ip
protected-mode yes #保护模式
port 6379 #端口
daemonize yes #以守护进程的方式运行
databases 16 #默认数据库数量
logfile“” #日志输出位置
save 60 10000 #60秒内进行10000次修改,进行持久化操作
stop-writes-on-bgsave-error yes #持久化错误是否继续进行操作
dir ./ #日志文件保存目录
config set requirepass "czb11230" #设置登录密码,设置之后所有命令失效,需要登录
auth "czb11230" #密码登录
#Aof设置
appendonly no #默认不开启Aof,使用rdb进行持久化
持久化操作RDB
redis database
Redis是内存数据库,不进行持久化会断电即失!
**基本原理:**RDB持久化主要是通过SAVE和BGSAVE两个命令对Redis数据库中当前的数据做snapshot并生成rdb文件来实现的。其中SAVE是阻塞的,BGSAVE是非阻塞的,通过fork了一个子进程来完成的。在Redis启动的时候会检测rdb文件,然后载入rdb文件中未过期的数据到服务器中。
配置信息:RDB可以通过向服务器提供配置信息来自动间隔性保存。如默认情况下服务器满足以下3个条件中任意一个条件就会触发BGSAVE命令。
save 900 1 // 服务器在900秒之内,对数据库进行了至少1次修改
save 300 10 // 服务器在300秒之内,对数据库进行了至少10次修改
save 60 10000 // 服务器在60秒之内,对数据库进行了至少10000次修改
触发规则:
1、save命令
2、执行flushall命令
3、退出redis
127.0.0.1:6379> config get dir
1) "dir"
2) "D:\\Redis-x64-5.0.14.1" #如果此目录下存在dump.rdb文件,启动就会自动恢复其中的数据
优点:
适合大规模的数据恢复,对数据完整性和一致性要求不高,主进程会fork一个子进程来处理保存工作,主进程不需要进行任何磁盘IO操作。
缺点:
在一定时间内做一次备份,如果redis出现宕机,会丢失最后一次快照后的所有修改。Fork子进程的时候,内存中的数据被克隆了一份,内存会产生2倍的膨胀并且耗时。
AOF(Append Only File)
基本原理:AOF持久化是通过存储每次执行的客户端命令,然后由一个伪客户端来执行这些命令将数据写入到服务器中的方式实现的。一共分为命令追加(append)、文件写入、*文件同步(sync)*三个步骤完成的。
命令追加
当有修改、删除操作时,服务器会在执行完之后以协议格式将被执行的写命令追加到服务器状态的aof_buf
缓冲区的末尾。
文件写入
Redis的服务进程就是一个事件循环,这个循环中的文件事件负责接收客文件同步。
flushAppendOnlyFile
函数通过服务器配置appendfsync
选项的值来决定的将每次循环结束之前aof_buf
缓冲区的数据写入到AOF文件后,将以何种方式同步到AOF文件里面。
Aof文件有问题,redis就不能启动,可以通过redis-check-aof --fix工具对aof进行修复。
AOF重写:解决AOF文件过大的问题
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
函数aof_rewrite
启动一个子进程创建AOF重写缓冲区,将Redis中所有的数据生成多条写命令写入AOF文件。在子进程进行AOF重写期间,服务器还会处理写请求的命令,这会导致服务器当前的数据库状态和重写后的AOF文件所保存的数据不一致。为了解决这个问题,子进程在执行AOF重写期间,服务器进程需要执行以下三件事情:
- 执行客户端发送来的命令
- 将执行后的写命令追加到AOF缓冲区
- 将执行后的写命令追加到AOF重写缓冲区
当子进程完成AOF重写工作后,会发送一个信号到父进程,父进程收到信号后会调用信号处理函数(这个过程会block主父进程),执行以下工作:
- 将AOF重写缓冲区中的数据全部写入到新AOF文件中,这时新AOF文件所保存的数据库状态和服务器当前的数据库状态一致
- 对新的AOF文件进行改名,原子的覆盖现有的AOF文件,完成新旧两个AOF文件的替换
Redis订阅发布
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFtYh42q-1659340284893)(C:\Users\DIY\AppData\Roaming\Typora\typora-user-images\image-20220727135947377.png)]
127.0.0.1:6379> publish czb hello #发送端
(integer) 1
127.0.0.1:6379> publish czb world
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> subscribe czb #接收端,自动接收订阅的消息,psubscribe可以接收多个订阅
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "czb"
3) (integer) 1
1) "message"
2) "czb"
3) "hello"
1) "message"
2) "czb"
3) "world"
Redis主从复制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GbEQORX9-1659340284894)(C:\Users\DIY\AppData\Roaming\Typora\typora-user-images\image-20220727141138981.png)]
数据的复制都是单向的!主机写,从机读,读写分离。默认每一台都是主节点。
全量复制:slave启动成功会发一个sync命令给Master,master接到命令将所有数据文件复制到slave。增量复制:Master继续继续讲所有后续命令传给slave。
主从复制的作用:
**读写分离:**主节点写,从节点读,提高服务器的读写负载能力
**数据冗余︰**主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
**故障恢复︰**当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 ; 实际上是一种服务的冗余。
**负载均衡︰**在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载 ; 尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
**高可用(集群)基石︰**除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
127.0.0.1:6379> info replication #查看当前库的信息
# Replication
role:master #默认是主机
connected_slaves:0 #没有从机
master_replid:b7441c735b709f1af503a4582388b1ba92ab2eea
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
slaveof 127.0.0.1 6379 #配置从机
slaveof no one #让自己编程主机,然后手动配置从机
哨兵模式
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
如果主机回来,就会被归到新的主机下当做从机!
redis缓存穿透和雪崩(面试常问)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-21S3qZSa-1659340284896)(C:\Users\DIY\AppData\Roaming\Typora\typora-user-images\image-20220727152348526.png)]
缓存穿透:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义,会导致数据库压力过大。
解决方案:
1、布隆过滤器:bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,在控制层进行校验,不符合则丢弃,避免了对底层存储系统的压力。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i1YoRRx8-1659340284896)(C:\Users\DIY\AppData\Roaming\Typora\typora-user-images\image-20220727152637723.png)]
缓存击穿:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
1、设置热点数据永不过期
2、加互斥锁
缓存雪崩:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是集群部署,将热点数据均匀分布在不同的缓存数据库中。
- 设置热点数据永远不过期。