Redis基础

Redis基础

基本操作:

package com.sangeng.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     * 
     * @param key
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

特点: 快:使用的内存

​ nosql(非关系型数据库)

​ 持久化(aof,rdb)

​ 单线程(重要)安全,快速

​ 有16个数据库

第一次用户查到数据时,后台从数据库拿到返回给前面,且将数据存入redis,后续再查的时候直接从redis查即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dtgj9PR-1681790602326)(file://C:/Users/CG/AppData/Roaming/Typora/typora-user-images/1667555577037.png?lastModify=1667555573)]

1 . NoSQL数据库(非关系型数据库)

  • oSQL最常见的解释是"non-relational", 很多人也说它是"Not Only SQL"
  • NoSQL仅仅是一个概念,泛指非关系型的数据库
  • 区别于关系数据库,它们不保证关系数据的ACID特性(原子性,隔离性,一致性,持久性,不会像mysql那样表中是什么类型,对应的java就得是什么类型)
  • NoSQL是一项全新的数据库革命性运动,提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入

2 . NoSQL的特点 (关系型数据库的补充)

应用场景:

  • 高并发的读写 10w/s
  • 海量数据读写
  • 高可扩展性 不限制语言、lua脚本增强
  • 速度快

不适用场景:

  • 需要事务支持

  • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询(用户自定义查询条件的查询)

    政府银行金融项目,还是使用关系型数据库。oracle

    redis命令:

    http://redis.cn/commands.html#list

3 . 几种常见的NoSql数据库:

memcache

  • 很早出现的NoSql数据库
  • 数据都在内存中,一般不持久化
  • 支持简单的key-value模式
  • 一般是作为缓存数据库辅助持久化的数据库

redis介绍

  • 几乎覆盖了Memcached的绝大部分功能
  • 数据都在内存中,支持持久化,主要用作备份恢复
  • 除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset(排好序的set)等。
  • 一般是作为缓存数据库辅助持久化的数据库
  • 现在市面上用得非常多的一款内存数据库

mongoDB介绍

  • 高性能、开源、模式自由(schema free)的文档型数据库
  • 数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘
  • 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
  • 支持二进制数据及大型对象
  • 可以根据数据的特点替代RDBMS,成为独立的数据库。或者配合RDBMS,存储特定的数据。

列式存储HBase介绍:

HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时读写操作的场景中。HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10亿行数据,还可处理有数百万列元素的数据表

1 . redis基本介绍:

中文网站[http://www.redis.cn

redis:

  • 它是一个开源的、使用ANSI C语言编写的key-value存储系统(区别于MySQL的二维表格形式存储)
  • 和Memcache类似,但很大程度补偿了Memcache的不足,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失(因为Memcache没有持久化功能)。redis具有持久化特性,可以保证数据不丢失。

内存的速度要比磁盘快几千倍。

2、Redis的应用场景

#2.1 取最新N个数据的操作

比如典型的取网站最新文章,可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取

#2.2.排行榜应用,取TOP N操作

这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。

#2.3需要精准设定过期时间的应用

比如可以把上面说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。(比如登录超过多长时间后就删除该登录记录,用户需要重新登录)

#2.4计数器应用

Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。

#2.5Uniq操作,获取某段时间所有数据排重值

这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。(set本身就有去重复性)

#2.6实时系统,反垃圾系统

通过上面说到的set功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。没有做不到,只有想不到。(比如登录的用户的集合set,记录进行了相关操作的用户的set集合,两个进行交集,那么就可以找出只登录而不做操作的用户(这些用户是否是占内存或者怀疑是否是监听我们网站系统的))

#2.7缓存

将数据直接存放到内存中,性能优于Memcached,数据结构更多样化。

#3、Redis的特点

  • 高效性 (内存)

    • Redis读取的速度是30w次/s,写的速度是10w次/s
  • 原子性 (主逻辑线程是单线程)

    • Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 pipline
  • 支持多种数据结构

    • string(字符串) a->b 配置 color–> red
    • list(列表) a->list 消息队列 msg—>[“hello”,“ydlclass”,“itlils”]
    • hash(哈希) a->map 购物车 1----->[“1”=>“剃须刀”,“2”=>“电脑”]
    • set(集合) a->set 去重 quchong–>[“北京”,“山西”,“河北“]
    • zset(有序集合) a->sorted set 排行榜 top10->[”xx拿了金牌,10“,“跑路了,9.5”]
  • 稳定性:持久化,主从复制(集群)(为了防止redis断电后内存的数据丢失)

  • 其他特性:支持过期时间,支持事务,消息订阅

我们解压完成redis后在当前路径cmd+回车

输入指令启动服务端:redis-server.exe redis.windows.conf

运行后显示出:(注意不要关闭该窗口,关了服务端就被关闭了,后面客户端启动就无法连接,启动不了)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sD2YppqN-1681790602327)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667569640557.png)]

然后我们启动客户端:

同样在redis解压当前路径下cmd+回车,输入客户端启动指令:

redis-cli.exe -h localhost -p 6379

redis-cli.exe:客户端

-h:连接

-p:端口号

运行后显示与服务端连接成功:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8NpIniA-1681790602328)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667569963854.png)]

我们输入一个ping 测试出现一个PONG,说明我们连接建立好了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wpyf0OcN-1681790602328)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667570450979.png)]

bin目录下的redis-server 的redis.conf文件来启动redis:

./bin/redis-server redis.conf

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3A7rX5S-1681790602328)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667616063117.png)]

linxu的服务端redis启动成功后,我们使用windows的redis客户端连接:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lPZvwT15-1681790602329)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667618486144.png)]

测试也能ping通。如果连接失败说明美哦与关闭防火墙,我们把linxu的防火墙关闭: systemctl stop firewalld

我们在测试一下:在客户端设置一个值:set a= b

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nBdvxQ2z-1681790602329)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667618713333.png)]

此时我们在linux上启动一个redis客户端连接上linxu的服务端redis,获取该值:

./redis-cli -h 192.168.58.128 -p 6379

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOeduLe7-1681790602329)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667619232838.png)]

我们再使用一个工具测试也能成功连接到服务端:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VEE9BHMy-1681790602330)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667618657787.png)]

我们使用该工具连接成功后,我们打开数据库(0-15个数据库)发现了前面设置的a=b的数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I4hjh08S-1681790602330)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667619440648.png)]

3 . redis的数据类型:

redis当中一共支持五种数据类型,分别是:

  • string字符串

key ---- “我爱学习 ”

  • list列表(内部是一个linkelist,因为linkedelist增删效率高)

    key-----{a,c,c,d}

  • set集合(不可重复的set)

    KEY ----- -{a—1,c----5,d—4}

  • hash表(map)

    KEY ----- {keyA—valueB, keyC—valueD}

  • zset有序集合(排好序的set)

    key ------ {a–100,b—90,c----60}

相关语法操作文档:https://www.runoob.com/redis/redis-keys.html

4 . 对String的操作:

1 设置值 获取值
set ydlclass value
get ydlclass
2 mset mget 一次性操作多组数据
mset ydlclass value ydlclass1 value1 ydlclass2 value2
mget ydlclass ydlclass1 ydlclass2
3 没有这个键我们才设置
setnx dlclass value
4 将key的值 加一,减一
incr stock 
decr stock
5设置 a值存活时间5秒,值是b    验证码
setex a 5 b

设置一个叫ydlclass的key value值为cgboy:

set ydlclass cgboy

取该key的value值:

get ydlclass

带m代表多个,

mset mget 一次性操作多组数据

设置key值ydlclass,ydlclass1,ydlclass2和对应的value值

mset ydlclass cgboy ydlclass1 cgboy1 ydlclass2 cgboy2

获取ydlclass,ydlclass1,ydlclass2的value值

mget ydlclass ydlclass1 ydlclass2

setnx key value:设置一个还没有存在的key ,和key对应的value值,设置成功后返回1(如果key存在则设置失败返回0)

setnx ydlcalss3 cc

strlen key :返回key的字符串长度

ydlclass—cgboy 计算的value值长度

strlen ydlcalss :5

msetnx key value:设置多个(还没有存在的)键值对:

msetnx boy cc [bb ss]

incr key value:将key的value值+1操作

incr age 15(将age的值15+1 :16)

decr key value:将key的value值-1操作

decr age 15(将age的值15-1 :14)

setex key seconds value

setex a 10 b:设置一个a它的值为b,存活时间为10s,10s后删除掉

5 . 对hash列表的操作:

1设置值 获取值
hset user username itlils
hset user age 18
hget user username
2批量
hmset user1 username itnanls age 19 
3获取所有的键值对
hgetall user
4获取所有小key
hkeys  user
5获取所有值
HVALS user
6删除 
hdel user age

对hash的操作的指令都是h开头

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hvGOA6dl-1681790602330)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667633647513.png)]

设置值

设置user的value值为一个hashmap,里面键值对:age—15

hset user age 15

获取值

hget user age : 15

