关于java:面试题之java缓存总结从单机缓存到分布式缓存架构

8次阅读

共计 14271 个字符,预计需要花费 36 分钟才能阅读完成。

1、缓存定义

高速数据存储层,进步程序性能

2、为什么要用缓存(读多写少,高并发)

1、进步读取吞吐量

2、晋升应用程序性能

3、升高数据库老本

4、缩小后端负载

5、打消数据库热点

6、可预测的性能

3、缓存分类

3.1、单机缓存(localCache)

实现计划

1、基于 JSR107 标准自研(理解即可):

1、Java Caching 定义了 5 个外围接口,别离是 CachingProvider, CacheManager, Cache, Entry 和 Expiry。
2、CachingProvider 定义了创立、配置、获取、治理和管制多个 CacheManager。一个利用能够在运行期拜访多个 CachingProvider。
3、CacheManager 定义了创立、配置、获取、治理和管制多个惟一命名的 Cache,这些 Cache 存在于 CacheManager 的上下文中。一个 CacheManager 仅被一个 CachingProvider 所领有。
4、Cache 是一个相似 Map 的数据结构并长期存储以 Key 为索引的值。一个 Cache 仅被一个 CacheManager 所领有。
5、Entry 是一个存储在 Cache 中的 key-value 对。
每一个存储在 Cache 中的条目有一个定义的有效期,即 Expiry Duration。
一旦超过这个工夫,条目为过期的状态。一旦过期,条目将不可拜访、更新和删除。缓存有效期能够通过 ExpiryPolicy 设置。

2、基于 ConcurrentHashMap 实现数据缓存

3.2、分布式缓存(redis、Memcached)

4、单机缓存

1、本人实现一个单机缓存

创立缓存类

/**
 * @author yinfeng
 * @description 本地缓存实现:用 map 实现一个简略的缓存性能
 * @since 2022/2/8 13:54
 */
public class MapCacheDemo {

    /**
     * 在构造函数中,创立了一个守护程序线程,每 5 秒扫描一次并清理过期的对象
     */
    private static final int CLEAN_UP_PERIOD_IN_SEC = 5;

    /**
     * ConcurrentHashMap 保障线程平安的要求
     * SoftReference <Object>  作为映射值,因为软援用能够保障在抛出 OutOfMemory 之前,如果短少内存,将删除援用的对象。*/
    private final ConcurrentHashMap<String, SoftReference<CacheObject>> cache = new ConcurrentHashMap<>();

    public MapCacheDemo() {
        // 创立了一个守护程序线程,每 5 秒扫描一次并清理过期的对象
        Thread cleanerThread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {
                try {Thread.sleep(CLEAN_UP_PERIOD_IN_SEC * 1000);
                    cache.entrySet().removeIf(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(CacheObject::isExpired).orElse(false));
                } catch (InterruptedException e) {Thread.currentThread().interrupt();}
            }
        });
        cleanerThread.setDaemon(true);
        cleanerThread.start();}

    public void add(String key, Object value, long periodInMillis) {if (key == null) {return;}
        if (value == null) {cache.remove(key);
        } else {long expiryTime = System.currentTimeMillis() + periodInMillis;
            cache.put(key, new SoftReference<>(new CacheObject(value, expiryTime)));
        }
    }

    public void remove(String key) {cache.remove(key);
    }

    public Object get(String key) {return Optional.ofNullable(cache.get(key)).map(SoftReference::get).filter(cacheObject -> !cacheObject.isExpired()).map(CacheObject::getValue).orElse(null);
    }

    public void clear() {cache.clear();
    }

    public long size() {return cache.entrySet().stream().filter(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(cacheObject -> !cacheObject.isExpired()).orElse(false)).count();}

    /**
     * 缓存对象 value
     */
    private static class CacheObject {
        private Object value;
        private final long expiryTime;

        private CacheObject(Object value, long expiryTime) {
            this.value = value;
            this.expiryTime = expiryTime;
        }

        boolean isExpired() {return System.currentTimeMillis() > expiryTime;
        }

        public Object getValue() {return value;}

        public void setValue(Object value) {this.value = value;}
    }

  
}

