分布式缓存

单线程的 Redis 如何实现高性能读写?(redis为什么快?)

redis脑裂解决方案

概念
redis的集群脑裂是指因为网络问题,导致redis master节点跟redis slave节点和sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。此时存在两个不同的master节点,就像一个大脑分裂成了两个。
解决
redis的配置文件中,存在两个参数

min-slaves-to-write 3
min-slaves-max-lag 10

第一个参数表示连接到master的最少slave数量
第二个参数表示slave连接到master的最大延迟时间
按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失。

参考

redis的失效策略?

通过
定期删除:每隔100ms抽样检查有效性(可能会漏,所以必须结合惰性)
惰性删除:访问key的时候判断是否失效。
内存淘汰机制:redis.cnf 中配置 maxmemory-policy [策略]
volatile-ttl:针对设置了expire的key(server.db[i].expires),挑选将要过期的
volatile-random:针对设置了expire的key(server.db[i].expires),随机挑选
volatile-lru:针对设置了expire的key(server.db[i].expires),挑选最近最少使用的
all-keys-lru:针对数据集(server.db[i].dict),挑选最近最少使用的
all-keys-random:针对数据集(server.db[i].dict),随机挑选
no-enviction:此时写入会报错

redis中的事务

概念:是一组命令的集合,redis最小执行单位。
非原子性(不支持回滚):如果命令出现问题会继续执行,不支持回滚保证简单快速。
事务相关指令:

MULTI : 开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列。
EXEC: 执行事务中的所有操作命令。
DISCARD: 取消事务,放弃执行事务块中的所有命令。
WATCH: 监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
UNWATCH: 取消WATCH对所有key的监视。

Redis 如何实现延时队列?

使用zset数据格式,客户端间隔使用命令
zrangebysocre key min max withscores limit 0 1

Redis 和 Memcached 有哪些区别?

redis Memcached
存储类型 k-v、list、set、hash k-v、 图片、视频
限制 单个value512m 单个value1m
策略 物理内存用完后可将部分kv交换至磁盘,定期+惰性删除 惰性删除
高可用 哨兵机制,原生集群方式
扩展性
可靠性 rdb,aof,启动恢复

缓存穿透

请求每次会查不到缓存最后去走库,从而对数据库造成压力
可能原因:
请求伪造;

解决方案:
代码过滤,布隆过滤器;
空数据也保存到缓存,但过时时间设置短一点;

缓存击穿

因缓存中没有数据,并发请求时最后都去读库,瞬间压垮db
可能原因:
某个时间点高频访问;

解决方案:
永不超时,若数据超时读取旧版本,此时异步请求数据库写入缓存;
读不到数据时用setnx加互斥锁读db,其它拿不到锁的请求sleep等待;

缓存雪崩

因大量缓存数据过期,大量请求并发查不同数据导致数据库压力大,
可能原因:
缓存服务器重启,导致集体失效

解决方案:
如果缓存服务器没有问题:
设置不同超时时间,可设随机值;
分布式锁;
读取旧数据版本;

如果缓存服务器有问题:
缓存预热;

穿透、击穿、雪崩区别

共同点:都绕过了缓存对数据库造成了压力
不同点:规模不一样,原因不一样

缓存预热

提前把数据加载到缓存中。

方案
启动加载;
定时刷新;
手动刷新,开后台;

如何保证缓存与数据库的双写一致性?

如果先更新缓存或是先更新数据库都不行,会出现不一致。

方案一 读写不分离
先删除缓存,再更新库,读请求会在没有缓存时去加载,可能会加载到旧数据(脏数据);
 解决:
 1. 先删缓存,再更新库,再更新缓存。(可能读请求会读取并写入脏数据)
 2. 写线程加互斥锁先删缓存,再更新库,再更新缓存,解锁。读取不到缓存数据判断是否加了互斥锁(写锁),若无锁则进行读库写缓存的逻辑,否则循环等待。(写线程可能异常中断,若不对互斥锁进行失效会导致读请求死循环;另外并发读请求可能会阻塞得很严重)(也可不用锁用队列实现,只要能达到读在所有的写后面执行就行)

