学习Redis6这一篇就够了


高性能,高可靠,高可扩展NoSQL数据库

前记

1、NoSQL数据库

1、概述

NoSQL = Not Only SQL 泛指非关系型数据库,不依赖业务逻辑存储,而以简单的key-value模式存储,因此大大增加了数据库的扩展能力

  • 不遵循SQL标准
  • 不支持ACID
  • 远超于SQL的性能

2、NoSQL适用场景

  • 对数据高并发的读写
  • 海量数据的读写
  • 对数据高可扩展性的

3、NoSQL不适用场景

  • 需要事务支持
  • 基于sql的结构化存储,处理复制的关系,需要即席查询
  • 用不着sql和用了sql也不行的情况,考虑用Nosql

2、Redis采用单线程+多路IO复用技术

1、与Memcache的三点不同:支持多数据类型,支持持久化,单线程+多路IO复用

2、多路复用指使用一个线程来检查多个文件描述符(Socket)的就绪状态,
比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就位,则返回,否则阻塞直到超时。
得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启用线程执行(比如使用线程池)、
就是说多路IO复用起到一个监视的效果,就绪后,Redis直接执行,不需要等待

3、串行 vs 采用多线程+锁(Memcached)vs 单线程+多路IO复用的比较

  • 1、串行:阻塞IO,一件事一件事的做,在做当前事时,不能做其他事,有等待
  • 2、采用多线程+锁:非阻塞IO,一直重复做当前的事,没有等待
  • 3、单线程+多路IO复用:做某件事情需要一定的时间,可以监视这件事,我们可以做其他的事。
    多路IO复用有select,poll,epoll这些模式。
    select监测数量能力有限。poll监测数量没有限制,但是需要一个一个核查。
    配epoll监测数量没有限制,也不需要一个一个核查,直接看是否有一个正确的标识。
    在这里插入图片描述

3、Redis的"两大维度,三大主线"

在这里插入图片描述



一、安装配置Redis

redis官网
1、下载最新版本tar.gz
在这里插入图片描述
2、安装C语言编译器gcc

yum install gcc

查看gcc版本
gcc --version

3、解压redis-tar.gz(解压到当前目录,本文的目录为/study,解压后为/study/redis-6.2.6)

tar -zxvf redis-6.2.6.tar.gz

4、编译为C文件(进入解压好的redis-6.2.6目录)

1、make
安装bin启动器到/study/redis-6.2.6目录下
2、make PREFIX=/study/redis-6.2.6 install 

5、拷贝一份redis.conf到conf目录

cp /study/redis.6.2.6/redis.conf /study/redis.6.2.6/conf/redis.conf

vim /study/redis.6.2.6/conf/redis.conf

开启后台运行
daemonize no 改为 yes

6、以配置文件后台启动(进入/study/redis-6.2.6/bin目录),连接客户端

./redis-server ../conf/redis.conf 
./redis-cli

7、关闭服务进程

客户端输入	showdown

ps -ef|grep redis
kill -9 pid


二、常用五大数据类型

Key

keys *  		查看所有key
exists <key>	判断key是否存在
type <key>		查看key是什么类型
del <key>  		删除指定key数据
unlink <key>    根据value选择非阻塞删除,仅将key从keyspace元数据删除,真正的删除会在后续异步操作
expire <key> 10 设置10秒过期时间
ttl <key>	 	查看存活时间	-1永不过期 -2已过期

select ?		切换数据库 默认0-15
dbsize			查看当前数据库key数量
flushdb			清空当前库
flushall		清空所有库

1、字符串(String)-- ArrayList

1、简介

简单动态字符串(Simple Dynamic String SDS)
类似ArrayList,采用预分配冗余空间的方式减少内存的频繁分配

二进制安全,最大存储512M

2、数据结构
在这里插入图片描述
3、命令

1、设值

set <key> v					设置单个键值
mset <key> v <key> v		设置多个键值
setex <key> <time> v		设置单个键值并设置过期时间 t 秒

setnx <key> v				当key不存在时设置
msetnx <key> v <key> v		当每个key不存在时设置

getset <key> v 				设置新值同时获取旧值

setrange <key> <index> v	覆写k所存储的v,从i下标开始追加

append <key> v				将给定的v追加到原值末尾 

2、查询

get <key>					查询单个键值
mget <key> <key>			查询多个键值

getrange <key> <s> <e>  	按下标查询键值

strlen <key>				获取值的长度

3、数字值操作(原子操作:得益于redis单线程,不会被其他线程打断的)

incr k			储存的数字值增 1
incrby k ?		储存的数字值增 ?
decr k 			储存的数字值减 1
decrby k ?		储存的数字值减 ?

2、列表(List)-- quickList

1、简介

单键多值
在这里插入图片描述
2、数据结构:
在这里插入图片描述
3、命令

