【读写分离?聊聊Mysql多数据源实现读写分离的几种方案】

文章目录

    • 一.什么是MySQL 读写分离
    • 二.读写分离的几种实现方式(手动控制)
      • 1.基于Spring下的AbstractRoutingDataSource
        • 1.yml
        • 2.Controller
        • 3.Service实现
        • 4.Mapper层
        • 5.定义多数据源
        • 6.继承Spring的抽象路由数据源抽象类,重写相关逻辑
        • 7. 自定义注解@WR,用于指定当前操作使用哪个库
        • 8. 切面逻辑
        • 9.源码简单分析
        • 10. 开始测试
      • 2.基于Mybatis的SqlSessionFactory
        • 1.yml
        • 2.Controller
        • 3.Service实现
        • 4.Mapper层
        • 5.配置类
          • 1. 指定哪些Mapper接口使用读数据源:
          • 2. 指定哪些Mapper接口使用写数据源
        • 6. 开始测试
      • 3.基于baomidou动态数据源实现读写分离(最简单)
        • 1. maven依赖
        • 2.yml
        • 3.Controller
        • 4.Service
        • 5.Mapper层
        • 6.开始测试
    • 三.小结

一.什么是MySQL 读写分离

我记得实习的第一家公司做个一个项目就用过mysql多数据源的读写分离方案(4年前了…依稀记得也是在mapper层面来分离的),但那时候是我同事弄的,完全不懂怎么实现的,觉得他好厉害。从此成了心里的一道坎,很久之前就了解了,一直想着要写篇博客记录下,ok,那赶紧开始吧~
先了解下概念什么是读写分离、优势、实现方式、注意事项、和使用场景。如果项目里面有用到数据库集群,开始有性能方面问题,结合业务场景及综合衡量下去考虑是否适用数据库读写分离方案。
以下解释来自chatgpt,我觉得说的挺好的。

MySQL 读写分离是一种数据库优化策略,通过将数据库的读操作和写操作分开,分别交由不同的数据库实例处理,以提高系统的性能和扩展性。具体来说,读写分离通常涉及一个主数据库(Master)和一个或多个从数据库(Slave),它们通过复制机制保持数据的一致性。
以下是读写分离的核心概念:

  1. 主从复制(Master-Slave Replication)
  • 主库(Master):负责处理所有的写操作(INSERT、UPDATE、DELETE 等),也可以处理读操作。
  • 从库(Slave):主要用于处理读操作(SELECT),不会直接接收写操作。从库通过复制机制从主库同步数据,确保数据一致性。
  1. 读写分离的优势
  • 提高读性能:由于从库处理读操作,可以通过增加从库实例来扩展系统的读性能,减轻主库的负担。
    减少主库压力:写操作集中在主库,从库处理大部分的读操作,主库的压力减少,有助于提高写操作的响应速度。
    容错性:在某些情况下,从库可以用作备份,如果主库出现故障,可以临时将从库提升为主库以保持服务的可用性。
  1. 实现方式
    读写分离可以通过多种方式实现,包括:
  • 手动分离:应用程序通过逻辑代码,手动决定读请求发送到从库,写请求发送到主库。
  • 代理层(中间件):使用数据库中间件(如 MySQL Proxy、MaxScale、MyCat等),在应用和数据库之间自动实现读写分离和负载均衡。
  • 连接池支持:某些数据库连接池(如 Druid、HikariCP)可以自动支持主从库的读写分离。
  1. 注意事项
  • 数据一致性问题:由于复制存在延迟,从库上的数据可能会比主库滞后。如果应用程序对实时数据一致性要求较高,需谨慎处理。
  • 负载均衡:要合理分配读请求到不同的从库,避免单个从库成为瓶颈。
  • 主库故障恢复:需要设计可靠的故障转移机制,确保主库出现问题时,从库能够及时接管。
  1. 使用场景
    读写分离适用于读操作远多于写操作的场景,例如电商平台、社交媒体网站等。在这些场景中,读请求往往占大多数,通过读写分离可以有效提升系统的扩展性和性能。

二.读写分离的几种实现方式(手动控制)

这里只介绍手动分离读写库:应用程序通过逻辑代码,手动决定读请求发送到从库,写请求发送到主库的几种实现方式。

