Redis总结

redis版本升级说明

接下来内容概述:

  • 安装redis6.0.8
  • redis传统五大数据类型的落地应用
  • 知道分布式锁吗?有哪些实现方案?你谈谈对redis分布式锁的理解,删key的时候有什么问题?
  • redis缓存过期淘汰策略
  • redis的LRU算法简介

安装redis6.0.8:

  • Redis官网
  • Redis中文网
  • 安全Bug按照官网提示,升级成为6.0.8
  • 进入Redis命令行,输入info,返回关于Redis服务器的各种信息(包括版本号)和统计数值。

redis两个小细节说明

redis基本类型:

  • string
  • list
  • set
  • zset(sorted set)
  • hash

其他redis的类型

  • bitmap
  • HyperLogLogs
  • GEO
  • Stream

备注

  • 命令不区分大小写,而key是区分大小写的
  • help @类型名词

Redis各种数据类型使用场景

string类型使用场景

最常用

  • SET key value
  • GET key

同时设置/获取多个键值

  • MSET key value [key value…]
  • MGET key [key…]

数值增减

  • 递增数字 INCR key(可以不用预先设置key的数值。如果预先设置key但值不是数字,则会报错)
  • 增加指定的整数 INCRBY key increment
  • 递减数值 DECR key
  • 减少指定的整数 DECRBY key decrement

获取字符串长度

  • STRLEN key

分布式锁

  • SETNX key value
  • SET key value [EX seconds] [PX milliseconds] [NX|XX]
  • EX:key在多少秒之后过期
  • PX:key在多少毫秒之后过期
  • NX:当key不存在的时候,才创建key,效果等同于setnx
  • XX:当key存在的时候,覆盖key

应用场景

  • 商品编号、订单号采用INCR命令生成
  • 是否喜欢的文章

hash类型使用场景

Redis的Hash类型相当于Java中Map>

一次设置一个字段值 HSET key field value

一次获取一个字段值 HGET key field

一次设置多个字段值 HMSET key field value [field value …]

一次获取多个字段值 HMGET key field [field …]

获取所有字段值 HGETALL key

获取某个key内的全部数量 HLEN

删除一个key HDEL

应用场景

- 购物车早期,当前小中厂可用

  • 新增商品 hset shopcar:uid1024 334488 1
  • 新增商品 hset shopcar:uid1024 334477 1
  • 增加商品数量 hincrby shopcar:uid1024 334477 1
  • 商品总数 hlen shopcar:uid1024
  • 全部选择 hgetall shopcar:uid1024

list类型使用场景

向列表左边添加元素 LPUSH key value [value …]

向列表右边添加元素 RPUSH key value [value …]

查看列表 LRANGE key start stop

获取列表中元素的个数 LLEN key

应用场景

- 微信文章订阅公众号

  • 大V作者李永乐老师和ICSDN发布了文章分别是11和22

  • 阳哥关注了他们两个,只要他们发布了新文章,就会安装进我的List
    lpush likearticle:阳哥id1122

  • 查看阳哥自己的号订阅的全部文章,类似分页,下面0~10就是一次显示10条
    lrange likearticle:阳哥id 0 10

set类型使用场景

添加元素 SADD key member [member …]

删除元素 SREM key member [member …]

获取集合中的所有元素 SMEMBERS key

判断元素是否在集合中 SISMEMBER key member

获取集合中的元素个数 SCARD key

从集合中随机弹出一个元素,元素不删除 SRANDMEMBER key [数字]

从集合中随机弹出一个元素,出一个删一个 SPOP key[数字]

集合运算

集合的差集运算A - B

  • 属于A但不属于B的元素构成的集合
  • SDIFF key [key …]

集合的交集运算A ∩ B

  • 属于A同时也属于B的共同拥有的元素构成的集合
  • SINTER key [key …]

集合的并集运算A U B

  • 属于A或者属于B的元素合并后的集合
  • SUNION key [key …]

应用场景

微信抽奖小程序

  • 用户ID,立即参与按钮
    SADD key 用户ID
  • 显示已经有多少人参与了、上图23208人参加
    SCARD key
  • 抽奖(从set中任意选取N个中奖人)
    SRANDMEMBER key 2(随机抽奖2个人,元素不删除)
    SPOP key 3(随机抽奖3个人,元素会删除)

