java redis

Redis

NoSQL

NoSQL = Not Only SQL (不仅仅是 SQL)

关系型数据库:表格、行、列

NoSQL 泛指 非关系型数据库,随着 web 2.0 互联网的诞生,传统的关系型数据库很难对付 web 2.0 时代,尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL 在当今的大数据环境下发展的十分迅速,Redis 是发展最快的。

1、NoSQL 特点

  1. 方便扩展(数据间没有关系,很好扩展)
  2. 大数据量性能高(Redis 一秒写 8 万次,读取 11 万次,NoSQL 的缓存记录级,是一种细粒度的缓存,性能比较高)
  3. 数据类型是多样的(不需要事先设计数据库,随取随用)

2、RDBMS 和 NoSQL

传统 RDBMS NoSQL
结构化组织 不仅仅是数据
SQL 没有固定的查询语言
数据和关系都存在单独的表中 键值对存储,列存储,文档存储,图形数据库(社交关系)
数据操作语言,数据定义语言 最终一致性
基础的事务 CPA 定理 和 BASE (异地多活)
高性能、高可用、高可扩

3、3 V 和 3 高

大数据时代的 3V :主要是描述问题的

  1. 海量 Volume
  2. 多样 Variety
  3. 实时 Velocity

大数据时代的 3 高:主要是对程序的要求

  1. 高并发
  2. 高可扩
  3. 高性能

4、NoSQL 的四大分类

4.1、K - V 键值对:

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + memecache

4.2、文档型数据(bson 格式 和 json 一样)

  • MongoDB
    • MongoDB 是一个基于分布式文件存储的数据库,由 c++ 编写,主要用来处理大量的文档
    • MongoDB 是一个介于关系型数据库和非关系型数据库中间的产品,MongoDB 是非关系型数据库中功能最丰富,最像关系型数据库的
  • ConthDB

4.3、列存储数据库

  • HBase
  • 分布式文件系统

4.4、图关系数据库

  • 存的是关系,比如:朋友圈社交网络,广告推荐
  • Neo4j、InfoGrid