1.基于Spring下的AbstractRoutingDataSource

根据大家平常开发习惯,我还是从controller层开始吧。

1.yml

我的yml配置如下:

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedatasource1:url: jdbc:mysql://127.0.0.1:3306/tl_mall_master?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverdatasource2:url: jdbc:mysql://127.0.0.1:3306/tl_mall_slave?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driver
2.Controller
@RestController
@RequestMapping("friend")
@Slf4j
public class FriendController {@Autowiredprivate FriendService friendService;@GetMapping(value = "select")public List<Friend> select(){return friendService.list();}@GetMapping(value = "insert")public String in(){Friend friend = new Friend();friend.setName("jinbiao666");friendService.save(friend);return "主库插入成功";}
}
3.Service实现
@Service
public class FriendImplService implements FriendService {@AutowiredFriendMapper friendMapper;@Override@WR("R")        // 库2public List<Friend> list() {return friendMapper.list();}@Override@WR("W")        // 库1public void save(Friend friend) {friendMapper.save(friend);}
}
4.Mapper层
public interface FriendMapper {@Select("SELECT * FROM friend")List<Friend> list();@Insert("INSERT INTO  friend(`name`) VALUES (#{name})")void save(Friend friend);
}
5.定义多数据源
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.datasource1")public DataSource dataSource1() {// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "spring.datasource.datasource2")public DataSource dataSource2() {// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}
}
6.继承Spring的抽象路由数据源抽象类,重写相关逻辑
  1. 继承 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource抽象类。
  2. 重写determineCurrentLookupKey方法,设置当前db操作应使用的数据源key
  3. 重写afterPropertiesSet方法,设置多数据源和默认数据源。
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {/***  通过ThreadLocal设置当前线程所使用的数据源key*/public static ThreadLocal<String> name = new ThreadLocal<>();// 写@AutowiredDataSource dataSource1;// 读@AutowiredDataSource dataSource2;// 返回当前数据源标识,根据返回的key决定最终使用的数据源@Overrideprotected Object determineCurrentLookupKey() {return name.get();}/*** InitializingBean 是 Spring 框架中的一个接口,用于在 Bean 初始化完成后执行特定的操作。它定义了一个方法 afterPropertiesSet(),当 Bean 的属性设置完成后会被调用。* Spring 容器会在实例化该 Bean 并设置完属性后,自动调用 afterPropertiesSet() 方法来执行一些初始化操作*/@Overridepublic void afterPropertiesSet() {// 为targetDataSources初始化所有数据源Map<Object, Object> targetDataSources=new HashMap<>();targetDataSources.put("W",dataSource1);targetDataSources.put("R",dataSource2);super.setTargetDataSources(targetDataSources);// 为defaultTargetDataSource 设置默认的数据源super.setDefaultTargetDataSource(dataSource1);super.afterPropertiesSet();}
}
7. 自定义注解@WR,用于指定当前操作使用哪个库
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WR {String value() default "W";
}
8. 切面逻辑
@Component
@Aspect
public class DynamicDataSourceAspect implements Ordered {// 前置@Before("within(com.tuling.dynamic.datasource.service.impl.*) && @annotation(wr)")public void before(JoinPoint point, WR wr){// 设置数据源key为注解值(determineCurrentLookupKey()方法里面会去取这个key)DynamicDataSource.name.set(wr.value());}@Overridepublic int getOrder() {return 0;}
}
9.源码简单分析

简单看下AbstractRoutingDataSource里面的determineTargetDataSource决定目标数据源方法。
在这里插入图片描述

10. 开始测试
  1. 给master写库的friend表清空,写入写库
    在这里插入图片描述

  2. 写库写入成功
    在这里插入图片描述

  3. 给slave读库的friend表插入一条数据rise,仅查询到读库的内容,成功实现读写分离。
    在这里插入图片描述

2.基于Mybatis的SqlSessionFactory

一样还是从yml开始吧,目录结果清晰些。

