Redis Lua解析器支持的库base lib.table lib.string lib.math lib.debug lib.bitop lib.redis.sha1hex function.struct lib. 外部库cjson lib. 外部库cmsgpack lib. 外部库转换规则：Redis Luaint numberbulk/str strmulti bulk table，表内可以有其他数据类型status table，表内ok包含状态信息error table，表内err包含错误信息Nil/Nil multi false1 trueEVAL "return true" 0 (integer) 1 EVAL "return false" 0 (nil)使用 EVAL 或 EVALSHAhelp @scripting# vim demo.lua local _M = { KEYS[1],KEYS[2], ARGV[1],ARGV[2] } return _M redis-cli --eval demo.lua key1 key2 , first second EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" "a42059b356c875f0717db19a51f6aaca9ae659ea" EVALSHA a42059b356c875f0717db19a51f6aaca9ae659ea 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second"Lua脚本的阻塞单实例，阻塞不能对缓存有任何操作集群阻塞的主节点上，阻塞；阻塞的主节点的从节点上，重定向到主节点，阻塞。从节点可查看；阻塞的节点上操作非阻塞的节点的缓存没有影响，可重定向到其他主机实例进行缓存操作不影响集群中非阻塞实例中的缓存操作，包括lua脚本的执行(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.SCRIPT KILL：仅能中断对数据集没有修改的操作。当中出现写入操作时无效SHUTDOWN NOSAVE：硬中断并关闭服务。数据不会改变。遇到死循环阻塞时，为何SCRIPT KILL仍可操作？Lua引擎提供各种各样的钩子函数。比如每执行10W条指令执行一次某个hook函数。Redis在钩子函数里会忙里偷闲的处理客户端的请求，并且在发现超时5s后才真正执行。Lua脚本内的redis调用redis.call()执行命令过程发生错误时停止执行，并返回一个脚本错误信息。redis.pcall()同上。仅区别于发生错误时。出错时并不引发(raise)错误，而是返回一个带 err 域的 Lua 表(table)，用于表示错误redis.log(loglevel,message)记录在Redis日志中loglevelredis.LOG_DEBUGredis.LOG_VERBOSEredis.LOG_NOTICEredis.LOG_WARNINGEVAL "redis.log(redis.LOG_NOTICE, 'HELLO REDIS LOG')" 0redis.breakpoint()打断点。配合--ldb调试
容器沙盒，把应用装起来的技术。这样一来，应用之间不仅没有互相干扰，还能方便搬运。是理想的 PaaS 状态。但沙盒了哪些东西？对于应用而言，静态表现为文件，存放于磁盘上。而动态表现为进程，变成了计算机里的数据和状态的。所以说，容器其实是特殊的进程而已容器技术的核心通过约束和修改进程的动态表现，创造出一个边界。CGroups：制造约束，隔离Namespace：修改进程视图，限制Namespace 技术领导给组长划分一个圈，圈内组长管工仔。业绩由组长向上汇报，对工仔来说，这个圈就是公司，组长是编号NO1的人物。但其实在这个公司，组长编号可能只是NO199，而领导才是真正NO1。这个“障眼法”有什么用？让工仔看到某些指定的内容，保持专注。这是一个Linux容器最基本的实现原理。/proc/<PID>/ns障眼法种类有：PID，CLONE_NEWPID，进程编号Netork，CLONE_NEWNET，网络栈IPC，CLONE_NEWIPC，信号量、消息队列、共享内存User，CLONE_NEWUSER，用户和用户组Mount，CLONE_NEWNS，挂载点UTS，CLONE_NEWUTS，主机名和域名CGROUP，CLONE_NEWCGROUP，资源限制使用clone()系统调用创建一个进程时，可以实施不同的障眼法# PID Namespace int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);对比虚拟机，这种隔离有哪些优缺点？虚拟机里运行一个完整的GuestOS；而容器无需虚拟机隔离更加彻底；而容器隔离不彻底，比如：时间，/proc虚拟机更牢固；容器更容易越狱虽然确实有Seccomp等技术，对容器发起的所有系统调用进行过滤和甄别来进行安全加固，但因为又多一层过滤，性能一定有影响。何况默认情况下，都不知道到底该开启哪些系统调用，禁止哪些系统调用CGroups 技术有了隔离，但当前组与其他组之间依旧是平等竞争关系。谁都不想自己组内的资源被他人挪用吧。Linux Control Group，限制一个进程组能够使用的资源上限，包括CPU、内存、磁盘、网络带宽CGroups 暴露给用户的操作接口是文件系统，以文件和目录的方式组织在操作系统的/sys/fs/cgroup/路径下。ls -l /sys/fs/cgroup 或 mount -t cgroup都能展示出子系统CPU子系统内存子系统IO子系统等等...在每类子系统下创建一个目录，那么这个目录称为一个控制组。你会发现控制组内自动生成该子系统对应的资源限制文件。实际上，CGroups 就是 一个子系统目录 + 一组资源限制文件 的组合。非常易用。# 进入CPU子系统 cd /sys/fs/cgroup/cpu/ # 创建一个控制组 mkdir -pv container # 运行一个吃CPU的进程，并得到PID。可top看效果 while :; do :; done & [1] 226 # 查看并修改限制，1ms = 1000us cat container/cpu.cfs_quota_us -1 cat container/cpu.cfs_period_us 100000 echo 20000 > container/cpu.cfs_quota_us # 将所需进程PID写入tasks。再用top看效果 echo 226 > container/tasksCGroups不完善的地方/proc 文件系统Linux下/proc记录着当前内核运行状态的一系列特殊文件，即整个公司的资料库。容器内应该仅能查询容器自身的数据。1.lxcfs2.Cgroup Namespace容器技术的本质容器技术非常重要的概念，容器是一个 “单进程” 模型。不是说容器不支持运行多进程，而是更希望容器本身和应用有相同的生命周期。避免容器是正常运行的，但应用早已挂了，这种情况。同时也便于后续容器的编排深入理解容器镜像Mount Namespace，对容器进程视图的改变，一定是伴随着挂载操作才能生效的。如果将宿主机系统目录挂载到容器里使用，还谈不上独立和隔离。容器对系统目录的修改也很有可能毁掉宿主机。那该怎么办？改变进程的根目录到你指定的位置：chrootmkdir -p $HOME/test mkdir -p $HOME/test/{bin,lib64,lib} T=$HOME/test cp -v /bin/{bash,ls} ${T}/bin list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')" for i in $list; do cp -v "$i" "${T}${i}"; done chroot ${T} /bin/bashMount Namespace基于对chroot的改良，成为Linux里的第一个Namespace。为了更加真实，一般在容器的根目录下挂载一个完整的文件系统。随后在容器中ls看到的内容就像一个完整的系统。这个挂载在容器上、用来为容器进程提供隔离后执行环境的文件系统，就是容器镜像，也叫作rootfs（根文件系统）这里体现了容器的另一特性：一致性rootfs里打包的不只应用，还有整个操作系统的文件和目录，这就是一个完整的依赖库嘛。所以轻松解决了线下线上环境不一致的问题。每个应用涉及不同开发环境，那每个应用都要打包一份rootfs吗？能不能实现增量？Docker引入分层概念，镜像制作的每一步都会生成一个layer，即一个增量rootfs。此处用到了一种Union File System（联合文件系统）的能力。UnionFS，最主要的功能是将多个不通位置的目录联合挂载到同一个目录下tree . ├── A │ ├── a │ └── x └── B ├── b └── x mkdir -p C mount -t aufs -o dirs=./A:./B none ./C tree ./C ├── a ├── b └── xMount Namespace 和 rootfs 的结合使用，容器为进程构建出一个完善的文件系统隔离环境。看清一个容器运行一个Docker项目，为待创建的用户进程完成以下操作：启用 Linux Namespace 配置指定 CGroups 参数切换进程根目录（chroot）By Jeff.WNamespace 构建了四周围墙（进程隔离）CGroups 构建了受控的天空优先使用阳光雨露（资源限制）Mount Namespace 和 rootfs 构建了脚下的大地（文件系统隔离）一切都是熟悉的，没有丝毫陌生感（容器一致性）如何进入容器内部docker exec命令如何进入容器里？Nampespace创建的隔离空间虽然见不着，但一个进程的Namespace信息确实存在宿主机上，且以文件方式存在于/proc/<PID>/ns/下。通过加入某个进程已有的Namespace，达到“进入”这个进程所在容器的目的。这是docker exec的实现原理。加入指定名称空间的小程序#define _GNU_SOURCE #include <fcntl.h> #include <sched.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE);} while (0) int main(int argc, char *argv[]) { int fd; fd = open(argv[1], O_RDONLY); if (setns(fd, 0) == -1) { errExit("setns"); } execvp(argv[2], &argv[2]); errExit("execvp"); }argv[1]当前进程要加入的Namespace文件路径，比如“/proc/1314/ns/net”；argv[2]表示运行的进程，比如“/bin/bash”。gcc -o set_ns set_ns.c ./set_ns /proc/1314/ns/net /bin/bash ifconfig容器数据卷使用Mount Namespace和rootfs实现文件系统隔离，思考两个问题：容器里新建文件，宿主机如何获取宿主机文件，容器如何访问这正是 Docker Volume 要解决的问题。允许将宿主机上指定的目录或文件，挂载到容器里面进行读取和修改。Volume声明有两种，本质都是把宿主机目录挂载进容器/test目录docker run -v /test ... docker run -v /home:/test ...第一种没声明宿主机目录，Docker默认使用宿主机上/var/lib/docker/volumes/[VOLUME_ID]/_data进行挂载。那么，具体如何实现挂载？容器进程被创建后，在执行chroot/pivot_root之前，容器进程一直可以看到宿主机的整个文件系统，包括容器镜像的各个层。这些层通过联合挂载在/var/lib/docker/aufs/mnt/，这样所需的rootfs就准备好了。而后在执行chroot之前，把Volume指定的宿主机目录（/home）挂载到指定容器目录（/test）在宿主机上对应的目录/var/lib/docker/aufs/mnt/[rwlayer_id]/test容器进程，是Docker创建容器的初始化进程（dockerinit），而不是应用进程（ENTRYPOINT+CMD）。dockerinit负责完成根目录的准备、挂载设备和目录、配置hostname等一系列需要在容器内进行的初始化操作。最后调用execv()，让应用进程取代自己成为容器里PID=1的进程
PaaS是什么PaaS之前，IaaS，主流普遍的用法就是租一批虚拟机，用脚本部署应用。但难以绕过的问题就是，云端虚拟机和本地环境不一致PaaS应用托管的能力可以很好解决这个问题PaaS最核心的能力：应用打包、分发机制、应用隔离打包：将可执行文件和启动脚本打进一个压缩包分发：通过调度器选择运行该应用的虚拟机隔离：调用系统CGroups和Namespace，为应用创建运行的隔离环境（沙盒）容器是什么就是沙盒。依赖操作系统内核支持。CGroupsNamespaceDocker为何能迅速崛起早期PaaS项目代表（Cloud Foundry），用户必须为每种语言、每种框架、甚至每个版本的应用维护一个打好的包。费尽心机。Docker如何解决打包问题：镜像镜像也是一个压缩包，大多数Docker镜像除了应用可执行文件和启停脚本外，还包含一个完整操作系统的所有文件和目录（不包括内核），赋予本地环境和云端环境高度一致的能力。完整操作系统的所有文件、目录在Dockerfile中对应FROM参数，指定引用的基础镜像应用可执行文件、启停脚本在Dockerfile中对应其他参数# 构建镜像 docker build -t NAME . # 解压镜像，创建沙盒运行应用 docker run NAME因为镜像的便利，Docker Swarm，推了一波CaaS（Container as a Service）编排通过某些具体或者配置完成一组虚拟机以及关联资源的定义、配置、创建、删除等工作，然后由云计算平台按照这些指定的逻辑来完成的过程。Docker ComposeDocker SwarmMesos MarathonKubernetesDocker Compose即fig项目，通过配置文件定义容器间的关联。仅支持单机。# 启动 docker-compose upDocker Swarm用户创建容器的请求会被Swarm拦截，通过具体调度算法找到合适的Daemon运行。Compose集群版。# Docker由单机到多机部署 docker run -H "Swarm API" NAMEMesos Marathon有超大规模集群的管理经验。Kubernetes简称K8s。从API到容器运行时的每一层，K8s项目都为开发者暴露出了可以扩展的插件机制，鼓励用户通过代码的方式介入到K8s项目的每一个阶段。催生出很多优秀的项目Istio，微服务治理Operator，有状态应用部署框架Rook，把重量级Ceph封装成简单易用的容器存储插件PaaS 什么有价值容器本身没有价值，有价值的是容器编排。
接口基础类型 -> structtype cat struct {...} type dog struct {...} func (c cat) eat() {...} func (d doy) eat() {...} // 该函数其实是想，传入什么，我就撸什么。如果x的类型是cat，将来只能传递cat类型的变量 // 那dog类型的怎么办？该函数无法支持。struct解决不了这类问题 func lu(x cat) {...} lu(cat) lu(dog) // 无法传入dog类型struct -> interface// 引入接口 type eater interface { eat() } func lu(x eater) {...} lu(cat) lu(dog)接口就是一个特殊类型。它规定了有哪些方法。一个变量实现了接口内的所有方法，就是这个特殊类型的变量。var d dog var e eater // d实例实现了eater的eat()，那么d就是这个接口类型的变量 // 此时d既是struct类型，又是interface类型 e = d接口变量保存以下 2 个部分：动态类型：零值nil动态值：零值nilfmt.Printf("%T, %v\n", e, e)这样就能实现接口变量的动态性。可以理解，接口就是一个动态变量。函数参数：接口相当于给函数传入实例对象的方法集，能实现方法集内的方法的实例对象，都能执行该函数。实现接口时：值接收者 VS 指针接收者值接收者：结构体类型、结构体指针类型的变量都支持指针接收者：仅支持结构体指针类型的变量接口嵌套type sleeper interface { sleep() } type eater interface { eat() } type animal interface { sleeper eater }空接口没有方法，所有类型都实现可以实现空接口。因此 interface{} 通常用来表示任意类型。// 接口作参数 func show(a interface{}) { fmt.Printf("%T, %v", a, a) } // 还不知道存入的值是什么类型时 var m = make(map[string]interface{}, 8)如何判断接口这个动态变量的类型类型断言反射类型断言x.(type) // x: 类型为 interface{} 的变量 // type: 断言这个x变量可能的类型 // 返回2个值 v, ok // 如果ok=true，那么v的类型就是type；如果ok=false，那么v的类型就不是typefunc guessType(a interface{}) { if s, ok := a.(string); ok { fmt.Printf("is a string, value is %v\n", s) } else { fmt.Printf("unknown type\n") } } func guessType(a interface{}) { switch v := a.(type) { case string: fmt.Printf("is a string, value is %v\n", v) case int: fmt.Printf("is a int, value is %v\n", v) case bool: fmt.Printf("is a bool, value is %v\n", v) default: fmt.Printf("unknown type\n") } }反射等待学习反射
return 做了什么？返回值 赋值return 返回值defer 什么时候执行？返回值 赋值defer funcreturn 返回值把 返回值 看作一个中间商，比如一个tmp变量。先回忆一下函数如何定义返回值。当 func 使用 有名返回值func f() x int { ... return }当 func 使用 匿名返回值func f() int { ... return x }我豁然开朗的关键点把 返回值 看作和x、y不一样的另一个变量即可，咱们只关注它的值的指向。栗子1func f1() int { x := 5 defer func() { x++ }() return x }返回值 = x = 5，返回值指向5不管defer如何操作return 返回值，即 return 5栗子2func f2() (x int) { defer func() { x++ }() return 5 }返回值x，x指向5闭包defer修改x的指向为6return 返回值，即 return x，return 6栗子3func f3() (y int) { x := 5 defer func() { x++ }() return x }返回值y，y=x=5，y指向5闭包defer修改x的指向为6return 返回值，即 return y，return 5栗子4func f4() (x int) { defer func(x int) { x++ }(x) return 5 }返回值x，x=5，x指向5defer值拷贝，内部操作与外部无关return 返回值，即 return x，return 5