hsetnx KEY key value :小key不存在时能设置小key的value值

批量设置值:

hmset user name cg age 15

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8N7bjjq-1681790602331)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667633807863.png)]

获取所有键值对:

hgetall user

把user中的所有小key的值拿出来:

hkeys user

hvals user

删除某个大key当中的一个或多个小key-value:

hdel user age:删除user中的age的键值对

hdel user age,name:删除user中的age,name的键值对

6 . 对list列表的操作:

1 设置值
lpush list1 1 2 3 4 1
rpush list1 6
2查看数据
lrange list1 0 -1
3 移除数据
lpop list1
rpop list1

对list列表操作的指令都是l开头的,按照插入顺序排序

内部是linkelist:有序的,增删效率快

默认左边为list头部,右边list尾部

带l的指令是从list表的左边操作,带r的指令是从list表的右边操作

将一个或多个值插入到列表头部:(从左边开始放)

lpush boy name cc age 15:给boy放入name – cc ,age — 15的键值对:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SpcsDxj5-1681790602331)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667634488813.png)]

rpush list1 5 6 :给list1 从右边放入5 , 6的值

查看list中所有的数据:

创建list:

lpush list 1,5,6,2,4,1(插入后的顺序是1,4,2,6,5,1)

lrange list 0 2 :查看的数据为1,5,6

将一个值插入到头部已经存在的值:

lpushx list 5

lindex key index:

获取list中0下标位置的数据:

lindex list 0

llen key :获取列表长度

移除数据:

已知list1:5,1,4,3,2,1,6,

左边移除

lpop list1

list1:1,4,3,2,1,6

右边移除

rpop list1

list1:1,4,3,2,1

通过移除的方式我们可以实现队列和栈的数据结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CB05vWTN-1681790602331)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667636613909.png)]

7 . 对set集合的操作:

1添加数据
sadd set1 1 2 3 4 5
2获取数据
smembers set1
3获取成员数量
scard set1
4业务 uv 当天登陆用户数
sadd uv:20220222 001 002 003 002
scard uv:20220222

set,无序,不可重复

set的指令一般都s开头

添加数据:

sadd set 1,2,3,6

获取数据(遍历数据)

smembers set

获取成员的数量:(4个)

scard set

uv:当天(2022年11月5日)登录的用户数量

saad uv:20221105 001 002 003

scard key : 统计当天登录用户数量(3)

scard uv:20221105

8 . 对key的操作:

1删除
del user1
2查看所有的key  
keys *     生产环境下,别用
3存在key
exists user1
4存活时间
expire ydlclass 5
5剩余存活时间   登陆续期
pttl user1
6随机获取 key
randomkey

删除key

del key

查看所有的key(生产环境别用)

keys *

查看某个key是否存在(1存在,0不存在)

exists user

给key设置存活时间(秒单位)

expire user 10 (10s后这个key(user)失效)

以毫秒为单位返回key的剩余的存活时间:(如果返回的是-1就代表永久存在)

pptl user

从当前数据库中随机返回一个key:

randomkey

9 . 对zset的操作,例如热搜:(有序的,排好序的set)-- 重要

1添加
zadd pv 100 page1.html 200 page2.html 300 page3.html
2查看
zcard pv
3查询指定权重范围的成员数
ZCOUNT pv 150 500
4增加权重
ZINCRBY pv 1 page1.html
5交集
ZADD pv_zset1 10 page1.html 20  page2.html
ZADD pv_zset2 5 page1.html 10  page2.html
ZINTERSTORE pv_zset_result 2 pv_zset1  pv_zset2
6成员的分数值
ZSCORE pv_zset page3.html   
7 获取下标范围内的成员。 排序,默认权重由低到高
ZRANGE pv 0 -1
8获取由高到低的几个成员(reverse)使用最多的
效率很高,因为本身zset就是排好序的。
ZREVRANGE key start stop

zset的操作指令一般都是z开头

添加数据

user用户访问各个网站的数量:page1.html:100个用户访问 page2.html:200个用户访问 page3.html:300个用户访问

zadd user 100 page1.html 200 page2.html 300 page3.html

添加后会按照数量排好序存储到set中

查看数据:获取有序集合的成员数量:

zcard user(3个值)

查询指定范围权重(100到300)的成员数量

zcount user 100 300(page1,page2 和 page3符合)

增加权重

zincrby user 1 page1.html (此时page1.html的权重为:101)

交集:

首先有两个zset数据

zadd pv_zset1 10 page1.html 20 page2.html

zadd pv_zset2 5 page1.html 10 page2.html

将两个zset表取交集合,合并为一个pv_zset表(因为两个集合的元素属性都同属一种所以他们直接权重相加即可)(里面的权重数值相加):

zinterstore pv_zset_result 2 pv_zset1 pv_zset2

此时我们查看pv——zset的权重数值:

zrange pv_zset page2.html zset的排序是默认由权重的由低到高来排序的

输出后为 30

zrange pv_zset page1.html zset的排序是默认由权重的由低到高来排序的

输出后为 15

获取下标范围内成员 (zset的排序是默认由权重的由低到高来排序的)

zrange pv 0 1

(使用最多)

反转:zset的排序是默认由权重的由低到高来排序,我们获取由高到低的几个成员

zrevrange user 0 -1 (全部反转)

zrevrange user 0 2 (反转 0 - 2 的元素)

10 . 位图 bitmap的操作:

  • Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量offset

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FynJokpR-1681790604130)(null)]

  • BitMaps 命令说明:将每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id

    如上图我们把0-7的下标代表8个用户,用户访问了网站的bit为1,没有访问的bit 为0

    (1)设置值:

    setbit key offset(下标) value

    unique:users:2022-04-05代表2022-04-05这天的独立访问用户的Bitmaps(设置以下的都为1,都访问过)

     setbit unique:users:2022-04-05 0  1  
     setbit unique:users:2022-04-05 5 1  
     setbit unique:users:2022-04-05 11 1  
     setbit unique:users:2022-04-05 15 1  
     setbit unique:users:2022-04-05 19 1
    

如果有很多用户达到上万的级别时,我们不可能要去找下标为10000的位图,假设该用户的id为:10002,那么我们做setbit操作时将用户id减去10000来处理,那么就剩2了,那么就很方便我们去操作和查找该用户了。

(2)获取值:

getbit key offset

获取键的第offset位的值(从0开始算),如:

获取id=8的用户是否在2022-04-05这天访问过, 返回0说明没有访问过。

getbit unique:users:2022-04-05 8
获取Bitmaps指定范围值为1的个数:
bitcount key [start end]

例:下面操作计算2022-04-05这天的独立访问用户数量:

bitcount unique:users:2022-04-05
Bitmaps间的运算:

bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中

已知设置了bitmap的值:

setbit unique:users:2022-04-04 1 1  
setbit  unique:users:2022-04-04 2 1  
setbit  unique:users:2022-04-04 5 1  
setbit  unique:users:2022-04-04 9 1  

例1:下面操作计算出2022-04-04和2022-04-05两天都访问过网站的用户数量, 如下所示

bitop and unique:users:and:2022-04-04_05  unique:users:2022-04-04 unique:users:2022-04-05  

两个表交集形成的一个新表:

bitcount unique:users:and:2022-04-04_05

例2:如果想算出2022-04-04和2022-04-05任意一天都访问过网站的用户数量(例如月活跃就是类似这种) , 可以使用or求并集, 具体命令如下:

bitop or unique:users:or:2022-04-04_05  unique:users:2022-04-04 unique:users:2022-04-05  

两个表交集形成的一个新表:

 bitcount unique:users:or:2022-04-04_05

11 . 对HyperLogLog结构的操作

(1) 应用场景

HyperLogLog常用于大数据量的统计,比如页面访问量统计或者用户访问量统计。

比如一个网站页面记录有多少用户在今天访问过(只算访问一次),使用set集合统计。那么当有上千万个用户访问了上千万的页面,一个页面就由上千万用户访问,上千万个页面就更多了。这样体量下redis就负荷不了了。

所以我们使用再redis中加入了HyperLogLog结构来解决这种大数据量的统计问题,且占用内存较小,性能高,但它的精确度的误差为0.81%

(1)设置值:

代表淘宝在2022-1106这天有个用户id为1的访问了淘宝的页面:

pfadd taobao:uv:2022-1106 1

hyperloglog内部是一个set,如果此时我们在插入一条:

pfadd taobao:uv:2022-1106 1

,显示时只有一条数据 ,不可重复。

(2)计算:

已知:访问该天用户有:1,2

pfadd taobao:uv:2022-1106 1

pfadd taobao:uv:2022-1106 2

计算该天访问了淘宝页面的用户数量:2

pfcount taobao:uv:2022-1106

12 . redis java api操作:

已知我们在linxu上开启了服务端的redis,且使用客户端连接上了服务端的redis得到了相关的数据库:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uA0jTy2U-1681790602332)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667718006125.png)]

