Redis笔记(4)

目录

事务

管道

发布/订阅(了解)

Redis复制(replica)

哨兵(sentinel)监控

集群分片

集群算法-分片-槽位slot:

配置Redis集群:

集群读写:

节点从属调整

主从扩容

主从缩容

注意:

Springboot整合redis

springboot连接redis常见问题

Jedis使用​编辑

lettuce使用

集成redistemplate

redistemplate连接单机:

redistemplate连接集群:


事务

redis的事务没有回滚操作

常用命令:

1.discard:取消事务

2.exec:执行事务中的命令

3.multi:标记事务开启

4.unwatch:取消watch命令对使用key的监视

5.watch key [key...]:监视一个或多个key,如果在事务执行前这个key被其他命令修改,事务被打断

正常执行流程:multi,cmd1,cmd2,......,exec

放弃事务流程:multi,cmd1,cmd2,......,discard

一条出错全部放弃流程(语法出错如忘记写value:set k1):multi,cmd1,出错cmd,exec

出错命令不影响其他命令流程(运行时异常,逻辑有问题):multi,set email a@qq.com,incr email,exec

watch监控流程:先监控在开启事务multi

管道

发布/订阅(了解)

Redis复制(replica)

就是主从复制,master以写为主,slave以读为主,当master数据变化时,自动将新的数据异步同步到其他slave数据库。

**功能:**1.读写分离 2.容灾恢复 3.数据备份 4.水平扩容支撑高并发

**配置:**slave数据库配置master数据库,如果master有密码,slave要配置masterauth来设置校验密码,否则master拒绝访问请求。

操作:

配置文件:

指定端口:

从机配置主机:

启动redis后查看:

哨兵(sentinel)监控

主从复制中当master数据库挂了就无法再往redis中进行写操作,因为slave集群中无法选举出一个新的master,所以引入了哨兵监控。

哨兵的作用:

1、监控redis的运行状态,包括master和slave

2、当master宕机了,能自动将一个slave切换为新的master

sentinel.conf:

redis数据库和redis哨兵是两种不同的程序

启动命令:

启动redis数据库:redis-server /redis6379.conf(配置文件) 默认端口:6379

启动redis哨兵:redis-sentinel /sentinel26379.conf(配置文件) 默认端口:26379

或者:redis-server /sentinel26379.conf --sentinel

**哨兵运行流程:**正常运行-》sdown主观下线(存在哨兵没有在规定时间内收到master的心跳)-》odown客观下线(所有哨兵讨论后(达到quorum)确认没有收到心跳)-》在哨兵中选择leader哨兵-》由leader通过故障切换流程选出新的master。

选举leader哨兵的方法:Raft算法

**Raft算法基本思路:**先到先得,即一轮选举中,哨兵a向b发送成为leader的申请,如果b未同意过其他哨兵,则同意a为leader。

选举新master的原理图:

哨兵的使用建议:

集群分片

哨兵监控+主从复制 并不能保证数据零丢失,因此有了集群分片

集群 与 哨兵+主从 是替换关系

集群算法-分片-槽位slot:

slot槽位映射一般使用3中办法:

1、哈希取余分区:

缺点:扩容后或者某个节点挂了容易导致之前存储key的大规模的混乱

2、一致性哈希算法分区:

优点:容错性 , 扩展性

缺点:数据倾斜问题

3、哈希槽分区。

**Redis集群不能保证数据强一致性:**当客户端在master完成写操作后,如果master还没来得及向slave复制数据就突然挂了,那么当slave被切换为master时,数据就会不一致。

配置Redis集群:

1.分别为每个redis数据库上创建一个对应的配置文件:

2.文件内容:

3.以集群方式启动每个redis服务:

4.建立主从关系:这个命令随机设置主从关系

5.可以依次打开每个redis检查主从关系:

6.也可以使用 cluster nodes 查看集群关系:

集群读写:

key会先计算:crc16(key)%16384 得到 槽位, 对应的槽位要到相应的redis节点

如果槽位不在当前redis节点会报错

解决方法:防止路由失效加参数-c,这样会将key存放到相应的redis节点

-c:以集群模式运行客户端

节点从属调整

redis集群中master挂了后,会自动将slave提升为master,原来的master恢复后变为slave,可以使用下面的命令切换master和slave。

