前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring boot3 / spring cloud遇到的一系列问题记录(二) —— 努力成为优秀的架构师

spring boot3 / spring cloud遇到的一系列问题记录(二) —— 努力成为优秀的架构师

作者头像
躺平程序员老修
发布2023-11-16 20:30:27
3010
发布2023-11-16 20:30:27
举报

Spring Cloud

注:本章内容承接 spring boot / spring cloud遇到的一系列问题记录(一) —— 努力成为优秀的架构师

由于数据库字段有限,特此进行拆分。 完整源码参考 https://github.com/ShyZhen/scd

搭建配置中心 spring-cloud-config-server

目前我们的项目是微服务架构,如果每个项目都有自己的配置文件,首先管理起来麻烦,其次不够高端,于是需要搭建一个统一管理配置的服务,也就是我们的config模块。

  • (1)在config模块的pom文件中引入依赖
代码语言:javascript
复制
        <!--  在config模块中引入spring-cloud-config-server依赖,搭建一个配置服务器  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
  • (2)在启动类标注@EnableConfigServer注解

/scd/config/src/main/java/com/litblc/config/ConfigApplication.java

代码语言:javascript
复制
// 标注@EnableConfigServer搭建配置服务器
@EnableConfigServer
@SpringBootApplication
public class ConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class, args);
    }
}
  • (3)配置文件设置本地文件读取配置,也可以设置其他方式,比如github读取、数据库读取等

更多配置参考文档 https://springdoc.cn/spring-cloud-config/

代码语言:javascript
复制
# 配置服务器的端口,通常设置为8888:
server:
  port: ${APP_PORT:8888}

spring:
  application:
    name: config-server
  profiles:
    # 从本地文件读取配置时,Config Server激活的profile必须设定为native:
    active: native
  cloud:
    config:
      server:
        # 禁用 JdbcEnvironmentRepository 的自动配置。
        jdbc:
          enabled: false
        native:
          # 设置配置文件的搜索路径:
          search-locations: file:./config-repo, file:../config-repo, file:../../config-repo
 
  # 参考 `步骤(7)`,可以删掉这段配置项
  # 依赖库中有spring-boot-starter-jdbc,必须配置本项目的DataSource
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fmock
    username: root
    password: root
  • (4)配置本地配置文件目录 在上面search-locations中我们配置了config-repo文件夹,根据我们项目名字的不同,创建几个配置文件(文件名和项目名要对应上),代码结构如下

一些示例配置源码如下:

/scd/config-repo/application.yml

代码语言:javascript
复制
# 通用common configuration
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fmock
    username: root
    password: root

  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      #password:
      database: ${REDIS_DATABASE:0}
      database1: ${REDIS_DATABASE1:1}

storage:
  local:
    # 文件存储根目录:
    root-dir: ${STORAGE_LOCAL_ROOT:/var/storage}
    max-size: ${STORAGE_LOCAL_MAX_SIZE:102400}
    allow-empty: false
    allow-types: jpg, png, gif

mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

/scd/config-repo/fmock.yml

代码语言:javascript
复制
# fmock configuration
# http://localhost:8888/fmock/default
server:
  port: ${APP_PORT:8081}
  servlet:
    context-path: /api

/scd/config-repo/push.yml

代码语言:javascript
复制
# push configuration
# http://localhost:8888/push/default
server:
  port: ${APP_PORT:8083}
  servlet:
    context-path: /api
  • (5)让其他项目使用配置中心的服务

完成上一步我们已经配置好了配置中心,启动项目输入http://localhost:8888/fmock/default即可看到生效的配置,application.yml的配置是会一起返回的(访问地址跟文件名对应)

接下来我们要做的是让我们的fmock模块、push模块使用上配置中心服务

首先需要在fmock/pom.xml添加客户端依赖,否则无法解析本地配置的import: configserver:xxx参数

代码语言:javascript
复制
        <!-- 使用配置中心,需要依赖SpringCloud Config客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

然后修改模块的fmock/src/main/resources/application.yml配置文件