写个 main 办法测试一下

public static void main(String[] args) throws InterruptedException {MapCacheDemo mapCacheDemo = new MapCacheDemo();
        mapCacheDemo.add("uid_10001", "{1}", 5 * 1000);
        mapCacheDemo.add("uid_10002", "{2}", 5 * 1000);
        System.out.println("从缓存中取出值:" + mapCacheDemo.get("uid_10001"));
        Thread.sleep(5000L);
        System.out.println("5 秒钟过后");
        // 5 秒后数据主动革除了
        System.out.println("从缓存中取出值:" + mapCacheDemo.get("uid_10001"));
    }

2、谷歌 guava cache 缓存框架

2.1、简介

Guava Cache 是一个内存缓存模块,用于将数据缓存到 jvm 内存中,是单个利用运行时的本地缓存,他不将数据放到文件或内部服务器。

2.2 简略应用

/**
 * @author yinfeng
 * @description guava 测试,https://github.com/google/guava
 * @since 2022/2/8 14:13
 */
public class GuavaCacheDemo {public static void main(String[] args) throws ExecutionException {
        // 缓存接口这里是 LoadingCache,LoadingCache 在缓存项不存在时能够主动加载缓存
        LoadingCache<String, User> userCache
                //CacheBuilder 的构造函数是公有的,只能通过其静态方法 newBuilder()来取得 CacheBuilder 的实例
                = CacheBuilder.newBuilder()
                // 设置并发级别为 8,并发级别是指能够同时写缓存的线程数
                .concurrencyLevel(8)
                // 设置写缓存后 8 秒钟过期
                .expireAfterWrite(8, TimeUnit.SECONDS)
                // 设置写缓存后 1 秒钟刷新
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                // 设置缓存容器的初始容量为 10
                .initialCapacity(10)
                // 设置缓存最大容量为 100,超过 100 之后就会依照 LRU 最近虽少应用算法来移除缓存项
                .maximumSize(100)
                // 设置要统计缓存的命中率
                .recordStats()
                // 设置缓存的移除告诉
                .removalListener(notification -> System.out.println(notification.getKey() + "被移除了,起因:" + notification.getCause()))
                //build 办法中能够指定 CacheLoader,在缓存不存在时通过 CacheLoader 的实现主动加载缓存
                .build(new CacheLoader<String, User>() {
                            @Override
                            public User load(String key) {System.out.println("缓存没有时,从数据库加载" + key);
                                // TODO jdbc 的代码~~ 疏忽掉
                                return new User("yinfeng" + key, key);
                            }
                        }
                );
        // 第一次读取
        for (int i = 0; i < 20; i++) {User user = userCache.get("uid" + i);
            System.out.println(user);
        }
        // 第二次读取
        for (int i = 0; i < 20; i++) {User user = userCache.get("uid" + i);
            System.out.println(user);
        }
        System.out.println("cache stats:");
        // 最初打印缓存的命中率等 状况
        System.out.println(userCache.stats().toString());
    }

    @Data
    @AllArgsConstructor
    public static class User implements Serializable {
        private String userName;
        private String userId;
        @Override
        public String toString() {return userId + "---" + userName;}
    }
}

5、分布式缓存

5.1 redis

5.1.1 介绍

Redis 是一个开源的应用 C 语言缩写、反对网络、可基于内存亦可长久化的日志型,Key-Value 数据库,并提供多种语言的 API。

实质是客户端 - 服务端应用软件程序。

特点是应用简略, 性能强悍, 性能利用场景丰盛。

5.1.2通用命令

命令 作用
DEL key 用于在 key 存在是删除 key
DUMP key 序列化给定的 key,并返回给定的值
EXISTS key 查看给定 key 是否存在
EXPIRE key seconds 为给定 key 设置过期工夫,单位秒
TTL key 以秒为单位,返回给定 key 的残余生存工夫
TYPE key 返回 key 所存储的值的类型

5.1.3 数据结构

1. String

定义

String 数据结构是简略的 key-value 类型,value 其实不仅是 String,也能够是数字。

应用场景:微博数,粉丝数(惯例计数)

常用命令