例:从左插入v1 v2 v3,实际从左到右为v3 v2 v1
在这里插入图片描述

1、插吐值

lpush/rpush k v1 v2 v3		从左/右插入v1,v2,v3

lpop/rpop k					从左/右吐出一个值,吐完值键消失

rpoplpush k1 k2				从k1右边吐一个值插到k2左边

2、查询(并非吐值)

lrange k s e				按照索引下标范围获得元素s start, e end
lindex k i					按照索引下标获得元素
llen l						获取列表长度

3、插值

linsert k before "v1" "new v1"		在v1后面插入newv1

演示
在这里插入图片描述
4、特殊删除

lrem k 2 "v" 				删除k左边的两个"v"

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

5、替换

lset k i v					将k下标为i的值换为v

演示
在这里插入图片描述

3、集合(Set)-- Hash

1、简介

自动排重

String类型的无序集合
底层是一个value为null的hash表,增删查复杂度都为O(1)
一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变

2、数据结构

是dict字典,字典使用哈希表实现的
Java中的HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。
Redis的set结构也一样,内部也使用Hash结构,所有的value指向同一个对象

3、命令

sadd k v1 v2 v3				添加
smembers k					返回集合中所有元素
srandmember k n				随机从集合查询n个值
sismember k v				判断集合中是否有v
scard k						查询集合中元素个数

srem k v1 v2				删除集合中的某个元素
spop k						随机从集合k吐出某个值(吐出的值消失)

smover k1 k2 v1				把集合k1中的值v1移到k2

sinter k1 k2				返回两个集合的交集元素
sunion k1 k2				返回两个集合的并集元素
sdiff k1 k2					返回两个集合的差集元素(k1中的存在的,k2中不存在的)

4、哈希(Hash)

1、简介

Hash是一个String类型的field和value的映射表,类似于Map<String,Object>

2、数据结构

Hash类型对应数据结构为:ziplist(压缩列表),hashtable(哈希表),
当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable

3、命令

hset k f v				给k中的f赋值v
hsetnx k f v			给k中的f赋值v,仅当f不存在时
例 hset user:1001 id 1	设置user1001的id为1
hmest k f1 v1 f2 v2 	给k中的多个f赋值v

hexist k f 				查看k中是否存在f
hkeys k					查看k中的所有field
hvals k					查看k中的所有value

hget k f				从k集合f查询v

hincrby k f n			为k中的f的v加上增量n

5、有序集合(Zset)

1、简介
在这里插入图片描述
2、数据结构
在这里插入图片描述
跳跃表实现过程
在这里插入图片描述
在这里插入图片描述

3、命令

zadd k s v s v					将一个或多个member元素及其score值加入有序集k中

zrange k s e 					返回k中下标在s-e中的元素
zrange k s e withscores 		返回k中下标在s-e中的元素并带score(按评分从小到大)
zrevrange k s e withscores 		返回k中下标在s-e中的元素并带score(按评分从大到小)

zrangebyscore k min max   		返回评分在min max的元素(包含min,max)从小到大
zrevrangebyscore k max min		返回评分在max min的元素(包含min,max)从大到小

zincrby k <increment> v			为元素的scrore加上增量
zrem k v						删除k中指定元素
zcount k min max				统计个数,返回个数
zrank k v						返回v的排名,小的是第一名,排名从0开始


三、Redis配置文件详解

1、### NETWORK 网络相关配置###

1、bind
在这里插入图片描述
2、protected-mode

默认为yes,远程无法访问
修改为no,远程可以访问

3、Port

默认6379

4、tcp-backlog
在这里插入图片描述

5、timeout
在这里插入图片描述

2、### SECURITY 安全###

1.requirepass

修改配置文件中的 requirepass 参数,
把前面 # 注释去掉,后面 foobared 改成自己想用的密码,
重启redis即可。

在这里插入图片描述
进入客户端输入

auth ***						连接

3、### GENERAL 通用###

1.daemonize
在这里插入图片描述
2.pidfile
在这里插入图片描述
3.loglevel
在这里插入图片描述

4.logfile
在这里插入图片描述

5、database 16

数据库设置默认16个 0-15

4、### LIMITS 限制###

1.maxclients
在这里插入图片描述

2.maxmemory
在这里插入图片描述
3.maxmemory-policy
在这里插入图片描述
4.maxmemory-samples
在这里插入图片描述



四、Redis的发布与订阅

1、什么是发布与订阅

在这里插入图片描述

2、流程

在这里插入图片描述

3、命令行实现

1.打开一个客户端订阅channel1

subscribe channel1

2.打开另一个客户端,给channe11发布消息hello

publish channel1 hello

3.打开第一个客户端收到hello



五、Redis新数据类型

1、Bitmaps

1.简介
在这里插入图片描述

在这里插入图片描述
2、命令

汇总