微信朋友圈点赞

  • 新增点赞
    sadd pub:msglD 点赞用户ID1 点赞用户ID2
  • 取消点赞
    srem pub:msglD 点赞用户ID
  • 展现所有点赞过的用户
    SMEMBERS pub:msglD
  • 点赞用户数统计,就是常见的点赞红色数字
    scard pub:msgID
  • 判断某个朋友是否对楼主点赞过
    SISMEMBER pub:msglD用户ID

微博好友关注社交关系

  • 共同关注:我去到局座张召忠的微博,马上获得我和局座共同关注的人
    sadd s1 1 2 3 4 5
    sadd s2 3 4 5 6 7
    SINTER s1 s2
  • 我关注的人也关注他(大家爱好相同)

QQ内推可能认识的人

sadd s1 1 2 3 4 5
sadd s2 3 4 5 6 7
SINTER s1 s2
SDIFF s1 s2
SDIFF s2 s1

zset类型使用场景

向有序集合中加入一个元素和该元素的分数

添加元素 ZADD key score member [score member …]

按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素 ZRANGE key start stop [WITHSCORES]

获取元素的分数 ZSCORE key member

删除元素 ZREM key member [member …]

获取指定分数范围的元素 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

增加某个元素的分数 ZINCRBY key increment member

获取集合中元素的数量 ZCARD key

获得指定分数范围内的元素个数 ZCOUNT key min max

按照排名范围删除元素 ZREMRANGEBYRANK key start stop

获取元素的排名

从小到大 ZRANK key member

从大到小 ZREVRANK key member

应用场景

根据商品销售对商品进行排序显示

  • 定义商品销售排行榜(sorted set集合),key为goods:sellsort,分数为商品销售数量。
    商品编号1001的销量是9,商品编号1002的销量是15 - zadd goods:sellsort 9 1001 15 1002
    有一个客户又买了2件商品1001,商品编号1001销量加2 - zincrby goods:sellsort 2 1001
    求商品销量前10名 - ZRANGE goods:sellsort 0 10 withscores

抖音热搜

  • 点击视频
    ZINCRBY hotvcr:20200919 1 八佰
    ZINCRBY hotvcr:20200919 15 八佰 2 花木兰
  • 展示当日排行前10条
    ZREVRANGE hotvcr:20200919 0 9 withscores

Redis分布式锁

redis分布式锁前情说明

常见的面试题:

Redis除了拿来做缓存,你还见过基于Redis的什么用法?
Redis做分布式锁的时候有需要注意的问题?
如果是Redis是单点部署的,会带来什么问题?那你准备怎么解决单点问题呢?
集群模式下,比如主从模式,有没有什么问题呢?
那你简单的介绍一下Redlock吧?你简历上写redisson,你谈谈。
Redis分布式锁如何续期?看门狗知道吗?

boot整合redis搭建超卖程序

使用场景:多个服务间 + 保证同一时刻内 + 同一用户只能有一个请求(防止关键业务出现数据冲突和并发错误)

建两个Module:boot_redis01,boot_redis02

pom

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-parent</artifactId>
	    <version>2.3.3.RELEASE</version>
	    <relativePath/> <!-- lookup parent from repository -->
	</parent>

	<groupId>com.lun</groupId>
	<artifactId>boot_redis01</artifactId> <!--boot_redis02-->
	<version>1.0.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>boot_redis01</name> <!--boot_redis02-->
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>

		<!-- web+actuator -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		
		<!-- SpringBootRedis整合依赖 -->

		<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>
		
		<!-- jedis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>3.1.0</version>
		</dependency>

		<!-- Spring Boot AOP技术-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		
		<!-- redisson -->
		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.13.4</version>
		</dependency>

		<!-- 一般通用基础配置 -->
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-devtools</artifactId>
		    <scope>runtime</scope>
		    <optional>true</optional>
		</dependency>
		
		<dependency>
		    <groupId>org.projectlombok</groupId>
		    <artifactId>lombok</artifactId>
		    <optional>true</optional>
		</dependency>
		
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
		    <exclusions>
		        <exclusion>
		            <groupId>org.junit.vintage</groupId>
		            <artifactId>junit-vintage-engine</artifactId>
		        </exclusion>
		    </exclusions>
		</dependency>

	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Properties

