springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5
近期在整合springboot + redis 的功能,本来想用原生的jedit api,最后想想有点 low,搜了一把,boot已经提供给我们操作的方法,那就是
使用 redisTemplate 或 StringRedisTemplate, 两者是有区别的,可以看下面的说明
1. 两者的关系是StringRedisTemplate继承RedisTemplate。
2. 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
引自: https://blog.csdn.net/yifanSJ/article/details/79513179
好了,有关概念的解释不在此处详细说明,这里只是记录如何快速搭建和实现操作redis,先看下我的工程结构,如图:
引入相关jar包,pom.xml如下:
<dependencies>
<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>
<!-- 因为需要使用lettuce连接池,这个包必须添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version><!--$NO-MVN-MAN-VER$-->
</dependency>
</dependencies>
配置Redis参数 application.properties:
# 配置redis参数
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# 连接超时时间,单位(毫秒)
spring.redis.timeout=5000
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=20000
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=10
# 集群
#spring.redis.cluster.nodes=192.168.211.134:7000,192.168.211.134:7001,192.168.211.134:7002
#spring.redis.cluster.max-redirects=6
创建RedisConfiguration类:
package com.szl.demo.common.redisConfig; import org.springframework.cache.annotation.CachingConfigurerSupport;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.*; @EnableCaching
@Configuration
public class RedisConfiguration extends CachingConfigurerSupport { /**
* @param connectionFactory
* @return
* @desc redis模板,存储关键字是字符串,
* 值jackson2JsonRedisSerializer是序列化后的值
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 开启事务
redisTemplate.setEnableTransactionSupport(true); // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 使用StringRedisSerializer来序列化和反序列化redis的key值
RedisSerializer<?> redisSerializer = new StringRedisSerializer();
// key
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
// value
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet();
return redisTemplate;
} }
DTO 类:
package com.szl.demo.common.dto; import java.io.Serializable;
import lombok.Data; @Data
public class UserDto implements Serializable {
private static final long serialVersionUID = -8858511759866491158L; private String userName;
private Integer userAge; }
UserService接口:
package com.szl.demo.service; import com.szl.demo.common.dto.UserDto; public interface UserService { /**
* @param userDto
* @desc 将字符串保存到redis中
*/
public void saveUserInfoToRedis(); /**
* @param key
* @return
* @desc 从redis中读取字符串
*/
public String getUserInfoFromRedis(String key); /**
* @param userDto
* @desc 将对象保存到redis中
*/
public void saveUserObject(UserDto userDto); /**
* @param userName
* @return
* @desc 从redis中获取对象
*/
public UserDto findUserObject(String userName); /**
* @param userDto
* @desc 锁机制保存对象数据
*/
public void lockOfUserProcess(UserDto userDto); }
UserServiceImpl实现接口:
package com.szl.demo.service.impl; import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.szl.demo.common.dto.UserDto;
import com.szl.demo.common.redisConfig.RedisDistributedLock;
import com.szl.demo.common.util.JsonWare;
import com.szl.demo.service.UserService;
import lombok.extern.slf4j.Slf4j; @Slf4j
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedisDistributedLock redisDistributedLock; /**
* @param userDto
* @desc 将字符串保存到redis中
*/
public void saveUserInfoToRedis() {
// 判断redis中是否存在key
boolean isExist = redisTemplate.hasKey("demo_test02");
if (!isExist) {
// 保存key,有效期为30秒
String msg = "abc123,你好,welcome.";
redisTemplate.opsForValue().set("demo_test02", msg, 30, TimeUnit.SECONDS);
} else {
// 删除key
redisTemplate.delete("demo哈哈");
}
} /**
* @param key
* @return
* @desc 从redis中读取字符串
*/
public String getUserInfoFromRedis(String key) {
String val = (String)redisTemplate.opsForValue().get(key);
return val;
} /**
* @param userDto
* @desc 将对象保存到redis中
*/
public void saveUserObject(UserDto userDto) {
// 判断redis中是否存在key
boolean isExist = redisTemplate.hasKey(userDto.getUserName());
if (!isExist) {
// 保存key,有效期为30秒
redisTemplate.opsForValue().set(userDto.getUserName(), userDto, 30, TimeUnit.SECONDS);
} else {
// 删除key
redisTemplate.delete(userDto.getUserName());
}
} /**
* @param userName
* @return
* @desc 从redis中获取对象
*/
public UserDto findUserObject(String userName) {
UserDto userDto = (UserDto) redisTemplate.opsForValue().get(userName);
return userDto;
} /**
* @param userDto
* @desc 锁机制保存对象数据
*/
public void lockOfUserProcess(UserDto userDto) {
String key = "myLock_" + userDto.getUserName();
int timeout = 300 * 1000;//超时时间 5分钟
long value = System.currentTimeMillis() + timeout; try {
// 加锁
if (!redisDistributedLock.setLock(key, String.valueOf(value))) {
throw new Exception("对不起,redis被挤爆了,请休息片刻再重试。");
}
// 做一些业务相关操作,这里只是demo,随便保存个对象信息
redisTemplate.opsForValue().set(userDto.getUserName(), userDto, 60, TimeUnit.SECONDS);
log.info("原来值内容:" + JsonWare.beanToJson(userDto));
// 修改值,重新保存到redis中
UserDto dto = new UserDto();
BeanUtils.copyProperties(userDto, dto);
dto.setUserAge(30);
redisTemplate.opsForValue().set(dto.getUserName(), dto, 60, TimeUnit.SECONDS);
log.info("修改值内容:" + JsonWare.beanToJson(dto));
} catch (Exception e) {
log.error("异常发生,信息如下:", e.getMessage());
} finally {
// 释放锁
redisDistributedLock.releaseLock(key, String.valueOf(value));
}
}
}
Controller类:
package com.szl.demo.controller; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.szl.demo.common.dto.UserDto;
import com.szl.demo.service.UserService; @Controller
public class DemoController {
@Autowired
private UserService userService; @RequestMapping(value = "/saveUser", method = RequestMethod.POST)
public void saveUser(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
userService.saveUserInfoToRedis();
} @RequestMapping(value = "/getUserInfo", method = RequestMethod.GET)
public void getUserInfo(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "key", required = false) String key) {
String msg = userService.getUserInfoFromRedis(key);
System.out.println(msg);
} @RequestMapping(value = "/saveUserObject", method = RequestMethod.POST)
public void saveUserObject(HttpServletRequest request, HttpServletResponse response) {
UserDto dto = new UserDto();
dto.setUserName("Jimmy Shan");
dto.setUserAge(21);
userService.saveUserObject(dto);
} @RequestMapping(value = "/getUserObject", method = RequestMethod.GET)
public void getUserObject(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "key", required = false) String key) {
UserDto dto = userService.findUserObject(key);
System.out.println("姓名: " + dto.getUserName() + ", 年龄: " + dto.getUserAge());
} @RequestMapping(value = "/lockDealWithDemo", method = RequestMethod.GET)
public void lockDealWithDemo(HttpServletRequest request, HttpServletResponse response) {
UserDto dto = new UserDto();
dto.setUserName("JimmyShan");
dto.setUserAge(16);
userService.lockOfUserProcess(dto);
System.out.println("这是lock的demo请求");
} }
RedisDistributedLock类(锁的工具类) :
package com.szl.demo.common.redisConfig; import javax.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import lombok.extern.slf4j.Slf4j; /**
* @author Jimmy Shan
* @desc Redis 锁工具类
*/
@Slf4j
@Component
public class RedisDistributedLock {
@Resource
private RedisTemplate<String, Object> redisTemplate; /**
* @param key redis key, 唯一键
* @param value redis value, 这里是时间戳
* @return
* @desc 加锁 true已锁 false未锁
*/
public boolean setLock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) { // 对应setnx命令
//可以成功设置,也就是key不存在
return true;
}
// 判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁
String currentValue = (String) redisTemplate.opsForValue().get(key);
// 如果锁过期
// currentValue 不为空且小于当前时间
if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
// 获取上一个锁的时间value
// 对应getset,如果key存在返回当前key的值,并重新设置新的值
// redis是单线程处理,即使并发存在,这里的getAndSet也是单个执行
// 所以,加上下面的 !StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)
// 就能轻松解决并发问题
String oldValue = (String) redisTemplate.opsForValue().getAndSet(key,value);
if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
} /**
* @param key redis key, 唯一键
* @param value redis value, 这里是时间戳
* @return
* @desc 释放锁 true已释放 false未释放
*/
public void releaseLock(String key, String value) {
try {
String currentValue = (String) redisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);// 删除key
}
} catch (Exception e) {
log.error("解锁出现异常了,{}", e);
}
}
}
我们去控制台看下效果
现在我们能看到,value为 "abc123,你好,welcome." 中的 中文字体已经被序列化了, 还有 UserDto对象,是以 json格式存储。
以上属于直连模式,这种方式在访问量不高的时候,足够应付,游刃有余,反之,该如何处理呢 ?
答案是:连接池
下面是如何使用连接池的方法,以上的代码项保持不变,只要修改 “ RedisConfiguration ” 这个类即可,看下面具体实现
使用连接池的 RedisConfiguration 类:
package com.szl.demo.common.redisConfig; import java.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import redis.clients.jedis.JedisPoolConfig;
import com.fasterxml.jackson.annotation.*; @EnableCaching
@Configuration
public class RedisConfiguration extends CachingConfigurerSupport {
@Autowired
private Environment env; /**
* @param connectionFactory
* @return
* @desc redis模板,存储关键字是字符串,
* 值jackson2JsonRedisSerializer是序列化后的值
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionPoolsFactory());
// 开启事务
//redisTemplate.setEnableTransactionSupport(true); // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 使用StringRedisSerializer来序列化和反序列化redis的key值
RedisSerializer<?> redisSerializer = new StringRedisSerializer();
// key
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
// value
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet();
return redisTemplate;
} /**
* @desc 使用jedis pool创建连接(连接池配置)
*/
private RedisConnectionFactory connectionPoolsFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲连接数, 默认8个
poolConfig.setMaxIdle(Integer.parseInt(env.getProperty("spring.redis.jedis.pool.max-idle")));
// 最小空闲连接数, 默认0
poolConfig.setMinIdle(Integer.parseInt(env.getProperty("spring.redis.jedis.pool.min-idle")));
// 最大连接数, 默认8个
poolConfig.setMaxTotal(Integer.parseInt(env.getProperty("spring.redis.jedis.pool.max-active")));
// 获取连接时的最大等待毫秒数, 如果不超时设置: -1
poolConfig.setMaxWaitMillis(Long.parseLong(env.getProperty("spring.redis.jedis.pool.max-wait")));
// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
poolConfig.setTimeBetweenEvictionRunsMillis(-1);
// 在获取连接的时候检查有效性, 默认false
poolConfig.setTestOnBorrow(true);
// 在空闲时检查有效性, 默认false
poolConfig.setTestWhileIdle(true); JedisClientConfiguration jedisClientConfiguration =
JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and()
.readTimeout(Duration.ofMillis(Long.parseLong(env.getProperty("spring.redis.timeout"))))
.connectTimeout(Duration.ofMillis(Long.parseLong(env.getProperty("spring.redis.timeout"))))
.build();
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(Integer.parseInt(env.getProperty("spring.redis.database")));
redisStandaloneConfiguration.setHostName(env.getProperty("spring.redis.host"));
redisStandaloneConfiguration.setPassword(env.getProperty("spring.redis.password"));
redisStandaloneConfiguration.setPort(Integer.parseInt(env.getProperty("spring.redis.port")));
return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
} }
至此,我们就跑起来看效果了,以上是本人经过测试并通过的代码和配置,另外需要说明一点,redis服务器本人使用3.0.5, 之前在使用2.4.5的时候,总是连接死锁(win环境),折腾了许久,
最后还是更新高版本解决问题。
如有朋友参考本人的笔记,有问题可以留言,转载请注明原著,谢谢。
锁来源参考:https://blog.csdn.net/qq_26525215/article/details/79182687
springboot2.1.3 + redisTemplate + Lock 操作 redis 3.0.5的更多相关文章
- 使用Spring操作Redis的key-value数据
前言 最近工作一直忙的不可开交,小Alan已经很久没有和大家分享知识了,在深圳待了两年多,依然感觉自己还是个小菜鸟,工作中还是会遇到很多自己在短期内无法搞定的事情,每当这个时候总是会感觉到很沮丧,就会 ...
- 【快学springboot】13.操作redis之String数据结构
前言 在之前的文章中,讲解了使用redis解决集群环境session共享的问题[快学springboot]11.整合redis实现session共享,这里已经引入了redis相关的依赖,并且通过spr ...
- 使用RedisTemplate的操作类访问Redis(转)
深入理解Spring Redis的使用 (三).使用RedisTemplate的操作类访问Redis 事务需要开启enableTransactionSupport,然后使用@transactional ...
- Spring中使用RedisTemplate操作Redis(spring-data-redis)
RedisTemplate如何检查一个key是否存在? return getRedisTemplate().hasKey(key); 由一个问题,复习了一下redis 抄自: https://www. ...
- springboot之使用redistemplate优雅地操作redis
概述 本文内容主要 关于spring-redis 关于redis的key设计 redis的基本数据结构 介绍redis与springboot的整合 sringboot中的redistemplate的使 ...
- SpringBoot 使用RedisTemplate操作Redis
新版: import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.T ...
- spring-data-redis 中使用RedisTemplate操作Redis
Redis 数据结构简介 Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串).List(列表).Set(集合).Hash(散列)和 Zset(有序集合 ...
- RedisTemplate操作Redis
RedisTemplate Redis 可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串).List(列表).Set(集合).Hash(散列)和 Zset(有序 ...
- Java 使用Jedis和RedisTemplate操作Redis缓存(SpringBoot)
package com.example.redis.controller; import com.example.redis.entity.User; import com.example.redis ...
随机推荐
- Mac下epub电子书制作编辑器 : Sigil
官方博客:https://sigil-ebook.com github项目地址:https://github.com/Sigil-Ebook V0.9.10下载:https://github.com/ ...
- rabbitmq - 消息接收,解析xml格式数据时异常:ERROR not well-formed (invalid token): line 4, column 46
ERROR alsv odoo.addons.cus_alsv.utils.alsv_about_mq.get_data_from_mq: parse_xml_data_from_mq: not we ...
- python 把带小数的浮点型字符串转换为整数的解决方案
以下内容在python中完全可以接受: 将整数的字符串表示形式传递给 int 将float的字符串表示形式传递给 float 但是,如果你将float型的字符串传递给int将会得到错误. >&g ...
- 【前端】input输入框只能输入大于等于0的正数
大于等于0的正数,允许小数 <input type="number" step="1" min="0" onkeyup="t ...
- alertmanager,grafana,prometheus
https://zhuanlan.zhihu.com/p/43637757 https://www.cnblogs.com/xiangsikai/p/11289966.html dashboard分文 ...
- [LeetCode] 190. Reverse Bits 翻转二进制位
Reverse bits of a given 32 bits unsigned integer. For example, given input 43261596 (represented in ...
- abp_vue导入导出excel
后端abp,前端vue导入excel,开始准备用直接用npoi,觉得要写太多的代码,就算从以前的复制粘贴也麻烦,所以偷懒直接用别人的轮子 Magicodes.IE.这样可以节省很多工作,根据实体生成e ...
- OpenJudge 4120 硬币
总时间限制: 1000ms 内存限制: 262144kB 描述 宇航员Bob有一天来到火星上,他有收集硬币的习惯.于是他将火星上所有面值的硬币都收集起来了,一共有n种,每种只有一个:面值分别为a1,a ...
- POJ 1221 UNIMODAL PALINDROMIC DECOMPOSITIONS
总时间限制: 1000ms 内存限制: 65536kB 描述 A sequence of positive integers is Palindromic if it reads the same f ...
- [转帖]Helm V2 迁移到 V3 版本
Helm V2 迁移到 V3 版本 -- :: Mr-Liuqx 阅读数 63更多 分类专栏: kubernetes 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上 ...