我们在idea使用java操作redis:

引入依赖:

<dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.14.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

测试类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQJNKk8U-1681790602332)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667718132169.png)]

注意:

我们的@Test必须是我们依赖中的api,才能成功使@BeforTest,@AfterTest成功生效

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOyq6Ocw-1681790602333)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667718163483.png)]

我们测试运行:成功输出了数据库中的所有key的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkLioToR-1681790602333)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667718266975.png)]

框架搭建好后,我们只要运行测试类,在运行前就会帮我们连接jedispool连接池,运行完后会自动关闭连接池,我们就可以在idea中操作redis中的数据了:

(1)对String类型操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42wxNBSX-1681790602333)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667719663192.png)]

运行后输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HCunC5sN-1681790602333)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667719681062.png)]

(2)对hash(map)操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsBpbzZ6-1681790602334)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667722644358.png)]

运行后输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RP4iudrb-1681790602334)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667722665190.png)]

(3)对list的操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkcO6Vhr-1681790602334)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667724199039.png)]

运行后输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7gfSFlYo-1681790602335)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667724215858.png)]

(4)对set操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nqHemOA-1681790602335)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667724931199.png)]

运行后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bAaGuxqK-1681790602335)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667724954176.png)]

(5)jedis操作bitmap:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDv2YpgH-1681790602335)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667726462119.png)]

(6)jedis操作HyperLogLog:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w20dWYM4-1681790602336)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667726870677.png)]

Redis进阶

课程目标(面试、运维)

  • 能够理解Redis的持久化

  • 能够理解Redis的主从复制架构

  • 能够理解Redis的Sentinel架构

  • 能够理解Redis cluster集群架构

  • redis缓存常见面试题

  • 常见问题

第一章 redis的持久化

因为redis的数据是存在内存的的,所以当断电等情况是会导致内存的数据丢失的。所以redis的数据持久化来避免这种问题。redis有两种数据持久化的方式:RDB和AOF,默认redis的数据持久化方式为RDB。

1 . RDB持久化方案

Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。

如下图,我们最开始存入内存的数据会定期复制一份到了硬盘当中,这样我们系统故障或者断电导致内存的数据消失,但是拷贝的硬盘的数据还存在的,保证了数据持久化。在硬盘的数据就是rdb。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8WsXnFf9-1681790602336)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667742415552.png)]

我们可以在配置文件中配置进行快照保存(定期拷贝数据到硬盘的时机)的时机:

save [seconds] [changes]  

意为在seconds秒内如果发生了changes次数据修改,则进行一次RDB快照保存,例如:

在60s内发生了100此对数据的修改操作,那么就将数据快照保存:

save 60 100

只要符合我们设置的条件,redis就会触发rdb保存。

一般情况下redis默认就是开启的rdb保存机制,可以通过save或者bgsave命令手动触发rdb快照保存,他们的方式各有区别:

save直接调用rdb保存,会阻塞redis主进程,知道将数据保存完为止,在主进程阻塞期间服务器不能处理客户端的请求(非常不常用)。

bgsave会分叉出一个子进程,去负责调用rdb保存,并在保存完成后向主进程发送信号,通知保存已完成。redis在子进程保存数据期间仍然可以继续处理客户端的请求。

(1)RDB方案优点:

  1. 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
  2. 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
  3. 使用RDB文件进行数据恢复比使用AOF要快很多。

(2)RDB方案缺点:

1 . 快照保存数据是定期保存的,或多或少会造成数据丢失。比如设置60s内,对数据操作100次时进行rdb保存数据,但是在59s时对数据进行了99次操作,如果此时服务器断电那么就会导致这些数据丢失。

2 . 如果数据集非常大且CPU不够强,会导致效率不高。

我们在linxue系统中,打开redis.conf的配置文件,在里面设置5s内进行了操作就进行rdb保存:

save 5 1

在进行了对配置文件的修改后,我们要重启redis服务器才能生效:

通过客户端redis告诉服务端redis你可以停掉了:

./bin/redis-cli -h 192.168.58.128 shutdown

然后再启动服务端redis:

./bin/redis-server redis.conf

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7Yqy1hd-1681790602336)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667745334847.png)]

为了保证客户端能连接上服务端,我们关闭linux的防火墙,随后客户端连接上服务端

systemctl stop firewalld

./bin/redis-cli -h 192.168.58.128

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JH3nlfYm-1681790602336)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667745409000.png)]

此时我们设置一个值,set a ”b",过了5s后,

我们将服务器直接杀死: exit指令使其服务器杀死,

此时内存已经将这个数据删除了,data的dump.rdb文件就保存了丢失的数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TkpCmx6-1681790602337)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667745568495.png)]

此时我们按照上面的操作使服务器重启,然后客户端连接上后,我们在get a就会出现a对应的值:b:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZrhZ9UF-1681790602337)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667745840607.png)]

这就意味着在服务器被突然杀死后,内存的数据虽然消失了,但是重启服务器后在获取这个数据时 ,会直接去data下的dump.rdb拿到消失的数据。

2 . AOF持久化方案

aof是redis默认关闭的。

在redis.conf文件中,配置开启aof:

  appendonly  yes 

开启后如上重启服务器才能生效。

(1)aof核心思想:

采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。

它也会将内存的数据完整的保存到硬盘,但是还会记录对数据的写操作的日志,此时内存的数据是被修改了的数据,如果此刻服务器宕机,内存的数据清空,那么去硬盘的将保存的完整的数据通过记录了的日志再次执行对数据的操作,然后拷贝给内存。这样就恢复了修改完成后的数据。但是这样也有问题,那就是在磁盘会再次执行对数据的操作以此来恢复数据,对硬盘的负荷太大了。所以会使用aof重写:

(2) aof rewrite:

随着AOF不断地记录写操作日志,因为所有的写操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。如下图:

我们在保存写操作时设置了一个

a -->b,后续又进行操作删除了a,那么就aof rewrite就会把这种多余的,成对存在的写操作记录删除掉,只保存非成对的:设置了c,d的值,但是没有他们对应删除操作。这样在恢复数据时就可以节省更多的时间和性能了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCifxzJ9-1681790602337)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667789501690.png)]

AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行

 auto-aof-rewrite-percentage 100 
 auto-aof-rewrite-min-size 64mb  

如上代码设置:Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite

auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才触发,后面的每次重写就不会根据这个变量了。该变量仅初始化启动Redis有效

64m—>10万次操作–>128M—>40M–100万次操作–80M

比如设置最开始记录的aof日志大小64m,随后又进行写操作,aof日志记录达到了128m翻了1倍,此时开始执行重写,随后又进行写操作,文件又增加了40m,在128m的基础上没有翻1倍,不执行重写,再进行写操作,达到了80m,此时再40m的基础上翻了一倍,执行重写,以此类推。

(3)配置aof:

AOF提供了三种fsync配置:always/everysec/no,通过配置项[appendfsync]指定:

  1. appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
  2. appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
  3. appendfsync everysec:折中的做法,交由后台线程每秒fsync一次

(4)aof优点:

  1. 最安全,在启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
  2. AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复
  3. AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据

(5)aof缺点:

  1. OF文件通常比RDB文件更大
  2. 性能消耗比RDB高
  3. 数据恢复速度比RDB慢

(6)RDB or AOF

每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟。

Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。

本人以前的公司,最后的从机上,rdb aof都开启。

第二章 Redis 事务

1 . redis事务介绍:

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

  • Redis事务没有隔离级别的概念

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

  • Redis不保证原子性

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

一个事务从开始到执行会经历以下三个阶段:

  • 第一阶段:开始事务
  • 第二阶段:命令入队
  • 第三阶段:执行事务

2 . redis事务相关命令:

multi:开启事务

exec:提交事务

discard:取消事务(如果我们执行事务时感觉有问题或不恰当,可以使用此指令取消事务):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yn7YrrDk-1681790602338)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667794577211.png)]

watch:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令

unwatch:取消WATCH对所有key的监视

3 . 事务演示:

开启事务,设置值后,提交事务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHoBRRbr-1681790602338)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667793582333.png)]

开启事务后,已知key1, key2设置值为v1,v2,随后对这两个值进行修改时,将其中一个set写错成sets,回车显示报错(编译时错误)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUxgJm9A-1681790602338)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667793860555.png)]

提交事务后,我们再查询值,发现值并没有改变。

已知key1,key2设置值v1,v2都是String类型,我们对其进行修改时再key中使用list的形式设置值,此时我们系统编译没有出错,但在我们提交事务后,显示报错说你设置的值的类型错误(运行时错误):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2BoVvIc-1681790602338)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667794164170.png)]

此时我们在查看key1,key2的值时发现key1的值修改成功,key2的值修改失败了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IONniUpa-1681790602339)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667794399812.png)]

由此可见redis的事务并没有保证整个事务的原子性。

第三章 数据删除有与淘汰策略

一 . 数据删除

1 . 过期数据:

(1)Redis中的数据特征

