Spring Boot 整合 Redis:提升应用性能的利器

  Redis (Remote Dictionary Server) 是一款高性能的键值对存储数据库,它以内存存储为主,具有速度快、支持丰富的数据类型等特点,被广泛应用于缓存、会话管理、排行榜等场景。 Spring Boot 提供了对 Redis 的良好支持,使得我们可以轻松地在 Spring Boot 项目中集成 Redis,从而提升应用的性能和可扩展性。
  本文将详细介绍如何在 Spring Boot 项目中整合 Redis,并提供完整的代码示例和最佳实践。

一、为什么要使用 Redis?

  1. 高性能缓存: Redis 以内存存储为主,读写速度非常快,可以作为缓存层,减少对数据库的访问,从而提升应用性能。
  2. 丰富的数据类型:Redis 支持 String、List、Set、Hash、Sorted Set 等多种数据类型,可以满足不同的应用场景需求。
  3. 会话管理: Redis 可以用于存储用户会话信息,实现会话共享和负载均衡。
  4. 排行榜: Redis 的 Sorted Set可以方便地实现排行榜功能。
  5. 消息队列: Redis 的 List 数据类型可以用于实现简单的消息队列功能。
  6. 分布式锁: Redis可以用于实现分布式锁,解决分布式环境下的资源竞争问题。

二、Spring Boot 整合 Redis实践

2.1 添加 Redis 依赖

   <!-- redis 缓存操作 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 缓存依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- pool 对象池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>

2.2 配置 Redis 连接信息

spring:# redis 配置redis:# 地址host: **.**.**.**# 端口,默认为6379port: 6380# 数据库索引database: 12# 密码password: ******# 连接超时时间timeout: 60slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms

2.3 重写redis中的StringRedisSerializer序列化器

import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.Assert;import java.nio.charset.Charset;public class HashKeyStringRedisSerializer implements RedisSerializer<Object> {private final Charset charset;private final String target = "\"";private final String replacement = "";public HashKeyStringRedisSerializer() {this(Charset.forName("UTF8"));}public HashKeyStringRedisSerializer(Charset charset) {Assert.notNull(charset, "Charset must not be null!");this.charset = charset;}@Overridepublic Object deserialize(byte[] bytes) {return (bytes == null ? null : new String(bytes, charset));}@Overridepublic byte[] serialize(Object object) {String string = JSON.toJSONString(object);if (string == null) {return null;}string = string.replace(target, replacement);return string.getBytes(charset);}
}