server.port=1111
#2222

#=========================redis相关配香========================
#Redis数据库索引(默认方0)
spring.redis.database=0
#Redis服务器地址
spring.redis.host=192.168.111.147
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=
#连接池最大连接数(使用负值表示没有限制)默认8
spring.redis.lettuce.pool.max-active=8
#连接池最大阻塞等待时间(使用负值表示没有限制)默认-1
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接默认8
spring.redis.lettuce.pool.max-idle=8
#连接池中的最小空闲连接默犬认0
spring.redis.lettuce.pool.min-idle=0

主启动类

@SpringBootApplication
public class BootRedis01Application{
    
    public static void main(String[] args){
        SpringApplication.run(BootRedis01Application.class, args);
    }
    
}

配置类

import java.io.Serializable;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
    
}

@RestController
public class GoodController{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/buy_goods")
    public String buy_Goods(){

        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
        if(goodsNumber > 0){
            int realNumber = goodsNumber - 1;
            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
            System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
            return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
        }else{
            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
        }

        return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
    }
    
}

测试

redis:set goods:001 100

浏览器:http://localhost:1111/buy_goods

boot_redis02拷贝boot_redis01

redis分布式锁01

JVM层面的加锁,单机版的锁

具体加哪种锁,看具体业务
synchronized
ReentraLock

class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...

    public void m() {
        lock.lock();  // block until condition holds//不见不散
        try {
            // ... method body
        } finally {
            lock.unlock()
        }
    }
     
     
    public void m2() {

       	if(lock.tryLock(timeout, unit)){//过时不候
            try {
            // ... method body
            } finally {
                lock.unlock()
            }   
        }else{
            // perform alternative actions
        }
   }
 }

redis分布式锁02

n多个线程,不能直接找具体的机器如1111,2222,由nginx默认的一些请求转发规则,如轮询或者权重,将微服务,打到1号机或者2号机。

分布式部署后,单机锁还是出现超卖现象,需要分布式锁

Redis总结_第1张图片
redis cluster

Nginx配置负载均衡,Nginx学习笔记or备份

反向代理+负载均衡

Nginx配置文件修改内容

upstream myserver{
    server 127.0.0.1:1111;
    server 127.0.0.1:2222;
}

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    location / {
        # 负责用到的配置
        proxy_pass  http://myserver;
        root   html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
    	root   html;
    }
}

启动两个微服务:1111,2222,多次访问http://localhost/buy_goods,服务提供端口在1111,2222两者之间横跳

由nginx提供代理,生产上直接输入nginx地址http://192.168.111.147/buy_goods,就不用再关注端口号

上面手点,下面高并发模拟

redis:set goods:001 100,恢复到100

用到Apache JMeter,100个线程同时访问http://localhost/buy_goods。

Redis总结_第2张图片
Redis总结_第3张图片
启动测试,后台打印如下:

Redis总结_第4张图片
Redis总结_第5张图片
这就是所谓分布式部署后出现超卖现象。

Redis具有极高的性能,且其命令对分布式锁支持友好,借助SET命令即可实现加锁处理

SET

EX seconds – Set the specified expire time, in seconds.
PX milliseconds – Set the specified expire time, in milliseconds.
NX – Only set the key if it does not already exist.
XX – Only set the key if it already exist.
@RestController
public class GoodController{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/buy_goods")
    public String buy_Goods(){

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
        if(!flag){
            return "抢锁失败";
        }
        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
        if(goodsNumber > 0){
            int realNumber = goodsNumber - 1;
            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
            System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
            stringRedisTemplate.delete(REDIS_LOCK);
            return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
        }else{
            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
        }

        return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
    }

}

在Java层面

public static final String REDIS_LOCK = "redis_lock";

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void m(){
    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);


    if(!flag) {
        return "抢锁失败";
    }
 
    ...//业务逻辑
    
    stringRedisTemplate.delete(REDIS_LOCK);
}

redis分布式锁03

上面Java源码分布式锁问题:出现异常的话,可能无法释放锁,必须要在代码层面finally释放锁

解决方法:try…finally…

public static final String REDIS_LOCK = "redis_lock";

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void m(){
    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

    try{
		Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);

   		if(!flag) {
        	return "抢锁失败";
	    }
        
    	...//业务逻辑
            
    }finally{
	    stringRedisTemplate.delete(REDIS_LOCK);   
    }
}

