缓存机制说明

问题1: 为什么用户直接操作数据库效率低?

链接创建/销毁需要大量的时间 JDBC/数据库链接池

问题2: 为什么创建链接耗时?

使用TCP协议 3次握手机制 4次挥手操作

说明: 缓存机制实质是为了降低用户访问物理设备的频次,减少用户的交互的时间,提高数据响应的能力.

一、实现缓存机制要素

1.数据结构应该采用什么类型? 采用K-V结构
2.内存特点断电即擦除如何防止? 将内存数据进行持久化操作(存到磁盘中)
3.内存容量有限,如何只保存热点数据? LRU算法/LFU算法/TTL算法/随机算法
4.为了与硬件交互性强,所以采用C语言的方式进行开发

二、Redis介绍

官网:https://redis.io/
中文网:http://www.redis.cn/
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库(非关系型数据库)缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmaps,hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

数据: 读: 11.2万次/秒
写: 8.6万次/秒
平均 10万次/秒

三、安装redis

1)上传安装包
安装包版本5.0.4(centos7目前对redis6.x.x的兼容性不好)
2)解压安装包并改名

tar -zxvf redis-5.0.4.tar.gz
mv redis-5.0.4 redis

3)跳入到redis根目录(cd redis)中执行 make make install命令
(1)make 执行效果
image.png
(2)make install效果
image.png
4)说明:redis的启动依赖核心配置文件redis.conf
修改redis配置文件:(注释部分的wxz为本人名字缩写,请忽略!!!)
(1)将IP绑定注释
image.png
(2)关闭保护模式
image.png
(3)开启后台启动
image.png

四、Redis入门命令

1.启动命令: redis-server redis.conf
2.进入客户端: redis-cli -p 6379
3.关闭redis: redis-cli -p 6379 shutdown

五、String类型的命令

命令说明案例
set添加key-valueset username admin
get根据key获取数据get username
strlen根据key获取值的长度strlen key
exists判断key是否存在exists name 返回1存在 0不存在
del删除redis中的keydel key
Keys用于查询符合条件的keykeys 星 查询redis中全部的key,keys n?me 使用占位符获取数据,keys nam* 获取nam开头的数据
mset赋值多个key-valuemset key1 value1 key2 value2 key3 value3
mget获取多个key的值mget key1 key2
append对某个key的值进行追加append key value
type检查某个key的类型type key
select切换redis数据库select 0-15 redis中共有16个数据库
flushdb清空单个数据库flushdb
flushall清空全部数据库flushall
incr自动加1incr key
decr自动减1decr key
incrby指定数值添加incrby key 10
decrby指定数值减decrby key 10
expire指定key的生效时间 单位秒expire key 20 key20秒后失效
pexpire指定key的失效时间 单位毫秒pexpire key 2000key 2000毫秒后失效
ttl检查key的剩余存活时间ttl key -2数据不存在 -1该数据永不超时
persist撤销key的失效时间persist key

六、Redis入门

1)导入jar包

        <!--spring整合redis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>

2)编辑测试案例
setnx 如果数据存在 不做任何操作,如果数据不存在则赋值。
setex 设置超时时间时,要保证操作的原子性。要么同时成功,要么同时失败

//如果数据存在 不做任何操作,如果数据不存在则赋值  
    @Test
    public void test02(){
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        jedis.flushAll();
        //jedis.set("redis", "AAA");
        jedis.setnx("redis","BBB");
        System.out.println(jedis.get("redis"));
    }

    //添加一个数据,并且设定超时时间 10秒
    //设定超时时间应该注意
    // 原子性: 要么同时成功/要么同时失败. 
    @Test
    public void test03() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //jedis.set("a","设定超时时间");
        //jedis.expire("a", 10);
        jedis.setex("a", 10, "设定超时时间");
        Thread.sleep(2000);
        System.out.println("剩余时间" + jedis.ttl("a"));
    }

    /**
     * 1.如果数据存在,则不允许修改  反之允许修改
     * 2.同时为数据设定超时时间. 10秒
     * 3.上述操作需要满足原子性要求
     *  XX: 存在才可以操作
     *  NX: 不存在才可以操作
     *  PX: 毫秒
     *  EX: 秒
     */
    @Test
     public void test04(){
         Jedis jedis = new Jedis("192.168.126.129", 6379);
         SetParams setParams = new SetParams();
         setParams.nx().ex(10);
         jedis.set("b", "xxxx",setParams);
         System.out.println(jedis.get("b"));
     }

