欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Redis基础总结

程序员文章站 2022-06-18 13:55:09
...

Redis基础总结

1、NoSQL出现的背景

单机MySQL→Memcached(缓存)+MySQL+垂直拆分→MySQL主从读写分离→分表分库+水平拆分+MySQL集群

MySQL存在的瓶颈

  • MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常慢,不容易快速数据库
  • 表结构更改困难

NoSQL–Not OnlySQL

  • 泛指非关系型数据库
  • 数据存储不需要固定的模式,无需多余操作就可以横向扩展

Nosql特点

  • 易扩展:去关系型数据库的关系型特性
  • 大数据量高性能:读写性能高、数据库的结构简单,细粒度的Cache(记录级)
  • 多样灵活的数据模型:无需事先建立字段,随时可以存储自定义的数据格式

RDBMS与NoSQL比较

  • RDBMS
    • 高度组织化结构化数据
    • 结构化查询语言(SQL)
    • 数据和关系都存储在单独的表中
    • 数据操纵语言,数据定义语言
    • 严格的一致性
    • 基础事务
  • NoSQL
    • 代表不仅仅是SQL
    • 没有声明查询语言,没有预定义的模式
    • 键-值对存储,列存储,文档存储,图形数据库
    • 最终一致性,而非ACID属性
    • 非结构化和不可预知的数据
    • CAP定理
    • 高性能、高可用和可伸缩性

ACID和CAP

  • ACID:原子性、一致性、隔离性、持久性
  • CAP:强一致性、高可用性、分布式容忍性

2、Redis入门

2.1 Redis初了解

Redis:Remote Dictionary Server 远程字典服务器

是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。

Redis特点

  • 支持数据持久化
  • 支持key-value类型数据,同时还提供list、set、zset、hash等数据结构
  • 支持数据备份,即master-slaver模式的数据备份
  • 使用单线程的多路IO复用模型

Redis是单线程:单线程模型来处理客户端的请求。对读写等事件的响应

  • Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是机器的内存和网络带宽,Redis是C语言编写的,官方提供的数据达到100000+的QPS(每秒内查询次数),这个数据不比Memcached差(单进程多线程的基于内存的Key-Value数据库)

Redis和Memecache区别

  • Redis支持丰富的数据类型,包括简单的key/value的数据类型,同时还提供list,set,zset,hash等数据结构的存储。Memecache只支持简单的数据类型;
  • Redis支持数据持久化,通过RDB和AOF方式持久化,而Memecache不支持;
  • Redis原生支持集群模式,Memecache没有原生集群模式,需要依靠客户端来实现往集群分片写入数据
  • Redis使用单线程的多路IO复用模型,Memecache是多线程非阻塞IO复用的网络模型。

Redis内存淘汰机制和实现

(1)内存淘汰策略

  • 从已设置过期时间的的数据集中淘汰
  • 从整个数据集中淘汰
1、volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
2、volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
3、volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
4、volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
5、allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
6、allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰
7、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
8、noeviction : 永不过期,返回错误,默认策略

(2)淘汰机制的实现

  • 删除失效主键

    • 消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它。redis在实现GET、MGET、HGET、LRANGE等所有涉及到读取数据的命令时都会调用 expireIfNeeded,它存在的意义就是在读取数据之前先检查一下它有没有失效,如果失效了就删除它。

      expireIfNeeded函数中调用的另外一个函数propagateExpire,这个函数用来在正式删除失效主键,并且广播告诉其他地方,目的地有俩:AOF文件,将删除失效主键的这一操作以DEL Key的标准命令格式记录下来;另一个就是发送到当前Redis服务器的所有Slave,同样将删除失效主键的这一操作以DEL Key的标准命令格式告知这些Slave删除各自的失效主键。

    • 积极方法(active way),周期性地探测,发现失效就删除

    • 主动删除:当内存超过maxmemory限定时,触发主动清理策略

  • 置换策略:为了避免频繁的触发淘汰策略,每次会淘汰掉一批数据,淘汰的数据的大小其实是和置换的大小来确定的,如果置换的数据量大,淘汰的肯定也多。

Redis简单命令

# 开启redis服务
redis-server /myredis/reids.conf
redis-cli -p 6379
    
# 查看进程是否开启
ps -ef|grep redis
# 关闭进程、关闭redis
shutdown
exit
    
# 切换数据库,select dataNum[0-15]
select 1
# 查看数据库中所有的key
keys *
# 清除当前数据库
flushdb
# 清除所有数据库的内容
FLUSHALL
  • 默认16个数据库,表从零开始,初始默认使用零号库

2.2 五大数据类型