方案二 读写分离
读只读缓存,写保证写数据库和缓存强一致(缓存失败会回滚库操作)(可能用到2pc)

可靠性

redis持久化分为两种机制,分别是RDB与AOF
参考

RDB

原理:fork一个子进程,在指定时间内将全部数据写入磁盘,每次写入会覆盖上一次的文件。
策略
redis.cnf中定义rdb条件——
save 900 1 # 900秒(15分钟)内有1个写入
save 300 10 # 300秒(5分钟)内有10个写入
save 60 10000 # 60秒(1分钟)内有10000个写入
多个条件可以组合使用,只要上面一个条件满足,我就会去进行备份。
读取
启动时候会读取,恢复数据状态,恢复时速度快;
相关命令
BGSAVE
优点
文件格式紧凑;恢复数据快;
缺点
fork非常耗时;

AOF (Append Only File)

在这里插入图片描述
原理:以日志形式记录所有的写指令,可以找到redis配置文件下图位置,进行开启,并指定记录文件名,系统默认是appendonly.aof,也可以自行修改。
image-20211025220225378
策略
你可以通过修改配置文件来打开 AOF 功能:
appendonly yes

由于操作系统也有缓存区,需业务方设置参数决定什么时候写到磁盘,这个涉及到性能问题。

appendfsync参数,三个取值:

always: 每个事件周期都同步刷新一次
everysec: 每一秒都同步刷新一次(默认)
no: 我只管写,让操作系统自己决定什么时候真正写入吧

重写机制
本质是压缩,去掉了一些中间状态,保留key最后结果,也是fork出一个子进程去做,但是才重写期间会把新的写请求写到重写缓冲区中。
在这里插入图片描述
读取
启动时候会优先读取,恢复数据状态;恢复时数据更完整;有可能因为文件格式出错被拒绝载入,可用自带工具修复——
$ redis-check-aof --fix
相关命令
BGREWRITEAOF
优点
默认策略下最多丢1s的数据;
缺点
文件体积小;海量数据下恢复可能有bug;

高可用

主从复制怎么实现?

参考
(第一次slaveof命令)初次:psync -1 master进行bgsave,发送rdb给slave,然后再发送缓冲区的写命令;
重连:psync runid offset,若runid不是master服务器id,进行完整同步;若偏移量之后的数据存在缓冲区,发送这部分数据;

sentinel有什么作用?

官方推荐的HA高可用解决方案,本身是一个独立的进程,可以监控多个master-slave集群,发现故障后能够自动切换。
– 状态监控:检查节点状态,当某个节点出现状况时,通知另外进程,比如它的客户端
– 自动切换master:当一个master不可用时,会从多个slave中选出一个作为master,并把其它slave节点指向该节点。

sentinel怎么解决自身单点问题

sentinel支持集群部署。
client可以链接任何一个Sentinel服务所获的结果都是一致的;

sentinel故障转移过程

img
主观下线:sentinel集群通过心跳检测,超时
客观下线:超过一定数量quorum认为该节点主观下线,如果下线的是redis从节点或者sentinel节点,没有后续操作
选主:raft协议选取sentinel leader 再由leader主导故障迁移
故障迁移:选master(优先级,偏移量最大,runId最小),从的master变更,故障的master变更

所有的Sentinel服务都会对Redis的主从服务进行监控,当监控到Master服务无响应的时候,Sentinel内部进行仲裁,从所有的 Slave选举出一个做为新的Master。并且把其他的slave作为新的Master的Slave。
最后通知所有的客户端新的Master服务地址。如果旧的Master服务地址重新启动,这个时候,它将被设置为Slave服务。

检测下线通过定时器来实现的,它拥有如下几个定时器——
10s:对master和从节点执行info,确认
2s:sentinel交换对master的判断信息
1s:ping,心跳,检测下线

