Redis

136 阅读16分钟

一、背景

  1. 单机的mysql不足以支撑海量用户访问
  2. 数据超过300万,就要建立索引(B+ Tree)机器也放不下
  3. 访问量(读写混合),一个服务器承受不了

二、解决办法

1.缓存

80%操作都是读操作,加一个缓存缓解压力

优化存储的数据结构(B+Tree)-->文件缓存(IO) --> 缓存+垂直拆分(读写分离) 2.企业架构分析

缓存+分库分表+水平拆分(MySql集群)

3.企业架构

用户的个人信息、社交网络、地理位置,用户自己产生的数据,用户日志等等爆发式增长

三、 NoSQL

Not Only SQL

泛指非关系型数据库

  1. 方便扩展(数据之间没有关系,易于扩展)
  2. 大数据量高性能(Redis 一秒写8万次,读11万次,NoSQL的缓存是记录级别的,是一种细粒度的缓存,性能比较高)
  3. 数据类型是多样型的,不需要实现设计数据库
  4. 传统RDBMS和noSql

传统RDBMS

  • 结构化组织
  • SQL
  • 数据和关系存在单独的表中
  • 操作sql语句
  • 基础的事务
  • ...

NoSql

  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文档存储,图形数据库(社交关系)
  • 最终一致性
  • CAP定理和BASE(异地多活)
  • 高性能、高可用、高可扩
  • ...

1. NoSQL的四大分类

  1. KV键值对:
  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis+memecache
  1. 文档型数据库(bson格式和json一样):
  • MongoDB

    • MongoDB是一个基于分布式文件存储的数据库,主要用来处理大量的文档

    • MongoDB是一个介于关系型和非关系型数据库中间的产品,最像关系型数据库

  • ConthDB

  1. 列存储数据库
  • HBase
  • 分布式文件系统
  1. 图关系数据库
  • 不是存图片的,是存储关系的

四、Redis

1. Remote Dictionary Server

远程字典服务

也被称为结构化数据库

Sql仅仅是数据库,存储一些信息

但NoSql不仅仅是数据库,还可以存储信息间的关系,即结构

2. linux下安装

一般在linux下安装

安装好的程序在 /usr/local/bin下

  1. 拷贝一份redis.conf来进行个性化配置

默认不是后台启动

修改:

daemonize yes 守护进程模式启动 后台

  1. 在bin下使用特定配置文件启动