七、hash类型的命令

说明:可以用散列类型保存对象和属性值
应用场景:一般在工作中存储的都是基于一个业务对象的数据
例子:User对象{id:2,name:小明,age:19}

命令说明案例
hset为对象添加数据hset key field value
hget获取对象的属性值hget key field
hexists判断对象的属性是否存在HEXISTS key field 1表示存在 0表示不存在
hdel删除hash中的属性hdel user field [field ...]
hgetall获取hash全部元素和值HGETALL key
hkyes获取hash中的所有字段HKEYS key
hlen获取hash中所有属性的数量hlen key
hmget获取hash里面指定字段的值hmget key field [field ...]
hmset为hash的多个字段设定值hmset key field value [field value ...]
hsetnx设置hash的一个字段,只有当这个字段不存在时有效HSETNX key field value
hstrlen获取hash中指定key的值的长度HSTRLEN key field
hvals获取hash的所有值HVALS user

举例:
一个订单下来,包含用户信息,订单信息,物流信息...

hset orderId user "用户信息"
hset orderId order "订单信息"
hset orderId shipping "物流信息"

八、list类型的命令

说明:Redis中的List集合是双端循环链表,分别可以从左右两个方向插入数据.
List集合可以当做队列使用,也可以当做栈使用
队列: 存入数据的方向和获取数据的方向相反
栈: 存入数据的方向和获取数据的方向相同

命令说明案例
lpush从队列的左边入队一个或多个元素LPUSH key value [value ...]
rpush从队列的右边入队一个或多个元素RPUSH key value [value ...]
lpop从队列的左端出队一个元素LPOP key
rpop从队列的右端出队一个元素RPOP key
lpushx当队列存在时从队列的左侧入队一个元素LPUSHX key value
rpushx当队列存在时从队列的右侧入队一个元素RPUSHx key value
lrange从列表中获取指定返回的元素LRANGE key start stop Lrange key 0 -1 获取全部队列的数据
lrem从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:•count > 0: 从头往尾移除值为 value 的元素。• count < 0: 从尾往头移除值为 value 的元素。•count = 0: 移除所有值为 value 的元素。LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。
Lset设置 index 位置的list元素的值为 valueLSET key index value

关于秒杀业务
redis没有线程并发安全性问题:单进程,单线程问题
悲观锁:同步代码块
乐观锁:数据库控制(加版本号,成功购买了,版本号加1)
image.png
若update执行结果为0,表示没有更新数据库,即没有抢到商品。
悲观锁,乐观锁都不好,都增加数据库的压力。故可以用redis来实现秒杀。

九、redis事务命令

redis中有事务控制。但是该事务控制是通过队列的方式实现的。
说明:redis中操作可以添加事务的支持.一项任务可以由多个redis命令完成,如果有一个命令失败导致入库失败时.需要实现事务回滚.

命令说明案例
multi标记一个事务开始127.0.0.1:6379> MULTI OK
exec执行所有multi之后发的命令127.0.0.1:6379> EXEC OK
discard丢弃所有multi之后发的命令

十、Redis 持久化策略说明(默认开启的就是rdb模式)(redis.conf)

1.需求说明

如果redis服务器宕机/或者断电 则可能会导致整个内存数据丢失. 为了防止该现象的发生,redis内部引入持久化机制.
持久化: 将内存数据按照特定的规则保存到磁盘中.

2.RDB模式

2.1 RDB模式说明
1) RDB模式是Redis默认持久化机制.
2) RDB模式记录的是内存数据的快照,持久化效率更高. (只会保留当前最新数据)
3) RDB模式是定期持久化. 也可以手动操作. save(同步) 用户操作可能阻塞 /bgsave 后端运 行(异步)登录redis客户端后执行这两条命令(redis-cli)
4).由于是定期备份,所以可能导致数据丢失.