分类 Examples举例 典型应用场景 数据模型 优点 缺点
键值(key-value) Tokyo Cabinet/Tyrant,
Redis,Voldemort,Oracle BDB
内容缓存,主要用于处理
大量数据的高访问负载,也用
于一些日志系统等等
key 指向 value的
键值对,通常用 hash table 来实现
查找速度快 数据无结构化,通常
只被当作字符串或者二进制数据
列存储数据库 Cassandra,HBase,
Riak
分布式的文件系统 以列簇式存储,
将同一列数据存在一起
查找速度快,可扩展性强,
更容易进行分布式扩展
功能相对局限
文档型数据库 CouchDB,MongoDB Web 应用(与 key - value 类似,value 是结构化的,
不同的是数据库能够了解 value 的内容
key - value 对应的键值对,
value 为结构化数据
数据结构要求不严格,表结构可变,
不需要像关系型数据库一样需要预先定义表结构
查询性能不高,而且
缺乏统一的查询语法
图形(Graph)数据库 Neo4J,InfoGrid,
Infinite Graph
社交网络,推荐系统等。
专注于构建关系图谱
图结构 利用图结构相关算法。
比如最短路径寻址,N度
关系查找
很多时候需要对整个图
做计算才能得出需要的信息,
而且这种结构不太好做分布式的集群方案

redis

5、概述

redis 是什么?

Redis(Remote Dictionary Server),即远程字典服务

是一个开源的使用 ANSI c语言 编写、支持网络、可基于内存亦可持久化的日志型、key - value 数据库,并提供多种语言的 api

redis 能干嘛 ?

  1. 内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
  2. 效率高,可以用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器

……

特性

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

……

6、基础的知识

redis 默认有 16 个数据库,默认使用第一个数据库

redis 是单线程的

redis 是基于内存操作,cpu 不是 redis 的性能瓶颈,redis 的瓶颈是根据机器的内存和网络带宽来决定的

redis 为什么这么快?

redis 是将所有数据全部存放到内存中的,所以说使用单线程去操作效率就是最高的,多线程(cpu 上下文切换是耗时的操作),对于内存系统来说,没有上下文切换效率就是最高的。

7、基础命令

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
# 获取数据库所有的键
keys *
# 获取键值
get [key]
# 设置键值
set [key] [value]
# 清除当前数据库
flushdb
# 清除全部数据库内容
flushall
# 是否存在某个键
exists [key]
# 移除键值
move [key] [value]
# 设置过期时间
expire [key] [time]
# 查看键值的数据类型
type [key]
# 追加字符串,如果当前 key 不存在相当于 setkey
append [key] [appendStr]
# 查看字符串的长度
strlen [key]
# 自增 1
incr [key]
# 自减 1
decr [key]
# 自增步长
incrby [key] [number]

8、基本数据类型

8.1、String

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

8.2、List

redis 可以将 list 实现为 栈、队列、阻塞队列

所有的 list 命令都是以 l 开头的

左右两边都可以插入数据

1
2
3
4
5
6
# 左右插入数据
lpush、rpush
# 移除元素
lpop、rpop
# 根据下标获取元素
lindex、rindex

小结

  • 它实际上是一个链表,before、node、after、left、right 都可以插入值
  • 如果 key 不存在,创建新的链表
  • 如果 key 存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高,中间元素,相对来说效率会低一点

消息排队,消息队列(lpush、rpop)、栈(lpush、lpop)

8.3、Set

set 开头都是 s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 添加值
sadd
# 查看指定 set 所有值
smembers
# 判断一个值是否在 set 集合中
sismember
# 获取集合元素个数
scard
# 移除 set 集合中的指定元素
srem
# 随机抽出指定元素
srandmember
# 差集
sdiff
# 交集
sinter
# 并集
sunion

8.4、Hash

map 集合 key - map

命令以 h 开头

1
2
3
4
5
6
7
# 添加值
hset
hget
# 存在即覆盖,不存在则创建
hmset
# 获取多个值
hmget

hash 变更的数据 user name age 尤其是用户信息之类的,经常变动的信息,hash 更适合于对象的存储、string 类型更适合字符串存储

8.5、Zset(有序集合)

在 set 的基础上,增加了一个值,zset k1 score1 v1

set 排序 存储班级成绩表 工资表排序

普通消息,1,重要消息 2,带权重进行判断

排行榜应用实现,取 top N 测试

9、三大特殊的数据类型

9.1、geospatial 地理位置

朋友的定位、附近的人,打车距离计算,这个功能可以推算出地理位置的信息,两地之间的距离、方圆几里的人

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
# 添加地理位置
geoadd [key] [lat] [lng] [locarion_name]

# 获取地理位置
geopos [key] [location_name] ...

# geodist 返回两人之间的距离
# 单位:
# m 米
# km 千米
# mi 英里
# ft 英尺

geodist [key] [location_name1] [location_name2] [unit]

# 以给定的经纬度为中心,找出某一半径内的元素
georadius [key] [lat] [lng] [radius] [unit] withdist [显示到中间距离的位置] withcoord [显示他人的定位信息] count [筛选出指定的结果]

# 以成员为中心,找出某一半径内的元素
georadiusbymember [key] [location_name] [radius] [unit]

# 返回一个或多个位置元素的 geohash 表示,返回 11 个字符的 geohash 字符串
geohash

# geo 的底层实现原理是 zset,可以使用 zset 命令操作 geo

9.2、Hyperloglog

什么是基数?

基数(不重复的元素),可以接受误差。

redis hyperloglog 基数统计的方法

优点:占用的内存是固定的,2^64不同的元素的技术,只需要12 kb

应用场景

网页的 UV (一个人访问一个网站多次,但是还是算为一个人)

传统的方式:set集合 保存用户 id

这种方式保存大量的用户的 id 会比较麻烦,目的是为了计数不是为了保存用户 id

redis hyperloglog:

1
2
3
4
5
6
7
PFadd key1 a b c d

PFCOUNT key1

PFadd key2 a b c d e

PFMERGE key3 key1 key2

9.3、Bitmaps

位存储

两个状态的都可以使用 bitmaps

Bitmaps 位图,数据结构都是操作二进制位来进行记录,就只有 0 和 1 两个状态

使用 bitmapp 来记录 周一到周日的打卡

1
2
3
setbit sign 0 1
......
setbit sign 6 0

查看某一天是否有打卡

1
2
3
getbit sign 0
getbit sign 6
bitcount sign # 统计所有打开的天数

10、事务

redis 单条命令是保证原子性的,但是事务是不保证原子性的

redis 事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。

一次性、顺序性、排他性

redis 事务没有隔离级别的概念

所有的命令在事务中并没有直接被执行,只有发起执行命令的时候才会执行

redis 事务:

  • 开启事务
  • 执行事务
1
2
3
4
5
6
multi # 开启事务
set k1 k2
....
# 期间执行的命令都将入队
exec # 执行事务,将按顺序执行队列里的所有命令
DISCARD # 取消事务
  • 编译型异常(代码有问题或命令有问题),事务中的所有命令都不会被执行
  • 运行时异常(1/0),如果事务队列中存在语法错误,那么执行命令的时候,其它命令是可以正常执行的,错误命令抛出异常

悲观锁

乐观锁

使用 watch 可以当作 redis 的乐观锁操作,如果事务执行失败,用 unwatch 解锁

redis 进阶

11、redis conf

网络

1
2
3
bind 127.0.0.1 # 绑定的ip
protecte-mode yes # 保护模式
port 6379 # 端口设置

通用 GENERAL

1
2
3
4
5
6
7
8
9
daemonize yes # 以守护进程的方式打开,默认是 no,需要自己开启

pidfile /var/run/redis_6379.pid # 如果以后台的方式运行需要指定一个 pid 文件

loglevel notice # debug verbose notice warning 日志级别

logfile "" # 日志文件的位置
databases 16 # 数据库的数量
always-show-logo no # 是否总是显示 logo

快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof

redis 是内存数据库,如果没有持久化,那么数据断电就会消失

1
2
3
4
5
6
7
8
9
10
11
12
# 如果 3600s 内进行了至少一次 key 操作,则进行持久化操作
save 3600 1
save 300 100
save 60 10000

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

dir ./ # rdb 文件保存的目录

replication 主从复制

security

设置 redis 密码,默认是没有密码的

client

1
2
3
maxclients 10000
maxmemory <bytes>
maxmemory-policy noeviction # 内存到达上限的处理策略

APPEND ONLY MODE aof 配置

1
appendonly no # 默认不开启,默认使用 rdb 方式持久化

12、redis 持久化

指定的时间间隔内,将内存中的数据集快照写入磁盘。恢复时是将快照文件直接读到内存里

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

触发机制

  1. save 的规则满足的情况下,会自动触发 rdb 规则
  2. 执行 flushall 命令,也会触发 rdb 规则
  3. 退出 redis ,也会产生 rdb 文件

恢复数据

将 rdb 文件 放在 redis 的启动目录即可(/usr/local/bin)

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果 redis 意外宕机了,这个最后一次修改的数据就没有了
  2. fork 进程的时候会占用一点的内存空间

13、redis 发布订阅

redi 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息

redis 客户端可以订阅任意数量的频道

第一个:消息发送者,第二个:频道,第三个:消息订阅者

1
2
3
4
5
6
7
8
9
10
11
12
# 订阅一个或多个符合给定模式的频道
PSUBSCRIBE pattern
# 查看订阅与发布系统状态
PUBSUB subcommand [argument ]
# 将消息发送到指定的频道
PUBLISH channel message
# 退订所有给定模式的频道
PUNSUBSCRIBE [pattern]
# 订阅给定的一个或多个频道的消息
SUBSCRIBE channel
# 退订指定的频道
UNSUBSCRIBE [channel]

14、redis 主从复制

主从复制,是指将一台 Redis 服务器的数据复制到其它的 redis 服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower)数据的复制是单向的,只能由主节点到从节点。Master 以写为主,Slave 以读为主。