redis-server config/***config.conf

  1. 连接端口

reids-cli -p 6379

  1. ping

显示pong则成功连通

  1. 通过另一个窗口查看redis进程信息

ps -ef|grep redis

  1. 关闭redis服务

shutdown

  1. 退出

exit

  1. redis性能测试工具

自带

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

redis-benchmark 自带工具 -h localhost 本机 -p 6379 端口号 -c 100 100个连接 -n 100000 100000个请求

3. 基础知识

redis默认有16个数据库

默认使用第0个数据库

可以使用select切换不同的数据库

DBSIZE 可以查看当前数据库大小

flushdb 清除当前数据库

flushall 清除全部数据库

redis是单线程的

避免了cpu上下文切换

redis 是基于内存操作 因此cpu不是其redis性能瓶颈,瓶颈是机器内存网络带宽

redis将数据放入内存中,使用单线程去操作效率最高 对于内存系统来说, 没有上下文切换效率就是最高的

redis速度快的原因

  1. 单线程

  2. 基于内存

  3. 采用多路复用技术:epoll+自身实现的简单事件机制

4. 作用

  1. 数据库

  2. 缓存

  3. 消息中间件MQ

5. Redis-Key相关

keys * -- 得到所有的key

set key value -- 给key设value值

exists key -- 是否存在key 1存在

move key db -- 移除数据库db中的key

expire key seconds--设置key的存活时间seconds秒

ttl key--查看key的剩余时间

type key -- 查看key的类型

6. 五大基本类型之String

append key value-- 给key追加value

strlen key --获取key长度

incr key -- key自增1

decr key -- key自减1

incrby key value -- key 增加 value

decrby key value -- key 减少value

getrange key start end -- 截取key索引从start到end字符串

setrange key offset value -- 给key索引为offset 设置value值

setex key time value --给key设置值value ,存活时间为time

setnx key value -- 如果key不存在,则设置value值(分布式锁中常用)

mset k1 v1 k2 v2 -- 一次设置多个值,k1设置v1 ,k2设置v2

mget k1 k2 -- 取k1 k2 值

msetnx k1 v1 k2 v2 --原子操作,同时设置k1 k2值 ,否则失效

mset 类:对象:属性 value--给对象设置value值

getset key value -- 先获取key(不存在值则返回null) 再设置value值(如果存在则此value覆盖旧值)

7. 五大基本类型之List列表

实际上是一个链表

List可做 栈、队列、阻塞队列(双端队列)

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

lpush key value -- value插入到表头(左)

rpush value -- value插入到表尾(右)

lrange key start end -- 获取list从start到end的值,end为-1代表全部

lpop key -- 从表头移除数据

rpop key -- 从表尾移除数据

lindex key value -- 获取key中索引为value的值

llen key -- 获取key的长度

lrem key count value -- 移除count个指定的值value

ltrim key start end -- 截取key中开始start到结束end的值,key被改变了

rpoplpush source des -- 移除当前source列表最后元素到des列表

lset key index value -- 将key中索引为index的位置设置为value值,事先需要key存在

linsert key before/after old value newvalue --在key中oldvalue 前或者后插入newvalue

8. 五大基本类型之Set集合

set中命令s开头

sadd key value -- 在key中添加value

sismember key value -- key中是否存在value

smembers key -- 查看key中的所有值

scard key -- 获取key中元素个数

srem key value -- 移除key中value

srandmemeber key count -- 随机抽选key中的count个元素

spop key -- 随机移除key中元素

smove source des member -- 将source中指定member移动到des

sdiff key1 key2 -- 查看key1 和key2中不同的元素

sinter key1 key2 -- 查看key1和key2的交集

sunion key1 key2 -- 查看key1和key2的并集

9. 五大基本类型之Hash散列

Map集合 key-map<k,v> value是一个map

h开头

hset key key1 value1 -- value是一个<key1,value1>的map

hdel key key1 删除hash指定的key1字段

hmset key key1 value1 key2 value2 -- <key1 value1> 和<key2 value2>两个键值对 

hgetall key 显示key中所有元素
为key1 value1 key2 value2
hlen key 显示key中所有键值对个数,key如上,则返回2  

hexists key key1 --判断key中指定字段key1是否存在

hkeys key--显示key中所有键

hvalues key -- 显示key中所有值

hincrby key key1 value--给key中key1增加value

存储一些变更的数据

比如用户信息保存

10. 五大基本类型之Zset排序集合

有序集合

在set基础上,增加了一个值

zadd key score value --在key中增加值value和value对应的score

zrangebyscore salary -inf +inf withscores -- 将salary有序集合中的score从负无穷到正无穷排序并显示对应scores

zrem salary value -- 移除salary表中value及score

zcard salary -- 获取salary中个数

zrevrange salary 0 -1 -- 获取salary从大到小排序

zcount key start end -- 获取key中从start到end的数量

11.三种特殊数据类型之geospatial 地理位置

朋友定位,附近的人,打车距离计算

六个命令

geoadd key 值(纬度,经度,地理名称) -- 给key增加经纬度名称地理信息

一般直接通过java程序导入

geopos key 名称-- 从key中返回地理名称的经纬度

geodist key member1 member2 km --查找key中member1和member2的直线距离(km显示)

georadius key 经度 纬度 半径 单位 withdist withcoord count 1

--查找key中 以经纬度为中心 半径加单位为半径中的(count 1) 1个地理名称附带纬度

georadiusbymember key 地理名称 radius unit -- 找出位于指定地理名称半径为radius 单位为unit的数据

geohash key 地理名称 -- 返回key中地理名称的11个字符的哈希值

geo底层的实现就是Zset 可以使用Zset命令操作geo

12. 三种特殊数据类型之Hyperloglog 数据结构

做基数统计的算法

基数是不重复元素的个数

Hyperloglog占用内存固定,只需占用12kB内存

===============================

网页的访问次数

传统的方式,使用set保存用户的id ,如果一个人多此访问,这个id就被覆盖了,去重,保存set集合

如果保存大量用户的id ,就会比较麻烦,Hyberloglog可以计数,不保存id

==================================

PFadd key a b c d e f g h i j -- 在key中添加这些元素

pfcount key -- 查询key 中元素数量

pfmerge des src1 src2 -- 将src1 和src2中元素去重合并为des,并集

需要允许容错

不允许容错,就使用set或者自己的数据类型

13.三种特殊数据类型之Bitmaps 位图

两个状态的都可以使用Bitmaps

数据结构,都是操作二进制位来进行记录

设置签到场景

setbit sign 0 0 -- 给sign表第0位设置状态0

setbit sign 1 1

setbit sign 2 1

setbit sign 3 0

...

getbit sign 3 -- 查看sign表周四是否打卡

bitcount sign start end -- 查看sign从start到end范围的为一的个数

五、 Redis的事务

Redis单条命令保存原子性,但Redis的事务** 不保证原子性 **

没有隔离级别的概念

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

保证了** 一次性、顺序性、排他性! **

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

1. redis事务

  • 开启事务
  • 命令入队
  • 执行事务

=====正常执行事务=====

  1. multi -- 开启事务

  2. 输入一堆命令,输入一条命令,只是将命令入队列,并不执行

  3. exec -- 执行事务

=====放弃事务=====

discard 第二步命令入队时放弃

=====事务错误=====

如果第二步命令入队时,就有错误命令而不处理,则所有事务均不执行

如果类似运行异常,即命令入队时不提示错误,则执行其他语句

2. 监控(乐观锁)

  1. mysql中更新的时候比较version

  2. redis中监视Watch

=====正常执行成功===== set money 100

set out 0

watch money 监视money

multi

decrby money 20

incrby out 20

exec

结果为

80

20

事务正常结束

=====事务执行失败=====

上述代码在未执行 exec时

另一个线程执行了

set money 1000 的操作

导致watch money监测到money改变,事务失败

返回(nil) 表示事务执行失败

可以unwatch放弃监视操作

再次watch 准备执行事务

六、Jedis

理解成用于连接Redis的Jar包

七、 持久化之RDB操作

由于Redis是保存在内存中的,因此断电即失,需要持久化

  1. RDB(Redis DataBase)

在指定的时间间隔内讲内存中的** 数据集快照 ** 写入到磁盘

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了 ,再用这个临时文件替换上次持久化好的文件,整个过程中,不进行任何IO操作,如果需要对大规模数据恢复,且不要求完整性,则RDB性能高于AOF ,缺点是最后一次持久化后的数据可能丢失,默认的就是RDB,一般不需要更改配置

dump.rdb 就是保存的快照文件 生产环境,有时会将其备份

在redis.conf中可以修改

save 60 5

60s内修改5次就会触发快照操作

  1. 触发机制
  • save的规则满足的情况下,会自动触发rdb规则

  • 执行flushall命令,也会触发我们的rdb规则

  • 退出redis,也会产生rdb文件

备份只自动生成一个dump.rdb

  1. 如何恢复rdb文件
  • 只需要将rdb文件放在我们redis启动目录即可,redis启动的时候会自动检查dump.rdb恢复其中的数据

  • 查看需要存在的位置 config get dir

如果在/usr/local/bin 目录下存在dump.rdb文件,启动会自动恢复其中数据

  1. 优点:
  • 适合大规模数据恢复

  • 对数据完整性要求不高

  1. 缺点;
  • 需要一定的时间间隔进程操作 如果redis意外宕机 最后修改的数据就丢失了

  • fork进程时,会占用一定内存空间

八、AOF(Append Only File)

以日志形式记录每个写操作,将Redis执行过的所有指令记录下来,只需追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

AOF 保存的是appendonly.aof 文件

也可以在redis.conf中配置

默认不开启 需要开启改成appendonly yes

如果这个aof文件有错误,此时redis启动不起来,需要修复这个aof文件

redis给我们提供了一个工具 redis-check-aof --fix appendonly.aof文件

可以设置aof超过多少大小,再新建一个进程对文件进行重写

aof默认就是文件命令的无限追加

1. 优点

  • 每一次修改都同步,文件的完整性会更好

  • 每秒同步一次,可能会丢失一秒的数据

  • 从不同步,效率最高

2. 缺点

  • 相对于数据文件来说,aof远大于rdb,修复速度比rdb慢

  • aof运行效率也比rdb慢,因此默认配置是rdb持久化

九、Redis发布订阅

1. 消息通信模式

发送者publish消息

订阅者监听sub接受消息

publish channel messsage 在channel 发布message消息

subscribe channel 接受channel通道消息,持续监听状态

十、主从复制

redis集群相关,一个redis服务器做主机master node,三个Redis服务器做从,完成读写分离

主以写为主 从以读为主 80%都是读操作

只能主 复制数据 到 从

一般一主二从

单台redis最大使用内存不应该超过20G

1. 作用

  • 数据冗余:实现了热备份,是持久化之外的一种数据保存方式(就是多了备份数据)

  • 故障恢复:主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复

  • 负载均衡: 配合读写分离 ,主提供写服务,从提供读服务,分担服务器压力

  • 高可用(集群)基石:是哨兵和集群能够实施的基础,即高可用的基础

2. 配置

只配置从库 不需要配置主库 默认情况下,每一台redis服务器都是主节点

需要更改配置信息 以启动不同的redis服务

  1. 端口
  2. pid名字
  3. log日志名字
  4. dump.rdb名字

修改完成后启动多个redis服务器

info replication可以查看redis-server 状态信息

  1. 配从机就是认老大

slaveof ip port 认ip 和端口port的服务器当老大

真实的主从配置应该在配置文件中配置,这样的话是永久的

redis.conf 中

replicaof 将ip port 输入即可

如果主机有密码 再输入 masterauth

  • 主机才能写 从机只能读

主机断开 从机仍然只是从机 可以使用salveof no one 就是主机了

其他从机需要手动认老大

从机断开 命令行设置的主从,如果重启从机就变成主机了

只要再设置为从机 又会复制主机的数据 全量复制

即时的复制称为增量复制

十一、哨兵模式

1. 背景

主机宕机,需要手动配置主从,因此出现自动版的配置主从

2. 原理

哨兵是一个独立的进程,原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个redis服务

一个哨兵进程对Redis服务器进行监控,可能出现问题,因此使用多个哨兵(集群)进行监控,哨兵之间也会互相监控

一个哨兵发现主机宕机,这个现象称为主观下线,其他哨兵同样监测到主机宕机,通过随机投票选取出新的主机,这个过程称为客观下线

如果主机重新启动,只能当从机

3. 配置

  1. vim sentinel.conf

sentinel monitor myredis ip(ip) port 1(投票时选取谁) 2. 启动哨兵

redis-sentinel ***config/sentinel.config

4. 优点

  1. 哨兵集群,基于主从复制模式,所有的主从的优点 ,他都有
  2. 主从可以切换,故障可以转移
  3. 哨兵模式就是主从模式的升级、手动到自动,更健壮

5. 缺点

  1. redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
  2. 实现哨兵模式的配置很麻烦

十二、缓存穿透和雪崩

用户访问缓存,如果缓存中有数据,直接返回,否则访问数据库,返回数据

1. 缓存穿透

如果缓存中和数据库都没有查询的数据,仍然有大量用户进行这个查询,导致数据库崩溃

解决:

  1. 布隆过滤器

位数组 + 哈希函数

布隆判断出数据存在,不一定存在

但判断出数据不存在,则一定不存在

错误率为1/位数组的长度,因为哈希冲突

有100和99 比如100经过哈希得到1 99经过哈希得到2

43经过哈希得到1 ,这就是一个错误 44经过哈希得到3, 直接返回没有

99经过哈希还是等于2,这是一个正确的结果

减少错误率,可以 1.增加哈希函数个数

2.增加数组长度

n是预估数据量,fpp是误判率,m是数组大小

  1. 空缓存对象(但更新缓存没更新,且空对象太多也不好)

2. 缓存击穿

大量用户访问缓存中存在的一个数据,此时缓存该数据失效更新,导致大量用户同时访问数据库,数据库崩溃

解决:

  1. 热点数据不过期(但耗空间)

  2. 分布式锁(给每一个key设置只有一个线程能到数据库查询的锁)

3. 缓存雪崩

大量用户访问缓存,缓存中数据集体数据失效更新或redis服务器宕机,导致大量用户同时访问数据库,数据库崩溃

解决:

  1. redis高可用,多设置几个redis
  2. 限流降级,关闭一些服务
  3. 数据预热,数据在正式部署时,先设置好缓存及失效时间,做测试