Redis
1. NoSQL四大类型
- 键值数据库: Redis
- 列族数据库: HBase、BigTable
- 文档数据库: MongoDB(使用者:百度云数据库)
- 图形数据存储: GraphDB
NoSQL特点:
- 解耦,方便扩展(数据之间没有关系)
- 大数据量高性能(redis一秒写8万次,读取11万,nosql的缓存记录级,是一种细粒度的缓存,性能较高)
- 数据类型是多样型的(不需事先设计数据库,随取随用)
2. 什么是Redis?为什么要用Redis?Redis应用场景、特性
定义:
Redis(Remote Dictionary Server)远程字典服务
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
提高查询的速度,把热点数据放在redis中
服务器启动时,就把热点数据直接放在redis中
(服务器启动时,没有往redis中放数据,用户第一次取数据时,先从redis中取,取不到,再从mysql中取,把mysql中的数据放到redis中。)
原因:
- redis以内存作为数据存储介质,读取数据的效率极高,远远超过一般数据库。
(redis一秒写8万次,读取11万,是mysql的10倍) - 存储在redis的数据是持久化的,断电或重启数据也不会丢失。因为redis的存储分为内存存储、磁盘存储和log文件三部分,重启后,redis可以重新将数据从磁盘加载到内存,这样redis就实现了持久化。
应用场景:
①会话缓存(最常用)
②消息队列,比如支付
③活动排行榜或计数
④发布订阅消息(消息通知)
⑤商品列表,评论列表
特性:
- 多样的数据类型
- 持久化
- 集群
- 事务
3. Redis支持的五大数据类型
String
- String是redis最基本的类型,能存放字符串,整数,浮点数 ,能存json,json能转成对象、图片
- String类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
- String类型最大能存储512MB。
相关命令:
- keys * 查询数据
- get key value 获取值
- set key value 设置值
- exists key 判断是否存在,如果存在值为1
- del key 删除
- type key 查看类型
- expire key 设置key的过期时间
- ttl key 查看key的有效秒数
List
- list是列表,可以存放字符串,整数,浮点数,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
- list可以重复
- List类型的数据较灵活,可以当做栈使用使其先进后出,也可以当做消息队列使用使其从一侧入,从另一侧出
相关命令:
- rpush 列表名 data 向列表头部存储数据(从右边压入)
- lpop 列表名 从左边弹出显示
- lLen 列表名 取列表长度
- lIndex 列表名 num 按下标num取某个值
- lrange 列表名 0 -1 返回列表中指定区间内的元素
0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。或者 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
Set
- Set是String类型的无序集合。
- 无序集合中元素不允许重复。
相关命令:
- sadd 集合名 data1 data2 向集合中添加数据(无序)
- smembers 集合名 读取集合中的所有数据
Hash
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
- hset myhash filed1 abc
将key为field1,value为abc放入hash名为的myhash中,如果存在filed1则覆盖,不存在则创建 - hgetall myhash 以键值对的形式返回所有的hash
hash更适合于对象的存储,String更加适合字符串存储
Zset
有序集合
4. 事务
Redis事务特点:
- Redis单条命令要保证原子性,但Redis事务不保证原子性
- Redis事务没有隔离性,没有隔离级别的概念
所有的命令在事务中并不会直接被执行,只有发起执行命令的时候才会执行(Exec) - 一次性、顺序性、排他性(不允许被干扰)
Redis事务三个阶段:
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
取消事务(discard)
注:
- 编译型异常时(代码有问题,命令有错),事务中所有的命令都不会被执行
- 运行时异常时 ,执行命令的时候,其他命令是可以正常运行的,错误命令抛出异常。
5. Redis和SpringBoot整合
整合(中间件Jedis)
Jedis是Redis官方推荐的Java连接开发工具
用jedis去操作redis数据库
- jedis = new Jedis(ip,port);
- jedis.set() 添加数据
- jedis.get() 获取数据
- jedis.rpush() 将一个或多个值插入到列表的尾部(最右边)
- jedis.hmset() 同时将多个 field-value (字段-值)对设置到哈希表中
- jedis.del() 删除某个键
- 创建项目,添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
- 写数据
application.yml
redis:
host: 192.168.216.201
port: 6379
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
username: root
password: 123456
mybatis:
mapperLocations: classpath:com.tedu.mapper/*.xml
logging:
path: ./logs
level:
com.tedu.mapper: debug
@RestController
public class RedisController {
@Value("${redis.host}")
String host;
@Value("${redis.port}")
String port;
@RequestMapping("/redis/insert")
public String insert() throws Throwable {
Jedis jedis = new Jedis(host, Integer.parseInt(port));
Item item1 = new Item("1001", "mate10");
Item item2 = new Item("1002", "mate20");
ObjectMapper objectMapper = new ObjectMapper();
String item1Json = objectMapper.writeValueAsString(item1);
String item2Json = objectMapper.writeValueAsString(item2);
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("1001", item1Json);
hashMap.put("1002", item2Json);
jedis.hmset("itemList", hashMap);
jedis.close();
return "ok";
}
}
3. 读数据
@RequestMapping("/get")
public Map<String,String> get() {
Jedis jedis=new Jedis(host,Integer.parseInt(port));
Map<String,String> map=jedis.hgetAll("itemList");
jedis.close();
return map;
}
6. Redis.conf配置内容
- 网络
- bind 127.0.0.1 绑定ip
- port 6379 端口
- protected-mode no 保护模式
- 通用
- daemonize yes 守护进程,默认是no,需要设置为yes开启后台运行
- 日志
- logfile “” #日志的文件位置名
- databases 16 #默认的数据库数量
- 快照 rdb
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb- save 900 1
如果900秒内至少有1个key修改了一次,进行持久化(保存) - save 300 10
如果300秒内至少有10个key修改了一次,进行持久化 - save 60 10000
如果60秒内至少有10000个key修改了一次,进行持久化
- save 900 1
- aof配置
- appendonly no
默认不开启aof模式,默认是rdb方式持久化,大部分情况下rdb够用了 - appendfsync everysec
每秒执行一次同步 - appendfsync no 不执行同步 (默认)
- appendonly no
7. Redis持久化
7.1 RDB(Redis DataBase)
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,他恢复时将快照文件直接读到内存里。
Redis会单独创建一个子进程(fork) 来进行持久化,会将数据写入到一个临时文件中,持久化过程都结束了,再将这个临时文件替换上次持久化好的文件。整个过程主进程是不进行任何IO操作的,这确保了极高的性能。
- rdb保存的文件是dump.rdb
- 配置默认是RDB持久化方式
- RDB方式比AOF方式更加高效
- 优点:
- 适合大规模的数据恢复
- 对数据的完整性不高
- 进行持久化的时候会fork子进程去执行,主进程的任务不受到影响
- 缺点:
- 最后一次持久化的数据可能丢失(!如果redis意外宕机了)
- 需要一定的时间间隔进行操作
- 、fork进程的时候,会占用一定的内存空间
触发机制:
- save的规则满足的情况下
- 执行flushall命令
- 退出redis (kill是不会触发的)
7.2 AOF(Append only File)
以日志的形式来记录写操作,将Redis执行过的所有指令记录下来(读操作不记录),只可追加文件不可以改写文件。
redis启动之初会读取该文件重新构建数据。即redis重启的话就根据日志文件内容将指令从前到后执行一次以完成数据的恢复。
- AOF保存的是appendonly.aof文件
- 手动进行配置 appendonly yes
- aof文件被破坏,redis会拒绝启动的,需要修复aof文件
- redis提供了修复工具redis-checj-aof --fix,错误数据会被清除
- rdb和aof可以同用,同用时恢复数据默认使用aof,redis默认开启rdb
- 优点:
- 数据保存较完整
- 缺点:
- 相对于数据文件来说,aof远大于rdb,修复速度也比rdb慢
- AOF运行效率比RDB慢
8. 分布式锁
redis是单进程单线程
- 线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
- 进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
- 分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
分布式锁使用场景:
- 线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的
- 分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。
分布式锁的实现:
分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。
- 在实现的时候要注意的几个关键点:
1、锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
2、同一时刻只能有一个线程获取到锁。
几个要用到的redis命令:
- setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。
- get(key):获得key对应的value值,若不存在则返回nil。
- getset(key, value):先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value。
- expire(key, seconds):设置key-value的有效期为seconds秒。
9. Redis缓存预热、缓存雪崩、缓存穿透
缓存预热
提前将热点数据写入redis缓存中
解决思路:
- 直接写个缓存刷新页面,上线时手工操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
缓存雪崩
可以简单的理解为:由于原有缓存失效,新缓存未到期间。
**原因:**设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期。
解决方法:
- 设置不同的过期时间
- 考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
缓存穿透
缓存和数据库中查不到
用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中。于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候。缓存都没有命中,于是请求去请求了持久层数据库。这会给持久层数据库造成很大的压力,这就相当于出现了缓存穿透。
eg:恶意访问不存在的数据
解决方法:
- 布隆过滤器:一种数据结构,对所有可能查询到的参数以hash形式存储,在控制层先进行校验,不符合的丢弃,从而避免了对底层存储系统的查询压力。
eg: 在controller中写一个set集合,里面存放id,查询不到时,立即返回,不会再去数据库中查找 - nginx限流
10. Redis主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者为主节点(master/leader),后者为从节点(slave/follower)。
数据的复制是单向的,只能由主节点到从节点。
master以写为主,slave以读为主。
作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复
- 负载均衡
- 高可用基石
面试题:如何保证redis与mysql中的数据一致
- 更新了mysql中的数据,也要更新redis
- 删除了mysql中的数据,也要删除redis中的数据
- update时,先删除redis中的数据,更新mysql。把查出来的最新数据再放入redis中。
面试题:Redis内存满了,怎么办
在redis.conf中配置回收算法
LRU:最近不访问的key,自动删除