redis持久化与集群机制

文章目录

redis环境搭建

redis环境搭建

redis数据结构

String类型、Hsh类型、List类型、Set类型 、Sorted-Sets

String类型

String是redis最基本的类型,一个key对应一个value,sring类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象, Sring类型是Redis最基本的数据类型,一个键最大能存储512MB

[root@location bin]# ./redis-cli 
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"

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

Hash类型

我们可以将Redis中的Hash类型看成具有<key,<key1,value>>,其中同一个key可以有多个不同key值的<key1,value>,所以该类型非常适合于存储值对象的信息。如Username、Password和Age等。如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。

127.0.0.1:6379> HMSET fristHash1 wangwu 20
OK
127.0.0.1:6379> HGETALL fristHash1 
1) "wangwu"
2) "20"
127.0.0.1:6379> 

在这里插入图片描述

List类型

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

LRANGE firstPush 0 10 获取的时候需要写上范围

127.0.0.1:6379> LPUSH firstPush zhangsan lisi wanwu ermazi
(integer) 4
127.0.0.1:6379> LRANGE firstPush
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> LRANGE firstPush 0 10
1) "ermazi"
2) "wanwu"
3) "lisi"
4) "zhangsan"
127.0.0.1:6379> 

在这里插入图片描述

Redis 集合(Set)

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
SADD nameSet zhangsan zhangsan lisi wanwu
SMEMBERS nameSet

127.0.0.1:6379> SADD nameSet zhangsan zhangsan lisi wanwu
(integer) 3
127.0.0.1:6379> SMEMBERS  nameSet 
1) "lisi"
2) "wanwu"
3) "zhangsan"
127.0.0.1:6379> 

在这里插入图片描述

Redis 有序集合(sorted set)

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

ZADD sortSet 1 redis
ZRANGE sortSet 0 10 WITHSCORES
在这里插入图片描述

Redis持久化机制

全量同步与增量同步的区别

全量同步:就是每天定时(避开高峰期)或者采用一个周期实现将数据拷贝到一个地方也就是Rdb存储。
增量同步:比如采用对行为的操作实现对数据的同步,也就是AOF。
全量与增量的比较:增量同步比全量同步更加消耗服务器的内存,但是能够更加的保证数据的同步。

RDB与AOF实现持久化的区别

Redis提供了两种持久化的机制,分别为RDB、AOF实现,RDB采用定时(全量)持久化机制,但是服务器因为某种原因宕机后可能数据会丢失,AOF是基于数据日志操作实现的持久化,所以AOF采用增量同步的方案。
Redis已经帮助我默认开启了rdb存储。

Redis的RDB与AOF同步配置

RDB

Redis默认采用rdb方式实现数据的持久化,以快照的形式将数据持久化到磁盘的是一个二进制的文件dump.rdb, 在redis.conf文件中搜索“dump.rdb “。
在这里插入图片描述Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
Set(包含增加和覆盖)、del
Set name yushengjun
Set name mayikt
Del name

Aof

在Redis的配置文件中存在三种同步方式,它们分别是:
appendfsync always #每次有数据修改发生时都会****写入AOF文件能够保证数据不丢失,但是效率非常低
appendfsync everysec #每秒钟同步一次,**可能会丢失1s内的数据,但是效率非常高
appendfsync no #从不同步。高效但是数据不会被持久化。
直接修改redis.coonf中 appendonly yes
建议
最好还是使用everysec** 既能够保证数据的同步、效率也还可以

Aof是以执行命令的形式实现同步

SpringBoot整合Redis

创建一个maven项目,添加jar

 <dependencies>
        <!-- 集成commons工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- 集成lombok 框架 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.30</version>
        </dependency>
        <!-- SpringBoot-整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

创建 application.yml

spring:
  redis:
    host: 192.168.198.15
    password: root
    port: 6379
    #设置存入第二个库
    database: 1

创建启动类

package com.oyzk;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Kingkang
 * @title RedisApplication springboot启动类
 * @create 2022/12/2
 **/
@SpringBootApplication
public class RedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class);
    }
}

创建redis 工具类

package com.oyzk.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author Kingkang
 * @title RedisUtils redis 工具类
 * @create 2022/12/2
 **/
@Component
public class RedisUtils {
    /**
     * 获取我们的redis模板
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void setString(String key,String value){
        setString(key,value,null);
    }
    /**
     *为了防止有的人把redis当成数据用,设置有效期
     * @param key
     * @param value
     * @param timeout 超时时间
     */
    public void setString(String key,String value,Long timeout){
        //设置key和value
        stringRedisTemplate.opsForValue().set(key,value);
        if (timeout!=null){
            //通过key设置过期时间
            stringRedisTemplate.expire(key,timeout, TimeUnit.SECONDS);
        }
    }

    public String getString(String key){
        return stringRedisTemplate.opsForValue().get(key);
    }
}

创建UserEntity

package com.oyzk.enity;

import lombok.Data;

import java.io.Serializable;

/**
 * @author Kingkang
 * @title UserEntity
 * @create 2022/12/2
 **/
@Data
public class UserEntity implements Serializable {
    private Long userId;
    private String userName;
}

创建 IndexController

package com.oyzk.controller;

import com.alibaba.fastjson.JSONObject;
import com.oyzk.enity.UserEntity;
import com.oyzk.utils.RedisTemplateUtils;
import com.oyzk.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Kingkang
 * @title IndexController2
 * @create 2022/12/2
 **/
@RestController
public class IndexController{
    @Autowired
    private RedisUtils redisUtils;
   
    @RequestMapping("/setRedisKey")
    public String setRedisKey(UserEntity userEntity){
        redisUtils.setString("userEntity", JSONObject.toJSONString(userEntity));
        return "存储成功";
    }

    @RequestMapping("/getRedisKey")
    public UserEntity getRedisKey(String key) {
        String userEntityJson = redisUtils.getString(key);
        UserEntity userEntity = JSONObject.parseObject(userEntityJson, UserEntity.class);
        return userEntity;
    }    
}

测试

http://localhost:8080/setRedisKey?userId=1&userName
在这里插入图片描述
在这里插入图片描述

http://localhost:8080/getRedisKey?key=userEntity
在这里插入图片描述

使用redis二进制形式存储对象

创建一个 RedisTemplateUtils

package com.oyzk.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author Kingkang
 * @title RedisTemplateUtils
 * @create 2022/12/2
 **/