另一个问题:部署了微服务jar包的机器挂了,代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key

public static final String REDIS_LOCK = "redis_lock";

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void m(){
    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

    try{
		Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
		//设定时间
        stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
        
   		if(!flag) {
        	return "抢锁失败";
	    }
        
    	...//业务逻辑
            
    }finally{
	    stringRedisTemplate.delete(REDIS_LOCK);   
    }
}

redis分布式锁04

新问题:设置key+过期时间分开了,必须要合并成一行具备原子性

解决方法:

public static final String REDIS_LOCK = "redis_lock";

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void m(){
    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

    try{
		Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法
            .setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
		//设定时间
        //stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
        
   		if(!flag) {
        	return "抢锁失败";
	    }
        
    	...//业务逻辑
            
    }finally{
	    stringRedisTemplate.delete(REDIS_LOCK);   
    }
}

另一个新问题:张冠李戴,删除了别人的锁

Redis总结_第6张图片
即在执行业务的过程中,由于中间某个过程比较慢,导致分布式锁过期了,而别的线程进来了,当这个线程执行完的时候,会删除分布式锁,但是却把别的线程的锁给删掉了。

public static final String REDIS_LOCK = "redis_lock";

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void m(){
    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

    try{
		Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法
            .setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
		//设定时间
        //stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
        
   		if(!flag) {
        	return "抢锁失败";
	    }
        
    	...//业务逻辑
            
    }finally{
        if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {
            stringRedisTemplate.delete(REDIS_LOCK);
        }
    }
}


redis分布式锁05

finally块的判断 + del删除操作不是原子性的

用lua脚本

用redis自身的事务

Redis事务复习,Redis学习笔记

事务介绍

Redis的事条是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成。
Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合。
Redis将命令集合序列化并确保处于一事务的命令集合连续且不被打断的执行。
Redis不支持回滚的操作。

命令 描述
DISCARD 取消事务,放弃执行事务块内的所有命令。
EXEC 执行所有事务块内的命令。
MULTI 标记一个事务块的开始。
UNWATCH 取消 WATCH 命令对所有 key 的监视。
WATCH key [key …] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

如图,原先修改k1为11,现在监听k1,并开启事务,这个线程将修改k1为3,但事务没有提交
Redis总结_第7张图片
这个时候来了另外一个线程,将k1修改为BBB
Redis总结_第8张图片
此时提交事务,但是修改失败了,k1已经被别人修改过了。
Redis总结_第9张图片
这个时候无法覆盖别人,只能在别人的基础上修改

@RestController
public class GoodController{

    public static final String REDIS_LOCK = "redis_lock";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/buy_goods")
    public String buy_Goods(){

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        try{
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
            if(!flag){
                return "抢锁失败";
            }
            String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if(goodsNumber > 0){
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);

                return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
            }else{
                System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
            }

            return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
        }finally {
            while(true){
                stringRedisTemplate.watch(REDIS_LOCK);
                if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
                    stringRedisTemplate.setEnableTransactionSupport(true);
                    stringRedisTemplate.multi();
                    stringRedisTemplate.delete(REDIS_LOCK);
                    List<Object> list = stringRedisTemplate.exec();
                    if(list == null){
                        continue;
                    }
                }
                stringRedisTemplate.unwatch();
                break;
            }
        }
    }

}

redis分布式锁07

Redis调用Lua脚本通过eval命令保证代码执行的原子性

RedisUtils:

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

public class RedisUtils {

	private static JedisPool jedisPool;
	
	static {
		JedisPoolConfig jpc = new JedisPoolConfig();
		jpc.setMaxTotal(20);
		jpc.setMaxIdle(10);
		jedisPool = new JedisPool(jpc);
	}
	
	public static JedisPool getJedis() throws Exception{
		if(jedisPool == null)
			throw new NullPointerException("JedisPool is not OK.");
		return jedisPool;
	}
	
}