1、setbit <key> <offset> <value>			设置bitmaps中某个偏移量的值(0或1)
   *offset:偏移量从0开始						设置成功返回0

2、getbit <key> <offset>					获取bitmaps中某个偏移量的值

3、bitcount <key> [start end] 				统计字符串从start字节到end字节比特值为1的数量

4、bitop and(or/not/xor) <destkey> [k1 k2...]		获取多个k中的and(交集),or(并集),not(非),xor(异或)
   的操作结果并将结果保存在destkey中					返回值为and(or/not/xor)后的数量

1、设置bitmaps中某个偏移量的值

setbit <key> <offset> <value>			设置bitmaps中某个偏移量的值(0或1)
*offset:偏移量从0开始						设置成功返回0

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

2、getbit

getbit <key> <offset>					获取bitmaps中某个偏移量的值

在这里插入图片描述
3、bitcount(获取的数量是byte并非bit)
在这里插入图片描述

bitcount <key> [start end] 				统计字符串从start字节到end字节比特值为1的数量

在这里插入图片描述

4、bitop

bitop and(or/not/xor) <destkey> [k1 k2...]		获取多个k中的and(交集),or(并集),not(非),xor(异或)
的操作结果并将结果保存在destkey中					返回值为and(or/not/xor)后的数量

演示
在这里插入图片描述

Bitmaps与set的对比
在这里插入图片描述
在这里插入图片描述

2、HypeLogLog

1、简介
在这里插入图片描述
2、命令

1、pfadd

如果执行命令后HLL估计的近似基数发生变化,则返回1,否则返回0

pfadd <key> <element>			添加指定元素

2、pfcount

pfcount <key>					计算HLL的近似基数,返回数量

3、pfmerge

pfmerge <destkey> <sourcekey>	将一个或多个HLL合并后的结果存在<destkey>

3、Geospatial

1、简介
在这里插入图片描述
2、命令

1、geoadd

geoadd <key> <longityde> <latitude> <member>				添加地理位置(经度,纬度,名称)
geoadd city 19.21 32.23 guangzhou

2、geopos

geopos <key> <member>										获取指定地区坐标值
geopos city guangzhou

3、geodist

geodist <key> <member1> <member2> [m|km|ft|mi]				获取两个位置的直线距离 单位m默认值

4、georadius

georadius <key> <longitude> <latitude> radius [m|km|ft|mi]	以给定的经纬度为中心,找出某一半径radius内的元素


六、Jedis操作

1、测试连接及jedis方法调用

1、pom文件

<dependency>
	<groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

2、开放防火墙

systemctl status firewalld			查看防火墙状态
systemctl stop firewalld			关闭防火墙

3、配置文件

1、bind127.0.0.1		-》			#bind127.0.0.1	注释掉61行 
2、protected-mode yes	-》			protected-mode no 关闭保护模式

4、重启redis

1、ps -ef|grep redis			查看进程
2、kill -9 pid					杀掉进程

5、测试连接

public class JedisDemo01 {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.88.129", 6379);
        
        String value = jedis.ping();
        System.out.println(value);        
        //输出PONG
    }
}

6、测试String

public void demo1() {
        Jedis jedis = new Jedis("192.168.88.129", 6379);

        jedis.set("name", "jiali");
        String name = jedis.get("name");
        
        System.out.println(name);

        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println("key:" + key);
        }
    }

2、模拟验证码发送

在这里插入图片描述
1、生成6位随机数

public static String getCode() {
    Random random = new Random();
    String code = "";
    for (int i = 0; i < 6; i++) {
        //取10之间的数字
        int rand = random.nextInt(10);
        code += rand;
    }
    return code;
}

2、每个手机每天只能发送三次,验证码放到redis中,设置过期时间

public static void sendCode(String phone) {
    Jedis jedis = new Jedis("192.168.88.129", 6379);
    jedis.auth("****");

    //拼接key
    //手机发送次数key
    String countKey = phone + ":count";
    //验证码key
    String codeKey = phone + ":code";

    //每个手机每天只能发送三次
    String count = jedis.get(countKey);
    if (count == null) {
        //没有发送次数,第一次发送
        //设置发送次数是1
        jedis.setex(countKey, 24 * 60 * 60, "1");
    } else if (Integer.parseInt(count) <= 2) {
        //发送次数+1
        jedis.incr(countKey);
    } else if (Integer.parseInt(count) > 2) {
        //发送三次,不能再发送
        System.out.println("今天发送次数已经超过三次");
        jedis.close();
        //中断获取验证码后续操作
        return;
    }

    //发送验证码放到redis
    String code = getCode();
    jedis.setex(codeKey, 120, code);
    jedis.close();

}

3、验证码校验

public static void verifyCode(String phone, String inputCode) {

    Jedis jedis = new Jedis("192.168.88.129", 6379);
    jedis.auth("****");

    String codeKey = phone + ":code";
    String redisCode = jedis.get(codeKey);

    if (redisCode.equals(inputCode)) {
        System.out.println("成功");
    } else {
        System.out.println("失败");
    }
}