我们设置了一个name---->itnanls 300s后失效,我们使用:

ttl name 查看剩余多少时间失效:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6h9zQri-1681790602339)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667801465897.png)]

Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态

TTL返回的值有三种情况:正数,-1,-2

  • 正数:代表该数据在内存中还能存活的时间
  • -1:永久有效的数据
  • -2 :已经过期的数据 或被删除的数据 或 未定义的数据

删除策略就是针对已过期数据的处理策略,已过期的数据是真的就立即删除了吗?其实也不是,我们会有多种删除策略,是分情况的,在不同的场景下使用不同的删除方式会有不同效果,这也正是我们要将的数据的删除策略的问题

(2)时效性数据的存储结构:

我们使用expire,expireat等指令设置值和设置了存活时间后,内存底层是这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wOiOyJGT-1681790602339)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667802787121.png)]

redis会先在内存开辟空间存储key-value的值,但是value还保存有存活时间的地址,redis会再开辟内存空间给这个过期时间,地址与存活时间是按照key-value的hash形式一一对应。

过期数据是一块独立的存储空间,Hash结构,field是内存地址,value是过期时间,保存了所有key的过期描述,在最终进行过期处理的时候,对该空间的数据进行检测, 当时间到期之后通过field找到内存该地址处的数据,然后进行相关操作。

2 . 删除策略

redis通过删除策略删除掉快要过期的数据。

(1)定时删除:

创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RtCHxmkC-1681790602340)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667803351287.png)]

  • 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
  • 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
  • 总结:用处理器性能换取存储空间(拿时间换空间)

(2)惰性删除:

数据到达过期时间,不做处理。等下次访问该数据时,我们需要判断:

  1. 如果未过期,返回数据
  2. 发现已过期,删除数据,返回不存在

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-98ex0nRg-1681790602340)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667803531397.png)]

  • 优点:节约CPU性能,发现必须删除的时候才删除
  • 缺点:内存压力很大,出现长期占用内存的数据
  • 总结:用存储空间换取处理器性能(拿时间换空间)

(3)定期删除:

定时删除和惰性删除这两种方案都是走的极端,那有没有折中方案?

我们来讲redis的定期删除方案:

redis启动服务器初始化时,读取配置server.hz的值,默认为10

每秒钟执行server.hz(10)次 去依次调用serverCron()方法------>adatabaseCron()方法--------->activeExpireCycle()方法对每个expire[*]逐一进行检测,每次执行耗时:250ms/server.hz(我们知道redis在1s内能执行10w次读写操作,那么就意味着只需要4/1的时间就可以完成这个功能,那么4/3的时间就可以执行读写操作了,会更省时间和性能)

redis的数据库有0-15个库:expire[0] — expire[15]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdJ76gUG-1681790602340)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667805585184.png)]

在内存中他们的分布类似这样:每个库存储了对应数据,存活时间和存活时间的地址:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mdBTOcxO-1681790602340)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667805618849.png)]

对某个expire[*]检测时,会随机挑选W个key检测:

如果key超时,删除key

如果一轮中删除的key的数量大于w数量的25%,那么继续对这个库进行检测

如果一轮中删除的key的数量小于w数量的25%,那么检查下一个库

  • 参数current_db用于记录activeExpireCycle() 进入哪个expires[*] 执行(比如当前这1s检测到了1库current_db此时就为2,下一秒开始检测2库)
  • 如果activeExpireCycle()执行时间到期,下次从current_db继续向下执行

w的取值也可自己配置:

  W取值=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值

总结定期删除:

总的来说:定期删除就是周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度,只占用了主线程的4/1

  • 特点1:CPU性能占用设置有峰值,检测频度可自定义设置
  • 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
  • 总结:周期性抽查存储空间(随机抽查,重点抽查)

3 . 删除策略对比

(1):定时删除:

节约内存,无占用,
不分时段占用CPU资源,频度高,
拿时间换空间

(2):惰性删除:

内存占用严重
延时执行,CPU利用率高
拿空间换时间

(3):定期删除:

内存定期随机清理
每秒花费固定的CPU资源维护内存
随机抽查,重点抽查

二 . 数据淘汰策略(逐出算法):面试必问

1 . 淘汰策略:

比如我们内存给了redis10g使用,当数据达到10g了,如果此时还在录入新的数据,那么就会造成内存溢出的严重问题,所以我们需要指定淘汰策略来让这10g内存的数据哪些淘汰掉,减轻内存负担。清理数据的策略称为逐出算法。

2 . 策略配置:

影响数据淘汰的相关配置如下:

1:最大可使用内存,即占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上

maxmemory ?mb

2:每次选取待删除数据的个数,采用随机获取数据的方式作为待检测删除数据

maxmemory-samples count

3:对数据进行删除的选择策略:policy表示下面介绍的8种策略,按照情况选取

maxmemory-policy policy

那数据删除的策略policy到底有几种呢?一共是3类8种

第一类:检测易失数据(可能会过期的数据集server.db[i].expires ) 同一个库

volatile-lru:挑选最近最少使用的数据淘汰      least recently used
volatile-lfu:挑选最近使用次数最少的数据淘汰   least frequently used
volatile-ttl:挑选将要过期的数据淘汰
volatile-random:任意选择数据淘汰

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I3GEBsSS-1681790604712)(null)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5w91Nq2p-1681790604808)(null)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HF2X1xzO-1681790604859)(null)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lHlsx27c-1681790604941)(null)]

第二类:检测全库数据(所有数据集server.db[i].dict )

allkeys-lru:挑选最近最少使用的数据淘汰
allkeLyRs-lfu::挑选最近使用次数最少的数据淘汰
allkeys-random:任意选择数据淘汰,相当于随机

第三类:放弃数据驱逐

no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发OOM(Out Of Memory)

注意:这些策略是配置到哪个属性上?怎么配置?如下所示

maxmemory-policy volatile-lru

数据淘汰策略配置依据

使用INFO命令输出监控信息,查询缓存 hit 和 miss 的次数,根据业务需求调优Redis配置

第四章 Redis的主从复制架构

一 . 主从复制简介:

1 . 高可用:

首先我们要理解互联网应用因为其独有的特性我们演化出的三高架构

  • 高并发

    应用要提供某一业务要能支持很多客户端同时访问的能力,我们称为并发,高并发意思就很明确了

  • 高性能

    性能带给我们最直观的感受就是:速度快,时间短

  • 高可用

  • 可用性:一年中应用服务正常运行的时间占全年时间的百分比,如下图:表示了应用服务在全年宕机的时间

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K1vO5VRo-1681790604997)(null)]

    我们把这些时间加在一起就是全年应用服务不可用的时间,然后我们可以得到应用服务全年可用的时间

    4小时27分15秒+11分36秒+2分16秒=4小时41分7秒=16867秒

    1年=3652460*60=31536000秒

    可用性=(31536000-16867)/31536000*100%=99.9465151%

    业界可用性目标5个9,即99.999%,即服务器年宕机时长低于315秒,约5.25分钟 这是不允许的,我们要让服务器保持使用那也是不太可能的,所以为了能高可用,我们使用主从复制。

2 . 主从复制概念:

知道了三高的概念之后,我们想:你的“Redis”是否高可用?那我们要来分析单机redis的风险与问题

问题1.机器故障

  • 现象:硬盘故障、系统崩溃
  • 本质:数据丢失,很可能对业务造成灾难性打击
  • 结论:基本上会放弃使用redis.

问题2.容量瓶颈

  • 现象:内存不足,从16G升级到64G,从64G升级到128G,无限升级内存
  • 本质:穷,硬件条件跟不上
  • 结论:放弃使用redis

结论:

为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用,同时实现数据冗余备份。

多台服务器连接方案:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2hAlHJ9-1681790605079)(null)]

  • 提供数据方:master

主服务器,主节点,主库主客户端

  • 接收数据方:slave

从服务器,从节点,从库

从客户端

  • 需要解决的问题:

数据同步(master的数据复制到slave中)

这里我们可以来解释主从复制的概念:

概念:主从复制即将master中的数据即时、有效的复制到slave中

特征:一个master可以拥有多个slave,一个slave只对应一个master

职责:master和slave各自的职责不一样

master:

写数据
执行写操作时,将出现变化的数据自动同步到slave
读数据(可忽略)

slave:

读数据
写数据(禁止)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZPI9qwMr-1681790605133)(null)]

3 . 主从复制的作用
  • 读写分离:master写、slave读,提高服务器的读写负载能力
  • 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数 量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
  • 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
  • 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
  • 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
4 . 主从复制流程:

主从复制过程大体可以分为3个阶段

  • 建立连接阶段(即准备阶段)
  • 数据同步阶段
  • 命令传播阶段(反复同步)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8ylmQTm-1681790605235)(null)]

而命令的传播其实有4种,分别如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rwJdAj8l-1681790605324)(null)]

