狂神redis课程笔记

Redis


总体大纲outline

  • nosql讲解,非关系型数据库
  • 阿里巴巴架构演进
  • nosql数据模型
  • Nosql四大分类
  • CAP
  • BASE
  • Redis入门
    • 五大基本类型
      • String
      • List
      • Set
      • Hash
      • Zset
    • 三种特殊数据类型
      • geo
      • hyprologlog
      • bitmap
    • Redis配置文件如何读取
    • Redis持久化
      • RDB
      • AOF
    • Redis的一些事务操作
    • Redis实现订阅发布
      • 消息队列
    • Redis主从复制
      • 哨兵模式,现在公司中所有的集群都采用哨兵模式
    • 缓存穿透,以及解决方式
    • 缓存雪崩,以及雪崩方式,微服务一环套一环,可能会导致雪崩
    • 基础API之jedis详情
    • SpringBoot集成Redis操作
    • Redis的实践分析

Nosql

大数据时代,一般的数据库无法进行分析处理了Hadoop

单机SQL时代,更多使用静态网页

  • 如果数据量过大,一个机器放不下
  • 数据量超过300万时一定要创建索引,数据库现在使用B+Tree,一个机器的内存也放不下
  • 数据库访问量比较大,一开始是读写混合的,一个服务器也承受不了

出现这三种情况,就需要升级,但是如果没有这么大的数据量,应该从单机开始

Memcached缓存+MySQL+垂直拆分(读写分离)

网站百80%的操作都是在读,减轻数据库的压力可以使用缓存提高效率,缓存使用什么技术都没所谓

  • 发展过程,先是优化数据结构和索引–文件缓存(IO操作)–Memcached(当时最热门的技术)

分库分表+水平拆分+M有SQL集群

使用缓存解决了读的问题,用分集群的方式解决读的问题,数据存在不同的库中

image-20201111101941445

早些年,MySAM使用 表锁,读取一行要将整个表锁起来,十分影响效率,高并发下会出现严重问题

后来转为innodb 行锁

慢慢的就开始使用分库分表来解决写的压力,将表拆分,不同的业务使用不同表,存放在不同的数据库中,不同的业务使用单独的数据库,结合微服务,mysql推出了表分区,但是很少公司使用

Mysql的集群已经满足了那个年代的需求

最近的年代

定位也是一种数据,Mysql的关系型数据库已经不够用了,数据量多,变化快,

非关系型数据库

  • Json
  • Bson
  • 图型数据库

放在缓存中一段时间之后再进行持久化的操作,来保证效率和安全

存储很大的文件,会导致数据表很大效率会变低, 如果有一种专门的数据库来处理这种数据Mysql压力就会变得十分小,研究如何处理这些问题

如果数据量变大,要在对数据库进行更改,增加一行是很难的

用户先访问企业防火墙,到负载均衡的主机,到App服务器,到mysql实例,然后是独立功能的服务器

image-20201111144034075

最后总结为什么要用NoSQL

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

NoSQL=Not Only SQL 不仅仅是SQL

泛指非关系型数据库,Web2.0的诞生,传统的关系型数据库很难对付Web2.0时代,尤其是超大规模的高并发的社区,

NoSQL在大数据环境下发展十分迅速

要存储的数据不是固定格式,以键值对来控制,Map< String , Object >,数据之间没有关系就很好扩展,Java现在要做面向接口编程也是为了解耦,

使用Redis也是为了高性能,官方的数据读取速度每秒11w次,写8w次

数据类型是多样性的,不需要事先设计数据库,不需要设计键值对,随取随用

传统的RDBMS和NoSQL

传统的RDBMS

  • 结构化组织
  • sql
  • 数据和关系都存储在表中 row column
  • 数据定义语言
  • 严格的一致性
  • 基础的事务
  • 。。。。

NoSQL

  • 不仅仅是数据
  • 没有固定查询语言
  • 很多的存储方式,列存储、文档存储、图形存储(社交关系)
  • 可以不用满足严格一致性,数据是可以有误差的,要保证的是最终一致性
  • CAP定理,和BASE理论
  • 高性能、高可用、高可扩展

大数据时代的3V和3高

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

  • 海量的Volume
  • 多样的Variety
  • 实时Velocity

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

  • 高并发
  • 高可拓(随时水平拆分,机器不足,可用扩展来)
  • 高性能

真正在公司中实践一定是:NoSQL+RDBMS一起使用才是最好的

阿里巴巴框架演进

image-20201111211659344

第五代架构改进

  • 敏捷开发
  • 极限编程
    • 业务快速增长,每天都要上线大量的小需求
    • 应用系统日益膨胀,耦合恶化,架构越来越复杂,带来更高的开发成本,如何保持业务开发的敏捷性
  • 开放,提升网站的开放性,吸引第三方开发者加入网站的建设
  • 体验,网站并发压力快速增长,用户对体验提出了更高的要求
  • image-20201111213026532
  • 使用了各种数据库,这么多种类型的数据库导致数据架构非常复杂,要简化架构,增加一层就行,像是jdbc一样,
  • 商品中的信息存在不同的数据库中
  • 商品的基本信息
    • 名称、价格、商家信息:
      • 关系型数据库就可以解决 MySQL / Oracle
      • 淘宝早些年就去IOE了,去掉IBM小型机,Oracle数据库,EMC存储设备
  • 商家的描述、评论(文字比较多)
    • 文档型数据库,MongoDB,
  • 图片
    • 分布式文件系统,FastDFS
    • 淘宝自己的TFS
    • Google的GFS
    • Hadoop HDFS
    • 阿里云的 oss
  • 商品的关键字
    • 搜索引擎
      • solr
      • elasticsearch
      • 淘宝用的是 Isearch
  • 商品热门的波段信息
    • 内存数据库
    • Redis 、Tair 、Memacache
  • 商品的交易,外部支付接口
    • 第三方应用

大型互联网应用问题:

  • 数据类型太多
  • 数据源繁多,经常重构
  • 数据要改造,大面积改造

阿里的解决方案

​ 统一数据服务层UDSL,在网站应用集群和底层数据源之间,构建一层代理,统一数据层

  • 模型数据映射
    • 实现 业务模型 各属性 与 底层不同类型数据源的模型数据映射
  • 统一的查询和更新API
    • 提供了基于业务模型的统一的查询和更新的API,简化网站应用跨不同数据源的开发模式
  • 性能优化
  • image-20201111224550972
  • 设计了一套统一的DSL,提供了统一的增删改查的API,开发速度问题是解决了,但是性能还是问题
  • 网站数据庞大,只能缓存热点数据,解决方案,开发热点缓存平台,提供UDSL作为缓存系统
  • image-20201111230011810
  • 以上都是NoSQL入门概述

NoSQL的四大分类

KV键值对
  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里,百度:Redis+memecache
