狂胜——Redis学习笔记

【狂神说Java】Redis最新超详细版教程通俗易懂_哔哩哔哩_bilibili

总体大纲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

Redis入门

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

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

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

Redis能干嘛

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

特性

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

常用网站

windows启动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

 Linux启动

 退出

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是否存在       EXISTS name
  • move 移除key     move name 1
  • expire 设置过期时间,可以用作单点登录     expire name 10   10秒后过期
  • ttl 查看过期时间       EXISTS name
  • type 查看key的类型    type name
  • config set requirepass XXX 设置密码
  • config set requirepass “” 取消密码
  • auth xxx 登录
     

String

append 追加value

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

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

decr xc 递减1

incrby 能设置自增量的自增     incrby xc 12   decrby xc 12

getrange 截取范围,下标从0开始   getrange xc 0 2    取xc第一到第三的数   getrange xc 0 -1 全部

setrange 修改范围的值   setrange xc 1 22   123456变122456

setex (set wth expire) 设置key时顺便设置生存时间    setex xc aaa  30

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

                setex xc  aa

mset 批量设置,空格间隔   mset xc1 1 xc2 2 xc3 3

mget    mget xc1 xc2 xc3

msetnx 原子性操作,批量设置,要么都成功或失败,nx如果存在就创建失败

对象操作


设置一个对象,值为json字符串来保存一个对象

user:{id}:{filed}

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

数据结构是相同的

String类似的使用场景:value除了是外面的字符串还可以是我们的数字

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

List

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

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

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

  • lpush left push
  • lrange 显示list 范围range
  • rpush right push

  • Rpop right pop
  • Lpop left pop

  • Llen xc                 返回xc集合(list)的长度
  • lindex list 0           获取list中第一给值
  • lrem remove指定下标

  • ltrim list截取保留指定数据

  • Rpoplpush pop再push到别的list

  • lset 设置指定下表值

  • exists 存在
  • Linsert 将某个具体的value插入到某个元素的前面或后面

Set

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

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

  • sadd        sadd myset xc像myset中插入xc
  • smembers 查看集合的全部值   
  • sismember 判断是否存在   SISMEMBER myset xc  判断xc是否存在
  • scard 获取集合元素个数   scard  myset
  • srem 移除某一个member   srem myset myxc  移除myset集合指定myxc
  • srandmember 随机抽取几个member  (srandmember myset)srandmember myset 2

  • spop 随机删除一个数据  spop myset
  • smove myset mysets xc2    将myset集合里的xc2移动到mysets里
  • sdiff difference set 差集 ,结果来自first_key为基准
  • sinter intersection set 交集
  • sunion union联合 并集
    • 将用户放到set中,共同关注、共同爱好、推荐好友

Hash

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

  • hset hset key field value  存值
  • hget                      取值

  • hmset   存多个数据
  • hmget   取多个数据
  • hgetall   查全部数据

  • Hdel 删除  hdel myhash xc   删除myhash里的xc数据
  • hlen    hlen  myhash  获取myhash字段长度
  • hexists 判断hash中指定字段是否存在    hexists  myhash  xc
  • hkeys 获取key所有的field
  • hvals 获取所有的value

  • hincrby 递增,参数设置为负数就是递减
  • hsetnx 是否存在,不存在就创建设置,存在就不设置

  • 做用户信息保存,比用string类型好点,存储经常变动的信息,hash更适合存储对象,

ZSet(有序集合)

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

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

  • 排序
  • zrangebyscore

  •  zcard salary  获取salary集合的个数
  • zconut     zcount myset 1 3 获取myset1-3区间的数量

三种特殊数据类型

  • 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
  • 纬度经度,member名称,geoadd可以一次添加多个

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

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

    • m、km、mi 英里、ft 英尺

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

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

  • 后面跟了几个参数,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
  • pfadd
  • pfcount 计数
  • pfmarge 合并 //合并到第一个key

Bitmaps

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

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

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

  • getbit

  • bitcount 统计key offset 为1的个数    bitcount sign

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

  • bitop 对一个或多个保存二进制位的字符串key进行位元操作,将结果保存在deskkey上

    • and、or、not、xor

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

事务transition

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