代码语言:javascript
复制
spring:
  application:
    # 必须设置app名称,要跟config-repo中的`文件名`对应:
    name: ${APP_NAME:fmock}
  config:
    # 导入Config Server地址:
    import: configserver:${CONFIG_SERVER:http://localhost:8888}

push/src/main/resources/application.yml

代码语言:javascript
复制
spring:
  application:
    # 必须设置app名称,要跟config-repo中的`文件名`对应:
    name: ${APP_NAME:push}
  config:
    # 导入Config Server地址:
    import: configserver:${CONFIG_SERVER:http://localhost:8888}
  • (6)启动fmock或者push项目即可自动匹配
  • (7)想区分环境,只需要更改模块的名字,并添加一个对应的配置文件即可

比如添加config-repo/fmock-dev.yml开发环境配置文件,然后更改fmock模块的spring.application.name=fmock-dev即可。

  • (8)优化补充

我们在(3)步骤中存在依赖库中有spring-boot-starter-jdbc,必须配置本项目的DataSource的配置,

这个jdbc并不是spring-cloud-config-server引起,因为引入的是可选<optional>true</optional>

实际上问题是之前我们的parent模块中统一引入了mybatis-plus依赖,导致间接引入了jdbc依赖。

正确处理方案是删除parent中的mybatis-plus依赖,放在dependencyManagement中,其他模块需要的时候单独引入。

这样就可以删除配置中心配置了无用的datasource字段问题。

如何调用其他模块的服务、方法等

总结:直接引用调用是不行的,毕竟不是一个jar包,想要访问其他模块的服务,只能通过http请求,使用类似openfeign的包;common模块或者其他模块能使用,是因为它就是单独的代码,并没有启动类,没启动服务所以,所以没有进入spring容器也无法使用注解,也不涉及IP和端口之类的。

参数接收@PathVariable@RequestParam@RequestBody的使用

  • @PathVariable是path路径参数,在路由中直接体现出来
  • @RequestParam是url参数,一般形如?xx=1&xx=2
  • @RequestBody是请求体参数,也就是postman中的raw格式

这里我们重点介绍@RequestBody,在使用他之前,必须定义raw的参数结构。

这里我们在common模块中创建一个bean:

/common/src/main/java/com/litblc/common/requestBean/test/TestRaw.java

代码语言:javascript
复制
package com.litblc.common.requestBean.test;

public class TestRaw {
    public long id;
    public long userId;
    public String nickname;
    public String avatar;
}

然后在使用该bean的模块中引入common依赖(我是在push模块)

代码语言:javascript
复制
        <!-- 内部模块引入 -->
        <dependency>
            <groupId>com.litblc</groupId>
            <artifactId>common</artifactId>
        </dependency>

控制器使用示例

代码语言:javascript
复制
    @Operation(summary = "三种接受参数测试")
    @PostMapping(value = "/raw/{path_id}/{sort_type}")
    public TestRaw raw(
            @PathVariable(value = "path_id") @Parameter(description = "path参数可以多个") long pathId,
            @PathVariable(value = "sort_type") @Parameter(description = "path参数") String sort_type,
            @RequestParam(value = "page", required = false, defaultValue = "1") @Parameter(description = "url参数可以多个") long page,
            @RequestParam(value = "page_size", required = false, defaultValue = "15") @Parameter(description = "url参数") long pageSize,
            @RequestBody TestRaw testRaw
            ) {

        System.out.println(pathId);
        System.out.println(sort_type);
        System.out.println(page);
        System.out.println(pageSize);
        System.out.println(testRaw.nickname);

        return testRaw;
    }

优化文档knife4j

我们从一开始使用的是springboot推荐的默认文档包springdoc-openapi-starter-webmvc-ui,这个包里集成了swagger-ui,但是用着不太方便,于是这里我们尝试换成knife4j。

  • 版本疑惑

我们在老项目中经常看到knife4j-spring-boot-starter或者knife4j-openapi2-spring-boot-starter这两个包,是因为该项目使用的是springboot2。

我们目前使用的是springboot3,需要使用knife4j-openapi3-jakarta-spring-boot-starter这个包。

参考官网 https://doc.xiaominfo.com/docs/quick-start/start-knife4j-version#21-spring-boot-2x

我们也可以从源码上看到一些端倪:

knife4j-spring-boot-starter引用的是旧版knife4j,其中properties规定java版本1.8

knife4j-openapi2-spring-boot-starterknife4j-openapi3-jakarta-spring-boot-starter虽然都引入的最新版knife4j,

默认的java<knife4j-java.version>1.8</knife4j-java.version>也是1.8

但是,后者覆盖了properties中的<knife4j-java.version>17</knife4j-java.version>版本为17。

感兴趣的朋友可以自己查看,这里不放图了。

  • 单个springboot项目使用

首先引入依赖knife4j-openapi3-jakarta-spring-boot-starter

代码语言:javascript
复制
        <!-- 接口文档 swagger UI 本地访问 http://ip:port/swagger-ui/index.html-->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.1.0</version>
        </dependency>

        <!-- 接口文档 knife4j测试 http://ip:port/doc.html-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.3.0</version>
        </dependency>

现在其实就可以使用了,现在访问http://ip:port/doc.html即可访问knife4j的优化文档。

注:我们曾经引入过springdoc-openapi-starter-webmvc-ui依赖,访问http://ip:port/swagger-ui/index.html依然可以用默认的swagger。

  • 最后是可选的自定义配置

单个项目使用不需要配置,使用默认的即可,如果需要其他配置可以参考官网:

增强特性https://doc.xiaominfo.com/docs/features/enhance

代码语言:javascript
复制
# http://ip:port/swagger-ui/index.html
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'springbootstudy'
      paths-to-match: '/**'
      packages-to-scan: com.example.springbootstudy    # packages-to-scan 默认为启动类所在的路径
    - group: 'project2'
      paths-to-match: '/**'
      packages-to-scan: com.example.springbootstudy

# knife4j的增强配置,继承springdoc的配置  http://ip:port/doc.html
knife4j:
  enable: true
  setting:
    language: ZH_CN    # EN

单个springboot项目多个模块的文档配置

网上都是springboot2然后写配置类的文档,都一个样,生气,还得靠自己多思考多尝试。

在上面的“自定义配置”中,我们有个配置group-configs并没有具体说明,参数packages-to-scan就是扫描的模块地址。

  • 有时候工程量比较大的时候会配置多个模块,用一个启动类管理,比如如下代码架构(主要是启动类提出来):
  • 然后更新配置文件,主要是设置group-configs:
代码语言:javascript
复制
# http://ip:port/swagger-ui/index.html
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'springbootstudy模块'
      paths-to-match: '/**'
      packages-to-scan: com.example.springbootstudy    # packages-to-scan 默认为启动类所在的路径
    - group: 'push模块'
      paths-to-match: '/**'
      packages-to-scan: com.example.push

# knife4j的增强配置,继承springdoc的配置  http://ip:port/doc.html
knife4j:
  enable: true
  setting:
    language: ZH_CN    # EN
  • 然后访问 http://ip:port/doc.html 和 http://ip:port/swagger-ui/index.html 发现已经成功搭建分组文档

题外话:这里我们加了一个push模块,可以测试@ComponentScan注解的使用

代码语言:javascript
复制
// 设置了这个就只扫描push包了,srpingbootstudy包就不会加载了,任何springbootstudy中的方法都不会生效
// @ComponentScan(value = "com.example.push") 
@SpringBootApplication
public class SpringBootStudyApplication {

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

spring cloud 配置knife4j

使用缓存@EnableCaching@Cacheable

参考https://springdoc.cn/spring-cache-redis-json/

spring的Spring Cache包可以设置多种缓存模式,我们使用redis的方式。注意前提是配置好redis能用。

  • springboot依赖是spring-boot-starter-cache
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 在配置文件中进行配置cache
代码语言:javascript
复制
spring:
  # 缓存设置
  cache:
    type: redis
    redis:
      # 缓存有效的时间,默认永久有效,默认单位为毫秒,如60000=1m
      time-to-live: 5m
      # 如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
      key-prefix: "fmock:"
      use-key-prefix: true
      # 是否缓存空值,防止缓存穿透
      # cache-null-values: true
  • 现在就能使用了,只不过存的不是json,不方便阅读,需要自己设置序列化方式为 JSON
代码语言:javascript
复制
package com.litblc.fmock.moduleA.config;

import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/**
 * 使`@Cacheable`操作存储的数据自动格式化为json,而不是默认的二进制
 * 注意与RedisTemplateConfig不要混淆,都要进行处理
 *
 * @Author zhenhuaixiu
 * @Date 2023/11/6 14:34
 * @Version 1.0
 */
@Configuration
public class CacheConfig {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

        // 先载入配置文件中的配置信息
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();

        // 根据配置文件中的定义,初始化 Redis Cache 配
        if (redisProperties.getTimeToLive() != null) {
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisProperties.getTimeToLive());
        }
        if (StringUtils.hasText(redisProperties.getKeyPrefix())) {
            redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix();
        }

        // 缓存对象中可能会有 LocalTime/LocalDate/LocalDateTime 等 java.time 段,所以需要通过 JavaTimeModule 定义其序列化、反序列化格式
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS")));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS")));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS")));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS")));

        // 基于 Jackson 的 RedisSerializer 实现:GenericJackson2JsonRedisSerializer
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();

        // 把 javaTimeModule 配置到 Serializer 中
        serializer = serializer.configure(config -> {
            config.registerModules(javaTimeModule);
        });

        // 设置 Value 的序列化方式
        return redisCacheConfiguration
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
    }
}
  • 使用 使用就比较简单了,声明@EnableCaching,@EnableCaching为开启缓存,可以放在任何一个能被自动加载的地方。