文档型数据库(Bson格式和Json格式)
  • MongoDB
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型中间的产品,非关系型数据库中功能最丰富的,最像关系型数据库的
  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图形关系数据库

  • 不是用来存放图形的,是用来寸关系的,朋友圈社交,广告推荐
  • Neo4j,InfoGrid

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sls3Tt2O-1606726398799)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201112164506.png)]

Redis入门

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

开源、使用C语言编写,支持网络、基于内存可持久化的日志型,Key-Value数据库,提供多种语言的API,可以用多种语言调用 ,NoSQL技术之一,也被称之为结构化数据库之一

读的速度是11w,写的速度是8w

Redis能干嘛

  • 内存存储,持久化,内存是断电即失的,持久化很重要, 持久化有两种机制(RBD,AOF)
  • 效率高,可以用于高速缓存
  • 发布订阅系统
  • 地图信息分析
  • 计数器,(浏览量)
  • 。。。

特性

  • 多样的数据类型
  • 持久化
  • 集群
  • 事务
  • 。。

常用网站

启动Redis

  • 安装Redis

  • brew install redis
    //安装redis
    brew info redis
    //查看软件详细信息,以来关系,注意事项等
    brew list redis
    安装包所在的位置
    
  • 启动Redis

  • redis-server
    
  • image-20201112165434578

  • 启动之后不要关闭

  • 连接测试

  • redis-cli
    
  • 默认端口是6379

  • 基本操作

  • image-20201112164825771

  • Redis推荐使用Linux开发

  • 好吧这里加入了Linux的知识,要开始学习Linux

  • 退出Redis

  • redis-cli shutdown
    

CentOS7 安装Redis

常用的命令

netstat -lnpt |grep 6379
//查看6379=端口占用情况,netstat CentOS不自带,需要另外安装
yum install -y net-tools

kill -9 [PID]
//结束对应PID进程

ln -s /usr/local/redis/bin/redis-cli /usr/bin/redis
//创建链接,使之可以直接使用/bin之中的命令,这创建应该回到~目录进行创建,称为命令软链接

cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/
//复制操作

make install PREFIX=/usr/local/redis
//安装到指定目录

 tar -zxvf redis-5.0.3.tar.gz
 //解压
 
 wget http://download.redis.io/releases/redis-5.0.3.tar.gz
 //通过链接下载



主要操作

不同版本的redis有不同的操作,选择高版本的redis,基本就只是解压,安装,选择配置文件启动

make install
make uninstall

https://www.cnblogs.com/heqiuyong/p/10463334.html

  1. 安装gcc
  2. 下载安装包
    1. 解压安装包
    2. 进入安装包
    3. 执行编译
    4. 安装到指定目录
  3. 启动
    1. 前台启动
    2. 后台启动
    3. 设置开启自启动
      1. 创建开机自启动文件.service
    4. 创建自启动链接
  4. 服务操作命令
    1. systemctl start Redis.service
    2. … stop …
    3. restart
    4. status
    5. … enable … 设置开机自启动
    6. … disable …

5.连接redis,redis

Redis测试性能

在/usr/local/bin 下有很多工具

image-20201119105549143

benchmark压力测试工具,官方自带的性能测试工具

  • -h 指定服务器主机名
  • -p 指定服务器端口
  • -c 指定并发连接数
  • -n 指定请求数
测试100个并发,每个并发100000个请求

redis-benchmark  -p 6379 -c 100 -n 10000
不加-h 就默认本机

解读测试数据

image-20201119110457181

  • 10000个set请求使用0.17秒
  • 每次请求都有100个并行的客户端
  • 每次写入三个字符串
  • 只有一台服务器来连接
  • 下面的是每毫秒处理百分之多少的请求,
  • 每秒能处理五万多次请求

基础知识

Redis是单线程的,Redis是基于内存操作的,CPU不是Redis的瓶颈,根据机器的内存和网络带宽的,可以用单线程实现

每秒10w+的QPS,完全不比使用key-value的Memecache差

单线程Redis

  • 误区1,高性能服务器一定是多线程
  • 误区2,多线程一定比单线程效率高
    • 多线程要涉及CPU的上下文切换
  • 核心:Redis是将所有数据全部放到内存中去操作,效率就是高,CPU上下文切换是耗时的操作,对于系统来说没有上下文切换,系统效率就是最高的,多次读写都是在一个cpu上的,在内存情况下效率就是最高的

Redis五大数据类型

  • String
  • List
  • Set
  • Hash
  • Zset

Redis官方介绍

  • Redis是内存中的数据结构存储系统,可以作用数据库、缓存、消息中间MQ
  • 支持多种数据类型
    • String字符串
    • hash散列
    • list列表
    • set 集合
    • sorted sets有序集合
    • bimemaps
    • hyperloglog
    • geospatial
  • Redis内置了主从复制replication、LUAscripting脚本,LRU 驱动事件,transactions事务,和不同级别的磁盘持久化persistence
  • 通过Redis哨兵Sentinel,和自动分区Cluster,提供高可用性high availability

Redis

  • redis-server &
    #后台启动
    redis-server   /etc/local/bin/redis.conf
    #指定文件启动
    -p
    #指定端口启动
    
  • keys * 查询全部key

  • select 3 切换数据库3

  • dbsize 查看数据库大小

  • flushdb 清空当前库

  • flushall 清空所有数据

  • exists 判断某个key是否存在

  • move 移除key

  • expire 设置过期时间,可以用作单点登录

  • ttl 查看过期时间

  • type 查看key的类型

  • config set requirepass XXX 设置密码

    • config set requirepass “” 取消密码
  • auth xxx 登录

String

  • append 追加value

    • 追加不存在的key会set key
  • strlen 查看value长度

  • incr 自增1,可以用作增加浏览量increase

  • decr decrease 递减

  • incrby 能设置自增量的自增

  • getrange 截取范围,下标从0开始

  • setrange 修改范围的值

  • setex set wth expire 设置key时顺便设置生存时间

  • setnx ( set if not exist )参数不存在就设置,在分布式锁经常使用,如果存在就创建失败

  • mset 批量设置,空格间隔

  • mget

  • msetnx 原子性操作,批量设置

  • 对象操作

    • 127.0.0.1:6379> mset user:1:name lwh user:1:age 3
      OK
      127.0.0.1:6379> mget user:1:name user:1:age
      1) "lwh"
      2) "3"
      127.0.0.1:6379> 
      
    • 设置一个对象,值为json字符串来保存一个对象

    • user:{id}:{filed}

  • getset 先get再set,规则就按getset来,无论key存不存在,都按getset来

List

在Redis中可以将List作为、栈、队列、阻塞队列

  • 实际上是一个链表,left、right 都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增value
  • 移除所有值,空链表
  • 在两边插入效率最高,对中间元素操作,效率会降低