2.2.2 RDB模式配置项(dump.rdb)
Redis 重启会通过加载dump.rdb文件恢复数据。
1).持久化策略

save 900 1       900秒 1次更新,则持久化一次
save 300 10      300秒 10次更新 则持久化一次
save 60 10000    60秒 10000更新 则持久化一次
save 1 1         效率极低. 不要这样配置 压测之后进行适当的调整.

2).持久化文件名称
image.png
3).持久化文件位置
image.png

2.3 AOF模式

2.3.1 AOF模式说明(appendonly.aof
1).默认条件下AOF模式处于关闭状态,如果需要开启,则手动配置(appendonly yes)
2).AOF模式记录用户的操作的过程,可以实现实时的持久化操作, 持久化文件相对较大维护不易.
3).如果同时开启了 RDB与AOF模式,则默认以AOF模式为准.但是用户可以手动执行save命令,此时就会生成rdb文件。
4).AOF模式是一种异步的操作,不会影响程序的正常使用

2.3.2 AOF模式配置
1).开启AOF配置
image.png
2).日志信息展现
image.png
3).AOF模式持久化策略
appendfsync always 用户执行一步操作,则持久化一次
appendfsync everysec 每秒持久化一次 性能略低于RDB模式
appendfsync no 不主动持久化

2.3.3 持久化方式如何选择
1.如果允许少量的数据丢失 首选RDB模式 快
2.如果不允许数据丢失, 首选AOF模式.
3.工作中一般如何选择 主机 选用RDB模式 从机 选用AOF模式.

2.3.4 关于持久化面试题
公司新来了个漂亮的实习生. 去生产环境下,误操作了flushall命令,问你作为主管 如何处理?
解决方案:
前提:持久化策略配的是AOF,如果是RDB则无法补救。
1.进入AOF文件中,修改文件信息.,将fluashAll命令删除
2.重启redis即可。

2.4 Redis 内存策略

2.4.1 需求介绍
Redis中的数据,保存到内存中. 但是内存资源相对较少.如何保证其中的数据都是热点数据呢

2.4.2 内存优化-LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的数据置换算法,选择最近最久未使用的数据予以淘汰。该算法赋予每个数据一个访问字段,用来记录一个数据自上次被访问以来所经历的时间 t,当须淘汰一个数据时,选择现有数据中其 t 值最大的,即最近最少使用的数据予以淘汰。
维度: 时间T

2.4.3 内存优化-LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度: 使用次数

2.4.4 内存优化-Random算法
2.4.5 内存优化-TTL算法
根据剩余的存活时间,排序,根据时间少的删除数据.

2.4.6 修改内存优化策略

volatile-lru 在设定超时时间的数据中采用LRU算法

allkeys-lru 在所有数据中采用LRU算法

volatile-lfu 在设定了超时时间的数据采用LFU机制进行计算

allkeys-lfu 在所有数据中采用LFU算法

volatile-random 设定超时时间的随机

allkeys-random 所有数据的随机算法

volatile-ttl 在设定了超时时间的数据中,采用TTL算法

noeviction 默认规则 如果内存满了,则不做任何操作 直接报错返回.

2.4.7 设定内存优化策略
image.png

十一、Redis分片机制

1 分片机制说明
说明:由于电商网站中的数据量一般很大,如果只是使用单个redis节点进行数据存储,则不能完成任务.所以需要准备多台Redis公共实现内存数据的扩容.
image.png
2 准备3台redis
2.1 编辑配置文件
1).准备文件目录
image.png
2).修改端口号
image.png
2.2 启动多台redis
命令:

redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf

image.png
Redis分片案例测试(操作ShardedJedis对象、JedisShardInfo)
image.png
3 一致性Hash算法
3.1 算法介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
3.2 原理说明
常识:
1.常见hash函数8位16进制数. (2^4)8 = 2^32 种可能性
2.如果对相同的数据进行hash计算 值必然相同.
3.如果值相同.,则数据不一定相同 hash碰撞.
image.png
3.3 特性-平衡性(均衡性)
①平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 [4] 。
说明: 通过虚拟节点实现数据的平衡
image.png
.3.4 特性-单调性
②单调性是指在新增或者删减节点时,不影响系统正常运行 [4] 。
特点: 如果实现数据迁移时,应该尽可能保证原有的数据不变.

