redis6 跟着b站尚硅谷学习

redis基础

1、redis简介(省略)–redis是单线程

1.1、redis快的原因:内存中操作 + io多路复用技术

io多路复用技术解释:
在这里插入图片描述
上面文字描述中的进入一个地方是指:
在这里插入图片描述

2、redis 多样的数据结构存储持久化数据(redis支持的使用场景)

在这里插入图片描述

3、linux下安装redis

3.1 安装redis之前,下载安装最新版的gcc编译器

安装C 语言的编译环境:
yum install centos-release-scl scl-utils-build
yum install -y devtoolset-8-toolchain
scl enable devtoolset-8 bash

测试 gcc版本
在这里插入图片描述

3.2 安装redis

在这里插入图片描述
在这里插入图片描述

3.3 安装目录查看及文件讲解

在这里插入图片描述

3.4 前台启动

在这里插入图片描述

3.5 后台启动

前提条件:
在这里插入图片描述
后台启动:

redis-server /myredis/redis.conf (这里的 redis.conf 是从安装目录下面复制到myredis中来的)

在这里插入图片描述

3.6 redis关闭(redis-cli shutdown或者shutdown)

在这里插入图片描述
在这里插入图片描述

4、redis介绍及相关知识

redis使用到的是:

单线程+多路io复用

特点:

支持多数据类型,支持持久化,单线程+多路IO复用

5、获得redis常见数据类型操作命令的网站地址

http://www.redis.cn/commands.html

6、常用5大类型和 key,value的操作命令

在这里插入图片描述

6.1、 key的命令

在这里插入图片描述

6.2、String(字符串)

6.2.1 简介

在这里插入图片描述

6.2.2 常用命令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2.3 string的数据结构

在这里插入图片描述

1、String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.
2、 如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

6.3、List 列表

6.3.1 简介

1、Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
2、它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

6.3.2 常用命令

在这里插入图片描述

6.3.3 数据结构

List的数据结构为快速链表quickList。
在这里插入图片描述

6.4、 Set 集合

6.4.1 简介

在这里插入图片描述

6.4.2 常用命令

在这里插入图片描述
在这里插入图片描述

6.4.3 数据结构

在这里插入图片描述

6.5、Hash(哈希)

6.5.1、简介

在这里插入图片描述
使用场景:
在这里插入图片描述

6.5.2、常用命令

在这里插入图片描述

6.5.3、数据结构

在这里插入图片描述

6.6、Zset(Redis有序集合)

6.6.1 简介

在这里插入图片描述

6.6.2 命令

在这里插入图片描述
在这里插入图片描述

6.6.3 数据结构

在这里插入图片描述

redis高级

7、redis配置文件(看文档)

8、使用本地的redis desktop manager连接远程linux中的redis

需要修改redis.conf,将bind 127.0.0.1注释掉(表示只能本机连接);将protected-mode 改成no(后台启动)

在这里插入图片描述

9、redis的发布和订阅

  1. 定义:

在这里插入图片描述

  1. :redis的发布和订阅

在这里插入图片描述

  1. 案例图解:
    订阅者:
    在这里插入图片描述
    发布者:
    在这里插入图片描述

10、redis的新数据类型

10.1、 Bitmaps(以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。)

10.1.1 简介

在这里插入图片描述

10.1.2 命令

10.1.2.1 setbit设置值
在这里插入图片描述
10.1.2.2 getbit获取值
在这里插入图片描述
在这里插入图片描述
10.1.2.3 bitcount 统计字符串被设置为1的bit数。
在这里插入图片描述
10.1.2.4 bitop (bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。)
and的测试:
在这里插入图片描述

10.2、HyperLogLog (去重,然后统计数据,唯一的优势是需要的容量小)

10.2.1 简介

在这里插入图片描述

10.2.2 命令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.3、Geospatial (地理信息)-没啥用

11、jedis的入门+整合和redission的锁学习在(jedis 6—redisson的使用)文章中.

12、redis事务

1、Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2、Redis事务的主要作用就是串联多个命令防止别的命令插队。

总结:Redis的事务可以看做一个队列,将需要执行的命令放在队列中。

12.1 、Multi(开启事务)、Exec(执行事务)、discard(取消事务)