4、验证

1、sendCode("10086");

2、进入redis-cli,查看keys *,查看验证码填入下面?

3、verifyCode("10086","?");

4、成功


七、Redis6与SpringBoot整合

1、pom

<dependency>
	<groupId>org.springframework.boot</groupId>
   	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2、配置文件

spring:
  redis:
    host: 192.168.88.129
    port: 6379
    password: ****
    database: 0
    #连接超时时间
    timeout: 1800000
    lettuce:
      pool:
        #最大连接数
        max-active: 20
        #最大阻塞等待时间(负数表示没限制)
        max-wait: -1
        #最大最小空间连接
        max-idle: 5
        min-idle: 0

3、RedisConfig

package com.laptoy.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();

        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();

        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        template.setConnectionFactory(factory);

        //key序列化
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashKeySerializer(jackson2JsonRedisSerializer);

        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {

        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();

        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //配置序列化(解决乱码问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();

        return cacheManager;
    }
}

4、测试Controller

@RestController
public class RedisTestController {

    @Autowired
    RedisTemplate redisTemplate;

    @GetMapping("/test")
    public String redisTest() {

        redisTemplate.opsForValue().set("name", "jiali");

        String name = (String) redisTemplate.opsForValue().get("name");

        return name;
    }
}


八、事务_锁机制_秒杀


1、Redis事务定义

在这里插入图片描述

2、Muti,Exec,discard

在这里插入图片描述

在这里插入图片描述

3、事务的错误处理

1、组队中某个命令出现报告错误,执行时整个的所有队列都会被回滚
在这里插入图片描述
2、执行阶段某个命令报错,则只有报错的不会执行,其他正常执行
在这里插入图片描述
在这里插入图片描述


4、事务冲突的问题

例子:同一个账户有10000块,三个人登录同一个账户同一时刻购买东西
在这里插入图片描述

1、悲观锁(效率低)

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

2、乐观锁

在这里插入图片描述

在这里插入图片描述

3、WATCH key [key]

在这里插入图片描述
客户端1
在这里插入图片描述
客户端2
在这里插入图片描述

4、UNWATCH

被watch的客户端的key成功执行事务后,自动取消watch状态
在这里插入图片描述

5、Redis事务三大特性

在这里插入图片描述

5、秒杀

1、核心代码演示

1.客户端添加秒杀商品 iphone 10件

set sk:iphone:kc 10

2.获取随机用户id

public static String getUid() {
    Random random = new Random();
    String uid = "";
    int rand = random.nextInt(20);
    uid += rand;

    return uid;
}

3.秒杀过程

public static void doSecKill(String uid, String pid) {

    if (uid == null || pid == null) {
        System.out.println("用户名或商品名不能为空");
    }

    Jedis jedis = new Jedis("192.168.88.129", 6379);
    jedis.auth("****");

    //1、库存key
    String kcKey = "sk:" + pid + ":kc";
    //2、用户key
    String userKey = "sk:" + pid + ":user";
    //3、判断秒杀是否开始
    String kc = jedis.get(kcKey);
    if (kc == null) {
        System.out.println("秒杀未开始");
        jedis.close();
        //中止
        return;
    }

    //4、判断用户是否重复秒杀
    if (jedis.sismember(userKey, uid)) {
        System.out.println("您已经秒杀成功,无法重复参与");
        jedis.close();
        //中止
        return;
    }

    //5、判断库存数量
    if (Integer.parseInt(kc) <= 0) {
        System.out.println("秒杀已结束");
        jedis.close();
        //中止
        return;
    }

    //6、秒杀过程
    //6.1、库存-1
    jedis.decr(kcKey);
    
    //6.2、把秒杀用户添加到清单里
    jedis.sadd(userKey, uid);
    System.out.println("秒杀成功");
    jedis.close();
}

4.测试

public static void main(String[] args) {
    for (int i = 0; i <20 ; i++) {
        doSecKill(getUid(), "iphone");
    }
}

2、秒杀并发模拟

自行模拟tomcat测试,此处省略

1.使用工具ab模拟测试
centos6默认安装,centos7需要手动安装

2.安装ab

yum install httpd-tools

3.命令

ab --help

ab      http://....			
	-n 请求数量
	-c 请求并发量
	-p post请求时提交参数
	-T 指定参数类型post/put

3、连接超时问题

1.使用数据库连接池解决–单例模式懒加载,双重校验锁

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

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);
                    //测试连接状态 ping pong
                    poolConfig.setTestOnBorrow(true);

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

    public static void release(JedisPool jedisPool, Jedis jedis) {
        if (null != jedis) {
            jedisPool.returnResource(jedis);
        }
    }
}

2.测试连接

JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();

4、超卖问题

