NoSQL的四大分类:
KV键值对
文档型数据库:
MongoDB(要掌握)是一个介于关系型数据库和非关系型数据库中间的产品,是非关系型数据库中功能最丰富,最像关系型数据库的
ConthDB
列存储数据库:
图关系数据库:
Redis:
Remote Dictionary Server 远程字典服务
Redis是单线程的 6.0之后有多线程,将所有的数据全部放在内存中,所以使用单线程去操作效率最高
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-value数据库,并提供多种语言的API
是热门的NoSQL技术之一,也被称为结构化数据库,可以用作数据库、缓存和消息中间件MQ
一共有17个数据库默认使用第0个
Redis作用:
1.内存存储、持久化 什么是持久化(rdb、aof)
2.效率高,可以用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器、计数器
Redis和Mysql的区别:
mysql是关系型数据库,redis是缓存数据库
mysql用于持久化的存储数据到硬盘,功能强大,速度较慢,基于磁盘,读写速度没有Redis快,但是不受空间容量限制,性价比高
redis用于存储使用较为频繁的数据到缓存中,读取速度快,基于内存,读写速度快,也可做持久化,但是内存空间有限,当数据量超过内存空间时,需扩充内存,但内存价格贵
windows用命令启动:redis-server redis.windows.conf
打开cli.exe
127.0.0.1:6379> ping ---->测试链接
PONG
127.0.0.1:6379> set name lv ---->设置set key value
OK
127.0.0.1:6379> get name ---->根据key获取值
"lv"
127.0.0.1:6379> keys * ---->查看当前数据库所有的key
1) "name"
127.0.0.1:6379> FLUSHDB ---->清空当前数据库
OK
127.0.0.1:6379> FLUSHALL ---->清空全部数据库
OK
127.0.0.1:6379> select 3 ---->切换3号数据库
OK
127.0.0.1:6379[3]> DBSIZE ---->查看DB大小
(integer) 0
127.0.0.1:6379> EXISTS name ---->查看name这个key是否存在
(integer) 1
127.0.0.1:6379> move name 1 ---->移除name这个key到1号数据库
(integer) 1
127.0.0.1:6379> EXPIRE name 10 ---->设置name这个key10秒后过期
(integer) 1
127.0.0.1:6379> ttl name ---->查看过期剩余时间
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type age ---->查看age这个key的类型
string
String(字符串):
String类似的使用场景:value除了是字符串还可以是数字
127.0.0.1:6379> APPEND KEY1 HELLO ---->往这个key后面加上HELLO,如果这个key不存在,就相当于新建一个key
(integer) 7
127.0.0.1:6379> GET KEY1
"V1HELLO"
127.0.0.1:6379> STRLEN KEY1 ---->获取这个key的长度
(integer) 7
127.0.0.1:6379> incr views ---->自增1
(integer) 1
127.0.0.1:6379> decr views ---->自减1
(integer) 0
127.0.0.1:6379> INCRBY views 10 ---->可以设置步长,指定增量为10
(integer) 10
127.0.0.1:6379> DECRBY views 5 ---->可以设置步长,指定减量为5
(integer) 5
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> GETRANGE name 0 3 ---->截取第0个到第三个的字符串
"zhan"
127.0.0.1:6379> GETRANGE name 0 -1 ---->截取全部字符串
"zhangsan"
127.0.0.1:6379> SETRANGE name 1 xx ---->替换指定位置开始的字符串
(integer) 8
127.0.0.1:6379> get name
"zxxngsan"
127.0.0.1:6379> setex age 10 22 ---->增加一个age值为22并在10秒后过期
OK
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> setnx mykey redis ---->如果不存在mykey才能增加成功 1代表成功0代表失败
(integer) 1
127.0.0.1:6379> setnx mykey mongoDB
(integer) 0
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 ---->批量增加key
OK
127.0.0.1:6379> keys *
1) "views"
2) "k2"
3) "name"
4) "k1"
5) "mykey"
6) "k3"
127.0.0.1:6379> mget k1 k2 k3 ---->批量获取key
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 ---->批量创建不存在的key 因为k1已经存在所以后面的k4并不会被创建 (原子性操作,要么一起成功,要么一起失败)
(integer) 0
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 22 ---->增加一个user:1对象name的值为zhangsan age的值为22
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "22"
127.0.0.1:6379> getset db redis ---->先get在set 如果没有db这个key会返回nil然后创建这个key
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb ---->先get在set 如果有db这个key会返回这个值然后更新这个值
"redis"
127.0.0.1:6379> get db
"mongodb"
List(列表):
实际上是一个链表,可以作用栈、队列、阻塞队列,在两边插入或改动值,效率最高
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 right ---->将一个值或多个值插入到列表尾部(右边)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> Lpop list ---->移除list第一个元素
"three"
127.0.0.1:6379> Rpop list ---->移除list最后一个元素
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> Lindex list 1 ---->通过下标获取list元素
"one"
127.0.0.1:6379> Llen list ---->获取list的长度
(integer) 2
127.0.0.1:6379> LREM list 1 one ---->移除list准确个数的value
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "one"
4) "two"
127.0.0.1:6379> ltrim list 1 2 ---->通过下标截取list指定的长度
OK
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "one"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "four"
3) "three"
4) "one"
127.0.0.1:6379> rpoplpush list list2 ---->移除列表最后一个元素并移动到list2里
"one"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "four"
3) "three"
127.0.0.1:6379> lrange list2 0 -1
1) "one"
127.0.0.1:6379> exists list ---->判断这个list是否存在
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "two"
127.0.0.1:6379> lset list 0 five ---->将列表指定下标的值替换为另外一个值(如果不存在会报错)
OK
127.0.0.1:6379> lrange list 0 0
1) "five"
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> linsert list before two three ---->将某个具体的value插入到列表某个value的前面
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> linsert list after two one ---->将某个具体的value插入到列表某个value的后面
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
Set(集合):
set中的值是不能重复的,并且是无序的
127.0.0.1:6379> sadd myset hello ---->往myset集合里赋值
(integer) 1
127.0.0.1:6379> sadd myset lv
(integer) 1
127.0.0.1:6379> sadd myset redis
(integer) 1
127.0.0.1:6379> SMEMBERS myset ---->查看myset集合里的值
1) "hello"
2) "redis"
3) "lv"
127.0.0.1:6379> SISMEMBER myset hello ---->判断myset集合里是否有hello这个值
(integer) 1
127.0.0.1:6379> SISMEMBER myset world ---->判断myset集合里是否有world这个值
(integer) 0
127.0.0.1:6379> scard myset ---->获取set集合中的元素个数
(integer) 3
127.0.0.1:6379> srem myset hello ---->移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "redis"
2) "lv"
127.0.0.1:6379> SRANDMEMBER myset ---->随机抽取set集合一个元素,后面可以加个数
"lv"
127.0.0.1:6379> SRANDMEMBER myset ---->随机抽取set集合一个元素,后面可以加个数
"redis"
127.0.0.1:6379> spop myset ---->随机移除set集合的元素
"mongodb"
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 lv ---->将lv元素从myset移动到myset2集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "lv"
数字集合类:差集、交集、并集:共同关注可以这样实现
127.0.0.1:6379> SDIFF myset myset2 ---->差集
1) "hello"
2) "world"
127.0.0.1:6379> SINTER myset myset2 ---->交集,拥有的同样的元素
1) "lv"
127.0.0.1:6379> SUNION myset myset2 ---->并集
1) "set2"
2) "hello"
3) "world"
4) "lv"
Hash(哈希):
可以看做Map集合 key-value,当key-map的时候,这个值是个map集合,本质和String类型没有太大区别,还是一个简单的key-value
使用场景:hash可以存变更的数据,比如用户信息,经常变动的信息,比较适合对象的存储
127.0.0.1:6379> hset myhash field1 lv ---->set一个key-value
(integer) 1
127.0.0.1:6379> hget myhash field1
"lv"
127.0.0.1:6379> hmset myhash field1 hello field2 world ---->set多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 ---->获取多个key的value
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash ---->获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 ---->删除指定的key-value
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
127.0.0.1:6379> hlen myhash ---->获取myhash的数量
(integer) 1
127.0.0.1:6379> hexists myhash field1 ---->判断指定的key是否存在
(integer) 0
127.0.0.1:6379> hkeys myhash ---->只获取所有的key
1) "field2"
127.0.0.1:6379> hvals myhash ---->只获取所有value
1) "world"
127.0.0.1:6379> hset myhash field1 5
(integer) 1
127.0.0.1:6379> hincrby myhash field1 1 ---->指定增量
(integer) 6
127.0.0.1:6379> hincrby myhash field1 -1 ---->指定减量
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello ---->如果不存在则创建 存在则不能设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 hello ---->如果不存在则创建 存在则不能设置
(integer) 0
Zset(有序集合):
在set的基础上,增加一个值。 普通消息,重要消息,带权重进行判断,应用场景:排行榜排序
127.0.0.1:6379> zadd myset 1 one ---->添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zrange myset 0 -1 ---->遍历集合
1) "one"
2) "two"
127.0.0.1:6379> zadd gz 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd gz 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd gz 8000 lv
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE gz -inf +inf ---->显示全部用户 从小到大
1) "xiaohong"
2) "zhangsan"
3) "lv"
127.0.0.1:6379> ZREVRANGE gz 0 -1 ---->显示全部用户 从大到小
1) "lv"
2) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE gz -1 0
(empty list or set)
127.0.0.1:6379> ZRANGEBYSCORE gz -inf +inf withscores ---->显示全部用户 从小到大并且附带值
1) "xiaohong"
2) "2500"
3) "zhangsan"
4) "5000"
5) "lv"
6) "8000"
127.0.0.1:6379> ZRANGEBYSCORE gz -inf 2500 withscores ---->显示工资小于2500的升序排序
1) "xiaohong"
2) "2500"
127.0.0.1:6379> zrem gz xiaohong ---->移除元素
(integer) 1
127.0.0.1:6379> zrange gz 0 -1
1) "zhangsan"
2) "lv"
127.0.0.1:6379> zcard gz ---->获取有序集合中的个数
(integer) 2
127.0.0.1:6379> zcount gz 2500 5000 ---->获取指定区间的数量
(integer) 2
127.0.0.1:6379> zcount gz 2500 8000
(integer) 3
geospatial地理位置:
场景:定位,附近的人,打车距离计算,底层命令就是Zset 可以用Zset命令来操作Geo
127.0.0.1:6379> GEOADD china:city 118.11022 24.490474 xiamen ---->添加地理位置 key 经度 纬度 名称
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.472644 31.231706 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 116.405285 39.904989 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> GEOPOS china:city beijing chongqing ---->获取指定的城市的经纬度
1) 1) "116.40528291463852"
2) "39.904988422912503"
2) 1) "106.49999767541885"
2) "29.529999579006592"
127.0.0.1:6379> GEODIST china:city beijing shanghai ---->查看两地的直线距离
"1067597.9668"
127.0.0.1:6379> GEODIST china:city beijing shanghai km ---->查看两地的直线距离 km为单位
"1067.5980"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km ---->在china:city以100,30经纬度查找半径1000km的城市
1) "chongqing"
2) "shenzhen"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km ---->在china:city以100,30经纬度查找半径500km的城市
1) "chongqing"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist ---->在china:city以100,30经纬度查找半径500km的城市并显示直线距离
1) 1) "chongqing"
2) "341.9374"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord ---->在china:city以100,30经纬度查找半径500km的城市并显示经纬度
1) 1) "chongqing"
2) 1) "106.49999767541885"
2) "29.529999579006592"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1 ---->在china:city以100,30经纬度查找半径500km的城市并显示经纬度 并且只显示一个结果
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885"
2) "29.529999579006592"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city xiamen 1000 km ----> 找出位于指定元素周围的其他元素
1) "shenzhen"
2) "xiamen"
3) "shanghai"
127.0.0.1:6379> geohash china:city beijing xiamen ---->将经纬度转为字符串
1) "wx4g0b7xrt0"
2) "ws7grdm5re0"
hyperloglog
基数统计的算法 场景:网页的uv 传统的方式,set保存用户的id,然后可以统计set中元素数量作为标准判断
127.0.0.1:6379> PFadd mykey a b c d e f g h i j k l ---->创建第一组元素mykey
(integer) 1
127.0.0.1:6379> PFcount mykey ---->统计mykey元素的基数数量
(integer) 12
127.0.0.1:6379> PFadd mykey2 j k l m n o p q r s t ---->创建第二组元素mykey2
(integer) 1
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 ---->合并两组mykey 和 mykey2 成mykey3 并集
OK
127.0.0.1:6379> pfcount mykey3
(integer) 19
Bitmaps
位存储,场景:统计用户信息,登陆和未登陆的用户,只有两个状态的都可以使用
数据结构,操作二进制位来进行记录,只有0和1两个状态
127.0.0.1:6379> setbit sign 0 1 ---->设置sign的元素
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> getbit sign 1 ---->查看sign的元素
(integer) 1
127.0.0.1:6379> bitcount sign ---->统计sign值为1的元素
(integer) 2
Redis的事务
事务本质:一组命令的集合 一个事务中的所有命令都会被序列化,执行过程中会按顺序执行,一次性、顺序性、排他性
单条命令有原子性,但是事务没有原子性,没有隔离级别的概念
1.开启事务(multi) 2.命令入队(…) 3.执行事务(exec)
127.0.0.1:6379> multi ---->开启事务
OK
127.0.0.1:6379> set k1 v1 ---->命令入队
QUEUED
127.0.0.1:6379> exec ---->执行事务
1) OK
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> discard ---->取消事务 队列里的不会被执行
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
(nil)
编译型异常(代码有问题,执行的命令有错误)事务中所有的命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 ---->错误的命令,后面执行事务的时候全部命令都不会被执行
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
运行时异常(1/0)如果事务队列中存在语法性,那么执行命令的时候其他命令是可以正常执行的,错误命令抛出异常
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range ---->
2) "v1"
乐观锁
多线程修改值,使用watch可以当做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 out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
Jedis
是redis官方推荐的java连接开发工具,使用java操作redis中间件
导入对应的依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
用IDEA模拟开启事务:
public class Test {
public static void main(String[] args) {
//新建一个jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","zhangsan");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toString();
//jedis.watch(result);
try {
multi.set("user1",result);
multi.set("user2",result);
multi.exec();
}catch (Exception e){
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
SpringBoot整合:
在SpringBoot2.x之后Jedis被替换成lettuce
Jedis:采用的直连,多个线程操作是不安全的,如果想避免不安全要使用jedis pool连接池 BIO模式
lettuce:采用的netty,实例可以在多个线程中共享,不存在线程不安全,可以减少线程数据 NIO模式
pom.xml加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
spring.redis.host=127.0.0.1
spring.redis.port=6379
Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化功能
RDB持久化操作:
配置文件中修改save 60
5,只要60秒内修改5次key,执行flushall命令和退出redis的时候就会触发rdb操作,自动生成的文件为dump.rdb
127.0.0.1:6379> config get dir
1) “dir”
2) “D:\Redis\Redis-x64-3.0.504” ---->dump.rdb文件目录
优点:
1.适合大规模的数据恢复
2.对数据的完整性要求不高
缺点:
1.需要一定的时间间隔进程操作,如果redis意外宕机了,最后一次修改的数据就没了
2.fork进程的时候,会占用一定的内容空间
AOF持久化操作:
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部执行一遍
配置文件中默认不开启appendonly no 改为yes就是开启
如果aof文件有错误,redis是启动不起来的,我们需要用redis-check-aof修复aof文件
优点:
1.每一次修改都同步,文件的完整性会更好
2.每秒同步一次,可能会丢失一秒的数据
3.从不同步,效率最高
缺点:
1.相对于数据文件来说,aof远远大于rdb,修复的速度比rdb慢
2.aof运行效率也比rdb慢,所以我们redis默认就是rdb持久化
总结:
RDB持久化方式就是能在指定的时间间隔内对你的数据进行快照存储
AOF持久化方式就是记录每次对服务器写的操作命令,服务器重启就会重新执行这些命令来恢复数据
只做缓存,如果希望数据在服务器运行的时候存在,可以不使用任何持久化
Redis发布订阅
是一种消息通信模式:发送者发送消息,订阅者接收消息
Redis主从复制
主从复制是指将一台Redis服务器的数据复制到其他的Redis服务器,数据的复制是单向的,只能从主节点到从节点,读写分离。
主机能写入数据,从机只能读取数据
主从复制的作用:
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际是一种服务的冗余
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务
4.高可用(集群)基石:主从复制还是哨兵和集群能够实施基础
127.0.0.1:6379> info replication ---->查看当前库的信息
# Replication
role:master ---->角色 主机
connected_slaves:0 ---->没有从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
默认情况下每台Redis服务器都是主节点,一般只用配置从机
SLAVEOF 127.0.0.1 6379 ---->配置主机是谁,命令配置的话只是暂时性的,用配置文件是永久的
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
只要重新链接master,一次全量复制将被自动执行
Redis哨兵模式
1.配置哨兵配置文件sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1 ---->后面的数字1代表主机宕机后slave投票票数最多的成为主机
2.启动哨兵
redis-sentinel kconfig/sentinel.conf
如果从机选举完了,主机连接回来了,主机会被哨兵模式切换成从机
优点:
1.哨兵集群,基于主从复制模式,所有的主从配置优点都有
2.主从可以切换,故障可以转移,系统的可用性好
3.哨兵模式就是主从模式的升级,手动到自动
缺点:
1.Redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
2.实现哨兵模式的配置是很麻烦的
哨兵模式的全部配置:
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# shell编程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!
Redis缓存穿透和雪崩(重点)
缓存穿透概念:用户想查一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有
于是本次查询失败,当用户很多的时候,缓存都没有命中,就都会去请求持久层数据库,这就给持久层数据库造成很大压力(一直查不到某个数据)
解决方案:
1.布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
缓存击穿概念:是指一个key非常热点,在不停的抗着大并发,大并发集中对这一点进行访问,当这个key在失效的瞬间,大并发就
穿破缓存,直接请求数据库,导致数据库瞬间压力过大(比如微博热搜某条热搜一直被搜索)
解决方案:
1.设置热点数据永不过期
2.分布式锁
缓存雪崩概念:是指在某一个时间段,缓存集体过期失效
解决方案:
1.redis高可用
2.限流降级
3.数据预热
内存淘汰和过期key删除策略
Redis 4.0前具体回收策略有以下6种: ● volatile-lru: 在所有带有过期时间的 key 中使用 LRU 算法淘汰数据;
● alkeys-lru: 在所有的 key 中使用最近最少被使用 LRU 算法淘汰数据,保证新加入的数据正常; ●
volatile-random: 在所有带有过期时间的 key 中随机淘汰数据; ● allkeys-random: 在所有的 key
中随机淘汰数据; ● volatile-ttl: 在所有带有过期时间的 key 中,淘汰最早会过期的数据; ● noeviction:
不回收,当达到最大内存的时候,在增加新数据的时候会返回 error,不会清除旧数据,这是 Redis 的默认策略;Ps: volatile-lru, volatile-random, volatile-ttl 这几种情况在 Redis 中没有带有过期
Key 的时候跟 noeviction 策略是一样的。淘汰策略是可以动态调整的,调整的时候是不需要重启的Redis 4.0后增加以下两种: ●
volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰(LFU(Least
Frequently Used)算法,也就是最频繁被访问的数据将来最有可能被访问到) ●
allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key。
常见的过期删除策略是惰性删除、定期删除、定时删除。Redis中同时使用了惰性删除和定期删除两种。
(1)惰性删除:只有访问这个键时才会检查它是否过期,如果过期则清除。 ● 优点:最大化地节约CPU资源。 ●
缺点:如果大量过期键没有被访问,会一直占用大量内存。(2)定时删除:为每个设置过期时间的key都创造一个定时器,到了过期时间就清除。 ● 优点:该策略可以立即清除过期的键。 ●
缺点:会占用大量的CPU资源去处理过期的数据。(3)定期删除:每隔一段时间就对一些键进行检查,删除其中过期的键。该策略是惰性删除和定时删除的一个折中,既避免了占用大量CPU资源又避免了出现大量过期键不被清除占用内存的情况。
Springboot使用Redis
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis序列化
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建String序列化工具
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 创建JSON序列化工具
Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 设置Key的序列化
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}