消息排队、消息队列、堆、栈

  • lpush left push
  • lrange 显示list 范围range
  • rpush right push
  • Rpop right pop
  • Lpop left pop
  • Llen listlength
  • lrem remove指定下标
  • ltrim trim截取
  • Rpoplpush pop再push到别的list
  • lset 设置指定下表值
  • exists 存在
  • Linsert 将某个具体的value插入到某个元素的前面或后面
#=======================================================================

# lpush、lrange、Rpush
127.0.0.1:6379> lpush list one  #插入队列左边
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1      #获取全部的值从0到-1,这是作为栈的最顶上的为index 0,
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush list Right
(integer) 4
127.0.0.1:6379> lrange list 0 -1   #插入队列右边
1) "three"
2) "two"
3) "one"
4) "Right"



########################################################################

#Rpop、Lpop   移出元素      lindex 获取下标对应值
127.0.0.1:6379> rpop list
"Right"
127.0.0.1:6379> lindex list 2
"one"

########################################################################

#  返回llen 长度\    lrem   移除
127.0.0.1:6379> llen list
(integer) 3


127.0.0.1:6379> lrem list 2 three    #lrem key count value  移除几个,从左边删除的
(integer) 1

########################################################################

#ltrim   截取范围,其余的删除
127.0.0.1:6379> rpush list l1
(integer) 1
127.0.0.1:6379> rpush list l2
(integer) 2
127.0.0.1:6379> rpush list l3
(integer) 3
127.0.0.1:6379> rpush list l4
(integer) 4
127.0.0.1:6379> ltrim list 2 3
OK
127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l4"
127.0.0.1:6379>
########################################################################

#rpoplpush  从一个list 的最右边的一个值pop出来push进别的list中
127.0.0.1:6379> rpush list 4 5 6
(integer) 6
127.0.0.1:6379> lrange list 
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:6379> rpoplpush list destination
"6"

########################################################################

#linsert   在之前或之后插入
127.0.0.1:6379> linsert list before 3 inserttest  #插入在3之前
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "value"
2) "2"
3) "inserttest"
4) "3"
5) "4"
6) "5"
127.0.0.1:6379> linsert list after 4 inserttest2  #插入在4之后
(integer) 7
127.0.0.1:6379> lrange list 0 -1
1) "value"
2) "2"
3) "inserttest"
4) "3"
5) "4"
6) "inserttest2"
7) "5"
127.0.0.1:6379> 

Set

集合中的值不能重复,set是无需不重复原则

string和list的元素都是value,set中是member

  • sadd
  • smembers 查看集合的全部值
  • sismember 判断是否存在
  • scard 查看一共有几个元素
  • srem 移除某一个member
  • srandmember 随机抽取几个member
  • spop 随机删除几个member
  • smove 移动member到另一个set,可以适用于共同关注功能实现
  • sdiff difference set 差集 ,结果来自first_key为基准
  • sinter intersection set 交集
  • sunion union联合 并集
    • 将用户放到set中,共同关注、共同爱好、推荐好友
127.0.0.1:6379> smembers set1
1) "m4"
2) "m3"
3) "m1"
4) "m2"
127.0.0.1:6379> smembers set2
1) "m3"
2) "m6"
3) "m1"
4) "m2"
5) "m5"
127.0.0.1:6379> sdiff set1 set2
1) "m4"
127.0.0.1:6379> sinter set1 set2 
1) "m3"
2) "m1"
3) "m2"
127.0.0.1:6379> 

Hash

map集合,key - map 本质和string类型没有太大区别,还是一个简单的key - value,像是增加了一层

  • hset hset key field value

  • hget

  • hmset

  • hmget

  • hmget

  • Hmget

  • Hdel 删除

  • hgetall

  • hlen

  • hexists 判断hash中指定字段是否存在

  • hkeys 获取key所有的field

  • hvals 获取所有的value

  • hincrby 递增,参数设置为负数就是递减

  • hsetnx 是否存在,不存在就创建设置,存在就不设置

  • 127.0.0.1:6379> hset user:1 name haoyun
    (integer) 1
    127.0.0.1:6379> hget user:1 name
    "haoyun"
    
  • 做用户信息保存,比用string类型好点,存储经常变动的信息,hash更适合存储对象,

127.0.0.1:6379> hset key field1 value1 field2 value2
(integer) 2
127.0.0.1:6379> hmset key field1 field2
OK
127.0.0.1:6379> hmget key 
(error) ERR wrong number of arguments for 'hmget' command
127.0.0.1:6379> hmget key *
1) (nil)
127.0.0.1:6379> hmget key field1 field2
1) "field2"
2) "value2"
127.0.0.1:6379> 

ZSet

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

可以作为一个排行榜功能,进场刷新,或者任务等级排序之类的,都可以做

  • image-20201120225008987

  • 127.0.0.1:6379> zadd myzset 1 one 
    (integer) 1
    127.0.0.1:6379> zadd myzset 2 two 3 three 
    (integer) 2
    127.0.0.1:6379> zrange myzset 0 -1
    1) "one"
    2) "two"
    3) "three"
    127.0.0.1:6379> 
    
  • 排序

  • zrangebyscore

    127.0.0.1:6379> zadd salary 3000 hao 2000 yun 100 lwh 
    (integer) 3
    127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #与分数
    1) "lwh"
    2) "100"
    3) "yun"
    4) "2000"
    5) "hao"
    6) "3000"
    127.0.0.1:6379> zrangebyscore salary -inf +inf   #正负无穷
    1) "lwh"
    2) "yun"
    3) "hao"
    
  • zconut

  • zcard 获取有序集合的个数

三种特殊数据类型

  • geospatial
  • hyperloglog
  • bitmaps

Geospatial

可以推算地理位置的信息,两地之间的距离,方圆几公里之内的人

需要注意:

  • 地球南北两极无法直接添加,
  • 一般会下载城市数据,直接通过Java程序一次性导入
  • 有效经纬度范围从-180到180,超出范围时会返回错误,
  • key由(纬度,经度,名称)构成

查看官网,一共有六个相关命令

