20191225 RedisCluster

RedisCluster原理

整个缓存查询命中率

一致性Hash正是为了解决这个问题而出现的,该路由算法通过引入一个一致性Hash环,以及进一步增加虚拟节点层,来实现尽可能高的命中率。

使用FNV1_32_HASH哈希算法来尽可能使key与节点分布得更加均匀,引入了虚拟节点,来做负载均衡。

 

集群内部通信

在redis cluster集群内部通过gossip协议进行通信集群元数据分散的存在于各个节点,通过gossip进行元数据的交换。

redis cluster采用分布式的元数据管理。

redis cluster集群中单节点一般配置两个端口,一个端口如6379对外提供api,另一个一般是加1w,比如16379进行节点间的元数据交换即用于gossip协议通讯。

 

gossip协议包含多种消息,如ping,pong,meet,fail等

1 meet:集群中节点通过向新加入节点发送meet消息,将新节点加入集群。

2 ping:节点间通过ping命令交换元数据。

3 pong:响应ping。

4 fail:某个节点主观认为某个节点宕机,会向其他节点发送fail消息,进行客观宕机判定。

 

集群高可用和主备切换

主观宕机和客观宕机

某个节点会周期性的向其他节点发送ping消息,当在一定时间内未收到pong消息会主观认为该节点宕机,即主观宕机。然后该节点向其他节点发送fail消息,其他超过半数节点也确认该节点宕机,即客观宕机。十分类似sentinel的sdown和odown。

客观宕机确认后进入主备切换阶段及从节点选举。

 

节点选举

检查每个 slave node 与 master node 断开连接的时间,如果超过了 cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成 master。

每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。

所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。

从节点执行主备切换,从节点切换为主节点。

注意: 1、一主一从的集群模式,不存在节点选举,从节点直接升级为主节点;2、一主多从的集群模式才会存在节点选举;

 

分片和寻址算法

hash slot即hash槽。redis cluster采用的正式这种hash槽算法来实现寻址

在redis cluster中固定的存在16384个hash slot。

hash slot = CRC16(key)%16384;

#CRC16算法可以简单的理解为一种hash算法。

这样我们就能找到key对应的hash slot。其实按照我的理解,hash slot就是在寻址和节点间加了一层映射关系。当节点动态变化,只需要改变hash slot ==> 节点的映射,然后只需要迁移指定slot到新添加的节点即可。既减少了hash寻址带来的数据全量迁移问题,相对一致性hash也使得负载均衡效果更加明显。

注意:哈希槽对应的是节点,数据是存储在Redis节点的内存里面的。先通过key找到对应的Hash slot值,然后通过Hash slot确定数据存放在集群的哪个节点上。

 

如上图,如果我们有三个节点。redis cluster初始化时会自动均分给每个节点16384个slot。

当增加一个节点4,只需要将原来node1~node3节点部分slot上的数据迁移到节点4即可。在redis cluster中数据迁移并不会阻塞主进程。对性能影响是十分有限的。总结一句话就是hash slot算法有效的减少了当节点发生变化导致的数据漂移带来的性能开销。

 

Redis事务的理解

执行流程:1)MULTI开始事务2)多个命令进入缓存队列3)exec命令执行事务;

注意:

1)收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。

2)在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。(没有回滚这一说)

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束.

1)multi,开启Redis的事务,置客户端为事务态。

2)exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。

3)watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。

 

Redis过期策略以及内存淘汰机制

过期键删除策略

我们都知道,删除键的目的,就是释放内存占用。那么,当一个键过期了,Redis什么时候会去删除它呢?

redis可以设置过期时间,针对过期的key使用的清除策略,策略分为:定期删除+惰性删除。

 

定期删除:redis默认每隔100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每隔>100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因>此,如果只采用定期删除策略,会导致很多key到时间没有删除。

惰性删除:也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

 

删除key以及对应的值,释放内存空间。过期策略是针对设置了过期时间的key而言的。过期策略没有触发,导致内存占用越来越高,所以需要内存淘汰机制。

 

定期删除:如果全部检查所有的key,可能导致卡死,单线程。

惰性删除:如果没有请求key,惰性删除没有被触发,可能导致内存占用越来越高。各有利弊,所以结合使用。

 