public static final String REDIS_LOCK = "redis_lock";

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void m(){
    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

    try{
		Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法
            .setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
		//设定时间
        //stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
        
   		if(!flag) {
        	return "抢锁失败";
	    }
        
    	...//业务逻辑
            
    }finally{
    	Jedis jedis = RedisUtils.getJedis();
    	
    	String script = "if redis.call('get', KEYS[1]) == ARGV[1] "
    			+ "then "
    			+ "    return redis.call('del', KEYS[1]) "
    			+ "else "
    			+ "    return 0 "
    			+ "end";
    	
    	try {
    		
    		Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK),// 
    				Collections.singletonList(value));
    		
    		if("1".equals(o.toString())) {
    			System.out.println("---del redis lock ok.");
    		}else {
    			System.out.println("---del redis lock error.");
    		}
    		
    		
    	}finally {
    		if(jedis != null) 
    			jedis.close();
    	}
    }
}

redis分布式锁08

确保RedisLock过期时间大于业务执行时间的问题

Redis分布式锁如何续期?

集群 + CAP对比ZooKeeper 对比ZooKeeper,重点,CAP

Redis - AP -redis异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了
ZooKeeper - CP
CAP

C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)
综上所述

Redis集群环境下,我们自己写的也不OK,直接上RedLock之Redisson落地实现。

redis分布式锁09

https://redisson.org/

Redisson配置类

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RedisConfig {

    @Bean
    public Redisson redisson() {
    	Config config = new Config();
    	config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
    	return (Redisson)Redisson.create(config);
    }
    
}

Redisson模板

public static final String REDIS_LOCK = "REDIS_LOCK";

@Autowired
private Redisson redisson;

@GetMapping("/doSomething")
public String doSomething(){

    RLock redissonLock = redisson.getLock(REDIS_LOCK);
    redissonLock.lock();
    try {
        //doSomething
    }finally {
        redissonLock.unlock();
    }
}

回到实例

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GoodController{

	public static final String REDIS_LOCK = "REDIS_LOCK";
	
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;
    
    @Autowired
    private Redisson redisson;
    
    @GetMapping("/buy_goods")
    public String buy_Goods(){
    	
    	//String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
    	
    	RLock redissonLock = redisson.getLock(REDIS_LOCK);
    	redissonLock.lock();
    	try {
	        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
	        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
	        if(goodsNumber > 0){
	            int realNumber = goodsNumber - 1;
	            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
	            System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
	            return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
	        }else{
	            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
	        }
	
	        return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
    	}finally {
    		redissonLock.unlock();
    	}
    }
    
}

重启boot_redis01,boot_redis02,Nginx,重置Redis:set goods:001 100,启动JMeter,100个线程访问http://localhost/buy_goods。最后,后台输出:

Redis总结_第10张图片

redis分布式锁10

让代码更加严谨

public static final String REDIS_LOCK = "REDIS_LOCK";

@Autowired
private Redisson redisson;

@GetMapping("/doSomething")
public String doSomething(){

    RLock redissonLock = redisson.getLock(REDIS_LOCK);
    redissonLock.lock();
    try {
        //doSomething
    }finally {
    	//添加后,更保险
		if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
    		redissonLock.unlock();
    	}
    }
}

可避免如下异常:

IllegalMonitorStateException: attempt to unlock lock,not loked by current thread by node id:da6385f-81a5-4e6c-b8c0

redis分布式锁总结回顾

synchronized单机版oK,上分布式

nginx分布式微服务单机锁不行

取消单机锁,上Redis分布式锁setnx

只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁

宕机了,部署了微服务代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,
需要有lockKey的过期时间设定

为redis的分布式锁key,增加过期时间,此外,还必须要setnx+过期时间必须同一行

必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1删2,2删3

Redis集群环境下,我们自己写的也不oK直接上RedLock之Redisson落地实现

redis缓存过期策略

redis内存调整默认查看

一些面试题:

  • 生产上你们你们的redis内存设置多少?
  • 如何配置、修改redis的内存大小
  • 如果内存满了你怎么办?
  • redis清理内存的方式?定期删除和惰性删除了解过吗
  • redis缓存淘汰策略
  • redis的LRU了解过吗?可否手写一个LRU算法
  • Redis内存满了怎么办?Redis默认内存多少?在哪里查看?如何设置修改?

查看Redis最大占用内存

配置文件redis.conf的maxmemory参数,maxmemory是bytes字节类型,注意转换。

redis默认内存多少可以用?

如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存

一般生产上你如何配置?

一般推荐Redis设置内存为最大物理内存的四分之三。