slave机实现对主机的同步后,也可作主机让客户端连接,这样能使服务器主机负荷降低,性能提高。(从机只能读操作,能不能进行写操作,主机负责写操作,读操作交给从机做)

主从复制的工作流程(第一阶段)

阶段一:建立连接

建立slave到master的连接,使master能够识别slave,并保存slave端口号

流程如下:

  1. 步骤1:设置master的地址和端口,保存master信息
  2. 步骤2:建立socket连接
  3. 步骤3:发送ping命令(定时器任务)定时去ping一下,保证master随时是和slave连接的
  4. 步骤4:身份验证
  5. 步骤5:发送slave端口信息

至此,主从连接成功!

当前状态:

slave:保存master的地址与端口

master:保存slave的端口

总体:之间创建了连接的socket

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qz1hxfzP-1681790605417)(null)]

第9部操作是:同步主机redis.conf配置文件的信息,监听端口 并发送指令给主机

第10部:主机接收到从机的操作后,知道了前面连接的是从机,保存了从机的ip,端口号。

master和slave互联

接下来就要通过某种方式将master和slave连接到一起

方式一:客户端发送命令

slaveof masterip masterport

方式二:启动服务器参数

redis-server --slaveof masterip masterport

方式三:服务器配置(主流方式

slaveof masterip masterport

slave系统信息

master_link_down_since_seconds
masterhost & masterport

master系统信息

uslave_listening_port(多个)

主从断开连接

断开slave与master的连接,slave断开连接后,不会删除已有数据,只是不再接受master发送的数据

slaveof no one

授权访问

master客户端发送命令设置密码

requirepass password

master配置文件设置密码

config set requirepass password
config get requirepass

slave客户端发送命令设置密码

auth password

slave配置文件设置密码

masterauth password

slave启动服务器设置密码

redis-server –a password
主从复制的工作流程(第二阶段):
  • 在slave初次连接master后,复制master中的所有数据到slave
  • 将slave的数据库状态更新成master当前的数据库状态
1 . 同步过程:

因为我们知道rdb恢复数据有可能会有部分数据流失,所以我们在使用了rdb的同时会用上aof,因为aof可以全方位的恢复数据,但是性能和速度都消耗过大,所以我们将两种持久化方案用在一起就能相辅相成解决问题。

  1. 步骤1:请求同步数据
  2. 步骤2:创建RDB同步数据
  3. 步骤3:恢复RDB同步数据
  4. 步骤4:请求部分同步数据
  5. 步骤5:恢复部分同步数据

至此,数据同步工作完成!

当前状态:

slave:具有master端全部数据,包含RDB过程接收的数据

master:保存slave当前数据同步的位置

总体:之间完成了数据克隆

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-da0UUl9y-1681790605463)(null)]

在上图第九步,对数据恢复完成后从机发送指令给主机说数据恢复完成了。如果此时主机收到后仍然有数据需要同步,那么循环上面的操作。

2 .数据同步阶段master说明

1:如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行

2:复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。以下是设置缓冲区内存大小:

repl-backlog-size ?mb

3:master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执 行bgsave命令和创建复制缓冲区。

3 .数据同步阶段slave说明
  1. 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
 slave-serve-stale-data yes|no
  1. 数据同步阶段,master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令
  2. 多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,因此数据同步需要根据业务需求,适量错峰(因为redis主逻辑是单线程的,所以当有多个从机要与主机同步时,那么主机就要为这些从机挨个的将rdb数据与从机同步,那么就要复制很多次在通过网络传输给从机,效率极低)。
  3. slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构(如下图),中间的节点既是master,也是 slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ophdHctJ-1681790605508)(null)]

生产环境:一开始,想好redis架构,一开始就把n主m从的redis服务都启动起来。

主从复制的工作流程(第三阶段):

当我们的用户对数据库数据进行各种增删改查后,需要把数据同步到从服务器上。

  • 当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播
  • master将接收到的数据变更命令发送给slave,slave接收命令后执行命令

命令传播阶段的部分复制

命令传播阶段出现了断网现象:

网络闪断闪连:忽略

短时间网络中断:部分复制

长时间网络中断:全量复制

这里我们主要来看部分复制,部分复制的三个核心要素

  1. 服务器的运行 id(run id)
  2. 主服务器的复制积压缓冲区
  3. 主从服务器的复制偏移量
  • 服务器运行ID(runid)
概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id

组成:运行id由40位字符组成,是一个随机的十六进制字符
例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce

作用:运行id被用于在服务器间进行传输,识别身份
如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别

实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave,
slave保存此ID,通过info Server命令,可以查看节点的runid
  • 复制缓冲区
概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
	复制缓冲区默认数据存储空间大小是1M
	当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列
作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set,select)

数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中,通过在data找到.aof的日志文件打开后呈现出使用过的每个指令和对应的指令占了几个字符的统计:如$3  set   ,set三个字符所以为$3:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbQhS4aK-1681790602344)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667832136610.png)]

如下图:缓冲区中存储的是一个字节数组:它的偏移量(也就是下标),主机中只要对数据进行了操作的指令都会记录到.aof文件中,并将文件中的数据拷贝发送给缓冲区,由缓冲区发送给从机,从机再根据这些记录恢复部分的数据。以set为列,因为上图我们知道$3下一行时set,在下一行是$5,再下一行是name,要在字节数组中表示那么0下标对应s,1下标对应3,要到下一行表示需要\r\n(回车换行)所以对应3,4下标,后续依次类推,通过这种方式该数组存储了.aof文件内的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zLnyqzkI-1681790605607)(null)]

复制缓冲区内部工作原理:

组成

  • 偏移量

    概念:一个数字,描述复制缓冲区中的指令字节位置

    分类:

    • master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
    • slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)

    作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用

    数据来源:

    • master端:发送一次记录一次
    • slave端:接收一次记录一次
  • 字节值

工作原理

  • 通过offset区分不同的slave当前数据传播的差异
  • master记录已发送的信息对应的offset
  • slave记录已接收的信息对应的offse

二 . 心跳机制

在命令传播阶段,主机和从机需要进行信息交换,使用心跳机制进行维护,保证双方一直连接在线的(比如我们客户端连接上服务器后,我们通常会ping一下发送给服务器,服务器连接没有断开的情况下会响应一个pong)

master心跳:

  • 内部指令:PING
  • 周期:由repl-ping-slave-period决定,默认10秒
  • 作用:判断slave是否在线
  • 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常

slave心跳任务

  • 内部指令:REPLCONF ACK {offset}
  • 周期:1秒
  • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
  • 作用2:判断master是否在线

心跳阶段注意事项:

  • 当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步
min-slaves-to-write 2
min-slaves-max-lag 8

slave数量少于2个,或者所有slave的延迟都大于等于8秒时,强制关闭master写功能,停止数据同步

  • slave数量由slave发送REPLCONF ACK命令做确认
  • slave延迟由slave发送REPLCONF ACK命令做确认

至此:我们可以总结出完整的主从复制流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OoCQPY0W-1681790602345)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667887970171.png)]

由上图,主和从连接成功后,会进行数据同步,同步完成后会定时发送ping指令来保证双方都是连接在线的。

最后就是在前面两阶段完成前产生的用户的数据(对数据库的操作)进行同步:

1 . 从机发送指令(假设数据量为10w):REPLCONF ACK {10w}

2 .主机接收后会判断从机发送的数据是否在缓冲区,如果不在缓冲区 ,说明数据都不一致,需要主机将当前所有数据全部复制给从机,保持主从同步。

3 . 在缓冲区的情况:

如果从机发送的offset(偏移量)和当前主机的offset(偏移量)不同,说明在数据同步完成之前,有其他的数据操作的指令产生,那么就要主机将这过程产生的这部分数据复制传输给从机使主从同步。(发送+CONTINUE指令,使用aof将这部分指令数据发送给从机,使主从数据相同)

4 . 从机接收到后,保存数据,执行bgrewrite aof 进行增量恢复数据(将之前主从同步完成前产生的新的数据同步到从机,使主从数据相同)

如果在缓冲区说明是同一份数据,从机发送的offset(偏移量)和当前主机的offset(偏移量)相同,忽略不处理。

三. 搭建主从架构:

真实的生产环境:一主二从,主从部署在不同的地方:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SMGfUXu-1681790602345)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667889613398.png)]

我们先复制一份相同redis:

cp -R redis-6.2.6/ redis6380

查看当前文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MztCmSVX-1681790602345)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667889881723.png)]

再复制一份:

cp -R redis-6.2.6/ redis6381

这样我们就有一个主机和两个从机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A9NmmzSM-1681790602346)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667889987822.png)]

由于这两个redis都哦是复制的主服务器的redis,我们先把data的数据都删除掉,然后配置好两个从机的redis的配置文件:(这里只展示其中6380的配置)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQnWjoYF-1681790602346)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667890271523.png)]

存放数据的目录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nYcj58A-1681790602346)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667890442981.png)]

