Redis基础认识

中间件Redis的了解与认识

Posted by John Doe on 2020-09-08
Words 3.2k and Reading Time 11 Minutes
Viewed Times

概述

Redis属于NOSQL((NoSQL = Not Only SQL ),意即“不仅仅是SQL”。NoSQL 数据库种类繁多(Redis、Mongodb、HBase、Elasticsearch 等等)),NOSQL的特点是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。无形之间也在架构的层面上带来了可扩展的能力。NoSQL数据库都具有非常高的读写性能,尤其在大量下数据,更能体现它的优势。这得益于它的无关系性,数据库的结构简单。

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库在内存当中进行操作,定期通过异步操作把数据库数据写到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key- Value DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。默认16个数据库,类似数组下标是从零开始,初始默认使用零号库,统一的密码管理,16个库都是同样密码,要么都连上要么一个也连接不上,redis默认端口是6379。

在 Redis 中,事务总是具有原子性 (Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时(快照、文件追加),事务也具有持久性(Durability)。

快照(snapshotting)持久化(RDB) Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。
快照实例配置

1
save 900 1              #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令 创建快照。

AOF(append-only file)持久化 与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启 AOF(append only file)方式的持久化,可以通过appendonly参数开启:

1
appendonly yes

开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的 保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
1
2
3
appendfsync always     #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no     #让操作系统决定何时进行同步

为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能 几乎没受到任何影响。而且这样即使出现系统崩溃,用户多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操 作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的大写入速度

redis的优点:
速度快:因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) 持久化:定期通过异步操作把数据库数据写到硬盘上进行保存
支持丰富数据类型:支持string,list,set,sorted set,hash
支持事务:操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

redis支持类型详细:

  1. String
    常用命令: set,get,decr,incr,mget 等。 String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。

  2. Hash
    常用命令: hget,hset,hgetall 等。
    Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅 仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息:

    1
    2
    3
    4
    5
    6
    7
    key=JavaUser293847
    value={
    “id”: 1,
    “name”: “SnailClimb”,
    “age”: 22,
    “location”: “Wuhan, Hubei”
    }
  3. List
    常用命令: lpush,rpush,lpop,rpop,lrange等
    list 就是链表,Redis list 的应用场景非常多,也是Redis重要的数据结构之一,比如微博的关注列表,粉丝列表, 消息列表等功能都可以用Redis的 list 结构来实现。 Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
    另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。

  4. Set
    常用命令: sadd,spop,smembers,sunion 等 set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
    当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。

  5. Sorted Set
    常用命令: zadd,zrange,zrem,zcard等 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
    举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维 度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。

缓存雪崩和缓存穿透问题解决方案

缓存雪崩
简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩 掉。
解决办法(中华石杉老师在他的视频中提到过,视频地址在后一个问题中有提到):
事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉 事后:利用 redis 持久化机制保存的数据尽快恢复缓存

缓存穿透
简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量 请求而崩掉。
解决办法: 有很多种方法可以有效地解决缓存穿透问题,常见的则是采用布隆过滤器,将所有可能存在的数据哈 希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压 力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,长不超过五分钟。

Redis序列化

针对数据的“序列化/反序列化”,springboot提供了多种可选择策略(RedisSerializer) :
JdkSerializationRedisSerializer:这个序列化方法就是Jdk提供的了。首先要求我们要被序列化的类继承自Serializeable
接口,然后通过,然后通过Jdk对象序列化的方法保存。(注:这个序列化保存的对象,即使是个String类型的,在redis
控制台,也是看不出来的,因为它保存了一些对象的类型什么的额外信息)。是目前最常用的序列化策略。

StringRedisSerializer:就是通过String.getBytes()来实现的。而且在Redis中,所有存储的值都是字符串类型的。所以这种方法保存后,通过Redis-cli控制台,是可以清楚的查看到我们保存了什么key,value是什么。是最轻量级和高效的策
略。

JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json 格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此策略封装起来稍微复杂。

spring boot导入redis依赖后,spring boot启动后会像spring 注入两个bean:RedisTemplate、StringRedisTemplate,

  1. 两者的关系是StringRedisTemplate继承RedisTemplate。
  2. RedisTemplate是一个泛型类,而StringRedisTemplate则不是。
  3. StringRedisTemplate只能对key=String,value=String的键值对进行操作,RedisTemplate可以对任何类型的key-value键值对 操作。
  4. 他们各自序列化的方式不同,但最终都是得到了一个字节数组,殊途同归,StringRedisTemplate使用的是StringRedisSerializer类;RedisTemplate使用的是JdkSerializationRedisSerializer类。反序列化,则是一个得到String,一个得到Object。

RedisTemplate在操作数据的时候,存入数据会将数据先序列化成字节数组然后在存入Redis数据库(默认JdkSerializationRedisSerializer:这个序列化方法就是Jdk提供的了,首先要求我们要被序列化的类继承自Serializeable接口,然后通过Jdk对象序列化的方法保存),这个时候打开Redis查看的时候,你会看到你的数据不是以可读的形式,展现的,而是以字节数组显示,
序列化不可读
(注:这个序列化保存的对象,即使是个String类型的,在redis控制台,也是看不出来的,因为它保存了一些对象的类型什么的额外信息)

Redis最近应用

个人开发小型JavaWeb程序利用redis存储token,记录获取验证的电话号码今日申请了几次,通过JWT将对象信息转化为String。
改造StringRedisTemplate、自定义RedisTemplate的实现类,使得Object对象转化为json使key-vlue再次转化为String-String进行序列化存储。
StringRedisSerializer源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.springframework.data.redis.serializer;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import org.springframework.lang.Nullable; import org.springframework.util.Assert;

public class StringRedisSerializer implements RedisSerializer<String> { private final Charset charset;
public static final StringRedisSerializer US_ASCII; public static final StringRedisSerializer ISO_8859_1;

public static final StringRedisSerializer UTF_8;

public StringRedisSerializer() { this(StandardCharsets.UTF_8);
}

public StringRedisSerializer(Charset charset) { Assert.notNull(charset, "Charset must not be null!"); this.charset = charset;
}

public String deserialize(@Nullable byte[] bytes) {
return bytes == null ? null : new String(bytes, this.charset);
}

public byte[] serialize(@Nullable String string) {
return string == null ? null : string.getBytes(this.charset);
}

static {
US_ASCII = new StringRedisSerializer(StandardCharsets.US_ASCII); ISO_8859_1 = new StringRedisSerializer(StandardCharsets.ISO_8859_1); UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);
}

自定义序列化方式使得可以接收Object对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.util.Assert;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
*@ClassName: MyStringRedisSerializer
*TODO:类文件简单描述
*@Version: 0.0.1
*/
public class MyStringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;

public MyStringRedisSerializer() {
this(StandardCharsets.UTF_8);
}

public MyStringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}

@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}

//修改部分
@Override
public byte[] serialize(Object object) {
if (object == null) {
return new byte[0];
}
if(object instanceof String){
return object.toString().getBytes(charset);
}else {
String string = JSON.toJSONString(object);
return string.getBytes(charset);
}
}
}


This is copyright.

...

...

00:00
00:00