如何修改redis内存设置

修改配置文件redis.conf的maxmemory参数,如:maxmemory 104857600
通过命令修改

config set maxmemory 1024
config get maxmemory

什么命令查看redis内存使用情况?

info memory

redis打满内存OOM

真要打满了会怎么样?如果Redis内存使用超出了设置的最大值会怎样?

我改改配置,故意把最大值设为1个

127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "0"
127.0.0.1:6379> config set maxmemory 1
OK
127.0.0.1:6379> set a 123
(error) OOM command not allowed when used memory > 'maxmemory'.

没有加上过期时间就会导致数据写满maxmemory为了避免类似情况,引出下一节内存淘汰策略

redis内存淘汰策略

往redis里写的数据是怎么没了的?

redis过期键的删除策略

如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢?

如果回答yes,你自己走还是面试官送你?

如果不是,那过期后到底什么时候被删除呢??是个什么操作?

三种不同的删除策略

定时删除 - 总结:对CPU不友好,用处理器性能换取存储空间(拿时间换空间)
惰性删除 - 总结:对memory不友好,用存储空间换取处理器性能(拿空间换时间)
上面两种方案都走极端 - 定期删除 - 定期抽样key,判断是否过期(存在漏网之鱼)

定时删除

Redis不可能时时刻刻遍历所有被设置了生存时间的key,来检测数据是否已经到达过期时间,然后对它进行删除。

立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu很忙的时候,比如正在做交集或排序等计算的时候,就会给cpu造成额外的压力,让CPU心累,时时需要删除,忙死。

这会产生大量的性能消耗,同时也会影响数据的读取操作。

惰性删除

数据到达过期时间,不做处理。等下次访问该数据时,

如果未过期,返回数据;

发现已过期,删除,返回不存在。

惰性删除策略的缺点是,它对内存是最不友好的。

如果一个键已经过期,而这个键又仍然保留在数据库中,那么只要这个过期键不被删除,它所占用的内存就不会释放。

在使用惰性删除策略时,如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么它们也许永远也不会被删除(除非用户手动执行FLUSHDB),我们甚至可以将这种情况看作是一种内存泄漏 – 无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息。

定期删除

定期删除策略是前两种策略的折中:

定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。

周期性轮询Redis库中的时效性数据,来用随机抽取的策略,利用过期数据占比的方式控制删除频度

特点1:CPU性能占用设置有峰值,检测频度可自定义设置

特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理

总结:周期性抽查存储空间(随机抽查,重点抽查)

举例:

redis默认每个100ms检查,是否有过期的key,有过期key则删除。注意:redis不是每隔100ms将所有的key检查一次而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis直接进去ICU)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

定期删除策略的难点是确定删除操作执行的时长和频率:如果删除操作执行得太频繁,或者执行的时间太长,定期删除策略就会退化成定时删除策略,以至于将CPU时间过多地消耗在删除过期键上面。如果删除操作执行得太少,或者执行的时间太短,定期删除策略又会和惰性删除束略一样,出现浪费内存的情况。因此,如果采用定期删除策略的话,服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。

上述步骤都过堂了,还有漏洞吗?

1.定期删除时,从来没有被抽查到
2.惰性删除时,也从来没有被点中使用过

上述2步骤====>大量过期的key堆积在内存中,导致redis内存空间紧张或者很快耗尽

必须要有一个更好的兜底方案

内存淘汰策略登场(Redis 6.0.8版本)

noeviction:不会驱逐任何key
volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除
volatile-Iru:对所有设置了过期时间的key使用LRU算法进行删除
volatile-random:对所有设置了过期时间的key随机删除
volatile-ttl:删除马上要过期的key
allkeys-lfu:对所有key使用LFU算法进行删除
allkeys-Iru:对所有key使用LRU算法进行删除
allkeys-random:对所有key随机删除

上面总结

2*4得8

2个维度

  • 过期键中筛选
  • 所有键中筛选

4个方面

  • LRU
  • LFU
  • random
  • ttl(Time To Live)

8个选项

如何配置,修改

命令

config set maxmemory-policy noeviction
config get maxmemory

配置文件 - 配置文件redis.conf的maxmemory-policy参数

你可能感兴趣的:(redis,数据库,redis,数据库,分布式)