6.1 工具-Redis

目录

6.1.1 Redis

6.1.1.1 前言

6.1.1.2 Redis

6.1.1.2.1 什么是Redis

6.1.1.2.2 Redis优势

6.1.1.3 Redis的启动与设置

6.1.1.3.1 前台显示形式启动

6.1.1.3.2 若要采用后台运行形式需修改配置

6.1.1.3.3 后台形式启动

6.1.2 Redis的数据类型

6.1.2.1 数据类型

6.1.2.2 字符串(String)

6.1.2.3 列表(List)

6.1.2.4 集合(Set,不允许出现重复的元素)

6.1.2.5 有序集合(sorted set)

6.1.2.6 哈希(Hash)

6.1.2.7 通用操作

6.1.3 Jedis

6.1.3.1 修改配置文件

6.1.3.2 IDEA中创建对象

6.1.4 Redis事务 

6.1.4.1 事务概述

6.1.4.2 Redis事务命令

6.1.4.3 Redis事务的特性总结

6.1.4.4 Redis中的持久化

6.1.4.4.1 RDB

6.1.4.4.2 AOF

6.1.4.4.3 RDB与AOF优缺点

6.1.4.5 Redis中的消息的订阅与发布

6.1.4.6 Redis图形化程序界面

6.1.4.7 Redis的主从复制

6.1.4.7.1 主从复制概念与作用

6.1.4.7.2 搭建

6.1.4.7.3 主从复制原理

6.1.4.8 Redis哨兵模式

6.1.4.8.1 哨兵的作用

6.1.4.8.2 搭建

6.1.4.8.3 哨兵模式的优缺点

6.1.4.9 Redis缓存穿透,击穿,雪崩,倾斜

6.1.4.9.1 缓存穿透

6.1.4.9.2 缓存击穿

6.1.4.9.3 缓存雪崩

6.1.4.9.4 缓存倾斜

6.1.4.10 Redis其他常见问题

6.1.4.10.1 key的生存时间到了,Redis会立即删除吗?

6.1.4.10.2 Redis的淘汰机制

6.1.4.11 Redis集群搭建


6.1.1 Redis

6.1.1.1 前言

前面使用到的mysql数据库会出现以下问题

由于用户量增大,请求数量也随之增大,数据压力过大

多台服务器之间数据不同步

多台服务器之间的锁,已经不存在互斥性了。

6.1.1.2 Redis

6.1.1.2.1 什么是Redis

Redis(Remote Dictionary Server)即远程字典服务,Redis是C语言编写,Redis是一款基于Key-Value的NoSQL(非关系型数据库,Not Only SQL),而且Redis是基于缓存存储数据的。单线程,多路IO复用。Redis还提供了多种持久化机制,性能可以达到110000/s读取数据以及81000/s写入数据,Redis还提供了主从,哨兵以及集群的搭建方式,可以更方便的横向扩展以及垂直扩展。

关系型数据库是基于关系表的数据库,最终会将数据持久化到磁盘上,而nosql数据库是基于特殊的结构,并将数据存储到内存的数据库。

NOSQL产品: redis,mongodb,memcached等

  • Key-Value型:Redis等
  • 文档型:ElasticSearch(后面会讲解,但是重点强调的是搜索,为不是非关系型数据库),Solr,Mongodb等

  • 面向列型:Hbase,Cassandra 等

  • 图形化型:Neo4j等

从性能上而言,nosql数据库要优于关系型数据库,从安全性上而言关系型数据库要优于nosql数据库,所以在实际开发中一个项目中nosql和关系型数据库会一起使用,达到性能和安全性的双保证。

Redis 命令参考 — Redis 命令参考