修改日志存放目录:logfile

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vy2gS2sR-1681790602346)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667890499069.png)]

配置完成保存退出。

复制一个新的标签页面,我们启动主redis(端口为6379):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2hYcgpd-1681790602347)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667891329511.png)]

我们测试一下6380redis以客户端连接主服务器:

使6380的redis启动以客户端连接主redis服务端:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUqlimGv-1681790602347)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667891357876.png)]

连接成功后,我们测试ping,服务器也成功响应了我们pong。

真正的以从机形式连接主服务器实现主从同步:先将6380杀掉,再重新连接:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jge3kS9A-1681790602347)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892256388.png)]

然后我们再登录6380的redis:登陆后再查看相关信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KpD5JYm6-1681790602347)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892376226.png)]

显示:我们可以看到6380变成了一个从服务器,它的端口,ip都是跟主服务器一样的了,显示了它们是连接着的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZCtCNpYX-1681790602347)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892473681.png)]

我们修改reds6381的配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0XIkYiV-1681790602348)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892694820.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7VFy7Ptq-1681790602348)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892734715.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aqNWFVzk-1681790602348)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892784656.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1AjdoXqS-1681790602348)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667892824994.png)]

修改完成保存退出。

我们让6381的redis作为6379主服务器的redsi的从机:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RAnXmFzO-1681790602349)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667893148128.png)]

然后我们使用客户端的身份登录,查看状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RcqTl0KU-1681790602349)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667893276887.png)]

显示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQ0f6Pxz-1681790602349)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667893327911.png)]

由此我们实现了一主(6379)二从(6380,6381)的主从架构。

1 . 主从同步测试:
(1)主节点写数据,从节点读数据:

我们在主节点上set 一个a = b:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7NyfQjSb-1681790602349)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667893556782.png)]

在从节点6381上get也能拿到a的值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbEWkPoI-1681790602349)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667893579321.png)]

(2)从节点不能写数据,只能读,不能写:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBZSL6Q0-1681790602350)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667893714136.png)]

第五章 Redis中的Sentinel架构

引入:

当主从保持连接的情况下,服务器突然宕机,那么从节点就会被当作主节点来继续维持运作。可是有很多的从节点,到底哪个来作为替代的主节点使用呢?

第一步:在主节点宕机了后,其他的从节点首先也要主动断开连接,因为此时主节点宕机后,从节点获得的数据就不对了。

第二步:找一个从节点充当主节点

第三步:修改其他的从节点的配置文件,让他们连接新的主节点

第四步:启动全新的主和从节点

第五步:开始全量复制和增量复制

但是也有问题:

现在充当主节点的从节点是只有读操作,不能写数据的,数据服务谁来操作呢?

从节点那么多,究竟选哪个充当主节点?

如果原来的主节点起来后又怎么操作?

我们需要哨兵来解决这些问题。

一 . 哨兵:

哨兵是一个分布式系统,用于对主从结构中每台服务器进行监控,当出现故障时通过投票机制选择新的主节点并将所有的从节点连接到新的主节点。

1 . 哨兵的作用:

(1)哨兵连接服务器主机知道了其他的从节点的信息,并不断地进行监控主机和从机的连接状态。

监控:监控master和slave

不断的检查master和slave是否正常运行

(2)当某个哨兵监控到主服务器宕机了,会告诉其他哨兵服务器宕机了,其他的哨兵也会去检查是否真的宕机了。

通知(提醒):当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知

(3)主机服务器真的宕机后,哨兵会通知从节点与主节点断开连接,然后所有的哨兵会投票选取出一个从机作为主服务器运行,最后让其他的从机连接新的主机,并且通知其他的客户端和哨兵。

自动故障转移:断开master与slave连接,选取一个slave作为master,将其他slave连接新的master,并告知客户端新的服务器地址

注意:哨兵也是一台redis服务器,只是不提供数据相关服务,通常哨兵的数量配置为单数,单数是因为在出现故障后哦,单数可以保证投票选取出一个从机充当新的主机

如下图 :注意哨兵并不是一一对应的监控每个服务器,哨兵是连接到主机服务器获得到其他的从服务器的信息,并不断监控主机和从机之间的连接状况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17W8WTHH-1681790602350)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667898338646.png)]

2 . 启用哨兵:

前面我们已经实现了一主二从的主从同步结构,我们给它们启用哨兵,结构大致如下:(端口号和ip舆图中不同)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LiBb2gwv-1681790602350)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667898738713.png)]

将redis的包拷贝了三份:

cp -R redis6380 sentinel26379
cp -R redis6380 sentinel26380
cp -R redis6380 sentinel26381

分别叫26379,26380,26381.随后修改了sentinel.conf的配置

我们配置了一主二从,三哨兵结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M7ZBDhE9-1681790602350)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667900147222.png)]

在每个哨兵的配置中都有一个sentinel.conf的配置文件:我们进行配置:每个哨兵端口根据要求更改:这里只写6379的配置。

port 26379
daemonize yes
pidfile "/var/run/redis-sentinel26379.pid"
logfile "/export/server/sentinel26379/log/log.log"
dir "/export/server/sentinel26379/data"
sentinel monitor mymaster 192.168.200.131 6379 2
sentinel resolve-hostnames no
sentinel announce-hostnames no

依次启动三个哨兵指令:在各自的哨兵的redis的包内启动指令:

./bin/redis-sentinel sentinel.conf

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECbdTIcw-1681790602350)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667903123354.png)]

三个哨兵都登录后,我们在其中一个连接主机服务器:并查看信息状态

./bin/redis-cli -h 192.168.58.128 -p 6379

info

发现是一个哨兵,而且还说了主机有两个从机,三个哨兵:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bb1z4f3I-1681790602351)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667910674334.png)]

我们连接登录主机服务器查看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdqlPghA-1681790602351)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911201482.png)]

发现确实有俩个从服务器6380,6381

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y411ydhe-1681790602351)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911146167.png)]

此时我们杀掉6379的主服务器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bn3nuoFB-1681790602351)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911306859.png)]

使用kill -9 4672指令:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MO3znnRu-1681790602351)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911352563.png)]

此时我们在去连接登录6380的从服务器并查看状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAXj3NeR-1681790602352)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911432156.png)]

状态显示:我们发现6380成了主机服务器了,且有从服务器6381

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z1LcYAtm-1681790602352)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911494459.png)]

这说明在主机宕机后,哨兵自动投票选取了6380的从服务器为主机服务器,让6381成为了6380的从服务器。

此时我们再将原来的主服务器6379恢复启动:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1IXc20Bd-1681790602352)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911663625.png)]

此时我们连接登录6380,并查看状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gPEyp4ST-1681790602352)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911793181.png)]

显示:6380仍然是一个主服务器,而原来的6379就成为了6380的从服务器了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6IkkfAm-1681790602352)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667911827220.png)]

3 . 使用代码操作sentinel:

依赖和相关引入我们再前面已经设置,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ukJTWkyV-1681790602353)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667912935207.png)]

代码:

public class RedisSentinelTest {



    //1. 在 创建一个新的类 ReidsSentinelTest
    //2. 构建JedisPoolConfig配置对象
    //3. 创建一个HashSet,用来保存哨兵节点配置信息(记得一定要写端口号)
    //4. 构建JedisSentinelPool连接池
    //5. 使用sentinelPool连接池获取连接
    JedisSentinelPool jedisSentinelPool;
    @BeforeTest
    public void befortest(){
        //创建jedis连接池
        JedisPoolConfig config = new JedisPoolConfig();
        //最大空闲连接:10个
        config.setMaxIdle(10);
        //最小空闲连接:5个
        config.setMinIdle(5);
        //设置超时时间(最大等待毫秒数):
        config.setMaxWaitMillis(3000);
        //最大的连接数量
        config.setMaxTotal(50);

        //设置三个哨兵
        Set<String> sentinels = new HashSet<>();
        sentinels.add("192.168.58.128:26379");
        sentinels.add("192.168.58.128:26380");
        sentinels.add("192.168.58.128:26381");
        jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels, config);
    }

    @Test
    public void keysTest(){
        Jedis jedis = jedisSentinelPool.getResource();
        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
        jedis.close();
    }


    @AfterTest
    public void aftertest(){
        jedisSentinelPool.close();
    }

运行后哦输出显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAm5YjtV-1681790602353)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667912994362.png)]

4 .哨兵工作原理:

哨兵在进行主从切换过程中经历三个阶段

  • 监控
  • 通知
  • 故障转移
#5.1 监控

用于同步各个节点的状态信息,哨兵连接主服务器后info指令查看相关信息,知道了从服务器,又去连接从服务器info查看状态,还要ping其他的哨兵看看是否都在线的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXkKqLtF-1681790606234)(null)]

  • 获取各个sentinel的状态(是否在线)
  • 获取master的状态
master属性
	prunid
	prole:master
各个slave的详细信息	
  • 获取所有slave的状态(根据master中的slave信息)