2.4 创建Redis配置类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;@Configuration
@EnableCaching
public class RedisConfig {/*** 过期时间1天*/private final Duration timeToLive = Duration.ofDays(1);/*** HaskKey的序列化方式*/private final HashKeyStringRedisSerializer hashKeySerializer = new HashKeyStringRedisSerializer();/*** String的序列化方式*/private final StringRedisSerializer stringSerializer = new StringRedisSerializer();/*** 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)*/private final Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer(Object.class);/*** 代码块,会优先执行* 用来设置Jackson2JsonRedisSerializer*/{ObjectMapper objectMapper = new ObjectMapper();//设置所有访问权限以及所有的实际类型都可序列化和反序列化objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型,类必须是非final修饰的objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);//下面两行解决Java8新日期API序列化问题objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));objectMapper.registerModule(javaTimeModule);valueSerializer.setObjectMapper(objectMapper);}/*** 在SpringBoot2.0之后,spring容器自动的生成了StringRedisTemplate和RedisTemplate<Object,Object>,可以直接注入* 但是在实际使用中,大多不会直接使用RedisTemplate<Object,Object>,而是会对key,value进行序列化,所以我们还需要新增一个配置类* 换句话说,由于原生的redis自动装配,在存储key和value时,没有设置序列化方式,故自己创建redisTemplate实例** @param factory* @return*/@Bean(name = "redisTemplate")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// key采用String的序列化方式template.setKeySerializer(stringSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(hashKeySerializer);// value序列化方式采用jacksontemplate.setValueSerializer(valueSerializer);// hash的value序列化方式采用jacksontemplate.setHashValueSerializer(valueSerializer);template.afterPropertiesSet();return template;}@Bean(name = "redisCacheManager")public RedisCacheManager cacheManager(RedisConnectionFactory factory) {// 配置序列化(解决乱码的问题),通过config对象对缓存进行自定义配置RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()// 设置缓存的默认过期时间.entryTtl(timeToLive).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer))// 不缓存空值.disableCachingNullValues();//根据redis缓存配置和reid连接工厂生成redis缓存管理器return RedisCacheManager.builder(factory).cacheDefaults(config).transactionAware().build();}}

2.5 创建Redis工具类

import cn.hutool.core.collection.CollectionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;@Component
public class RedisCache {@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 自增计数器* @param key* @param delta* @return*/public Long incrementValue(final String key, final Long delta){return redisTemplate.opsForValue().increment(key, delta);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection){return redisTemplate.delete(collection);}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 缓存Map** @param key* @param dataMap*/public <K, V> void setCacheCommonMap(final String key, final Map<K, V> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 获得缓存的Map** @param key* @return*/public <K, V> Map<K, V> getCacheCommonMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键*/public void deleteCacheMapValue(final String key, final String hKey){redisTemplate.opsForHash().delete(key, hKey);}/*** 删除并缓存List数据** @param key 缓存的键值* @param dataMap 待缓存的Map数据* @return 缓存的对象*/public <T> void deleteAndSetCacheMap(final String key, final Map<String, T> dataMap){if(redisTemplate.delete(key)){setCacheMap(key, dataMap);}}/*** 删除并缓存List数据,带过期时间** @param key 缓存的键值* @param dataMap 待缓存的Map数据* @return 缓存的对象*/public <T> void deleteAndSetCacheMap(final String key, final Map<String, T> dataMap, final long timeout, final TimeUnit unit){if(redisTemplate.delete(key)){setCacheMap(key, dataMap);expire(key, timeout, unit);}}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}/*** 删除并缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long deleteAndSetCacheList(final String key, final List<T> dataList){long count = 0;if(redisTemplate.delete(key)){count = redisTemplate.opsForList().rightPushAll(key, dataList);}return count;}/*** 删除并缓存List数据,带过期时间** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long deleteAndSetCacheList(final String key, final List<T> dataList, final long timeout, final TimeUnit unit){long count = 0;if(redisTemplate.delete(key)){count = redisTemplate.opsForList().rightPushAll(key, dataList);expire(key, timeout, unit);}return count;}/*** 向集合中插入元素,并设置分数* @param key* @param value* @param score* @param <T>*/public <T> void addCacheZSet(final String key, final T value, double score){redisTemplate.opsForZSet().add(key, value, score);}/*** 批量添加ZSet* @param key* @param map* @param <T>*/public <T> void batchAddCacheZSet(final String key, final Map<T, Double> map){Set<DefaultTypedTuple<T>> set = new HashSet<>();Iterator<Map.Entry<T, Double>> iterator = map.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<T, Double> entry = iterator.next();set.add(new DefaultTypedTuple<T>(entry.getKey(), entry.getValue()));}redisTemplate.opsForZSet().add(key, set);}/*** 给指定元素添加分数* @param key* @param value* @param score* @param <T>* @return*/public <T> Double incrementZSetScore(final String key, final T value, double score){return redisTemplate.opsForZSet().incrementScore(key, value, score);}/*** 获取指定元素的分数* @param key* @param value* @param <T>* @return*/public <T> Double getZSetScore(final String key, final T value){return redisTemplate.opsForZSet().score(key, value);}/*** 删除指定元素* @param key* @param values* @param <T>*/public <T> void deleteZSetKey(final String key, final T[] values){redisTemplate.opsForZSet().remove(key, values);}/*** 根据key模糊删除* @param key* @return*/public Long blurDelete(final String key) {Set<String> keys = redisTemplate.keys(key + ":*");if(CollectionUtil.isEmpty(keys)){return null;}return deleteObject(keys);}
}

2.6 编写测试方式操作Redis

@RestController
@RequestMapping("/yes")
public class ExtendController extends BaseController {@Autowiredprivate UserInfoService userInfoService;@Autowiredprivate RedisCache redisCache;@GetMapping("/{id}")public Result<UserInfo> getUserById(@PathVariable int id) {UserInfo user = userInfoService.getById(id);redisCache.setCacheObject(String.valueOf(id), user, 1440, TimeUnit.MINUTES);return Result.success(user);}
}

三、最佳实践