6.1.1.2.2 Redis优势

  1. redis是基于内存存储计算,性能速读远超mysql等数据库,计算速度很快,所以在使用的时候数据响应很快,
  2. redis支持多种多样的数据结构,如字符串、tree、ztree、map、等,这些丰富的数据结构,可以满足我们在开发工作大部分常见数据结构,进行存储。
  3. redis丰富的api支持,让我们在使用的时候,常见的查询存储都能够很方便的使用,支持自定的查询的api等等
  4. redis的生态比较成熟,很多家大型公司都在使用,很多相关的知识扩展以及分析
  5. redis分布式集群化扩展性极高,而且稳定,能够支撑大量的数据吞吐,只要硬件支持

6.1.1.3 Redis的启动与设置

6.1.1.3.1 前台显示形式启动

 ./redis-server 

客户端

关闭服务 

6.1.1.3.2 若要采用后台运行形式需修改配置

若采用后台运行需要修改相应配置文件

6.1.1.3.3 后台形式启动

由于软件安装位置为/usr/local,所以先进入/usr/local目录

 进入redis-6.2.4目录

进入bin目录 

启动服务端,注意这里的启动命令后带的redis.conf是我们前面为了后台形式启动所修改的配置文件,让启动命令运行指定的配置文件

启动客户端 

 

6.1.2 Redis的数据类型

6.1.2.1 数据类型

常用的5种数据结构:

  • key-string:一个key对应一个值。
  • key-hash:一个key对应一个Map。
  • key-list:一个key对应一个列表。
  • key-set:一个key对应一个集合。
  • key-zset:一个key对应一个有序的集合。

另外三种数据结构:

  • HyperLogLog:计算近似值的。
  • GEO:地理位置。
  • BIT:一般存储的也是一个字符串,存储的是一个byte[]。

value最常用的五种数据类型:

  1. 字符串(String):最常用的,一般用于存储一个值
  2. 列表(List):使用list结构实现栈和队列结构
  3. 集合(Set) :交集,差集和并集的操作
  4. 有序集合(sorted set) :排行榜,积分存储等操作
  5. 哈希(Hash):存储一个对象数据的

对象存储,也可通过JSON,用String存储,提取时,通过JSON提取出对象

6.1.2.2 字符串(String)

set key value

设定key持有指定的字符串value,如果该key存在则进行覆盖操作,总是返回"OK"

setnx key value

只有在key不存在是添加

get key

获取key的value。如果与该key关联的value不是String类型,redis将返回错误信息,因为get命令只能用于获取String value,如果该key不存在,返回null

getset key value

先获取该key的值,然后在设置该key的值。

incr key

将指定的key的value原子性的递增1.如果该key不存在,其初始值为0,在incr之后其值为1。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息。

decr key

将指定的key的value原子性的递减1.如果该key不存在,其初始值为0,在incr之后其值为-1。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息。

incrby key increment

将指定的key的value原子性增加increment,如果该key不存在,器初始值为0,在incrby之后,该值为increment。如果该值不能转成整型,如hello则失败并返回错误信息。

decrby key decrement

将指定的key的value原子性减少decrement,如果该key不存在,器初始值为0,在decrby之后,该值为decrement。如果该值不能转成整型,如hello则失败并返回错误信息。

append key value

如果该key存在,则在原有的value后追加该值;如果该key不存在,则重新创建一个key/value。

setex key seconds value

expire key seconds

过期时间

ttl

查看生存时长

6.1.2.3 列表(List)

lpush key value1 value2...

在指定的key所关联的list的头部插入所有的values,如果该key不存在,该命令在插入的之前创建一个与该key关联的空链表,之后再向该链表的头部插入数据。插入成功,返回元素的个数。

rpush key value1 value2…

在该list的尾部添加元素。

lrange key start end

获取链表中从start到end的元素的值,start、end可为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推….

lpushx key value

当key存在时,在头部插入value,否则将不插入

rpushx key value

在key的尾部插入value

 lpop key

返回并弹出指定的key关联的链表中的第一个元素,即头部元素。

rpop key

从尾部弹出元素。

 rpoplpush resource destination

将链表中的尾部元素弹出并添加到头部。

llen key

返回指定的key关联的链表中的元素的数量。

lset key index value

