数据同步后数据总条数对不上的问题解决

news2024/11/24 8:33:00

文章目录

    • @[toc]
  • 1.问题
  • 2.解决办法
    • 2.1)设置合理的线程池参数
    • 2.2)设置url连接参数
    • 2.3) 优化msql的系统参数
    • 2.4)使用CountDownLatch减法计数器和数据插入的公共方法新开一个事务
    • 2.5)sql批量注入器执行成功后,当前线程sleep(1000)睡1s
  • 3.业务代码套路如下
  • 4.总结

1.问题

  使用上一篇文章的思路来实现数据库表全量数据同步,遇到了一个奇葩的问题,在本地跑代码数据条数对上了,但是生产上线的时候跑数据居然条数对不上,于是乎,我进行了思考,最大的问题有以下几个原因:
  1.1)数据量太大,线程池的参数设置不合理,开的线程太多会导致数据库最大连接数不够而报一个最大连接数不够的异常,从而多出来的处理不了的连接超时就会被数据库丢弃了。
  1.2)数据库的参数性能不一样导致,发送过去插入的数据处理不过来,发送给数据库插入的批次数据越多,由于数据库性能不高处理不过来,导致数据库端阻塞,参数设置(最大连接数据,缓冲区大小等等参数)不合适,在大批量插入场景需要优化,优化数据库系统参数需要重启,所以该方案不适合生产环境。

  1.3)线程提交到线程池执行是异步都在一个主线程中,都是默认springBoot的事务隔离级别,就会导致,主线程执行完毕,但是子线程没有执行完毕,会导致所有的子线的程事务在同一个事务主线程事务里面,这个主线程的事务提交的数据量就会很大,主线程执行完后子线程还在执行,不断的往mysql数据库发送数据,导致mysql端阻塞处理不过来,而丢了一些批次的数据。

2.解决办法

2.1)设置合理的线程池参数

  核心线程数设置为4个,最大线程数设置为8个足够了,设置太多的话已经超过了mysql数据库机器性能了,一般机器就8核cpu,所以设置4-8个线程处理数据足够了,设置了太多的线程,一个是消耗资源不说,二一个是增大mysql的压力(使用过多的线程给mysql端发送大量的数据)。

package xxxxx;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
public class EventListenerExecutor {
    public static ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    static {
        executor.setCorePoolSize(4);
        // 配置最大线程数
        executor.setMaxPoolSize(8);
        // 配置缓存队列大小
        executor.setQueueCapacity(10000);
        // 空闲线程存活时间
        executor.setKeepAliveSeconds(15);
        executor.setThreadNamePrefix("event-listener");
        // 线程池拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
//        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //自定义数据策略
        executor.setRejectedExecutionHandler((r, executor) -> {
            try {
                executor.getQueue().put(r);
            } catch (InterruptedException e) {
                log.error("线程处理拒绝策略失败:{}",e.getMessage());
                e.printStackTrace();
            }
        });
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是被没有完成的任务阻塞
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
    }

    /**
     * 直接执行 不给返回值
     * @param task
     */
    public static void execute(Runnable task) {
        executor.execute(task);
    }

    /**
     * 执行一哈 给返回值
     * @param task 定时处理
     * @return
     * @param <T>
     */
    public static <T> Future<T> submit(Callable<T> task){
        return executor.submit(task);
    }

}

2.2)设置url连接参数

​    mysql的驱动连接的url需要加:rewriteBatchedStatements=true参数

​    关于rewriteBatchedStatements这个参数介绍:

​    MySQL的JDBC连接的url中要加rewriteBatchedStatements参数,并保证5.1.13以上版本的驱动,才能实现高性能的批量插入。
MySQL JDBC驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySQL数据库,批量插入实际上是单条插入,直接造成较低的性能。
只有把rewriteBatchedStatements参数置为true, 驱动才会帮你批量执行SQL
另外这个选项对INSERT/UPDATE/DELETE都有效

​    多数据源配置参看上一篇文章:

spring:
  datasource:
    p6spy: true
    dynamic:
      datasource:
        master:
          username: xxx
          password: xxxxx
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://xxxx:3306/xxxxxx?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
        old_db:
          url: jdbc:mysql://xxxxxx:3306/xxxxx?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
          username: xxxxxxx
          password: xxxx
          driver-class-name: com.mysql.cj.jdbc.Driver