命令:cluster failover

redis-cli 的从机将 与其主机调换角色。

该命令只能在群集slave节点执行,让slave节点进行一次人工故障切换。

主从扩容

用哪个redis启动的集群,在进行扩容的时候,指定的ip和端口,必须为那个redis

将启动的redis数据库加到集群中:

检查集群情况:

重新分派槽位:

设置分派数量:(可以设置为 16384 / master数)

再次检查集群情况:

为新的master分配slave:

主从缩容

1.先清除slave

slave成功删除

2.把槽位重新分派

检查集群情况:要删除的master变成了一个master的slave

3.删除master

检查集群情况:

注意:

Springboot整合redis

就像java要访问mysql需要一些中间操作:jdbc,jdbctemplate,mybatis;

java访问redis也需要,一般使用3种:jedis,lettuce,RedisTemplate(推荐)

jedis存在一些安全问题,lettuce能力比jedis更强

RedisTemplate就是spring对lettuce的封装。

springboot连接redis常见问题

注释bind配置或设置为bind 0.0.0.0

保护模式设置为no

Linux系统的防火墙设置

redis的ip地址和密码是否正确

记得写访问redis的服务器端口号和auth密码

Jedis使用

操作步骤:

创建一个java项目:redis-project

导入pom依赖:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<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>

    <groupId>com.qiu</groupId>
    <artifactId>redis-project</artifactId>
    <version>1.0-SNAPSHOT</version>
<!--    设置为springboot工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/>
    </parent>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
<!--        springboot 通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        jedis依赖-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.3.1</version>
        </dependency>
<!--        通用基础配置-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>
</project>

编写配置文件:application.properties

XML 复制代码
server.port=7777
spring.application.name=redis_study

入门程序:

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.args.ListPosition;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JedisDemo {
    public static void main(String[] args) {
        //通过ip和端口获取redis的connection
        Jedis jedis = new Jedis("192.168.234.127",6379);
        //指定redis的密码
        jedis.auth("123456");
        //测试是否连通
        jedis.flushDB();
        System.out.println("jedis.ping() = " + jedis.ping());
        //keys  *
        Set<String> keys = jedis.keys("*");
        System.out.println("keys = " + keys.toString());
        //string
        System.out.println("============string===============");
        jedis.set("k1","v1");
        String s = jedis.get("k1");
        System.out.println("s = " + s);
        jedis.mset("k2", "v2", "k3", "v3");
        List<String> mget = jedis.mget("k1", "k2", "k3");
        System.out.println("mget = " + mget.toString());

        //列表
        System.out.println("============列表===============");
        jedis.lpush("l1","l11","l12","l13");
        List<String> l1 = jedis.lrange("l1", 0, -1);
        System.out.println("l1 = " + l1.toString());
        jedis.linsert("l1", ListPosition.AFTER,"l11","l11.5");
        List<String> l11 = jedis.lrange("l1", 0, -1);
        System.out.println("l1 = " + l11.toString());

        //hash
        System.out.println("============hash===============");
        jedis.hset("h1","name","qiu");
        String hget = jedis.hget("h1", "name");
        System.out.println("hget = " + hget);
        Map<String,String> map = new HashMap<String,String>();
        map.put("name","liu");
        map.put("age","23");
        map.put("addr","广东");
        jedis.hset("h2",map);
        List<String> hmget = jedis.hmget("h2", "name", "addr", "age");
        System.out.println("hmget = " + hmget.toString());

        //set
        System.out.println("============set===============");
        jedis.sadd("set1","setv1","setv2","setv3");
        Set<String> set1 = jedis.smembers("set1");
        System.out.println("set1. = " + set1.toString());
        long set11 = jedis.scard("set1");
        System.out.println("set11 = " + set11);
        jedis.srem("set2","setv1");
        jedis.sadd("set2","setv1","set2v2");
        Set<String> set2 = jedis.smembers("set2");
        System.out.println("set2 = " + set2.toString());
        Set<String> sdiff = jedis.sdiff("set1", "set2");
        System.out.println("sdiff = " + sdiff.toString());


        //zset
        System.out.println("============zset===============");
        jedis.zadd("z1",1d,"v1");
        jedis.zadd("z1",2d,"v2");
        jedis.zadd("z1",0d,"v3");
        List<String> z1 = jedis.zrange("z1", 0, -1);
        System.out.println("z1 = " + z1.toString());
    }
}