slave属性
	prunid
	prole:slave
	pmaster_host、master_port
	poffset

其内部的工作原理具体如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bdv9hjHq-1681790606328)(null)]

#5.2 通知

sentinel在通知阶段要不断的去获取master/slave的信息,然后在各个sentinel之间进行共享,具体的流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oN9R4rnm-1681790606389)(null)]

#5.3 故障转移

当master宕机后sentinel是如何知晓并判断出master是真的宕机了呢?我们来看具体的操作流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpsBns9D-1681790606506)(null)]

当sentinel认定master下线之后,此时需要决定更换master,那这件事由哪个sentinel来做呢?这时候sentinel之间要进行选举,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZByODvvI-1681790606577)(null)]

在选举的时候每一个人手里都有一票,而每一个人的又都想当这个处理事故的人,那怎么办?大家就开始抢,于是每个人都会发出一个指令,在内网里边告诉大家我要当选举人,比如说现在的sentinel1和sentinel4发出这个选举指令了,那么sentinel2既能接到sentinel1的也能接到sentinel4的,接到了他们的申请以后呢,sentinel2他就会把他的一票投给其中一方,投给谁呢?谁先过来我投给谁,假设sentinel1先过来,所以这个票就给到了sentinel1。那么给过去以后呢,现在sentinel1就拿到了一票,按照这样的一种形式,最终会有一个选举结果。对应的选举最终得票多的,那自然就成为了处理事故的人。需要注意在这个过程中有可能会存在失败的现象,就是一轮选举完没有选取,那就会接着进行第二轮第三轮直到完成选举。

接下来就是由选举胜出的sentinel去从slave中选一个新的master出来的工作,这个流程是什么样的呢?

首先它有一个在服务器列表中挑选备选master的原则

  • 不在线的OUT

  • 响应慢的OUT

  • 与原master断开时间久的OUT

  • 优先原则

    优先级 offset runid(选从和主的差距比较小的。都一样的就是用runid选)

选出新的master之后,发送指令( sentinel )给其他的slave:

  • 向新的master发送slaveof no one
  • 向其他slave发送slaveof 新masterIP端口

总结:故障转移阶段

  1. 发现问题,主观下线与客观下线
  2. 竞选负责人
  3. 优选新master
  4. 新master上任,其他slave切换master,原master作为slave故障恢复后连接

第六章 Redis cluster集群

引入:什么是集群cluster

在sentinel的架构中,因为一主多从的情况,主机只能存放32g数据,那么从机都是放着和主机一样的32g的数据,如果规模做大要存80g的数据,此时一主多从就满足不了要求了。所以我们需要集群模式来解决:

使用多主多从水平扩容的方式,每个主对应一个从,每个从和从之间,主和主直接是平等关系,这样当需要80g容量时,我们设置3主3从的结构,也就意味着我们有32*3=96g的内容可使用了,而且有3主3从的情况3主能分担更多的写操作(一个主从10w次读写,3个主从就是30w了),3从能分担更多的读操作了。这就是集群cluster。

如下图:三主一从

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ughoPN0-1681790602354)(C:\Users\陈刚\AppData\Roaming\Typora\typora-user-images\image-20221228223548432.png)]

集群作用:

  • 分散单台服务器的访问压力,实现负载均衡
  • 分散单台服务器的存储压力,实现可扩展性
  • 降低单台服务器宕机带来的业务灾难

但是现在又有问题,当数据来了后,我们的数据是要去哪个服务器存储呢?去7001?7002?还是7003?

一 . cluster集群结构设计:

数据存储设计:

  1. 通过算法设计,计算出key应该保存的位置

  2. 将所有的存储空间计划切割成16384份,每台主机保存一部分CRC16(KEY)散发算出一个值再去取模16384就会得到一个对应的槽位的值,通过该值找到对应的槽位。(槽位用来存储数据)

    注意:每份代表的是一个存储空间,不是一个key的保存空间

  3. 将key按照计算出的结果放到对应的存储空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJyOoOZW-1681790606691)(null)]

那redis的集群是如何增强可扩展性的呢?譬如我们要增加一个集群节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ha2MhDEe-1681790606780)(null)]

当我们查找数据时,集群是如何操作的呢?

各个数据库相互通信,保存各个库中槽的编号数据

CRC16(key)算出一个值再去取模16384会有两种情况

  • 一次命中,直接返回(取模的值在A服务器刚好找到对应槽位)
  • 一次未命中,告知具体位置(取模的值没在A服务器,在B服务器能找到该值,此时A会这次的查询请求转发给B,B会找到这个数据反馈给A,最后返回给客户端)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thgfjs7D-1681790606874)(null)]

Cluster集群结构搭建

首先要明确的几个要点:

  • 配置服务器(3主3从)
  • 建立通信(Meet)
  • 分槽(Slot)
  • 搭建主从(master-slave)

1 创建6个redis单体服务 7001-7006。修改redis.conf

port 7001
bind 192.168.200.131
protected-mode no
daemonize yes
pidfile /var/run/redis_7001.pid
logfile "/export/server/redis7001/log/redis.log"
dir /export/server/redis7001/data/
appendonly yes
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000

2让六台机器组成集群

redis-cli --cluster create 192.168.200.131:7001 192.168.200.131:7002 192.168.200.131:7003 192.168.200.131:7004 192.168.200.131:7005 192.168.200.131:7006 --cluster-replicas 1

1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKZxwKxn-1681790606930)(null)]

输入yes

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8FExDWB-1681790607019)(null)]

客户端连接cluster

./redis-cli -c -h 192.168.200.131 -p 7001

1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6xDbXm4q-1681790607073)(null)]

设置值,跳转到正确的服务器上。

二 . Cluster配置

  • 是否启用cluster,加入cluster节点
cluster-enabled yes|no
  • cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容
cluster-config-file filename
  • 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点
cluster-node-timeout milliseconds
  • master连接的slave最小数量
cluster-migration-barrier min_slave_number

Cluster节点操作命令

  • 查看集群节点信息
cluster nodes
  • 更改slave指向新的master
cluster replicate master-id
  • 发现一个新节点,新增master
cluster meet ip:port
  • 忽略一个没有solt的节点
cluster forget server_id
  • 手动故障转移
cluster failover

集群操作命令:

  • 创建集群
redis-cli –-cluster create masterhost1:masterport1 masterhost2:masterport2  masterhost3:masterport3 [masterhostn:masterportn …] slavehost1:slaveport1  slavehost2:slaveport2 slavehost3:slaveport3 -–cluster-replicas n

注意:master与slave的数量要匹配,一个master对应n个slave,由最后的参数n决定

master与slave的匹配顺序为第一个master与前n个slave分为一组,形成主从结构

  • 添加master到当前集群中,连接时可以指定任意现有节点地址与端口
redis-cli --cluster add-node new-master-host:new-master-port now-host:now-port
  • 添加slave
redis-cli --cluster add-node new-slave-host:new-slave-port master-host:master-port --cluster-slave --cluster-master-id masterid
  • 删除节点,如果删除的节点是master,必须保障其中没有槽slot
redis-cli --cluster del-node del-slave-host:del-slave-port del-slave-id
  • 重新分槽,分槽是从具有槽的master中划分一部分给其他master,过程中不创建新的槽
redis-cli --cluster reshard new-master-host:new-master:port --cluster-from src-  master-id1, src-master-id2, src-master-idn --cluster-to target-master-id --  cluster-slots slots

注意:将需要参与分槽的所有masterid不分先后顺序添加到参数中,使用,分隔

指定目标得到的槽的数量,所有的槽将平均从每个来源的master处获取

  • 重新分配槽,从具有槽的master中分配指定数量的槽到另一个master中,常用于清空指定master中的槽
redis-cli --cluster reshard src-master-host:src-master-port --cluster-from src-  master-id --clus

