redis基础

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

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

io多路复用技术解释:

redis支持ipv6配置 redis ipv6_spring


上面文字描述中的进入一个地方是指:

redis支持ipv6配置 redis ipv6_spring_02

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

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_03

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版本

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_04

3.2 安装redis

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_05


redis支持ipv6配置 redis ipv6_spring_06

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

redis支持ipv6配置 redis ipv6_redis_07

3.4 前台启动

redis支持ipv6配置 redis ipv6_数据库_08

3.5 后台启动

前提条件:

redis支持ipv6配置 redis ipv6_缓存_09


后台启动:

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

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_10

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

redis支持ipv6配置 redis ipv6_spring_11


redis支持ipv6配置 redis ipv6_spring_12

4、redis介绍及相关知识

redis使用到的是:

单线程+多路io复用

特点:

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

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

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

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

redis支持ipv6配置 redis ipv6_数据库_13

6.1、 key的命令

redis支持ipv6配置 redis ipv6_缓存_14

6.2、String(字符串)

6.2.1 简介

redis支持ipv6配置 redis ipv6_redis_15

6.2.2 常用命令

redis支持ipv6配置 redis ipv6_数据库_16


redis支持ipv6配置 redis ipv6_缓存_17


redis支持ipv6配置 redis ipv6_数据库_18


redis支持ipv6配置 redis ipv6_缓存_19

6.2.3 string的数据结构

redis支持ipv6配置 redis ipv6_缓存_20

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 常用命令

redis支持ipv6配置 redis ipv6_数据库_21

6.3.3 数据结构

List的数据结构为快速链表quickList。

redis支持ipv6配置 redis ipv6_数据库_22

6.4、 Set 集合

6.4.1 简介

redis支持ipv6配置 redis ipv6_缓存_23

6.4.2 常用命令

redis支持ipv6配置 redis ipv6_redis_24


redis支持ipv6配置 redis ipv6_redis支持ipv6配置_25

6.4.3 数据结构

redis支持ipv6配置 redis ipv6_缓存_26

6.5、Hash(哈希)

6.5.1、简介

redis支持ipv6配置 redis ipv6_redis_27


使用场景:

redis支持ipv6配置 redis ipv6_redis_28

6.5.2、常用命令

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_29

6.5.3、数据结构

redis支持ipv6配置 redis ipv6_数据库_30

6.6、Zset(Redis有序集合)

6.6.1 简介

redis支持ipv6配置 redis ipv6_缓存_31

6.6.2 命令

redis支持ipv6配置 redis ipv6_缓存_32


redis支持ipv6配置 redis ipv6_redis支持ipv6配置_33

6.6.3 数据结构

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_34

redis高级

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

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

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

redis支持ipv6配置 redis ipv6_数据库_35

9、redis的发布和订阅

  1. 定义:

redis支持ipv6配置 redis ipv6_缓存_36

  1. :redis的发布和订阅

redis支持ipv6配置 redis ipv6_数据库_37

  1. 案例图解:
    订阅者:

    发布者:

10、redis的新数据类型

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

10.1.1 简介

redis支持ipv6配置 redis ipv6_缓存_38

10.1.2 命令

10.1.2.1 setbit设置值

redis支持ipv6配置 redis ipv6_spring_39


10.1.2.2 getbit获取值

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_40


redis支持ipv6配置 redis ipv6_缓存_41


10.1.2.3 bitcount 统计字符串被设置为1的bit数。

redis支持ipv6配置 redis ipv6_缓存_42


10.1.2.4 bitop (bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。)

and的测试:

redis支持ipv6配置 redis ipv6_缓存_43

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

10.2.1 简介

redis支持ipv6配置 redis ipv6_缓存_44

10.2.2 命令

redis支持ipv6配置 redis ipv6_数据库_45


redis支持ipv6配置 redis ipv6_数据库_46


redis支持ipv6配置 redis ipv6_redis_47

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

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

12、redis事务

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

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

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

redis支持ipv6配置 redis ipv6_spring_48

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