命令 作用
Get 获取指定 key 的值
Set 设置指定 key 的值
Incr 将 key 中贮存的数字值增一
Decr 将 key 中贮存的数字值减一
Mget 获取所有 (一个或多个) 给定 key 的值

2. List

定义

List 就是链表,依赖于链表构造

应用场景:微博的关注列表,粉丝列表

常用命令

命令 作用
Lpush 将一个或多个值插入到列表头部
Rpush 在列表中增加一个或多个值
Lpop 移出并获取列表的第一个元素
Rpop 移除列表的最初一个元素, 返回值为移除的元素
Lrange 获取所有 (一个或多个) 给定 key 的值

3. Set

定义

Set 就是一个汇合,汇合的概念就是一堆不反复值的组合。利用 Reds 提供的 Set 数据结构,能够存储一些汇合性的数据。

应用场景:实现如独特关注,独特爱好,二度好友

常用命令

命令 作用
Lpush 向汇合中增加一个或多个成员
Rpush 移除并返回汇合中的一个随机元素
Lpop 返回汇合中的所有成员
Rpop 返回所有给定汇合的并集

4. Sorted set

定义

Sorted set 的应用场景与 set 相似,区别是 set 不是主动有序的,而 sorted set 能够通过用户额定提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即主动排序。

应用场景:排行榜、依照用户投票和工夫排序

常用命令

命令 作用
Zadd 向有序汇合增加一个或多个成员, 或者更新已存在成员的分数
Zrange 通过索引区间返回有序汇合中指定区间内的成员
Zrem 移除有序汇合中的一个或多个成员
Zcard 获取有序汇合的成员数

5. Hash

定义

Hash 是一个 sting 类型的 field 和 value 的映射表

应用场景:存储局部变更数据,如用户信息

常用命令

命令 作用
Zadd 获取存储在哈希表中指定字段的值
Zrange 将哈希表 key 中的字段 field 的值设为 value
Hgetall 获取在哈希表中指定 key 的所有字段和值

6. GEO

定义

GEO3.2 版本开始对 GEO(地理位置)的反对

应用场景:LBS 利用开发

常用命令

命令 作用
GEOADD 减少地理位置的坐标, 能够批量增加地理位置
GEODIST 获取两个地理位置的间隔
GEOHASH 获取某个地理位置的 geohash 值
GEOPOS 获取指定地位的坐标, 能够批量获取多个地理位置的坐标
GEORADIUS 依据给定地理位置坐标获取指定范畴内的地理位置汇合(留神: 该命令的中心点由输出的经度和结度决定)
GEORADIUSBYMEMBER 依据给定成员的地位获取指定范畴内的地位信息汇合(留神: 该命令的中心点足由给定的地位元素决定)

7. Stream

定义

Stream5.0 版本开始的新构造“流”

应用场景: 消费者生产者场景(相似 MO)

常用命令

命令 作用
XADD 减少地理位置的坐标, 能够批量增加地理位置
XLEN stream 流中的音讯数量
XDEL 删除流中的音讯
XRANGE 返回流中满足给定 ID 范畴的音讯
XREAD 从一个或者多个流中读取音讯
XINFO 检索对于流和关联的消费者组的不同的信息

5.1.4 长久化机制

1. 介绍

redis 的数据都寄存在内存中,如果没有配置长久化,重启后数据就全失落,于是须要开启 redis 的长久化性能,将数据保留到磁盘上,当 redis 重启后, 能够从磁盘中复原数据

2. 长久化形式

  1. RDB 长久化:RDB 长久化形式可能在指定的工夫距离对你的数据进行快照存储
  2. AOF 长久化:AOF 长久化形式记录每次对服务器写的操作, 当服务器重后的时候会从新执行这些命令来复原原始的数据

3.RDB 形式

客户端间接通过命令 BGSAVE 或者 SAVE 来创立一个内存快照:

  1. BGSAVE 调用 fork 来创立一个子过程,子过程负责将快照写入磁盘, 而父过程依然持续解决命令。
  2. SAVE 执行 SAVE 命令过程中, 不再响应其余命令。