比如放在启动类上,或者放在你的redis配置上,比如我放在redis的序列化配置上

代码语言:javascript
复制
package com.litblc.fmock.moduleA.config;

import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 使redis保存的数据支持<string,object>,并保存的数据为json字符串
 *
 * @Author zhenhuaixiu
 * @Date 2023/11/6 10:51
 * @Version 1.0
 */
@Configuration
@EnableCaching  // @EnableCaching为开启缓存,可以放在任何一个能被自动加载的地方
public class RedisTemplateConfig extends RedisTemplate<String, Object> {

    public RedisTemplateConfig(RedisConnectionFactory redisConnectionFactory) {

        // 构造函数注入 RedisConnectionFactory,设置到父类
        super.setConnectionFactory(redisConnectionFactory);

        // 使用 Jackson 提供的通用 Serializer
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        serializer.configure(mapper -> {
            // 如果涉及到对 java.time 类型的序列化,反序列化那么需要注册 JavaTimeModule
            mapper.registerModule(new JavaTimeModule());
        });

        // String 类型的 key/value 序列化
        super.setKeySerializer(StringRedisSerializer.UTF_8);
        super.setValueSerializer(serializer);

        // Hash 类型的 key/value 序列化
        super.setHashKeySerializer(StringRedisSerializer.UTF_8);
        super.setHashValueSerializer(serializer);
    }
}
  • 控制器只需要声明@Cacheable即可