三 . 使用代码操作cluster:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jil69f8s-1681790602356)(C:\Users\CG\AppData\Roaming\Typora\typora-user-images\1667918835291.png)]

 public class RedisClusterTest {
    JedisCluster jedisCluster;

    @BeforeTest
    public void beforeTest(){
        //创建jedis连接池
        JedisPoolConfig config=new JedisPoolConfig();
        //最大空闲连接
        config.setMaxIdle(10);
        //最小空闲连接
        config.setMinIdle(5);
        //最大空闲时间
        config.setMaxWaitMillis(3000);
        //最大连接数
        config.setMaxTotal(50);

        Set<HostAndPort> nodes=new HashSet<>();
        nodes.add(new HostAndPort("192.168.200.131", 7001));
        nodes.add(new HostAndPort("192.168.200.131", 7002));
        nodes.add(new HostAndPort("192.168.200.131", 7003));
        nodes.add(new HostAndPort("192.168.200.131", 7004));
        nodes.add(new HostAndPort("192.168.200.131", 7005));
        nodes.add(new HostAndPort("192.168.200.131", 7006));

        jedisCluster= new JedisCluster(nodes,config);
    }

    @Test
    public void addTest(){
        jedisCluster.set("c", "d");
        String str = jedisCluster.get("c");
        System.out.println(str);
    }

    @AfterTest
    public void afterTest(){
        try {
            jedisCluster.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第六章 redis面试常问:缓存

1 . 缓存预热

当有大量用户访问数据时,后端接收后,不是直接去硬盘查询数据库的数据,因为直接从硬盘进行数据的变动速度太慢了,而是将硬盘数据库的数据拷贝到redis,用户的请求查询后,直接去缓存的redis查询就行了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WjKzgysJ-1681790602356)(C:\Users\陈刚\AppData\Roaming\Typora\typora-user-images\image-20221228223650926.png)]

如果数据量特别巨大,内存不够用就可以使用集群来水平扩容。

如果此时:服务器启动后迅速宕机,导致缓存的数据全部消失。产生两个问题:

(1)请求数量较高,大量的请求过来之后都需要去从缓存中获取数据,但是缓存中又没有,此时redis就要从数据库中查找数据然后将数据再存入redis缓存,造成了短期内对redis的高强度操作从而导致问题

(2)主从之间数据吞吐量较大,数据同步操作频度较高

此时我们就是用缓存预热

缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

2 . 缓存雪崩:短时间范围内,大量key集中过期

短时间内缓存中redis大量的key集中过期了,那么大量用户就只能直接去访问数据库的数据了,导致数据库的负荷骤然增大,可能数据库受不了就崩了。导致一连串的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRhVuIXD-1681790602356)(C:\Users\陈刚\AppData\Roaming\Typora\typora-user-images\image-20221228223740949.png)]

解决方案:

我们把每个数据的过期时间岔开,而不是都设置在同一时间,使他们不在同一时间点过期。这样就有效的防止缓存雪崩了。

热点数据不要设置过期时间,构建多级缓存架构,检测Mysql严重耗时业务进行优化等

3 . 缓存击穿

redis中某个key为热点数据,而大量用户访问该key时,key过期了。(比如双11特价新出的手机,此时大量用户访问这个数据,而redis中这个数据的key过期了)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UjTmZ9TW-1681790602356)(C:\Users\陈刚\AppData\Roaming\Typora\typora-user-images\image-20221228223843447.png)]

解决方案:

将热点数据不设置过期时间

将热点数据分布式存储,在缓存另开一块内存专门存储热点数据

4 . 缓存穿透:

大量的查询一个缓存和数据库都没有的值时,redis查不到,就会去数据库查(数据库也查不到,为null)。那么是不是也就意味着这些查询每次都会走redis,再走数据库,那么就是一直都在走数据库了,穿过了redis缓存后,走到数据库,类似redis就像透明的,没用的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElCRtch3-1681790602356)(C:\Users\陈刚\AppData\Roaming\Typora\typora-user-images\image-20221228223921666.png)]

解决方案:

redis查不到,去数据库也查不到,会返回一个null,把查询结果返回给redis存储。那么之后类似的查询都会从redis返回null结果。

设置白名单:设置一个网关,监控到访问的id多次异常查询为null时,将其纳入黑名单,禁止该ip访问。

key加密:对key每天进行加密,当访问不符合key的规则,驳回访问。

中分配指定数量的槽到另一个master中,常用于清空指定master中的槽

redis-cli --cluster reshard src-master-host:src-master-port --cluster-from src-  master-id --clus

三 . 使用代码操作cluster:

[外链图片转存中…(img-Jil69f8s-1681790602356)]

 public class RedisClusterTest {
    JedisCluster jedisCluster;

    @BeforeTest
    public void beforeTest(){
        //创建jedis连接池
        JedisPoolConfig config=new JedisPoolConfig();
        //最大空闲连接
        config.setMaxIdle(10);
        //最小空闲连接
        config.setMinIdle(5);
        //最大空闲时间
        config.setMaxWaitMillis(3000);
        //最大连接数
        config.setMaxTotal(50);

        Set<HostAndPort> nodes=new HashSet<>();
        nodes.add(new HostAndPort("192.168.200.131", 7001));
        nodes.add(new HostAndPort("192.168.200.131", 7002));
        nodes.add(new HostAndPort("192.168.200.131", 7003));
        nodes.add(new HostAndPort("192.168.200.131", 7004));
        nodes.add(new HostAndPort("192.168.200.131", 7005));
        nodes.add(new HostAndPort("192.168.200.131", 7006));

        jedisCluster= new JedisCluster(nodes,config);
    }

    @Test
    public void addTest(){
        jedisCluster.set("c", "d");
        String str = jedisCluster.get("c");
        System.out.println(str);
    }

    @AfterTest
    public void afterTest(){
        try {
            jedisCluster.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第六章 redis面试常问:缓存

1 . 缓存预热

当有大量用户访问数据时,后端接收后,不是直接去硬盘查询数据库的数据,因为直接从硬盘进行数据的变动速度太慢了,而是将硬盘数据库的数据拷贝到redis,用户的请求查询后,直接去缓存的redis查询就行了。

[外链图片转存中…(img-WjKzgysJ-1681790602356)]

如果数据量特别巨大,内存不够用就可以使用集群来水平扩容。

如果此时:服务器启动后迅速宕机,导致缓存的数据全部消失。产生两个问题:

(1)请求数量较高,大量的请求过来之后都需要去从缓存中获取数据,但是缓存中又没有,此时redis就要从数据库中查找数据然后将数据再存入redis缓存,造成了短期内对redis的高强度操作从而导致问题

(2)主从之间数据吞吐量较大,数据同步操作频度较高

此时我们就是用缓存预热

缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

2 . 缓存雪崩:短时间范围内,大量key集中过期

短时间内缓存中redis大量的key集中过期了,那么大量用户就只能直接去访问数据库的数据了,导致数据库的负荷骤然增大,可能数据库受不了就崩了。导致一连串的问题。

[外链图片转存中…(img-hRhVuIXD-1681790602356)]

解决方案:

我们把每个数据的过期时间岔开,而不是都设置在同一时间,使他们不在同一时间点过期。这样就有效的防止缓存雪崩了。

热点数据不要设置过期时间,构建多级缓存架构,检测Mysql严重耗时业务进行优化等

3 . 缓存击穿

redis中某个key为热点数据,而大量用户访问该key时,key过期了。(比如双11特价新出的手机,此时大量用户访问这个数据,而redis中这个数据的key过期了)

[外链图片转存中…(img-UjTmZ9TW-1681790602356)]

解决方案:

将热点数据不设置过期时间

将热点数据分布式存储,在缓存另开一块内存专门存储热点数据

4 . 缓存穿透:

大量的查询一个缓存和数据库都没有的值时,redis查不到,就会去数据库查(数据库也查不到,为null)。那么是不是也就意味着这些查询每次都会走redis,再走数据库,那么就是一直都在走数据库了,穿过了redis缓存后,走到数据库,类似redis就像透明的,没用的。

[外链图片转存中…(img-ElCRtch3-1681790602356)]

解决方案:

redis查不到,去数据库也查不到,会返回一个null,把查询结果返回给redis存储。那么之后类似的查询都会从redis返回null结果。

设置白名单:设置一个网关,监控到访问的id多次异常查询为null时,将其纳入黑名单,禁止该ip访问。

key加密:对key每天进行加密,当访问不符合key的规则,驳回访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于学习Redis基础知识,可以按照以下思路进行学习: 1. 了解Redis的概念和特点:首先需要了解Redis是什么,它的主要特点是什么,它为什么被广泛应用于缓存、消息队列、会话管理等场景。 2. 安装和配置Redis:根据你的操作系统,安装Redis并进行相关配置。可以参考Redis官方文档或其他教程来完成这一步。 3. 学习Redis的数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合和有序集合等。了解每种数据结构的特点、用途和操作命令,并通过实际操作来加深理解。 4. 掌握Redis的常用命令:学习Redis的常用命令,如get、set、hget、hset、lpush、lrange、sadd、smembers等,了解每个命令的具体用法和参数含义。 5. 理解Redis的持久化机制:了解Redis的RDB和AOF两种持久化方式,以及它们的优缺点。学习如何进行备份和恢复数据。 6. 学习Redis的事务和Lua脚本:了解Redis事务的基本概念和使用方法,以及如何使用Lua脚本来进行复杂的操作。 7. 深入了解Redis的性能优化和高可用方案:学习如何优化Redis的性能,包括配置调优、使用合适的数据结构、合理地使用缓存等。同时了解Redis的高可用方案,如主从复制、哨兵模式和集群模式。 8. 学习Redis与其他技术的结合:了解Redis如何与其他技术进行结合,如与Python、Java等编程语言的配合使用,以及与Spring、Django等框架的整合。 以上是学习Redis基础知识的一个思路,你可以根据自己的实际情况和需求进行学习和拓展。推荐参考一些经典的Redis教程和实战案例,通过实际操作和项目实践来提升自己的技能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值