在 redis.conf 中调整 save 配置选项,当在规定的工夫内,redis 产生了写操作的个数满足条件会触发 BGSAVE 命令

#900 秒之内至多一次写操作
save 900 1
#300 秒之内至多产生 10 次写操作
save 300 10

优缺点

长处 毛病
对性能影响最小 同步时失落数据
RDB 文件进行数据恢复比应用 AOF 要快很多 如果数据集十分大且 CPU 不够强(比方单核 CPU),Redis 在 fork 子过程时可能会耗费绝对较长的工夫, 影响 RediS 对外提供服务的能力

4. AOF 长久化形式

开启 AOF 长久化

appendonty yes

AOF 策略调整

# 每次有数据批改产生时都会写入 AOF 文件
appendfsync always
#每秒钟同步一次, 该策略为 AOF 的默认策略
appendfsync everysec
#从不同步。高效然而数据不会被长久化
appendfsync no

长处

长处 毛病
最平安 文件体积大
容灾 性能耗费比 RDB 高
易读, 可批改 数据恢复速度比 RDB 慢

5.1.5 内存治理

1、内存调配

不同数据类型的大小限度:

Strings 类型:一个 Strings 类型的 Value 最大能够存储 512M。

Lists 类型:list 的元素个数最多为 2^32- 1 个

Sets 类型:元素个数最多为 2^32- 1 个

Hashes 类型:键值对个数最多为 2^32- 1 个

最大内存管制:

maxmemory 最大内存阈值

maxmemory-policy 达到阈值的执行策略

2、内存压缩

# 配置字段最多 512 个
hash-max-zipmap-entries 512
#配置 value 最大为 64 字节
hash-max-zipmap-value 64
#配置元素个数最多 512 个
lst-max-zipmap-entries 512
#配置 value 最大为 64 字节
list-max-zipmap-value 64
#配置元素个数最多 512 个
set-max-zipmap-entries 512

大小超出压缩范畴,溢出后 redis 将主动将其转换为失常大小

3、过期数据的解决策略

被动解决 (redis 被动触发检测 key 足否过期) 每秒抗行 10 次。过程如下:

  1. 从具备相干过期的 key 汇合中测试 20 个随机 key
  2. 删除找到的所有已过期 key
  3. 如果超过 25% 的 key 已过期, 请从步骤 1 从新开始

被动解决:

  1. 每次拜访 key 的时候,发现超时后被动过期,清理掉

数据恢复阶段过期数据的解决策略:

RDB 形式:过期的 Key 不会被长久化到文件中。载入时过期的 key,会通过 redis 的被动和被动形式清理掉。

AOF 形式:每次遇到过期的 key,redis 会追加一条 DEL 命令到 AOF 文件,也就是说只有咱们程序载入执行 AOF 命令文件就会删除过期的 key

留神:过期数据的计算和计算机本身的工夫是有间接分割的!

Redis 内存回收策略:

配置文件中设置:maxmemory-poIicy noeviction

命令动静调整:config set maxmemory-policy noeviction

回收策略 阐明
noeviction 客户端尝试执行会让更多内存被应用的命令间接报错
allkeys-lru 在所有 key 里执行 LRU 算法革除
volatile-lru 在所有曾经过期的 key 里执行 LRU 算法革除
allkeys-lfu 在所有 key 里执行 LFU 算法革除
volatile-lfu 在所有曾经过期的 key 里执行 LFU 算法革除
allkeys-random 在所有 key 里随机回收
volatile-random 在曾经过期的 key 里随机回收
volatile-ttl 回收曾经过期的 key,并且优先回收存活工夫 (TTL) 较短的 key

4、LRU 算法

LRU(最近起码应用):依据数据的历史拜访记录来进行沟汰数据

核心思想:如果数据最近被拜访过,那么未来被拜访的几率也更高。

留神:Redis 的 LRU 算法并非残缺的实现,残缺的 LRU 实现须要太多的内存。

办法:通过对大量 keys 进行取样(50%),而后回收其中一个最好的 key。

配置形式:maxmemory-samples 5

5、LFU 算法

LFU:依据数据的历史拜访频率来沟汰数据