使用

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.5.0</version>
</dependency>
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.exceptions.JedisConnectionException;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

        //redis 连接池配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(MAX_IDLE);
        poolConfig.setMaxTotal(MAX_TOTAL);
        poolConfig.setMinIdle(MIN_IDLE);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(true);
        Set<String> hosts = new HashSet<String>(Arrays.asList(redisHosts.split(";")));
        if (StringUtils.isBlank(password)) {
            JedisSentinelPool pool = new JedisSentinelPool(redisMaster, hosts, poolConfig);
        } else {
            JedisSentinelPool pool = new JedisSentinelPool(redisMaster, hosts, poolConfig, password);
        }



    public String get(String key) throws JedisConnectionException {
        Jedis jedis = pool.getResource();
        try {
            return jedis.get(key);
        } catch (JedisConnectionException e) {
            throw e;
        } finally {
            jedis.close();
        }
    }


高扩展

Redis Cluster

redis3.0引入的分布式解决方案,该方案通过一致性hash达到了负载均衡的目标。

槽slot:对应了一致性hash中的虚拟节点,范围为0~16383,所有的key通过公式slot = CRC16(key)&16383 映射到该范围内。
物理节点:维护一部分槽和槽所映射的键值对。

image-20211025213655450

Redis Cluster 使用

配置同sentinel
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.5.0</version>
</dependency>
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.exceptions.JedisConnectionException;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;


  GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(MAX_IDLE);
        poolConfig.setMaxTotal(MAX_TOTAL);
        poolConfig.setMinIdle(MIN_IDLE);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(true);

String redisHosts = "127.0.0.1:6378;127.0.0.1:6379;127.0.0.1:6380"; //如:127.0.0.1:26379;127.0.0.1:26378
        //Redis Cluster 初始化
        Set<String> hosts = new HashSet<String>(Arrays.asList(redisHosts.split(";")));
        Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
        for (String host : hosts) {
            HostAndPort hostAndPort = new HostAndPort(host.split(":")[0], Integer.parseInt(host.split(":")[1]));
            nodes.add(hostAndPort);
        }

        if (StringUtils.isBlank(password)) {
            JedisCluster  jedisCluster = new JedisCluster(nodes, CONNECT_TIMEOUT, SO_TIMEOUT, MAX_ATTEMPTS, poolConfig);
        } else {
            JedisCluster  jedisCluster = new JedisCluster(nodes, CONNECT_TIMEOUT, SO_TIMEOUT, MAX_ATTEMPTS, password, poolConfig);
        }


    public String get(String key) throws JedisConnectionException {
        try {
            return jedisCluster.get(key);
        } catch (JedisConnectionException e) {
            throw e;
        }
    }

Codis

代理中间件,实现了redis协议,客户端访问它跟直接访问redis没什么两样(就像 Twemproxy),可以简单地认为它是一个无限大的redis。

组成部分
codis-proxy 是客户端连接的 Redis 代理服务, codis-proxy 本身实现了 Redis 协议, 表现得和一个原生的 Redis 没什么区别 (就像 Twemproxy), 对于一个业务来说, 可以部署多个 codis-proxy, codis-proxy 本身是无状态的.

codis-config 是 Codis 的管理工具, 支持包括, 添加/删除 Redis 节点, 添加/删除 Proxy 节点, 发起数据迁移等操作. codis-config 本身还自带了一个 http server, 会启动一个 dashboard, 用户可以直接在浏览器上观察 Codis 集群的运行状态.

codis-server 是 Codis 项目维护的一个 Redis 分支, 基于 2.8.13 开发, 加入了 slot 的支持和原子的数据迁移指令. Codis 上层的 codis-proxy 和 codis-config 只能和这个版本的 Redis 交互才能正常运行.

Codis 依赖 ZooKeeper 来存放数据路由表和 codis-proxy 节点的元信息, codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy.
Codis 支持按照 Namespace 区分不同的产品, 拥有不同的 product name 的产品, 各项配置都不会冲突.

Twemprox

Twemprox 是 Twtter 开源的一个 Redis 和 Memcached 代理服务器,主要用于管理 Redis 和Memcached 集群,减少与Cache 服务器直接连接的数量。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注