在这里插入图片描述

  1. 开启事务,执行事务成功的案例:

在这里插入图片描述

  1. 开启事务,在事务中报错,则会在事务执行的时候报错:

在这里插入图片描述

  1. 开启事务,放在队列中的命令有错误的,执行事务时,会将队列中其他的命令执行,错误的命令报错:
    在这里插入图片描述

13、乐观锁和悲观锁

注意:redis是单线程的,但是他不能处理多线程的并发问题,还是需要通过乐观锁或者悲观锁来处理

13.1 悲观锁(概念跟java中的悲观锁一样)

在这里插入图片描述

13.2 乐观锁(跟java中一样,根据版本号机制)

在这里插入图片描述

13.3 watch key[key …] (监控一个key/多个key。**如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断,事务就算执行了,里面的命令也没有用)

  1. 成功案例:
    在这里插入图片描述
  2. 失败案例:
    在这里插入图片描述

13.4、unwatch取消watch命令对所有key的监视

在这里插入图片描述

14、事务的三大特性(单独的隔离操作:因为是单线程,所以会顺序执行;没有隔离级别概念;不保证原子性)

在这里插入图片描述

实际案例:秒杀案例(看秒杀案例专题文章)–学完分布式锁,我觉得也可以用分布式锁+lua

15、持久化操作RDB和AOF

15.1、RDB (看文档)

在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

在这里插入图片描述

15.2、AOF

15.2.1、什么是AOF

在这里插入图片描述

15.2.2、持久化流程

在这里插入图片描述

15.2.3、恢复数据

Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

15.2.4、开启aof

在这里插入图片描述

15.2.5、修复aof文件(使用redis-check-aof)

在这里插入图片描述

15.2.6、aof同步频率

在这里插入图片描述

15.2.7、Rewrite压缩(看文档)

15.3、RDB和AOF哪个好(存储在磁盘上的话,2个都使用)

官方推荐两个都启用。
如果对数据不敏感,可以选单独用RDB。
不建议单独用 AOF,因为可能会出现Bug。
如果只是做纯内存缓存,可以都不用。

在这里插入图片描述
在这里插入图片描述

16、主从复制

16.1、正常是1主多从,考虑到1主的安全性,可以搭建集群,每一个节点里面都是1主多从的结构。

16.2、搭建1主2从

16.2.1、前置准备

在这里插入图片描述

16.2.2、配置1主机,2从机的.conf文件

在这里插入图片描述

16.2.3、启动服务,查看进程

在这里插入图片描述

16.2.4、info replication查看主机运行情况

在这里插入图片描述

16.2.5、slaveof 配从(库)不配主(库)

在这里插入图片描述

16.2.6、一点注意事项

在这里插入图片描述

16.3、主从复制原理

在这里插入图片描述

16.4、薪火相传

在这里插入图片描述

16.5、反客为主(slaveof no one)

在这里插入图片描述

17、哨兵模式(sentinel):当主机a挂掉之后,选择一个从机b作为主机,其他的从机中的master的地址+端口就变成了从机(主机)b,当主机a再次启动之后,会变成从机,其中的master也变成了从机(主机)b

在这里插入图片描述

17.1、前期准备配置

在这里插入图片描述

17.2、启动哨兵

在这里插入图片描述

17.3、当主机挂掉,从机选举中产生新的主机

在这里插入图片描述

17.4、哨兵模式有一个缺点,就是会复制延时(差不多会丢失10秒的数据)

在这里插入图片描述

17.5、设置主机挂掉之后,哪个从机变成master(slave-priority 100,值越小优先级越高)

17.6、java代码:哨兵sentinel + sentinenl池 + lua脚本(解决库存剩余容量的问题)的实际代码案例

service层
package com.bear.service;

import com.bear.config.JedisPoolUtil;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisSentinelPool;

import java.io.IOException;

/**
 * <简述>
 * <详细描述>
 *
 * @author LiuShanshan
 * @version $Id$
 */
@Service
public class SewckillServiceTwo {