lettuce使用

导入lettuce依赖

java 复制代码
<!--        lettuce依赖-->
        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
            <version>6.2.1.RELEASE</version>
        </dependency>
java 复制代码
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

public class LettuceDemo {
    public static void main(String[] args) {
        //使用构建器链式编程来builder RedisURI
        RedisURI uri = RedisURI.builder().redis("192.168.234.127").withPort(6379)
                .withAuthentication("default","123456").build();
        //创建连接客户端
        RedisClient client = RedisClient.create(uri);
        StatefulRedisConnection connect = client.connect();

        //通过connect创建操作命令
        RedisCommands cmd = connect.sync();

        //命令
        cmd.set("k1","v1");

        //关闭资源
        connect.close();
        client.shutdown();


    }
}

集成redistemplate

redistemplate连接单机:

添加依赖:

XML 复制代码
<!--        springboot与redis整合依赖-->
        <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>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

spring与redis整合包:

复制代码
spring-boot-starter-data-redis

swagger:调用微服务接口的工具

配置文件修改:

XML 复制代码
server.port=7777
spring.application.name=redis_study
# logging
logging.level.root = info
logging.level.com.qiu.redis = info
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n

# swagger
spring.swagger2.enadble=true

spring.mvc.pathmatch.matching-strategy=ant_path_matcher

# redis 单机
spring.redis.database=0

spring.redis.host=192.168.234.127
spring.redis.port=6379
spring.redis.password=123456
#连接池
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

编写配置类:

swagger配置类:

java 复制代码
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Value("${spring.swagger2.enabled}")
    private Boolean enadbled;

    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enadbled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.qiu.redis"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("springboot使用swagger构建api接口文档  "+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now()))
                .description("springboot+redis整合")
                .version("1.0")
                .build();
    }
}

编写控制层和业务层代码:

service:

java 复制代码
@Service
@Slf4j
public class OrderService {
    private static final String ORDER_KEY = "ord:";
    @Resource
    private RedisTemplate redisTemplate;

//    添加京东订单
    public void addOrder(){
        int keyId = ThreadLocalRandom.current().nextInt(1000)+1;
        String serialNo = UUID.randomUUID().toString();

        String key = ORDER_KEY+keyId;
        String value = "京东订单"+serialNo;

        //string类型
        redisTemplate.opsForValue().set(key,value);
        log.info("********key:{}",key);
        log.info("********value:{}",value);
    }
    //获取订单
    public String getOrderById(Integer keyId){
        return (String) redisTemplate.opsForValue().get(ORDER_KEY+keyId);
    }

}

controller:

java 复制代码
@RestController
@Slf4j
@Api(tags = "订单接口")
public class OrderController {

    @Resource
    private OrderService orderService;

    @ApiOperation("新增订单")
    @PostMapping(value = "/order/add")
    public void addOrder(){
        orderService.addOrder();
    }

    @ApiOperation("按照keyid查询订单")
    @PostMapping("order/{keyId")
    public void getOederById(@PathVariable Integer keyId){
        String orderById = orderService.getOrderById(keyId);
        System.out.println("orderById = " + orderById);
    }


}

启动服务

访问:

但此时访问redis数据库:

序列化问题

解决方法1:不使用RedisTemplate,使用他的子类StringRedisTemplate

解决方法2:配置springboot项目中redis配置类

java 复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
redistemplate连接集群:

这时可以正常操作,但如果redis服务有一个宕机了,springboot无法动态感知集群的最新消息(即redis已经将slave升级为master但springboot不知道)

第一种方法的操作:(不推荐)

第三种方法操作:

在application配置文件中添加

相关推荐
冷眼看人间恩怨37 分钟前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
独行soc43 分钟前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain1 小时前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship1 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站1 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
梦想平凡3 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
TianyaOAO3 小时前
mysql的事务控制和数据库的备份和恢复
数据库·mysql
Ewen Seong3 小时前
mysql系列5—Innodb的缓存
数据库·mysql·缓存
码农老起4 小时前
企业如何通过TDSQL实现高效数据库迁移与性能优化
数据库·性能优化