默认情况下,每台 redis 服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 redis 数据时应用连接主节点,读 redis 数据时应用连接从节点),分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 redis 服务器的并发量
  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制时 redis 高可用的基础

环境配置

只配置从库,不用配置主库

1
2
3
4
5
# 查看角色
info replication

# 从机配置主库
SLAVEOF [ip] [port]

复制原理

slave 启动成功连接到 master 后会发送一个 sync 同步命令

master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master 将传送整个数据文件到 slave,并完成一次完全同步

全量复制:slave 服务在接收到数据库文件数据之后,将其存盘并加载到内存中

增量复制:master 继续将新的所有收集到的修改命令依次传给 slave ,完成同步

只要是重新连接 master,一次完成同步(全量复制)将被自动执行

哨兵模式

哨兵模式是一种特殊的模式,哨兵是一个独立的进程,它会独立运行,其原理是哨兵通过发送命令,等待 redis 服务器响应,从而监控运行的多个 redis 实例。一般哨兵也会配置集群

img

15、redis 缓存穿透、击穿和雪崩

缓存穿透(查不到)

用户查询一个数据,发现 redis 内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败,当用户很多时,缓存都没有命中,于是都去请求了持久层的数据库,这个会给持久层数据库库造成很大的压力,这时候就会出现缓存穿透

解决方案:

  1. 布隆过滤器:是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
  2. 缓存空对象:当存储层不命中后,即使是返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据就会从缓存中获取,保护了后端数据源
    • 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多空值的键
    • 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

缓存击穿(量太大,缓存过期)

是指一个非常热点的 key 在不停的扛着大并发的集中访问,当这个 key 失效的瞬间,持续的大并发就会击穿缓存,直接请求数据库,就像在屏幕上凿开了一个洞

当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库压力瞬间过大

解决方案:

  1. 设置热点数据永不过期
  2. 分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其它线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

缓存雪崩

是指在某一个时间段,缓存集中失效

解决方案:

  1. 搭建集群,异地多活
  2. 限流降级,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其它线程等待
  3. 数据预热,在正式部署之前,把可能被大量访问的数据先访问一遍,这样部分可能被大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
  • Copyrights © 2022-2023 hqz

请我喝杯咖啡吧~

支付宝
微信