code:https://github.com/moby/moby/tree/v0.1.0代码结构 ├── archive.go ├── archive_test.go ├── auth # 主要用于服务器认证 │ ├── auth.go │ └── auth_test.go ├── AUTHORS ├── changes.go ├── commands.go ├── container.go ├── container_test.go ├── contrib # 一些脚本的源码 │ ├── install.sh │ ├── mkimage-busybox.sh │ └── README ├── deb # 系统打包源码 │ ├── debian │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ ├── copyright │ │ ├── docs │ │ ├── rules │ │ └── source │ │ └── format │ ├── etc │ │ ├── docker-dev.upstart │ │ └── docker.upstart │ ├── Makefile -> ../Makefile │ ├── Makefile.deb │ └── README.md -> ../README.md ├── docker # 实现docker核心功能 │ └── docker.go # 程序入口 ├── docs # 文档目录 │ ├── images-repositories-push-pull.md │ └── README.md ├── graph.go ├── graph_test.go ├── image.go ├── LICENSE ├── lxc_template.go ├── mount_darwin.go ├── mount.go ├── mount_linux.go ├── network.go ├── network_test.go ├── NOTICE ├── puppet # 基于puppet部署工具的相关源码 │ ├── manifests │ │ └── quantal64.pp │ └── modules │ └── docker │ ├── manifests │ │ └── init.pp │ └── templates │ ├── dockerd.conf │ └── profile ├── rcli # Remote Command-Line Interface 一个简单协议的命令行界面 │ ├── http.go │ ├── tcp.go │ └── types.go ├── README.md ├── registry.go ├── runtime.go ├── runtime_test.go ├── state.go ├── sysinit.go ├── tags.go ├── term # term的跨平台支持 │ ├── term.go │ ├── termios_darwin.go │ └── termios_linux.go ├── utils.go ├── utils_test.go └── Vagrantfile后面go.mod发展参考链接https://learn-docker-the-hard-way.readthedocs.io本文概述本文讲述笔者在学习docker过程中的心路历程，从docker的整体框架开始初识docker，之后开始阅读v0.1.0版本的docker源码，然后跟着教程用go实现了一个mini的docker，最后再去回看docker的源码，总结而言，是一个理论指导实践，实践加深理论的理论与实践的循环学习过程。初识dockerDocker is a set of platform as a service(PaaS) products that use OS-level virtualization to deliver software in packages called containers.这是wikipedia上对docker的定义，这句话高瞻远瞩地从宏观上告诉了我们docker是什么，确实很难找到比这更为准确的定义，但是，OS-level的虚拟化技术又是指什么，为什么要有docker，容器又有什么用呢？在接触docker之前一个相关的概念是虚拟机，它可以模拟完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。它与docker一样，都是一种虚拟化技术，不同点在于：Docker 属于 Linux 容器的一种封装，提供简单易用的容器使用接口。它并不是完整的操作系统，只是相当于在正常进程外套了壳。容器内的进程接触到的各种资源都是虚拟的，但本质上都是在调用底层系统，不过通过命名空间等技术实现了与底层系统的有效隔离。虚拟机通过在物理主机上运行一个完整的操作系统来实现虚拟化。每个虚拟机都有自己的操作系统内核、系统进程和设备驱动程序。简单而言，很多时候我们运行一些程序并不需要一个整个操作系统环境，只需要某些接口就能work,那从资源、从运行时间等角度考虑，就没必要模拟整个操作系统（虚拟机），只需要模拟一部分（容器）。我们已经明确了这个东西是有用的， 那怎么用呢？docker 有镜像（Image）、容器（Container）、仓库（Respository）三个基本概念。其实可以用代码库的概念来类比，仓库就如github、gitlab等公开或者私有的仓库，镜像就像是代码里的仓库，这是不变的，而容器就是我将仓库里的代码拉到的本地版本，可以本地修改，也可以通过某些操作将这种修改传递到仓库的镜像上。当然，镜像与容器更像是类与实例的关系。使用docker过程即，明确需求获取对应的仓库镜像，创建本地容器，根据业务的具体要求修改容器。那这又是如何实现的呢，这样一个虚拟化的技术，这样一个从镜像到容器的过程docker又是怎么做的呢？这个答案其实在docker的架构中都能找到答案，docker服从C/S架构。Clinet即用户与Docker Daemon通信的客户端，即命令行终端，其包装命令发送api请求。而Docker的服务端是送耦合的结构，各模块各司其职，有机组合。其中daemon是常驻后台运行的进程，接受客户端请求并管理docker容器，engine是真正处理客户端请求的后端请求。具体为：docker client和daemon建立通信，client发送请求给daemondaemon作为主体部分，提供server功能，能让其接受client的请求engine执行处理内部的一系列工作每个工作是一个job形式存在，需要镜像时，从docker registry中下载，通过graphdrive镜像管理驱动下载镜像并存储（Graph形式）。networkdrive负责创建配置容器的网络运行用户指令或者限制容器资源时，通过execdrive完成execdrive以及networkdrive通过libcontainer具体实现从镜像到容器：docker run命令发生了什么？(1) Docker Client接受docker run命令, 发送http请求给到server; (2)Docker Server接受以上HTTP请求，并交给mux.Router，mux.Router通过URL以及请求方法来确定执行该请求的具体handler; (3)mux.Router将请求路由分发至相应的handler，具体为PostContainersCreate；(4) PostContainersCreate中create job被创建 (5)create job调用graphdriver并将rootfs下镜像加载到docker容器对应位置 (6) 上述过程无误，则类似上述过程创建start job并交由networkdrive来实现，制止创建容器并最终执行用户要求启动的命令。详见参考连结可以发现，本质上就是client与server的通信，是serve内部组件的通信，接收到client指令，server负责根据命令分发至对应handle, 对应handle创建相应job, 而engine则根据不同的job调用不同的后端组件来实现job, 实现job后再将结果返回到client。容器的OS-level虚拟化支持主要是由libcontainer提供的， 它是Docker架构中一个使用Go语言设计实现的库，设计初衷是希望该库可以不依靠任何依赖，直接访问内核中与容器相关的API。正是由于libcontainer的存在，Docker可以直接调用libcontainer，而最终操纵容器的namespace、cgroups、apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖LXC或者其他包。从docker v0.1.0开始已经基本了解了docker的架构和它的一些模块，那接下来想要深入了解它最好的方法自然是阅读源码，当前github上moby仓库目前的版本代码量极大，并不容易阅读，于是从docker的v0.1.0版本代码开始，代码文件并不多，且已经具备了docker的核心功能。入口函数位于/docker/docker.go// ./docker/docker.go func main() { if docker.SelfPath() == "/sbin/init" { // Running in init mode docker.SysInit() return } // FIXME: Switch d and D ? (to be more sshd like) fl_daemon := flag.Bool("d",false, "Daemon mode") fl_debug := flag.Bool("D",false, "Debug mode") flag.Parse() rcli.DEBUG_FLAG = *fl_debug if *fl_daemon { if flag.NArg() != 0 { flag.Usage() return } if err := daemon(); err !=nil { log.Fatal(err) } }else { if err := runCommand(flag.Args()); err !=nil { log.Fatal(err) } } } 可以看到，代码运行首先判断docker可执行文件的绝对路径是否在/sbin/init目录下，如果在，则设置docker容器启动之前的环境 ，进行初始化。如果不存在则根据参入的命令行参数：去选择是启动docker deamon 还是执行 docker cli 的命令调用。SysInit系统的初始化设置位于/sysinit.go//sysinit.go func SysInit() { iflen(os.Args) <= 1 { fmt.Println("You should not invoke docker-init manually") os.Exit(1) } var u = flag.String("u", "", "username or uid") var gw = flag.String("g", "", "gateway address") flag.Parse() setupNetworking(*gw) changeUser(*u) executeProgram(flag.Arg(0), flag.Args()) } 其中包含网络设置：根据参数中的网关ip添加网关路由用户设置：根据参数中的username或uid，通过系统调用设置uid和gid启动docker程序：启动docker程序，根据命令行参数决定启动docker deamon还是docker cli的命令daemon如果输出参数为-d 则启动daemon// ./docker/docker.go func daemon()error { // NewServer在commands.go中 service, err := docker.NewServer() if err !=nil { return err } return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) }启动daemon是在创建server对下岗，然后通过该对象启用tcp服务。而创建server其实就是在创建运行时对象// command func NewServer() (*Server,error) { rand.Seed(time.Now().UTC().UnixNano()) if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } runtime, err := NewRuntime() if err !=nil { returnnil, err } srv := &Server{ runtime: runtime, } return srv,nil }在创建runtime时，首先会在 /var/lib/docker目录下创建对应的文件：containers，graph文件夹，然后创建对应的镜像tag存储对象，通过名为lxcbr0的卡的网络创建网络管理，最后创建dockerhub的认证对象AuthConfig,详见代码注释。func NewRuntime() (*Runtime,error) { return NewRuntimeFromDirectory("/var/lib/docker") } func NewRuntimeFromDirectory(rootstring) (*Runtime,error) { runtime_repo := path.Join(root, "containers") // 创建/var/lib/docker/containers目录 if err := os.MkdirAll(runtime_repo, 0700); err !=nil && !os.IsExist(err) { returnnil, err } // 创建/var/lib/docker/graph目录，同时创建Graph对象 g, err := NewGraph(path.Join(root, "graph")) if err !=nil { returnnil, err } // 创建var/lib/docker/repositories目录，同时创建TagStore对象 repositories, err := NewTagStore(path.Join(root, "repositories"), g) if err !=nil { returnnil, fmt.Errorf("Couldn't create Tag store: %s", err) } // 通过名为lxcbr0的卡的网络创建网络管理 netManager, err := newNetworkManager(networkBridgeIface) if err !=nil { returnnil, err } // 读取认证文件 authConfig, err := auth.LoadConfig(root) if err !=nil && authConfig ==nil { // If the auth file does not exist, keep going returnnil, err } // 创建runtime对象 runtime := &Runtime{ root: root, // /var/lib/docker repository: runtime_repo, // /var/lib/docker/containers containers: list.New(), // container/list(list.New())双向链表 networkManager: netManager, // NetworkManager graph: g, // Graph repositories: repositories, // TagStore authConfig: authConfig, // AuthConfig } // 读取/var/lib/docker/containers目录，实际就是所有之前运行过的容器的目录 // 检查配置中的id和所加载的容器id是否一样，以此判断容器信息是否被更改过 if err := runtime.restore(); err !=nil { returnnil, err } return runtime,nil }创建好serve后 配置tcp服务端// ./rcli/tcp.go func ListenAndServe(proto, addrstring, service Service)error { // 创建监听器 listener, err := net.Listen(proto, addr) if err !=nil { return err } log.Printf("Listening for RCLI/%s on %s\n", proto, addr) defer listener.Close() for { // 接受tcp请求 if conn, err := listener.Accept(); err !=nil { return err }else { gofunc() { if DEBUG_FLAG { CLIENT_SOCKET = conn } // 处理请求 if err := Serve(conn, service); err !=nil { log.Printf("Error: " + err.Error() + "\n") fmt.Fprintf(conn, "Error: "+err.Error()+"\n") } conn.Close() }() } } returnnil }请求处理过程于/rcli/type.go LocalCall()所示， 具体为获取请求中的参数然后调用call，call根据参数是否有值来执行不同方法，如果没有参数，则执行runtime的help方法；如果有参数，进行参数的处理，处理逻辑：获取第二个参数，作为docker后的命令，然后获取命令之后的所有参数，整条命令进行日志打印输出，之后再通过cmd命令和反射技术找到对应的cmd所对应的方法，最后将参数传入方法，执行cmd对应的方法，结果返回connect中。connect在此作为io.Writer类型参数，命令结果将写入到其中。runCommandrunCommand即客户端模式，详见代码注释func runCommand(args []string)error { var oldState *term.State var errerror // 检查标准输入模式，并确保环境变量NOARW非空 if term.IsTerminal(0) && os.Getenv("NORAW") == "" { oldState, err = term.MakeRaw(0) if err !=nil { return err } defer term.Restore(0, oldState) } // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose // CloseWrite(), which we need to cleanly signal that stdin is closed without // closing the connection. // See http://code.google.com/p/go/issues/detail?id=3345 // TCP连接服务端并传递参数 if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err ==nil { // 启动一个goroutine从连接中接收stdout并写入os.Stdout receive_stdout := docker.Go(func()error { _, err := io.Copy(os.Stdout, conn) return err }) // 启动一个goroutine将os.Stdin的数据发送到连接中 send_stdin := docker.Go(func()error { _, err := io.Copy(conn, os.Stdin) if err := conn.CloseWrite(); err !=nil { log.Printf("Couldn't send EOF: " + err.Error()) } return err }) if err := <-receive_stdout; err !=nil { return err } if !term.IsTerminal(0) { if err := <-send_stdin; err !=nil { return err } } }else { // 如果连接失败，创建一个本地的docker服务器 service, err := docker.NewServer() if err !=nil { return err } // 使用本地docker服务器调用rcli.LocalCall来执行命令，将标准输入和标准输出传递给docker服务器 if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err !=nil { return err } } if oldState !=nil { term.Restore(0, oldState) } returnnil } 本章总结从源码可以发现，docker v0.1.0完全基于lxc来实现。其初始化阶段先实现切换用户全线，添加网桥默认路由等功能，而后创建server端与client，server端通过docker daemon进行管理，将相关容器数据加载到runtime对象中，创建服务监听client端请求，并根据对应请求调用相应模块进行处理。动手实现minidocker纸上得来终觉浅，绝知此事要躬行。了解docker最好的当然是自己动手写一个。主要参考教程实现docker的虚拟化功能，具体包括namspace、cgroups、文件系统以及网络配置。实验环境Linux version 6.1.55-1-MANJARO + go1.21.2 linux/amd64命名空间 cmd := exec.Command("/bin/zsh") cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC, } cmd.Env = os.Environ() cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err !=nil { log.Fatal(err) }启动一个新的shell并通过syscall.SysProAttr来配置进程属性，创建新的命名空间：CLONE_NEWUTS：隔离主机名和域名等系统标识。具体表现为：容器内更改hostname 主机名不受影响 hostname newnameCLONE_NEWPID：隔离进程 ID。具体表现为：容器内echo $$查看当前pid号,从1开始。但是pd -ef仍能看见主机内进程：解决方案： mount -t proc proc /proc top确实看不到了 但是父节点挂了被卸载掉了先mount —make-rprivate / 再mount -t proc procCLONE_NEWNS：新的 Mount 命名空间，用于隔离文件系统挂载点。CLONE_NEWNET：新的网络命名空间，用于隔离网络设备。具体表现为 route -n 没有ip没有路由表都需要重新配置CLONE_NEWIPC：新的 IPC 命名空间，用于隔离进程间通信资源。具体表现为 主机ipcmk -Q创建消息队列 容器内ipcs无法看到通过li nu x 的命令进行实验可以发现确实达到了各种隔离，且提高了对命名空间的理解。但是当前仍然存在问题，即使用了主机内的文件系统，pwd仍能看到主机。文件系统下载文件系统https://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/并进行挂载。switch os.Args[1] { case "run": fmt.Println("run mode: run pid", os.Getpid(), "ppid", os.Getppid()) initCmd, err := os.Readlink("/proc/self/exe") //读取当前进程可执行文件的路径 if err !=nil { fmt.Println("get init process error", err) return } os.Args[1] = "init" cmd := exec.Command(initCmd, os.Args[1:]...) cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC, } cmd.Env = os.Environ() cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err !=nil { log.Fatal(err) } return case "init": fmt.Println("init mode: run pid", os.Getpid(), "ppid", os.Getppid()) pwd, err := os.Getwd() fmt.Println("pwd:", pwd) if err !=nil { fmt.Println("pwd", err) return } path := pwd + "/ubuntu" syscall.Mount("", "/", "", syscall.MS_BIND|syscall.MS_REC, "") if err := syscall.Mount(path, path, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err !=nil { fmt.Println("Mount", err) return } if err := os.MkdirAll(path+"/.old", 0700); err !=nil { fmt.Println("mkdir", err) return } //syscall.PivotRoot会报错invalid argument 可以先执行 unshare -m命令，然后将 ubuntu/.old 文件夹删除 //原因是systemd会将 fs 修改为 shared，pivot root 不允许 parent mount point和 new mount point 是 shared。 //参考：https://www.retainblog.top/2022/10/26/%E4%BD%BF%E7%94%A8Golang%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%B7%B1%E7%9A%84Docker%EF%BC%88%E4%BA%8C%EF%BC%89/ err = syscall.PivotRoot(path, path+"/.old") if err !=nil { fmt.Println("pivot root", err) return } //syscall.Chroot("./ubuntu-base-16.04.6-base-amd64") //Chroot切换进程文件系统 进程的根文件系统变化 但是进程的命名空间无变化 syscall.Chdir("/") defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV syscall.Mount("proc", "/proc", "proc",uintptr(defaultMountFlags), "") cmd := os.Args[2] err = syscall.Exec(cmd, os.Args[2:], os.Environ()) if err !=nil { fmt.Println("exec proc fail", err) return } fmt.Println("forever exec it") return } fmt.Println("hello world") }增加init模式配置容器，此外运行多个容器进程会使用同一份根文件系统目录 容器的修改会改变其他 使用联和文件系统， 运行完在/root/mnt会出现两个容器空间， 运行完自动销毁。网络配置容器内部网络无法访问互联网，通过网桥实现。具体的linux内部配置为，minidocker即通过调用系统接口的该配置的代码实现。# 允许防火墙 路由转发 iptables -A FORWARD -j ACCEPT # 内核允许路由转发， 修改值为1 sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward' # 创建网桥 brctl addbr br0 # 设置网桥开启状态 sudo ip linkset br0 up #开启后通过ifconfig可以看到网桥 但是没有ip地址 # 为网桥分配ip地址 ip addr add 192.168.15.6/24 dev br0 # 创建veth设备 veth设备一般成对出现 sudo ip link add veth-red type veth peer name veth-red-br sudo ip link add veth-bule type veth peer name veth-blue-br # 开启主机上的veth网卡 sudo ip linkset veth-red-br up sudo ip linkset veth-blue-br up # ifconfig可见 # 将veth-red放到red namespace里 进程的网络命名空间 先启动进程查看进程pid sudo ip linkset veth-red netns 进程id sudo ip linkset veth-blue netns 进程id # veth设备一端链接到网桥上 sudo ip linkset veth-red-br master br0 sudo ip linkset veth-blue-br master br0 # 为veth设备添加ip 容器内 sudo ip linkset veth-red up sudo ip addr add 192.168.15.5/24 dev veth-red # 设置后ifconfig可以看到veth-red route -n 路由表也配置上了192.168.15.0网段 sudo ip linkset veth-blue up sudo ip addr add 192.168.14.7/24 dev veth-blue # 现在两个容器可以访问 但是无法访问外网，需要通过网桥转发到eth0网卡 # 添加网关路由 容器内 ip route add defaule via 192.168.15.6 dev veth-red ip route add default via 192.168.15.6 dev veth-blue # 但是网络包出去后，回来后还需要 # 防火墙nat设置 iptables -t nat -A POSTROUTING -s 192.168.15.0/24 -j MASQUEREADcgroups之前通过命名空间达到资源隔离，但是这种隔离无法对硬件资源的使用隔离，cgroups实现对cpu使用率进行限制。内核基本已经完成，只需要调用相关接口就行了。cd /sys/fs/cgroup/ # 文件夹下包含cpu memory等 为cgroup的子系统 # cpu.cfs_period_us代表了cpu运行一个周期的时长 # 修改memory_limit_in_bytes文件可以设置程序的限制内存大小cgroups的实现即通过系统接口实现对容器内部资源管理文件的修改。再探docker源码可以发现docker v0.1.0版本基本实现来功能，但其实还是不少问题，或者说功能点的欠缺。如采用的是aufs文件系统，而其在2.6.32 Linux内核中并不完全支持。后续的版本主要围绕其隔离程度、安全性、稳定性、功能等进行补充优化。比如：从LXC转向自有的libcontainer：后续版本中，Docker从依赖LXC转向了自己的容器运行时接口libcontainer（现在已经演化为containerd），这改善了安全性和可移植性。安全性增强：引入了更多的安全特性，比如AppArmor、SELinux策略支持，以及后来的用户命名空间，这些都增加了容器的隔离性和安全性。网络和存储驱动：引入可插拔的网络和存储驱动，支持多种网络配置和持久化存储选项。Orchestration and Scaling：引入了Docker Compose, Docker Swarm等工具，为容器编排和扩展提供支持。界面和API：改进了命令行界面（CLI），增加了REST API，为自动化和集成提供了更强的支持。开放和标准化：Docker开始参与并推动开放容器标准，例如Open Container Initiative (OCI)。问题可以发现，docker本质上还是基于linux的namespace和cgroup等接口实现的，但是也有docker for windows， 它是如何实现的呢。可以发现两者结构很类似。与 Linux 类似，Windows 也新抽象出来了 CGroup 和 Namespace 的概念，并提供出一个新的抽象层次 Compute Service，即宿主机运算服务（Host Compute Service，hcs）。相较于底层可能经常重构的实现细节，hcs 旨在为外部（比如 Docker 引擎）提供较稳定的操作接口。所以，本质上就是通过加抽象来解决问题。总结Docker is a set of platform as a service(PaaS) products that use OS-level virtualization to deliver software in packages called containers.再去回看wikipad对docker的定义，说的确实不错，容器即隔离，docker是云原生时代必不可少的一项产品，上云需要虚拟化，而虚拟化需要docker，这种轻量的、可扩展的容器技术能够有效避免重复造轮子，能够极大了改善传统的环境配置问题，让软件服务能够即插即用。参考链接https://github.com/moby/mobyhttp://en.wikipedia.org/wiki/Docker_(software))https://learn-docker-the-hard-way.readthedocs.io/zh-cn/latest/Part1/1_docker.go/https://zhuanlan.zhihu.com/p/25773225https://zhuanlan.zhihu.com/p/551753838https://www.kancloud.cn/infoq/docker-source-code-analysis/80525https://www.imooc.com/article/335433vohttps://www.yzktw.com.cn/post/1281202.htmlhttps://insights.thoughtworks.cn/can-i-use-docker-on-windows/
感觉每天要看的东西有点多，而且这些内容分散在各个网站、公众号。要花时间访问多个应用来获取资讯属实是有点麻烦，那不妨用RSS来提升体验。先找个合适的RSS的阅读器，主要需求是PC端和手机端能够同步，Feedly是挺好的，不过国内用起来应该不太舒服，除非一直挂着代理。综合考虑，最后PC端选择了Fluent-reader，手机端选择了Fluent-reader-lite。直接从github上下Release版本就行。https://github.com/yang991178/fluent-readerhttps://github.com/yang991178/fluent-reader-lite因为Fluent-reader-lite支持fever api，为了实现同步服务，就需要搭建一个rss订阅工具。由于Miniflux的配置较为简单，就选了它。配置Miniflux先配置个postgres数据库# 创建数据库存储目录 sudo mkdir -p /data/postgresql_data sudo docker run -d --name postgres --restart=always --net=host \ -e POSTGRES_USER=dbadmin \ -e POSTGRES_PASSWORD=123456 \ -e POSTGRES_DB=miniflux \ -p 5432:5432 \ -v /data/postgresql_data:/var/lib/postgresql/data postgres:13然后安装一下miniflux# 改个BASE_URL就行 sudo docker run -d --name miniflux --restart=always --net=host \ -e DATABASE_URL=postgres://dbadmin:souk5ping8CLULwhik@127.0.0.1/miniflux?sslmode=disable \ -e RUN_MIGRATIONS=1 \ -e CREATE_ADMIN=1 \ -e ADMIN_USERNAME=admin \ -e ADMIN_PASSWORD=123456 \ -e POLLING_FREQUENCY=30 \ -e BASE_URL=http://47.118.38.242:8080 \ -p 8080:8080 miniflux/miniflux然后访问 IP:8080就行了，如果是云服务记得把云平台的端口打开。如果要域名访问的话，再配个nginx。打开设置里的"集成"，启用Fever API，在手机和PC上配置一下，就完成了，接下来就是添加自己感兴趣的RSS源。由于很多网站本身是不支持RSS源的，可以用RSSHUB，它写好了一些解析规则。但是国内访问rsshub也不太方便，也是就自建一个rsshub的服务。sudo docker run -d --name rsshub --restart=always -p 1200:1200 -e CACHE_EXPIRE=600 diygod/rsshub创建好后，直接进入服务在RSSHUB（https://docs.rsshub.app）上找感兴趣的rss源，把前缀（https://rsshub.app）替换为http://域名：端口就行了，域名和端口均为云上RSS服务的。还有个浏览器插件RSSHUB-Rader（https://github.com/DIYgod/RSSHub-Radar）也顺便装了一下，对于用户访问的网页，它可以自动生成rss参考：https://www.wolai.com/7n4LGrG96sJmCrwJU1tCdj
风险管理与金融衍生物风险风险：结果的不确定性金融衍生物（financial derivatives）是一种风险管理的工具，它的价值依赖于其他更基本的原生资产（标的资产）（underlying assets）的变化而变化。三种最基本的金融衍生工具：远期合约（forward contracts）,期货（futures）和期权（options）。如果原生资产是股票，债券，汇率...，则有股票期货（期权），债券期货（期权），货币期货（期权）......远期合约远期合约：在未来确定时间内，以确定价格购（销）一定数量和质量的某原生资产的协议。合约的购入方为多头（long position），销售方为空头(short position)。合约中标明的确定价格和确定日期为交割价（delivery price）和交割日（maturity）远期合约在交割日的收益 $$ V_T=S_T-K（多头方）\\ V_T=K-S_T（空头方） $$其中K为交割价格，T为交易日，V表示收益，$S_T$表示原生资产在T日的价格远期合约一般都在场外交易（over-the-counter,OTC）期货期货跟远程合约相同，但是它是由远期合约逐步标准化而形成的，它们之间的区别在于期货交易通常在交易所期货合约具有标准化条约期货合约上的交割价格通常是由场内交易决定的，它依赖于供求关系期权期权：持有人在确定时间，按确定价格向出售方购（销）一定数量和质量的原生资产的协议，但他不承担必须购入（销售）的义务。期权持有人具有实施协议的权利，但没有必须实施的义务。确定价称为实施价格（exercise price）或敲定价格（strike price），确定日期称为到期日（expiry date），按期权合约规定执行或销售原生资产称为实施（exercise）.期权按合约中购入和销售原生资产来划分：看涨期权（call option）:一张在确定时间，按确定价格有权购入一定数量和质量的原生资产的合约。看跌期权（put option）：一张在确定时间，按确定价格有权出售一定数量和质量的原生资产的合约。期权按合约中有关实施的条约来划分：欧式期权（European options）：只能在合约规定的到期日实施美式期权（American options）：能在合约规定的到期日以前（包括到期日）的任何一个工作日实施。 $$ V_T=(S_T-K)^+ \quad(call \quad option)\\ V_T=(K-S_T)^+ \quad(put \quad option) $$期权是一种未定权益（contingent claim）。为取得这个权益需要付出的代价称为期权金（premium），因此持有人总收益P为 $$ P_T=(S_T-K)^+-p \quad(call \quad option)\\ P_T=(K-S_T)^+-p \quad(put \quad option) $$期权定价期权定价问题即为求$V=V(S,t),(0\le S < \infty,0\le t\le T)$,使得 $$ V(S,T)=\left\{ \begin{gathered} (S-K)^+ \quad(call \quad option)\\ (K-S)^+ \quad(put \quad option) \end{gathered} \right. $$t时刻原生资产价格为$S_t$，期权价格为$V_t$交易者的类型套期保值者（hedger）:两面下注避免损失。投机者（speculator）：甘愿用资金去冒险，不断其买进卖出衍生证券（期货、期权），希望从市场价格的经常变化中获取利润的行为。投资与期权与直接投资于原生资产相比具有高回报、高风险的特点；其原因在于：期权投资的杠杆作用（leverage）很大,投资人通过投入少量资金（支付期权金），而实际进行的是比它大数倍的原生资产的投资。套利者（arbitrageur）：基于对同一类风险资产的观察，利用市场价格的差异，在不同的市场同时进行交易，获取瞬时无风险利润。套利与投机不同；投机是基于对未来价格水平的预测，以牟取利润，这是有风险的。套利是利用不同市场在价格联系上的差异的现实，以谋取利润，这是无风险的。Black-Scholes 模型为了对期权进行定价，需要构建一个由金融衍生品和标的资产组成的无风险套利组合。在无套利的条件下，交易组合的收益率等于无风险收益率$r_f$。当前讨论的期权以股票作为标的资产，并假设其价格遵循几何布朗运动。$$ dS(t) = \mu S(t)dt +\sigma S(t)dW(t) $$其中$\mu$是预计收益率，即$r_f$,$\sigma$表示股票价格的波动率，其中$\mu$和$\sigma$都是常量。$dW(t)$受布朗运动影响，服从$dW(t) = \varepsilon \sqrt{dt}$，$\varepsilon$是一个受标准正态分布影响的随机变量。令$c=c(t,S(t))$表示标的资产看涨期权的价格，根据伊藤定理可以得到$$ dc = \frac{\partial c}{\partial t}dt + \frac{\partial c}{\partial S}dS(t)+ \frac{1}{2}\frac{\partial^2 c}{\partial S^2}(dS(t))^2 \\ dc = (\frac{\partial c}{\partial t} + \frac{\partial c}{\partial S}\mu S+ \frac{1}{2}\sigma^2S^2\frac{\partial^2 c}{\partial S^2})dt+\frac{\partial c}{\partial S}\sigma SdW(t) $$为了实现无风险组合，因此构造$\Delta t$份标的股票多头和一份期权空头 ，并且由于其是无风险的，所以有$$ V(t) = \Delta_t S(t) - c(t,S(t)) \\ dc(t,S(t)) - \Delta_t dS(t) = r(c(t,S_t) - \Delta_t S(t)) dt $$代入上式可以得到$$ r(c(t,S_t) - \Delta S)dt = (\Delta_t\mu S(t)-\frac{\partial c}{\partial t} - \frac{\partial c}{\partial S}\mu S- \frac{1}{2}\sigma^2S^2\frac{\partial^2 c}{\partial S^2})dt+(\Delta_t\sigma S - \frac{\partial c}{\partial S}\sigma S)dW(t) $$因为等式左端是无风险的，因此右端的随机项$dW_t$系数为0，则有$\Delta_t = \frac{\partial c}{\partial S}$，回代可得到BS方程$$ \frac{\partial c}{\partial t} +rS\frac{\partial c}{\partial S}+\frac{1}{2}\sigma^2S^2\frac{\partial^2 S}{\partial S^2}-rc =0 $$针对以上推导出的期权定价偏微分方程，可以继续推导得到Black-Scholes公式，未期权定价的闭合公式。也可以应用传统的数值方法进行进行求解。Heston模型Heston模型在BS模型的基础上引入了随机波动率的模型，其认为标的资产的波动并非为常数，而是一个符合布朗运动的随机过程。$$ dS = \mu dt+\sqrt{v_t}dW_{t1} \\ dv_t = \kappa(\theta-v_t)dt + \sigma \sqrt{v_t}dW_{t_2} \\ dW_{t_1}\cdot dW_{t_2} = \rho d_t $$对于Heston模型的求解可以采用有限差分法、有限元法等数值方法。但对于高维资产情形的期权定价，偏微分方程的计算时间和记忆容量随着资产数量的增加呈指数级增长，使得这些数值方法并未表现出特别的优势。蒙特卡罗模拟的方法可以有效对高维资产情形下的期权进行定价。尽管蒙特卡罗方法能够最终有效地逼近理论值，然而，随着资产数量的增加，其所需时间也急剧增加，这也限制了它在高维情景中的应用。
服务端与客户端启动# 启动服务端 redis-server.exe # 启动本地客户端 redis-cli.exe # 启动远程客户端 redis-cli.exe -h host -p port -a passwordKey# 创建key SET key value # 删除key DEL key # 序列化key DUMP key # 检查key是否存在 EXISTS key # 设置key过期时间 秒|过期时间戳（秒）|毫秒为单位|过期时间戳（毫秒） [EXPIPE|EXPIPEAT|PEXPIRE|PEXPIPEAT] key [seconds|timestamp|milliseconds|milliseconds-timestamp] # 查找所有符合给定模式（pattern）的key e.g. [KEYS test] [KEYS *] KEYS pattern # 将当前db的key移动到给定db MOVE key db # 移除key过期时间 PERSIST key # 返回key的过期时间 毫秒|秒 [PTTL|TTL] key # 从当前db随机返回一个key RANDOMKEY # 修改key名称 如果存在newkey 则会覆盖 RENAME key newkey # 仅当newkey不存在时，将key改名为newkey RENAMENX key newkey # 迭代数据库中的数据库键 SCAN cursor [MATCH pattern][COUNT count] # 返回key所存储的值的类型 TYPE key 基本数据结构String、Hash、List、Set、ZSet(Sorted Set)、HyperLogLog(Redis 2.8.9+，用于基数（不重复元素个数）统计)、GEO(Redis 3.2+，存储地理位置信息)、Stream(Redis 5.0+，主要用于消息队列，实现消息的持久化和主备复制功能)发布订阅发布订阅可以分发消息，但是无法记录历史消息，无法消息持久化。如果网络端口、Redis宕机，则消息被丢弃# 订阅客户端 SUBSCRIBE channel [channel ...] # 发布客户端 PUBLISH channel message事务# 开始事务 MULTI # 执行所有事务块 （命令一般放在MULTI和EXEC中间） EXEC # 取消事务 DISCARD # 监视一个或多个key，如果key在事务执行前改动，则事务被打断 WATCH key [key...] # 取消对所有key的监视 UNWATCH其它# 执行lua脚本 EVAL script numkeys key [key ...] arg [arg ...] # 验证密码 AUTH password # 切换指定数据 SELECT index # 查看服务运行状态 PING # 获取服务端信息 INFO # 数据备份 在redis安装目录创建dump.rdb SAVE # CONFIG可以获取redis目录，数据恢复，将dump.rdb移动到redis安装目录并启动服务即可 CONFIG GET dir # 创建redis备份文件 BGSAVE # 设置密码 CONFIG set requirepass "password" # 性能测试 redis-benchmark [option] [option value]相关基本数据结构：String、Hash、List、Set、ZSet(Sorted Set)、HyperLogLog(Redis 2.8.9+，用于基数（不重复元素个数）统计)、GEO(Redis 3.2+，存储地理位置信息)、Stream(Redis 5.0+，主要用于消息队列，实现消息的持久化和主备复制功能)使用场景：计数器：利用redis中原子性的自增操作；缓存：减轻mysql压力；排行榜：利用zset实现；消息队列；会话缓存持久化：RDB持久化保存某个时间点的全部数据（只有一个文件便于保存，但是中间redis出故障会导致数据丢失）；AOF持久化记录所有变更数据库状态的指令，以append的形式追加保存到AOF文件中（即便中途出故障，服务器当机，也可以通过redis-check-aof来解决数据一致性问题，数据量过大启动效率低）。哨兵模式：哨兵至少需要三个实例来保持自己的健壮性。集群监控：负责监控 redis master 和 slave 进程是否正常工作。消息通知：如果某个 redis 实例有故障，那么哨兵负责发送消息作为报警通知给管理员。故障转移：如果 master node 挂掉了，会自动转移到 slave node 上。配置中心：如果故障转移发生了，通知 client 客户端新的 master 地址。缓存穿透：缓存穿透指缓存和数据库都没有需要查询的数据，攻击者不断发送这种需要，导致数据库压力过大。解决方案：接口层增加数据校验，如上述案例，对id进行基础校验，对id为-1的请求进行拦截。从缓存和数据库都得不到的数据，在缓存中添加空数据，即key-value存放空的结构体。对并发读数据设置并发锁，降低并发性。缓存雪崩：缓存雪崩指的是缓存中有一大批数据到达过期时间而被删除，但是这批数据量巨大，查询全部走数据库，造成数据库压力过大。解决方案：缓存数据过期时间随机设置，防止同一时间大量数据过期现象发生。如果缓存数据库是分布式部署，将热点数据均匀分布在不同的缓存中设置热点数据永久不过期缓存击穿：缓存击穿指的是缓存中没有但是数据库中存在的数据，这时候由于并发用户特别多，同时都缓存没有读到数据，又同时去数据库中获取数据，引起数据库压力瞬间变大.解决方案：添加互斥锁。在并发的多请求中，只有一个线程能拿到锁并执行数据库查询操作。热点数据永不过期。
EmotionLinepaper：https://arxiv.org/pdf/1802.08379v2.pdfdataset：http://doraemon.iis.sinica.edu.tw/emotionlines/download.html需要填表申请数据集，基于对话标注的情绪，来自2000个对话的29245个标记的话。含有八类anger, disgust, fear, happiness, sadness, surprise, neutral, and non-neutral.（non-neutral表示含有两种及以上不同的情绪）ISEARpaper：https://doi.org/10.1037/0022-3514.67.1.55dataset:https://www.unige.ch/cisa/research/materials-and-online-research/research-material/有来自五大洲37个个国家的3000名受访者的数据，基于问卷采集的数据，含7类情感1 = JOY, 2 = FEAR, 3 = ANGER, 4 = SADNESS, 5 = DISGUST, 6 = SHAME, 7 = GUILTKaggle上有处理好的数据集 https://www.kaggle.com/datasets/faisalsanto007/isear-datasetSILICONEpaper：https://arxiv.org/pdf/2009.11152v3.pdfdataset:https://huggingface.co/datasets/silicone整合了各领域的口语场景，英文数据集，部分数据集包含情绪标签，其情绪分析数据集子集的数据格式{ 'Utterance': "'oh , breaktime flies .'", 'Emotion': 5, # "sadness" 'Dialogue_ID': "997" }GoEmotionspaper：https://arxiv.org/pdf/2005.00547v2.pdfdataset:https://github.com/google-research/google-research/tree/master/goemotions2020年google开源的数据集，有58009句话，27个label，最长序列长度为30情绪标签为：admiration, amusement, anger, annoyance, approval, caring, confusion, curiosity, desire, disappointment, disapproval, disgust, embarrassment, excitement, fear, gratitude, grief, joy, love, nervousness, optimism, pride, realization, relief, remorse, sadness, surprise.huggingface上也有https://huggingface.co/datasets/go_emotionsEmotion Datasetpaper:https://www.aclweb.org/anthology/D18-1404/dataset:https://github.com/dair-ai/emotion_dataset推特上的评论，含有6类情绪
在使用anaconda创建环境时出现错误Solving environment: failed CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/noarch/repodata.json> Elapsed: - An HTTP error occurred when trying to retrieve this URL. HTTP errors are often intermittent, and a simple retry will get you on your way. SSLError(MaxRetryError('HTTPSConnectionPool(host=\'mirrors.tuna.tsinghua.edu.cn\', port=443): Max retries exceeded with url: /anaconda/pkgs/free/noarch/repodata.json (Caused by SSLError(SSLError("bad handshake: Error([(\'SSL routines\', \'ssl3_get_server_certificate\', \'certificate verify failed\')])")))')) 尝试重新配置源# 重置源配置 conda config --remove-key channels # 重新添加清华源 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ conda config --set show_channel_urls yes # 查看效果 cat ~/.condarc没什么用为防火墙的问题，通过如下命令解决conda config --set ssl_verify false
Hadoop简介核心组件：Hadoop HDFS（分布式文件存储系统）：解决海量数据存储Hadoop YARN（集群资源管理和任务调度框架）：解决资源任务调度Hadoop MapReduce（分布式计算框架）：解决海量数据计算特性：扩容能力：集群可扩展到数以千计的节点成本低：运行通过普通机器组成集群效率高：并发处理数据可靠性：自动维护数据的多份复制，并在任务失败后能自动重新部署Hadoop 集群Hadoop集群包含两个集群，两个集群都是主从架构集群，逻辑上分类、物理上在一起：HDFS集群（分布式存储）：NameNode+DataNode+SecondaryNameNodeYARN集群（资源管理、调度）：ResourceManger+NodeManagerHadoop源码编译（或者可以直接下载jar包）：安装依赖yum install gcc gcc-c++ make autoconf automake libtool curl lzo-devel zlib-devel openssl openssl-devel ncurses-devel snappy snappy-devel bzip2 bzip2-devel lzo lzo-devel lzop libXtst zlib -y yum install -y doxygen cyrus-sasl* saslwrapper-devel*安装cmake、snappy，需要版本支持安装JDK1.8并配置环境变量安装配置maven，配置环境变量，配置阿里云仓库安装ProtocolBuffer 3.7.1编译Hadooptar zxvf hadoop-3.3.0-src.tar.gz cd /root/hadoop-3.3.0-src mvn clean package -Pdist,native -DskipTests -Dtar -Dbundle.snappy -Dsnappy.lib=/usr/local/libHadoop集群分布式安装：集群规划，主机角色安排基础环境配置，配置各台设备hosts，同步集群时间，防火墙关闭，ssh免密登录，JDK1.8环境解压hadoop包（这里直接采用官网的包，也可以采用上面的编译源码实现）tar zxvf hadoop-3.3.0-Centos7-64-with-snappy.tar.gz修改配置文件hadoop-env.sh#文件最后添加 export JAVA_HOME=/export/server/jdk1.8.0_241 export HDFS_NAMENODE_USER=root export HDFS_DATANODE_USER=root export HDFS_SECONDARYNAMENODE_USER=root export YARN_RESOURCEMANAGER_USER=root export YARN_NODEMANAGER_USER=root core-site.xml：集群全局参数hdfs-site.xml：HDFS参数mapred-site.xml：MapReduce参数yarn-site.xml：YARN参数分发同步hadoop安装包，在一台机器上配置好分发到集群其它机器cd /export/server scp -r hadoop-3.3.0 root@node2:$PWD scp -r hadoop-3.3.0 root@node3:$PWD添加Hadoop到环境变量Hadoop集群启动首次启动需要格式化namenodehdfs namenode -format脚本启动与关闭#HDFS集群 start-dfs.sh stop-dfs.sh #YARN集群 start-yarn.sh stop-yarn.sh #Hadoop集群 start-all.sh stop-all.shWeb UI PageHDFS集群：http://node1:9870/YARN集群：http://node1:8088/Hadoop HDFSHDFS应用场景：适合场景：大文件、数据流式访问、一次写入多次读出、低成本部署，廉价PC、高容错不适合场景：小文件、数据交互式访问、频繁任意修改、低延迟处理HDFS整体概述（重要特性）：主从架构：Namenode是HDFS主节点，存储元数据，DataNode从节点，存储数据分块存储：文件在物理上是分块存储（block），默认block-size为128M，不足128M为一块。更改默认块大小可以修改hdfs-default.xml中dfs.blocksize副本机制：文件的所有block都会有副本，副本数由参数dfs.replication控制，默认值是3，也就是会额外再复制2份，连同本身总共3份副本。副本存储策略：第一块副本：优先客户端本地，否则随机第二块副本：不同于第一块副本的不同机架。第三块副本：第二块副本相同机架不同机器。元数据记录：NameNode管理的元数据具有以下两种类型文件自身属性信息：文件名称、权限，修改时间，文件大小，复制因子，数据块大小。文件块位置映射信息：记录文件块和DataNode之间的映射信息，即哪个块位于哪个节点上。抽象统一的目录树结构：支持传统的层次性文件组织结构数据块存储：文件的各个block具体存储由datanode承担，每个block都可以在多个datanode上存储。HDFS shell操作：hadoop fs -ls file:/// #操作本地文件系统 hadoop fs -ls hdfs://node1:8020/ #操作HDFS分布式文件系统 hadoop fs -ls / #直接根目录，没有指定协议，将加载读取fs.defaultFS值 # 常用命令 # 创建文件夹 hadoop fs -mkdir [-p] <path> #-p沿着路径递归创建 # 查找指定目录下内容 hadoop fs -ls [-h] [-R] [<path>] #-h显示文件size -R递归查看目录以及子目录 # 上传文件到HDFS指定目录 hadoop fs -put [-f] [-p] <localsrc>...<dst> #-f覆盖目标文件 -p保留访问和修改时间，所有权和权限 # e.g. hadoop fs -put file:///etc/profile hdfs:///node1:8020/test # e.g. hadoop fs -put zookeeper.out /test # 查看HDFS文件内容 hadoop fs -car <src> # 下载HDFS文件 hadoop fs -get [-f] [-p] <src>...<localsrc> # 拷贝HDFS文件 hadoop fs -cp [-f] <src>...<dst> # 追加数据到HDFS文件 hadoop fs -appendToFile <localsrc>...<dst> # HDFS数据移动操作 hadoop fs -mv <src>...<dst> HDFS 集群角色：NameNode：维护和管理文件系统元数据，访问HDFS的唯一入口仅存储HDFS的元数据知道HDFS中任何给定文件的块列表及其位置不持久化存储每个文件中各个块所在的datanode的位置信息，这些信息会在系统启动时从DataNode重建。是Hadoop集群中的单点故障。NameNode所在机器通常会配置有大量内存（RAM）DataNode：负责具体的数据块存最终数据块block的存储DataNode启动时，会将自己注册到NameNode并汇报自己负责持有的块列表。当某个DataNode关闭时，不会影响数据的可用性。NameNode将安排由其他DataNode管理的块进行副本复制。DataNode所在机器通常会配置有大量磁盘空间SecondaryNameNode：充当NameNode的辅助节点，但不能替代NameNode。主要是帮助主角色进行元数据文件的合并动作HDFS写数据（上传文件）：HDFS客户端创建对象实例DistributedFileSystem，该对象中封装了与HDFS文件系统操作的相关方法。调用DistributedFileSystem对象的create()方法，通过RPC请求NameNode创建文件。NameNode执行各种检查判断：目标文件是否存在、父目录是否存在、客户端是否具有创建该文件的权限。检查通过，NameNode就会为本次请求记下一条记录，返回FSDataOutputStream输出流对象给客户端用于写数据。客户端通过FSDataOutputStream输出流开始写入数据。客户端写入数据时，将数据分成一个个数据包（packet 默认64k）,内部组件DataStreamer请求NameNode挑选出适合存储数据副本的一组DataNode地址，默认是3副本存储。DataStreamer将数据包流式传输到pipeline的第一个DataNode,该DataNode存储数据包并将它发送到pipeline的第二个DataNode。同样，第二个DataNode存储数据包并且发送给第三个（也是最后一个）DataNode。传输的反方向上，会通过ACK机制校验数据包传输是否成功；客户端完成数据写入后，在FSDataOutputStream输出流上调用close()方法关闭。DistributedFileSystem联系NameNode告知其文件写入完成，等待NameNode确认。因为namenode已经知道文件由哪些块组成（DataStream请求分配数据块），因此仅需等待最小复制块即可成功返回。最小复制是由参数dfs.namenode.replication.min指定，默认是1.HDFS读数据（下载文件）：HDFS客户端创建对象实例DistributedFileSystem，调用该对象的open()方法来打开希望读取的文件。DistributedFileSystem使用RPC调用namenode来确定文件中前几个块的块位置（分批次读取）信息。对于每个块，namenode返回具有该块所有副本的datanode位置地址列表，并且该地址列表是排序好的，与客户端的网络拓扑距离近的排序靠前。DistributedFileSystem将FSDataInputStream输入流返回到客户端以供其读取数据。客户端在FSDataInputStream输入流上调用read()方法。然后，已存储DataNode地址的InputStream连接到文件中第一个块的最近的DataNode。数据从DataNode流回客户端，结果客户端可以在流上重复调用read（）。当该块结束时，FSDataInputStream将关闭与DataNode的连接，然后寻找下一个block块的最佳datanode位置。这些操作对用户来说是透明的。所以用户感觉起来它一直在读取一个连续的流。客户端从流中读取数据时，也会根据需要询问NameNode来检索下一批数据块的DataNode位置信息。一旦客户端完成读取，就对FSDataInputStream调用close()方法。Hadoop MapReduceMapReduce的核心思想是“先分再合，分而治之”。Map表示第一阶段，负责“拆分”：即把复杂的任务分解为若干个“简单的子任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算，彼此间几乎没有依赖关系。Reduce表示第二阶段，负责“合并”：即对Map阶段的结果进行全局汇总。MapReduce设计构思：采用分而治之的策略应对大数据处理场景构建抽象编程模型map: 对一组数据元素进行某种重复式的处理； reduce: 对Map的中间结果进行某种进一步的结果整理。map: (k1; v1) → (k2; v2) reduce: (k2; [v2]) → (k3; v3)MapReduce处理的数据类型是<key,value>键值对统一架构、隐藏底层细节通过抽象模型与计算框架把需要做什么(what need to do)与具体怎么做(how to do)分开了，为程序员提供一个抽象和高层的编程接口和框架。程序员仅需要关心其应用层的具体计算问题，仅需编写少量的处理应用本身计算问题的业务程序代码。MapReduce特点：易于编程：提供了用于二次开发的接口，简单实现接口，即可完成分布式程序良好的扩展性：当计算机资源不能得到满足的时候，可以通过增加机器来扩展它的计算能力。高容错性：单一机器节点宕机，可将计算任务转移其它节点，这由Hadoop内部完成适合海量数据的离线处理MapReduce局限性：实时计算性能差，主要用于离线计算不能进行流式计算，主要针对静态数据，即数据时不能动态变化的。MapReduce实例进程：一个完整的MapReduce程序在分布式运行时有三类MRAppMaster：负责整个MR程序的过程调度及状态协调MapTask：负责map阶段的整个数据处理流程ReduceTask：负责reduce阶段的整个数据处理流程MapReduce阶段组成：一个MapReduce编程模型中只能包含一个Map阶段和一个Reduce阶段，或者只有Map阶段；如果用户的业务逻辑非常复杂，那就只能多个MapReduce程序串行运行。MapReduce官方示例程序：程序路径：hadoop-3.3.0/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.0.jar程序提交命令：[hadoop jar|yarn jar] hadoop-mapreduce-examples-3.3.0.jar args…评估PI值：hadoop jar hadoop-mapreduce-examples-3.3.0.jar pi 10 50参数PI表任务，参数10指并发数，参数50每个map任务取样的个wordcount任务：hadoop fs -mkdir /input hadoop fs -put 1.txt /input hadoop jar hadoop-mapreduce-examples-3.3.0.jar wordcount /input /output MapReduce整体执行流程：Map阶段执行过程把输入目录下文件按照一定的标准逐个进行逻辑切片，形成切片规划。每一个切片由一个MapTask处理对切片中的数据按照一定的规则读取解析返回<key,value>对。默认是按行读取数据。调用Mapper类中的map方法处理数据。按照一定的规则对Map输出的键值对进行分区partition。默认不分区，因为只有一个reducetask。分区的数量就是reducetask运行的数量。Map输出数据写入内存缓冲区，达到比例溢出到磁盘上。溢出spill的时候根据key进行排序sort。默认根据key字典序排序。对所有溢出文件进行最终的merge合并。Reduce阶段执行过程ReduceTask会主动从MapTask复制拉取属于需要自己处理的数据。把拉取来数据，全部进行合并merge，即把分散的数据合并成一个大的数据。再对合并后的数据排序。对排序后的键值对调用reduce方法。键相等的键值对调用一次reduce方法。最后把这些输出的键值对写入到HDFS文件中。Shuffle机制将map端的无规则输出按指定的规则“打乱”成具有一定规则的数据，以便reduce端接收处理。一般把从Map产生输出开始到Reduce取得数据作为输入之前的过程称作shuffle。Shuffle是MapReduce程序的核心。Shuffle中频繁涉及到数据在内存、磁盘之间的多次往复。Map端ShuffleCollect阶段：将MapTask的结果收集输出到默认大小为100M的环形缓冲区，保存之前会对key进行分区的计算，默认Hash分区。Spill阶段：当内存中的数据量达到一定的阀值的时候，就会将数据写入本地磁盘，在将数据写入磁盘之前需要对数据进行一次排序的操作，如果配置了combiner，还会将有相同分区号和key的数据进行排序。Merge阶段：把所有溢出的临时文件进行一次合并操作，以确保一个MapTask最终只产生一个中间数据文件Reducer端shuffleCopy阶段：ReduceTask启动Fetcher线程到已经完成MapTask的节点上复制一份属于自己的数据。Merge阶段：在ReduceTask远程复制数据的同时，会在后台开启两个线程对内存到本地的数据文件进行合并操作。Sort阶段：在对数据进行合并的同时，会进行排序操作，由于MapTask阶段已经对数据进行了局部的排序，ReduceTask只需保证Copy的数据的最终整体有效性即可。Hadoop YARNYARN（Yet Another Resource Negotiator）是一个通用资源管理系统和调度平台：资源管理系统：集群的硬件资源，和程序运行相关，比如内存、CPU等。调度平台：多个程序同时申请计算资源如何分配，调度的规则（算法）。通用：不仅仅支持MapReduce程序，理论上支持各种计算程序。YARN三大组件：ResourceManager（RM）：主角色，决定系统中所有应用程序之间资源分配的最终权限，即最终仲裁者。NodeManager（NM）：从角色，负责管理本机器上的计算资源。根据RM命令，启动Container容器、监视容器的资源使用情况。并且向RM主角色汇报资源使用情况。ApplicationMaster（App Mstr）：用户提交的每个应用程序均包含一个AM。负责程序内部各阶段的资源申请，监督程序的执行情况。RM和NM是集群物理层面，AM是App层面核心交互流程：MR作业提交Client-->RM资源的申请MrAppMaster-->RMMR作业状态汇报Container（Map|Reduce Task）-->Container（MrAppMaster）节点的状态汇报NM-->RMMR提交YARN交互流程：总体分两个阶段第一个阶段是客户端申请资源启动运行本次程序的ApplicationMaster；第二个阶段是由ApplicationMaster根据本次程序内部具体情况，为它申请资源，并监控它的整个运行过程，直到运行完成。YARN资源调度器Scheduler：YARN调度程序的工作是根据一些定义的策略为应用程序分配资源。在YARN中，负责给应用分配资源的就是Scheduler，它是ResourceManager的核心组件之一调度器策略FIFO Scheduler（先进先出调度器）、Capacity Scheduler（容量调度器）、Fair Scheduler（公平调度器）。Hive数仓简介：数据仓库（英语：Data Warehouse，简称数仓、DW）,是一个用于存储、分析、报告的数据系统。数据仓库本身并不“生产”任何数据，也不需要“消费”任何的数据，专注分析数据存储：联机事务处理系统（OLTP） 关系型数据库（RDBMS）是OLTP典型应用，比如：Oracle、MySQL、SQL Server等在OLTP系统下进行数据分析也可以，但是没必要。因此数仓专注数据分析，为什么？数据分析会增大数据读取压力OLTP仅存储数周或数月的数据数据分散在不同系统不同表中，字段类型属性不统一数仓主要特征：面向主题：主题是一个抽象的概念，是较高层次上数据综合、归类并进行分析利用的抽象，即数据分析是面向主题的集成性：主题相关数据会集成到数仓主题下；需要统一与综合，对数据进行抽取、清理、转换和汇总（如单位、同名异义等）非易失性：是数据分析的平台，而非创造数据的平台；数据仓库的数据反映的是一段相当长的时间内历史数据的内容；查询操作多，修改删除少时变性：数据随时间更新，以适应决策需要Hive简介：Apache Hive是一款建立在Hadoop之上的开源数据仓库系统，可以将存储在Hadoop文件中的结构化、半结构化数据文件映射为一张数据库表，基于表提供了一种类似SQL的查询模型，称为Hive查询语言（HQL），用于访问和分析存储在Hadoop文件中的大型数据集。Hive核心是将HQL转换为MapReduce程序，然后将程序提交到Hadoop群集执行。优点避免直接写MR，使用MR直接处理数据需要掌握Java，实现复杂逻辑开发难度大Hive操作接口采用类SQL语法，易于上手支持自定义函数，功能扩展方便背靠Hadoop，擅长存储分析海量数据集Hive与Hadoop：Hive利用HDFS存储数据，利用MapReduce查询分析数据。用户专注于编写HQL，Hive将其转化为MapReduce程序完成对数据的分析。Hive架构：用户接口：包括CLI、JDBC/ODBC、WebGU元数据存储：通常是存储在关系数据库如mysql/derby中。Hive 中的元数据包括表的名字，表的列和分区及其属性，表的属性（是否为外部表等），表的数据所在目录等。Metastore即元数据服务。Metastore服务的作用是管理metadata元数据。MateStore配置存在内嵌模式（Derby，不单独配置）、本地模式（Mysql，不单独配置），远程模式（Mysql，单独配置）。一般采用远程模式。Driver驱动程序，包括语法解析器、计划编译器、优化器、执行器：完成HQL 查询语句从词法分析、语法分析、编译、优化以及查询计划的生成。生成的查询计划存储在HDFS 中，并在随后有执行引擎调用执行。执行引擎：Hive本身并不直接处理数据文件。而是通过执行引擎处理。当下Hive支持MapReduce、Tez、Spark3种执行引擎Hive部署（matestore远程模式）：Hadoop集群健康可用，因为Hive基于Hadoop；整合Hadoop和Hive，修改Hadoop中core-site.xml，并且Hadoop集群同步配置文件，重启生效。Mysql安装与初始化配置，可能需要卸载CentOS默认的mariadbHive安装上传解压安装包解决Hive与Hadoop之间guava版本差异cd /export/server/apache-hive-3.1.2-bin/ rm -rf lib/guava-19.0.jar cp /export/server/hadoop-3.3.0/share/hadoop/common/lib/guava-27.0-jre.jar ./lib/修改配置文件hive-env.shhive-site.xml上传mysql jdbc驱动到hive安装包lib下初始化matedatacd /export/server/apache-hive-3.1.2-bin/ bin/schematool -initSchema -dbType mysql -verbos #初始化成功会在mysql中创建74张表启动hive：hive自带有：beeline cli连接或者hive cli连接，beeline安全性更高，而二代客户端。可以采用可视化客户端连接，如DataGrip启动matestore#前台启动 关闭ctrl+c /export/server/apache-hive-3.1.2-bin/bin/hive --service metastore #前台启动开启debug日志 /export/server/apache-hive-3.1.2-bin/bin/hive --service metastore --hiveconf hive.root.logger=DEBUG,console #后台启动 进程挂起 关闭使用jps+ kill -9 nohup /export/server/apache-hive-3.1.2-bin/bin/hive --service metastore & * 启动hiveserver2 nohup /export/server/apache-hive-3.1.2-bin/bin/hive --service hiveserver2 & #注意 启动hiveserver2需要一定的时间 不要启动之后立即beeline连接 可能连接不上 * beeline连接 /export/server/apache-hive-3.1.2-bin/bin/beeline beeline> ! connect jdbc:hive2://node1:10000 beeline> rootDDL语法：库CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name [COMMENT database_comment] [LOCATION hdfs_path] [WITH DBPROPERTIES (property_name=property_value, ...)];#用于指定一些数据库的属性配置。 USE DATABASE; DROP (DATABASE|SCHEMA) [IF EXISTS] database_name [RESTRICT|CASCADE];表CREATETABLE[IF NOT EXISTS] [db_name.]table_name (col_name data_type [COMMENT col_comment], ... ) [COMMENT table_comment] [ROW FORMAT DELIMITED …]; # ROW FORMATDELIMITED语法用于指定字段之间等相关的分隔符，这样Hive才能正确的读取解析数据。 # Hive默认的分割符是'\001' # LazySimpleSerDe是Hive默认的，包含4种子语法，分别用于各种情况的分隔符号。如下： ROW FORMAT DELIMITED [FIELDS TERMINATED BY char]#指定字段之间分隔符 [COLLECTION ITEMS TERMINATED BY char]#集合元素之间分隔符 [MAP KEYS TERMINATED BY char]#Map映射kv之间分隔符 [LINES TERMINATED BY char]#行数据之间分隔符Show语法#1、显示所有数据库SCHEMAS和DATABASES的用法功能一样 show databases; show schemas; #2、显示当前数据库所有表 show tables; SHOW TABLES [IN database_name]; #指定某个数据库 #3、查询显示一张表的元数据信息 desc formatted tablename;DML语法：加载数据Load 也可以采用hadoop fs –put|-mv 或者web页面操作将数据移动到表文件夹下LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename;可以上传本地数据或者hadoop数据，LOCAL指的本地是Hiveserver2服务所在机器的本地Linux文件系统，而非Hive客户端所在的本地文件系统Load将数据文件移动到与Hive表对应的位置，移动时是纯复制、移动操作。本地是纯复制，HDFS是纯移动Insert一般采用insert+select保存临时数据需要保证查询结果列的数目和需要插入数据表格的列数目一致。如果查询出来的数据类型和插入表格对应的列数据类型不一致，将会进行转换，但是不能保证转换一定成功，转换失败的数据将会为NULL。insert into table student_from_insert select num,name from student;SelectSELECT [ALL | DISTINCT] select_expr, select_expr, ... FROM table_reference [WHERE where_condition] [GROUP BY col_list] [ORDER BY col_list] [LIMIT [offset,] rows];DISTINCT指定从结果集中删除重复的行。默认ALL在WHERE表达式中，可以使用Hive支持的任何函数和运算符，但聚合函数除外。在SQL中增加HAVING子句原因是，WHERE关键字无法与聚合函数一起使用。HAVING子句可以让我们筛选分组后的各组数据,并且可以在Having中使用聚合函数，因为此时where，group by已经执行结束，结果集已经确定。HAVING和WHERE的区别having是在分组后对数据进行过滤where是在分组前对数据进行过滤having后面可以使用聚合函数where后面不可以使用聚合函数oreder by 默认asc ，降序desclimit可以接受1个或者两个参数（第一个参数指定要返回的第一行的偏移量第二个参数指定要返回的最大行数。当给出单个参数时，它代表最大行数，并且偏移量默认为0。）执行顺序：from > where > group（含聚合）> having >order > select；join语法：inner join 内连接#以下三者等价 即join=inner join=隐式连接 joinselect e.id,e.name,e_a.city,e_a.street from employee e inner join employee_address e_a on e.id =e_a.id joinselect e.id,e.name,e_a.city,e_a.street from employee e join employee_address e_a on e.id =e_a.id select e.id,e.name,e_a.city,e_a.street from employee e , employee_address e_a where e.id =e_a.id;left join：join时以左表的全部数据为准，右边与之关联；左表数据全部返回，右表关联上的显示返回，关联不上的显示null返回。Hive常用函数：使用show functions查看当下可用的所有函数；通过describe function extended funcname来查看函数的使用方式。Hive的函数分为两大类：内置函数（Built-in Functions）、用户定义函数UDF（User-Defined Functions）：内置函数可分为：数值类型函数、日期类型函数、字符串类型函数、集合函数、条件函数等；用户定义函数根据输入输出的行数可分为3类：UDF、UDAF、UDTF。（其实内置函数也可以按照输入输出划分，现在弱化了UD(User-Defined)）UDF（User-Defined-Function）普通函数，一进一出UDAF（User-Defined Aggregation Function）聚合函数，多进一出UDTF（User-Defined Table-Generating Functions）表生成函数，一进多出实际案例ETL(Extract-Transform-Load)数据清洗问题存在空数据，非法数据，采用where过滤信息融合在一起 采用substr截取信息为一个字段，采用切割函数split将etl后数据报存到新表 create table as select
前段时间为了测试云数据库的性能，建库删库不少。因为是通过宝塔平台的可视化操作的，刚开始建的那个测试的数据库排序在列表第二个，我的网站数据库是第一个。可网站刷新后我的数据库变成了第二个，而我直接就把它删掉了。全部的网站博客数据都没掉。为了恢复数据，我尝试了3个小时，试了网上能搜到的各种方法，最终根据部分日志恢复了最近3个月的数据。由于数据库没有备份，同时日志也会定期清理，整个数据恢复非常困难。在这个事件后，我立马开启了数据库的定期备份。同时，进行本地备份与云端备份。写了个网站爬虫实现将网站文章爬取到本地。Typecho爬虫代码开源在本人Github上。
当前代码即完成在页面上实现词云图的功能，一切正常。但往 中增加了上百行代码后，wordcloud为undefined，即获取不到DOM。尝试网上的什么异步方法，jq......均不行。更改mounted为updated生效。由于DOM中的某个DOM采用了v-if、v-show 或者 v-for（即根据获取到的后台数据来动态操作 DOM，即响应式），则DOM无法在mounted阶段获取到。mounted为加载阶段，而updated阶段则是完成了数据更新到DOM的阶段，使用this.$refs.wordcloudchart就能找到DOM。参考链接：https://www.cnblogs.com/menxiaojin/p/14236647.html
在用vue-cli脚手架创建项目后通常用npm run serve来启动项目，但是这中间到底发生了什么呢？溯源通过vue create test创建测试目录，可以观察基本的目录结构为├── test | ├── node_modules // 当前项目所有依赖，一般不可以移植给其他电脑环境 | ├── public | | ├── favicon.ico // 标签图标 | | └── index.html // 当前项目唯一的页面 | ├── src | | ├── assets // 静态资源img、css、js | | ├── components // 小组件 | | ├── views // 页面组件 | | ├── App.vue // 根组件 | | ├── main.js // 全局脚本文件（项目的入口） | | ├── router | | | └── index.js// 路由脚本文件（配置路由 url链接 与 页面组件的映射关系） | | └── store | | | └── index.js// 仓库脚本文件（vuex插件的配置文件，数据仓库） | ├── README.md └ └── package.json等配置文件查看根目录下的package.json文件，存在如下代码 "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" },这里的serve就是npm run serve的serve，即通过serve定位到这里后npm run serve就相当于执行`vue-cli-service serve命令。这里我们也可以将serve改为dev然后执行npm run dev，效果是一样的。可我执行在命令行下输入vue-cli-service是无效的，那npm run serve是如何启动的呢？原来运行npm run serve命令时npm会把node_modules下的.bin目录添加到Path，执行完后再删掉。node_modules\.bin\vue-cli-service相当于一个快捷方式，它被添加到了环境变量，但它指向的是node_modules\@vue\cli-service\bin\vue-cli-service.js。综上所述，npm run serve就是在运行vue-cli-service.js这个js文件函数解析#!/usr/bin/env node const { semver, error } = require('@vue/cli-shared-utils') const requiredVersion = require('../package.json').engines.node // 检测node版本是否符合vue-cli运行的需求。不符合则打印错误并退出。 if (!semver.satisfies(process.version, requiredVersion, { includePrerelease: true })) { error( `You are using Node ${process.version}, but vue-cli-service ` + `requires Node ${requiredVersion}.\nPlease upgrade your Node version.` ) process.exit(1) } // cli-service的核心类 const Service = require('../lib/Service') // 新建一个service的实例。并将项目路径传入 const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())// 传入当前项目的所在路径 // 参数处理 const rawArgv = process.argv.slice(2) // minimist 命令行参数解析器 const args = require('minimist')(rawArgv, { boolean: [ // build 'modern', 'report', 'report-json', 'inline-vue', 'watch', // serve 'open', 'copy', 'https', // inspect 'verbose' ] }) const command = args._[0] // 将参数传入service这个实例并启动后续工作。如果我们运行的是npm run serve。则command = "serve"。 service.run(command, args, rawArgv).catch(err => { error(err) process.exit(1) }) vue-cli-service.js即创建了service并对象调用了它的run 方法。Service类定义位于\node_modules\@vue\cli-service\lib\Service.js.阅读Service.js，代码比较长，只摘录其中run方法以及init。module.exports = class Service { constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) { process.VUE_CLI_SERVICE = this this.initialized = false //一般为项目根目录 this.context = context this.inlineOptions = inlineOptions // webpack相关收集。不是本文重点。 this.webpackChainFns = [] this.webpackRawConfigFns = [] this.devServerConfigFns = [] //存储的命令 this.commands = {} // Folder containing the target package.json for plugins this.pkgContext = context // package.json containing the plugins // 键值对存储的pakcage.json对象 this.pkg = this.resolvePkg(pkg) // If there are inline plugins, they will be used instead of those // found in package.json. // When useBuiltIn === false, built-in plugins are disabled. This is mostly // for testing. this.plugins = this.resolvePlugins(plugins, useBuiltIn) // pluginsToSkip will be populated during run() this.pluginsToSkip = new Set() // resolve the default mode to use for each command // this is provided by plugins as module.exports.defaultModes // so we can get the information without actually applying the plugin. //收集插件中的默认配置信息 this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { return Object.assign(modes, defaultModes) }, {}) } init (mode = process.env.VUE_CLI_MODE) { if (this.initialized) { return } this.initialized = true this.mode = mode // load mode .env if (mode) { this.loadEnv(mode) } // load base .env this.loadEnv() // load user config // 读取用户的配置信息.一般为vue.config.js const userOptions = this.loadUserOptions() this.projectOptions = defaultsDeep(userOptions, defaults()) debug('vue:project-config')(this.projectOptions) // apply plugins. this.plugins.forEach(({ id, apply }) => { if (this.pluginsToSkip.has(id)) return apply(new PluginAPI(id, this), this.projectOptions) }) // apply webpack configs from project config file if (this.projectOptions.chainWebpack) { this.webpackChainFns.push(this.projectOptions.chainWebpack) } if (this.projectOptions.configureWebpack) { this.webpackRawConfigFns.push(this.projectOptions.configureWebpack) } } async run (name, args = {}, rawArgv = []) { // resolve mode // prioritize inline --mode // fallback to resolved default modes from plugins or development if --watch is defined const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]) // --skip-plugins arg may have plugins that should be skipped during init() this.setPluginsToSkip(args) // load env variables, load user config, apply plugins this.init(mode) args._ = args._ || [] let command = this.commands[name] if (!command && name) { error(`command "${name}" does not exist.`) process.exit(1) } if (!command || args.help || args.h) { command = this.commands.help } else { args._.shift() // remove command itself rawArgv.shift() } // 执行命令。例如vue-cli-service serve 则，执行serve命令。 const { fn } = command return fn(args, rawArgv) } resolveChainableWebpackConfig () { const chainableConfig = new Config() // apply chains this.webpackChainFns.forEach(fn => fn(chainableConfig)) return chainableConfig } 其实质就相当于是一个配置文件。通过vue-cli-service中的new Service，加载插件信息，缓存到Service实例的plugins变量中。当得到命令行参数后，在通过new Service的run方法，异步执行命令。run 方法调用init方法项目配置信息。init方法又调用node_modules\@vue\cli-service\lib\PluginAPI.js中的PluginAPI.js类，关联Service和插件，并存放关系到Service.commands中，最后commands[cmdArgName]调用该方法，完成插件调用。总结npm run serve即创建存在当前插件的一个环境并运行当前vue项目的参考链接https://www.cnblogs.com/jhpy/p/11873270.htmlhttps://blog.csdn.net/weixin_39555179/article/details/111137470https://segmentfault.com/a/1190000017876208