String、List、Hash、Set、Zset

Redis:key

keys *			# 查询数据库所有的key
set key value	# 设置值
get key			# 获得值
exists key		# 判断当前的key是否存在
expire key 10	# 设置key的过期时间,单位秒
ttl key 		# 查看当前key存活的时间
type key		# 查看当前key的类型

String:字符串

set key value	# 设置值
get key			# 获得值
exists key		# 判断当前的key是否存在
append key hello	# 追加字符串hello,如果当前key不存在,就相当于设置值
strlen key		# 获取字符串的长度
incr key		# 自增1
desr key		# 自减1
incrby key 10	# 设置步长,指定增量
decrby key 10
getrange key 0 3	# 截取字符串[0,3],[0,-1]获取全部字符串
setex			# 设置过期时间
setnx			# 判断不存在,设置值
mset			# 设置多个值
mget
msetnx			# 原子性操作,有一个失败就全部失败

List:列表

list: 栈、队列、阻塞队列

lpush list value	# 将一个值或多个值,插入到列表头部(左,后进先出) [栈]
lrange list 0 1 	# 获取区间具体的值,[0,-1]获取list全部值
rpush list value	# 将一个值或多个值,插入到列表尾部(右,先进先出) [队列]

lpop list		# 移除list的第一个元素
rpop list		# 移除list最后一个元素
lrem list 2 one	# 移除list指定个数的value,精准匹配(移除key中2个one)

ltrim list 1 2	# 截取指定下标长度的list,并把截取部分赋值给list
rpoplpush list otherlist	# 移除list的最后一个元素,并将它移动到新的list中
exists list		# 判断这个列表是否存在

lset list 0 re	# 更新list中0下标的值为re
linsert list before[after] world other	# 将other插入到列表list的world前面或后面
  • list的底层是链表结构
  • 在两边插入或改动值效率最高,中间元素相对效率低一点
  • 消息队列(lpush、rpop),栈(lpush、lpop)

Set:集合

set值是无序,不重复

sadd set1 value ...		# 添加值到set1
smembers set 1			# 查看set1的所有值
sismember set1 hello 	# 判断hello是否在set1中
scard set1				# 获取set1集合的元素个数
srem set1 hello			# 移除set1集合中的hello元素
srandmember set1 [2]	# 随机抽出元素,可以指定抽出元素个数,默认为1
spop set1 				# 随机删除set集合中元素,可以指定精确删除的值
smove set1 setother xuzy # 将元素xuzy从set1移动到setother

sdiff set1 set2		# 差集
sinter set1 set2	# 交集
sunion set set2 	# 并集

Hash:哈希

Map集合,key-value

hset hash1 field1 value ...		# 设置一个hash,值为field1-value
hget hash1 field1 field2 ...	# 获取值
hgetall hash1					# 获取所有数据
hdel hash1 field1				# 删除hash1里的field1字段
hlen hash1 				# 获取hash1的字段数量
hexists hash1 field1	# 判断field1在hash1中是否存在
hkeys hash1 			# 获取hash1中所有field
hvals hash1				# 获取hash1中所有的value

hincrby hash1 field1 1	# 指定增量为1
hsetnx hash1 field hello 	# 如果不存在可以设置,如果存在着不能设置

Zset:有序集合

zadd zset1 score1 v1	# 添加值
zrange zset1 0 -1		# 获取所有值

zrangebyscore zset1 -inf +inf [withscores] 	# 显示所有值,并且根据score从小到大,[附带成绩],inf也可用实际的数值表示条件筛选

zrem zset1 xuzy		# 删除指定元素xuzy
zcard zset1			# 获取有序集合的个数

zcount zset1 1 3    # 获取指定score区间的成员数量

2.3 三种特殊数据类型

Geospatial:地理位置

# geoadd key 经度 纬度 城市
geoadd city 116.40 39.90 beijing
geopos city beijing

# 两地(人)距离 单位
geodist city beijing guangzhou km
# 以给定的经纬度为中心,找出某一个半径内的元素,[显示到中心点距离],[显示半径内的元素的定位信息]
georadius city 110 20 1000 km [withdist] [withcoord]
# 筛选结果的数量
georadius city 110 20 1000 km count 1
# 找出位于指定元素(指定)周围的其他元素
georadiusbymember china:city beijing 1000 km

# 将二维的经纬度转换为字符串,两个字符串越接近,距离越近
geohash city beijing guangzhou
  • 延伸
# geo底层实现原理是Zset,因此可以使用Zset的命令操作geo
# 查看全部元素
zrange city 0 -1
# 删除指定元素
zrem city beijing

Hyperloglog:基数