redis支持ipv6配置 redis ipv6_redis_49

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

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_50

  1. 开启事务,放在队列中的命令有错误的,执行事务时,会将队列中其他的命令执行,错误的命令报错:

13、乐观锁和悲观锁

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

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

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_51

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

redis支持ipv6配置 redis ipv6_数据库_52

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

  1. 成功案例:
  2. 失败案例:

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

redis支持ipv6配置 redis ipv6_redis_53

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

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_54

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

15、持久化操作RDB和AOF

15.1、RDB (看文档)

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

redis支持ipv6配置 redis ipv6_spring_55

15.2、AOF

15.2.1、什么是AOF

redis支持ipv6配置 redis ipv6_spring_56

15.2.2、持久化流程

redis支持ipv6配置 redis ipv6_缓存_57

15.2.3、恢复数据

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

15.2.4、开启aof

redis支持ipv6配置 redis ipv6_数据库_58

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

redis支持ipv6配置 redis ipv6_缓存_59

15.2.6、aof同步频率

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_60

15.2.7、Rewrite压缩(看文档)

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

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

redis支持ipv6配置 redis ipv6_spring_61


redis支持ipv6配置 redis ipv6_redis支持ipv6配置_62

16、主从复制

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

16.2、搭建1主2从

16.2.1、前置准备

redis支持ipv6配置 redis ipv6_redis_63

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

redis支持ipv6配置 redis ipv6_缓存_64

16.2.3、启动服务,查看进程

redis支持ipv6配置 redis ipv6_spring_65

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

redis支持ipv6配置 redis ipv6_缓存_66

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

redis支持ipv6配置 redis ipv6_spring_67

16.2.6、一点注意事项

redis支持ipv6配置 redis ipv6_数据库_68

16.3、主从复制原理

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_69

16.4、薪火相传

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_70

16.5、反客为主(slaveof no one)

redis支持ipv6配置 redis ipv6_redis_71

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

redis支持ipv6配置 redis ipv6_缓存_72

17.1、前期准备配置

redis支持ipv6配置 redis ipv6_缓存_73

17.2、启动哨兵

redis支持ipv6配置 redis ipv6_数据库_74

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

redis支持ipv6配置 redis ipv6_缓存_75

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

redis支持ipv6配置 redis ipv6_spring_76

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、什么是集群

redis支持ipv6配置 redis ipv6_spring_77

18.2、配置

redis支持ipv6配置 redis ipv6_spring_78


redis支持ipv6配置 redis ipv6_spring_79


redis支持ipv6配置 redis ipv6_redis支持ipv6配置_80

18.3 、启动6个redis服务

redis支持ipv6配置 redis ipv6_数据库_81

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

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_82


redis支持ipv6配置 redis ipv6_数据库_83

18.5、进入集群

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_84

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

redis支持ipv6配置 redis ipv6_缓存_85

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

redis支持ipv6配置 redis ipv6_缓存_86


redis支持ipv6配置 redis ipv6_spring_87

18.8、在集群中录入值

redis支持ipv6配置 redis ipv6_redis_88

18.9、cluster 查询集群中的值

redis支持ipv6配置 redis ipv6_缓存_89

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

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

redis支持ipv6配置 redis ipv6_数据库_90

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缓存中没有,数据库中查询也没有,一直重复这种操作,最终导致数据库压力过大,崩溃)—多见于黑客攻击

redis支持ipv6配置 redis ipv6_redis_91

19.1.2、解决方案

redis支持ipv6配置 redis ipv6_spring_92

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

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

redis支持ipv6配置 redis ipv6_redis_93

19.2.2、解决方案

redis支持ipv6配置 redis ipv6_数据库_94

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

19.3.1、定义

redis支持ipv6配置 redis ipv6_redis_95

19.3.2、解决方案

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_96

20、分布式锁

20.1、上锁:

redis支持ipv6配置 redis ipv6_redis支持ipv6配置_97

20.2、释放锁:

redis支持ipv6配置 redis ipv6_spring_98

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新功能