代码语言:javascript
复制
    @GetMapping(value = "listDesc")
    @Operation(summary = "获取文章列表")
    @Cacheable(value = "posts")
    public List<Posts> postsListDesc() {
        System.out.println("再次访问这个接口,这句话不会输出,证明走了缓存");

        List<Posts> res = this.postsService.getAllPosts();
        this.redisTemplate.opsForValue().set("posts1", res, 60L, TimeUnit.SECONDS);  // 手动存储的是字符串
        this.redisTemplate.opsForValue().set("posts2", res);  // 永久期限,正常json格式

        return res;
    }

定时任务

  • 需要依赖spring-boot-starter
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 在启动类、或者其他能扫描到的类添加注解@EnableScheduling
  • 方法中添加@Scheduled()即可,如下示例代码
代码语言:javascript
复制
package com.litblc.fmock.moduleA.crontab;

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author zhenhuaixiu
 * @Date 2023/11/10 17:03
 * @Version 1.0
 */

// @Scheduled 参数可以接受两种定时的设置,一种是我们常用的`cron="*/6 * * * * ?"`,一种是 fixedRate = 6000,两种都表示每隔六秒跑一次。
// @Scheduled(fixedRate = 6000) :上一次开始执行时间点之后6秒再执行
// @Scheduled(fixedDelay = 6000) :上一次执行完毕时间点之后6秒再执行
// @Scheduled(initialDelay=1000, fixedRate=6000) :第一次延迟1秒后执行,之后按 fixedRate 的规则每6秒执行一次
// cron參考 https://blog.csdn.net/Linweiqiang5/article/details/86741258

@EnableScheduling
@Component
public class SchedulerTask {

    private int count = 0;

    @Scheduled(cron = "*/6 * * * * ?")
    private void task1() {
        System.out.println("这样就执行定时任务,第几次:" + (++this.count));
    }

    @Scheduled(fixedRate = 6000)
    public void task2() {
        System.out.println("现在时间:" + (new Date()));
    }
}

在单应用中可以这么使用,比较简单,但是在微服务架构中就不行了,比如启用了多个实例,那么定时任务也会执行多次。 所以后面我们一点一点完善升级。

#QR{padding-top:20px;} #QR a{border:0} #QR img{width:180px;max-width:100%;display:inline-block;margin:.8em 2em 0 2em} #rewardButton { border: 1px solid #ccc; /*width: 20%;*/ line-height: 53px; text-align: center; height: 70px; display: block; border-radius: 10px; -webkit-transition-duration: .4s; transition-duration: .4s; background-color: #f77b83; color: #f7f7f7; margin: 20px auto; padding: 8px 25px; } #rewardButton:hover { color: #eb5055; border-color: #f77b83; outline-style: none; background-color: floralwhite; }

本文由 litblc 创作,采用 知识共享署名4.0 国际许可协议进行许可

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

最后编辑时间为: Nov 10, 2023 at 05:40 pm

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Cloud
    • 搭建配置中心 spring-cloud-config-server
      • 如何调用其他模块的服务、方法等
        • 参数接收@PathVariable、@RequestParam、@RequestBody的使用
          • 优化文档knife4j
            • 单个springboot项目多个模块的文档配置
              • spring cloud 配置knife4j
                • 使用缓存@EnableCaching 和 @Cacheable
                  • 定时任务
                  相关产品与服务
                  云数据库 Redis
                  腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档