image-20201121151049547

  • add、dist、hash、pos、radius、rediusbymember

  • geodist 返回两个给定位置之间的距离

  • geohash 返回geohash对位置进行的编码,用于内部调试,一般用不到

  • geopos 返回指定member的经纬度信息

  • georadius : 根据半径查找,需要给定中心点数据

  • georadiusbymember : 也是根据半径查找,但是中心点是已经存在的member

  • zrange 遍历member

  • zrem 移除member

  • 127.0.0.1:6379> geoadd key:city 116 99 beijing1
    (error) ERR invalid longitude,latitude pair 116.000000,99.000000
    127.0.0.1:6379> geoadd key:city 116.23128 40.220779 beijing
    (integer) 1
    127.0.0.1:6379> geoadd key:city 31.40527 121.48941 shanghai 
    (error) ERR invalid longitude,latitude pair 31.405270,121.489410
    127.0.0.1:6379> geoadd key:city  121.48941 31.40527 shanghai 
    (integer) 1
    
  • 纬度经度,member名称,geoadd可以一次添加多个

  • 可以使用geopos读取地理位置

  • geodist,输出两地距离,加上unit单位,设置输出距离单位

    • 127.0.0.1:6379> geodist key:city beijing shanghai
      "1088645.3557"
      127.0.0.1:6379> geodist key:city beijing shanghai km
      "1088.6454"
      127.0.0.1:6379> 
      
    • m、km、mi 英里、ft 英尺

  • 我附近的人功能,通过半径来查询,获取附近的人的定位地址

  • image-20201121154950406

  • georadius命令来实现,longitude纬度、latitude经度、radius半径 单位,输入查询位置的经纬度就能从key集合中找到在半径范围的元素

  • 127.0.0.1:6379> georadius key:city 110 30 500 km 
    (empty array)
    127.0.0.1:6379> georadius key:city 110 30 5000 km 
    1) "shanghai"
    2) "beijing"
    127.0.0.1:6379> georadius key:city 110 30 5000 km withcoord
    1) 1) "shanghai"
       2) 1) "121.48941010236740112"
          2) "31.40526993848380499"
    2) 1) "beijing"
       2) 1) "116.23128265142440796"
          2) "40.22077919326989814"
    127.0.0.1:6379> georadius key:city 110 30 5000 km withdist
    1) 1) "shanghai"
       2) "1109.3250"
    2) 1) "beijing"
       2) "1269.4847"
       127.0.0.1:6379> georadius key:city 110 30 5000 km withhash
    1) 1) "shanghai"
       2) (integer) 4054807796443227
    2) 1) "beijing"
       2) (integer) 4069896088584646
       127.0.0.1:6379> georadius key:city 110 30 5000 km withdist asc count 1
    1) 1) "shanghai"
       2) "1109.3250"
    127.0.0.1:6379> 
    
    • 后面跟了几个参数,withdist、withcoord、withhash、asc、desc、count
      • Withdist:返回元素位置的同时,把与中心之间相差的距离一同返回
      • withcoord : 将元素经纬度一同返回
      • withhash : 返回经过geohash编码的有序集合分值,主要用于底层调试,作用不大
      • asc : 根据中心的位置,从近到远的方式返回位置元素
      • desc:从远到近的方式返回元素
      • count :获取前n个匹配元素,对于提高效率是有效的

HyperLogLog

是一种概率数据结构,计数唯一事物,从技术上讲估计一个集合的基数,通常计数唯一项需要使用成比例的内存,因为需要记录使用过的元素,以免多次记录,但是hyperloglog的算法可以用内存换精度,虽然有误差,但是误差小于1%,算法的神奇之处在于只需要很小的内存,最大也不超过12k,类似集合的功能,能记录2^64的计数,从内存的角度来说,hyperloglog是首选

能用在网页的UV

  • 传统方式,set保存用户的id,用户可能是uuid,这样可能占用巨大的内存
  • 但是只是需要计数功能并不需要保存用户的id
7.0.0.1:6379> pfadd loglog2 6 7 8 9 0
(integer) 1
127.0.0.1:6379> pfcount loglog1 loglog2
(integer) 10
127.0.0.1:6379> pfmerge loglog1 loglog2
OK
127.0.0.1:6379> pfcount loglog1
(integer) 10
127.0.0.1:6379> 
  • pfadd
  • pfcount 计数
  • pfmarge 合并 //合并到第一个key

Bitmaps

统计用户信息,活跃与不活跃,登录未登录只有两种状态的数据,可以使用BitMaps

可以用作打卡功能实现,到达一定数目之后进行统计,判断预期数目与统计得出的数目是否达到预期

  • image-20201121185616297

  • setbit 中的offset是偏移量,可以看作下标,value只能是0或1

  • getbit

  • bitcount 统计key offset 为1的个数

  • bitpos 查看 key offset 为0或1的位置,并且可以设置range

    • 127.0.0.1:6379> bitpos bit1 1 0 -1
      (integer) 0 
      127.0.0.1:6379> getbit bit1 0
      (integer) 1
      #查询bit1 范围从0到-1,bit值为1的元素下标
      
      
  • bitop 对一个或多个保存二进制位的字符串key进行位元操作,将结果保存在deskkey上

    • and、or、not、xor

    • 除了not之外,其他都能加入多个key进行运算

    • 127.0.0.1:6379> setbit bit1 0 1 
      (integer) 0
      127.0.0.1:6379> setbit bit1 1 1
      (integer) 0
      127.0.0.1:6379> setbit bit2 0 1
      (integer) 0
      127.0.0.1:6379> setbit bit2 1 0
      (integer) 0
      127.0.0.1:6379> bitop and bit1 bit2
      (integer) 1
      127.0.0.1:6379> get bit1
      "\x80"
      127.0.0.1:6379> getbit bit1 0
      (integer) 1
      127.0.0.1:6379> getbit bit1 1
      (integer) 0
      127.0.0.1:6379> 
      

事务transition

Redis事务的本质是一组命令的集合,一次执行多个指令,事务中所有命令都被序列化,其他客户端提交的命令请求不会插入到事务执行命令序列中

顺序、排他、一次性

单条命令是原子性执行的,但事务不保证原子性,且没有回滚,事务中任意命令执行失败,其余命令仍会被执行

  • 开启事务(multi)
  • 命令入队(。。。)
  • 执行事务(exec)
  • 取消事务(discard)
  • 监视、加锁(watch)
  • 取消监视、解锁(unwatch)

乐观锁应该适用于读多写少的情况,悲观锁应该适用于写多读少的情况

悲观锁

  • 为了避免其他人同时修改,直接对数据进行加锁以防止并发,修改数据前锁定,修改的方式被称为悲观并发控制 Pessimistic Concurrency Control 悲观、并发、控制
  • 独占性、排他性
  • image-20201122194314577
  • 在整个数据处理过程中,数据处于锁定状态
  • 线程操作数据,对数据添加排他锁
  • 假设最坏的情况,每次都默认其他线程更改数据
  • 传统数据库使用的几种加锁机制,都是在操作之前上锁
    • 行锁
    • 表锁
    • 读锁
    • 写锁
  • 在Java中同步用synchronized关键字实现
  • 悲观锁住要分共享锁和排他锁
    • 共享锁 shared locks
      • 读锁、S锁,多个事务对同一个数据可以共享一把锁,都能访问数据,只能读不能修改
    • 排他锁 exclusive locks
      • 写锁、X锁、不能与其他锁并存,一个事务获取了数据行的排他锁,其他事物就不能再获得该行的其他锁,获取排他锁的事物可以对数据行读取和修改
  • 悲观并发控制 先取锁再访问 的保守策略,开销大,还增加死锁的概率,降低并发性,其他事务必须等待