为什么需要淘汰机制?

过期策略存在的问题,由于redis定期删除是随机抽取检查,不可能扫描清除掉所有过期的key并删除,然后一些key由于未被请求,惰性删除也未触发。这样redis的内存占用会越来越高。此时就需要结合内存淘汰机制。

 

淘汰机制原理介绍

内存占用达到内存限制设定值时触发的redis的淘汰策略来删除键。redis配置文件中可以使用maxmemory 将内存使用限制为指定的字节数,当达到内存限制时,Redis会根据选择的淘汰策略来删除键。

有很多种内存淘汰策略,推荐使用的volatile-lru:在带有过期时间的键中选择最近最少使用的。allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。

 

redis集群批量操作

Redis集群是没法执行批量操作命令的,如mget,pipeline等。这是因为redis将集群划分为16383个哈希槽,不同的key会划分到不同的槽中。

 

redis的客户端jedis里构造器中有soTimeout和connectionTimeout两个参数,分别代表什么?

connectionTimeout:表示连接超时时间;

soTimeout:表示读取数据超时时间,,就是不能读一个数据读太久;

maxAttempts:连接尝试次数,连接不成功。

maxWaitMillis 请求连接最大等待时间(毫秒),默认值 无限期 ,超出时间将抛出异常 

config.setBlockWhenExhausted(true);//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true

 

testOnBorrow和testOnReturn在生产环境一般是不开启的,主要是性能考虑。失效连接主要通过testWhileIdle保证,如果获取到了不可用的数据库连接,一般由应用处理异常。

testOnBorrow,true,指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个.

testOnReturn,false,指明是否在归还到池中前进行检验。

testWhileIdle,false,指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,

则连接将被从池中去除.

 

java语言默认情况下是int类型的,因此如果你仅仅写一个 23那就是int类型的'23',如果你想要一个长整型(long)的'23'那么就要加以区分,在后面加上L这样 23L就是 long类型的。

Redis H是指HashMap。

 

redis集群模式:三主三从,任意结点之间能够相互通讯。

 

key的规则是什么? key带前缀,前缀规则。 防止数据分布不均匀。

如何直接存储对象,获取对象?

存储集合,获取集合?

 

redis设计键,让其映射到同一个slot。

redis在使用hash算法将键映射到slot时,只会计算{}里面的内容,若{}内的内容相同,则将键映射到同一个slot。例子中{}内容均为materialType,这样在JedisClusterCRC16.getSlot(key)时得到相同的slot编码号。

 

Redis会将数据缓存到内存中,执行效率会非常快。同一时候异步将数据写入到磁盘中。进行持久化。redis支持主从同步,支持分布式部署,支持N多数据结构.

如何进行异步持久化的?

 

jedis的分布式连接池,这样方便于redis横向扩展,添加高可用性。

使用的是jedis的分布式连接池,便于Redis横向扩展。