3.3.5 特性-分散性
③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据 [4] 。
解释: 鸡蛋不要放到一个篮子里.

十二、Redis哨兵机制(一主两从)

1.1 分片机制存在的问题
如果redis分片机制其中有一台redis宕机,则整个Redis分片将不能正常的使用. 分片机制没有高可用的效果.

1.2 哨兵机制说明
1.2.1 配置哨兵的前提
说明:实现redis数据同步(主从数据同步),是实现哨兵配置的前提条件.

1.2.2 复制文件
image.png
1.2.3删除多余的持久化文件
image.png
删除完成之后,依次启动redis.如图
image.png
1.2.4 实现主从挂载
主机: 6379
从机: 6380/6381
命令:

  1. 检查节点的状态 info replication
    image.png
    2.主从挂载命令
    image.png
  2. 主从检查
    image.png
    1.3 哨兵工作原理
    image.png
    步骤:
    1).当哨兵启动时,会动态的监控主机,之后利用PING-PONG 心跳检测机制,检查主机是否正常.
    2).哨兵链接主机之后,获取相关的主从的服务信息. 方便以后选举.
    3).当哨兵发现主机宕机,之后采用随机算法 选择新的主机. 之后其他节点当新主机的从.
    1.4 哨兵配置
    1.4.1 复制文件
    image.png
    1.4.2 修改哨兵配置文件
    1).关闭保护模式
    !image.png
    2).开启后端运行
    image.png
    3).监控主机
    目前就一个哨兵,所以1票就生效
    哨兵设置为奇数个
    image.png
    4).设定哨兵选举的时间(当主机宕机后,多久进行选举)
    image.png
    1.4.3 哨兵高可用测试
    前提:
    由于之前搭建好redis主从,故3台redis都是启动状态。
    image.png
    1).启动哨兵
    image.png
命令:
redis-sentinel sentinel.conf

2).哨兵高可用测试
第一步: 检查redis主机的状态.
第二步: 关闭redis主机.
第三步: 等待10秒 检查从机是否切换为主机.
image.png
此时配置文件(sentinel.conf)中也会自动发生改变(随机选举):
image.png
第四步: 重启主机,检查是否为新主机的从.
image.png
1.4.4 哨兵入门案例

    /**
     * 实现redis哨兵测试
     * mymaster 配置文件(sentinel.conf)中写的就是mymaster
     * 哨兵只连接主机,不连接从机
     */
    @Test
    public void testSentinel(){
        //1.链接哨兵的集合
        Set<String> sentinels = new HashSet<>();
        sentinels.add("192.168.126.129:26379");
        JedisSentinelPool pool = new JedisSentinelPool("mymaster",sentinels);
        Jedis jedis = pool.getResource();
        jedis.set("aaa", "aaaaaaa");
        System.out.println(jedis.get("aaa"));

spring整合:
1.把池交给Spring管理。

    @Bean
    public JedisSentinelPool jedisSentinelPool(){
        //1.设定连接池大小
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMinIdle(5);    //设定最小的空闲数量
        poolConfig.setMaxIdle(10);   //设定最大空闲数量
        poolConfig.setMaxTotal(100); //最大链接数

        //2.链接哨兵的集合
        Set<String> sentinels = new HashSet<>();
        sentinels.add(sentinel);
        return new JedisSentinelPool("mymaster",sentinels,poolConfig);
    }

2.注入这个池对象,并获取资源

@AutoWired
private JedisSentinelPool sentinelPool ;

//从池中, 动态获取数据
Jedis jedis = sentinelPool.getResource();

jedis.close();  //用完之后,关闭(还)链接;

分片哨兵总结

1.分片主要实现了内存扩容, 没有高可用的效果.
2.哨兵主要实现了高可用效果, 没有实现内存数据的扩容. 哨兵本身没有高可用的效果.
如何优化: 内存扩容,节点实现高可用 redis集群实现.

十三、Redis集群搭建:

集群搭建


summer
30 声望3 粉丝

这一路我披荆斩棘,只为遇见你