  1. 选择合适的数据类型: 根据业务需求选择合适的 Redis 数据类型,例如 String、List、Set、Hash、SortedSet。
  2. 合理设置过期时间: 为缓存数据设置合理的过期时间,避免缓存过期导致大量请求访问数据库。
  3. 使用连接池: Spring Boot默认使用连接池来管理 Redis 连接,可以提高性能。
  4. 考虑序列化: 对于非 String类型的数据,需要考虑序列化和反序列化。Spring Boot 默认使用 JdkSerializationRedisSerializer进行序列化。
  5. 监控 Redis: 使用 Redis 自带的监控工具或第三方工具监控 Redis 的性能和状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/68729.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue2+OpenLayers添加缩放、滑块缩放、拾取坐标、鹰眼、全屏控件(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、代码实现 四、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、代码实现 废话不多说&#xff0c;直接给完整代码&#xff0c;替换成自己的KEY即可运行&#xff1a; <template><div><div i…

Vulnhub-Tr0ll靶机笔记

Tr0ll靶机笔记 概述 靶机地址&#xff1a;https://www.vulnhub.com/entry/tr0ll-1,100/ 这台靶机比较简单&#xff0c;包含ftp的渗透&#xff0c;pcap流量包的分析&#xff0c;常规的web渗透和系统内核提权。让我们开始吧 Hack it&#xff01; 一、nmap扫描 1、端口扫描 …

高效建站指南:通过Portainer快速搭建自己的在线网站

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

直驱式风电储能制氢仿真模型matlab/simulink

接着还是以直驱式风电为DG中的研究对象&#xff0c;上篇博客考虑的风电并网惯性的问题&#xff0c;这边博客主要讨论功率消纳的问题。 考虑到风速是随机变化的&#xff0c;导致风电输出功率的波动性和间歇性问题突出&#xff1b;随着其应用规模的不断扩大以及风电在电网中渗透率…

C#中字符串方法

字符串属性&#xff1a;Lenght 长度比最大索引大1 string str "frerfgd"; 1.可以通过索引&#xff0c;获取字符串中的某一个字符&#xff0c;下标“0&#xff0c;1.......” Console.WriteLine(str[0]);//f Console.WriteLine(str[1]);//r //Console.WriteLine(s…

Spring Boot--@PathVariable、@RequestParam、@RequestBody

目录 声明&#xff01;&#xff01; 什么是RESTful&#xff1f; RESTful 的基本原则 无状态性&#xff08;Stateless&#xff09; 统一接口&#xff08;Uniform Interface&#xff09; 分层系统&#xff08;Layered System&#xff09; 缓存&#xff08;Cacheable&#…

React的响应式

在 React 中&#xff0c;useState 是一个 Hook&#xff0c;用于在函数组件中定义和管理状态。 setCount 是由 useState 返回的第二个值&#xff0c;用于更新状态并触发组件重新渲染。它的本质是一个状态更新函数&#xff0c;背后是 React 的状态管理和调度机制。下面是对 setCo…

Docker Compose的使用

文章首发于我的博客&#xff1a;https://blog.liuzijian.com/post/docker-compose.html 目录 Docker Compose是什么Docker Compose安装Docker Compose文件Docker Compose常用命令案例&#xff1a;部署WordPress博客系统 Docker Compose是什么 Docker Compose是Docker官方的开源…

ovs实现lb负载均衡

负载均衡定义 负载均衡器的实现原理是通过硬件或软件设备将客户端访问流量根据转发策略分发到多个服务器或设备上&#xff0c;以确保系统的负载均衡。常见的实现方式包括&#xff1a; 二层负载均衡‌&#xff1a;使用虚拟MAC地址方式&#xff0c;根据OSI模型的二层进行负载均…

JDK长期支持版本(LTS)

https://blogs.oracle.com/java/post/the-arrival-of-java-23 jdk长期支持版本&#xff08;LTS&#xff09;&#xff1a;JDK 8、11、17、21&#xff1a;

AI 在人形机器人发展中的作用

摘要&#xff1a;本文主要探讨了 AI 在人形机器人发展中的关键作用。通过对相关技术和应用案例的分析&#xff0c;阐述了 AI 如何赋予人形机器人智能感知、学习决策、自然语言处理及运动控制等能力&#xff0c;推动人形机器人在多领域的应用和产业发展&#xff0c;同时也对其面…

python(25) : 含有大模型生成的公式的文本渲染成图片并生成word文档(支持flask接口调用)

公式样例 渲染前 \[\sqrt{1904.615384} \approx 43.64\] 渲染后 安装依赖 pip install matplotlib -i https://mirrors.aliyun.com/pypi/simple/ requestspip install sympy -i https://mirrors.aliyun.com/pypi/simple/ requestspip install python-docx -i https://mirro…

SSM宠物医院信息管理系统

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式咨询本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 宠…

Swift 专题二 语法速查

一 、变量 let, var 变量是可变的&#xff0c;使用 var 修饰&#xff0c;常量是不可变的&#xff0c;使用 let 修饰。类、结构体和枚举里的变量是属性。 var v1:String "hi" // 标注类型 var v2 "类型推导" let l1 "标题" // 常量class a {…

mysql查看binlog日志

mysql 配置、查看binlog日志&#xff1a; 示例为MySQL8.0 1、 检查binlog开启状态 SHOW VARIABLES LIKE ‘log_bin’; 如果未开启&#xff0c;修改配置my.ini 开启日志 安装目录配置my.ini(mysql8在data目录) log-binmysql-bin&#xff08;开启日志并指定日志前缀&#xff…

某国际大型超市电商销售数据分析和可视化

完整源码项目包获取→点击文章末尾名片&#xff01; 本作品将从人、货、场三个维度&#xff0c;即客户维度、产品维度、区域维度&#xff08;补充时间维度与其他维度&#xff09;对某国际大型超市的销售情况进行数据分析和可视化报告展示&#xff0c;从而为该超市在弄清用户消费…

Linux 存储设备和 Ventoy 启动盘制作指南

一、Linux 存储设备基础知识 1. 设备路径&#xff08;/dev&#xff09; 设备路径是 Linux 系统中物理存储设备的唯一标识&#xff0c;类似设备的"身份证号"。 命名规则解析 /dev/sda&#xff1a; /dev&#xff1a;device&#xff08;设备&#xff09;的缩写&…

PostgreSQL-01-入门篇-简介

文章目录 1. PostgreSQL是什么?2. PostgreSQL 历史 2.1. 伯克利 POSTGRES 项目2.2. Postgres952.3. PostgreSQL来了 3. PostgreSQL vs MySQL4. 安装 4.1 Windows 安装4.2 linux 安装4.3 docker安装 1. PostgreSQL是什么 PostgreSQL 是一个基于加州大学伯克利分校计算机系开…

数仓建模:当DWS构建好后,突然来了一个新的需求,需要添加某个或某几个维度字段时,应该如何处理?

目录 1. 需求评估与分析 2. 表结构调整 3. ETL 流程修改 4. 数据更新和回填 5. 数据分析和报表调整

Git原理与应用(三)【远程操作 | 理解分布式 | 推送拉取远程仓库 | 标签管理】

Git 理解分布式版本控制系统远程仓库新建远程仓库克隆远程仓库向远程仓库推送配置Git忽略特殊文件 标签管理理解标签创建标签操作标签删除标签 理解分布式版本控制系统 我们⽬前所说的所有内容&#xff08;工作区&#xff0c;暂存区&#xff0c;版本库等等&#xff09;&#x…