@Component
public class RedisTemplateUtils {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    public void setObject(String key,Object value){
        setObject(key,value,null);
    }
    public void setObject(String key,Object value,Long timeOut){
        //设置key和value
        redisTemplate.opsForValue().set(key,value);
        if (timeOut!=null){
            //通过key设置过期时间
            redisTemplate.expire(key,timeOut, TimeUnit.SECONDS);
        }
    }
    public Object getObject(String key){
        return redisTemplate.opsForValue().get(key);
    }

}

更新IndexController

package com.oyzk.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.oyzk.enity.UserEntity;
import com.oyzk.utils.RedisTemplateUtils;
import com.oyzk.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Kingkang
 * @title IndexController
 * @create 2022/12/2
 **/
@RestController
public class IndexController {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private RedisTemplateUtils redisTemplateUtils;

    @RequestMapping("/setRedisKey1")
    public String setRedisKey(UserEntity userEntity){
       // redisUtils.setString("userEntity", JSONObject.toJSONString(userEntity));
        //
        redisTemplateUtils.setObject("userEntity",userEntity);
        return "存储成功";
    }

    @RequestMapping("/getRedisKey1")
    public UserEntity getRedisKey(String key){
//        String userEntityJson = redisUtils.getString("userEntity");
//        UserEntity userEntity = JSONObject.parseObject(userEntityJson, UserEntity.class);
       // return userEntity;
        return (UserEntity)redisTemplateUtils.getObject(key);
    }
}

测试

设置值,
http://localhost:8080/setRedisKey1?userId=2&userName=wangwu
在这里插入图片描述http://localhost:8080/getRedisKey1?key=userEntity
在这里插入图片描述

MySQL与Redis一致性解决同步问题

开启@EnableCaching

引入jar

  <!--mysql数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

application.yml文件加入数据源配置

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    urL: jdbc:mysql://127.0.0.1:3306/frame?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

创建entity

package com.oyzk.enity;

import lombok.Data;

import java.io.Serializable;

/**
 * @author Kingkang
 * @title PeopleEntity
 * @create 2022/12/2
 **/
@Data
public class PeopleEntity  implements Serializable {
    private int id;
    private String name ;
    private Double money;
}

创建PeopleController

package com.oyzk.controller;

import com.oyzk.enity.PeopleEntity;
import com.oyzk.mapper.PeopleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author Kingkang
 * @title PeopleController
 * @create 2022/12/2
 **/
@RestController
public class PeopleController {
    @Autowired
    private PeopleMapper peopleMapper;

    /**
     * redis 缓存下的cacheNames, key 'findPeopleAll' key一定要加双引号
     * @return
     */
    @RequestMapping("/findPeopleAll")
    @Cacheable(cacheNames = "people",key = "'findPeopleAll'")
    public List<PeopleEntity> findPeopleAll(){
        return  peopleMapper.findPeopleAll();
    }
}


测试

http://localhost:8080/findPeopleAll
在这里插入图片描述
在这里插入图片描述

那怎么去认证是否生效呢?

把数据库里面的值手动的改变,再去查询,会发现值是没有变的
在这里插入图片描述

解决方式1:直接清除Redis的缓存,重新读取数据库即可

在这里插入图片描述再去请求,就已经变掉了在这里插入图片描述
假如我项目里有很多个这样的,所以这种方式比较笨,临时用用可以

方式2:使用mq异步订阅mysql binlog实现增量同步

一般都是使用mq去异步订阅,

方式3:使用alibaba的canal框架

canal底层也是使用mq异步订阅实现的

redis六种淘汰策略

将Redis用作缓存时,如果内存空间用满,就会自动驱逐老的数据。

noeviction

官方解释:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
自己理解:当内存使用达到阈值的时候,执行命令直接报错

allkeys-lru

官方解释:在主键空间中,优先移除最近未使用的key。(推荐)
自己理解:在所有的key中,优先移除最近未使用的key。(推荐)

volatile-lru

官方解释:在设置了过期时间的键空间中,优先移除最近未使用的key。

allkeys-random

官方解释:在主键空间中,随机移除某个key。

volatile-random

官方解释:在设置了过期时间的键空间中,随机移除某个key。

volatile-ttl

官方解释:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。

如何配置Redis淘汰策略

在redis.conf文件中,在这里插入图片描述
设置Redis 内存大小的限制,我们可以设置maxmemory ,当数据达到限定大小后,会选择配置的策略淘汰数据
比如:maxmemory 300mb

通过配置在这里插入图片描述
设置redis的淘汰策略。比如:maxmemory-policy volatile-lru,然后保存,重启就可以生效了

redis中的自动过期机制并回调,实现订单30分钟有效期

实现需求:处理订单过期的自动取消,比如下单30分钟,未支付自动更改订单状态
解决问题方案:

  • 定时任务,30分钟后检查该笔订单是否已经支付。
  • 根据key有效期事件回调实现。(这是要演示的)
    原理:
  • 创建订单的时候绑定一个订单token存放在redis中(有效期只有30分钟)key=token value=订单id
  • 对改key绑定过期事件回调,执行我们的回调方法传递:token
实现
第一步、配置redis.conf,以后重启redis,防止不生效

当我们的key失效时,可以执行我们的客户端回调监听的方法。需要在Redis中配置:
notify-keyspace-events “Ex”
在这里插入图片描述重启redis并关闭防火墙

[root@location bin]# ps -aux |grep redis
root       1406  0.2  0.8 163148  8392 ?        Rsl  09:51   0:17 ./redis-server *:6379
root       1866  0.0  0.0 112824   988 pts/0    S+   11:46   0:00 grep --color=auto redis
[root@location bin]# kill -9 1406
[root@location bin]# ./redis-server ./redis.conf 
[root@location bin]# systemctl stop firewalld
[root@location bin]# 

关闭防火墙:systemctl stop firewalld
开启防火墙:ystemctl start firewalld