使用jedis的原生JedisCluster。

 

    public static void main(String[] args) {
        Environment dev = Environment.fromString("DEV");
        System.out.println(dev.toString());
        System.out.println(dev.getValue());

        //获取某个环境的枚举
        System.out.println(Environment.valueOf("TEST1"));

        /*
        CacheConfig cacheConfig = new CacheConfig("DEV");
        CacheClient cacheClient = new CacheClient(cacheConfig);
        cacheClient.set("key1", "测试框架的封装");
        System.out.println(cacheClient.get("key1"));
        */

        //分数排名
        String key = "user_score_rank_20191225";

        CacheConfig cacheConfig = new CacheConfig("DEV");
        CacheClient cacheClient = new CacheClient(cacheConfig);

        cacheClient.zadd(key, 100, "111");
        cacheClient.zadd(key, 99, "112");
        cacheClient.zadd(key, 98, "113");
        cacheClient.zadd(key, 101, "114");

        //添加成员
        HashMap<String, Double> map = new HashMap<>();
        map.put("115", 80.0D);
        map.put("116", 70.0D);
        cacheClient.zadd(key, map);

        //list集合的方法模拟堆栈和消息队列 数据结构。
        //lpush,rpush,lpop,rpop方法


        //sort set所有的方法都是以字母z开头。
        //使用场景:活动排名。(排名设计)

        //利用sort set自动从小到大的排序规则。两个元素:score,member。(用户和其分数)
        //获取某个人的排名:从小到大进行排名
        //System.out.println(cacheClient.zrank(key, "114"));
        //总共有多少个人
        //System.out.println(cacheClient.zcard(key));
        //获取某个人的分数
        //System.out.println(cacheClient.zscore(key, "111"));
        //把某个人的分数增加1分
        //System.out.println(cacheClient.zincrby(key, 1, "114"));

        //计算有序集合中指定分数区间的成员数量。
        System.out.println(cacheClient.zcount(key, "98", "100"));
        //打印出所有的set集合(member,按照分数的从高到、低排列)
        System.out.println(cacheClient.zrevrange(key,0,101));
        //rank表示排名
        //range表示界限,范围
        //rev表示排序规则,从大到消

        //分值相同 如何处理? 按照时间进行处理,看看谁先到达这个时间?

        //直接存取对象
        /*
        RedisEntity crs = new RedisEntity("crs", 22);
        if ( cacheClient.set("obj",crs)){
            RedisEntity obj = cacheClient.get("obj", RedisEntity.class);
            System.out.println(obj.toString());
        }
        */


        //直接存取集合
        /*
        RedisEntity qgyd = new RedisEntity("qgyd", 23);
        ArrayList<RedisEntity> redisEntityList = new ArrayList<>();
        redisEntityList.add(crs);
        redisEntityList.add(qgyd);
        if (cacheClient.set("list",redisEntityList)){
            List<RedisEntity> list = cacheClient.getList("list", RedisEntity.class);
            for (RedisEntity item: list) {
                System.out.println(item);
            }
        }
        */

        //把用户信息存储到HashMap里
        /*
        HashMap<String, String> infoMap = new HashMap<>();
        infoMap.put("name","test");
        infoMap.put("age","25");
        infoMap.put("hospital","闵行区中心医院");

        String cache_user_1="cache_user_1";
        Set<Map.Entry<String, String>> entries = infoMap.entrySet();
        for (Map.Entry<String, String> entry: entries) {
            String key1 = entry.getKey();
            String value = entry.getValue();
            cacheClient.hset(cache_user_1,key1,value);
        }
        Map<String, String> resultMap = cacheClient.hgetall(cache_user_1);
        Set<Map.Entry<String, String>> entries1 = resultMap.entrySet();
        for (Map.Entry<String, String> item: entries1) {
            System.out.println(item.getKey()+"---->"+item.getValue());
        }
        */

        //让键存储到同一个redis结点上;这样设置,直接分布到同一个hash slot上了。
        cacheClient.sadd("{111}222","222");
        cacheClient.sadd("{111}333","333");
        cacheClient.spop("{111}222");
        cacheClient.spop("{111}333");

        //1.查看集群节点
        //cluster nodes
        //2.查看key对应的slot
        //cluster keyslot key
        //3.查看slot和节点的对应关系
        //cluster slots


        //info查询所有库的key数量
        //dbsize查询当前库的key数量
        //keys * 查询当前库的所有key.注意:数量少的时候可以用,当key很多的时候很慢。会卡死生产环境的。
        //dbsize和keys *统计的key数可能是不一样的,如果没记错的话,keys *统计的是当前db有效的key
        //而dbsize统计的是所有未被销毁的key(有效和未被销毁是不一样的,具体可以了解redis的过期策略)


        //redis的一致性hash算法,让key均匀的分布
    }

1、为什么使用接口定义和实现?

2、为什么要使用final关键字?

3、为什么要使用build模式,是不合理的?

4、如何设置持久化策略?

 

 

final关键字

final意为“最终的,最后的”,我理解为“不能被改变的”。

被final修饰的类不能被继承,即它不能拥有自己的子类;否在会在编译期间报错。

被final修饰的方法不能被重写;

final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。

在看String为什么要被final修饰:主要是为了”安全性“和”效率“的缘故。

 

final的类不能被继承,不能让别人继承有什么好处? 

意义就在于,安全性,如此这般: (这个解释有问题:java代码和虚拟机进行交互,虚拟机才会和本地方法交互)