基数:不重复的元素,可以接受误差,如果不能容错,就使用set集合统计

Redis Hyperloglog 基数统计算法

pfadd key value1 value2 ...		# 创建第一组元素
pfcount key						# 统计key元素的基数数量
pfmerge key3 key key2			# 将key和key2合并到key3 并集

Bitmap:位存储

存储二进制记录,即只有0和1两种状态

# setbit key offset value 设置值
setbit sign 0 1
# getbit key offset	获取值
getbit sign 0
bitcount sign	# 统计值为1的个数

3、事务

Redis事务:一组命令的集合、所有命令序列化执行。

一次性、顺序性、排他性

注意:Redis单条命令具有原子性,但事务不保证原子性

  • 开启监控(watch)/【unwatch解锁】
  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)/取消事务(discard)

编译期异常,事务所有命令都不会被执行。

运行期异常,错误命令无法执行,其他命令可以正常执行。(这是事务没有原子性的原因)

乐观锁

  • 基于数据版本的记录机制实现
  • 使用watch监视键值对,如果事务提交exec时,watch监视的键值对发生变化,事务将被取消

分布式锁

//TODO

Redis的并发竞争问题

问题描述:Redis的并发竞争Key问题是多个系统同时对一个key进行操作,但最后执行的顺序和我们期望的顺序不同,导致结果的不同。

解决方法:基于Zookeeper临时有序节点实现分布式锁。大致思想:每个客户端对某个方法加锁时,在Zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬间有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬间节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

5、解读Redis.conf

启动redis的配置文件

redis-server /../redis.conf
redis-cli -p 6379

配置文件配置内容解读

# unit单位,大小写不敏感

# include 包含 引入其他配置文件
# include /path/to/local.conf

# 网络
bind 127.0.0.1 		# 绑定IP,只有绑定的IP才能访问
protect-mode yes	# 保护模式,默认yes
port 6379			# 端口设置

# general 通用
daemonize yes		# 以守护进程方式运行,默认是no
pidfile /var/run/redis_6379.pid		#以后台方式运行,需要指定一个pid文件

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice
logfile ""		# 日志文件位置名
databases 16	# 数据库数量,默认16
always-show-logo yes	# 是否显示logo

# snapshot 快照
# RDB持久化配置区域
save 900 1	# 900s内,至少有1个key进行修改,则触发持久化操作
save 300 10
save 60 10000

stop-write-on-bgsave-error yes	# 持久化出错,是否还继续工作
rdbcompression yes		# 是否压缩rdb文件,需要消耗一点CPU资源
rdbchecksum yes			# 保存rdb文件的时候,进行错误检查校验
dir ./		# rdb文件保存目录

# replication 复制
# 主从复制设置

# security 安全
# 设置redis密码,默认没有密码

# clients 限制
maxclients 10000	# 设置能连接上redis的最大客户端数量
maxmemory <bytes>	# 配置最大内存容量
maxmemory-policy noeviction 	# 内存到达上限之后的处理策略
    1、volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
    2、volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
    3、volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
    4、volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
    5、allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
    6、allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰
    7、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
    8、noeviction : 永不过期,返回错误,默认策略

# append only file 模式
# AOF持久化配置
appendonly no	# 默认不开启AOF模式,大部分场景下RDB完全够用
appendfilename "appendonly.aof"		# 持久化的文件名字

# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb		# 如果aof文件大于64mb,将自动fork一个新的进程来将重写

6、Redis持久化

Redis是内存数据库,如果不将内存中的数据持久化到磁盘中,一旦服务器进程退出,则服务器终端数据库状态也会消失,因此Redis提供了持久化功能。

Redis持久化有两种方法:RDB和AOF

6.1 RDB

Redis会单独创建(fork)一个子进程来进行持久化,首先将数据写入一个临时RDB文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作,确保了极高的性能。如果需要进行大规模数据恢复,且对数据恢复的完整性不是很敏感,那么RDB方式比AOF方式要更加高效。

缺点:

  • 如果发生宕机,最后一次持久化的数据可能丢失
  • fork进程的时候,会占用一定的内容空间

Redis基础总结

6.2 AOP(Append Only File)

以日志形式记录每个写操作,将Redis执行的所有指令记录下来(读操作不记录),只许追加文件,但不可以改写文件,Redis启动前会读取该文件重构数据。

redis提供一个检查校验工具:redis-check-aof --fix

优点:

  • 每一次修改都同步,数据同步的完整性更好
  • 每秒同步一次,最多丢失1s数据
  • 从不同步的效率最高,由操作系统自己同步数据