2.3) 优化msql的系统参数

set global max_allowed_packet=1024 *1024 * 512; # 单个packet可以允许的最大值
set global max_connections = 60000; # 并发连接请求量比较大,建议调高此值,以增加并行连接数量
set global innodb_lock_wait_timeout=16 * 1024; # 事务锁超时时间,默认50s,超过就报错
set global bulk_insert_buffer_size=512 * 1024 * 1024; # 加快insert插入效率
set global wsrep_max_ws_size=1024*1024*1024*4; # 避免事务大小超过限制,最大4G

  以上参数如果是在Navicat里面本次会话设置的,使用Navicat执行批量插入sql脚本只对本次会话有效,mysql重启后或会话关闭后失效,永久配置需要在my.cnf配置文件中修改,然后重启数据库即可,永久配置可以参考网上教程。

2.4)使用CountDownLatch减法计数器和数据插入的公共方法新开一个事务

  使用CountDownLatch减法计数器是为了让主线程等待每个子线程都执行完在往下执行,数据插入的公共方法上新开一个事务,一批数据开启一个新的事务,就相当于一个线程来执行数据提交,隔离性好,不会相互影响,特别是id不唯一的情况下会在一个事务中会相互影响的,小批次提高了效率。

 @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void dealData(CopyOnWriteArrayList<CreditsRecordVO> creditsRecordVOS, CountDownLatch countDownLatch) {
        if (CollectionUtil.isNotEmpty(creditsRecordVOS)) {
            EventListenerExecutor.execute(() -> {
                doWork(creditsRecordVOS);
                countDownLatch.countDown();
            });
        }
    }

2.5)sql批量注入器执行成功后,当前线程sleep(1000)睡1s

  执行批量插入成功后,睡1s是为了给数据库一个缓冲的时间,让已经提交到mysql的数据执行完后在发送下一批数据过去,不至于发送大量数据导致mysq那边处理不过来而交通拥挤被挤出了赛道。

Integer cr = creditRecordMapper.insertBatchSomeColumn(result);
if (cr > 0) {
   log.info("插入积分记录ok");
   Thread.sleep(1000);
}

3.业务代码套路如下

McMembersServiceImpl类如下:

package xxxxx;

import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@DS("old_db")
@Service
public class McMembersServiceImpl extends ServiceImpl<McMembersMapper, McMembers> implements McMembersService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private CreditRecordService creditRecordService;

    @Override
    public void dealData() {
        int pageSize = 20000;
        AtomicInteger index = new AtomicInteger(1);
        AtomicInteger totalIndex = new AtomicInteger(0);
        Long total2 = this.baseMapper.getTotal2();
        log.info("total2:{}",total2);
        Long pageCount2 = (total2 + pageSize - 1) / pageSize; //推荐写法
        log.info("pageCount2:{}", pageCount2);
        CountDownLatch countDownLatch = new CountDownLatch(Math.toIntExact(pageCount2));
        try (SqlSession sqlSession = sqlSessionFactory.openSession();
             Cursor<CreditsRecordVO> pageCursor = sqlSession.getMapper(McMembersMapper.class).getCursorData()) {
            List<CreditsRecordVO> creditsRecordVOS = new ArrayList<>();
            for (CreditsRecordVO creditsRecordVO : pageCursor) {
                creditsRecordVOS.add(creditsRecordVO);
                totalIndex.getAndIncrement();
                log.info("total:{}", totalIndex.get());
                if (index.getAndIncrement() == pageSize) {
                    CopyOnWriteArrayList<CreditsRecordVO> creditsRecordVO2 = new CopyOnWriteArrayList<>(creditsRecordVOS);
                    log.info("creditsRecordVO2.size:{}", creditsRecordVO2.size());
                    creditRecordService.dealData(creditsRecordVO2, countDownLatch);
                    creditsRecordVOS.clear();
                    log.info("清空list:{}", creditsRecordVOS.size());
                    index.set(0);
                }
            }
            if (CollectionUtil.isNotEmpty(creditsRecordVOS)) {
                CopyOnWriteArrayList<CreditsRecordVO> creditsRecordVO3 = new CopyOnWriteArrayList<>(creditsRecordVOS);
                log.info("creditsRecordVO3.size:{}", creditsRecordVO3.size());
                creditRecordService.dealData(creditsRecordVO3, countDownLatch);
                creditsRecordVOS.clear();
                log.info("清空list2:{}", creditsRecordVOS.size());
                index.set(0);
            }
            countDownLatch.await();
        } catch (Exception e) {
            log.error("游标查询异常:{}", e.getMessage());
        }
        log.info("total:{}", totalIndex.get());
    }

    @Override
    public Long getTotal2() {
        return baseMapper.getTotal2();
    }

}