核心思想:如果数据过来被拜访屡次, 那么未来被拜访的频率也更高。

启用 LFU 算法后,能够应用热点数据分析性能。

5.1.6 主从复制

1、介绍

为什么要主从复制

redis-server 单点故障

单节点 QPS 无限

利用场景剖析

读写拆散场景,躲避 redis 单机瓶颈

故障切换,master 出问题后还有 slave 节点能够应用

2、搭建主从复制

主 Redis Server 以一般模式启动,次要是启动从服务器的形式

  1. 命令行

    # 连贯须要实现从节点的 rediS, 执行上面的命令
    slaveof [ip] [port]
  1. redis.conf 配置文件

    # 配置文件中减少
    slaveof [ip] [port]
    #从服务器是否只读(默认 yes)
    slave-read-only yes
  2. 退出主从集群的形式

    slaveof no one

3、查看主从复制

#redis 客户端执行
info replication

4、主从复制流程

  1. 从服务器通过 psync 命令发送服务器已有的同步进度(同步源 ID,同步进度 offset)
  2. master 收到申请,同步源为以后 master,则依据偏移量增量同步
  3. 同步源非以后 master,则进入全量同步:maser 生成 rdb,传输到 slave,加载到 slave 内存

5、主从复制外围常识

  1. Redis 默认应用异步复制,slave 和 master 之间异步地确认解决的数据量
  2. 一个 master 能够领有多个 slave
  3. Slave 能够承受其余 slave 的连贯。slave 能够有上级 sub slave
  4. 主从同步过程在 master 侧是非阻塞的
  5. slave 首次同步须要删除旧数据,加载新数据,会阻塞到来的连贯申请

6、利用场景

  1. 主从复制能够用来反对读写拆散
  2. slave 服务器设定为只读,能够用在数据安全的场景下。
  3. 能够应用主从复制来防止 master 长久化造成的开销。master 敞开长久化,slave 设置为不定期保留或开启 AOF
  4. 留神:重新启动的 master 程序将从一个空数据集开始, 如果一个 slave 试图与它同步,那么这个 slave 也会被清空。

7、注意事项

  1. 读写拆散场景:

    数据复制延时导致读到过期数据或者读不到数据(网络起因,slave 阻塞)

    从节点故障(多个 client 如何迁徙)

  2. 全量复制状况下:

    第一次建设主从关系或者 runid 不匹配会导致全量复制

    故障转移的时候也会呈现全量复制

  3. 复制风暴:

    master 故障重启,如果 slave 节点过多,所有 slave 都要复制,对服务器的性能,网络的压力都有很大影响。

    如果一个机器部署了多个 master

  4. 写能力无限

    主从复制还是只有一台 master,提供的写服务能力无限

  5. master 故障状况下:

    如果是 mater 无长久化,Slave 开启长久化来保留数据的场展,倡议不要配置 redis 主动重启。

    启动 redis 主动重启,master 启动后,无备份数据,可能导致集群数据失落的状况

  6. 带有效期的 key:

    Slave 不会让 key 过期,而是期待 master 让 key 过期

    在 Lua 脚本执行期间,不执行任何 key 过期操作

5.1.7 哨兵模式

1、哨兵 (Sentinel) 机制核心作用

客户端 == 询问主 redis 地址 ==> redis 哨兵 == 监控、揭示、故障转移(主从切换)==> 主 redis(master)== 主从复制关系 ==> 从 redis(slave)

2、外围运作流程

服务发现和健康检查流程

搭建 redis 主从集群 ==> 启动哨兵(客户端通过哨兵发现 Redis 实例信息) ==> 哨兵通过连贯 master 发现主从集群内的所有实例信息 ==> 哨兵监控 redis 实例的健康状况

故障切换流程

哨兵一旦发现 master 不能失常提供服务,则告诉给其余哨兵 ==> 当肯定数量的哨兵都认为 master 挂了 ==> 选举一个哨兵作为故障转移的执行者 ==> 执行者在 slave 中选取一个作为新的 master ==> 将其余 slave 从新设定为新 master 的隶属

3、哨兵如何晓得 Redis 主从信息