一次性、顺序性、排他性,一次执行多个指令,其他客户端提交的命令请求不会插入到事务执行命令序列中

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

  • 开启事务(multi)
  • 命令入队(。。。)
  • 执行事务(exec)

  • 取消事务(discard)
  • 监视、加锁(watch)
  • 取消监视、解锁(unwatch)

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

悲观锁

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

 

Jedis

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

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

测试

package com.xc;

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

/**
 * 星晨
 */
public class TestPing {
	public static void main(String[] args) {
		//1.new Jedis对象即可
		Jedis jedis = new Jedis("127.0.0.1",6379);
		System.out.println(jedis.ping());//连接成功打印PONG

		jedis.flushDB();//清空当前数据

		JSONObject jsonObject = new JSONObject();
		jsonObject.put("hello","world");
		jsonObject.put("name","xingchen");

		//开启事务
		Transaction multi = jedis.multi();
		String result = jsonObject.toJSONString();

		try {
			multi.set("user1",result);
			multi.set("user2",result);
//			int i = 1/0;//代码抛出异常事务,执行失败!

			multi.exec();//执行事务
		} catch (Exception e){
			multi.discard();//放弃事务
			e.printStackTrace();
		} finally {
			System.out.println(jedis.get("user1"));
			System.out.println(jedis.get("user2"));
			jedis.close();//关闭连接
		}


	}
}

SpringBoot整合

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

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

  • jedis:采用的直连,多个线程操作的话,不安全,要提高安全性要使用jedis pool连接池 BIO
  • 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>
  • 配置连接
# 配置Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
  • 测试
package com.xc;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class Redis02SpringBootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        //redisTemplate  操作不同的数据类型,api和我们的指令是一样的
        //opsForValue 操作字符串 类似String
        //opsForList  操作List  类似List
        //opsForSet
        //opsForHash
        //opsForZSet
        //opsForGeo
        //opsForHyperLogLog

        //除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD

        //获取redis的连接对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushDb();
//        connection.flushAll();

        redisTemplate.opsForValue().set("mykey","xingchen");
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }

}

Redis工具类

package com.xc.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time, timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time, timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, TimeUnit timeUnit, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time, timeUnit);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time,TimeUnit timeUnit) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time,timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time,TimeUnit timeUnit) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time,timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }

    // ===============================HyperLogLog=================================

    public long pfadd(String key, String value) {
        return redisTemplate.opsForHyperLogLog().add(key, value);
    }

    public long pfcount(String key) {
        return redisTemplate.opsForHyperLogLog().size(key);
    }

    public void pfremove(String key) {
        redisTemplate.opsForHyperLogLog().delete(key);
    }

    public void pfmerge(String key1, String key2) {
        redisTemplate.opsForHyperLogLog().union(key1, key2);
    }


}

Redis.conf详解

Redis持久化

持久化RDB、AOF,重点

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

RDB(Redis DataBase)

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

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

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

RDB保存的文件是dump.rdb

AOF保存的文件是appendonly.aof

配置快照在snapshots配置区域下

dump.rdb文件

  • 通过Redis config get dir 获取

config get dir
  • 触发机制

    • 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文件会无限扩大

扩展

  • 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订阅发布

 

 

 

 

 

 

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

一主二从

默认情况下,每台Redis服务器都是主节点;我们一般情况下只用配置从机就好了

认老大,一主(6379)、二从(6380、6381)

 

  • 真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的

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

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

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

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

哨兵模式

当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,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线
     

多哨兵模式实现

1、配置sentinel.conf

sentinel monitor  被监控的名称   主机地址  端口  1

sentinel monitor mymaster 127.0.0.1 6379 1

 后面的这个数字1,代表主机挂了,slave(从机)投票看让谁接替成为主机,票数最多的就会成为主机。

2、启动哨兵

  • 然后启动就行

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

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

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

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

主机宕机之后哨兵会重新在从机中选出一个从机做为新主机,如果旧主机重新启动回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则。

哨兵模式优缺点

优点

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

缺点

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

Redis缓存穿透、击穿、雪崩(面试高频,工作常用)

都是服务的三高问题

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

面试高频,工作常用

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

布隆过滤器、缓存空对象

缓存穿透

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

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

布隆过滤器

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

缓存空对象(查不到)

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

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

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

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

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

解决方案

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

 

缓存雪崩

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

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

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

image-20201130151759127

解决方案:

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

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值