设置链表中的index的脚标的元素值,0代表链表的头元素,-1代表链表的尾元素。

 lrem key count value

删除count个值为value的元素,如果count大于0,从头向尾遍历并删除count个值为value的元素,如果count小于0,则从尾向头遍历并删除。如果count等于0,则删除链表中所有等于value的元素。

 linsert key before|after pivot value

在pivot元素前或者后插入value这个元素。

6.1.2.4 集合(Set,不允许出现重复的元素)

sadd key value1 value2…

向set中添加数据,如果该key的值已有则不会重复添加。

smembers key

获取set中所有的成员。

scard key

获取set中成员的数量。

 sismember key member

判断参数中指定的成员是否在该set中,1表示存在,0表示不存在或者该key本身就不存在。

srem key member1 member2…

删除set中指定的成员。

 srandmember key

随机返回set中的一个成员。

 sdiff key1 key2

返回key1中有但key2中无的成员,而且与key的顺序有关,即返回差集。

 sdiffstore destination key1 key2

将key1、key2相差的成员存储在destination上。

 sinter key[key1,key2…]

返回交集。

sinterstore destination key1 key2

将返回的交集存储在destination上。

 sunion key1 key2

返回并集。

 sunionstore destination key1 key2

将返回的并集存储在destination上

6.1.2.5 有序集合(sorted set)

zadd key score member score2 member2 …

将所有成员以及该成员的分数存放到sorted-set中。

 zcard key

获取集合中的成员数量。

zcount key min max

获取分数在[min,max]之间的成员。

zincrby key increment member

设置指定成员的增加的分数。

zrange key start end [withscores]

获取集合中脚标为start-end的成员,[withscores]参数表明返回的成员包含其分数。

 zrangebyscore key min max withscores

返回分数在[min,max]的成员并按照分数从低到高排序。[withscores]:显示分数;[limit offset count]:offset,表明从脚标为offset的元素开始并返回count个成员。

 zrank key member

返回成员在集合中的位置。

zrem key member[member…]

移除集合中指定的成员,可以指定多个成员。

zscore key member

返回指定成员的分数。

6.1.2.6 哈希(Hash)

hset key field value

为指定的key设定field/value对(键值对)。

hgetall key

获取key中的所有filed-vaule。

hget key field

返回指定的key中的field的值。

hmset key fields

设置key中的多个filed/value。

hmget key fileds

获取key中的多个filed的值。

 hexists key field

判断指定的key中的filed是否存在。

hlen key

获取key所包含的field的数量。

hincrby key field increment

设置key中filed的值增加increment,如:age增加20。

6.1.2.7 通用操作

keys patten

获取所有与patten匹配的key,*表示任意字符,?表示一个字符。

 del key1 key2....

删除指定的key。

exists key

判断该key是否存在,1表示存在,0表示不存在。

rename key newkey

为当前key重命名。

expire key second

为当前key设置过期时间(单位:秒)。

ttl key

查看当前key剩余过期时间。

type key

查看当前key类型。

flushall

删除所有key

6.1.3 Jedis

Jedis:可以直接在IDEA中使用Redis

6.1.3.1 修改配置文件

修改/usr/local/redis-5.0.4/bin目录下的redis.conf配置文件,然后启动redis服务端

 将绑定127.0.0.1注释掉,然后把保护模式关掉

如需设置密码,可以使用以下两种方式:

方式一:通过修改 redis.conf 文件,设置Redis的密码校验

requirepass 密码

方式二:在不修改 redis.conf 文件的前提下,在第一次链接Redis时,输入命令:Config set requirepass 密码

如果想取消密码

Config set requirepass ''

英文的两个单引号

后续连接redis客户端的时候,需要先 AUTH 做一下校验

127.0.0.1:6379> auth 密码

注意连接的ip地址和端口号 

6.1.3.2 IDEA中创建对象

创建Maven工程,导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.12</version>
</dependency>

实体类 

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer uid;
    private String username;
    private String password;
}

 测试类

public class JedisDemo {