java 自出生那天起就是“为人民服务”,这也就是为什么java做不了病毒,也不一定非得是病毒,反正总之就是为了安全, 人家java的开发者目的就是不想让 java干这类危险的事儿,java并不是操作系统本地语言, 换句话说java必须借助操作系统本身的力量才能做事,JDK中提供的好多核心类比如 String,这类的类的内部好多方法的实现都不是java编程语言本身编写的,好多方法都是调用的操作系统本地的API,这就是著名的“本地方法调用”,也只有这样才能做事,这种类是非常底层的, 和操作系统交流频繁的,那么如果这种类可以被继承的话,如果我们再把它的方法重写了,往操作系统内部写入一段具有恶意攻击性质的代码什么的, 这不就成了核心病毒了么? 

上面所述是最重要的,另外一个方面,上面2位老兄说的也都很对, 就是不希望别人改,这个类就像一个工具一样,类的提供者给我们提供了, 就希望我们直接用就完了,不想让我们随便能改,其实说白了还是安全性, 如果随便能改了,那么java编写的程序肯定就很不稳定,你可以保证自己不乱改, 但是将来一个项目好多人来做,管不了别人,再说有时候万一疏忽了呢?他也不是估计的, 所以这个安全性是很重要的,java和C++相比,优点之一就包括这一点; 

原因绝对不只有这么多,因为如果这些个核心的类都能被随便操作的话,那是很恐怖的,会出现好多好多未知的错误,莫名其妙的错误….

 

使用场景是什么?

1)当前类是工具类,不希望被别人修改,直接拿来使用就可以了。比如CacheClient类,对外暴露的。

2)好多方法都是调用操作系统本地的API,这就是著名的“本地方法调用”.

3)和操作系统交流频繁的,那么如果这种类可以被继承的话,如果我们再把它的方法重写了,往操作系统内部写入一段具有恶意攻击性质的代码什么的, 这不就成了核心病毒了么?(这句话有问题)

4)希望变量在类加载的时候,进行初始化。使用final 修饰的变量,必须在构造函数中进行初始化。

在CacheClient中有两个地方用到了final关键字,类上和字段上。

没有必要去继承,功能都完整了,直接拿来用就好了,比如Integer,Long,提供的方法已经住够用可,很完善了,功能已经很强大了。

 

为什么需要使用构建者模式?

使用场景:HttpClient的封装,Dialog的封装

使用builder模式:创建对象的方式比较灵活,比如对HttpClient的封装,发送网络请求比较多,需要创建多个独享。每次对象可能都不一样。

只创建一个对象,只创建一次,不需要使用builder模式,比如ClientConfig就创建一次,完全没必要使用构建者模式的。

同样的构建过程,可以创建不同的对象。

将一个复杂对象的构建与它的表示分离;同样的构建过程可以创建不同的表示(对象)。

要注意构建者模式的使用场景,不要为了应用而应用

build模式的好处:链式调用、代码简洁

静态内部类:返回类PicaResponse的封装。

 

为什么要进行接口继承,为什么存在接口继承?

接口是对功能的描述。接口是一种协议,是一种约定。接口不是类,不能使用new。 

1)如果只有一套实现,完全可以不使用接口的;多态就是接口存在多套实现。三层架构中Service层其实可以不使用接口,也可以实现功能。

2)面向接口编程的需要。

3)可以在尚未实现具体Service情况下编写上层改代码,如Controller对Service的调用。可以对Service进行多实现。

4)Spring无论是AOP还是事务管理的实现都是基于动态代理的,而动态代理的实现依赖于接口,所以必须有接口的定义才能使用这些功能。

使用接口是为了调用与实现解耦(子类对接口进行实现,调用的时候使用接口对象),带来的好处是可以各干各的了.

 

redis配置文件 ( redis.conf文件,编辑即可,vi redis.conf)

# Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程.

# 启用守护进程后,Redis会把pid写到一个pidfile中,在/var/run/redis.pid daemonize no

 

# 指定Redis监听端口,默认端口为6379

 # 如果指定0端口,表示Redis不监听TCP连接 port 6379

 

# 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 timeout 0