1.yml
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedatasource1:url: jdbc:mysql://127.0.0.1:3306/tl_mall_master?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverdatasource2:url: jdbc:mysql://127.0.0.1:3306/tl_mall_slave?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driver
server:port: 8080
2.Controller
@RestController
@RequestMapping("friend")
@Slf4j
public class FriendController {@Autowiredprivate FriendService friendService;@GetMapping(value = "select")public List<Friend> select(){return friendService.select();}@GetMapping(value = "insert")public void insert(){Friend friend = new Friend();friend.setName("jinbiao666");friendService.insert(friend);}
}
3.Service实现
/**** 读数据源配置:* 1. 指定扫描的mapper接口包(从库)* 2. 指定使用sqlSessionFactory是哪个(从库)*/
@Service
public class FriendImplService implements FriendService {@Autowiredprivate RFriendMapper rFriendMapper;@Autowiredprivate WFriendMapper wFriendMapper;// 读-- 读库@Overridepublic List<Friend> select() {return rFriendMapper.select();}// 保存-- 写库@Overridepublic void insert(Friend friend) {wFriendMapper.insert(friend);}}
4.Mapper层

在mapper层做的读写区分。

public interface RFriendMapper {@Select("SELECT * FROM friend")List<Friend> select();@Insert("INSERT INTO  friend(`name`) VALUES (#{name})")void save(Friend friend);
}
public interface WFriendMapper {@Select("SELECT * FROM friend")List<Friend> list();@Insert("INSERT INTO  friend(`name`) VALUES (#{name})")void insert(Friend friend);
}
5.配置类
1. 指定哪些Mapper接口使用读数据源:
  • 通过@MapperScan注解扫对应的mapper接口,然后设置数据源为从数据源构造一个SqlSessionFactory 对象。
  • 事务管理器用作事务回滚,暂不测试事务回滚了,都是可成功的.
/**** 写数据源配置:* 1. 指定扫描的mapper接口包(主库)* 2. 指定使用sqlSessionFactory是哪个(主库)*/
@Configuration
@MapperScan(basePackages = "com.tuling.datasource.dynamic.mybatis.mapper.r", sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.datasource2")public DataSource dataSource2() {// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}@Bean@Primarypublic SqlSessionFactory rSqlSessionFactory() throws Exception {final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();// 指定主库sessionFactory.setDataSource(dataSource2());// 指定主库对应的mapper.xml文件/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/r/*.xml"));*/return sessionFactory.getObject();}@Beanpublic DataSourceTransactionManager rTransactionManager(){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource2());return dataSourceTransactionManager;}@Beanpublic TransactionTemplate rTransactionTemplate(){return new TransactionTemplate(rTransactionManager());}
}
2. 指定哪些Mapper接口使用写数据源
@Configuration
@MapperScan(basePackages = "com.tuling.datasource.dynamic.mybatis.mapper.w", sqlSessionFactoryRef="wSqlSessionFactory")
public class WMyBatisConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.datasource1")public DataSource dataSource1() {// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}@Bean@Primarypublic SqlSessionFactory wSqlSessionFactory() throws Exception {final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();// 指定主库sessionFactory.setDataSource(dataSource1());// 指定主库对应的mapper.xml文件/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/order/*.xml"));*/return sessionFactory.getObject();}@Bean@Primarypublic DataSourceTransactionManager wTransactionManager(){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource1());return dataSourceTransactionManager;}@Beanpublic TransactionTemplate wTransactionTemplate(){return new TransactionTemplate(wTransactionManager());}
}
6. 开始测试

1.写写库,成功,之前只有1条现在2条。ok,基于Mybatis在mapper层面的读写分离也成功了
在这里插入图片描述
2.读读库
在这里插入图片描述
其他场景比如写库失败回滚都是可以的,因为我们给DataSourceTransactionManager注入了写库的数据源。这里不展示了。

3.基于baomidou动态数据源实现读写分离(最简单)

1. maven依赖
  <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version></dependency>
2.yml

一主两从