哨兵配置文件中,保留着主从集群中 master 的信息,能够通过 info replication 命令,进行主从信息主动发现。

4、什么是主观下线(sdown)

主观下线:单个哨兵本身认为 redis 实例曾经不能提供服务

检测机制:哨兵向 redis 发送 ping 申请,+PONG,-LOADING,-MASTERDOWN 三种状况视为失常,其余回复均视为有效

对应配置文件的配置项:sentinel down-after-milliseconds mymaster 1000

5、什么是主观下线(odown)

主观下线:肯定数量值的哨兵认为 master 曾经下线。

检测机制:当哨兵主观认为 maser 下线后,则会通过 SENTINEL is-master-down-by-addr 命令询问其余哨兵是否认为 master 曾经下线,如果达成共识(达到 quorum 个数),就会认为 master 节点主观下线,开始故障转移流程

对应配置文件的配置项:sentinel monitor mymaster 1.0.0.1 6380 2

6、哨兵之间如何通信

  1. 哨兵之间的主动发现:公布本人的信息,订阅其余哨兵音讯(pub/sub)
  2. 哨兵之间通过命令进行通信:直连发送命令
  3. 哨兵之间通过订阅公布进行通信:互相订阅指定主题(pub/sub)

7、哨兵领导选举机制

基于 Raft 算法实现的选举机制,流程简述如下:

  1. 拉票阶段:每个哨兵节点心愿本人成为领导者;
  2. Sentinel 节点收到拉票命令后,如果没有收到或批准过其余 sentinel 节点的申请,就批准该 sentinel 节点的申请(每个 sentinel 只持有一个批准票数)
  3. 如果 sentinel 节点发现自己的票数曾经超过一半的数值,那么它将成为领导者,去执行故障转移
  4. 投票完结后,如果超过 failover-timeout 的工夫内,没进行理论的故障转移操作,则从新拉票选举。

8、slave 选举计划

slave 节点状态 > 优先级 > 数据同步状况 > 最小的 run id

9、最终主从切换的过程

针对行将成为 master 的 slave 节点,将其撒出主从集群,主动执行:slaveof NO ONE

针对其余 slave 节点,使它们成为新 master 的隶属,主动执行:slaveof new_master_host new_master_port

10、哨兵服务部署计划

不举荐:一主一从,两个哨兵

举荐:一主两从,三个哨兵

redis 集群非强统一:一主两从, 网络分区下可能呈现数据不统一或失落。

5.1.8 redis 集群分片存储

1、为什么要分片存储

redis 的内存需要可能超过机器的最大内存。(一台机器不够用)

2、官网集群计划

redis cluster 是 redis 的分布式集科解决方案,在 3.0 版本推出后无效地解决了 redis 分布式分面的需要,实现了数据在多个 Redis 节点之间主动分片, 故障主动转移, 扩容机制等性能。

次要基于 CRC16(key) % 16384 计算出每个 key 对应的 slot,而后依据 redis 集群中实例的预设槽 slot(16384 个)进行对应的操作,slot 不存储数据,仅仅用来做片区划分。

#### 3、搭建集群

  1. 筹备 6 个独立的 redis 服务
  2. 通过 redis-cli 工具创立集群
  3. 测验集群
  4. 故障转移测试
  5. 集群扩容
  6. 集群节点删除