# 设置数据库的数量,默认数据库为0,可以使用select <dbid>命令在连接上指定数据库id # dbid是从0到‘databases’-1的数目 databases 16

 

snapshotting 内存快照:默认开启开启了rdb

指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

# 满足以下条件将会同步数据:

# 900秒(15分钟)内有1个更改

# 300秒(5分钟)内有10个更改

# 60秒内有10000个更改

save 900 1

save 300 10

save 60 10000

 

replication复制

主从复制。使用slaveof从Redis服务器复制一个Redis实例,注意,该配置仅限于当前slave有效。

设置当本机为slave服务时,设置master服务的ip地址及端口,在Redis启动时,它会自动从master进行数据同步

# slaveof <masterip> <masterport>

# 当master服务设置了密码保护时,slav服务连接master的密码 # 下文的“requirepass”配置项可以指定密码

# masterauth <master-password>

 

SECURITY 

设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过auth <password>命令提供密码(必须要设置密码连接),默认关闭。

 

Redis持久化

Redis支持RDB和AOF两种持久化机制,内存快照与操作日志。

持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化文件即可实现数据恢复。

默认开启RDB, 默认关闭AOF。

如何开启AOF? (appendonly yes 和 更新频率)

appendonly yes,若开启了AOF,一般AOF更新频率高,所以优先AOF还原数据库。只有AOF关闭时,才会使用RDB还原数据库。

redis.conf中的appendfysnc是对redis性能有重要影响的参数之一。可取三种值:always、everysec和no。

1、设置为always时,会极大消弱Redis的性能,因为这种模式下每次write后都会调用fsync(Linux为调用fdatasync)。

2、如果设置为no,则write后不会有fsync调用,由操作系统自动调度刷磁盘,性能是最好的。

3、everysec为最多每秒调用一次fsync,这种模式性能并不是很糟糕,一般也不会产生毛刺,这归功于Redis引入了BIO线程,所有fsync操作都异步交给了BIO线程。

 

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发

内存快照,保存到硬盘,手动触发,自动触发。

手动触发分别对应save和bgsave命令。

1、save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,先上环境不建议使用。

2、bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一段时间很短。

bgsave命令是针对save阻塞问题做的优化。因此Redis内部所有涉及到RDB操作都采用bgsave的方式,而save命令可以废弃。

 

RDB流程:

1) 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如只RDB/AOF子进程,如果存在bgsave命令直接返回。

2) 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork以操作的耗时,单位为微秒。

3) 父进程仍fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。

4) 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成尺RDB的时间,对应info统计的rdb_last_save_time选项。

5) 进程发送信号给父进程表示完成,父进程更新统计信息,具体见info Persistence下的rdb_*相关选项。

 

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。

开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件通过appendfilename 配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化方式一致。通过dir配置指定。AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)。

AOF流程:

1)所有的写入命令会追加到aof_buf(缓冲区)中。

2)AOF缓冲区根据对应的策略向硬盘做同步操作。

3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。

4)当Redis服务重启时,可以加载AOF文件进行数据恢复。

 

缓存使用

1、缓存时机,缓存哪些数据?

1)、登录成功后缓存用户信息;

2)、刷新所有缓存功能,刷新redis中的数据和mysql保持一致,特别重要。

2、redis数据类型的选择; 选择String,还是HashMap类型的数据,还是Set类型的数据。

3、redis key前缀的设计; 如何更好地区分当前缓存和其他缓存?

 

Redis中没有Int类型,只有五种具体的数据类型;以字符串的形式保存数字。

判断当前key是否存在:exists()方法。EXISTS KEY_NAME命令:若key存在返回1,否则返回 0。

 

Redis限速器:限速器是特殊化的计算器,它用于限制一个操作可以被执行的速率(rate)。

限速器的典型用法是限制公开 API 的请求次数,一个限速器实现示例,将 API 的最大请求数限制在每个IP地址每秒钟十个之内。如何用redis incr功能来实现高并发计数器或者计分器?

需求:一分钟内接口的调用次数不超过1000次 次数放在redis缓存里。

方案:redis本身就是事件驱动模型,incr递增好了,然后get获取判断一下是否超过次数。

 