spring:datasource:dynamic:#设置默认的数据源或者数据源组,默认值即为masterprimary: master#严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:master:url: jdbc:mysql://127.0.0.1:3306/tl_mall_master?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverslave_1:url: jdbc:mysql://127.0.0.1:3306/tl_mall_slave?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverslave_2:url: jdbc:mysql://127.0.0.1:3306/tl_mall_user?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driver
server:port: 8080
3.Controller
@RestController
@RequestMapping("frend")
@Slf4j
public class FriendController {@Autowiredprivate FriendService friendService;@GetMapping(value = "select")public List<Friend> select(){return friendService.select();}@GetMapping(value = "insert")public void insert(){Friend friend = new Friend();friend.setName("jinbiao666");friendService.insert(friend);}
}   
4.Service
@Service
public class FriendImplService implements FriendService {@AutowiredFriendMapper friendMapper;@Override@DS("slave2")  // 从库2public List<Friend> select() {return friendMapper.select();}@Override@DS("master")  // 主库//@DS("#session.userID")  基于session里面的用户id取数据源,sass化,数据源动态根据用户选择。@DSTransactional   //开启事务操作public void insert(Friend friend) {friendMapper.insert(friend);}
}
5.Mapper层
public interface FriendMapper {@Select("SELECT * FROM friend")List<Friend> select();@Insert("INSERT INTO  friend(`name`) VALUES (#{name})")void insert(Friend friend);
}
6.开始测试

使用是不是超级简单,省去了很多自己注入的步骤,如使用@DS注解选择数据源、@DSTransactional注解回滚对应的数据源事务等等都由baomidou帮我们实现了。

  1. 写写库,成功,之前只有2条现在3条。ok,基于baomidou动态数据源实现读写分离也成功了
    在这里插入图片描述
  2. 读slave_2(tl_mall_user),可以看到数据库3条数据:
    在这里插入图片描述
    接口测试:查询从库slave_2,没问题, 事务回滚暂不在这里做测试了,替大家测过了的,没问题~
    在这里插入图片描述

三.小结

  • 经过上面3种方式介绍,多数据源读写分离是不是很简单。
  • 不过上面都是对单数据源写入操作的,可以使用@Transactional或者@DSTransactional帮我们回滚单数据源的事务。
  • 如果涉及到多数据源的写入需要统一提交回滚怎么实现呢?小伙伴们不妨也思考一下这个问题,这其实就是相当于是分布式事务的回滚了。

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

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

相关文章

Linux-TCP重传

问题描述&#xff1a; 应用系统进行切换&#xff0c;包含业务流量切换&#xff08;即TongWeb主备切换&#xff09;和MYSQL数据库主备切换。首先进行流量切换&#xff0c;然后进行数据库主备切换。切换后发现备机TongWeb上有两批次慢请求&#xff0c;第一批慢请求响应时间在133…

【探索智谱AI的CogVideoX:视频生成的新前沿】

2024年8月6日&#xff0c;智谱AI宣布其开源视频生成模型CogVideoX&#xff0c;激发了开发者的创造力和对新技术的期待。 一、CogVideoX模型概述 CogVideoX 是一款先进的视频生成工具&#xff0c;可基于最长 226 个 token 的提示生成视频&#xff0c;时长可达 6 秒&#xff0c;…

0基础学习PyTorch——时尚分类(Fashion MNIST)训练和推理

大纲 环境准备安装依赖下载训练集训练定义模型训练加载训练集定义损失函数和优化器训练模型保存模型完整文件 推理加载模型加载并预处理本地文件推理完整文件 代码地址参考资料 时尚分类是PyTorch官方文档中推荐的案例。本文将拆解这个案例&#xff0c;进行部署以及测试。 环境…

电路板上电子元件检测系统源码分享

电路板上电子元件检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comp…

SpringCloud源码:客户端分析(二)- 客户端源码分析

背景 我们继续分析EurekaClient的两个自动化配置类&#xff1a; 自动化配置类功能职责EurekaClientAutoConfiguration配置EurekaClient确保了Eureka客户端能够正确地&#xff1a;- 注册到Eureka服务端- 周期性地发送心跳信息来更新服务租约- 下线时通知Eureka服务端- 获取服务实…

TypeScript 设计模式之【建造者模式】

文章目录 **建造者模式**&#xff1a;打造你的梦想之屋建造者的秘密建造者有什么利与害&#xff1f;如何使用建造者搭建各种房子代码实现案例建造者模式的主要优点建造者模式的主要缺点建造者模式的适用场景总结 建造者模式&#xff1a;打造你的梦想之屋 假设你想要一栋完美的…

SpringBoot代码实战(MyBatis-Plus+Thymeleaf)