1.乐观锁解决
在这里插入图片描述
代码演示
1.注释秒杀过程

//6、秒杀过程
//6.1、库存-1
//jedis.decr(kcKey);
//6.2、把秒杀用户添加到清单里
//jedis.sadd(userKey, uid);

2.加了乐观锁

jedis.watch(kcKey);

Transaction multi = jedis.multi();

multi.decr(kcKey);
multi.sadd(userKey, uid);

List<Object> result = multi.exec();
if (result == null || result.size() == 0) {
    System.out.println("秒杀失败了,请重试");
    jedis.close();
    return;
}

5、库存遗留问题

1.Lua脚本
在这里插入图片描述
在这里插入图片描述

测试代码

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.joran.conditional.ElseAction;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;

public class SecKill_redisByScript {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

	public static void main(String[] args) {
		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
 
		Jedis jedis=jedispool.getResource();
		System.out.println(jedis.ping());
		
		Set<HostAndPort> set=new HashSet<HostAndPort>();

	//	doSecKill("201","sk:0101");
	}
	
	static 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" ;
			 
	static String secKillScript2 = 
			"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
			" return 1";

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

		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis=jedispool.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;
	}
}


九、持久化之RDB(Redis DataBase)默认开启

优点

  • (1)节省磁盘空间
  • (2)恢复速度快

缺点

  • (1) Fork的时候,内存的数据被克隆了一份,大致2倍的膨胀率需要考虑
  • (2)虽然fork时使用了写时拷贝技术,但是如果数据庞大比较消耗性能
  • (3)在备份周期一定间隔时间做一次备份,但是如果Redis意外down掉的话,就会丢失最后一次修改

1、是什么

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照
Redis将内存存储和持久化存储相结合,即可提供数据访问的高效性,又可保证数据存储的安全性。

2、备份是如何执行的

当满足条件时,redis需要执行RDB的时候服务器会执行以下操作:

  • 1.redis调用系统的fork()函数创建一个子进程
  • 2.子进程将数据集写入一个临时的RDB文件
  • 3.当子进程完成对临时的RDB文件的写入时,redis用新的RDB文件来替换原来旧的RDB文件,并将旧的RDB文件删除

redis在进行快照的过程中不会对RDB文件进行修改,只有快照结束后才会将旧快照替换成新快照,也就是说任何时候RDB都是完整的

3、Fork

在这里插入图片描述