    // 通过java程序访问redis数据库
    // 获得单一的jedis对象操作数据库
    @Test
    public void test1() {

        //获得连接对象
//主机号,端口号 
        Jedis jedis = new Jedis("192.168.153.132", 6379);
		//认证密码
        //jedis.auth("root");
        //获得之前redis中存储的数据
        String name = jedis.get("name");
        System.out.println(name);

        //存储数据
        jedis.set("password", "123");
        System.out.println(jedis.get("password"));

        //关闭
        jedis.close();

    }

    //通过jedis的pool获得jedis连接对象
    @Test
    public void test2() {

        // 创建池子的配置对象
        JedisPoolConfig poolConfig = new JedisPoolConfig();

        poolConfig.setMaxIdle(30);// 最大闲置个数
        poolConfig.setMinIdle(10);// 最小闲置个数
        poolConfig.setMaxTotal(50);// 最大连接数

        // 创建一个redis的连接池
        JedisPool pool = new JedisPool(poolConfig, "192.168.153.132", 6379);
        // 从池子中获取redis的连接资源
        Jedis jedisPoolResource= pool.getResource();
        // 创建User类进行存储
        User user = new User(1001, "李四", "123");
        // 将对象转换成json存储
        jedisPoolResource.set("user", JSON.toJSONString(user));
        String db_user = jedisPoolResource.get("user");
        // 返回json数据
        System.out.println(db_user);
        // 返回User类型
        System.out.println(JSON.parseObject(db_user,User.class));
        // 关闭资源
        jedis.close();
        pool.close();
    }
}

6.1.4 Redis事务 

6.1.4.1 事务概述

Redis中的事务和MySQL中的事务有本质的区别,Redis中的事务是一个单独的隔离操作,事务中所有的命令都会序列化,按照顺序执行,事务在执行的过程中,不会被其他客户端发来的命令所打断,因为Redis服务端是个单线程的架构,不同的Client虽然看似可以同时保持连接,但发出去的命令是序列化执行的,这在通常的数据库理论下是最高级别的隔离。

Redis中的事务的作用就是串联多个命令,防止别的命令插队。

6.1.4.2 Redis事务命令

常用命令:multi、exec、discard、watch、unwatch

当输入multi命令时,之后输入的命令都会被放在队列中,但不会执行,直到输入exec后,Redis会将队列中的命令依次执行,discard用来撤销Exec之前被暂存的命令,并不是回滚。

 

 watch/unwatch

在执行multi之前,先执行watch key1 [key2...] ,watch提供的乐观锁功能(初始时一个版本号,exec之后会更新当前版本号),在你exec的那一刻,如果被watch的键发生过改动,则multi到exec之间的指令全部不执行。

watch表示监控,相当于加锁,但在执行完exec时就会解锁。

unwatch取消所有锁。

6.1.4.3 Redis事务的特性总结

1.单独的隔离操作

事务中的所有命令都会序列化,然后按顺序执行,在执行过程中,不会被其他客户端发送的命令打断。

2.没有隔离级别的概念

队列中的命令没有被提交之前都不会执行。

3.不能保证原子性

Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,不会回滚

6.1.4.4 Redis中的持久化

 Redis有两种持久化方式:RDB和AOF

6.1.4.4.1 RDB

Redis DataBase 

将内存中的数据以快照的方式写入磁盘中,内容紧凑,在redis.conf文件中,我们可以找到如下配置:

 

save 900 1

save 300 10

save 60 10000

配置含义:

900秒内,如果超过1个key被修改,则发起快照保存

300秒内,如果超过10个key被修改,则发起快照保存

60秒内,如果1万个key被修改,则发起快照保存

RDB方式存储的数据会在dump.rdb文件中(在哪个目录启动redis服务端,该文件就会在对应目录下生成),该文件不能查看,如需备份,对Redis操作完成之后,只需拷贝该文件即可(Redis服务端启动时会自动加载该文件)

shutdown 关机前会把内存中的key写入 dump.rdb文件中