乐观锁

  • 假设数据一般情况不会造成冲突,在数据提交更新时正式对数据的冲突与否进行检测,发现了冲突,发出错误信息,让用户决定如何处理,适用于读操作多的场景,可以提高程序的吞吐量
  • 为了避免数据库的幻读,业务处理时间过长,乐观锁不会刻意使用数据库本身的锁机制,而是一句数据本身来保证数据的正确性
  • image-20201122194339249
  • 实现方法
    • CAS实现:java.util.concurrent.atomic 包下的原子变量
    • 版本号控制,在数据表添加爱version字段,数据被修改时,version值+1,线程更新数据,同时读取version值,提交更新时,读取到的version值与当前数据库中的version值相等才更新,否则重试,直到更新成功

multi实现

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1 k2 v2
QUEUED
127.0.0.1:6379> getget k1
(error) ERR unknown command `getget`, with args beginning with: `k1`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> #在中间出现了语法性错误,会取消其他命令的执行
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> mset k1 test k2 v2
QUEUED
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) "v2"
4) OK
5) "v3"
127.0.0.1:6379> #出现执行中产生的错误,能正确入队,不会影响到其他命令的执行

watch监视实现

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0P4WWoO-1606726398812)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201122205159.png)]

  • 当有多个线程在操控redis的时候

  • 被watch监视的key值如果发生改变,正在进行的事务将会失败

  • 每次加锁后都要进行解锁,再加锁去重新获取最新的值

  • 线程1

    • 127.0.0.1:6379> watch key
      OK
      127.0.0.1:6379> set key1 10
      OK
      127.0.0.1:6379> multi
      OK
      127.0.0.1:6379> incrby key 10
      QUEUED
      127.0.0.1:6379> incrby key1 20
      QUEUED
      127.0.0.1:6379> exec
      (nil)
      127.0.0.1:6379> mget key key1
      1) "30"
      2) "10"
      127.0.0.1:6379> #对key进行监控,key被阻止事务更新,key1在事务中也无法更新
      
  • 线程2

    • 127.0.0.1:6379> set key 30
      OK
      127.0.0.1:6379> #在线程1进行事务时,watch之后 exec之前,执行了set操作,会导致线程1的事务执行失败
      

brew install redis

几个命令

  • brew install redis  #brew 安装redis
    brew list redis  #查看redis安装的位置
    cd   #打开对应位置
    open .      #在terminal当前位置打开访达
    
  • image-20201121003941229

  • 安装都要设置redis.conf

  • 但是redis.conf并不在这个文件夹中

  • image-20201121004101655

  • 这里有一个homebrew.mxcl.redis.plist properties list文件

  • 用xcode打开看会比较清楚

  • image-20201121004217862

  • redis.conf 就在这里说明了

  • image-20201121004623111

  • 可以用/daemonize 查找 ,这里改成yes,其他设置参见其它博主,这里主要展示如何找到brew install redis 的redis.conf 文件位置

Jedis

官方推荐的操作Redis的中间件,SpringBoot已经有整合RedisTemplate

  • 导入包
  • 建立连接
  • 操作
  • 可关闭连接,或者直接就使用jedis连接池
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

里面的API接口名和操作名一样,根据API文档使用就行

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestRedis {
    public static void main(String[] args) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "haoyun");
        Jedis localhost = new Jedis("localhost");
        Transaction multi = localhost.multi();
        String result = jsonObject.toJSONString();
        try {
            multi.set("user1", result);
            multi.set("user2", result);
            multi.exec();

        } catch (Exception e) {
            multi.discard();

            e.printStackTrace();
        } finally {
            System.out.println(localhost.get("user1"));
            System.out.println(localhost.get("user2"));
            localhost.close();
        }


    }
}

剩下的就是自由发挥了

SpringBoot整合

SpringBoot操作数据:是封装在Spring-data中的,jpa、jdbc、mongodb、redis

在SpringBoot2.x以后与原来使用的jedis被替换成来看lettuce,底层已经不使用jedis了

  • jedis:采用的直连,多个线程操作的话,不安全,要提高安全性要使用jedis pool连接池
  • lettuce:采用netty,高性能网络框架,异步请求,实例在多线程中可以共享,不存在线程不安全的情况,dubbo底层也是用netty,可以减少线程数量,更像NIO
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

原理讲解

  • SpringBoot所有配置类,都会有一个自动配置类

  • image-20201123181024492

  • 自动配置类都会绑定一个properties配置文件

  • RedisAutoConfiguation

  • image-20201123184404313

  • 启动配置类中有一个RedisProperties配置类

  • image-20201123184540147

  • 里面有很多以前缀spring.redis开头的配置,可以在application中配置

  • 如host、password、、配置

  • RedisAutoConfiguation中封装了两个Bean

    • RedisTemplate

      • @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
              throws UnknownHostException {
           RedisTemplate<Object, Object> template = new RedisTemplate<>();
           template.setConnectionFactory(redisConnectionFactory);
           return template;
        }
        
      • 没有过多的设置,Redis的对象都是需要序列化的

      • 两个泛型都是object,后面使用需要强制转换

      • 靠自己重写config来替换这个template

    • StringRedisTamplate

      • 大部分情况下String类型是最常用的,就会多一个stringRedisTemplate
  • @ConditionalOnMissingBean(name = "redisTemplate")
    //重写一个redisTemplate就能替换掉这个bean
    

整合实现

  • 导入依赖
  • 配置连接
  • 测试