4、配置文件(### SNAPSHOT###)

1.Save
1、默认1分钟内改了1万次,或5分钟内改了100次,或15分钟改了1次执行
2、如何禁用
不设置save指令,或者给save传入空字符串
3、动态禁止

redis-cli config set save ""

在这里插入图片描述

2.Save VS bgSave
1、save:只管保存,其他不管,全部阻塞。手动保存,不建议
2、bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
   可以通过lastsave命令获取最后一次成功执行快照的时间

3.stop-writes-on-bgsave-error 停止写入
当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐开启
在这里插入图片描述4.rdbcompression 压缩文件
对于存储到磁盘中的快照,可以设置是否进行压缩存储,redis会采用LZF算法进行压缩。推荐开启
缺点:消耗CPU
在这里插入图片描述
5.rdbchecksun 检查完整性
存储快照后,redis可以使用CRC64算法进行数据校验。推荐开启
缺点:消耗CPU
在这里插入图片描述

5、rdb备份与恢复

查看文件目录(xxx.rdb文件放到该目录自动进行恢复)

config get dir					查看文件目录(xxx.rdb文件放到该目录自动进行恢复)


十、持久化之AOF(Append Only File) 默认不开启

优点

  • 1、备份机制更稳健,丢失数据概率较低
  • 2、可读的日志文件,通过操作AOF文件,可以处理误操作
  • 3、AOF以appen-only(追加)的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高。

缺点

  • 1、比RDB占用更多磁盘空间
  • 2、恢复备份速度慢,不适合做冷备
  • 3、每次读写都同步,有一定的性能压力
  • 4、存在个别bug,造成恢复不能
    bin目录输入 redis-check-aof --fix xxx.aof可以修复一些操作

1、是什么

  • 1、以日志的形式来记录每个写操作
  • 2、只许追加文件但不能改写文件
  • 3、Redis启动之初会重新构建数据

2、AOF持久化流程

  • 1、客户端的请求命令会被append追加到AOF缓存区中
  • 2、AOF缓存区根据AOF持久化策略[always,everysec,no]将操作sync同步到AOF文件
  • 3、AOF文件超过重写策略或手动重写时,会对AOF文件进行rewrite重写,压缩AOF文件容量
  • 4、Redis服务重启时,重写load加载AOF文件的写操作达到数据恢复的目的

3、开启AOF(若RDB同时开启,Redis听AOF的)

appendonly yes					开启AOF
appendfilename "xxx.aof"		自定义文件名称

4、AOF启动/修复/恢复

1、恢复
查看文件目录(xxx.aof文件放到该目录自动进行恢复)

config get dir					查看文件目录(xxx.aof文件放到该目录自动进行恢复)

2、异常修复,修复部分语句错误(bin目录)

redis-check-aof --fix xxx.aof	继续输入y

5、AOF同步频率

appendfsync always				始终同步。性能较差但完整性好
appendfsync everysec			每秒同步。如果宕机,本秒的数据可能丢失
appendfsync no					不主动同步,把同步时机交给操作系统

6、Rewrite压缩

1、是什么
  AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

2、如何实现重写
  AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
  redis4.0版本后的重写,实质上就是把rdb的快照以二进制的形式附在新的aof头部,作为已有的历史数据,替换掉原理的流水账操作
no-appendfsync-on-rewrite

举例:

set k1 v1 + set k2 v2 压缩成 set k1 v1 k2 v2
1、auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%开始重写
2、auto-aof-rewrite-min-size:设置重写的基准值,最小文件64M,达到这个值开始重写

例如:
文件达到70MB开始重写,重写为50MB,下次当100MB开始重写
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size
如果Redis的下一次AOF大小>=base_size+base_size*100%且当前大小大于64MB时开始重写



十一、RDB和AOF对比

1、到底如何选择

  1. 不要仅仅使用RDB这样会丢失很多数据。
  2. 也不要仅仅使用AOF,因为这一会有两个问题,第一通过AOF做冷备没有RDB做冷备恢复的速度快;第二RDB每次简单粗暴生成数据快照,更加健壮。
  3. 综合AOF和RDB两种持久化方式,用AOF来保证数据不丢失,作为恢复数据的第一选择;用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,可以使用RDB进行快速的数据恢复。

2、Redis 持久化 之 AOF 和 RDB 同时开启,Redis听谁的?

听AOF的,RDB与AOF同时开启 默认无脑加载AOF的配置文件
相同数据集,AOF文件要远大于RDB文件,恢复速度慢于RDB
AOF运行效率慢于RDB,但是同步策略效率好,不同步效率和RDB相同



十二、主从复制

1、简介

  • 读写分离–主机写 从机读
  • 容灾快速恢复
  • 主机更新后根据配置和策略,自动同步到从机

2、演示(一主两从)

1.创建msconf文件夹存放主从配置文件

2.复制一份redis.conf到msconf

  • 开启守护进程 daemonize yes
  • 关闭AOF持久化 appendonly no
  • 修改dump文件生成到到当前目录 dir /study/redis-6.2.6/msconf/

3.配置三个配置文件

  • include /
  • pidfile /
  • port
  • dbfilename

在这里插入图片描述

4.开启服务
在这里插入图片描述
5.查看服务器状态

info replication

6、配置从机 进入从机客户端redis-cli内

slaveof 127.0.0.1 port

3、常用三招

  • 1、从服务器如果停止了,重启变为master,需要重新执行该命令变为slave
  • 2、主服务器如果停止了,重启还是连接着两台从服务器(你大哥还是你大哥)

1、一主二仆

一台Master多台Slave

2、薪火相传

Master的slave可以被另一台服务器slaveof

3、反客为主

Master挂掉后其中一台slave执行
slaveof no one
成为新的Master

4、复制原理

  • 1、slave启动成功连接到master后会发送一个sync同步命令
  • 2、当master接到slave发送过来的同步消息,首先把master数据进行持久化成rdb文件,把rdb文件发送到slave,slave拿到rdb文件进行读取
  • 3、每次主服务器进行写操作后,和从服务器进行数据同步

1、全量复制:slave接收到数据库文件数据后,将其存盘并加载到内存中
2、增量复制:master继续将新的所有收集到的修改命令依次传给slave
3、但只要时重新连接master,一次完成同步(全量复制)将被自动执行
在这里插入图片描述

5、哨兵模式(sentinel)

反客为主的自动版,Master故障自动从Slave选出新的Master

1.新建sentinel.conf文件到/study/redis-6.2.6/msconf/
mymaster为监控对象起的服务器名称 ,1为至少有多少个哨兵同意迁移的数量

sentinel monitor mymaster 127.0.0.1 6379 1

2.启动哨兵

./redis-sentinel /study/redis-6.2.6/msconf/sentinel.conf

在这里插入图片描述
3.身份切换
当Master宕机,重新选举一个Slave成为新的Master,旧的Master重启后也成为Slave

4.选举优先级 redis.conf

replica-priority 100 		值越小优先级越高
  • 1、选择优先级靠前的
  • 2、选择偏移量最大的(获得原主机数据最全的)
  • 3、选择runid最小的(每个redis实例启动后随机生成40为runid)

5.复制延迟
由于所有写操作都在Master执行,然后同步更新到Slave,所以从Master同步到Slave机器有一点的延迟,当系统很繁忙的时候,延迟问题更严重,Slave机器的增加也会使这个问题更严重



十三、集群(Redis Cluster)

优点

  1. 实现扩容
  2. 分摊压力
  3. 无中心化配置简单

不足

  1. 不支持多键操作
  2. 不支持多键的Redis事务,不支持lua脚本
  3. 由于集群方案出现较晚,很多公司已经采用了其他方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大

1、简介

Redis集群实现对Redis的水平扩容,即启动N个Redis节点,将整个数据库分布存储在这N个节点中,每个节点存储数据的1/N.
Redis集群通过分区(partition)来提供一定程度的可用]性:即使有一部分节点失效或无法通讯,集群也可以处理命令请求.