构建项目 修改pom.xml文件&#xff0c;添加其他依赖以及设置 <!--MyBatis-Plus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.6</version><…

LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大

LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大 1、直播播放2、录像播放3、搭建GB28181视频直播平台 1、直播播放 国标设备-》查看通道-》播放 &#xff0c;左键单击可以拉取矩形框&#xff0c;放大选中的范围&#xff0c;释…

序列化流(对象操作输出流)反序列化流(对象操作输入流)

可以把Java中的对象写到本地文件中 序列化流&#xff08;对象操作输出流&#xff09; 构造方法 成员方法 使用对象输出流将对象保存到文件会出现NotSerializableException异常 解决方案&#xff1a;需要让Javabean类实现Serializable接口 Student package myio;import java.…

家政服务预约系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;客户管理&#xff0c;员工管理&#xff0c;家政服务管理&#xff0c;服务预约管理&#xff0c;员工风采管理&#xff0c;客户需求管理&#xff0c;接单信息管理 微信端账号功能包括&#xff1a;系统首…

MySQL_子查询

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

力扣最热一百题——寻找重复数(中等)

目录 题目链接&#xff1a;287. 寻找重复数 - 力扣&#xff08;LeetCode&#xff09; 题目描述 示例 提示&#xff1a; 解法一&#xff1a;暴力搜寻 Java写法&#xff1a; 运行时间 解法二&#xff1a;排序搜寻 Java写法&#xff1a; 运行时间 C写法&#xff1a; 运…

2024/9/26 英语每日一段

In part, that’s because it’s harder to empathize with someone who feels distant or unknown than a close loved one. “The more shared experiences you have with someone, the more of a rich, nuanced representation you can draw on,” Cameron says. But empath…

【Java网络编程】使用Tcp和Udp实现一个小型的回声客户端服务器程序

网络编程的概念 Java中的网络编程是指使用Java语言及其库创建和管理网络应用程序的过程。这一过程使得不同的计算机可以通过网络进行通信和数据交换。Java提供了一系列强大的API&#xff08;应用程序编程接口&#xff09;来支持网络编程&#xff0c;主要涉及以下几个概念&…

简易STL实现 | 红黑树的实现

1、原理 红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉搜索树 红黑树具有以下特性&#xff0c;这些特性保持了树的平衡&#xff1a; 节点颜色&#xff1a; 每个节点要么是红色&#xff0c;要么是黑色根节点颜色&#xff1a; 根节点是黑色的。叶子节点&…

【stm32】TIM定时器输出比较-PWM驱动LED呼吸灯/舵机/直流电机

TIM定时器输出比较 一、输出比较简介1、OC&#xff08;Output Compare&#xff09;输出比较2、PWM简介3、输出比较通道(高级)4、输出比较通道(通用)5、输出比较模式6、PWM基本结构配置步骤&#xff1a;程序代码&#xff1a;PWM驱动LED呼吸灯 7、参数计算8、舵机简介程序代码&am…

【笔记】KaiOS 系统框架和应用结构(APP界面逻辑)

KaiOS系统框架 最早自下而上分成Gonk-Gecko-Gaia层,代码有同名的目录,现在已经不用这种称呼。 按照官网3.0的版本迭代介绍,2.5->3.0已经将系统更新成如下部分: 仅分为上层web应用和底层平台核心,通过WebAPIs连接上下层,这也是kaios系统升级变更较大的部分。 KaiOS P…

括号匹配问题 -------------

1.题目说明&#xff1a; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有…

Jenkins入门:从搭建到部署第一个Springboot项目(踩坑记录)

本文讲述在虚拟机环境下(模拟服务器)&#xff0c;使用docker方式搭建jenkins&#xff0c;并部署一个简单的Springboot项目。仅记录关键步骤和遇到的坑&#xff0c;后续再进行细节补充。 一、环境准备和基础工具安装 1. 环境 系统环境为本机vmware创建的Ubuntu24.04。 2. yum…

【C++】STL--string(下)

1.string类对象的修改操作 erase&#xff1a;指定位置删除 int main() {string str1("hello world");str1.push_back(c);//尾插一个ccout << str1 << endl;string str2;str2.append("hello"); // 在str后追加一个字符"hello"cout…