第二步、springboot整合key失效监听
1、数据库表
CREATE TABLE `order_number` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_name` varchar(255) DEFAULT NULL,
  `order_status` int(11) DEFAULT NULL,
  `order_token` varchar(255) DEFAULT NULL,
  `order_id` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
2、创建OrderEntity
package com.oyzk.enity;

import lombok.Data;

/**
 * @author Kingkang
 * @title OrderEntity
 * @create 2022/12/3
 **/
@Data
public class OrderEntity {

    public OrderEntity() {
    }

    private Long id;
    private String orderName;
    /**
     * 0 待支付 1 已经支付
     */
    private Integer orderStatus;

    private String orderToken;
    private String orderId;

    public OrderEntity(Long id, String orderName, String orderId, String orderToken) {
        this.id = id;
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderToken = orderToken;
    }
}

3、创建OrderMapper
package com.oyzk.mapper;

import com.oyzk.enity.OrderEntity;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

/**
 * @author Kingkang
 * @title OrderMapper
 * @create 2022/12/3
 **/
public interface OrderMapper {
    @Insert("insert into order_number values (null,#{orderName},0,#{orderToken},#{orderId})")
    int insertOrder(OrderEntity OrderEntity);


    @Select("SELECT ID AS ID ,order_name AS ORDERNAME ,order_status AS orderstatus,order_token as ordertoken,order_id as  orderid FROM order_number\n" +
            "where order_token=#{orderToken};")
    OrderEntity getOrderNumber(String orderToken);

    @Update("update order_number set order_status=#{orderStatus} where order_token=#{orderToken};")
    int updateOrderStatus(String orderToken, Integer orderStatus);
}

###### 4、创建RedisListenerConfig 
package com.oyzk.listener;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * @author Kingkang
 * @title RedisListenerConfig
 * @create 2022/12/3
 **/
@Configuration
public class RedisListenerConfig {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){
        RedisMessageListenerContainer container=new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

5、创建RedisKeyExpirationListener
package com.oyzk.listener;

import com.oyzk.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

/**
 * @author Kingkang
 * @title RedisKeyExpirationListener
 * @create 2022/12/3
 **/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    
    /**
     * Redis失效事件 key
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String expiraKey = message.toString();
        System.out.println("进来了,expirakey:"+expiraKey+",失效了");
    }
}

6、重启项目,并关闭linux的里的防火墙,

在这里插入图片描述

在这里插入图片描述 ###### 7、在linux的操作redis,
set userName zhangsan ex 5 -设置一个名字,过期时间为5秒,五秒后,就会进入我们后台写入的一个监听方法

[root@location bin]# ./redis-cli 
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> set userName zhangsan ex 5
OK
127.0.0.1:6379> 

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

这只是一个简单的实现,
8、 创建OrderController

redisUtils之前写过,在这里贴一下

package com.oyzk.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author Kingkang
 * @title RedisUtils redis 工具类
 * @create 2022/12/2
 **/
@Component
public class RedisUtils {
    /**
     * 获取我们的redis模板
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void setString(String key,String value){
        setString(key,value,null);
    }
    /**
     *为了防止有的人把redis当成数据用,设置有效期
     * @param key
     * @param value
     * @param timeout 超时时间
     */
    public void setString(String key,String value,Long timeout){
        //设置key和value
        stringRedisTemplate.opsForValue().set(key,value);
        if (timeout!=null){
            //通过key设置过期时间
            stringRedisTemplate.expire(key,timeout, TimeUnit.SECONDS);
        }
    }

    public String getString(String key){
        return stringRedisTemplate.opsForValue().get(key);
    }
}

package com.oyzk.controller;

import com.oyzk.enity.OrderEntity;
import com.oyzk.mapper.OrderMapper;
import com.oyzk.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * @author Kingkang
 * @title OrderController
 * @create 2022/12/3
 **/
@RestController
public class OrderController {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RedisUtils redisUtils;
    @RequestMapping("/addOrder")
    public String addOrder(){
        //1、提前生成订单token 临时且唯一
        String orderToken = UUID.randomUUID().toString();
        long orderId = System.currentTimeMillis();
        //2、将我们的token存放到redis 中
        redisUtils.setString(orderToken,orderId+"",10l);
        OrderEntity orderEntity = new OrderEntity(null, "测试订单有效期并且回调", orderId + "", orderToken);
        return orderMapper.insertOrder(orderEntity)>0?"success":"fail";
    }
}

9、 需要在我们RedisKeyExpirationListener监听类里面,完善一下回调方法
package com.oyzk.listener;

import com.oyzk.enity.OrderEntity;
import com.oyzk.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author Kingkang
 * @title RedisKeyExpirationListener
 * @create 2022/12/3
 **/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    /**
     * 待支付
     */
    private static final Integer ORDER_STAYPAY = 0;
    /**
     * 失效
     */
    private static final Integer ORDER_INVALID = 2;

    @Resource
    private OrderMapper orderMapper;

    /**
     * Redis失效事件 key
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String expiraKey = message.toString();
        System.out.println("进来了,expirakey:"+expiraKey+",失效了");
        OrderEntity orderNumber = orderMapper.getOrderNumber(expiraKey);
        if(orderNumber==null){
            return;
        }
        //获取订单状态
        Integer orderStatus = orderNumber.getOrderStatus();
        //如果订单状态为待支付的情况下,直接将该订单修改为已超时
        if(orderStatus.equals(ORDER_STAYPAY)){
            orderMapper.updateOrderStatus(expiraKey,ORDER_INVALID);
            //库存在加上1
        }
    }
}

10、 测试

http://localhost:8080/addOrder

在这里插入图片描述
在这里插入图片描述10秒后进入我们的监听的方法

在这里插入图片描述
在这里插入图片描述
到这里自动过期机制并回调就已ok了。

Redis事务操作

Multi 开启事务
EXEC 提交事务
Watch 可以监听一个或者多个key,在提交事务之前是否有发生了变化 如果发生边了变化就不会提交事务,没有发生变化才可以提交事务 版本号码 乐观锁
watch name
multi
set name zhangsan
exec
Discard 取消提交事务
注意Redis官方是没有提供回滚方法, 值提供了取消事务。

注意:Redis官方是没有提供回滚方法, 值提供了取消事务。
思考:Redis与Mysql中的事务有那些区别
redis事务没有隔离性,mysql反之,
在redis里开启事务以后,其它线程依旧是可以对我的key做操作

思考:取消事务跟回滚有什么区别呢?
mysql开启了事务,对该行数据上行锁
commit可以提交
回滚:对事务取消和行锁都会撤销
redis没有回滚 单纯的取消事务(不提交事务) 不上锁

事务演示

这是时候我们通过key去拿是拿不到值的
在这里插入图片描述在这里插入图片描述
进行提交
在这里插入图片描述
提交后,再查就可以查询到了
在这里插入图片描述

事务 演示同时对两个key进行做操作

两边同时操作同一key

第一个连接开启事务并对zhangsan这个key进行赋值
在这里插入图片描述
第二连接也对zhangsan这个key做操作,也是可以是操作成功
在这里插入图片描述第一个连接进行提交并查询,通过keyzhangsan查询
在这里插入图片描述第二个进行提交:

在这里插入图片描述
这时候我在回到第一个连接时,通过zhangsankey再去查,你会发现值以及变掉了
在这里插入图片描述

总结

在redis中使用multi对key开启事务,其它的线程开始也可以对该key执行set操作的,所以数据不安全,解决方案:我们可以用Watch

watch操作

第一个连接 multi lisi key在这里插入图片描述

第二连接watch
在这里插入图片描述

第一个连接进行提交
在这里插入图片描述

第二连接watch再进行提交,就会报错
在这里插入图片描述
通过这个这个key去拿,发现已经有值了
在这里插入图片描述

总结

所以,我们这时就可以通过watch进行监听key有没有发生变化,如果有,就不能提交,没有就提交

Redis实现分布式锁

什么分布式锁? 举个场景

本地锁:在多个线程中,保证只有一个线程执行(线程安全的问题)
分布锁:在分布式中,保证只有一个jvm执行(多个jvm线程安全问题)
如果我们服务器是集群的时候,定时任务可能会重复执行 可以采用分布式锁解决

Redis实现分布式锁基于SetNx命令,因为在Redis中key是保证是唯一的。所以当多个线程同时的创建setNx时,只要谁能够创建成功谁就能够获取到锁。
Set 命令 每次set时,可以修改原来旧值;
SetNx命令 每次SetNx检查该 key是否已经存在,如果已经存在的话不会执行任何操作。返回为0 如果已经不存在的话直接新增该key。1:新增key成功 0 失败

获取锁的时候:当多个线程同时创建SetNx k,只要谁能够创建成功谁就能够获取到锁。
释放锁:可以对该key设置一个有效期可以避免死锁的现象。

基于redis实现分布式锁的思路

1、获取锁
多个不同的jvm同时创建一个相同的标记使用setnx命令,因为(全局唯一的)redisKey 必须保证是唯一的,只要谁能够创建成功谁就能够获取锁
SetNx**命令 每次SetNx检查该 key是否已经存在,如果已经存在的话不会执行任何操作。返回为0 如果已经不存在的话直接新增该key。1:新增key成功 0 失败
2、释放锁
对我们的redis的key设置一个有效期(或者主动删除该key) 可以灵活的自动的释放该全局唯一的标记,其他的jvm重新进入到获取锁资源。
3、超时锁(没有获取锁、已经获取锁)
等待获取锁的超时时间
已经获取到锁 锁的有效期5s

Zookeeper实现分布式锁思路

Zookeeper实现分布式锁核心采用临时节点+事件通知,因为zk节点路径是保证全局唯一的,当多个线程同时创建该临时节点,只要谁能够创建成功谁就能够获取到锁。

获取锁:当多个线程同时创建该临时节点,只要谁能够创建成功谁就能够获取到锁。
释放锁:关闭当前Session连接,自动的删除当前的zk节点路径,其他线程重新进入到获取锁阶段。

分布式锁的应用场景有那些

1、分布式任务调度平台保证任务的幂等性
2、分布式全局id 的生成

分布式锁实现方案:

  • 基于数据库方式实现
  • 基于Zk方式实现 采用临时节点+事件通知
  • 基于Redis方式实现 setnx 方式

Redis分布式锁核心代码(仅限于单机版本)

创建maven项目,添加jar

 <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!-- 集成commons工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
    </dependencies>

创建RedisUtil

package com.oyzk.utils;

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

/**
 * @author Kingkang
 * @title RedisUtil  redis工具类
 * @create 2022/12/5
 **/
public class RedisUtil {
    private static String IP = "192.168.198.15";

    //Redis的端口号
    private static int PORT = 6379;

    //可用连接实例的最大数目,默认值为8//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 100;

    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 20;

    //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
    private static int MAX_WAIT = 3000;

    private static int TIMEOUT = 3000;

    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    private static boolean TEST_ON_BORROW = true;

    //return给pool时,是否提前进行validate操作;
    private static boolean TEST_ON_RETURN = true;

    private static JedisPool jedisPool = null;

    /**
     * redis过期时间,以秒为单位
     */
    public final static int EXRP_HOUR = 60 * 60; //一小时
    public final static int EXRP_DAY = 60 * 60 * 24; //一天
    public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一个月

    /**
     * 初始化Redis连接池
     */
    private static void initialPool() {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);

            jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "root");
        } catch (Exception e) {
            //logger.error("First create JedisPool error : "+e);
            e.getMessage();
        }
    }


    /**
     * 在多线程环境同步初始化
     */
    private static synchronized void poolInit() {
        if (jedisPool == null) {
            initialPool();
        }
    }


    /**
     * 同步获取Jedis实例
     *
     * @return Jedis
     */
    public synchronized static Jedis getJedis() {
        if (jedisPool == null) {
            poolInit();
        }
        Jedis jedis = null;
        try {
            if (jedisPool != null) {
                jedis = jedisPool.getResource();
            }
        } catch (Exception e) {
            e.getMessage();
            // logger.error("Get jedis error : "+e);
        }
        return jedis;
    }


    /**
     * 释放jedis资源
     *
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null && jedisPool != null) {
            jedisPool.returnResource(jedis);
        }
    }

    public static Long sadd(String key, String... members) {
        Jedis jedis = null;
        Long res = null;
        try {
            jedis = getJedis();
            res = jedis.sadd(key, members);
        } catch (Exception e) {
            //logger.error("sadd  error : "+e);
            e.getMessage();
        }
        return res;
    }
}

创建RedisLock

package com.oyzk.utils;

import redis.clients.jedis.Jedis;
import sun.plugin.javascript.JSClassLoader;

import java.util.UUID;

/**
 * @author Kingkang
 * @title RedisLock
 * @create 2022/12/5
 **/
public class RedisLock {

    /**
     *
     * @param lockKey 在redis中创建的key值
     * @param notLockTime   尝试获取锁的超时时间
     * @param timeOut   锁的有效期,防止死锁的产生
     * @return  返回lock成功值
     */
    public String getLock(String lockKey,int notLockTime,int timeOut){
        //获取redis 连接
        Jedis jedis = RedisUtil.getJedis();
         //计算我们尝试获取锁的超时时间
        Long endTime=System.currentTimeMillis()+ notLockTime;
        //当前系统时间小于endTime说明获取锁没有超时 继续循环 否则退出循环
        while (System.currentTimeMillis()<endTime){
            String lockValue = UUID.randomUUID().toString();
            //当多个不同的jvm同时创建一个相同的rediskey 只要谁能够创建成功谁就能获取锁
            if(jedis.setnx(lockKey,lockValue)==1){
                //设置锁的有效期
                jedis.expire(lockKey,timeOut/1000);
                return lockValue;
                //退出循环
            }
            //否则情况下, 继续循环
        }
        try {
            if(jedis!=null){
                jedis.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 释放锁
     * @return
     */
    public boolean unLock(String lockey,String lockValue){
        //获取redis连接
        Jedis jedis = RedisUtil.getJedis();
        try {
            //判断获取锁的值,如果通过key返回来的值与要删除的值一致,说明删除的是自己,防止误删除
            if(lockValue.equals(jedis.get(lockey))){
                return jedis.del(lockey)>0?true:false;
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return false;
    }
    /**
     * 获取锁
     * 执行业务
     * 释放锁
     */
}

创建测试

package com.oyzk.service;

import com.oyzk.utils.RedisLock;
import org.apache.commons.lang3.StringUtils;

/**
 * @author Kingkang
 * @title OrderService
 * @create 2022/12/5
 **/
public class OrderService {
    private static final String LOCKKEY="pro_orderId";
    public static void testLoack(){
        RedisLock redisLock = new RedisLock();
        String lockValue = redisLock.getLock(LOCKKEY, 5000,5000);
        if (StringUtils.isEmpty(lockValue)){
            System.out.println(Thread.currentThread().getName()+",获取锁失败了,");
        }
        //执行我们的业务逻辑
        System.out.println(Thread.currentThread().getName()+",获取锁成功了,"+lockValue);

        //释放锁  加上有效期后,就算没有释放锁也没有关系
//        redisLock.unLock(LOCKKEY,lockValue);

    }

    public static void main(String[] args) {
        testLoack();
    }
    /**
     * 尝试获取锁为什么次数限制?
     * 如果我们业务逻辑5s内没有执行完毕呢?
     *
     * 分场景:
     * 1、锁的超时时间根据业务场景来预估
     * 2、可以自己延迟锁的时间
     * 3、在提交事务  的时候检查锁是否已经超时 如果已经超时则回滚(手动回滚) 否则提交
     *
     * 仅限于单击版本
     */
}

在这里插入图片描述

redis集群高可用环境

redis主从复制

基本概念:
单个Redis如果因为某种原因宕机的话,可能会导致Redis服务不可用,可以使用主从复制实现一主多从,主节点负责写的操作,从节点负责读的操作,主节点会定期将数据同步到从节点中,保证数据一致性的问题。

第一步、 准备三台linux,里面都安装了redis

在这里插入图片描述我这里是打算是 redis1 为主,redis2、redis3为从

第二步、配置redis.conf

redis1不用动,

配置redis2的redis.conf
#编辑文件
vi /usr/redis7/bin/redis.conf

相关配置Redis.conf
进入redis.conf
搜索

 /# replicaof <masterip> <masterport>

slaveof 192.168.198.15 6379
masterauth root

在这里插入图片描述

配置好以后,关闭防火墙:
systemctl stop firewalld

重启redis

[root@location bin]# ps aux|grep redis
root       1529  0.2  0.8 163148  8424 ?        Ssl  15:56   0:00 ./redis-server *:6379
root       1549  0.0  0.0 112824   988 pts/0    R+   16:00   0:00 grep --color=auto redis
[root@location bin]# kill -9 1529

进入redis,查看信息 info replication
[root@location bin]# ./redis-cli
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.198.15
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_read_repl_offset:98
slave_repl_offset:98
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:f957cee571628bcbd0ef7a0a41b78852d17a335c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98

在这里插入图片描述

说明redis2已经配置成功,

测试

在redis1主节点负责写,从节点redis2只能是读,说明是对的
在这里插入图片描述
测试redis2进行写报错,因为从节点只能读

在这里插入图片描述
在redsi2执行get username ,获取redis1新增的username,是可以获取
在这里插入图片描述

总结

测试完毕,说明redis1和redis2主从关系已经配置完毕
接下来配置redis3,
注意:该主从同步方式存在 如果从节点非常多的话,会导致对主节点同步多个从节点压力非常大
可以采用树状类型解决该问题
那如果采用树状类型的话,redis3配置的ip应该是redis2的信息(ip port pwd),redis3配置就不演示,附一张图:

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

主从复制数据同步的过程

1.Redis从节点向主节点建立socket连接
2.Redis采用全量或者增量的形式将数据同步给从节点

从Redis2.8版本以后 过程采用增量和全量同步
全量复制:一般用于在初次的复制场景(从节点与主节点一次建立)
增量复制:网络出现问题,从节点再次连接主节点时,主节点补发缺少的数据,每次数据增量同步

主从复制存在那些缺陷

如果主节点存在了问题,整个Redis环境是不可以实现写的操作,需要人工更改配置变为主操作
如何解决该问题:使用哨兵机制可以帮助解决Redis集群主从选举策略

主从哨兵机制

Redis的哨兵机制就是解决我们以上主从复制存在缺陷(选举问题),解决问题保证我们的Redis高可用,实现自动化故障发现与故障转移。

哨兵机制原理

1、哨兵机制每个10s时间只需要配置监听我们的主节点就可以获取当前整个Redis集群的环境列表,采用info 命令形式。
2、哨兵不建议是单机的,最好每个Redis节点都需要配置哨兵监听。
3、哨兵集群原理是如何:多个哨兵都执行同一个主的master节点,订阅到相同都通道,有新的哨兵加入都会向通道中发送自己服务的信息,该通道的订阅者可以发现新哨兵的加入,随后相互建立长连接。
4、Master的故障发现 单个哨兵会向主的master节点发送ping的命令,如果master节点没有及时的响应,哨兵会认为该master节点为“主观不可用状态”会发送给其他都哨兵确认该Master节点是否不可用,当前确认的哨兵节点数>=quorum(可配置),会实现重新选举。

实现哨兵机制1-树状型

第一步、copy 哨兵的配置文件

从redis-7.0.5copy到redis7在这里插入图片描述

cp /usr/redis-7.0.5/sentinel.conf  /usr/redis7/bin

cd  /usr/redis7/bin
第二步、编辑sentinel.conf
vi sentinel.conf
三台redis都要修改 daemonize

意思是:后台启动

daemonize yes

在这里插入图片描述

修改 redis2的哨兵sentinal的mymaster
配置redis2

2 根据实际情况来,如果哨兵是三个建议是2,

sentinel monitor mymaster 192.168.198.15 6379 2
sentinel auth-pass mymaster root

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

配置redis1
sentinel monitor mymaster 192.168.198.15 6379 2
sentinel auth-pass mymaster root

在这里插入图片描述

配置redis3
sentinel monitor mymaster 192.168.198.15 6379 2
sentinel auth-pass mymaster root

在这里插入图片描述

第二步、启动哨兵

启动哨兵之前需要把防火墙关闭
启动命令
./redis-sentinel ./sentinel.conf

[root@location bin]# ls
dump.rdb  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis.conf  redis-sentinel  redis-server  sentinel.conf
[root@location bin]# ./redis-sentinel ./sentinel.conf

第三步、测试

现在我们可以看一下,主从关系

[root@location bin]# ./redis-cli
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.198.16,port=6379,state=online,offset=37351,lag=1
master_failover_state:no-failover
master_replid:f957cee571628bcbd0ef7a0a41b78852d17a335c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:37780
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:37780
127.0.0.1:6379>  

手动的把redis1主节点关掉
[root@location bin]# ps -aux|grep redis
root       1656  0.2  0.8 163148  8216 ?        Ssl  16:02   0:12 ./redis-server *:6379
root       1746  0.5  0.8 163272  8684 ?        Ssl  17:17   0:01 ./redis-sentinel *:26379 [sentinel]
root       1762  0.0  0.0 112824   984 pts/1    S+   17:23   0:00 grep --color=auto redis
[root@location bin]# kill -9 1656

在这里插入图片描述

这时候就会在redis2和redis3进行选举了

查看关系

info replication

redis2redis3

这时候你会发现redis2变成主节点了,此时redis2也可以写入了
在这里插入图片描述

重启redis1节点,此时redis1会变成从节点,

但是哨不能帮助我们解决数据同步的问题,所以要自己配置一下
在这里插入图片描述修改redis.conf

slaveof 192.168.198.16 6379
masterauth root

在这里插入图片描述
重启redis1就可以解决

在这里插入图片描述

实现哨兵机制2

一个哨兵为主,其他都是跟随

第一步、还是copyredis哨兵配置文件

之前有配置过哨兵配置文件的,可以先删除,再进行copy

rm -rf sentinel.conf 
cp /usr/redis-7.0.5/sentinel.conf  /usr/redis7/bin

第二步、修改配置文件

sentinel.conf
以及redis.conf

# sentinel.conf
sentinel monitor mymaster 192.168.198.15 6379 1
sentinel auth-pass mymaster root

#redis.conf
# replicaof <masterip> <masterport>
slaveof 192.168.198.15 6379
masterauth root

redis1
redis2
redis3

第三步、重启哨兵
daemonize yes

如果不是后台启动,可以修改一下配置

[root@location bin]# ./redis-server ./redis.conf
[root@location bin]# ./redis-sentinel ./sentinel.conf
2049:X 06 Dec 2022 09:58:42.035 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2049:X 06 Dec 2022 09:58:42.036 # Redis version=7.0.5, bits=64, commit=00000000, modified=0, pid=2049, just started
2049:X 06 Dec 2022 09:58:42.036 # Configuration loaded
2049:X 06 Dec 2022 09:58:42.040 * Increased maximum number of open files to 10032 (it was originally set to 1024).
2049:X 06 Dec 2022 09:58:42.040 * monotonic clock: POSIX clock_gettime
2049:X 06 Dec 2022 09:58:42.040 # Warning: Could not create server TCP listening socket *:26379: bind: Address already in use
2049:X 06 Dec 2022 09:58:42.040 # Failed listening on port 26379 (TCP), aborting.
[root@location bin]# ps -aux |grep redis
root       1969  0.3  0.8 163148  8908 ?        Sl   09:51   0:01 ./redis-sentinel *:26379 [sentinel]
root       2044  0.2  0.8 165708  8596 ?        Ssl  09:58   0:00 ./redis-server *:6379
root       2051  0.0  0.0 112824   984 pts/2    R+   09:59   0:00 grep --color=auto redis
[root@location bin]# 

第三步、访问,查看信息
[root@location bin]# ./redis-cli
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.198.15
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:97721
slave_repl_offset:97721
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:2
slave0:ip=192.168.198.16,port=6379,state=online,offset=97721,lag=1
slave1:ip=192.168.198.17,port=6379,state=online,offset=97721,lag=0
master_failover_state:no-failover
master_replid:14ef6bd979658c2d5c63677b64d32297fbc02234
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:97721
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:92587
repl_backlog_histlen:5135
127.0.0.1:6379> 


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

第四步、测试,断掉主节点redis,让哨兵自动选举

在这里插入图片描述
此时选举完:查看

在这里插入图片描述sentinel配置文件也会自动的变化
在这里插入图片描述

Redis Cluster集群

传统Redis集群存在那些问题

Redis哨兵集群模式,每个节点都保存全量同步数据,冗余的数据比较多;而在Redis Cluster模式中集群中采用分片集群模式,可以减少冗余数据,缺点就是构建该集群模式成本非常高,

传统RedisCluster集群的原理

Redis3.0开始官方推出了集群模式 RedisCluster原理采用hash槽的概念,预先分配14884个卡槽,并且将该卡槽分配给具体服务的节点;通过key进行crc16(key)%14884 获取余数,余数就是对应的卡槽的位置,一个卡槽可以存放多个不同的key,从而将读或者写转发到该卡槽的服务的节点。 最大的有点:动态扩容、缩容

RedisCluster集群模式环境搭建

redis.conf

[root@location usr]# mkdir rediscluster

[root@location usr]# cd rediscluster/

mkdir redis7000
mkdir redis7001
mkdir redis7002
mkdir redis7003
mkdir redis7004
mkdir redis7005

每个配置文件内容

daemonize yes #后台启动
protected-mode no ; ## 允许外部访问
port 7005 #修改端口号,从7000到7005
cluster-enabled yes #开启cluster,去掉注释
cluster-config-file 7000nodes.conf #自动生成
cluster-node-timeout 15000 #节点通信时间
dbfilename 7000dump.rdb #修改对应的rdb文件名建议:端口号+dump.rdb
logfile   /usr/rediscluster/redis7005/redis.log

启动我们的redis

/usr/redis/bin/redis-server /usr/rediscluster/redis7000/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7001/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7002/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7003/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7004/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7005/redis.conf



连接一个redis

/usr/redis/bin/redis-cli -h 192.168.198.15 -p 7000

set username zhangsan

在这里插入图片描述

(error) CLUSTERDOWN Hash slot not served  说明没有分配hash槽\
#分配卡槽
/usr/redis/bin/redis-cli --cluster create  192.168.198.15:7000  192.168.198.15:7001  192.168.198.15:7002  192.168.198.15:7003  192.168.198.15:7004  192.168.198.15:7005  --cluster-replicas 1

(建议最好使用服务器的ip地址搭建)

分配我们hash操作

在这里插入图片描述Can I set the above configuration? (type ‘yes’ to accept): yes

说明卡槽分配成功
在这里插入图片描述

/usr/redis/bin/redis-cli -h 192.168.198.15 -p 7000
set username zhangsan

在这里插入图片描述不会自动的重定向

修改为Redis的集群方式连接
/usr/redis/bin/redis-cli -h 192.168.198.15 -p 7000 -c
set username zhangsan
/usr/redis/bin/redis-cli --cluster help
这样的话就支持转发
#查看当前服务器集群节点
192.168.198.15:7000> cluster nodes

在这里插入图片描述说明已经成功了

RedisCluster集群模式扩容节点

首先新增两台Redis节点 一主、一从;

/usr/redis/bin/redis-server /usr/rediscluster/redis7006/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7007/redis.conf

案例:
/usr/redis/bin/redis-cli --cluster add-node 新增集群地址:端口 集群中已经存在的任意一台连接地址:端口
实战:

/usr/redis/bin/redis-cli --cluster add-node 192.168.198.15:7006   192.168.198.15:7000  

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

可能出现的错误

[ERO]Node 192.168.198.15:7000 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0

解决办法: 重新启动我们所有的redis、rdb和aof文件能够产生冲突 其次 可以使用情况当前所有的内容的数据源

默认在集群中是为master节点;

扩容从节点: aebf0e9f947a8565cee1ec20c753acd5640450a6 id为7006节点的

/usr/redis/bin/redis-cli --cluster add-node   192.168.198.15:7007  192.168.198.15:7000  --cluster-slave   --cluster-master-id  aebf0e9f947a8565cee1ec20c753acd5640450a6

在这里插入图片描述运行效果
在这里插入图片描述查看

[root@location rediscluster]# /usr/redis/bin/redis-cli -h 192.168.198.15 -p 7000 -c
192.168.198.15:7000> cluster nodes

在这里插入图片描述如果中有三台master节点,则每台卡槽为5462
如果中有四台master节点,则每台卡槽为4096

分配卡槽

/usr/redis/bin/redis-cli --cluster reshard  192.168.198.15:7000

在这里插入图片描述
然后回车,输入yes即可

[root@location rediscluster]# /usr/redis/bin/redis-cli -h 192.168.198.15 -p 7000 -c
192.168.198.15:7000> cluster nodes
192.168.198.15:7001> set mm mm
-> Redirected to slot [125] located at 192.168.198.15:7006
OK
192.168.198.15:7006> get mm

在这里插入图片描述

RedisCluster master宕机效果

如果master宕机之后,会自动的在从节点会顺利的选为主节点;当原来的主节点在恢复启动的时候会变为从节点;

[root@location rediscluster]# ps aux|grep redis
root       2403  0.6  1.1 163208 11464 ?        Ssl  10:57   0:12 /usr/redis/bin/redis-server *:7000 [cluster]
root       2405  0.6  1.0 162696 10032 ?        Ssl  10:57   0:11 /usr/redis/bin/redis-server *:7001 [cluster]
root       2410  0.5  0.8 156552  8100 ?        Ssl  10:57   0:10 /usr/redis/bin/redis-server *:7002 [cluster]
root       2415  0.1  0.8 156552  8044 ?        Ssl  10:57   0:03 /usr/redis/bin/redis-server *:7003 [cluster]
root       2420  0.1  0.8 156552  8040 ?        Ssl  10:57   0:03 /usr/redis/bin/redis-server *:7004 [cluster]
root       2428  0.1  0.8 156552  8044 ?        Ssl  10:57   0:03 /usr/redis/bin/redis-server *:7005 [cluster]
root       2442  1.0  0.8 156552  8044 ?        Ssl  10:59   0:17 /usr/redis/bin/redis-server *:7006 [cluster]
root       2447  0.1  0.8 156552  8024 ?        Ssl  10:59   0:03 /usr/redis/bin/redis-server *:7007 [cluster]
root       2509  0.0  0.0 112824   984 pts/1    S+   11:27   0:00 grep --color=auto redis
[root@location rediscluster]# kill 2442
[root@location rediscluster]# /usr/redis/bin/redis-cli -h 192.168.198.15 -p 7000 -c
192.168.198.15:7000> cluster nodes
d784718c901f7645cbc937b6a0f1763410e6c9e7 192.168.198.15:7002@17002 master - 0 1672284515000 3 connected 12288-16383
af6b71a9d858b5d66fcebc9fe606be3920995ad2 192.168.198.15:7001@17001 master - 0 1672284515000 2 connected 6827-10922
90f8bc451e60c3e15c21536bd456eb736a8eec8a 192.168.198.15:7003@17003 slave d784718c901f7645cbc937b6a0f1763410e6c9e7 0 1672284514000 4 connected
a15a69faa459a3e2722066cc349febad9489ee46 192.168.198.15:7007@17007 master - 0 1672284517195 9 connected 0-1364 5461-6826 10923-12287
18de4c238658eae8676c6f9e5deea8b8f9b48146 192.168.198.15:7004@17004 slave f0ea588089e2c64d496c53d93be633291dadbf13 0 1672284516168 5 connected
d60f9b17b148f7af523bcc83a67af02dda9f87b6 192.168.198.15:7005@17005 slave af6b71a9d858b5d66fcebc9fe606be3920995ad2 0 1672284515141 6 connected
f0ea588089e2c64d496c53d93be633291dadbf13 192.168.198.15:7000@17000 myself,master - 0 1672284512000 1 connected 1365-5460
aebf0e9f947a8565cee1ec20c753acd5640450a6 192.168.198.15:7006@17006 master,fail - 1672284495959 1672284492000 8 disconnected
192.168.198.15:7000> 

在这里插入图片描述如果重新启动:7006,7006就会自动的变为从节点
在这里插入图片描述

RedisCluster集群模式缩容节点

/usr/redis/bin/redis-cli --cluster  reshard  192.168.198.15:7000  --cluster-from  
需要缩容的卡槽节点  --cluster-to  接受卡槽的节点  --cluster-slots 4096
/usr/redis/bin/redis-cli --cluster  reshard  192.168.198.15:7000  --cluster-from   a15a69faa459a3e2722066cc349febad9489ee46 --cluster-to f0ea588089e2c64d496c53d93be633291dadbf13 --cluster-slots 4096

然后我们可以查询之前7007节点新增的mm,显示已经在7000节点,说明已经缩容成功

redis支持RedisCluster集群

 <dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
</dependencies>

单机版Jedis

 		Jedis jedis = new Jedis("192.168.198.15", 7000);
        String value = jedis.get("mm");
        System.out.println("value:" + value);
        jedis.close();

Redis支持集群版本

public class RedisCluster {
    private static JedisCluster jedis;

    static {
        // 添加集群的服务节点Set集合
        Set<HostAndPort> hostAndPortsSet = new HashSet<HostAndPort>();
        // 添加节点
        hostAndPortsSet.add(new HostAndPort("192.168.198.15", 7000));
        hostAndPortsSet.add(new HostAndPort("192.168.198.15", 7001));
        hostAndPortsSet.add(new HostAndPort("192.168.198.15", 7002));
        hostAndPortsSet.add(new HostAndPort("192.168.198.15", 7003));
        hostAndPortsSet.add(new HostAndPort("192.168.198.15", 7004));
        hostAndPortsSet.add(new HostAndPort("192.168.198.15", 7005));
        // Jedis连接池配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大空闲连接数, 默认8个
        jedisPoolConfig.setMaxIdle(100);
        // 最大连接数, 默认8个
        jedisPoolConfig.setMaxTotal(500);
        //最小空闲连接数, 默认0
        jedisPoolConfig.setMinIdle(0);
        // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1 设置2秒
        jedisPoolConfig.setMaxWaitMillis(2000);
        //对拿到的connection进行validateObject校验
        jedisPoolConfig.setTestOnBorrow(true);
        jedis = new JedisCluster(hostAndPortsSet, jedisPoolConfig);
    }

    public String getKye(String key) {
        return jedis.get(key);
    }

    public static void main(String[] args) {
        String value = new RedisCluster().getKye("mm");
        System.out.println("value:" + value);
    }


}

基于布隆过滤器解决缓存穿透问题

缓存穿透

产生的背景:
缓存穿透是指使用不存在的key进行大量的高并发查询,导致缓存无法命中,每次请求都要都要穿透到后端数据库查询,使得数据库的压力非常大,甚至导致数据库服务压死;
解决方案:
4.接口层实现api限流、用户授权、id检查等;
5.从缓存和数据库都取不到数据的话,一样将数据库空值放入缓存中,设置30s有效期避免使用同一个id对数据库攻击压力大;

布隆过滤器基本介绍

布隆过滤器适用于判断某个数据是否在集合中存在,不一定百分百准备, Bloom Filter基本实现原理采用位数组与联合函数一起实现;

基于布隆过滤器解决缓存穿透问题

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>22.0</version>
</dependency>
public class BlongTest {
    /**
     * 在布隆中存放100万条数据
     */
    private static Integer size = 1000000;

    public static void main(String[] args) {
        BloomFilter<Integer> integerBloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.01);
        for (int i = 0; i < size; i++) {
            integerBloomFilter.put(i);
        }
        // 从布隆中查询数据是否存在
        ArrayList<Integer> strings = new ArrayList<>();
        for (int j = size; j < size + 10000; j++) {
            if (integerBloomFilter.mightContain(j)) {
                strings.add(j);
            }
        }
        System.out.println("误判数量:" + strings.size());

    }
}

基于布隆过滤器解决Redis击穿问题

 @RequestMapping("/getOrder")
public OrderEntity getOrder(Integer orderId) {
    if (integerBloomFilter != null) {
        if (!integerBloomFilter.mightContain(orderId)) {
            System.out.println("从布隆过滤器中检测到该key不存在");
            return null;
        }
    }

    // 1.先查询Redis中数据是否存在
    OrderEntity orderRedisEntity = (OrderEntity) redisTemplateUtils.getObject(orderId + "");
    if (orderRedisEntity != null) {
        System.out.println("直接从Redis中返回数据");
        return orderRedisEntity;
    }
    // 2. 查询数据库的内容
    System.out.println("从DB查询数据");
    OrderEntity orderDBEntity = orderMapper.getOrderById(orderId);
    if (orderDBEntity != null) {
        System.out.println("将Db数据放入到Redis中");
        redisTemplateUtils.setObject(orderId + "", orderDBEntity);
    }
    return orderDBEntity;
}

@RequestMapping("/dbToBulong")
public String dbToBulong() {
    List<Integer> orderIds = orderMapper.getOrderIds();
    integerBloomFilter = BloomFilter.create(Funnels.integerFunnel(), orderIds.size(), 0.01);
    for (int i = 0; i < orderIds.size(); i++) {
        integerBloomFilter.put(orderIds.get(i));
    }

    return "success";
}

布隆过滤器最大的问题:就是可能会存在一个误判的问题,如果向误判概率越低,则二进制数组会越大,同时也会非常占用空间

缓存穿透的问题?

缓存的穿透:指定使用一些不存在的key进行大量的查询Redis,导致无法命中
每次的请求都会要传查询到我们数据库;对我们的数据库的压力非常大;

1.对我们的服务接口api实现限流、用户授权、黑名单和白名单拦截;
2.从缓存和数据库都查询不到结果的话,一样将数据库空值结果缓存到Redis
中;设置30s的有效期避免使用同一个id对数据库攻击。
如果黑客真的在攻击的情况下,随机成id肯定是不一样的。
3.布隆过滤器

什么是布隆过滤器呢?

HashMap? 检测该key是否存在 原理:先计算对应的key存放到数组的
Index? O(1)

布隆过滤器 作用:

布隆过滤器适用于判断一个元素在集合中是否存在,但是可能会存在误判的问题。
实现的原理采用二进制向量数组和随机映射hash函数。

布隆过滤器为什么会产生冲突 ,会根据key计算hash值,可能与布隆过滤器中存放的元素hash产生冲突都是为1,布隆可能会产生误判可能存在。
如何解决这个问题:二进制数组长度设置比较大,可以减少布隆误判的概率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值