kill -9 reids-uid 强行杀死进程 :数据可能会丢失(是否满足指定时间内的指定数量操作)

6.1.4.4.2 AOF

Append Of File

AOF默认是不开启的,需要手动开启,同样是在redis.conf文件中开启 

配置文件中的appendonly修改为yes,开启AOF持久化。开启后,启动redis服务端,发现多了一个appendonly.aof文件。

之后任何的操作都会保存在appendonly.aof文件中,可以进行查看,Redis启动时会将appendonly.aof文件中的内容执行一遍。

如果AOF和RDB同时开启,系统会默认读取AOF的数据。

6.1.4.4.3 RDB与AOF优缺点

RDB

优点

  • 如果要进行大规模数据的恢复,RDB方式要比AOF方式恢复速度要快。
  • RDB是一个非常紧凑(compact)的文件,它保存了某个时间点的数据集,非常适合用作备份,同时也非常适合用作灾难性恢复,它只有一个文件,内容紧凑,通过备份原文件到本机外的其他主机上,一旦本机发生宕机,就能将备份文件复制到redis安装目录下,通过启用服务就能完成数据的恢复。

缺点

  • RDB这种持久化方式不太适应对数据完整性要求严格的情况,因为,尽管我们可以用过修改快照实现持久化的频率,但是要持久化的数据是一段时间内的整个数据集的状态,如果在还没有触发快照时,本机就宕机了,那么对数据库所做的写操作就随之而消失了并没有持久化本地dump.rdb文件中。

AOF

优点

  • AOF有着多种持久化策略:
  • appendfsync always:每修改同步,每一次发生数据变更都会持久化到磁盘上,性能较差,但数据完整性较好。
  • appendfsync everysec: 每秒同步,每秒内记录操作,异步操作,如果一秒内宕机,有数据丢失。
  • appendfsync no:不同步。
  • AOF文件是一个只进行追加操作的日志文件,对文件写入不需要进行seek,即使在追加的过程中,写入了不完整的命令(例如:磁盘已满),可以使用redis-check-aof工具可以修复这种问题
  • Redis可以在AOF文件变得过大时,会自动地在后台对AOF进行重写:重写后的新的AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建AOF文件的过程中,会继续将命令追加到现有的AOF文件中,即使在重写的过程中发生宕机,现有的AOF文件也不会丢失。一旦新AOF文件创建完毕,Redis就会从旧的AOF文件切换到新的AOF文件,并对新的AOF文件进行追加操作。

缺点

  • 对于相同的数据集来说,AOF文件要比RDB文件大。
  • 根据所使用的持久化策略来说,AOF的速度要慢于RDB。一般情况下,每秒同步策略效果较好。不使用同步策略的情况下,AOF与RDB速度一样快。

6.1.4.5 Redis中的消息的订阅与发布

不常用

 subscribe channel  订阅频道

subscribe chan01

publish channel content  在指定频道中发布内容

publish chan01 hello

同时打开两个客户端,一个订阅频道,一个在频道中发布内容,订阅频道的客户端会接收到消息。

6.1.4.6 Redis图形化程序界面

Redis中默认有16个库,可以在不同的库中存储数据,默认使用0号库存储数据,使用select 0-15可以选择不同的库。

常见的图形化工具

6.1.4.7 Redis的主从复制

6.1.4.7.1 主从复制概念与作用

什么是主从复制

主从复制是指将一台Redis服务器的数据,复制到其它的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点

默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用

1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,但实际上是一种服务的冗余.

3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

4.高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础.

6.1.4.7.2 搭建

查看当前库的信息

127.0.0.1:6379> info replication

关闭当前运行的redis,打开四个链接,进行测试,拷贝三个redis.conf文件

如改成6379,6380,6381

分别修改这三个文件信息,需要修改:端口,pid名字,dump.rdb名字,log文件名字(可改可不改)

日志文件不修改就按默认 

 如果要修改就自己命名一个文件

如改成以下

port 6379