4、集群关怀的问题

  1. 减少了 slot 槽的计算, 是不是比单机性能差?

    不是的,为了防止每次都须要服务器计算重定向,优良的 Java 客户端都实现了本地计算,并且缓存服务器 slots 调配,有变动时再更新本地内容,从而防止了屡次重定向带来的性能损耗。

  2. redis 集群大小, 到底能够装多少数据?

    实践是能够做到 16384 个槽,每个槽对应一个实例,然而 redis 宫方倡议是最大 1000 个实例,因为存储曾经足够大了。

  3. 集群节点间是怎么通信的?

    每个 Redis 群集节点都有一个额定的 TCP 端口,每个节点应用 TCP 连贯与每个其余节点连贯。检测和故障转移这些步骤根本和哨兵模式相似。

  4. ask 和 moved 重定向的区别

    重定向包含两种状况

    若确定 slot 不属于以后节点,redis 会返回 moved。

    若以后 redis 节点正在解决 slot 迁徙,则代表此处申请对应的 key 临时不在此节点,返回 ask,通知客户端本次申请重定向。

  5. 数据歪斜和拜访歪斜的问题

    歪斜导致集群中局部节点数据多,压力大。解决方案分为后期和前期:

    后期是业务层面提前预测,哪些 key 是热点,在设计的过程中躲避。

    前期是 slot 迁徙,尽量将压力摊派(slot 调整有主动 rebalance、reshard 和手动)。

  6. slot 手动迁徙怎么做?

    1. 在迁徙目标节点执行 cluster setslot <slot> IMPORTING <node ID> 命令,指明须要迁徙的 slot 和迁徙源节点。
    2. 在迁徙源节点执行 cluster setslot <slot> MIGRATING <node ID> 命令,指明须要迁徙的 slot 和迁徙目标节点。
    3. 在迁徙源节点执行 cluster getkeysinslot 获取该 slot 的 key 列表
    4. 在迁徙源节点执行对每个 key 执行 migrate 命令,该命令会同步把该 key 迁徙到目标节点。
    5. 在迁徙源节点重复执行 cluster getkeysinslo 命令,直到该 slot 的列表为空。
    6. 在迁徙源节点和目标节点执行 cluster setslot <slot> NODE <node ID>,实现迁徙操作。
  7. 节点之间会替换信息,传递的音讯包含槽的信息,带来带宽耗费。留神:防止应用大的一个集群, 能够分多个集群。
  8. Pub/Sub 公布订阅机制:对集群内任意的一个节点执行 pubish 公布音讯,这个音讯会在集群中进行流传,其余节点都接管到公布的音讯。
  9. 读写拆散:

    redis-cluster 默认所有 从节点上的读写,都会重定向到 key 对应槽的主节点上。

    能够通过 readonly 设置以后连贯可读,通过 readwrite 勾销以后连贯的可读状态。

    留神:主从节点仍然存在数据不统一的问题

5.1.9 redis 监控

1、monitor 命令

monitor 是一个调试命令,返回服务器解决的每个命令。对于发现程序的谬误十分有用。出于平安思考,某些非凡治理命令 CONFIG 不会记录到 MONITOR 输入。

留神:运行一个 MONITOR 命令可能升高 50% 的吞吐量,运行多个 MONITOR 命令升高的吞吐量更多。

2、info 命令

INFO 命令以一种易于了解和浏览的格局,返回对于 Redis 服务器的各种信息和统计数值。

info 命令 返回信息
server Redis 服务器的个别信息
clients 客户端的连贯局部
memory 内存耗费相干信息
persistence 长久化相干信息
stats 个别统计
replication 主 / 从复制信息
cpu 统计 CPU 的耗费
commandstats Redis 命令统计
cluster Redis 集群信息
keyspace 数据库的相干统计

能够通过 section 返回局部信息,如果没有应用任何参数时,默认为 detault。

3、图形化监控工具:Redis-Live

5.2 memcached 入门

因为 memcached 缓缓淡出了人们的视线,应用的公司越来越少,所以这里只是做个入门介绍。

1、简介

是一个收费开源的、高性能的、具备分布式内存对象的缓存零碎,它通过加重数据库负载减速动静 web 利用。

实质上就是一个内存 key-Value 缓存

协定简略,应用的是基于文本行的协定

不反对数据的长久化,服务器敞开之后数据全副失落

Memcached 简洁而弱小,便于疾速开发,上手较为容易

没有平安机制

2、设计理念

  1. 简略的键 / 值存储:服务器不关怀你的数据是什么样的,只管数据存储
  2. 服务端性能简略,很多逻辑依赖客户端实现

    客户端专一如何抉择读取或写入的服务器,以及无奈分割服务器时要执行的操作。

    服务器专一如何存储和治理何时革除或重用内存

  3. Memcached 实例之间没有通信机制
  4. 每个命令的复杂度为 0(1):慢速机器上的查问应该在 1ms 以下运行。高端服务器的吞吐量能够达到每秒数百万
  5. 缓存主动革除机制
  6. 缓存生效机制