实现

  • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • image-20201123190125887

  • 根据在properties中看到的配置参数,以spring.redis为prefix的配置

  • 但是建议使用lettuce

  • 在redisTemplate的parameter中需要给入一个RedisConnectionFactory

  • image-20201123190946352

  • 有两个方法实现了这个接口

  • 在Jedis中有多个爆红,没下载完整

  • image-20201123191158826

  • lettuce中下载完整,为了避免不必要的错误,建议使用lettuce,默认生效

  • 测试

  • image-20201123192421507

  • 注入,RedisTamplate,里面有ops,表示operations 操作

  • 操作value字符串,hash、list、set、cluter集群、、、

  • 操作的几种数据类型,有些操作也直接拿出来了,可以直接调用,其他比较细化的操作就要进入各自的数据类型的操作

  •     @Test
        void contextLoads() {
            redisTemplate.opsForValue().set("name","haoyun");
            System.out.println(redisTemplate.opsForValue().get("name"));
            RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            connection.flushAll();
            connection.close();
    
        }
    
  • 能直接操作也能获取连接

  • 127.0.0.1:6379> keys *
    1) "\xac\xed\x00\x05t\x00\x04name"
    127.0.0.1:6379> 
    
  • 但是会出现乱码

  • Redis的对象都需要序列化serialization

  • 默认的序列化是JDK序列化,可能要使用JSON来序列化

  • 需要自己来定义配置类

  • 创建对象时需要序列化、implement Serializable

  •     @Test
        void test2() throws JsonProcessingException {
            User user = new User();
    //        String jsonUser = new ObjectMapper().writeValueAsString(user);
            redisTemplate.opsForValue().set("user",user);
            System.out.println(redisTemplate.opsForValue().get("user"));
        }
    
  • 直接将user给入会产生defaultSerializer序列化问题

  •     @Test
        void test2() throws JsonProcessingException {
            User user = new User();
    //        String jsonUser = new ObjectMapper().writeValueAsString(user);
            redisTemplate.opsForValue().set("user",user);
            System.out.println(redisTemplate.opsForValue().get("user"));
        }
    
  • 直接给入user对象,会报错DefaultSerializer错误

  • image-20201123201835889

  • 将对象序列化,或通过fastjson的方法专为string类型

  • 正式的开发一般通过json来传不会直接传对象

  • 虽然不报错了,但是存储的数据还是乱码

  • 127.0.0.1:6379> keys *
    1) "\xac\xed\x00\x05t\x00\x04name"
    2) "\xac\xed\x00\x05t\x00\x04user"
    127.0.0.1:6379> 
    
  • 默认序列化是JDKSerializer

  • RedisSerializer有多种实现方法

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAT2IeGz-1606726398826)(…/…/…/Library/Application Support/typora-user-images/image-20201123202648831.png)]

  • 选取一个实现方法,给setKeySerializer

  • template.setKeySerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
    
  • 多使用cmd+p查看方法需要的parameter,给进去就好

  • RedisConfig

  • package com.haoyun.redisspringboot.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.net.UnknownHostException;
    
    @Configuration
    @SuppressWarnings("all")
    //镇压所有警告
    public class RedisConfig {
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
                throws UnknownHostException {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
    //      默认的连接配置
            template.setConnectionFactory(redisConnectionFactory);
    
    //        序列化配置
    //        new 一个Jackson序列化对象,用于后面的设置
            Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
            ObjectMapper objectMapper = new ObjectMapper();
    //        用于转义
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    
            objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    //       创建string的序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    
    //       string的key和hash的key都采用string的序列化
    //        value都采用Jackson的序列化
    
            //key采用string序列化方式
            template.setKeySerializer(stringRedisSerializer);
            //hash的key采用string序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            //value采用Jackson序列化方式
            template.setValueSerializer(objectJackson2JsonRedisSerializer);
            //hash的value采用Jackson序列化方式
            template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
    
    
            return template;
        }
    }
    
  • 测试

  • package com.haoyun.redisspringboot;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.haoyun.redisspringboot.pojo.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    
    @SpringBootTest
    class RedisSpringbootApplicationTests {
        @Autowired
        @Qualifier("redisTemplate")
        private RedisTemplate redisTemplate;
    
        @Test
        void test1() throws JsonProcessingException {
            User name1 = new User("name", 3);
            String name = new ObjectMapper().writeValueAsString(name1);
            redisTemplate.opsForValue().set("key1", name);
            System.out.println(redisTemplate.opsForValue().get("key"));
    
        }
    
    
    }
    
    
  • 通过这样的序列化之后key就不会乱码了,但是在企业开发中一般不直接以原生的编写,将常用的操作封装为RedisUtils,自己写一些工具类来使用

  • 在工具类中应该加入一些容错操作,能抛出异常

  • 在公司能看到一些封装的RedisUtils

配置文件分析

bind 127.0.0.1
#绑定的ip
protected-mode yes
#保护模式
port 6379
#端口
#这些配置之后可能会经常使用

daemonize yes 
#以守护线程的方式开启

#日志
debug、verbose、notice、warning
#设置日志等级
loglevel notice

logfile
#设置日志文件位置

database 16
#16个数据库

always-show-logo yes 
#永远显示logo

snapshotting#快照
	三个方法,在规定时间内,执行了多少次操作,则会持久化到文件  .rdb  .aof
	redis是内存数据库,没有持久化,数据就会丢失
	save 900 1  #900秒内,至少有一个key进行了修改,就进行持久化操作
	save 300 10  #。。。。。
	save 60 10000  #同理
	
	stop-writes-on-bgseve-error yes
	#持久化错误之后是否要继续工作,默认开启
	
	rdbcompression yes
	#是否压缩rdb文件,需要消耗cpu资源
	
	rdbchecksum yes
	#保存rdb文件是否要进行错误检查校验
	
	dir 	./
	#rdb文件保存的目录
	
replication #主从复制,需要搭建多个redis


Security #安全设置
requirepass foobared
#默认没有密码
#通过命令config set requirepass 可以设置密码
#auth password   进行登录

########################################################################
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123
OK
127.0.0.1:6379> ping 
PONG
127.0.0.1:6379> quit
haoyun@HAOYUN ~ % redis-cli         #设置密码操作
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> 
########################################################################


maxlients 10000 
#设置能连接上redis的最大客户端数量
maxmemory <bytes>
#redis配置最大的内存数
maxmemory-policy noeviction
#内存到达上限之后的处理策略
		#移除一些过期的key
		#报错、、、
		#六种机制
		volatile-lru:设置了过期时间的key进行lru移除
		allkeys-lru:删除
		volatile-random:删除即将过期的key
		allkeys-random:随机删除
		volatile-ttl:删除即将过期的
		noeviction:永远不过期,直接报错
		


Append only模式  aof模式
#持久化的两种方式之一RDB、AOF
appendonly no
#默认是不开启的,默认使用RDB持久化,大部分情况下RDB完全够用

appendfilename "appendonly.aof"
#aof持计划文件名

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



	

Redis持久化

持久化RDB、AOF,重点

Redis是内存数据库,断电即失去,只要是内存数据库就一定会有持久化操作

RDB(Redis DataBase)

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

image-20201127162330130

  • 单独创建一个子进程,fork分支
  • 将内存内容写入临时RDB文件
  • 再用临时文件替换上次持久化完成的文件

整个过程主进程不进行任何io操作,保证了性能,如果进行大规模数据恢复,RDB和AOP都可以进行数据恢复,RDB数据恢复完整性不敏感,RDB更加高效,缺点时最后一次持久化后的数据可能丢失,默认使用的就是RDB,一般情况不需要修改这个配置

RDB保存的文件是dump.rdb

AOF保存的文件是appendonly.aof

配置快照在snapshots配置区域下

dump.rdb文件

  • 通过Redis config get dir 获取

  • config get dir
    
  • image-20201127165137385

  • image-20201127165146617

    触发机制

    • save规则触发
    • 执行flushall命令
    • 关闭redis
  • 备份会自动生成dump.rdb文件

如何恢复备份文件

只要将rdb文件放在redis规定的目录,redis启动时会自动检查dump.rdb文件恢复数据

查看位置,config get dir

