文章目录
Redis
Redis本质上是一个Key-Value
类型的内存数据库,很像memcached(分布式的高速缓存系统),不过数据可以持久化,而且支持的数据类型很丰富,有字符串,链表,集合和有序集合。支持在服务器端计算集合的并,交和补集(difference)等,还支持多种排序功能。所以Redis也可以被看成是一个数据结构服务器。
Redisd的整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。
Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
Redis的常见的几种数据结构:
- String:字符串
- Hash: 哈希
- List : 列表
- set :集合
- Zset :有序集合
缓存的优点:
- 缓存以key-value的形式存储,查询性能要高于数据库
- 缓存一般都存储在内存中,数据库的数据是存储在磁盘中的,在内存上操作效率要高于磁盘。
- 缓存可扩展性比较高,更容易进行分布式部署。
缓存的分类:
- 本地缓存
- 分布式缓存
1 常见缓存的使用:
- 本地缓存的常见使用:Spring的Cache、MyBatis的缓存
- 分布式缓存的常见使用:Redis 和Memcached
(1)本地缓存:
SpringBoot中本地缓存的使用:
1)开启缓存
主启动器类上配置@EnableCaching
@SpringBootApplication
@EnableCaching
public class SpringcacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcacheApplication.class, args);
}
}
2)配置缓存
编写service,缓存的名称为score,缓存数据对应的键为"#score
"
@Service
public class UserService {
@Cacheable(cacheNames = "score",key="#score")
public String getScore(int score){
System.out.println("开始计算成绩");
return score+"分";
}
}
编写controller
@Controller
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/getscore")
@ResponseBody
public String getScore(int score){
return userService.getScore(score);
}
}
注:
配置@ResponseBody,返回类型为json形式
调用postman访问http://localhost:8080/getscore?score=59
前端打印:
后端打印:
当再次访问http://localhost:8080/getscore?score=59
时,后端不再显示内容,说明缓存开始起作用了。
(2)分布式缓存
不同的项目操作的是同一个Redis,因此Redis对于整个项目来说是全局的(分布式的)。
1) Redis和Memcached的区别:
- 存储方式不同:memchache把数据全部存在内存中,断电后会挂掉,数据不能超过内存大小;Redis有部分存在硬盘上,保证了数据的持久性。
- 数据支持类型:memcache对数据类型支持相对简单;Redis有复杂的数据类型。
- 存储大小不同:Redis最大可以达到512mb, memcache只有1mb;
注:
单机情况下使用Spring中的Cache,分布式环境下,使用Redis.
2 Redis的数据类型
Redis有5大基础数据类型:
- String: 字符串类型
- Hash: 字典类型
- List: 列表类型
- Set: 集合类型
- ZSet: 有序集合类型
(1)字符串类型
字符串类型(Simple Dynamic Strings, SDS)——简单动态字符串,它是以键值对key-value的形式进行存储的,根据key来存储/获取value值。
使用:
set key1 hello ex 5
get key1
输出 hello
注:
ex 5
为5秒后过期
常见使用场景:
- 存放用户(登录信息);
- 存放文章详情和列表信息;
- 存放和累计网页的统计信息;
(2)字典类型
字典类型(Hash)——散列类型/哈希表类型,它将一个key和一个特殊的“哈希表”关联起来,这个哈希表包含两列数据:字段和值。类似于Java中的Map<String,Map<String,String>>
使用:
hset h1 key1 v1 k2 word
hget h1 k2
输出 word
(3)列表类型
列表类型(List)是一个使用链表结构存储的有序结构,它的元素插入会按照先后顺序存储到链表结构中,因此它的元素操作(插入和删除)时间复杂度为O(1),查找的时间复杂度为O(n)。
使用:
lpush list1 1 34 5 6
lpop list1
输出19
列表的使用场景:
- 消息队列:列表类型可以使用
lpush
实现先进先出的功能,同时又可以使用lpop
弹出第一个元素,所以列表类型可以用来实现消息队列。 - 文章列表:对于博客来说,当用户和文章较多时,为了加快程序的响应速度,我们可以将用户自己的文章放入List中,而List是有序的结构,我们可以实现分页功能,从而加速了程序的响应速度。
(4)集合类型
集合类型Set是一个无序并唯一的键值集合。
使用:
sadd set1 v1 v3 v4
smembers set1
输出 v1 v3 v4
使用场景:
- 微博中关注的人可以用集合存储,保证人员不会重复;
- 中奖信息也可以用集合类型存储,保证一个人不会重复中奖;
集合类型Set和列表类型List的区别:
- 列表可以存储重复元素,集合只能存储非重复的元素
- 列表是按照元素的先后顺序存储元素的,而集合是无序方式存储元素的。
(5)有序集合类型
有序集合Sorted Set,相对于集合类型多了一个排序属性score(分值),对于有序集合ZSet来说,每个存储元素相当于有两个值组成,一个是元素值,一个是排序值。有序集合的元素不能重复,但是分值score可以重复。
使用:
zadd z1 30 xi 40 lao 50 gao
zrange z1 0 -1 # -1表示所有的
输出:xi lao gao
使用场景:
- 学生成绩排名
- 粉丝列表,根据关注的先后时间排序
3 Redis持久化
持久化就是数据从内存保存到磁盘的过程,目的就是为了防止数据丢失。因为内存中的数据在服务器重启后就会丢失,而磁盘中的数据则不会,因此为了系统的稳定,我们需要将数据进行持久化。同时持久化也是Redis和Memcached的最主要区别之一,因为Memcached是不支持持久化的。
Redis持久化的3种方式:
- 快照方式(RDB,Redis DataBase):将某一时刻的内存数据,以二进制的方式写入磁盘。
- 文件追加方式(AOF,Append Only File):记录所有的操作命令,并以文本的形式追加到文件中;
- 混合持久化方法: Redis4.0之后新增的方式,混合持久化是结合了RDB和AOF的优点,在写入的时候,先把当前的数据以RDB的形式写入文件的开头,再将后续的操作命令以AOF的格式存入文件,这样既能保证Redis重启时的速度,又能降低数据丢失的风险。
持久化的策略设置
可以在reids-cli命令行中执行config set aof-use-rdb-preamble yes
来开启混合持久化,当开启混合持久化时,Redis就以混合持久化的方式来作为持久化策略;当没有开启混合持久化时,使用config set appendonly yes
来开启AOF持久化策略,当AOF和混合持久化都没开启的情况下,默认使用RDB的持久化方式。
(1)RDB快照方式的优缺点
RDB优点:
- RDB的内容为二进制的数据,占用内存更小,适合作为备份文件;
- RDB对灾难恢复非常有用,可以更快的传输到远程服务器进行Redis服务恢复;
- RDB可以更大程度的提高Redis的运行速度,因为每次持久化时主进程都会fork()一个子进程,进行数据持久化到磁盘,Redis主进程并不会执行磁盘的I/O等操作;
- 与AOF格式的文件相比,RDB文件可以更快的重启。
RDB缺点:
- RDB只能保存某个时间间隔的数据,如果中途Redis服务被意外终止了,则会丢失一段时间的Redis数据
- RDB需要经常fork()才能使用子进程将其持久化到磁盘上。如果数据集比较大,fork()可能会很耗时,如果数据集很大,而CPU的性能欠佳,则可能导致Redis停止为客服端服务几毫秒。
注:
fork()
是创建进程函数。一个进程调用fork()
函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。- fork返回两个值,一个代表父进程,一个代表子进程:代表父进程的值是一串数字,这串数字是子进程的ID(地址);一个代表子进程,值为0。
(2)文件追加方式AOF的优缺点
AOF优点:
- AOF持久化保存的数据更加完整,AOF提供了三种保存策略,每次操作保存、每秒保存一次、跟随系统的持久化策略保存。其中每秒保存一次,从数据的安全性和性能两方面考虑是一个不错的选择,也是AOF默认的策略,即使发生了意外的情况,最多只会丢失1s钟的数据;
- AOF采用的是命令缀加写入的方式,所以不会出现文件损坏的问题,即使由于某些意外原因导致了最后操作的持久化数据只写入了一半,也可以通过redis-check-aof工具进行修复。
- AOF持久化文件非常容易理解和解析,它把所有Redis键值操作命令,以文件的方式存入了磁盘。即使不小心用flushall删除了所有的键值信息,只要在AOF文件中删除最后的flushall命令,重启Redis即可恢复之前误删的数据。
AOF缺点:
- 对于相同的数据集来说,AOF文件大于RDB文件
- 在Redis负载比较高的情况下,RDB要比AOF的性能要好
- RDB使用快照的形式持久化整个Redis数据,而AOF只是将每次执行的命令追加到AOF文件中,因此理论上说,RDB比AOF更加健壮。
(3)混合持久化的优缺点
混合持久化优点:
- 混合持久化结合了RDB和AOF持久化的优点,开头以RDB的格式,使得Redis可以更快的启动,同时结合AOF的优点,降低了大量数据丢失的风险。
混合持久化的缺点:
- AOF文件中添加了RDB格式的北荣,使得AOF文件的可读性变差
- 兼容性比较差,如果开启了混合持久化,那么Redis 4.0之前的版本是不支持的。
4 常见的缓存问题
(1)缓存雪崩
缓存雪崩是指在短时间内,有大量的缓存同时过期,导致大量的请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机。
解决方法:
1)加锁排队
加锁排队可以起到缓冲的作用,防止大量的请求同时操作数据库,但缺点就是会增加系统的响应时间,降低系统的吞吐量,牺牲了一部分用户体验。
2)随机化过期时间
为了避免缓存同时过期,可以在设置缓存时额外添加一个随机时间,这样可以极大的避免大量的缓存同时过期。
int exTime = 60; //原本的过期时间
Random random = new Random();
//缓存设置
jedis.setex(cacheKey,exTime+random.nextInt(1000),value);
3)设置二级缓存
当Redis失效后,先去查询二级缓存。
(2)缓存穿透
缓存穿透是指查询缓存和数据库时都没有查询到数据,对于此类查询,因为在缓存中查不到数据,所以都会去数据库再次查询。
解决方法
1)缓存空结果
将数据库查询的结果保存到缓存中(包括空结果),为了提高用户体验,我们可以将空结果的缓存时间设置的短一些(3-5分钟),以防止用户长时间查询不到数据的情况。
(3)缓存击穿
缓存击穿是指某一时刻,某个热点缓存突然失效了,引起了大量的并发请求,对数据库造成了巨大压力。
解决方法:
- 加锁排队
和缓存雪崩时的加锁排队方式一致,都是在查询数据库时进行加锁排队。
2)设置永不过期
对于某些热点缓存,我们可以设置永不过期,这样能保证缓存的稳定性,但需要注意在数据更改之后要及时更新热点缓存,不然会造成查询结果的误差。
(4)缓存预热
缓存预热是一个优化方案,用此来提高用户体验。缓存预热是指在系统启动的时候,先把查询结果预存到缓存中,以便用户以后查询时,可以直接从缓存中读取,以节约用户的等待时间。
缓存预热的方案:
- 将需要缓存的方法写到系统的初始化方法中,这样系统启动时就会自动加载数据并缓存数据。
- 把需要缓存的方法加到某个页面或后端的接口上,手动触发缓存预热。
- 设置定时任务,定时自动进行缓存预热
5 Redis集群
Redis的多机服务
- Redis主从同步
- Redis哨兵模式
- Redis集群服务
(1)中从同步
主从同步是Redis高可用服务的基石,也是多机运行中最基础的一个。我们将主要存储数据的节点称为主节点(master),把通过复制主节点数据的副本节点称为从节点(slave).
主从同步的优缺点:
优点:
- 性能方面:主从同步可以将查询任务分配给从服务器,主服务器主要用来执行写操作,这极大的提高了程序运行的效率,把所有的压力分摊到各个服务器了;
- 高可用:当主服务器宕机后,可以迅速把从节点提升为主节点,为Redis服务器的宕机恢复节省了时间。
- 防止数据丢失:当注服务器磁盘坏掉后,其他从服务器还保留着相关的数据,不至于数据全部丢失。
缺点:
当主节点崩溃后,需要人工干预才能恢复Redis的正常使用。
(2)哨兵模式
主从服务器节点较多的情况下,节点宕机需要人工恢复的难度比较大。因此我们需要一个自动工具——Redis Sentinel(哨兵模式)来把手动的过程变成自动的,让Redis拥有自动容灾恢复的能力。哨兵模式主要负责监控主从服务器的节点,当主从服务器节点出现问题的时候可以进行自动容灾恢复。
(3)Redis集群服务
Redis集群(Redis Cluster)是Redis多机运行的终极方案,它是Redis 3.0之后推出的服务,它的出现可以让我们完全抛弃主从同步和哨兵模式来实现Redis的多机运行。
- redis集群采用 去中心化的方式(每个节点都会跟其他节点保持连接,用来交换彼此的信息)实现,无需proxy代理,客户端直接与redis集群的每个节点连接,根据同样的hash算法(哈希槽 (hash slot))计算出key对应的slot,然后直接在slot对应的redisj节点上执行命令。
- redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。
注:
每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。