2、搭建集群

1、创建6个配置文件端口号6379-6384

cluster-enabled yes						打开集群模式
cluster-config-file nodes-63xx.conf 	设定节点配置文件名
cluster-node-timeout 15000				设定节点失联时间,超过该时间ms,集群自动进行主从切换

2、先改一份redis6379.conf
在这里插入图片描述
额外:在redis.conf中

  1. 关闭保护模式 protected-more no
  2. 开启外网访问 #bind:127.0.0.1
  3. 开启后台运行 daemonize yes
  4. 设置rdb文件目录 dir /study/redis-6.2.6/msconf

3、拷贝出其余redis63**.conf
在这里插入图片描述
4、更改文件内容 vi编辑器内

:%s/6379/63**				快速替换6379为63**

5、启动6个服务
在这里插入图片描述

6、6个节点合成集群 进入/study/redis-6.2.6/src

-replicas 1 采用最简单的方式配置集群,一主一从

../bin/redis-cli --cluster create --cluster-replicas 1 \
192.168.88.129:6379 \
192.168.88.129:6380 \
192.168.88.129:6381 \
192.168.88.129:6382 \
192.168.88.129:6383 \
192.168.88.129:6384

输入yes而不是y接受

7、集群连接(去中心化)

./redis-cli -c -p 63xx			去中心化连接客户端
			-c 代表了可以自动重定向到相对应的槽位的master
			
./redis-cli --cluster check 192.168.88.129:6379		集群信息检查

cluster nodes					查看集群节点信息

在这里插入图片描述

3、集群概念

1、如何分配这六个节点?

  1. 一个集群至少有三个节点
  2. 选项 --cluster-replicas 1 表示为每个主节点创建一个从节点
  3. 分配原则尽量保证主数据库运行在不同IP地址,每个主库和从库不在同一个IP地址
    在这里插入图片描述

2、插槽slots

  1. 一个redis集群包含16384个插槽,数据库中的每个键都属于这16384个插槽中的其中一个
  2. 集群使用公式CRC16(key)%16384来计算键属于哪个槽,其中CRC16(key)语句用于计算键key的CRC16校验和
  3. 集群终端每个节点负责处理一部分插槽
    在这里插入图片描述

3、在集群中录入值

1.自动切换到对应master
在这里插入图片描述
2.不在一个slot下的键值是不能直接使用mset,mget操作
在这里插入图片描述
解决:用{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去

mset name{user} jiali age{user} 21		用user计算插槽

在这里插入图片描述
在这里插入图片描述
3.常用命令

cluster keyslot <key>					计算key的插槽值
cluster countkeysinslot <slot>			返回slot槽中的键(需要在对应插槽范围内进行查询)
cluster getkeysinslot <slot> <count>	返回count个slot槽中的键

4.故障恢复

  1. 主机宕机后从机上位,旧主机启动后变为从机
  2. 如果某一点插槽的主从全部宕机,根据redis,conf
    ---- 且cluster-require-full-coverage yes,那么整个集群都挂掉
    ---- 且cluster-require-full-coverage no , 那么插槽数据全部不能使用,也无法存储

4、Jedis操作集群

public class RedisCluster {
    public static void main(String[] args) {
        //创建对象
        HostAndPort hostAndPort = new HostAndPort("192.168.88.129", 6379);
        JedisCluster jedisCluster = new JedisCluster(hostAndPort);

        jedisCluster.set("name", "laptoy");
        System.out.println(jedisCluster.get("name"));

        jedisCluster.close();
    }
}


十四、缓存穿透、击穿、雪崩、分布式锁

缓存处理流程

  前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
在这里插入图片描述

1、缓存穿透

1、问题
  key对应的数据源并不存在,每次针对此key的请求从缓存中获取不到,请求会压倒数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户请求,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
在这里插入图片描述
2、解决

  1. 对空值缓存:如果一个查询返回为空,仍把空值缓存并设置短过期时间不超过五分钟
  2. 设置可访问的名单:使用bitmaps定义一个可以访问的名单,id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果id不在名单内,进行拦截,不允许访问
  3. 采用布隆过滤器:实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)
  4. 进行实时监控:当发现Redis的命中率开始急速降低,进行排查

