从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载
本文为从零开始写 Docker 系列第六篇,实现类似 docker -v 的功能,通过挂载数据卷将容器中部分数据持久化到宿主机。
完整代码见:https://github.com/lixd/mydocker
欢迎 Star
推荐阅读以下文章对 docker 基本实现有一个大致认识:
- 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
- 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
- 基于 cgroups 的资源限制
- 初探 Linux Cgroups:资源控制的奇妙世界
- 深入剖析 Linux Cgroups 子系统:资源精细管理
- Docker 与 Linux Cgroups:资源隔离的魔法之旅
- 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
- 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络
开发环境如下:
root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal
root@mydocker:~# uname -r
5.4.0-74-generic
注意:需要使用 root 用户
1. 概述
上一篇中基于 overlayfs 实现了容器和宿主机文件系统间的写操作隔离。但是一旦容器退出,容器可读写层的所有内容都会被删除。
那么,如果用户需要持久化容器里的部分数据该怎么办呢?
docker volume 就是用来解决这个问题的。
启动容器时通过-v
参数创建 volume 即可实现数据持久化。
本节将会介绍如何实现将宿主机的目录作为数据卷挂载到容器中,并且在容器退出后,数据卷中的内容仍然能够保存在宿主机上。
具体实现主要依赖于 linux 的 bind mount 功能。
bind mount
是一种将一个目录或者文件系统挂载到另一个目录的技术。它允许你在文件系统层级中的不同位置共享相同的内容,而无需复制文件或数。
例如:
mount -o bind /source/directory /target/directory/
这样,/source/directory
中的内容将被挂载到 /target/directory
,两者将共享相同的数据。对其中一个目录的更改也会反映到另一个目录。
基于该技术我们只需要将 volume 目录挂载到容器中即可,就像这样:
mount -o bind /host/directory /container/directory/
这样容器中往该目录里写的数据最终会共享到宿主机上,从而实现持久化。
如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。
搜索公众号【探索云原生】即可订阅
2. 实现
volume 功能大致实现步骤如下:
- 1)run 命令增加 -v 参数,格式个 docker 一致
- 例如 -v /etc/conf:/etc/conf 这样
- 2)容器启动前,挂载 volume
- 先准备目录,其次 mount overlayfs,最后 bind mount volume
- 3)容器停止后,卸载 volume
- 先 umount volume,其次 umount overlayfs,最后删除目录
注意:第三步需要先 umount volume ,然后再删除目录,否则由于 bind mount 存在,删除临时目录会导致 volume 目录中的数据丢失。
runCommand
首先在 runCommand 命令中添 -v flag,以接收 volume 参数。
var runCommand = cli.Command{Name: "run",Usage: `Create a container with namespace and cgroups limitmydocker run -it [command]`,Flags: []cli.Flag{cli.BoolFlag{Name: "it", // 简单起见,这里把 -i 和 -t 参数合并成一个Usage: "enable tty",},cli.StringFlag{Name: "mem", // 限制进程内存使用量,为了避免和 stress 命令的 -m 参数冲突 这里使用 -mem,到时候可以看下解决冲突的方法Usage: "memory limit,e.g.: -mem 100m",},cli.StringFlag{Name: "cpu",Usage: "cpu quota,e.g.: -cpu 100", // 限制进程 cpu 使用率},cli.StringFlag{Name: "cpuset",Usage: "cpuset limit,e.g.: -cpuset 2,4", // 限制进程 cpu 使用率},cli.StringFlag{ // 数据卷Name: "v",Usage: "volume,e.g.: -v /ect/conf:/etc/conf",},},/*这里是run命令执行的真正函数。1.判断参数是否包含command2.获取用户指定的command3.调用Run function去准备启动容器:*/Action: func(context *cli.Context) error {if len(context.Args()) < 1 {return fmt.Errorf("missing container command")}var cmdArray []stringfor _, arg := range context.Args() {cmdArray = append(cmdArray, arg)}tty := context.Bool("it")resConf := &subsystems.ResourceConfig{MemoryLimit: context.String("mem"),CpuSet: context.String("cpuset"),CpuCfsQuota: context.Int("cpu"),}log.Info("resConf:", resConf)volume := context.String("v")Run(tty, cmdArray, resConf, volume)return nil},
}
在 Run 函数中,把 volume 传给创建容器的 NewParentProcess 函数和删除容器文件系统的 DeleteWorkSpace 函数。
func Run(tty bool, comArray []string, res *subsystems.ResourceConfig, volume string) {parent, writePipe := container.NewParentProcess(tty, volume)if parent == nil {log.Errorf("New parent process error")return}if err := parent.Start(); err != nil {log.Errorf("Run parent.Start err:%v", err)return}// 创建cgroup manager, 并通过调用set和apply设置资源限制并使限制在容器上生效cgroupManager := cgroups.NewCgroupManager("mydocker-cgroup")defer cgroupManager.Destroy()_ = cgroupManager.Set(res)_ = cgroupManager.Apply(parent.Process.Pid, res)// 在子进程创建后才能通过pipe来发送参数sendInitCommand(comArray, writePipe)_ = parent.Wait()container.DeleteWorkSpace("/root/", volume)
}
NewWorkSpace
在原有创建过程最后增加 volume bind 逻辑:
- 1)首先判断 volume 是否为空,如果为空,就表示用户并没有使用挂载参数,不做任何处理
- 2)如果不为空,则使用 volumeUrlExtract 函数解析 volume 字符串,得到要挂载的宿主机目录和容器目录,并执行 bind mount
func NewWorkSpace(rootPath, volume string) {createLower(rootPath)createDirs(rootPath)mountOverlayFS(rootPath)// 如果指定了volume则还需要mount volumeif volume != "" {mntPath := path.Join(rootPath, "merged")hostPath, containerPath, err := volumeExtract(volume)if err != nil {log.Errorf("extract volume failed,maybe volume parameter input is not correct,detail:%v", err)return}mountVolume(mntPath, hostPath, containerPath)}
}
volumeExtract
语法和 docker run -v 一致,两个路径通过冒号分隔。
// volumeExtract 通过冒号分割解析volume目录,比如 -v /tmp:/tmp
func volumeExtract(volume string) (sourcePath, destinationPath string, err error) {parts := strings.Split(volume, ":")if len(parts) != 2 {return "", "", fmt.Errorf("invalid volume [%s], must split by `:`", volume)}sourcePath, destinationPath = parts[0], parts[1]if sourcePath == "" || destinationPath == "" {return "", "", fmt.Errorf("invalid volume [%s], path can't be empty", volume)}return sourcePath, destinationPath, nil
}
mountVolume
挂载数据卷的过程如下。
- 1)首先,创建宿主机文件目录
- 2)然后,拼接处容器目录在宿主机上的真正目录,格式为:
$mntPath/$containerPath
- 因为之前使用了 pivotRoot 将
$mntPath
作为容器 rootfs,因此这里的容器目录也可以按层级拼接最终找到在宿主机上的位置。
- 因为之前使用了 pivotRoot 将
- 3)最后,执行 bind mount 操作,至此对数据卷的处理也就完成了。
// mountVolume 使用 bind mount 挂载 volume
func mountVolume(mntPath, hostPath, containerPath string) {// 创建宿主机目录if err := os.Mkdir(hostPath, constant.Perm0777); err != nil {log.Infof("mkdir parent dir %s error. %v", hostPath, err)}// 拼接出对应的容器目录在宿主机上的的位置,并创建对应目录containerPathInHost := path.Join(mntPath, containerPath)if err := os.Mkdir(containerPathInHost, constant.Perm0777); err != nil {log.Infof("mkdir container dir %s error. %v", containerPathInHost, err)}// 通过bind mount 将宿主机目录挂载到容器目录// mount -o bind /hostPath /containerPathcmd := exec.Command("mount", "-o", "bind", hostPath, containerPathInHost)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {log.Errorf("mount volume failed. %v", err)}
}
DeleteWorkSpace
删除容器文件系统时,先判断是否挂载了 volume,如果挂载了则删除时则需要先 umount volume。
注意:一定要要先 umount volume ,然后再删除目录,否则由于 bind mount 存在,删除临时目录会导致 volume 目录中的数据丢失。
func DeleteWorkSpace(rootPath, volume string) {mntPath := path.Join(rootPath, "merged")// 如果指定了volume则需要umount volume// NOTE: 一定要要先 umount volume ,然后再删除目录,否则由于 bind mount 存在,删除临时目录会导致 volume 目录中的数据丢失。if volume != "" {_, containerPath, err := volumeExtract(volume)if err != nil {log.Errorf("extract volume failed,maybe volume parameter input is not correct,detail:%v", err)return}umountVolume(mntPath, containerPath)}umountOverlayFS(mntPath)deleteDirs(rootPath)
}
umountVolume
和普通 umount 一致
func umountVolume(mntPath, containerPath string) {// mntPath 为容器在宿主机上的挂载点,例如 /root/merged// containerPath 为 volume 在容器中对应的目录,例如 /root/tmp// containerPathInHost 则是容器中目录在宿主机上的具体位置,例如 /root/merged/root/tmpcontainerPathInHost := path.Join(mntPath, containerPath)cmd := exec.Command("umount", containerPathInHost)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {log.Errorf("Umount volume failed. %v", err)}
}
3.测试
下面来验证一下程序的正确性。
挂载不存在的目录
第一个实验是把一个宿主机上不存在的文件目录挂载到容器中。
首先还是要在 root 目录准备好 busybox.tar,作为我们的镜像只读层。
$ ls
busybox.tar
启动容器,把宿主机的 /root/volume 挂载到容器的 /tmp 目录下。
root@mydocker:~/feat-volume/mydocker# ./mydocker run -it -v /root/volume:/tmp /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0 }","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"mkdir parent dir /root/volume error. mkdir /root/volume: file exists","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"mkdir container dir /root/merged//tmp error. mkdir /root/merged//tmp: file exists","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"Current location is /root/merged","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-18T16:47:29+08:00"}
新开一个窗口,查看宿主机 /root 目录:
root@DESKTOP-9K4GB6E:~# ls
busybox busybox.tar merged upper volume work
多了几个目录,其中 volume 就是我们启动容器是指定的 volume 在宿主机上的位置。
同样的,容器中也多了 containerVolume 目录:
/ # ls
bin dev home root tmp var
containerVolume etc proc sys usr
现在往 /tmp 目录写入一个文件
/ # echo KubeExplorer > tmp/hello.txt
/ # ls /tmp
hello.txt
/ # cat /tmp/hello.txt
KubeExplorer
然后查看宿主机的 volume 目录:
root@mydocker:~# ls /root/volume/
hello.txt
root@mydocker:~# cat /root/volume/hello.txt
KubeExplorer
可以看到,文件也在。
然后测试退出容器后是否能持久化。
退出容器:
/ # exit
宿主机中再次查看 volume 目录:
root@mydocker:~# ls /root/volume/
hello.txt
文件还在,说明我们的 volume 功能是正常的。
挂载已经存在目录
第二次实验是测试挂载一个已经存在的目录,这里就把刚才创建的 volume 目录再挂载一次:
root@mydocker:~/feat-volume/mydocker# ./mydocker run -it -v /root/volume:/tmp /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0 }","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"mkdir parent dir /root/volume error. mkdir /root/volume: file exists","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"mkdir container dir /root/merged//tmp error. mkdir /root/merged//tmp: file exists","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"Current location is /root/merged","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-18T17:02:48+08:00"}
查看刚才的文件是否存在
/ # ls /tmp/hello.txt
/tmp/hello.txt
/ # cat /tmp/hello.txt
KubeExplorer
还在,说明目录确实挂载进去了。
接下来更新文件内容并退出:
/ # echo KubeExplorer222 > /tmp/hello.txt
/ # cat /tmp/hello.txt
KubeExplorer222
/ # exit
在宿主机上查看:
root@mydocker:~# cat /root/volume/hello.txt
KubeExplorer222
至此,说明我们的 volume 功能是正常的。
4. 小结
本篇记录了如何实现 mydocker run -v
参数,增加 volume 以实现容器中部分数据持久化。
一些比较重要的点:
首先要理解 linux 中的 bind mount 功能。
bind mount
是一种将一个目录或者文件系统挂载到另一个目录的技术。它允许你在文件系统层级中的不同位置共享相同的内容,而无需复制文件或数。
其次,则是要理解宿主机目录和容器目录之间的关联关系。
以 -v /root/volume:/tmp
参数为例:
-
1)按照语法,
-v /root/volume:/tmp
就是将宿主机/root/volume
挂载到容器中的/tmp
目录。 -
2)由于前面使用了 pivotRoot 将
/root/merged
目录作为容器的 rootfs,因此,容器中的根目录实际上就是宿主机上的/root/merged
目录- 第四篇:
-
3)那么容器中的
/tmp
目录就是宿主机上的/root/merged/tmp
目录。 -
4)因此,我们只需要将宿主机
/root/volume
目录挂载到宿主机的/root/merged/tmp
目录即可实现 volume 挂载。
在清楚这两部分内容后,整体实现就比较容易理解了。
如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。
搜索公众号【探索云原生】即可订阅
完整代码见:https://github.com/lixd/mydocker
欢迎 Star
相关代码见 feat-volume
分支,测试脚本如下:
需要提前在 /root 目录准备好 busybox.tar 文件,具体见第四篇第二节。
# 克隆代码
git clone -b feat-volume https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 查看文件系统是否变化
./mydocker run -it /bin/ls
./mydocker run -it -v /root/volume:/tmp /bin/sh
相关文章:
从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载
本文为从零开始写 Docker 系列第六篇,实现类似 docker -v 的功能,通过挂载数据卷将容器中部分数据持久化到宿主机。 完整代码见:https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识: …...
网站引用图片但它域名被墙了或者它有防盗链,我们想引用但又不能显示,本文附详细的解决方案非常简单!
最好的办法就是直接读取图片文件,用到php中一个常用的函数file_get_contents(图片地址),意思是读取远程的一张图片,在输出就完事。非常简单~话不多说,直接上代码 <?php header("Content-type: image/jpeg&quo…...
Java八股文(RabbitMQ)
Java八股文のRabbitMQ RabbitMQ RabbitMQ RabbitMQ 是什么?它解决了哪些问题? RabbitMQ 是一个开源的消息代理中间件,用于在应用程序之间进行可靠的异步消息传递。 它解决了应用程序间解耦、消息传递、负载均衡、故障恢复等问题。 RabbitMQ …...
科研学习|论文解读——一种用于短文本消息中的释义检测的深度网络模型(IPM, 2018)
论文原标题 A deep network model for paraphrase detection in short text messages 摘要 本文研究释义检测,即识别语义相同的句子。检测用自然语言编写的相似句子的能力对一些应用程序至关重要,如文本挖掘、文本摘要、剽窃检测、作者身份认证和问题回答。认识到这一…...
鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Web)下篇
onRequestSelected onRequestSelected(callback: () > void) 当Web组件获得焦点时触发该回调。 示例: // xxx.ets import web_webview from ohos.web.webviewEntry Component struct WebComponent {controller: web_webview.WebviewController new web_webv…...
3月19日做题
[NPUCTF2020]验证🐎 if (first && second && first.length second.length && first!second && md5(firstkeys[0]) md5(secondkeys[0]))用数组绕过first1&second[1] 这里正则规律过滤位(Math.) (?:Math(?:\.\w)?) : 匹配 …...
Java8中Stream流API最佳实践Lambda表达式使用示例
文章目录 一、创建流二、中间操作和收集操作筛选 filter去重distinct截取跳过映射合并多个流是否匹配任一元素:anyMatch是否匹配所有元素:allMatch是否未匹配所有元素:noneMatch获取任一元素findAny获取第一个元素findFirst归约数值流的使用中…...
构建Helm chart和chart使用管道与函数简介
目录 一.创建helm chart(以nginx为例) 1.通过create去创建模板 2.查看模板下的文件 3.用chart模版安装nginx 二.版本更新和回滚问题 1.使用upgrade -f values.yaml或者命令行--set来设置 2.查看历史版本并回滚 三.helm模板内管道和函数 1.defau…...
深入理解OnCalculate函数的运行机制
文章目录 一、学习 OnCalculate 函数的运行原理的意义二、OnCalculate 函数原型三、OnCalculate 函数在MT4与MT5区别四、OnCalculate 函数的运行原理 一、学习 OnCalculate 函数的运行原理的意义 OnCalculate函数是MQL语言中的一个重要函数,它用于计算技术指标的值。…...
快速从0-1完成聊天室开发——环信ChatroomUIKit功能详解
聊天室是当下泛娱乐社交应用中最经典的玩法,通过调用环信的 IM SDK 接口,可以快速创建聊天室。如果想根据自己业务需求对聊天室应用的 UI界面、弹幕消息、礼物打赏系统等进行自定义设计,最高效的方式则是使用环信的 ChatroomUIKit 。 文档地址…...
nginx实现多个域名和集群
要通过Nginx实现多个域名和集群,你需要配置Nginx作为反向代理服务器,将来自不同域名的请求转发到集群中的相应后端服务器。下面是一个基本的配置示例,你可以根据自己的需求进行修改和扩展。 首先,确保你已经安装了Nginxÿ…...
C. Left and Right Houses
Problem - C - Codeforces 题目分析 <1>0:想被分割至左边; 1:想被分割至右边 <2>使得左右两侧均有一半及其以上的人满意(我*******) <3>答案若有多个,取最接近中间位置的答案 <4…...
缓存与内存:加速你的Python应用
在现代计算中,缓存和内存是提高程序性能的关键组件。在这篇文章中,我们将深入探讨这两个概念,了解它们是如何工作的,以及如何在Python中有效地使用它们来优化你的程序。 缓存与内存:加速你的Python应用 缓存和内存&…...
Go语言之函数、方法、接口
一、函数 函数的基本语法: func 函数名(形参列表)(返回值列表) {执行语句...return 返回值列表 } 1.形参列表:表示函数的输入 2.函数中的语句:表示为了实现某一功能的代码块 3.函数可以有返回…...
【Week Y2】使用自己的数据集训练YOLO-v5s
Y2-使用自己的数据集训练YOLO-v5s 零、遇到的问题汇总(1)遇到git的import error(2)Error:Dataset not found(3)Error:删除中文后,训练图片路径不存在 一、.xml文件里保存…...
蓝桥杯--基础(哈夫曼)
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner;public class BASIC28 {//哈夫曼书public static void main(String[] args) {Scanner Scannernew Scanner(System.in);int nScanner.nextInt();List<Integer&…...
【Redis内存数据库】NoSQL的特点和应用场景
前言 Redis作为当今最流行的内存数据库,已经成为服务端加速的必备工具之一。 NoSQL数据库采用了非关系型的数据存储模型,能够更好地处理海量数据和高并发访问。 内存数据库具有更快的读写速度和响应时间,因为内存访问速度比磁盘访问速度快…...
JavaScript基础知识2
求数组的最大值案例 let arr[2,6,1,7,400,55,88,100]let maxarr[0]let minarr[0]for(let i1;i<arr.length;i){max<arr[i]?maxarr[i]:maxmin>arr[i]?minarr[i]:min}console.log(最大值是:${max})console.log(最小值是:${min}) 操作数组 修改…...
Linux之线程同步
目录 一、问题引入 二、实现线程同步的方案——条件变量 1、常用接口: 2、使用示例 一、问题引入 我们再次看看上次讲到的多线程抢票的代码:这次我们让一个线程抢完票之后不去做任何事。 #include <iostream> #include <unistd.h> #inc…...
03 龙芯平台openstack部署搭建-keystone部署
#!/bin/bash #创建keystone数据库并授权,可通过mysql -ukeystone -ploongson验证授权登录 mysql -uroot -e “set password for rootlocalhost password(‘loongson’);” mysql -uroot -ploongson -e ‘CREATE DATABASE keystone;’ #本地登录 mysql -uroot -ploo…...
定义了服务器的端口号和Servlet的上下文路径
server: port: 1224 servlet: context-path: /applet 这个配置定义了服务器的端口号和Servlet的上下文路径。 下面是配置的解释: server.port: 1224:这表示服务器应该监听在1224端口上。server.servlet.context-path: /applet:这表…...
AI论文速读 | UniST:提示赋能通用模型用于城市时空预测
本文是时空领域的统一模型——UniST,无独有偶,时序有个统一模型新工作——UniTS,感兴趣的读者也可以阅读今天发布的另外一条。 论文标题:UniST: A Prompt-Empowered Universal Model for Urban Spatio-Temporal Prediction 作者&…...
rabbitmq-spring-boot-start配置使用手册
rabbitmq-spring-boot-start配置使用手册 文章目录 1.yaml配置如下2.引入pom依赖如下2.1 引入项目resources下libs中的jar包依赖如下2.2引入maven私服依赖如下 3.启动类配置如下4.项目中测试发送消息如下5.项目中消费消息代码示例6.mq管理后台交换机队列创建及路由绑定关系如下…...
操作系统知识-操作系统作用+进程管理-嵌入式系统设计师备考笔记
0、前言 本专栏为个人备考软考嵌入式系统设计师的复习笔记,未经本人许可,请勿转载,如发现本笔记内容的错误还望各位不吝赐教(笔记内容可能有误怕产生错误引导)。 本章的主要内容见下图: 1、操作系统的作用…...
Go语言中的锁与管道的运用
目录 1.前言 2.锁解决方案 3.管道解决方案 4.总结 1.前言 在写H5小游戏的时候,由于需要对多个WebSocket连接进行增、删、查的管理和对已经建立连接的WebSocket通过服务端进行游戏数据交换的需求。于是定义了一个全局的map集合进行连接的管理,让所有…...
前端 - 基础 表单标签 -- 表单元素( input - type属性) 文本框和密码框
表单元素 : 在表单域中可以定义各种表单元素,这些表单元素就是允许用户在表单中输入或选择 的内容控件。 表单元素的外观也各不一样,有小圆圈,有正方形,也有方框,乱七八糟的,各种各样…...
关于MySQL模糊搜索不区分大小写
在我们日常使用ORM框架进行模糊查询时,会发现,搜索的结果是不区分关键字的英文大小写的,那这是为什么呢? 原因是MySQL的like本就不区分大小写;如果在建表的时候,没有设置好字段区分大小 //包含j和J的都会被…...
论文阅读——MoCo
Momentum Contrast for Unsupervised Visual Representation Learning 动量在数学上理解为加权移动平均: yt-1是上一时刻输出,xt是当前时刻输入,m是动量,不想让当前时刻输出只依赖于当前时刻的输入,m很大时࿰…...
ARM 寄存器学习:(一)arm多种模式下得寄存器
一.ARM7种状态以及每种状态的寄存器: ARM 处理器共有 7 种不同的处理器模式,在每一种处理器模式中可见的寄存器包括 15 个通用寄存器( R0~R14)、一个或两个(User和Sys不是异常模式,没有spsr寄存器)状态寄存器(cpsr和spsr&…...
【nfs报错】rpc mount export: RPC: Unable to receive; errno = No route to host
NFS错误 问题现象解决方法 写在前面 这两天搭建几台服务器,需要使用nfs服务,于是六台选其一做服务端,其余做客户端,搭建过程写在centos7离线搭建NFS共享文件,但是访问共享时出现报错:rpc mount export: RPC…...
wordpress网站布局/新手怎么推广自己的店铺
MariaDB数据库 数据库介绍 MySQL与MariaDB 数据库部署1.安装MariaDB数据库2.重启mariadb服务并加入开机启动项3.!数据库初始化4.设置防火墙策略5.登录数据库 数据库常用语句 创建数据库 查询指定位置数据 数据库备份 彻底删除数据库 恢复数据 数据库介绍 数据库:是…...
域名服务商排名/seo如何优化
2019独角兽企业重金招聘Python工程师标准>>> 一、链接数据库 $conn new Mongo("mongodb://IP:PORT/DBname:DBPWD"); //这里采用默认连接本机的27017端口,当然你也可以连接远程主机如 $db$conn->selectDB("DBName"); $c…...
西安监控系统网站开发/关键词排名优化软件策略
第一次启动App,设置一个初始化的pin code: 输入C4C tenant的url,用户名和密码: 登录进系统后,找到Sales Order工作中心: 可以看到系统里很多已有的销售订单了。点击屏幕左下角的创建按钮: Accou…...
什么叫整合营销/合肥网站快速优化排名
Description 喜欢西游记的同学肯定都知道悟空偷吃蟠桃的故事,你们一定都觉得这猴子太闹腾了,其实你们是有所不知:悟空是在研究一个数学问题!什么问题?他研究的问题是蟠桃一共有多少个!不过,到最…...
怎么看网站创建者是谁/帮别人发广告赚钱平台
TOM 科技讯美国东部时间 10 月 20 日(北京时间 10 月 21 日)据外电的最新报道,市场调研公司 IDC 最新的调查结果显示,Linux 操作系统用户是全球操作系统用户中最忠实的操作系统用户。一旦使用该操作系统之后,只有很少一部分 Linux 操作系统用…...
厦门企业做网站/seo做的比较好的公司
泛型概述Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。优缺点从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻…...