在生产环境中最好对dump.rdb文件进行备份

RDB优缺点

优点:

  • 父进程正常处理用户请求,fork分支一个子进程进行备份
  • 适合大规模的数据恢复,如果服务器宕机了,不要删除rdb文件,重启自然在目录下,自动会读取

缺点:

  • 需要一定的时间间隔,可以自行修改设置
  • 如果redis意外宕机,最后一次的修改数据会丢失
  • fork进程的时候,会占用一定的内存空间

AOF(Append Only File)

将所执行的所有命令都记录下来,处读操作以外,恢复时重新执行一次,如果是大数据就需要写很久

aof默认是文件无限追加,大小会不断扩张

在主从复制中,rdb是备用的,在从机上使用,aof一般不使用

image-20201127214428307

  1. fork分支出子进程
  2. 根据内存中的数据子进程创建临时aof文件
  3. 父进程执行的命令存放在缓存中,并且写入原aof文件
  4. 子进程完成新aof文件通知父进程
  5. 父进程将缓存中的命令写入临时文件
  6. 父进程用临时文件替换旧aof文件并重命名
  7. 后面的命令都追加到新的aof文件中

开启AOF

配置文件Append Only Modo区块中设置

appendonly no
#默认关闭appendonly 手动设置yes开启

appendfilename "appendonly.aof"
#默认名字

# appendfsync always
appendfsync everysec
# appendfsync no
#每次都进行修改
#每秒钟都进行修改
#不进行修改

no-appendfsync-on-rewrite no
#是否进行重写

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#percentage重写百分比
#重写时文件最小的体积


#一般保持默认,一般只需要开启

这些配置也能在连接redis后在redis中通过config set 进行更改

image-20201127191738906

与RDB类似的触发机制,也能生成配置文件

image-20201127191904912

进行了一些操作,如list在同一个key上覆盖值操作,aof是一同操作的,把之前的值进行了覆盖,但是保存的并不是最新的值,而是把全部进行的操作保存了下来,lpush lpop,当从aof文件中恢复数据时,不管最新的值是什么都重新的进行一遍操作,这样在时间上和效率上并不是最优的,但是能保证在每次的操作能进行备份,保证数据不丢失,如果出于绝对的安全考虑可以开启aof

image-20201127193149734

aof文件损坏情况

  • 人为测试aof文件损坏,aof文件是根据文件的大小进行比对,判断文件是否损坏,使用

  • haoyun@HAOYUN ~ % redis-check-aof --fix /usr/local/var/db/redis/appendonly.aof 
    AOF analyzed: size=23, ok_up_to=23, diff=0
    AOF is valid
    
  • 损坏的aof会导致redis无法打开

  • 这个修复真垃圾,给我数据删没了,删除规律数据不好修复,但是加入明显没有逻辑的错误,还是能修复

  • redis-check-rdb 能修复rdb文件

优缺点

优点:

  • 可设置文件修改每次都同步备份,文件完整性更好,但是消耗性能
  • 设置每秒同步一次可能会丢失一秒的数据
  • 从不同步效率最高

缺点

  • 对于数据文件,aof远远大于rdb,修复速度也比rdb慢
  • aof是io操作,所以默认是aof
  • aof文件会无限扩大

redis成功启动配置文件不生效

个人贪图简便,使用brew安装redis,可在任何位置直接启动redis,有以下操作

redis-server &
redis-server & /usr/local/etc/redis.conf

这两种方式都能成功启动redis,但是在redis.conf中设置的配置都没有实现,通过

redis-cli下的
info


# Server
redis_version:6.0.8
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:25b38681eed52ae
redis_mode:standalone
os:Darwin 19.6.0 x86_64
arch_bits:64
multiplexing_api:kqueue
atomicvar_api:atomic-builtin
gcc_version:4.2.1
process_id:20853
run_id:45c7bef7cd21b8993e4fa34f9a8806cb0f7872f2
tcp_port:6379
uptime_in_seconds:61
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:12645496
executable:/Users/haoyun/redis-server
config_file:									
#这个配置文件并没有路径,所以在此配置的config并不会更新到配置文件中,也不会从配置文件读取
io_threads_active:0

说明以上两种开启redis的方式都没有读取到配置文件,之后还把配置文件放在了桌面也没有读取到,怀疑这个配置无法读取管理员用户的文件,使用sudo也被拒绝,所以把配置文件放在了根目录到/etc下

redis-server /etc/redis.conf


executable:/Users/haoyun/redis-server
config_file:/etc/redis.conf  #可以找到了
io_threads_active:0


127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"
127.0.0.1:6379> 
#在配置文件中的配置也能看到了

扩展

  • rdb持久化方式能够在指定的时间间隔内对数据进行快照存储

  • aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大

  • 如果只做缓存不需要使用任何持久化

  • 同时开启两种持计划方式

    • 重启时优先载入aof文件来恢复数据
    • 只会找aof文件,但是推荐只使用rdb用于备份,能快速重启,并且不会有aof可能潜在的bug
  • 性能建议

    • rdb文件只做后背用途,建议只在slave上持久化rdb文件,15分钟备份一次,使用save 900 1 规则

    • 使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据

      • 代价:持续的io
      • rewrite 过程中产生的新数据写到新文件造成的阻塞不可避免,尽量减少rewrite的频率
    • 不使用aof,也可以通过Master-Slave Replication 实现高可用性也可以,能省去一大笔io,减少rewrite带来的系统波动

      • 代价:如果Master-Slave 同时倒掉,回丢失十几分钟的数据,启动脚本也要比较Master-Slave中的rdb文件,选择最新的文件,载入新的,微博就是这种架构

Redis主从复制

一个Master有多个slave,将一台redis服务器数据,复制到其他的redis服务器,前者称为主节点(masterleader),后者称为从节点(slave、follower),数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主

默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点

主从复制作用包括:

  • 数据冗余
    • 实现了数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复
    • 主节点出现问题,从节点可以提供服务,实现快速的故障恢复,实际上是一种服务的冗余
  • 负载均衡
    • 在主从复制的基础上,配合读写分离,主节点提供写服务,从节点提供读服务,写redis数据时连接主节点,读redis数据连接从节点,分担服务器负载,尤其在写少读多的场景下通过,多个从节点分担负载,可以提高redis性能
  • 高可用(集群)基石
    • 哨兵、集群,能够实施的基础,主从复制时高可用的基础

不能只使用一台redis的原因:

  • 从结构上讲,单个redis服务器会发生单点故障,一台服务器需要处理所有请求,压力大
  • 从容量上讲,单个redis服务器内存容量有限,并且不能完全使用全部的内存,单台redis的最大内存不应该超过20g压力过大

通常的电商网站都是一次上传吗,无数次浏览,读多写少 ,主从复制,读写分离,80%的情况都在进行读操作,起码一主二从

image-20201128163922689