缺点:

  • 数据文件大小:aof远大于rdb,修复的速度也比rdb慢
  • 运行效率:aof运行效率比rdb慢,redis默认的配置就是rdb持久化

Redis基础总结

7、主从复制

主从复制(master-slave)主节点Redis服务器的数据,复制到从节点Redis服务器。

默认情况下,每台Redis服务器都是主节点。

主从复制的作用:

  • 数据冗余:实现数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复:当主节点发生问题,可以由从节点提供服务,实现快速的故障恢复
  • 负载均衡:读写分离,提高Redis服务器的并发量
  • 高可用(集群):Redis高可用的基础

环境配置:只配置从库

info replication	# 查看当前库信息
slaveor ip port		# 设置作为主机(ip)的从机

复制原理

  • Slave启动成功连接master后会发送一个sync同步命令
  • master在第一次接到同步命令时,会进行一次全量复制,后面开始做增量复制
    • 全量复制:一次完全同步操作
    • 增量复制:master将新的修改命令依次传给slave,完成同步

哨兵模式

哨兵(Sentinel)模式:后台监控注解是否故障,如果故障将根据投票数自动将从库转换为主库,其他从库作为新选定的主库的从库。

Redis基础总结

  1. 配置哨兵配置文件sentinel.conf

    # sentinel monitor 被监控的名称 host port 1
    sentinel monitor myredis 127.0.0.1 6379 1
    
  2. 启动哨兵

    redis-sentinel /../sentinel.conf
    

注意:主机宕机重连后,会成为新主机的从机

8、Redis缓存穿透和雪崩

缓存穿透

故意的去请求缓存中不存在的数据,导致请求都打到了数据库上,导致数据库异常。

【缓存穿透是指缓存和数据库都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到的数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层查询,失去缓存的意义。】

解决方案:

  • 接口层增加校验:用户鉴权校验、id基础校验
  • 在缓存取不到的数据,在数据库中也没有取到的,这时将key-value写入key-null、设置缓存存活时间(setnx)短点,可以防止用同一个id进行的暴力攻击,对一些需要保持一致性的业务有影响

缓存击穿

redis中存储的是热点数据,当高并发请求访问redis中热点数据的时候,如果redis中的数据过期了,会造成缓存击穿的现象,请求都打到了数据库上。

【缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户太多,引起数据库压力瞬间增大。】

解决方案:

  • 设置热点数据永不过期
  • 接口限流与垄断,降级。重要的接口做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口的某些服务不可用时,进行熔断,失败快速返回机制
  • 布隆过滤器:类似一个hash set,用于快速判断某个元素是否存在集合中
  • 加互斥锁:
    • 分布式锁:保证对应每个key同时只有一个线程去查询后端业务,其他线程没有获得分布式锁权限,因此只需要等待即可。

缓存雪崩

缓存同一时间大面积的失效,这个时候又来的一波请求都到数据库上,导致数据库连接异常。

【缓存雪崩是指缓存中数据大批量到过期时间,而查询量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同,缓存击穿是指并发查同一条数据,缓存雪崩是指不同数据都过期了,很多数据在缓存查不到从而查数据库。】

解决方案:

  • 缓存数据过期时间设置随机,防止同一时间大量数据过期
  • 如果缓存数据库是分布式部署,可以将热点数据均匀分布在不同的缓存数据库中
  • 设置热点数据永不过期

4、Java连接Redis数据库

4.1 Jedis

解决连接虚拟机的Redis时出现的拒绝访问和超时的问题

https://blog.csdn.net/DragonFreedom/article/details/79512686?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

  1. 导入依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>
  1. 测试
public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.204.128",6379);
    System.out.println(jedis.ping());
}

常用API接口方法,与上述2命令一致。

4.2 SpringBoot整合Redis

springboot操作数据:spring-data jpa jdbc mongodb redis

springboot2.x以后,底层由原来的jedis替换为lettuce

  • jedis:采用直连方式,多线程操作,不安全。如果想避免不安全,使用jedis pool连接池。
  • lettuce:采用netty,实例可以在多个线程*享,不存在线程不安全的情况,可以减少线程数据。

手写一个RedisConfig替换自动配置redisTemplate组件

@Configuration
public class RedisConfig {
	// 自己定义了一个 RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
    factory) {
        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String,
        Object>();
        template.setConnectionFactory(factory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
        Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new
        StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

参考文献

1、【狂神说Java】Redis最新超详细版教程通俗易懂:https://www.bilibili.com/video/BV1S54y1R7SB

2、【面试】redis缓存穿透、缓存击穿、缓存雪崩区别和解决方案:https://blog.csdn.net/fcvtb/article/details/89478554