CreditRecordServiceImpl类如下:

package xxxxxxx;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.MD5Utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class CreditRecordServiceImpl extends ServiceImpl<CreditRecordMapper, CreditRecord> implements CreditRecordService {

    private final TransactionDefinition transactionDefinition;

    private final DataSourceTransactionManager transactionManager;

    private final CreditRecordMapper creditRecordMapper;

    private final MemberMapper memberMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void dealData(CopyOnWriteArrayList<CreditsRecordVO> creditsRecordVOS, CountDownLatch countDownLatch) {
        if (CollectionUtil.isNotEmpty(creditsRecordVOS)) {
            EventListenerExecutor.execute(() -> {
                doWork(creditsRecordVOS);
                countDownLatch.countDown();
            });
        }
    }

    private void doWork(CopyOnWriteArrayList<CreditsRecordVO> creditLogVOS) {
        try {
            List<CreditRecord> result = new ArrayList<>();
            for (CreditsRecordVO creditLogVO : creditLogVOS) {
                CreditRecord creditRecord = new CreditRecord();
                creditRecord.setId(Long.valueOf(creditLogVO.getId()));
                creditRecord.setContent(creditLogVO.getRemark() != null ? creditLogVO.getRemark() : "");
                if (Objects.nonNull(creditLogVO.getNum())) {
                    creditRecord.setNum(BigDecimal.valueOf(creditLogVO.getNum()));
                    if (creditLogVO.getNum() > 0) {
                        creditRecord.setFlowType(1);
                    } else if (creditLogVO.getNum() < 0) {
                        creditRecord.setFlowType(1);
                    }
                } else {
                    continue;
                }
                creditRecord.setCreditType(creditLogVO.getCredittype());
                creditRecord.setStatus(1);
                if (Objects.nonNull(creditLogVO.getUid())) {
                    creditRecord.setMemberId(Long.valueOf(creditLogVO.getUid()));
                } else {
                    continue;
                }
                Integer createtime = creditLogVO.getCreatetime();
                if (Objects.nonNull(createtime)) {
                    LocalDateTime localDateTime = LocalDateTime.ofInstant(
                            Instant.ofEpochSecond(createtime), ZoneOffset.ofHours(8)
                    );
                    creditRecord.setCreateTime(localDateTime);
                }
                creditRecord.setBalanceSnapshot(BigDecimal.valueOf(-1));
                creditRecord.setDeleted(0);
                creditRecord.setIsExp(0);
                creditRecord.setFromId(0L);
                creditRecord.setOpType(0);
                creditRecord.setOrderNo("");
                creditRecord.setUpdateTime(LocalDateTime.now());
                result.add(creditRecord);
            }
            log.info("插入数据条数:{}", result.size());
            Integer cr = creditRecordMapper.insertBatchSomeColumn(result);
            if (cr > 0) {
                log.info("插入积分记录ok");
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("插入积分记录异常:{}", ExceptionUtils.getMessage(e));
        }
    }

}

4.总结

  这个是一个经过实战的套路,最近重构了一个会员模块涉及到会员表数据(4百多万)、粉丝表数据(3百多万)、关联映射表数据(4百多万)还有积分日志记录数据(1千多万),需要清洗同步到新的表中,前三个表是有关联的白表总的数据量有1千多万,后面的1千多万,总的2千多万数据同步时间只需要20分钟就可以搞定的,性能和效率还是杠杠的,执行时间如下图所示:

在这里插入图片描述

  如果这个问题没有处理好,生产少的的会员数据就需要重新去反查,使用sql的join查出少了的数据重新补上去,由于用户数据同步少了,老用户登录系统就会变成一个新用户,就会带来一系列的问题,之前用老账号下的单子,登录后是一个新用户,关联不到订单了,所以需要去修复各种业务数据,数据补完后需要把新用户相关删除,然后清除redis的用户相关的数据,什么token/access_token/用户信息了,数据量大,那生产redis的用户相关的缓存数据也大的,这些由于数据同步的问题带来的后续这种蛋疼的问题,也是让人头疼,所以写了一篇避坑提效的文章,希望对你有帮助,请一键三连,么么哒,如何批量删除redis中的用户信息,请看下一篇文章!

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

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

相关文章

蛊卦-拨乱反正

目录 前言 卦辞 爻辞 总结 前言 题外话&#xff0c;今天占卜时&#xff0c;看错了&#xff0c;以为占到了蛊卦&#xff08;后续会对自己的占卦经历进行补充&#xff0c;不断完善这个易经学习的专栏&#xff09;&#xff0c;那顺便就学习一下蛊卦&#xff0c;蛊惑人心&#…

线程和进程同步互斥你真的掌握了吗?(同步互斥机制保姆级讲解与应用)

目录 同步互斥的概念 互斥锁 初始化互斥锁 销毁互斥锁 申请上锁 解锁 案例1&#xff1a;没有互斥锁 多任务的运行情况 案例2&#xff1a;有互斥锁 多任务的运行情况 死锁 读写锁 初始化读写锁 销毁读写锁 申请读锁 申请写锁 释放读写锁 案例&#xff1a;两个任务…

回归预测 | MATLAB实现GA-RBF遗传算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GA-RBF遗传算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现GA-RBF遗传算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果…

从0到1:通用后台管理系统 echarts图使用及其参数

这一章主要讲在系统概览模块中&#xff0c;所使用的echarts图及其参数 echarts是一个基于 JavaScript 的开源可视化图表库&#xff0c; 官网直通车 是在各种后台管理系统的开发中都常见的一种库&#xff0c;也是前端开发管理系统所必学的一种库 那么在项目中主要是使用了饼…

【前端】React快速入门+Redux状态管理

本文旨在记录react的基础内容&#xff0c;帮助有需要的同学快速上手,需要进一步了解描述更加稳妥和全面的信息&#xff0c;请查阅官方文档 官方文档点击这里进行跳转 React快速入门 先导 react框架 vue,react,angular这几种主流前端框架使用频率较高…本质还是js库。 React…

1.Jetson Orin Nano Developer Kit系统刷机

本教程有3种方法刷机&#xff0c;根据需要自己选择适合自己的方案。 一:使用>32G的SD卡安装开发套件; 二:在Ubuntu18.04下通过SDK Manager软件在线安装系统. 三:在Ubuntu18.04下通过脚本方式安装系统. 注意&#xff1a;Ubuntu的账号不能为一些常见的包名如&#xff1a;p…

【学习日记】【FreeRTOS】FreeRTOS 移植到 STM32F103C8

前言 本文基于野火 FreeRTOS 教程&#xff0c;内容是关于 FreeRTOS 官方代码的移植的注意事项&#xff0c;并将野火例程中 STM32F103RC 代码移植到 STM32F103C8。 一、FreeRTOS V9.0.0 源码的获取 两个下载链接&#xff1a; 官 网 代码托管 二、源码文件夹内容简介 Source…

Docker(一)-安装、架构、业务开发常用命令、Dockerile、镜像卷、镜像仓库

基于业务开发使用Docker Docker是一个开源的容器引擎&#xff0c;它有助于更快地交付应用。Docker可将应用程序和基础设施层隔离&#xff0c;并且能将基础设施当作程序一样进行管理。使用 Docker可更快地打包、测试以及部署应用程序&#xff0c;并可以缩短从编写到部署运行代码…

为什么需要单元测试?

为什么需要单元测试&#xff1f; 从产品角度而言&#xff0c;常规的功能测试、系统测试都是站在产品局部或全局功能进行测试&#xff0c;能够很好地与用户的需要相结合&#xff0c;但是缺乏了对产品研发细节&#xff08;特别是代码细节的理解&#xff09;。 从测试人员角度而言…

Springboot 实践(10)spring cloud 与consul配置运用之服务的注册与发现

前文讲解&#xff0c;完成了springboot、spring security、Oauth2.0的继承&#xff0c;实现了对系统资源的安全授权、允许获得授权的用户访问&#xff0c;也就是实现了单一系统的全部技术开发内容。 Springboot是微服务框架&#xff0c;单一系统只能完成指定系统的功能&#xf…

【简单认识Docker网络管理】

文章目录 一、Docker 网络实现原理二、Docker 的网络模式1.四种网络模式2.各网络模式详解&#xff08;1&#xff09;Host模式&#xff08;2&#xff09;Container模式&#xff08;3&#xff09;None模式&#xff08;4&#xff09;Bridge模式 3.指定容器网络模式4.自定义网络模式…

web文件上传

文件上传指的是&#xff0c;将本地的图片、视频、音频上传到服务器&#xff0c;提供给其他用户浏览和下载的过程 前端需求 想要进行文件上传对于web前端来说有三个重要要素 1.<input type"file" name"image"> 提供这样的file文件上传格式 2. metho…

【Unity】自带的录屏插件Recorder

目录 Recorder简介Recorder导入Recorder使用 Recorder简介 Recorder是Unity官方的录屏插件&#xff0c;可以直接录制Game窗口&#xff0c;还可以录制不同相机的视图。不仅可以直接生成视频、帧动画图、还可以制作gif和animation。 Recorder导入 菜单栏Windows→Package Mana…

VINS-Mono中的边缘化与滑窗 (4)——VINS边缘化为何是局部变量边缘化?

文章目录 0.前言1.系统构建1.1.仿真模型1.2.第一次滑窗优化1.3.第二次全局优化 2.边缘化时不同的舒尔补方式2.1.边缘化时舒尔补的意义2.2.不同的边缘化方式 3.边缘化时不同的舒尔补方式实验验证3.1.全局schur的操作方式3.2.VIO或VINS中局部边缘化的方式3.3.两种方式和全局优化方…

【Linux】文件的描述符和重定向

文件的描述符和重定向 C语言的文件读写操作代码 open系统打开文件方法系统读写文件操作文件描述符文件重定向怎么理解文件缓冲区 C语言的文件读写操作 文件写入 fputs int fputs(const char *s, FILE *stream); s&#xff1a;要写入的字符串 stream&#xff1a;要写入对应的目标…

halcon库文件封装操作方法介绍

头文件写法参考 getarea.h 导出函数设定&#xff1a; extern “C” __declspec(dllexport) void _stdcall cpp文件写法参看 3.vs 库文件配置介绍 头文件及包含的库文件目录设定 &#xff08;1&#xff09;头文件设定E:\halcon\env\opencv\include\opencv2;E:\halcon\env\ope…

接口限流注解 RateLimiter (现成直接用)

1. 限流注解 import com.ruoyi.common.constant.Constants; import com.ruoyi.common.enums.LimitType;import java.lang.annotation.*;/*** 限流注解* */ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface RateLimiter {/*** 限流…

DSO 系列文章(3)——DSO后端正规方程构造与Schur消元

文章目录 DSO代码注释&#xff1a;https://github.com/Cc19245/DSO-CC_Comments

javascript期末作业【三维房屋设计】 【文末源码+文档下载】

1、引入three.js库 官网下载three.js 库 放置目录并引用 引入js文件: 设置场景&#xff08;scene&#xff09; &#xff08;1&#xff09;创建场景对象 &#xff08;2&#xff09;设置透明相机 1,透明相机的优点 透明相机机制更符合于人的视角,在场景预览和游戏场景多有使用…

安装docker配置镜像加速器,容器等

1.安装docker服务&#xff0c;配置镜像加速器 2.下载系统镜像&#xff08;Ubuntu、 centos&#xff09; 3.基于下载的镜像创建两个容器 &#xff08;容器名一个为自己名字全拼&#xff0c;一个为首名字字母&#xff09; 4.容器的启动、 停止及重启操作 5.怎么查看正在运行的容器…