3、常用命令

分组 命令 形容
存储命令 set 用于将 value 存储在指定的 key 中。key 曾经存在,更新该 key 所对应的原来的数据。
add 用于将 value 存储在指定的 key 中,存在则不更新。
replace 替换已存在的 key 的 Value,不存在,则替换失败。
append 用于向已存在 key 的 value 前面追加数据
prepend 向已存在 key 的 value 后面追加数据
cas 比拟和替换,比对后,没有被其余客户端批改的状况下能力写入。
检索命令 get 获取存储在 key 中的 value,不存在,则返回空。
gets 获取带有 CAS 令牌存的 value,若 key 不存在,则返回为空
删除 delete 删除已存在的 key
计算 incr/decr 对已存在的 key 的数字值进行自增或自减操作
统计 stats 返回统计信息如 PID(过程号)、版本号、连接数等
stats items 显示各个 slab 中 item 的数目和存储时长(最初一次拜访间隔当初的秒数)
stats slabs 显示各个 slab 的信息,包含 chunk 的大小、数目、应用状况等。
stats sizes 显示所有 item 的大小和个数
革除 flush_all 革除所有内容

4、客户端应用

客户端反对的个性:集群下多服务器抉择,节点权重配置,失败 / 故障转移,数据压缩,连贯治理

5、服务端配置

  1. 命令行参数

    查看 memcached- h 或 man memcached 获取最新文档

  2. init 脚本

    如果通过 yum 利用商店装置,能够应用 /etc/sysconfig/memcached 文件进行参数配置

  3. 查看运行配置

    stats settings 查看运行中的 memcached 的配置

6、memcached 性能

Memcached 性能的要害是硬件,外部实现是 hash 表,读写操作都是 0(1)。硬件好,几百万的 OPS 都是没问题的。

最大连接数限度 :外部基于事件机制(相似 JAVA NIO) 所以这个限度和 nio 相似,只有内存,操作系统参数进行调整,轻松几十万。

集群节点数量限度:实践是没限度的,然而节点越多,客户端须要建设的连贯就会越多。

留神:memcached 服务端没有分布式的性能,所以不论是集群还是主从备份,都须要第三方产品反对。

7、服务器硬件须要

CPU 要求:CPU 占用率低,默认为 4 个工作线程

内存要求

memcached 内容存在内存外面,所有内存使用率高。

倡议 memcached 实例独占服务器, 而不是混用。

倡议每个 memcached 实例内存大小都足统一的,如果不统一则须要进行权重调整

网络要求

依据我的项目传输的内容来定,网络越大越好,尽管通常 10M 就够用了

倡议: 我的项目往 memcached 传输的内容放弃尽可能的小

8、Memcached 利用场景

  1. 数据查问缓存:将数据库中的数据加载到 memcached,提供程序的访问速度
  2. 计数器的场景:通过 incr/decr 命令实现评论数量、点击数统计,操作次数等等场景。
  3. 乐观锁实现:例如打算工作多实例部暑的场景下,通过 CAS 实现不反复执行
  4. 避免反复解决:CAS 命令

5.3 互联网高并发缓存架构

5.3.1 缓存架构剖析图

5.3.2 缓存雪崩

定义:因为缓存服务挂掉或者热点缓存生效,从而导致所有申请都去查数据库,导致数据库连贯不够用或者数据库解决不过去,从而导致整个零碎不可用。

罕用解决方案

  1. 缓存数据的过期工夫设置随机,避免同一时间大量数据过期景象产生。
  2. 缓存降级,间接返回错误码;
  3. 加锁实现避免大量申请堆到数据库。
  4. 设置热点数据永远不过期,避免了主动生效的状况,通过其余后盾检查程序,避免缓存数据和数据库长期不同步

5.3.2 缓存击穿

定义:查问必然不存在的数据,申请透过 Redis,直击数据库。

罕用解决方案

  1. 用户内容预生成。
  2. 拜访频率限度。
  3. 缓存中无数据,也不查询数据库,间接返回错误码。
  4. 布隆过滤器

正文完
 0