    String secKillScript ="local userid=KEYS[1];\r\n" +
            "local prodid=KEYS[2];\r\n" +
            "local qtkey='sk:'..prodid..\":qt\";\r\n" +
            "local usersKey='sk:'..prodid..\":usr\";\r\n" +
            "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
            "if tonumber(userExists)==1 then \r\n" +
            "   return 2;\r\n" +
            "end\r\n" +
            "local num= redis.call(\"get\" ,qtkey);\r\n" +
            "if tonumber(num)<=0 then \r\n" +
            "   return 0;\r\n" +
            "else \r\n" +
            "   redis.call(\"decr\",qtkey);\r\n" +
            "   redis.call(\"sadd\",usersKey,userid);\r\n" +
            "end\r\n" +
            "return 1" ;


    public boolean doSecKill(String prodid,String uid) throws IOException {

        // 从redis线程池里面获取redis线程
        JedisSentinelPool jedisPoolInstance = JedisPoolUtil.getJedisFromSentinel();
        Jedis jedis = jedisPoolInstance.getResource();

//        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
//        Jedis jedis = jedisPoolInstance.getResource();
        //String sha1=  .secKillScript;
        String sha1=  jedis.scriptLoad(secKillScript);
        Object result= jedis.evalsha(sha1, 2, uid,prodid);

        String reString=String.valueOf(result);
        if ("0".equals( reString )  ) {
            System.err.println("已抢空!!");
        }else if("1".equals( reString )  )  {
            System.out.println("抢购成功!!!!");
        }else if("2".equals( reString )  )  {
            System.err.println("该用户已抢过!!");
        }else{
            System.err.println("抢购异常!!");
        }
        jedis.close();
        return true;
    }
}

util层(包装sentinel池)
package com.bear.config;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;
import java.util.Set;

public class JedisPoolUtil {
	private static volatile JedisPool jedisPool = null;

	private JedisPoolUtil() {
	}

	public static JedisPool getJedisPoolInstance() {
		if (null == jedisPool) {
			synchronized (JedisPoolUtil.class) {
				if (null == jedisPool) {
					JedisPoolConfig poolConfig = new JedisPoolConfig();
					poolConfig.setMaxTotal(200);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWaitMillis(100*1000);
					poolConfig.setBlockWhenExhausted(true);
					poolConfig.setTestOnBorrow(true);  // ping  PONG

					jedisPool = new JedisPool(poolConfig, "120.48.77.231", 6379, 60000 );
				}
			}
		}
		return jedisPool;
	}

	private static JedisSentinelPool jedisSentinelPool = null;

	public static JedisSentinelPool getJedisFromSentinel() {
		if (jedisSentinelPool == null) {
			Set<String> sentinelSet = new HashSet<>();
			sentinelSet.add("120.48.77.231:26379");

			JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
			jedisPoolConfig.setMaxTotal(200); //最大可用连接数
			jedisPoolConfig.setMaxIdle(32); //最大闲置连接数
			jedisPoolConfig.setMinIdle(5); //最小闲置连接数
			jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
			jedisPoolConfig.setMaxWaitMillis(100*1000); //等待时间
			jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong

			return new JedisSentinelPool("mymaster", sentinelSet, jedisPoolConfig, 60000);
		}
		return jedisSentinelPool;

	}
}

17.7、17.6遇到的坑,我的在linux中的sentile的配置主机master的配置里面的ip是linux的本地地址(127.0.0.1),要换成linux的真实地址

todo:上面说的坑,其实细想,应该是启动1个master和多个slave的时候,就应该设置他们的ip是真实ip(不使用127.0.0.1),然后在sentinel.conf中设置master的ip为真实ip。如果master挂掉,选举其他的slave为master,也能不用再改sentinel.conf中的配置将新的master的ip变成真实ip了。

18、集群(此处案例为1主1从,有三组)

18.1、什么是集群

在这里插入图片描述

18.2、配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

18.3 、启动6个redis服务

在这里插入图片描述

18.4、将6个节点合成一个集群

根据虚拟机里面的真实ip来:
redis-cli --cluster create --cluster-replicas 1 192.168.0.4:6379 192.168.0.4:6380 192.168.0.4:6381 192.168.0.4:6389 192.168.0.4:6390 192.168.0.4:6391

在这里插入图片描述
在这里插入图片描述

18.5、进入集群

在这里插入图片描述

18.6、cluster nodes 命令查看集群信息

在这里插入图片描述