需要配置的config选项

  • daemonize
  • port
  • pidfile
  • logfile
  • dbfilename
  • rdb-del-sync-files

Redis replication实现

  • 查看启动的服务

  • ps -ef|grep redis
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8p8phEba-1606726398835)(…/…/…/Library/Application Support/typora-user-images/image-20201129230519933.png)]

  • 默认情况下每台redis主机都是主节点

  • image-20201129231637446

  • 一般只要配置从机,让从机找主机

  • master 6379 | slave 6380 6381

  • slaveof 127.0.0.1 6379
    #找端口为6379的作为master host
    info replication
    #查看配置
    
  • image-20201129232312658

  • 6380从机中显示的主机器的地址,和端口,和当前role角色的状态为slave

  • 在主机中也会显示从机的配置

  • 127.0.0.1:6379> info replication
    # Replication
    role:master
    connected_slaves:2
    slave0:ip=127.0.0.1,port=6380,state=online,offset=602,lag=1
    slave1:ip=127.0.0.1,port=6381,state=online,offset=602,lag=1
    master_replid:3f9a8d15d0e7a2b3978ad8e6cfc1d8fc490b464e
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:602
    second_repl_offset:-1
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:1
    repl_backlog_histlen:602
    127.0.0.1:6379> 
    
    
  • 真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的

  • 配置在redis.conf文件中replication区块下

  • replicaof <masterip> <masterport>
    replicaof 127.0.0.1 6379
    
  • 配置文件设置好,启动时就不用重新设置

  • 根据读写分离的原则,主机只能写,从机只能读

  • slave 会自动write master中的数据,但是不能往slave中写数据

  • 127.0.0.1:6380> set k2 v2
    (error) READONLY You can't write against a read only replica.
    #你不能对只读副本进行写操作
    
  • 在没配置哨兵的情况下,当master崩溃了,slave还是slave

  • 测试情况,细节问题:

    • 如:主机崩溃,从机是否还能读取
      • 从机能读取,还是不能写,希望改进为从slave中选出一个master
    • 从机崩溃,主机继续写入数据,从机恢复,能否get到恢复时段主机读取的值,分两两种情况
      • 没在redis.conf中设置的slave,读取不到崩溃时master set的数据
      • 在redis.conf中配置的slave,能读取到
      • 只要变为从机就会立马从主机中获取值
  • 复制原理

    • slave启动成功连接到master后会发送一个sync同步命令
    • master接到命令,启动后台的存盘进程,同时收集所接收到的用于修改数据集命令,后台执行完毕之后,master将传送整个数据文件到slave,并完成一次同步,成为增量复制
    • 专有名词
      • 全量复制
        • slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
      • 增量复制
        • master继续将新的所有收集到的修改命令依次传给slave,完成同步
    • 只要重新连接master,一次完全同步(全量复制)将被自动执行,数据一定能在从机中看到
  • 结构

    • image-20201130092430226
    • 两种结构,星型结构,链式结构
    • 链式结构的slave81 点master设置为slave 80 ,实现的功能也是一样,当master 79 崩溃,slave 80也不会变成master

哨兵模式

当master宕机时让slave变为master

slaveof no one
#让自己变为主机

这种设置是手动的,使用哨兵模式将自动选取master

此时master恢复后使用slaveof no one 的主机也还会继续当master,要重新作为slave只能重新配置

单哨兵模式、多哨兵模式

概述切换技术的方法是,当master服务器宕机后,需要人工切换,费事,更多时候选择优先考虑是哨兵模式,redis2.8 开始正确提供sentinel(哨兵 )

能够监控后台的主机是否故障,根据投票自动将从库专为主库

哨兵模式是一种特殊模式,哨兵是一个独立的进程,作为进程独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例

image-20201130095837202

像每台发送信息确定主机是否存活,优点类似于springcloud的心跳检测

这种图成为单机哨兵,当单个哨兵也宕机也会有风险,创建多个哨兵是个不错的选择,称为多哨兵模式

当哨兵模式检测到master宕机,会自动将slave切换成master,通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机

image-20201130101436660

多哨兵模式

  • 假设master宕机,sentinel先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,sentinel之间会发起一次投票,投票的结果由随机一个sentinel发起,进行failover操作,得到sentinel票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线

多哨兵模式实现

  • 配置

  • sentinel monitor mymaster 127.0.0.1 6379 1
    ## sentinel monitor <master-name> <ip> <redis-port> <quorum>
    #quorum(法定人数)至少需要<quorum>个哨兵同意的情况下,能确定处于客观关闭状态
    #(Objectively Down) state only if at least <quorum> sentinels agree.
    
  • 然后启动就行

  • 默认端口为26379,默认pid为69427

  • 当master 79 宕机,sentinel选举了81为newmaster

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sB5Z3Bw3-1606726398841)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201130112103.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6d6UPsD-1606726398842)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201130112115.png)]

  • master节点断开,这时候从slave中选择一个座位master,其中有投票算法,自行了解

  • 当79重新启动后,是以80作为master的slave role存在

  • 69996:X 30 Nov 2020 11:25:38.335 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
    

哨兵模式优缺点

优点

  • 基于集群,基于主从复制,所有的主从配置的优点,它全有
  • 主从可以切换,故障可以切换,系统的可用性提高
  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点

  • redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  • 哨兵模式需要很多配置
    • 多哨兵,多端口配置复杂,一般由运维来配置

Redis缓存穿透、击穿、雪崩

都是服务的三高问题

  • 高并发
  • 高可用
  • 高性能

面试高频,工作常用

redis缓存的使用极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,数据一致性问题,严格意义上来讲,问题无解,对一致性要求极高,不推荐使用缓存

布隆过滤器、缓存空对象

缓存穿透

用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透

在直达持久层的路径上加上过滤器、或者缓存中专门增加一个为空的请求

布隆过滤器

  • 布隆过滤器是一种数据结构,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统查询压力
  • image-20201130145545450

缓存空对象

  • 当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源
  • image-20201130150140646
  • 需要面临的问题
    • 存储空的key也需要空间
    • 对空值设置了过期时间,还会存在缓存层和存储层的数据有一段时间窗口不一致,对于需要保持一致性的业务会有影响

缓存击穿

例子微博服务器热搜,巨大访问量访问同一个key

一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库

某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大

解决方案

  • 设置热点数据不过期
    • 一直缓存也会浪费空间
  • 加互斥锁
    • 分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

缓存雪崩

在某一个时间段,缓存集中过期失效,redis宕机

产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机

双十一时会停掉一些服务,保证主要的一些服务可用,springcloud中说明过

image-20201130151759127

解决方案:

  • 增加集群中服务器数量
    • 异地多活
  • 限流降级
    • 缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待
  • 数据预热
    • 正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀
  • 19
    点赞
  • 54
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论

打赏作者

好运haoyun

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值