2、缓存击穿(热点数据过期)

1、问题
  某个热点key对应的数据存在,但在redis中过期,此时若有大量的并发请求进来,这些请求发现缓存过期一般都会从后台DB加载数据并设置缓存,这个时候大并发的请求会瞬间把DB压垮
在这里插入图片描述
2、解决

  1. 预先设置热点数据,加大热门数据key的时长
  2. 实时调整key的过期时间
  3. 使用锁
    在缓存失效的时候,先使用缓存工具的某些带成功操作返回值的操作(setnx key不存在时设置)
    当操作返回成功时,在进行load db操作,并回设缓存,最后删除该key
    当操作失败返回,证明又线程在load db,当前线程睡眠一段时间再重试get缓存方法

在这里插入图片描述

3、缓存雪崩

1、描述
   缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
在这里插入图片描述

2、解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 构建多级缓存架构:nginx缓存+redis缓存+其他缓存
  3. 使用锁或者队列
  4. 设置过期标志自动更新缓存

4、分布式锁

set <key> <value> nx ex <time>			上锁并设置过期时间
del <key>								释放锁
@GetMapping("/testLock")
    public void testLock() {

        String uuid = UUID.randomUUID().toString();

        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);

        if (lock) {
            Object value = redisTemplate.opsForValue().get("num");

            if (!StringUtils.isEmpty(value)) {
                int num = Integer.parseInt(value + "");
                redisTemplate.opsForValue().set("num", ++num);
                //释放锁
                String lockValue = (String) redisTemplate.opsForValue().get("lock");
                //解决锁误删,只能释放自己的锁
                if(uuid.equals(lockValue)){
                    redisTemplate.delete("lock");
                }
            }
        } else {
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

以上还需进行lua脚本改进原子性

if(uuid.equals(lockValue)){
    redisTemplate.delete("lock");
}
//又可能判断uuid相等后卡住了导致锁自动释放后删除其他锁

1、满足以下要求

  1. 互斥性。在任一时刻,只要一个客户端能持有锁
  2. 不会发生死锁------------------------------设置过期时间
  3. 加锁和解锁必须是同一个客户端------设置随机uuid
  4. 加锁和解锁必须具有原子性-------------Lua脚本


十五、Redis6新特性

1、ACL

在redis6以前,无法进行用户权限管理,只有一个auth密码验证的功能,如果验证码通过那么就是root权限,如果我们想要禁用一些redis指令,只能使用rename将原指令名字修改,这样做很不方便。而在redis6中引入了ACL模块,可以定制不同用户的权限,包括:

  1. 用户名和密码。
  2. 可以执行的指令。
  3. 可以操作的key。
    root密码登入的用户名为default,查看ACL列表的指令为:ACL LIST。
127.0.0.1:6379> acl list
# 格式为:user 用户名 on 密码(如果没有密码那么为nopass) 可以执行的指令 可以操作的key
1) "user default on #06f2fc19c20fc7aae5e74974e7ea85fab4093055564939ebf2102d9ed3d13afc ~* +@all"
2) "user bigkai on #565c9a0d69440194596f8bf3cf3c5b1f904245f5c539a72a5a4f9baddd933dce ~* +@all -set"

ACL规则:下面是有效ACL规则的列表。某些规则只是用于激活或删除标志,或对用户ACL执行给定更改的单个单词。其他规则是字符前缀,它们与命令或类别名称、键模式等连接在一起。

1、启动和禁用用户:

  1. on:激活某用户账号。
  2. off:禁用某用户账号。注意,已验证的连接仍然可以工作。如果默认用户被标记为off,则新连接将在未进行身份验证的情况下启动,并要求用户使用AUTH选项发送AUTH或HELLO,以便以某种方式进行身份验证。

2、权限的添加删除:
在这里插入图片描述

3、可操作键的添加或删除:
~<pattern>:添加可作为用户可操作的键的模式。例如~*允许所有的键。

acl cat						查看所有指令分类
acl setuser cat				查看某个分类下的指令

# acl setuser 用户名 on 密码(可多个) 权限 键模式
acl setuser testuser on >testuser1 >testuser2 >testuser3 +@all ~*

acl setuser testuser off	禁用用户
acl users					查看所有用户
acl list					展现用户权限列表
acl whoami					查看当前用户
acl deluser test			删除当前用户

2、多线程I/O

  1. CPU成为redis瓶颈的情况并不常见,可能性更高的是内存和网络的约束。
  2. 从redis4开始会将多线程技术用于数据清理、模块方法调用。

然后现在为了解决网络IO的问题,redis使用了多进程,对网络事件进行电梯,分发给工作线程处理,处理完之后再交给主线程进行执行操作,执行之后交给工作线程回写响应数据

在这里插入图片描述

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Laptoy

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值