18.7、slots插槽(数据库中的每个键都属于这 16384 个插槽的其中一个)

在这里插入图片描述
在这里插入图片描述

18.8、在集群中录入值

在这里插入图片描述

18.9、cluster 查询集群中的值

在这里插入图片描述

18.10、故障修复(某一个master挂掉之后,下面的从slave会变成master。等挂掉的master重启之后,会变成slave)

注意:如果主节点下线?从节点能否自动升为主节点?注意:15秒超时

在这里插入图片描述

18.11、java代码

public class JedisClusterTest {
  public static void main(String[] args) {
     Set<HostAndPort> set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("120.48.77.231",6379));
     JedisCluster jedisCluster=new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}

19、redis应用问题解决

19.1、缓存穿透(黑客恶意攻击)

19.1.1 定义(redis缓存中没有,数据库中查询也没有,一直重复这种操作,最终导致数据库压力过大,崩溃)—多见于黑客攻击

在这里插入图片描述

19.1.2、解决方案

在这里插入图片描述

19.2、缓存击穿(某个key过期)

19.2.1、定义(key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到redis缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。)

在这里插入图片描述

19.2.2、解决方案

在这里插入图片描述

19.3、缓存雪崩(有多个key过期)

19.3.1、定义

在这里插入图片描述

19.3.2、解决方案

在这里插入图片描述

20、分布式锁

20.1、上锁:

在这里插入图片描述

20.2、释放锁:

在这里插入图片描述

20.3、java代码中对redis分布式锁的操作

第一种:加锁+解锁+uuid防止误删
第二种:加锁+解锁+uuid防止误删+lua脚本保证删除的原子性

package com.atguigu.redis_springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/redisTest")
public class RedisTestController {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     *<简述>   优化之LUA脚本保证删除的原子性
     *<详细描述>
     * @author Liushanshan
     * @param
     * @return void
    */
    @GetMapping("testLockLua")
    public void testLockLua() {
        //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
        String uuid = UUID.randomUUID().toString();
        //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
        String skuId = "25"; // 访问skuId 为25号的商品 100008348542
        String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

        // 3 获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

        // 第一种: lock 与过期时间中间不写任何的代码。
        // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
        // 如果true
        if (lock) {
            // 执行的业务逻辑开始
            // 获取缓存中的num 数据
            Object value = redisTemplate.opsForValue().get("num");
            // 如果是空直接返回
            if (StringUtils.isEmpty(value)) {
                return;
            }
            // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
            int num = Integer.parseInt(value + "");
            // 使num 每次+1 放入缓存
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
            /*使用lua脚本来锁*/
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            // 设置一下返回值类型 为Long
            // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
            redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
        } else {
            // 其他线程等待
            try {
                // 睡眠
                Thread.sleep(1000);
                // 睡醒了之后,调用方法。
                testLockLua();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *<简述>   加锁,释放锁;加入uuid防误删
     *<详细描述>
     * @author Liushanshan
     * @param
     * @return void
    */
    @GetMapping("testLock")
    public void testLock(){
        String uuid = UUID.randomUUID().toString();
        //1获取锁,setne
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
        //2获取锁成功、查询num的值
        if(lock){
            Object value = redisTemplate.opsForValue().get("num");
            //2.1判断num为空return
            if(StringUtils.isEmpty(value)){
                return;
            }
            //2.2有值就转成成int
            int num = Integer.parseInt(value+"");
            //2.3把redis的num加1
            redisTemplate.opsForValue().set("num", ++num);
            //2.4释放锁,del
            //判断比较uuid值是否一样
            String lockUuid = (String)redisTemplate.opsForValue().get("lock");
            if(lockUuid.equals(uuid)) {
                redisTemplate.delete("lock");
            }
        }else{
            //3获取锁失败、每隔0.1秒再获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    @GetMapping
    public String testRedis() {
        //设置值到redis
        redisTemplate.opsForValue().set("name","lucy");
        //从redis获取值
        String name = (String)redisTemplate.opsForValue().get("name");
        return name;
    }
}

**20.4、分布式锁一般不使用redisTemplate,而是用redission

21、redis新功能(看文档)

21.1、acl

21.2、io多线程

21.3、工具支持cluster

21.4、redis新功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值