pidfile /var/run/redis_6379.pid

logfile "6379.log"

dbfilename dump6379.rdb

port 6380

pidfile /var/run/redis_63780pid

logfile "6380.log"

dbfilename dump6380.rdb

port 6381

pidfile /var/run/redis_6381.pid

logfile "6381.log"

dbfilename dump6381.rdb

启动三台服务器

此时是用一台设备模拟三台服务器,实际开发中,每台设备只开启一个服务

 启动客户端

./redis-cli -p 指定端口

 此时查看会发现三台服务器都是master状态,此时不存在主节点从节点

 创建主节点和两个从节点,主节点负责写操作,从节点负责读操作

6379:主节点

6380,6381:从节点

 slaveof  所属主节点的ip  端口号

 6381配置同上

 查看6379信息

主节点既可以写也可以读数据,但是从节点只能读取数据,不能写。

 当主机宕机后,由于从节点还存在,依然提供服务,不影响数据的读取,但是会影响写操作。

即使主机断开链接(127.0.0.1:6379>shutdown),从机仍然可以连接到主机,如果使用的是命令行配置的从机,从机一旦断开链接后,就会变回主机了,如果再次变回从机,仍旧可以获取主机中的值.

如果主机断开链接,从机可以使用命令:127.0.0.1:6380>slaveof no one 使自己成为主机

6.1.4.7.3 主从复制原理

Slave启动成功连接到master后会发送一个sync同步命令,Master接到命令后,会启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到salve,并完成一次完整的同步.

全量复制:salve服务在接收到数据库文件数据后,将其存盘并加载到内存中.

增量复制:master继续将新的所有收集到的修改命令依次传递给salve,完成同步.

6.1.4.8 Redis哨兵模式

6.1.4.8.1 哨兵的作用

在上面的讲解中,我们发现一个问题,当主服务器宕机后,若并没有及时发现,这时候可能会出现数据丢失或程序无法运行。此时,redis的哨兵模式就派上用场了,可以用它来做redis的高可用。

1. 10*T+t

2.优先级

3.offset

哨兵功能作用:

1.监控(monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。

2.提醒(Notifation):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

3.自动故障转移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

6.1.4.8.2 搭建

哨兵配置文件

[root@localhost bin]# vim sentinel.conf

sentinel monitor 被监控名称 host port 2

注意:

后面的数字为2 表示什么也不做;1表示主机宕机后,slave会以投票的方式选举成为主机

超过30s不回应,就认为宕机了,此时是 sdown 主观宕机

超过3分钟没有回应,则为odown 客观宕机 

 

 之后从从节点里选择一个新的主节点

启动哨兵

[root@localhost bin]# ./redis-sentinel sentinel.conf

带着相应的配置文件启动

 如果Master节点断开了(主机宕机了),过一会,会发送哨兵日志,并自动通过算法在其他两个从机中选择一个成为主机.

 通过选举,将6381设置为主节点

 如果之前的主机6379又重新启动了,过一会,哨兵检测到了之后,会把6379设置为从节点

6.1.4.8.3 哨兵模式的优缺点

优点

1.哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。

2.主从可以切换,故障可以转移,系统可用性更好。

3.哨兵模式是主从模式的升级,系统更健壮,可用性更高。

缺点

1.Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

2.实现哨兵模式的配置也不简单,甚至可以说有些繁琐

6.1.4.9 Redis缓存穿透,击穿,雪崩,倾斜

6.1.4.9.1 缓存穿透

简单来说:查不到

当用户去查询数据的时候,发现redis内存数据库中没有,于是向持久层数据库查询,发现也没有,于是查询失败,当用户过多时,缓存都没有查到,于是都去查持久层数据库,这会给持久层数据库造成很大的压力,此时相当于出现了缓存穿透。

解决方案

  1. 布隆过滤器:是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力.
  2. 缓存空对象:当存储层查不到时,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护后端数据.

但会有两个问题:

  1. 如果空值被缓存起来,就意味着需要更多的空间存储更多的键,会有很多空值的键.
  2. 即使对空值设置了过期时间,还是会存在 缓存层和存储层会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响.

6.1.4.9.2 缓存击穿

简单来说:访问量大,缓存过期

指对某一个key的频繁访问,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会直接请求数据库,就像在一个屏障上凿开了一个洞,例如微博由于某个热搜导致宕机.

其实就是:当某个key在过期的瞬间,有大量的请求并发访问,这类数据一段是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并回写缓存,导致数据库瞬间压力过大。

解决方案:

  1. 设置热点数据永不过期:从缓存层面上来说,不设置过期时间,就不会出现热点key过期后产生的问题.
  2. 添加互斥锁:使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁上,对分布式锁也是一种极大的考验.

6.1.4.9.3 缓存雪崩

 指在某一个时间段,缓存集中过期失效或Redis宕机导致的,例如双十一抢购热门商品,这些商品都会放在缓存中,假设缓存时间为一个小时,一个小时之后,这些商品的缓存都过期了,访问压力瞬间都来到了数据库上,此时数据库会产生周期性的压力波峰,所有的请求都会到达存储层,存储层的调用量暴增,造成存储层挂掉的情况.

其实比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,此时的数据库还是可以顶住压力的,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,有可能瞬间就把服务器压垮.

解决方案:

  1. 配置Redis的高可用:其实就是搭建集群环境,有更多的备用机.
  2. 限流降级:在缓存失效后,通过加锁或者队列来控制读服务器以及写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待.
  3. 数据预热:在项目正式部署之前,把可能用的数据预先访问一边,这样可以把一些数据加载到缓存中,在即将发生大并发访问之前手动触发加载缓存中不同的key,设置不同的过期时间,让缓存失效的时间尽量均衡.

6.1.4.9.4 缓存倾斜

 指某一台redis服务器压力过大而导致该服务器宕机.

6.1.4.10 Redis其他常见问题

6.1.4.10.1 key的生存时间到了,Redis会立即删除吗?

不会立即删除

  1. 定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。
  2. 惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。

6.1.4.10.2 Redis的淘汰机制

在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。(redis.conf中配置)

  1. volatile-lru:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少使用的key。
  2. allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的key。
  3. volatile-random:在内存不足时,Redis会在设置过了生存时间的key中随机干掉一个。
  4. allkeys-random:在内存不足时,Redis会在全部的key中随机干掉一个。
  5. volatile-ttl:在内存不足时,Redis会在设置过了生存时间的key中干掉一个剩余生存时间最少的key。
  6. noeviction:(默认)在内存不足时,直接报错。

方案:指定淘汰机制的方式:maxmemory-policy具体策略,设置Redis的最大内存:maxmemory 字节大小

6.1.4.11 Redis集群搭建

6.2.4 SpringBoot整合Redis

引入Redis启动器

<!--redis启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

创建测试类

 RedisTest



import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import java.util.Map;
import java.util.Set;

@RunWith(SpringRunner.class)
//@RunWith(SpringRunner.class)注解:是一个测试启动器,可以加载SpringBoot测试注解
// 让测试在Spring容器环境下执行。如测试类中无此注解,将导致service,dao等自动注入失败
@SpringBootTest
public class RedisTest {

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;

    @Test
    public void testString(){
        //两种添加数据,获取数据的方法
        // ValueOperations<Object, Object> opsForValue = redisTemplate.opsForValue();
        // opsForValue.set("username", "张三");
        // Object username = opsForValue.get("username");

        redisTemplate.boundValueOps("username").set("张三");
        Object username = redisTemplate.boundValueOps("username").get();
        System.out.println(username);
    }

    @Test
    public void testList(){
        redisTemplate.opsForList().leftPush("mylist", "a");
        redisTemplate.opsForList().leftPush("mylist", "b");
        redisTemplate.opsForList().leftPush("mylist", "b");
        redisTemplate.opsForList().leftPush("mylist", "c");
        redisTemplate.opsForList().leftPush("mylist", "d");
        List<Object> mylist = redisTemplate.opsForList().range("mylist", 0, -1);
        System.out.println(mylist);

        // redisTemplate.boundListOps("mylist")
    }

    @Test
    public void testSet(){
        redisTemplate.boundSetOps("myset").add("java","python");
        Set<Object> set = redisTemplate.boundSetOps("myset").members();
        System.out.println(set);
    }

    @Test
    public void testHash(){
        redisTemplate.boundHashOps("myHash").put("name","zhangsan");
        redisTemplate.boundHashOps("myHash").put("age","18");
        redisTemplate.boundHashOps("myHash").put("address","beijing");
        Map<Object, Object> myHash = redisTemplate.boundHashOps("myHash").entries();
        Object name = redisTemplate.boundHashOps("myHash").get("name");
        System.out.println(name);
        System.out.println(myHash);
    }

    @Test
    public void testZset(){
        redisTemplate.opsForZSet().add("myZset", "zhangsan", 20);
        redisTemplate.opsForZSet().add("myZset", "lisi", 25);
        redisTemplate.opsForZSet().add("myZset", "jack", 35);
        Set<Object> myZset = redisTemplate.opsForZSet().range("myZset", 0, -1);
        System.out.println(myZset);
    }


}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
R的极客理想:工具篇 带书签扫描版(1/2) 目录 封面 -20 书名 -18 版权 -17 前言 -16 目录 -7 第一部分 R基础 1 第1章 R语言基础包 2 1.1 R是最值得学习的编程语言 1.2 R的历史版本安装 8 1.3 fortunes记录R语言的大智慧 11 1.4 formatR代码自动化排版 13 1.5 多人在线协作R开发RStudio Server 22 1.6 R和JSON的傻瓜式编程 29 1.7 R语言的高质量图形渲染库Cairo 40 1.8 caTools:一个奇特的工具集 46 第2章 时间序列基础包 58 2.1 R语言时间序列基础库zoo 58 2.2 可扩展的时间序列xts 75 2.3 时间序列可视化plot.xts 93 第3章 R性能监控包 104 3.1 R语言本地缓存工具memoise 104 3.2 R语言性能监控工具Rprof 108 3.3 R语言性能可视化工具lineprof 116 第二部分 R服务器 122 第4章 R语言的跨平台通信 122 4.1 Rserve与Java的跨平台通信 122 4.2 Rsession让Java调用R更简单 126 4.3 解惑rJava R与Java的高速通道 132 4.4 Node.js与R跨平台通信 137 第5章 R的服务器实现 143 5.1 R语言服务器程序Rserve详解 143 5.2 Rserve的R语言客户端RSclient 149 5.3 FastRWeb:跑在Web上的R程序 153 5.4 R语言构建Websocket服务器 159 第三部分 数据库和大数据 168 第6章 数据库和NoSQL 168 6.1 RMySQL数据库编程指南 168 6.2 R利剑NoSQL 之MongoDB 183 6.3 R利剑NoSQL之Redis 192 6.4 R利剑NoSQL之Cassandra 200 6.5 R利剑NoSQL之Hive 206 6.6 用RHive从历史数据中提取逆回购信息 212 第7章 RHadoop 222 7.1 R语言为Hadoop注入统计血脉 222 7.2 RHadoop安装与使用 226 7.3 RHadoop实验:统计邮箱出现次数 233 7.4 RHadoop实现基于MapReduce的协同过滤算法 236 7.5 rHBase安装与使用 249 7.6 解决RHadoop安装错误:PipeMapRed.waitOutputThreads() 253 第四部分 附录 262 附录A Java环境安装 262 附录B MySQL数据库安装 266 附录C Redis数据库安装 270 附录D MongoDB数据库安装 273 附录E Cassandra数据库安装 277 附录F Hadoop安装 280 附录G Hive环境安装 287 附录H HBase安装 290

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老李头喽

高级内容,进一步深入JA领域

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值