封装目标:Redis的场景启动器或者jar包。

通过RedisTemplate来访问redis; 不和Spring Boot结合使用,单独封装成一个jar包。

 

redis-cli命令:打开redis的操作面板

vim redis.conf:里面能编辑的配置项不多

sudo su:命令,权限命令

 

 

类的结构以及继承关系

Constants:所有结点的配置

Environment:环境枚举

CacheConfig:根据不同的环境,获取不同的JedisPoolConfig。

因为环境是可以动态获取的,每个环境的redis集群不同。

CacheCoreCommands 主要用于对redis命令进行封装,不对外暴露处理逻辑;但是要特别注意异常逻辑。

CacheClient:真正对外暴露。 直接调用ICacheCoreCommands接口中的方法即可。

如果有特别需要,可以按照业务封装Command接口。

如何不让用户直接new对象?

对key进行处理,防止数据倾斜。一致性哈希,防止数据倾斜。CacheKeyRule,这个类还是很有先见之名的。防止数据倾斜,数据分布不均匀。

CacheClient的继承关系是什么? 为什么要这样继承? 继承自父类,就可以使用父类中的方法。写一个类,让它继承即可。

对于redis jar包中方法的封装。对这个方法包装一层。

 

CacheClient类

1、继承自CacheKeyRule,可以使用父类中的方法,key的规则处理。

2、实现ICacheClient接口;

 

ICacheClient接口继承自ICacheCoreCommands,ICacheTokenCommands等接口;如果接口存在继承,那么子类需要实现所有的接口方法。

 

Redis使用场景:分数排名,订单超时,提高并发数,原子操作解决并发问题。

 

Redis中哪些命令会有性能影响?

1)keys *:执行keys命令,redis会锁定;如果数据庞大的话可能需要几秒或更长,对于生产服务器上锁定几秒这绝对是灾难了。

2)save命令:Redis Save 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘。

3)HGETALL命令:操作HashMap;Hash的field越多,当使用HgetAll获取全量数据时,性能越差,该命令的性能与field字段的数量成正比。 

返回key指定的哈希集中所有的字段和值。返回值中,每个字段名的下一个是它的值,所以返回值的长度是哈希集大小的两倍。

 

Redis是单线程的!当它处理一个请求时其他的请求只能等着。通常请求都会很快处理完,但是当我们使用HGETALL的时候,必须遍历每个字段来获取数据,这期间消耗的CPU资源和字段数成正比。

序列化字段冗余

 

解决hgetall产生的性能问题:

Redis在存储HASH的时候,多保存一个名为「all」的字段,其内容是原HASH数据的序列化,实际查询的时候,只要HGET这个冗余字段后再反序列化即可。此方案的优势在于通过序列化字段冗余,我们把原本的HGETALL操作简化为HGET,也就是说,不再需要遍历HASH中的每一个字段,因此即便不能让多个CPU参与运算,但是却大幅降低了操作数量,所以性能的提升仍然是显著的;当然劣势也很明显,和所有的冗余方式一样,此方案浪费了大量的内存。

有人会问,这样虽然没有了遍历字段的过程,但是却增加了反序列化的过程,而反序列化的成本往往也是很高的,难道这样也能提升性能?问题的关键在于开始我们遍历字段的操作是在一个CPU上完成的,后来反序列化的操作,不管是什么语言,都可以通过多进程或多线程来保证是在多个CPU上完成的,所以性能总体上是提升的。

 

缓存穿透和缓存雪崩

数据的一致性问题;如果对数据的一致性要求很高,那么就不能使用缓存。

1、缓存穿透:查询直接穿透缓存层,到达存储层,比如-1不存在,又不能进行redis存储。

 

针对某一个查询接口,如果出现了缓存雪崩现象,应该怎么处理?(恶意的)

在工作中,会采用缓存空值的方式,也就是【代码流程】中第5步,如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。

 

2、缓存雪崩:是指在某一个时间段,缓存集中过期失效。

产生周期性的压力波峰。同一时间点 缓存同时失效。采取不同分类商品,缓存不同周期。尽可能分散缓存过期时间。

缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

缓存集中失效或者缓存宕机,存储层压力瞬间激增。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值