-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
1 lines (1 loc) · 188 KB
/
index.json
1
[{"content":"用 docker 起 mongo,有两种限制方式:\n 通过 Docker 限制内存 通过 MongoDB 自己的配置文件限制 一、通过 Docker 限制内存 内存限制相关参数:\n 参数 简介 -m, \u0026ndash;memory 内存限制,格式:数字+单位,单位可以是b, k, m, g,最小4M \u0026ndash; -memory-swap 存和交换空间总大小限制,注意:必须比-m参数大,-1 表示不受限 例子:\ndocker run -m 100M --memory-swap -1 mongo:5.0 二、通过 mongo 配置文件限制 配置文件位置:\n3.x : /etc/mongod.conf\n4.x : /etc/mongod.conf.orig\n 默认配置:\nstorage: # mongod 进程存储数据目录,此配置仅对 mongod 进程有效 dbPath: /data/mongodb/db 是否开启 journal 日志持久存储,journal 日志用来数据恢复,是 mongod 最基础的特性,通常用于故障恢复。64 位系统默认为 true,32 位默认为 false,建议开启,仅对 mongod 进程有效。 journal: enabled: true # 存储引擎类型,mongodb 3.0 之后支持 “mmapv1”、“wiredTiger” 两种引擎,默认值为“mmapv1”;官方宣称 wiredTiger 引擎更加优秀。 engine: mmapv1 systemLog: # 日志输出目的地,可以指定为 “file” 或者“syslog”,表述输出到日志文件,如果不指定,则会输出到标准输出中(standard output) destination: file # 如果为 true,当 mongod/mongos 重启后,将在现有日志的尾部继续添加日志。否则,将会备份当前日志文件,然后创建一个新的日志文件;默认为 false。 logAppend: true # 日志路径 path: /var/log/mongodb/mongod.log net: # 指定端口 port: 27017 # 绑定外网 op 多个用逗号分隔 bindIp: 0.0.0.0 maxIncomingConnections: 10000 限制内存、使用 wiredTiger 引擎后配置:\n# mongod.conf # for documentation of all options, see: # http://docs.mongodb.org/manual/reference/configuration-options/ # Where and how to store data. storage: dbPath: /data/db journal: enabled: true engine: wiredTiger wiredTiger: engineConfig: # 限制 5GB 内存大小 cacheSizeGB: 5 # where to write logging data. systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log # network interfaces net: port: 27017 bindIp: 0.0.0.0 maxIncomingConnections: 10000 # how the process runs processManagement: timeZoneInfo: /usr/share/zoneinfo 三、用 docker-compose 启动 docker-comopose 文件内容:\nversion: \u0026#39;2\u0026#39; services: mongo: image: \u0026#34;mongo:5.0\u0026#34; container_name: \u0026#34;mongo-v5\u0026#34; restart: always mem_limit: 6G environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: xxxxxx ports: - 27017:27017 volumes: - ./db:/data/db - ./logs:/var/log/mongodb - ./mongod.conf.orig:/etc/mongod.conf.orig command: mongod --auth mongo-express: image: \u0026#34;mongo-express:0.54\u0026#34; container_name: \u0026#34;mongo-express\u0026#34; links: - mongo depends_on: - mongo ports: - 8081:8081 environment: ME_CONFIG_BASICAUTH_USERNAME: admin ME_CONFIG_BASICAUTH_PASSWORD: xxxxxx ME_CONFIG_MONGODB_ADMINUSERNAME: admin ME_CONFIG_MONGODB_ADMINPASSWORD: xxxxxx 做了 volume 映射,./db:/data/db 保证容器删了数据不会丢失,./mongod.conf.orig 记得放到该 docker-compose.yam 同目录。./logs 查看 MongoDB 日志。另外起了 mongo-express 容器方便查看数据。\n至此,完。\n","permalink":"https://jsharkc.github.io/post/mongo-compose/","summary":"用 docker 起 mongo,有两种限制方式:\n 通过 Docker 限制内存 通过 MongoDB 自己的配置文件限制 一、通过 Docker 限制内存 内存限制相关参数:\n 参数 简介 -m, \u0026ndash;memory 内存限制,格式:数字+单位,单位可以是b, k, m, g,最小4M \u0026ndash; -memory-swap 存和交换空间总大小限制,注意:必须比-m参数大,-1 表示不受限 例子:\ndocker run -m 100M --memory-swap -1 mongo:5.0 二、通过 mongo 配置文件限制 配置文件位置:\n3.x : /etc/mongod.conf\n4.x : /etc/mongod.conf.orig\n 默认配置:\nstorage: # mongod 进程存储数据目录,此配置仅对 mongod 进程有效 dbPath: /data/mongodb/db 是否开启 journal 日志持久存储,journal 日志用来数据恢复,是 mongod 最基础的特性,通常用于故障恢复。64 位系统默认为 true,32 位默认为 false,建议开启,仅对 mongod 进程有效。 journal: enabled: true # 存储引擎类型,mongodb 3.","title":"限制内存 mongo docker-compsoe 部署"},{"content":"一条命令安装 官方脚本:\ncurl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 国内 daocloud 安装命令:\ncurl -sSL https://get.daocloud.io/docker | sh 手动安装指定版本 卸载旧版本 sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine \\ container-selinux \\ docker-selinux 安装相关依赖 yum-utils 提供 yum-config-manager 工具, devicemapper存储驱动依赖 device-mapper-persistent-data 和 lvm2.\nyum install -y yum-utils device-mapper-persistent-data lvm2 配置版本镜像库 季度更新的稳定 stable 版和 test 版\nyum-config-manager --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo yum-config-manager --enable docker-ce-test 由于 docker.com 服务器下载很慢,所以改为国内镜像.\nyum-config-manager --add-repo \\ https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 如需禁止 test 版本, 可以执行下面的命令\nyum-config-manager --disable docker-ce-test 安装 Docker 更新缓存\nyum clean all yum makecache or\nyum makecache fast 安装\nyum install docker-ce docker-ce-cli containerd.io 安装完后,查看安装的软件\nrpm -qa | grep docker 输出结果为:\n docker-ce-19.03.9-3.el7.x86_64 docker-ce-cli-19.03.9-3.el7.x86_64\n 启动 Docker systemctl enable docker systemctl start docker 查看 Docker 版本:\ndocker --version 结果:(注:我这个)\n Docker version 20.10.9, build 9d988398e7\n 因为没有指定版本,所以安装的是最新版本,如果想安装指定版本,先查看所有版本列表:\nyum list docker-ce --showduplicates | sort -r sort -r 会按版本倒序排序,第二列是版本号,el7 表示 centos7,第三列是库名。\n docker-ce.x86_64 3:20.10.9-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.8-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.7-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.6-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.5-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.4-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.3-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.2-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.1-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.12-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.11-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.10-3.el7 docker-ce-stable docker-ce.x86_64 3:20.10.0-3.el7 docker-ce-stable docker-ce.x86_64 3:19.03.9-3.el7 docker-ce-stable docker-ce.x86_64 3:19.03.8-3.el7 docker-ce-stable\n 例如安装 3:19.03.9-3.el7:\nyum install docker-ce-19.03.9-3.el7 docker-ce-cli-19.03.9-3.el7 containerd.io 安装完成后,检查版本:\ndocker --version Docker version 19.03.9, build 9d988398e7\n 非root用户启动docker 默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。\n建立 docker 组:\n$ sudo groupadd docker 将当前用户加入 docker 组:\n$ sudo usermod -aG docker $USER ","permalink":"https://jsharkc.github.io/post/centos7-install-docker/","summary":"一条命令安装 官方脚本:\ncurl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 国内 daocloud 安装命令:\ncurl -sSL https://get.daocloud.io/docker | sh 手动安装指定版本 卸载旧版本 sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine \\ container-selinux \\ docker-selinux 安装相关依赖 yum-utils 提供 yum-config-manager 工具, devicemapper存储驱动依赖 device-mapper-persistent-data 和 lvm2.\nyum install -y yum-utils device-mapper-persistent-data lvm2 配置版本镜像库 季度更新的稳定 stable 版和 test 版","title":"Centos7 安装 Docker"},{"content":"flutter 最新版 2.5.3 安装 CocoaPods 需要 ruby 2.6 以上,而我 MAC 上 ruby 只有 2.5 所以需要更新 ruby,brew install ruby 后,就碰到了这个问题。\n报错信息:\ndyld: Library not loaded: /usr/local/opt/ruby/lib/libruby.2.5.dylib Referenced from: /usr/local/bin/vi Reason: image not found 也就是说,现在 ruby 2.6 所以找不到 ruby 2.5 了\n首先\n\u0026gt; which vi 找到 vi 所在的位置,/usr/local/bin/vi,然后 通过 otool 找到该命令依赖的库 \u0026gt; otool -L /usr/local/bin/vi /usr/local/bin/vi: ... /usr/local/opt/ruby/lib/libruby.dylib (compatibility version 2.5.0, current version 2.5.1) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) ... 有很多内容,为了能看清楚,我用 ... 省略掉了,然后通过 install_name_tool 修改依赖 \u0026gt; install_name_tool -change /usr/local/opt/ruby/lib/libruby.2.5.dylib /usr/local/opt/ruby/lib/libruby.dylib /usr/local/bin/vi install_name_tool 命令格式是: install_name_tool -change 原依赖 需要换成的依赖 命令位置 修改之后,就能正常使用了。\n希望各位都能解决问题。\n","permalink":"https://jsharkc.github.io/post/fix-dyld-library-not-loaded/","summary":"flutter 最新版 2.5.3 安装 CocoaPods 需要 ruby 2.6 以上,而我 MAC 上 ruby 只有 2.5 所以需要更新 ruby,brew install ruby 后,就碰到了这个问题。\n报错信息:\ndyld: Library not loaded: /usr/local/opt/ruby/lib/libruby.2.5.dylib Referenced from: /usr/local/bin/vi Reason: image not found 也就是说,现在 ruby 2.6 所以找不到 ruby 2.5 了\n首先\n\u0026gt; which vi 找到 vi 所在的位置,/usr/local/bin/vi,然后 通过 otool 找到该命令依赖的库 \u0026gt; otool -L /usr/local/bin/vi /usr/local/bin/vi: ... /usr/local/opt/ruby/lib/libruby.dylib (compatibility version 2.5.0, current version 2.5.1) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) ... 有很多内容,为了能看清楚,我用 .","title":"修复 dyld: Library not loaded"},{"content":"Log 用的 go.uber.org/zap 库。\n按大小切割日志 按大小切割日志,用到 github.com/natefinch/lumberjack 库,代码如下:\npackage log import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/natefinch/lumberjack\u0026#34; \u0026#34;go.uber.org/zap\u0026#34; \u0026#34;go.uber.org/zap/zapcore\u0026#34; ) var Sugar *zap.SugaredLogger = nil var Raw *zap.Logger = nil // LogInit 初始化日志库 // dev 开发模式,日志不入文件 // logPath 日志文件存放路径 // logName 日志文件名称 // maxSize 单个文件大小,超过后切割,单位 M // maxBackups 旧的日志文件最多保留个数,0 为保存所有 // maxAge 旧的日志文件最多保留天数,0 为保存所有 // MaxBackups 和 maxAge 只要有一个不满足,就不再保留 func LogInit(dev bool, logPath, logName string, maxSize, maxBackups, maxAge int) (err error) { if dev { Raw, _ = zap.NewDevelopment() Sugar = Raw.Sugar() return } if !strings.HasSuffix(logPath, \u0026#34;/\u0026#34;) { logPath += \u0026#34;/\u0026#34; } if !strings.HasSuffix(logName, \u0026#34;.log\u0026#34;) { logName += \u0026#34;.log\u0026#34; } _, err = os.Stat(logPath) if os.IsNotExist(err) { // Create parent directory if it does not exist if err = os.MkdirAll(logPath, 0744); err != nil { fmt.Println(\u0026#34;os.MkdirAll failed, err=\u0026#34;, err) return } } if err != nil { fmt.Println(\u0026#34;os.Stat failed, err=\u0026#34;, err) return } w := zapcore.AddSync(\u0026amp;lumberjack.Logger{ Filename: logPath + logName, MaxSize: maxSize, MaxBackups: maxBackups, MaxAge: maxAge, }) core := zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), w, zap.InfoLevel, ) Raw = zap.New(core) Sugar = Raw.Sugar() return } zap.NewProductionEncoderConfig() 产生的日志,事件格式为时间戳,人类不方便读,如果想自定义时间格式的话,只需要替换 core := zapcore.NewCore 即可,代码如下:\ncore := zapcore.NewCore( zapcore.NewConsoleEncoder(MyEncoderConfig()), w, zap.InfoLevel, ) func MyEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ TimeKey: \u0026#34;ts\u0026#34;, LevelKey: \u0026#34;level\u0026#34;, NameKey: \u0026#34;logger\u0026#34;, CallerKey: \u0026#34;caller\u0026#34;, FunctionKey: zapcore.OmitKey, MessageKey: \u0026#34;msg\u0026#34;, StacktraceKey: \u0026#34;stacktrace\u0026#34;, LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format(\u0026#34;2006/01/02T15:04:05\u0026#34;)) }, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } } 按时间切割日志 按时间切割日志,用到 github.com/lestrrat/go-file-rotatelogs 库,代码如下:\npackage log import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; rotatelogs \u0026#34;github.com/lestrrat/go-file-rotatelogs\u0026#34; \u0026#34;go.uber.org/zap\u0026#34; \u0026#34;go.uber.org/zap/zapcore\u0026#34; ) var Sugar *zap.SugaredLogger = nil var Raw *zap.Logger = nil // LogInit 初始化日志库 // logPath 日志文件存放路径 // logName 日志文件名称 // rotationTime 按时间分割,分割的时间长度 func LogInit(logPath, logName string, rotationTime time.Duration) (err error) { if !strings.HasSuffix(logPath, \u0026#34;/\u0026#34;) { logPath += \u0026#34;/\u0026#34; } if !strings.HasSuffix(logName, \u0026#34;.log\u0026#34;) { logName += \u0026#34;.log\u0026#34; } _, err = os.Stat(logPath) if os.IsNotExist(err) { // Create parent directory if it does not exist if err = os.MkdirAll(logPath, 0744); err != nil { fmt.Println(\u0026#34;os.MkdirAll failed, err=\u0026#34;, err) return } } if err != nil { fmt.Println(\u0026#34;os.Stat failed, err=\u0026#34;, err) return } rotate, err := rotatelogs.New( logPath+logName+\u0026#34;.%Y%m%d\u0026#34;, rotatelogs.WithLinkName(logPath+logName), rotatelogs.WithMaxAge(rotationTime), // 这里不写默认 24 hour ) if err != nil { fmt.Println(\u0026#34;rotatelogs.New failed, err=\u0026#34;, err) return } writer := zapcore.AddSync(rotate) core := zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), writer, zap.InfoLevel, ) Raw = zap.New(core) Sugar = Raw.Sugar() return } 假设 rotationTime 为 24hour 即「天」,那么当你的程序在任意时间启动,都会在半夜 12 点,产生日志切割。\n以上\n","permalink":"https://jsharkc.github.io/post/golang-devide-log-by-size-or-time/","summary":"Log 用的 go.uber.org/zap 库。\n按大小切割日志 按大小切割日志,用到 github.com/natefinch/lumberjack 库,代码如下:\npackage log import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/natefinch/lumberjack\u0026#34; \u0026#34;go.uber.org/zap\u0026#34; \u0026#34;go.uber.org/zap/zapcore\u0026#34; ) var Sugar *zap.SugaredLogger = nil var Raw *zap.Logger = nil // LogInit 初始化日志库 // dev 开发模式,日志不入文件 // logPath 日志文件存放路径 // logName 日志文件名称 // maxSize 单个文件大小,超过后切割,单位 M // maxBackups 旧的日志文件最多保留个数,0 为保存所有 // maxAge 旧的日志文件最多保留天数,0 为保存所有 // MaxBackups 和 maxAge 只要有一个不满足,就不再保留 func LogInit(dev bool, logPath, logName string, maxSize, maxBackups, maxAge int) (err error) { if dev { Raw, _ = zap.","title":"Golang 按大小 or 时间切割日志"},{"content":"有进程为什么还创造线程 原因: 进程属于在CPU和系统资源等方面提供的抽象,能够有效提高CPU的利用率。\n线程是在进程这个层次上提供的一层并发的抽象:\n(1)能够使系统在同一时间能够做多件事情;\n(2)当进程遇到阻塞时,例如等待输入,线程能够使不依赖输入数据的工作继续执行\n(3)可以有效地利用多处理器和多核计算机,在没有线程之前,多核并不能让一个进程的执行速度提高\n进程是什么? 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。\n在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。\n有了进程为什么还要线程? 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:\n 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。 如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还 要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时 候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而 我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。\n现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。\n线程的优点 因为要并发,我们发明了进程,又进一步发明了线程。只不过进程和线程的并发层次不同:进程属于在处理器这一层上提供的抽象;线程则属于在进程这个层 次上再提供了一层并发的抽象。如果我们进入计算机体系结构里,就会发现,流水线提供的也是一种并发,不过是指令级的并发。这样,流水线、线程、进程就从低 到高在三个层次上提供我们所迫切需要的并发!\n除了提高进程的并发度,线程还有个好处,就是可以有效地利用多处理器和多核计算机。现在的处理器有个趋势就是朝着多核方向发展,在没有线程之前,多 核并不能让一个进程的执行速度提高,原因还是上面所有的两点限制。但如果讲一个进程分解为若干个线程,则可以让不同的线程运行在不同的核上,从而提高了进 程的执行速度。\n例如:我们经常使用微软的Word进行文字排版,实际上就打开了多个线程。这些线程一个负责显示,一个接受键盘的输入,一个进行存盘等等。这些线程 一起运行,让我们感觉到我们输入和屏幕显示同时发生,而不是输入一些字符,过一段时间才能看到显示出来。在我们不经意间,还进行了自动存盘操作。这就是线 程给我们带来的方便之处。\n进程与线程的区别 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程 只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线 程的程序 健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。\n注:前一阵子去淘宝面试,面试官就问了我这个问题:进程与线程的区别是什么?我当时说了一大堆,但感觉还是没说关键的点上,最后他又问最本质的区别是什么?我傻了一会,难道刚我说的没有说到吗?嘿嘿,确实有点囧啊~~\n原文链接:\nhttps://www.cnblogs.com/Berryxiong/p/6429723.html\n参考文献:\n【1】 邹恒明. 计算机的心智 操作系统之哲学原理. 机械工业出版社\n【2】 Andrew著 陈向群译. 现代操作系统. 机械工业出版社\n【3】 http://blog.csdn.net/zengjibing/archive/2009/02/22/3923357.aspx\n","permalink":"https://jsharkc.github.io/post/%E6%9C%89%E8%BF%9B%E7%A8%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%98%E8%A6%81%E7%BA%BF%E7%A8%8B/","summary":"有进程为什么还创造线程 原因: 进程属于在CPU和系统资源等方面提供的抽象,能够有效提高CPU的利用率。\n线程是在进程这个层次上提供的一层并发的抽象:\n(1)能够使系统在同一时间能够做多件事情;\n(2)当进程遇到阻塞时,例如等待输入,线程能够使不依赖输入数据的工作继续执行\n(3)可以有效地利用多处理器和多核计算机,在没有线程之前,多核并不能让一个进程的执行速度提高\n进程是什么? 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。\n在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。\n有了进程为什么还要线程? 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:\n 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。 如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还 要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时 候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而 我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。\n现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。\n线程的优点 因为要并发,我们发明了进程,又进一步发明了线程。只不过进程和线程的并发层次不同:进程属于在处理器这一层上提供的抽象;线程则属于在进程这个层 次上再提供了一层并发的抽象。如果我们进入计算机体系结构里,就会发现,流水线提供的也是一种并发,不过是指令级的并发。这样,流水线、线程、进程就从低 到高在三个层次上提供我们所迫切需要的并发!\n除了提高进程的并发度,线程还有个好处,就是可以有效地利用多处理器和多核计算机。现在的处理器有个趋势就是朝着多核方向发展,在没有线程之前,多 核并不能让一个进程的执行速度提高,原因还是上面所有的两点限制。但如果讲一个进程分解为若干个线程,则可以让不同的线程运行在不同的核上,从而提高了进 程的执行速度。\n例如:我们经常使用微软的Word进行文字排版,实际上就打开了多个线程。这些线程一个负责显示,一个接受键盘的输入,一个进行存盘等等。这些线程 一起运行,让我们感觉到我们输入和屏幕显示同时发生,而不是输入一些字符,过一段时间才能看到显示出来。在我们不经意间,还进行了自动存盘操作。这就是线 程给我们带来的方便之处。\n进程与线程的区别 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程 只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线 程的程序 健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。\n注:前一阵子去淘宝面试,面试官就问了我这个问题:进程与线程的区别是什么?我当时说了一大堆,但感觉还是没说关键的点上,最后他又问最本质的区别是什么?我傻了一会,难道刚我说的没有说到吗?嘿嘿,确实有点囧啊~~\n原文链接:\nhttps://www.cnblogs.com/Berryxiong/p/6429723.html\n参考文献:\n【1】 邹恒明. 计算机的心智 操作系统之哲学原理. 机械工业出版社\n【2】 Andrew著 陈向群译. 现代操作系统. 机械工业出版社\n【3】 http://blog.csdn.net/zengjibing/archive/2009/02/22/3923357.aspx","title":"有进程为什么还创造线程"},{"content":"查看、安装 zsh 查看是否安装了 zsh\n# 方法一: chsh -l # 方法二: cat /etc/shells # 可能结果: /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bin/bash /usr/sbin/nologin /bin/zsh # 如果有 /bin/zsh 代表已经安装,反之则没有 安装 zsh\nyum install -y zsh 切换 shell 为 zsh\nchsh -s /bin/zsh 安装 oh-my-zsh 安装需要 git,没有安装需要先安装:\nyum install -y git 1、可以通过别人已经写好的脚本安装,用 curl 或者 wget 下载脚本来安装:\n 通过 curl sh -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 通过 wget sh -c \u0026#34;$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 2、也可以自己用 git 安装\ngit clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc # 国内用户可通过以下命令: git clone https://gitee.com/mirrors/oh-my-zsh.git ~/.oh-my-zsh cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc; 查看、修改主题 可以选择自己喜欢的主题,查看主题命令如下:\nls ~/.oh-my-zsh/themes 修改主题\nvim ~/.zshrc # 找到 ZSH_THEME 行,修改为自己想用的主题名称即可 默认的主题是 ZSH_THEME=\u0026quot;robbyrussell\u0026quot; ,改成自己喜欢的即可,也可以用我自定义的一个主题,安装方法:\nsh -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Jsharkc/jacobin-zsh-theme/master/install.sh)\u0026#34; 国内用户: sh -c \u0026#34;$(curl -fsSL https://gitee.com/jsharkc/jacobin-zsh-theme/raw/master/install.sh)\u0026#34; source ~/.zshrc 截图\n安装想用的插件 我想安装「自动补全」和「语法高亮」插件\n自动补全插件 zsh-autosuggestions 下载该插件到.oh-my-zsh的插件目录 git clone https://github.com/zsh-users/zsh-autosuggestions.git $ZSH_CUSTOM/plugins/zsh-autosuggestions 国内用户: git clone https://gitee.com/Coxhuang/zsh-autosuggestions.git $ZSH_CUSTOM/plugins/zsh-autosuggestions 编辑.zshrc文件 找到 plugins= 这一行,添加 zsh-autosuggestions,例如:\nplugins=( git zsh-autosuggestions ) 使插件生效 source ~/.zshrc 语法高亮插件 zsh-syntax-highlighting 下载该插件到.oh-my-zsh的插件目录 git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting 国内用户: git clone https://gitee.com/Coxhuang/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting 编辑.zshrc文件 找到 plugins= 这一行,添加 zsh-syntax-highlighting,例如:\nplugins=( git zsh-autosuggestions zsh-syntax-highlighting ) 使插件生效 source ~/.zshrc 想了解更多 oh-my-zsh 内容请前往 oh-my-zsh\n","permalink":"https://jsharkc.github.io/post/centos-install-oh-my-zsh/","summary":"查看、安装 zsh 查看是否安装了 zsh\n# 方法一: chsh -l # 方法二: cat /etc/shells # 可能结果: /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bin/bash /usr/sbin/nologin /bin/zsh # 如果有 /bin/zsh 代表已经安装,反之则没有 安装 zsh\nyum install -y zsh 切换 shell 为 zsh\nchsh -s /bin/zsh 安装 oh-my-zsh 安装需要 git,没有安装需要先安装:\nyum install -y git 1、可以通过别人已经写好的脚本安装,用 curl 或者 wget 下载脚本来安装:\n 通过 curl sh -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 通过 wget sh -c \u0026#34;$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 2、也可以自己用 git 安装","title":"Centos 安装 oh-my-zsh"},{"content":"文章转自 https://www.jianshu.com/p/4f0573f3c5db\n步骤 下载Charles安装包 双击dmg文件,将charles拖入应用程序中 在应用程序中右键 Charles,选择「显示包内容」,打开目录「Contents」=\u0026gt; 「Java」,用下载的 charles.jar (在 dmg 文件中) 替换目录中的 charles.jar 打开 Charles,在菜单中找到 Help =\u0026gt; Register Charles\u0026hellip;,随便输入信息完成注册 重启 Charles 可能遇到的问题 1. 打不开xxx软件,因为它不是从app store下载的 解决方法:\n左上角苹果标志 =\u0026gt; 系统偏好设置 =\u0026gt; 安全性与隐私\n选择\u0026quot;仍要打开\u0026quot;,就可以安装了\n2. 破解之后显示软件已损坏 其实并没有损坏,只是软件来自身份不明的开发者(见上图),然后苹果就告诉你,它是坏的\u0026hellip;..\n解决方法:\n打开终端,输入命令: sudo spctl --master-disable 会让你输入密码,输入后按回车就好 再看一下安全性与隐私里面\n多出一个任何来源,现在打开软件就没有任何问题了\n","permalink":"https://jsharkc.github.io/post/mac-install-charles/","summary":"文章转自 https://www.jianshu.com/p/4f0573f3c5db\n步骤 下载Charles安装包 双击dmg文件,将charles拖入应用程序中 在应用程序中右键 Charles,选择「显示包内容」,打开目录「Contents」=\u0026gt; 「Java」,用下载的 charles.jar (在 dmg 文件中) 替换目录中的 charles.jar 打开 Charles,在菜单中找到 Help =\u0026gt; Register Charles\u0026hellip;,随便输入信息完成注册 重启 Charles 可能遇到的问题 1. 打不开xxx软件,因为它不是从app store下载的 解决方法:\n左上角苹果标志 =\u0026gt; 系统偏好设置 =\u0026gt; 安全性与隐私\n选择\u0026quot;仍要打开\u0026quot;,就可以安装了\n2. 破解之后显示软件已损坏 其实并没有损坏,只是软件来自身份不明的开发者(见上图),然后苹果就告诉你,它是坏的\u0026hellip;..\n解决方法:\n打开终端,输入命令: sudo spctl --master-disable 会让你输入密码,输入后按回车就好 再看一下安全性与隐私里面\n多出一个任何来源,现在打开软件就没有任何问题了","title":"Mac安装破解版 Charles"},{"content":"有个闲置的「红米Note4」,回收也值不了几个钱,就想着是否能再利用一下,网上查了查,用 Termux 可以把 Android 手机当成 linux 服务器用,于是就有了接下来的部分了。\n下载 Termux Termux 下载地址\n下载之后安装即可,不 root 也可以用,但是很多目录没权限访问,所以能 root 还是 root 一下。\n配置 Termux Termux 自带 apt 包管理器,进行更新,安装 ssh 和用户管理模块\napt update apt upgrade apt install openssh pkg install termux-auth 查看用户名,ip,设置密码\nwhoami # 结果为:u0_a150,你的可能不一样,用自己的 ifconfig # 找到 inet addr,我的是 192.168.0.104,也是用你自己的 passwd # 这个是设置密码 sshd -p 9999 # 让 ssh 监听 9999 端口 设置好后,用电脑登录\nssh [email protected] -p 9999 # 回车,输入密码就行了 之后通过 apt、pkg 安装 git、golang 等,就成服务器了,还可以通过 termux-setup-storage 插件把手机目录挂载到 /data/data/com.termux/files/home/storage/shared目录下,之后就可以随意操作了。用法是在命令行输入以下命令即可:\ntermux-setup-storage 当然如果你有公网服务器,还可以通过 frp 内网穿透,就可以在公网访问你的 Android 服务器了\n利用 frp 内网穿透 安装 Golang\npkg install golang frp 下载地址\nAndroid 下载以arm结尾的,服务器端根据自己的服务器选。\n写个 Hello world http 服务\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello World\u0026#34;) }) http.ListenAndServe(\u0026#34;:8085\u0026#34;, nil) } 配置 frp 服务端 # frps.ini [common] bind_port = 7001 # frp 服务绑定的端口 vhost_http_port = 8082 # 外网可访问的 web 服务端口 配置 frp 客户端 # frpc.ini [common] server_addr = xx.xx.xx.xx # 你的服务器地址 server_port = 7001 # 服务器绑定的端口,与服务端配置 bind_port 一致 [ssh] type = tcp local_ip = 127.0.0.1 local_port = 9999 # 本地 ssh 监听端口 remote_port = 6000 # 远程连接用的端口 [web] type = http local_port = 8085 # 本地 web 服务监听端口 custom_domains = xx.xx.xx # 服务器域名,有域名写域名,没域名写 ip 运行 frp 服务端\n./frps -c ./frps.ini 客户端\n./frpc -c ./frpc.ini 测试\ncurl http://xx.xx.xx:8082 # 可以看到 Hello world ssh 连接 ssh [email protected] -oPort=6000 格式:ssh [Android用户名]@[服务器地址] -oPort=[frpc.ini 中 ssh 下 remote_port]\n安装发行版 linux 可以再 Termux 上安装发行版 linux,包括 fedora、debian、alpine、aosc、arch、ubuntu、centos。\n安装 atilo echo \u0026quot;deb [trusted=yes] https://yadominjinta.github.io/files/ termux extras\u0026quot; \u0026gt;\u0026gt; $PREFIX/etc/apt/sources.list pkg in atilo-cn 安装系统 atilo install centos 删除系统 atilo remove centos ","permalink":"https://jsharkc.github.io/post/android-phone-as-linux-server/","summary":"有个闲置的「红米Note4」,回收也值不了几个钱,就想着是否能再利用一下,网上查了查,用 Termux 可以把 Android 手机当成 linux 服务器用,于是就有了接下来的部分了。\n下载 Termux Termux 下载地址\n下载之后安装即可,不 root 也可以用,但是很多目录没权限访问,所以能 root 还是 root 一下。\n配置 Termux Termux 自带 apt 包管理器,进行更新,安装 ssh 和用户管理模块\napt update apt upgrade apt install openssh pkg install termux-auth 查看用户名,ip,设置密码\nwhoami # 结果为:u0_a150,你的可能不一样,用自己的 ifconfig # 找到 inet addr,我的是 192.168.0.104,也是用你自己的 passwd # 这个是设置密码 sshd -p 9999 # 让 ssh 监听 9999 端口 设置好后,用电脑登录\nssh [email protected] -p 9999 # 回车,输入密码就行了 之后通过 apt、pkg 安装 git、golang 等,就成服务器了,还可以通过 termux-setup-storage 插件把手机目录挂载到 /data/data/com.","title":"Android手机作为linux服务器"},{"content":"文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。\n文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于 UNIX、Linux 这样的操作系统。\nLinux下两个进程打开同一个文件返回的文件描述符一样吗? 答案:不一样\n 两个进程中分别产生生成两个独立的fd 两个进程可以任意对文件进行读写操作,操作系统并不保证写的原子性 进程可以通过系统调用对文件加锁,从而实现对文件内容的保护 任何一个进程删除该文件时,另外一个进程不会立即出现读写失败 两个进程可以分别读取文件的不同部分而不会相互影响 一个进程对文件长度和内容的修改另外一个进程可以立即感知 文件描述符与打开文件的关系 内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。 文件描述表中每一项都是一个指针,指向一个用于描述打开的文件的数据块——file对象,file对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的file对象。 file对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的file对象,从而共享这个打开的文件。 file对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同一个file对象的进程。 参考文章:\n linux下两个进程可以同时打开同一个文件吗?返回的文件描述符一样吗? Linux IO模式及 select、poll、epoll详解 ","permalink":"https://jsharkc.github.io/post/linux-file-descriptor/","summary":"文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。\n文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于 UNIX、Linux 这样的操作系统。\nLinux下两个进程打开同一个文件返回的文件描述符一样吗? 答案:不一样\n 两个进程中分别产生生成两个独立的fd 两个进程可以任意对文件进行读写操作,操作系统并不保证写的原子性 进程可以通过系统调用对文件加锁,从而实现对文件内容的保护 任何一个进程删除该文件时,另外一个进程不会立即出现读写失败 两个进程可以分别读取文件的不同部分而不会相互影响 一个进程对文件长度和内容的修改另外一个进程可以立即感知 文件描述符与打开文件的关系 内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。 文件描述表中每一项都是一个指针,指向一个用于描述打开的文件的数据块——file对象,file对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的file对象。 file对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的file对象,从而共享这个打开的文件。 file对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同一个file对象的进程。 参考文章:\n linux下两个进程可以同时打开同一个文件吗?返回的文件描述符一样吗? Linux IO模式及 select、poll、epoll详解 ","title":"文件描述符 fd"},{"content":"有时 update 更新语句会需要根据另一个表进行更新,举例如下:\n-- 方式一: update tableA a, tableB b set a.Name=b.Name, a.Age=b.Age where a.IDCard=b.IDCard; -- 方式二: update tableA a inner join tableB b on a.IDCard=b.IDCard set a.Name=b.Name, a.Age=b.Age; ","permalink":"https://jsharkc.github.io/post/mysql-update-%E7%BB%93%E5%90%88%E5%8F%A6%E4%B8%80%E4%B8%AA%E8%A1%A8%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE/","summary":"有时 update 更新语句会需要根据另一个表进行更新,举例如下:\n-- 方式一: update tableA a, tableB b set a.Name=b.Name, a.Age=b.Age where a.IDCard=b.IDCard; -- 方式二: update tableA a inner join tableB b on a.IDCard=b.IDCard set a.Name=b.Name, a.Age=b.Age; ","title":"Mysql update 结合另一个表更新数据"},{"content":"Flutter 返回按钮返回桌面 想实现点击返回按钮,直接返回桌面,本想 flutter 有方法的话,直接用,然而好像没有,所以采用调用本地方法返回桌面\nAndroid 端 MainActivity 代码如下 package com.dreamreal.example; import android.os.Bundle; import io.flutter.app.FlutterActivity; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { // 字符串常量,回到手机桌面 private final String chanel = \u0026#34;back/desktop\u0026#34;; // 返回到桌面事件 static final String backDesktopEvent = \u0026#34;backDesktop\u0026#34;; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); MethodChannel(getFlutterView(), chanel).setMethodCallHandler( (methodCall, result) -\u0026gt; { if (methodCall.method.equals(backDesktopEvent)) { moveTaskToBack(false); result.success(true); } } ); } } 如果是 kotlin 的话,如下:\npackage com.dreamreal.example import android.os.Bundle import io.flutter.app.FlutterActivity import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { // 字符串常量,回到手机桌面 private final val channel = \u0026#34;back/desktop\u0026#34;; // 返回到桌面事件 private final val backDesktopEvent = \u0026#34;backDesktop\u0026#34;; override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) MethodChannel(flutterView, channel).setMethodCallHandler { methodCall, result -\u0026gt; if (methodCall.method.equals(backDesktopEvent)) { moveTaskToBack(false); result.success(true); } } } } Flutter 端代码如下 import \u0026#39;package:flutter/services.dart\u0026#39;; import \u0026#39;package:flutter/material.dart\u0026#39;; class BackDesktop { // 字符串常量,回到手机桌面 static const String chanel = \u0026#34;back/desktop\u0026#34;; // 返回到桌面事件 static const String backDesktopEvent = \u0026#34;backDesktop\u0026#34;; // 返回到桌面方法 static Future\u0026lt;bool\u0026gt; backDesktop() async { final platform = MethodChannel(chanel); try { await platform.invokeMethod(backDesktopEvent); } on PlatformException catch (e) { debugPrint(e.toString()); } return Future.value(false); } } 调用时如下 WillPopScope( onWillPop: BackDesktop.backDesktop, child: 「这里是你需要拦截返回按钮的页面」 ); ","permalink":"https://jsharkc.github.io/post/flutter-%E8%BF%94%E5%9B%9E%E6%A1%8C%E9%9D%A2/","summary":"Flutter 返回按钮返回桌面 想实现点击返回按钮,直接返回桌面,本想 flutter 有方法的话,直接用,然而好像没有,所以采用调用本地方法返回桌面\nAndroid 端 MainActivity 代码如下 package com.dreamreal.example; import android.os.Bundle; import io.flutter.app.FlutterActivity; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { // 字符串常量,回到手机桌面 private final String chanel = \u0026#34;back/desktop\u0026#34;; // 返回到桌面事件 static final String backDesktopEvent = \u0026#34;backDesktop\u0026#34;; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); MethodChannel(getFlutterView(), chanel).setMethodCallHandler( (methodCall, result) -\u0026gt; { if (methodCall.method.equals(backDesktopEvent)) { moveTaskToBack(false); result.success(true); } } ); } } 如果是 kotlin 的话,如下:","title":"Flutter 返回按钮返回桌面"},{"content":"Install v2ray-core step 1: Add official tap\nbrew tap v2ray/v2ray step 2: Install v2ray-core:\nbrew install v2ray-core Update v2ray-core step 1: update tap\nbrew update step 2: update v2ray-core\nbrew upgrade v2ray-core 使用 直接在命令行上输入 v2ray 就可以运行 v2ray-core。(配置文件在当前目录则不用写参数,直接 v2ray)\n默认配置文件位于:/usr/local/etc/config.json\n编辑默认配置文件:\nvim /usr/local/etc/config.json 配置文件也可以从 http://free-ss.tk/或者http://free-ss.tk/ 下载:\nconfig.json 添加 http 代理:\n\u0026#34;inboundDetour\u0026#34;: [{ \u0026#34;protocol\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;port\u0026#34;: 1081, \u0026#34;settings\u0026#34;: { \u0026#34;udp\u0026#34;: true } }], .zshrc 添加\n# proxy alias proxy='export https_proxy=http://127.0.0.1:1081;export http_proxy=https://127.0.0.1:1081;export socks5_proxy=socks5://127.0.0.1:1080' alias unproxy='unset https_proxy http_proxy socks5_proxy' 然后配置一下 chrome 插件 Proxy SwitchyOmega\nOK,现在可以冲浪了!\n","permalink":"https://jsharkc.github.io/post/v2ray%E4%BD%BF%E7%94%A8/","summary":"Install v2ray-core step 1: Add official tap\nbrew tap v2ray/v2ray step 2: Install v2ray-core:\nbrew install v2ray-core Update v2ray-core step 1: update tap\nbrew update step 2: update v2ray-core\nbrew upgrade v2ray-core 使用 直接在命令行上输入 v2ray 就可以运行 v2ray-core。(配置文件在当前目录则不用写参数,直接 v2ray)\n默认配置文件位于:/usr/local/etc/config.json\n编辑默认配置文件:\nvim /usr/local/etc/config.json 配置文件也可以从 http://free-ss.tk/或者http://free-ss.tk/ 下载:\nconfig.json 添加 http 代理:\n\u0026#34;inboundDetour\u0026#34;: [{ \u0026#34;protocol\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;port\u0026#34;: 1081, \u0026#34;settings\u0026#34;: { \u0026#34;udp\u0026#34;: true } }], .zshrc 添加\n# proxy alias proxy='export https_proxy=http://127.0.0.1:1081;export http_proxy=https://127.0.0.1:1081;export socks5_proxy=socks5://127.0.0.1:1080' alias unproxy='unset https_proxy http_proxy socks5_proxy' 然后配置一下 chrome 插件 Proxy SwitchyOmega","title":"Mac 使用 V2ray"},{"content":"create-react-app 脚手架添加 less 支持和 antd 样式按需加载 1. 创建项目 npm install -g create-react-app /* 安装create-react-app,建议使用cnpm */ create-react-app react-test /* 使用命令创建应用,myapp为项目名称 */ cd react-test /* 进入目录,然后启动 */ npm start 2.create-react-app 把 webpack 配置文件暴露出来 create-react-app 生成的项目文,看不到webpack相关的配置文件,需要先暴露出来,使用如下命令即可:\nnpm run eject 3. 添加 babel-plugin-import babel-plugin-import 是一个用于 按需加载 组件代码和样式 的 babel 插件(原理)。\nnpm install babel-plugin-import --save-dev 4.添加 less 、less-loader npm install less less-loader --save-dev 5.修改 webpack 配置文件 修改 webpack.config.dev.js 和 webpack.config-prod.js 配置文件\n test: /\\.css$/ 改为 /\\.(css|less)$/ test: /\\.css$/ 的 use 数组配置增加 less-loader { test: /\\.(css|less)$/, use: [ require.resolve(\u0026#39;style-loader\u0026#39;), { loader: require.resolve(\u0026#39;css-loader\u0026#39;), options: { importLoaders: 1, }, }, { loader: require.resolve(\u0026#39;postcss-loader\u0026#39;), options: { // Necessary for external CSS imports to work // https://github.com/facebookincubator/create-react-app/issues/2677 ident: \u0026#39;postcss\u0026#39;, plugins: () =\u0026gt; [ require(\u0026#39;postcss-flexbugs-fixes\u0026#39;), autoprefixer({ browsers: [ \u0026#39;\u0026gt;1%\u0026#39;, \u0026#39;last 4 versions\u0026#39;, \u0026#39;Firefox ESR\u0026#39;, \u0026#39;not ie \u0026lt; 9\u0026#39;, // React doesn\u0026#39;t support IE8 anyway ], flexbox: \u0026#39;no-2009\u0026#39;, }), ], }, }, { loader: require.resolve(\u0026#39;less-loader\u0026#39;) // compiles Less to CSS } ], }, 6.修改 package.json 文件,添加 .babelrc 文件 package.json\n\u0026#34;babel\u0026#34;: { \u0026#34;presets\u0026#34;: [ \u0026#34;react-app\u0026#34; ], \u0026#34;plugins\u0026#34;: [ [ \u0026#34;import\u0026#34;, { \u0026#34;libraryName\u0026#34;: \u0026#34;antd\u0026#34;, \u0026#34;style\u0026#34;: true } ] ] }, .babelrc\n{ \u0026#34;presets\u0026#34;: [ \u0026#34;react-app\u0026#34; ], \u0026#34;plugins\u0026#34;: [ [ \u0026#34;import\u0026#34;, { \u0026#34;libraryName\u0026#34;: \u0026#34;antd\u0026#34;, \u0026#34;style\u0026#34;: true } ] ] } 然后就可以了,哈哈哈!\n","permalink":"https://jsharkc.github.io/post/create-react-app-add-less-and-antd-%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD/","summary":"create-react-app 脚手架添加 less 支持和 antd 样式按需加载 1. 创建项目 npm install -g create-react-app /* 安装create-react-app,建议使用cnpm */ create-react-app react-test /* 使用命令创建应用,myapp为项目名称 */ cd react-test /* 进入目录,然后启动 */ npm start 2.create-react-app 把 webpack 配置文件暴露出来 create-react-app 生成的项目文,看不到webpack相关的配置文件,需要先暴露出来,使用如下命令即可:\nnpm run eject 3. 添加 babel-plugin-import babel-plugin-import 是一个用于 按需加载 组件代码和样式 的 babel 插件(原理)。\nnpm install babel-plugin-import --save-dev 4.添加 less 、less-loader npm install less less-loader --save-dev 5.修改 webpack 配置文件 修改 webpack.config.dev.js 和 webpack.config-prod.js 配置文件\n test: /\\.css$/ 改为 /\\.","title":"create-react-app 脚手架添加 less 支持和 antd 样式按需加载"},{"content":"Cobra - Golang 命令行库 简介: Cobra 是一个创建 CLI 命令行的 golang 库。\n组成: Cobra 结构由三部分组成:命令( Command )、参数( Args )、标志( Flag )。\ntype Command struct { Use string // The one-line usage message. Short string // The short description shown in the \u0026#39;help\u0026#39; output. Long string // The long message shown in the \u0026#39;help\u0026lt;this-command\u0026gt;\u0026#39; output. Run func(cmd *Command, args []string) // Run runs the command. } 前三个是不同场景下的说明,最后一个是要执行的函数。\n安装 安装 Cobra 很简单,首先,用 go get 安装最新版本的库,这个命令会安装 Cobra 框架生成工具和依赖。\ngo get -u github.com/spf13/cobra/cobra 然后,把 Cobra 添加到你的 app 中:\nimport \u0026#34;github.com/spf13/cobra\u0026#34; 快速开始 一般用 cobra 命令生成d的项目结构如下:\n▾ appName/ ▾ cmd/ add.go your.go commands.go here.go main.go main 函数中非常简洁,只有一个目的:初始化 Cobra.\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;{pathToYourApp}/cmd\u0026#34; ) func main() { if err := cmd.RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } 用 Cobra 工具生成项目框架 cobra init cobra init [yourAppName] ,这个命令可以让你的程序有一个正确的结构,你立刻能够感受到 Cobra 带给你的快乐,你可以配置它自动生成你需要的开源协议。\ncobra init newAppName newAppName 是你的项目名称。它会在你的 GOPATH 目录下面生成项目。\n我们看一下 main.go 函数\npackage main import \u0026#34;cobra_exp1/cmd\u0026#34; func main() { cmd.Execute() } main 调用 cmd.Execute(),那我们找到这个地方,cmd/root.go 文件:\npackage cmd import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) var cfgFile string // RootCmd represents the base command when called without any subcommands var RootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;cobra_exp1\u0026#34;, Short: \u0026#34;A brief description of your application\u0026#34;, Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) } } func init() { cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags, which, if defined here, // will be global for your application. RootCmd.PersistentFlags().StringVar(\u0026amp;cfgFile, \u0026#34;config\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (default is $HOME/.cobra_exp1.yaml)\u0026#34;) // Cobra also supports local flags, which will only run // when this action is called directly. RootCmd.Flags().BoolP(\u0026#34;toggle\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;Help message for toggle\u0026#34;) } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != \u0026#34;\u0026#34; { // enable ability to specify config file via flag viper.SetConfigFile(cfgFile) } viper.SetConfigName(\u0026#34;.cobra_exp1\u0026#34;) // name of config file (without extension) viper.AddConfigPath(\u0026#34;$HOME\u0026#34;) // adding home directory as first search path viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Println(\u0026#34;Using config file:\u0026#34;, viper.ConfigFileUsed()) } } 我们看到 Execute() 函数中调用 RootCmd.Execute(),RootCmd 是开始讲组成 Command 结构的一个实例。\n我们运行看看:\n\u0026gt; go run main.go A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application. 空空如也,还什么也没有,那接下来我们来添加一些子命令。\ncobra add 这个命令用来创建子命令,子命令就是像下面这样:\n app serve app config app config create 在你项目的目录下,运行下面这些命令:\ncobra add serve cobra add config cobra add create -p \u0026#39;configCmd\u0026#39; 这样以后,你就可以运行上面那些 app serve 之类的命令了。项目目录如下:\n▾ app/ ▾ cmd/ serve.go config.go create.go main.go 然后再运行程序:\n❯ go run main.go A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application. Usage: llleon [command] Available Commands: config A brief description of your command help Help about any command serve A brief description of your command Flags: --config string config file (default is $HOME/.llleon.yaml) -h, --help help for llleon -t, --toggle Help message for toggle Use \u0026#34;llleon [command] --help\u0026#34; for more information about a command. 现在我们有了三个子命令,并且都可以使用,然后只要添加命令逻辑就能真正用了。\nFlag cobra 有两种 flag,一个是全局变量,一个是局部变量。全局什么意思呢,就是所以子命令都可以用。局部的只有自己能用。先看全局的:\nRootCmd.PersistentFlags().StringVar(\u0026amp;cfgFile, \u0026#34;config\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (default is $HOME/.cobra_exp1.yaml)\u0026#34;) 在看局部的:\nRootCmd.Flags().BoolP(\u0026#34;toggle\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;Help message for toggle\u0026#34;) 区别就在 RootCmd 后面的是 Flags 还是 PersistentFlags。\n好了,入门教程到此结束,感兴趣的童鞋可以到 Cobra 深入研究一番。\n","permalink":"https://jsharkc.github.io/post/cobra%E5%85%A5%E9%97%A8%E5%B0%8F%E6%95%99%E7%A8%8B/","summary":"Cobra - Golang 命令行库 简介: Cobra 是一个创建 CLI 命令行的 golang 库。\n组成: Cobra 结构由三部分组成:命令( Command )、参数( Args )、标志( Flag )。\ntype Command struct { Use string // The one-line usage message. Short string // The short description shown in the \u0026#39;help\u0026#39; output. Long string // The long message shown in the \u0026#39;help\u0026lt;this-command\u0026gt;\u0026#39; output. Run func(cmd *Command, args []string) // Run runs the command. } 前三个是不同场景下的说明,最后一个是要执行的函数。\n安装 安装 Cobra 很简单,首先,用 go get 安装最新版本的库,这个命令会安装 Cobra 框架生成工具和依赖。","title":"Cobra - 一个 Golang 命令行项目生成工具"},{"content":"Golang slice 切片原理 \tgolang 中的 slice 是比较好用的一种结构,能根据需求变长,相对于 array 的死板,slice 更加灵活也更加常用,有道说:知其然,知其所以然。现在,我们就看看 slice 到底是怎样一种结构。\nslice源码 type slice struct { array unsafe.Pointer len int cap int } \t根据平常使用情况,我们推测 len 是 slice 长度,cap 是 slice 的容量,而 array 则是指向底层数组的指针。\n\t有同学可能不知道 unsafe.Pointer是什么,那么我们一并在这讲解一下。\npackage unsafe type ArbitraryType int //\t- A pointer value of any type can be converted to a Pointer. //\t- A Pointer can be converted to a pointer value of any type. //\t- A uintptr can be converted to a Pointer. //\t- A Pointer can be converted to a uintptr. type Pointer *ArbitraryType func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr unsafe.Pointer:一个指向 int类型的指针,通常用于转换不同类型的指针,go 语言中指针不能运算。\nuintptr:内置类型,能存储指针的整型,底层类型是int,可以和unsafe.Pointer互相转换,因为就\n\t是 int型,所以可以用来做运算,GC 不会把它当指针,所以不持有对象,会被回收。\n上面的四句注释是什么意思呢,解释一下就是:\n 任何类型的指针可转换成一个 Pointer 类型的值。 Pointer 类型可以转换成任何类型的指针。 uintptr 类型可转换成 Pointer 类型。 Pointer 类型可以转换成 uintptr 类型。 同学们有没有想到一些什么?就是说,go 语言中虽然没有指针运算,但是通过 unsafe 包,可以把指针转换成\nunsafe.Pointer,再转换成 uintptr,之后就能和 C 语言指针运算类似的功能了。举个例子:\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;unsafe\u0026#34; ) type Entity struct { a\tbyte b\tbyte } func main() { entity := Entity{} fmt.Println(entity)\tp := unsafe.Pointer(\u0026amp;entity)\t// 转换成通用指针 unsafe.Pointer 类型 \tuintpa := uintptr(p)\t// 保存结构体 entity 实例地址,偏移量为0 \tpb := (*byte)(p)\t// 将通用指针转换为 byte 型指针 \t*pb = 10\t// 给转换后的指针赋值 \tfmt.Println(entity)\t// 结构体内容跟着改变 uintpb := uintpa + unsafe.Offsetof(entity.b)\t// 偏移到 entity.b 字段开始的位置 \tp = unsafe.Pointer(uintpb)\t// 将偏移后的地址转换为通用指针 unsafe.Pointer 类型 \tpb = (*byte)(p)\t// 将通用指针转换为 byte 型指针 \t*pb = 20\t// 给转换后的指针赋值 \tfmt.Println(entity)\t// 结构体内容跟着改变 } // 结果: // {0 0} // {10 0} // {10 20} 好,unsafe.Pointer 就告一段落,继续讲我们的 slice。\n\t[图片取自 the way to go ,侵权请告知,删]\n通过 make 创建切片 make 创建切片的源码:\nfunc makeslice(et *_type, len, cap int) slice { // 根据类型获取此类型能包含元素的最大长度 \tmaxElements := maxSliceCap(et.size) // 比较容量和长度 若比零小或比最大值大,越界 \tif len \u0026lt; 0 || uintptr(len) \u0026gt; maxElements { panic(errorString(\u0026#34;makeslice: len out of range\u0026#34;)) } if cap \u0026lt; len || uintptr(cap) \u0026gt; maxElements { panic(errorString(\u0026#34;makeslice: cap out of range\u0026#34;)) } // 向内存申请一块此类型 array 的空间 \tp := mallocgc(et.size*uintptr(cap), et, true) // 将指针、长度、容量赋值并返回切片结构 \treturn slice{p, len, cap} } 切片增长 通过 append 可以对切片扩容,源码看下面:\nfunc growslice(et *_type, old slice, cap int) slice { if raceenabled { callerpc := getcallerpc(unsafe.Pointer(\u0026amp;et)) racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice)) } if msanenabled { msanread(old.array, uintptr(old.len*int(et.size))) } if et.size == 0 { if cap \u0026lt; old.cap { panic(errorString(\u0026#34;growslice: cap out of range\u0026#34;)) } // 创建一个不为nil的切片 \treturn slice{unsafe.Pointer(\u0026amp;zerobase), old.len, cap} } newcap := old.cap doublecap := newcap + newcap if cap \u0026gt; doublecap { newcap = cap } else { if old.len \u0026lt; 1024 { newcap = doublecap } else { for newcap \u0026lt; cap { newcap += newcap / 4 } } } var lenmem, newlenmem, capmem uintptr const ptrSize = unsafe.Sizeof((*byte)(nil)) switch et.size { case 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) newcap = int(capmem) case ptrSize: lenmem = uintptr(old.len) * ptrSize newlenmem = uintptr(cap) * ptrSize capmem = roundupsize(uintptr(newcap) * ptrSize) newcap = int(capmem / ptrSize) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem = roundupsize(uintptr(newcap) * et.size) newcap = int(capmem / et.size) } if cap \u0026lt; old.cap || uintptr(newcap) \u0026gt; maxSliceCap(et.size) { panic(errorString(\u0026#34;growslice: cap out of range\u0026#34;)) } var p unsafe.Pointer if et.kind\u0026amp;kindNoPointers != 0 { p = mallocgc(capmem, nil, false) memmove(p, old.array, lenmem) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { p = mallocgc(capmem, et, true) if !writeBarrier.enabled { memmove(p, old.array, lenmem) } else { for i := uintptr(0); i \u0026lt; lenmem; i += et.size { typedmemmove(et, add(p, i), add(old.array, i)) } } } return slice{p, old.len, newcap} } 切片在append的时候如果有额外的容量可用,append将可用的元素合并到切片的长度,然后对他进行赋值,如果没有可用的容量,append会创建新的底层数组,将现有的值复制到新的数组里再追加新的值。\n切片复制 通过 copy 可以复制一个切片,源码如下:\nfunc slicecopy(to, fm slice, width uintptr) int { if fm.len == 0 || to.len == 0 { return 0 } n := fm.len if to.len \u0026lt; n { n = to.len } if width == 0 { return n } if raceenabled { callerpc := getcallerpc(unsafe.Pointer(\u0026amp;to)) pc := funcPC(slicecopy) racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc) racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc) } if msanenabled { msanwrite(to.array, uintptr(n*int(width))) msanread(fm.array, uintptr(n*int(width))) } size := uintptr(n) * width if size == 1 { *(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer \t} else { memmove(to.array, fm.array, size) } return n } copy切片会把源切片值(第二个参数值)中的元素复制到目标切片(第一个参数值)中,并返回被复制的元素个数,copy 的两个类型必须一致,并且实际复制的数量等于实际较短切片长度。\n","permalink":"https://jsharkc.github.io/post/slice%E6%9C%BA%E5%88%B6/","summary":"Golang slice 切片原理 \tgolang 中的 slice 是比较好用的一种结构,能根据需求变长,相对于 array 的死板,slice 更加灵活也更加常用,有道说:知其然,知其所以然。现在,我们就看看 slice 到底是怎样一种结构。\nslice源码 type slice struct { array unsafe.Pointer len int cap int } \t根据平常使用情况,我们推测 len 是 slice 长度,cap 是 slice 的容量,而 array 则是指向底层数组的指针。\n\t有同学可能不知道 unsafe.Pointer是什么,那么我们一并在这讲解一下。\npackage unsafe type ArbitraryType int //\t- A pointer value of any type can be converted to a Pointer. //\t- A Pointer can be converted to a pointer value of any type.","title":"Golang slice 切片原理"},{"content":"安装 go get github.com/bamzi/jobrunner 使用 eg1 package main import \u0026#34;github.com/bamzi/jobrunner\u0026#34; func main() { jobrunner.Start() jobrunner.Schedule(\u0026#34;@every 5s\u0026#34;, ReminderEmails{}) select{} } type ReminderEmails struct {} func (e ReminderEmails) Run() { fmt.Printf(\u0026#34;Every 5 sec send reminder emails \\n\u0026#34;) } ***解析:***在 main() 函数中,jobrunner 是导入的包, jobrunner.Start() 代表启动,jobrunner.Schedule() 代表添加定时 job,第一个参数是时间格式,第二个是任务实例,任务必须实现 Run() 方法。\nJobrunner 支持两种时间格式:\n \u0026ldquo;@XXX\u0026rdquo;\n \u0026ldquo;@yearly\u0026rdquo;, \u0026ldquo;@annually\u0026rdquo; 每年 \u0026ldquo;@monthly\u0026rdquo; 每月 \u0026ldquo;@weekly\u0026rdquo; 每星期 \u0026ldquo;@daily\u0026rdquo;, \u0026ldquo;@midnight\u0026rdquo; 每天 \u0026ldquo;@hourly\u0026rdquo; 每小时 \u0026ldquo;@every 5s\u0026rdquo; 支持 \u0026ldquo;ns\u0026rdquo;, \u0026ldquo;us\u0026rdquo; (or \u0026ldquo;µs\u0026rdquo;), \u0026ldquo;ms\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;m\u0026rdquo;, \u0026ldquo;h\u0026rdquo;。 \u0026ldquo;x x x x x x\u0026rdquo;\n从左到右依次代表 :\n1.秒\n2.分钟\n3.小时\n4.月份中的日期\n5.月份\n6.星期中的日期\n 名 支持的数字 支持的字符 Seconds 0-59 - * / Minutes 0-59 - * / Hours 0-23 - * / Day-of-month 1-31 - * ? / Month 1-12 or JAN-DEC - * / Day-of-Week 1-7 or SUN-SAT - * ? / 解释 :\n * 是一个通配符,表示任何值,用在Minutes字段中表示每分钟。 ? 只可以用在 day-of-month 或者 Day-of-Week 字段中,用来表示不指定特殊的值。 - 用来表示一个范围,比如10-12用在Month中表示10到12月。 , 用来表示附加的值,比如 MON,WED,FRI 在 day-of-week 字段中表示礼拜一和礼拜三和礼拜五。 / 用来表示增量,比如0/15用在Minutes字段中表示从0分开始0和15和30和45分。 e.g.\n \u0026ldquo;0 1 0 1 1-12 ?\u0026rdquo; 表示每月1号0点1分执行。 \u0026ldquo;0 0 21 ? * 1\u0026rdquo; 表示每个礼拜天21点0分执行。 \u0026ldquo;0 0 0 * * ?\u0026rdquo; 表示每天0点0分执行。 \u0026ldquo;0 * 22 * * ?\u0026rdquo; 表示每天22点开始每分钟 \u0026ldquo;0 * 0-23 * * ?\u0026rdquo; 表示每天每分钟 源码解析 jobrunner 开始函数 Start() func Start(v ...int) { MainCron = cron.New() if len(v) \u0026gt; 0 { if v[0] \u0026gt; 0 { workPermits = make(chan struct{}, v[0]) } else { workPermits = make(chan struct{}, DEFAULT_JOB_POOL_SIZE) } } if len(v) \u0026gt; 1 { if v[1] \u0026gt; 0 { selfConcurrent = true } else { selfConcurrent = false } } MainCron.Start() fmt.Printf(\u0026#34;%s[JobRunner] %v Started... %s \\n\u0026#34;, magenta, time.Now().Format(\u0026#34;2006/01/02 - 15:04:05\u0026#34;), reset) } **解析:**Start() 接受不定长参数,可以不传,可以传多个,但实际上最多前两个参数有效。首先,创建一个 Cron 定时任务实例 MainCron。如果传入了 \u0026gt; 0 个参数,并且第一个参数 \u0026gt; 0,则用此参数初始化 workPermits 参数,此参数用于控制最多同时运行的 job 数量,如果 \u0026lt;= 0,则用默认 DEFAULT_JOB_POOL_SIZE 常量。如果传入了 \u0026gt; 1 个参数,并且第二个参数 \u0026gt; 0,则用此参数初始化 selfConcurrent 参数,此参数用于控制同一个 job 是否可以并行执行,如果 \u0026lt;= 0,则不可并行。最后,MainCron 开始执行,并打印开始时间。\n添加任务的四种方式 1. Schedule(spec string, job cron.Job) func Schedule(spec string, job cron.Job) error { sched, err := cron.Parse(spec) if err != nil { return err } MainCron.Schedule(sched, New(job)) return nil } **解析:**第一个参数是表示什么时间执行,第二个参数就是任务。首先调用 cron 的Parse 解析函数,判断是否有错误,成功的话添加到 MainCron 中准备执行。\n2.Every(duration time.Duration, job cron.Job) func Every(duration time.Duration, job cron.Job) { MainCron.Schedule(cron.Every(duration), New(job)) } **解析:**这个方法是以固定时间间隔执行任务。\n3.Now(job cron.Job) func Now(job cron.Job) { go New(job).Run() } **解析:**新起一个协程立即运行一次。\n4.In(duration time.Duration, job cron.Job) func In(duration time.Duration, job cron.Job) { go func() { time.Sleep(duration) New(job).Run() }() } **解析:**在第一个参数的时间后,执行一次。\njob 结构 type Job struct { Name string // inner 类型名称 \tinner cron.Job // 实现 Run() 方法的 type \tstatus uint32 // 状态数 \tStatus string // 状态解释 \tLatency string // job 运行所用时间 \trunning sync.Mutex // 锁 } 新建一个 job func New(job cron.Job) *Job { name := reflect.TypeOf(job).Name() if name == \u0026#34;Func\u0026#34; { name = UNNAMED } return \u0026amp;Job{ Name: name, inner: job, } } **解析:**用反射获取一下传入的 job 的类型,并新建一个 Job 。\n更新 job 状态 func (j *Job) StatusUpdate() string { if atomic.LoadUint32(\u0026amp;j.status) \u0026gt; 0 { j.Status = \u0026#34;RUNNING\u0026#34; return j.Status } j.Status = \u0026#34;IDLE\u0026#34; return j.Status } **解析:**以原子方式获取 job.status 的状态数,如果 \u0026gt;0 ,设置 job.Status = “RUNNING”, 否则 job.Status = “IDLE”\njob 运行 func (j *Job) Run() { start := time.Now() // If the job panics, just print a stack trace. \t// Don\u0026#39;t let the whole process die. \tdefer func() { if err := recover(); err != nil { var buf bytes.Buffer logger := log.New(\u0026amp;buf, \u0026#34;JobRunner Log: \u0026#34;, log.Lshortfile) logger.Panic(err, \u0026#34;\\n\u0026#34;, string(debug.Stack())) } }() if !selfConcurrent { j.running.Lock() defer j.running.Unlock() } if workPermits != nil { workPermits \u0026lt;- struct{}{} defer func() { \u0026lt;-workPermits }() } atomic.StoreUint32(\u0026amp;j.status, 1) j.StatusUpdate() defer j.StatusUpdate() defer atomic.StoreUint32(\u0026amp;j.status, 0) j.inner.Run() end := time.Now() j.Latency = end.Sub(start).String() } **解析:**先获取一下当前时间,作为任务开始时间记录下来,并添加 defer recover 防止崩溃,并打印错误信息。如果 selfConcurrent 为 false ,不能同一个任务并行执行,则加锁,一个一个执行。否则可以同时执行。如果 workPermits 不为空,则限制同时最大执行数量。以原子方式把 job.status 存储为 1,更新一下状态,以 defer 延迟修改 job.status 存储为 0,并更新状态。最后,run job。\n","permalink":"https://jsharkc.github.io/post/jobrunner/","summary":"安装 go get github.com/bamzi/jobrunner 使用 eg1 package main import \u0026#34;github.com/bamzi/jobrunner\u0026#34; func main() { jobrunner.Start() jobrunner.Schedule(\u0026#34;@every 5s\u0026#34;, ReminderEmails{}) select{} } type ReminderEmails struct {} func (e ReminderEmails) Run() { fmt.Printf(\u0026#34;Every 5 sec send reminder emails \\n\u0026#34;) } ***解析:***在 main() 函数中,jobrunner 是导入的包, jobrunner.Start() 代表启动,jobrunner.Schedule() 代表添加定时 job,第一个参数是时间格式,第二个是任务实例,任务必须实现 Run() 方法。\nJobrunner 支持两种时间格式:\n \u0026ldquo;@XXX\u0026rdquo;\n \u0026ldquo;@yearly\u0026rdquo;, \u0026ldquo;@annually\u0026rdquo; 每年 \u0026ldquo;@monthly\u0026rdquo; 每月 \u0026ldquo;@weekly\u0026rdquo; 每星期 \u0026ldquo;@daily\u0026rdquo;, \u0026ldquo;@midnight\u0026rdquo; 每天 \u0026ldquo;@hourly\u0026rdquo; 每小时 \u0026ldquo;@every 5s\u0026rdquo; 支持 \u0026ldquo;ns\u0026rdquo;, \u0026ldquo;us\u0026rdquo; (or \u0026ldquo;µs\u0026rdquo;), \u0026ldquo;ms\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;m\u0026rdquo;, \u0026ldquo;h\u0026rdquo;。 \u0026ldquo;x x x x x x\u0026rdquo;","title":"Jobrunner 源码解析"},{"content":"Interface 解析 转自 https://zhuanlan.zhihu.com/p/27652856\n先看一段代码:\nfunc Foo(x interface{}) { if x == nil { fmt.Println(\u0026#34;empty interface\u0026#34;) return } fmt.Println(\u0026#34;non-empty interface\u0026#34;) } func main() { var x *int = nil Foo(x) } 上面的例子的输出结果如下\n$ go run test_interface.go non-empty interface 可能你会感觉奇怪,为什么会是 non-empty inerface,那么继续往下看,你就会知道答案。\ninterface 底层结构 根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface。eface表示不含 method 的 interface 结构,或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息。\ntype eface struct { _type *_type data unsafe.Pointer } type _type struct { size uintptr // type size ptrdata uintptr // size of memory prefix holding all pointers hash uint32 // hash of type; avoids computation in hash tables tflag tflag // extra type information flags align uint8 // alignment of variable with this type fieldalign uint8 // alignment of struct field with this type kind uint8 // enumeration for C alg *typeAlg // algorithm table gcdata *byte // garbage collection data str nameOff // string form ptrToThis typeOff // type for pointer to this type, may be zero } iface 表示 non-empty interface 的底层实现。相比于 empty interface,non-empty 要包含一些 method。method 的具体实现存放在 itab.fun 变量里。如果 interface 包含多个 method,这里只有一个 fun 变量怎么存呢?这个下面再细说。\ntype iface struct { tab *itab data unsafe.Pointer } // layout of Itab known to compilers // allocated in non-garbage-collected memory // Needs to be in sync with // ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs. type itab struct { inter *interfacetype _type *_type link *itab bad int32 inhash int32 // has this itab been added to hash? fun [1]uintptr // variable sized } 我们使用实际程序来看一下。\npackage main import ( \u0026#34;fmt\u0026#34; ) type MyInterface interface { Print() } type MyStruct struct{} func (ms MyStruct) Print() {} func main() { x := 1 var y interface{} = x var s MyStruct var t MyInterface = s fmt.Println(y, z) } 查看汇编代码。\n$ go build -gcflags '-l' -o interface11 interface11.go $ go tool objdump -s \u0026quot;main\\.main\u0026quot; interface11 TEXT main.main(SB) /Users/kltao/code/go/examples/interface11.go interface11.go:15 0x10870f0 65488b0c25a0080000 GS MOVQ GS:0x8a0, CX interface11.go:15 0x10870f9 483b6110 CMPQ 0x10(CX), SP interface11.go:15 0x10870fd 0f86de000000 JBE 0x10871e1 interface11.go:15 0x1087103 4883ec70 SUBQ $0x70, SP interface11.go:15 0x1087107 48896c2468 MOVQ BP, 0x68(SP) interface11.go:15 0x108710c 488d6c2468 LEAQ 0x68(SP), BP interface11.go:17 0x1087111 48c744243001000000 MOVQ $0x1, 0x30(SP) interface11.go:17 0x108711a 488d057fde0000 LEAQ 0xde7f(IP), AX interface11.go:17 0x1087121 48890424 MOVQ AX, 0(SP) interface11.go:17 0x1087125 488d442430 LEAQ 0x30(SP), AX interface11.go:17 0x108712a 4889442408 MOVQ AX, 0x8(SP) interface11.go:17 0x108712f e87c45f8ff CALL runtime.convT2E(SB) interface11.go:17 0x1087134 488b442410 MOVQ 0x10(SP), AX interface11.go:17 0x1087139 4889442438 MOVQ AX, 0x38(SP) interface11.go:17 0x108713e 488b4c2418 MOVQ 0x18(SP), CX interface11.go:17 0x1087143 48894c2440 MOVQ CX, 0x40(SP) interface11.go:19 0x1087148 488d15b1000800 LEAQ 0x800b1(IP), DX interface11.go:19 0x108714f 48891424 MOVQ DX, 0(SP) interface11.go:19 0x1087153 488d542430 LEAQ 0x30(SP), DX interface11.go:19 0x1087158 4889542408 MOVQ DX, 0x8(SP) interface11.go:19 0x108715d e8fe45f8ff CALL runtime.convT2I(SB) 代码 17 行 var y interface{} = x 调用了函数 runtime.convT2E,将 int 类型的 x 转换成 empty interface。代码 19 行 var t MyInterface = s 将 MyStruct 类型转换成 non-empty interface: MyInterface。\nfunc convT2E(t *_type, elem unsafe.Pointer) (e eface) { ... x := newobject(t) typedmemmove(t, x, elem) e._type = t e.data = x return } func convT2I(tab *itab, elem unsafe.Pointer) (i iface) { t := tab._type ... x := newobject(t) typedmemmove(t, x, elem) i.tab = tab i.data = x return } 看上面的函数原型,可以看出中间过程编译器将根据我们的转换目标类型的 empty interface 还是 non-empty interface,来对原数据类型进行转换(转换成 \u0026lt;*_type, unsafe.Pointer\u0026gt; 或者 \u0026lt;*itab, unsafe.Pointer\u0026gt;)。这里对于 struct 满不满足 interface 的类型要求(也就是 struct 是否实现了 interface 的所有 method),是由编译器来检测的。\nitab iface 结构中最重要的是 itab 结构。itab 可以理解为 pair\u0026lt;interface type, concrete type\u0026gt; 。当然 itab 里面还包含一些其他信息,比如 interface 里面包含的 method 的具体实现。下面细说。itab 的结构如下。\ntype itab struct { inter *interfacetype _type *_type link *itab bad int32 inhash int32 // has this itab been added to hash? fun [1]uintptr // variable sized } 其中 interfacetype 包含了一些关于 interface 本身的信息,比如 package path,包含的 method。上面提到的 iface 和 eface 是数据类型(built-in 和 type-define)转换成 interface 之后的实体的 struct 结构,而这里的 interfacetype 是我们定义 interface 时候的一种抽象表示。\ntype interfacetype struct { typ _type pkgpath name mhdr []imethod } type imethod struct { //这里的 method 只是一种函数声明的抽象,比如 func Print() error name nameOff ityp typeOff } _type 表示 concrete type。fun 表示的 interface 里面的 method 的具体实现。比如 interface type 包含了 method A, B,则通过 fun 就可以找到这两个 method 的具体实现。这里有个问题 fun 是长度为 1 的 uintptr 数组,那么怎么表示多个 method 呢?看一下测试程序。\npackage main type MyInterface interface { Print() Hello() World() AWK() } func Foo(me MyInterface) { me.Print() me.Hello() me.World() me.AWK() } type MyStruct struct {} func (me MyStruct) Print() {} func (me MyStruct) Hello() {} func (me MyStruct) World() {} func (me MyStruct) AWK() {} func main() { var me MyStruct Foo(me) } 看一下函数调用对应的汇编代码。\n$ go build -gcflags '-l' -o interface8 interface8.go $ go tool objdump -s \u0026quot;main\\.Foo\u0026quot; interface8 TEXT main.Foo(SB) /Users/kltao/code/go/examples/interface8.go interface8.go:10 0x104c060 65488b0c25a0080000 GS MOVQ GS:0x8a0, CX interface8.go:10 0x104c069 483b6110 CMPQ 0x10(CX), SP interface8.go:10 0x104c06d 7668 JBE 0x104c0d7 interface8.go:10 0x104c06f 4883ec10 SUBQ $0x10, SP interface8.go:10 0x104c073 48896c2408 MOVQ BP, 0x8(SP) interface8.go:10 0x104c078 488d6c2408 LEAQ 0x8(SP), BP interface8.go:11 0x104c07d 488b442418 MOVQ 0x18(SP), AX interface8.go:11 0x104c082 488b4830 MOVQ 0x30(AX), CX //取得 Print 函数地址 interface8.go:11 0x104c086 488b542420 MOVQ 0x20(SP), DX interface8.go:11 0x104c08b 48891424 MOVQ DX, 0(SP) interface8.go:11 0x104c08f ffd1 CALL CX // 调用 Print() interface8.go:12 0x104c091 488b442418 MOVQ 0x18(SP), AX interface8.go:12 0x104c096 488b4828 MOVQ 0x28(AX), CX //取得 Hello 函数地址 interface8.go:12 0x104c09a 488b542420 MOVQ 0x20(SP), DX interface8.go:12 0x104c09f 48891424 MOVQ DX, 0(SP) interface8.go:12 0x104c0a3 ffd1 CALL CX //调用 Hello() interface8.go:13 0x104c0a5 488b442418 MOVQ 0x18(SP), AX interface8.go:13 0x104c0aa 488b4838 MOVQ 0x38(AX), CX //取得 World 函数地址 interface8.go:13 0x104c0ae 488b542420 MOVQ 0x20(SP), DX interface8.go:13 0x104c0b3 48891424 MOVQ DX, 0(SP) interface8.go:13 0x104c0b7 ffd1 CALL CX //调用 World() interface8.go:14 0x104c0b9 488b442418 MOVQ 0x18(SP), AX interface8.go:14 0x104c0be 488b4020 MOVQ 0x20(AX), AX //取得 AWK 函数地址 interface8.go:14 0x104c0c2 488b4c2420 MOVQ 0x20(SP), CX interface8.go:14 0x104c0c7 48890c24 MOVQ CX, 0(SP) interface8.go:14 0x104c0cb ffd0 CALL AX //调用 AWK() interface8.go:15 0x104c0cd 488b6c2408 MOVQ 0x8(SP), BP interface8.go:15 0x104c0d2 4883c410 ADDQ $0x10, SP interface8.go:15 0x104c0d6 c3 RET interface8.go:10 0x104c0d7 e8f48bffff CALL runtime.morestack_noctxt(SB) interface8.go:10 0x104c0dc eb82 JMP main.Foo(SB) 其中 0x18(SP) 对应的 itab 的地址。fun 在 x86-64 机器上对应 itab 内的地址偏移为 8+8+8+4+4 = 32 = 0x20,也就是 0x20(AX) 对应的 fun 的值,此时存放的 AWK 函数地址。然后 0x28(AX) = \u0026amp;Hello,0x30(AX) = \u0026amp;Print,0x38(AX) = \u0026amp;World。对的,每次函数是按字典序排序存放的。\n我们再来看一下函数地址究竟是怎么写入的?首先 Golang 中的 uintptr 一般用来存放指针的值,这里对应的就是函数指针的值(也就是函数的调用地址)。但是这里的 fun 是一个长度为 1 的 uintptr 数组。我们看一下 runtime 包的 additab 函数。\nfunc additab(m *itab, locked, canfail bool) { ... *(*unsafe.Pointer)(add(unsafe.Pointer(\u0026amp;m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn ... } 上面的代码的意思是在 fun[0] 的地址后面依次写入其他 method 对应的函数指针。熟悉 C++ 的同学可以类比 C++ 的虚函数表指针来看。\n剩下的还有 bad,link,inhash。其中 bad 是一个表征 itab 状态的变量。而这里的 link 是 *itab 类型,是不是表示 interface 的嵌套呢?并不是,interface 的嵌套也是把 method 平铺而已。link 要和 inhash 一起来说。在 runtime 包里面有一个 hash 表,通过 hash[hashitab(interface_type, concrete_type)] 可以取得 itab,这是出于性能方面的考虑。主要代码如下,这里就不再赘述了。\nconst ( hashSize = 1009 ) var ( ifaceLock mutex // lock for accessing hash hash [hashSize]*itab ) func itabhash(inter *interfacetype, typ *_type) uint32 { // compiler has provided some good hash codes for us. h := inter.typ.hash h += 17 * typ.hash // TODO(rsc): h += 23 * x.mhash ? return h % hashSize } func additab(...) { ... h := itabhash(inter, typ) m.link = hash[h] m.inhash = 1 atomicstorep(unsafe.Pointer(\u0026amp;hash[h]), unsafe.Pointer(m)) } 3. Type Assertion 我们知道使用 interface type assertion (中文一般叫断言) 的时候需要注意,不然很容易引入 panic。\nfunc do(v interface{}) { n := v.(int) // might panic } func do(v interface{}) { n, ok := v.(int) if !ok { // 断言失败处理 } } 这个过程体现在下面的几个函数上。\n// The assertXXX functions may fail (either panicking or returning false, // depending on whether they are 1-result or 2-result). func assertI2I(inter *interfacetype, i iface) (r iface) { tab := i.tab if tab == nil { // explicit conversions require non-nil interface value. panic(\u0026amp;TypeAssertionError{\u0026quot;\u0026quot;, \u0026quot;\u0026quot;, inter.typ.string(), \u0026quot;\u0026quot;}) } if tab.inter == inter { r.tab = tab r.data = i.data return } r.tab = getitab(inter, tab._type, false) r.data = i.data return } func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) { tab := i.tab if tab == nil { return } if tab.inter != inter { tab = getitab(inter, tab._type, true) if tab == nil { return } } r.tab = tab r.data = i.data b = true return } // 类似 func assertE2I(inter *interfacetype, e eface) (r iface) func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) 4. 总结 从某种意义上来说,Golang 的 interface 也是一种多态的体现。对比其他支持多态特性的语言,实现还是略有差异,很难说谁好谁坏。\n","permalink":"https://jsharkc.github.io/post/interface%E8%A7%A3%E6%9E%90/","summary":"Interface 解析 转自 https://zhuanlan.zhihu.com/p/27652856\n先看一段代码:\nfunc Foo(x interface{}) { if x == nil { fmt.Println(\u0026#34;empty interface\u0026#34;) return } fmt.Println(\u0026#34;non-empty interface\u0026#34;) } func main() { var x *int = nil Foo(x) } 上面的例子的输出结果如下\n$ go run test_interface.go non-empty interface 可能你会感觉奇怪,为什么会是 non-empty inerface,那么继续往下看,你就会知道答案。\ninterface 底层结构 根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface。eface表示不含 method 的 interface 结构,或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息。\ntype eface struct { _type *_type data unsafe.Pointer } type _type struct { size uintptr // type size ptrdata uintptr // size of memory prefix holding all pointers hash uint32 // hash of type; avoids computation in hash tables tflag tflag // extra type information flags align uint8 // alignment of variable with this type fieldalign uint8 // alignment of struct field with this type kind uint8 // enumeration for C alg *typeAlg // algorithm table gcdata *byte // garbage collection data str nameOff // string form ptrToThis typeOff // type for pointer to this type, may be zero } iface 表示 non-empty interface 的底层实现。相比于 empty interface,non-empty 要包含一些 method。method 的具体实现存放在 itab.","title":"Golang Interface 解析"},{"content":"十条有用的 Go 技术 转自 mikespook.com\n这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指: 某个应用需要适配一个灵活的环境。你不希望每过 3 到 4 个月就不得不将它们全部重构一遍。添加新的特性应当很容易。许多人参与开发该应用,它应当可以被理解,且维护简单。许多人使用该应用,bug 应该容易被发现并且可以快速的修复。我用了很长的时间学到了这些事情。其中的一些很微小,但对于许多事情都会有影响。所有这些都仅仅是建议,具体情况具体对待,并且如果有帮助的话务必告诉我。随时留言:)\n1. 使用单一的 GOPATH 多个 GOPATH 的情况并不具有弹性。GOPATH 本身就是高度自我完备的(通过导入路径)。有多个 GOPATH 会导致某些副作用,例如可能使用了给定的库的不同的版本。你可能在某个地方升级了它,但是其他地方却没有升级。而且,我还没遇到过任何一个需要使用多个 GOPATH 的情况。所以只使用单一的 GOPATH,这会提升你 Go 的开发进度。\n许多人不同意这一观点,接下来我会做一些澄清。像 etcd 或 camlistore 这样的大项目使用了像 godep 这样的工具,将所有依赖保存到某个目录中。也就是说,这些项目自身有一个单一的 GOPATH。它们只能在这个目录里找到对应的版本。除非你的项目很大并且极为重要,否则不要为每个项目使用不同的 GOPATH。如果你认为项目需要一个自己的 GOPATH 目录,那么就创建它,否则不要尝试使用多个 GOPATH。它只会拖慢你的进度。\n2. 将 for-select 封装到函数中 如果在某个条件下,你需要从 for-select 中退出,就需要使用标签。例如:\nfunc main() { L: for { select { case \u0026lt;-time.After(time.Second): fmt.Println(\u0026#34;hello\u0026#34;) default: break L } } fmt.Println(\u0026#34;ending\u0026#34;) } 如你所见,需要联合break使用标签。这有其用途,不过我不喜欢。这个例子中的 for 循环看起来很小,但是通常它们会更大,而判断break的条件也更为冗长。\n如果需要退出循环,我会将 for-select 封装到函数中:\nfunc main() { foo() fmt.Println(\u0026#34;ending\u0026#34;) } func foo() { for { select { case \u0026lt;-time.After(time.Second): fmt.Println(\u0026#34;hello\u0026#34;) default: return } } } 你还可以返回一个错误(或任何其他值),也是同样漂亮的,只需要:\n// 阻塞 if err := foo(); err != nil { // 处理 err } 3. 在初始化结构体时使用带有标签的语法 这是一个无标签语法的例子:\ntype T struct { Foo string Bar int } func main() { t := T{\u0026#34;example\u0026#34;, 123} // 无标签语法 fmt.Printf(\u0026#34;t %+v\\n\u0026#34;, t) } 那么如果你添加一个新的字段到T结构体,代码会编译失败:\ntype T struct { Foo string Bar int Qux string } func main() { t := T{\u0026#34;example\u0026#34;, 123} // 无法编译 fmt.Printf(\u0026#34;t %+v\\n\u0026#34;, t) } 如果使用了标签语法,Go 的兼容性规则(http://golang.org/doc/go1compat)会处理代码。例如在向net包的类型添加叫做Zone的字段,参见:http://golang.org/doc/go1.1#library。回到我们的例子,使用标签语法:\ntype T struct { Foo string Bar int Qux string } func main() { t := T{Foo: \u0026#34;example\u0026#34;, Qux: 123} fmt.Printf(\u0026#34;t %+v\\n\u0026#34;, t) } 这个编译起来没问题,而且弹性也好。不论你如何添加其他字段到T结构体。你的代码总是能编译,并且在以后的 Go 的版本也可以保证这一点。只要在代码集中执行go vet,就可以发现所有的无标签的语法。\n4. 将结构体的初始化拆分到多行 如果有两个以上的字段,那么就用多行。它会让你的代码更加容易阅读,也就是说不要:\nT{Foo: \u0026#34;example\u0026#34;, Bar:someLongVariable, Qux:anotherLongVariable, B: forgetToAddThisToo} 而是:\nT{ Foo: \u0026#34;example\u0026#34;, Bar: someLongVariable, Qux: anotherLongVariable, B: forgetToAddThisToo, } 这有许多好处,首先它容易阅读,其次它使得允许或屏蔽字段初始化变得容易(只要注释或删除它们),最后添加其他字段也更容易(只要添加一行)。\n5. 为整数常量添加 String() 方法 如果你利用 iota 来使用自定义的整数枚举类型,务必要为其添加 String() 方法。例如,像这样:\ntype State int const ( Running State = iota Stopped Rebooting Terminated ) 如果你创建了这个类型的一个变量,然后输出,会得到一个整数(http://play.golang.org/p/V5VVFB05HB):\nfunc main() { state := Running // print: \u0026#34;state 0\u0026#34; fmt.Println(\u0026#34;state \u0026#34;, state) } 除非你回顾常量定义,否则这里的0看起来毫无意义。只需要为State类型添加String()方法就可以修复这个问题(http://play.golang.org/p/ewMKl6K302):\nfunc (s State) String() string { switch s { case Running: return \u0026#34;Running\u0026#34; case Stopped: return \u0026#34;Stopped\u0026#34; case Rebooting: return \u0026#34;Rebooting\u0026#34; case Terminated: return \u0026#34;Terminated\u0026#34; default: return \u0026#34;Unknown\u0026#34; } } 新的输出是:state: Running。显然现在看起来可读性好了很多。在你调试程序的时候,这会带来更多的便利。同时还可以在实现 MarshalJSON()、UnmarshalJSON() 这类方法的时候使用同样的手段。\n6. 让 iota 从 a +1 开始增量 在前面的例子中同时也产生了一个我已经遇到过许多次的 bug。假设你有一个新的结构体,有一个State字段:\ntype T struct { Name string Port int State State } 现在如果基于 T 创建一个新的变量,然后输出,你会得到奇怪的结果(http://play.golang.org/p/LPG2RF3y39):\nfunc main() { t := T{Name: \u0026#34;example\u0026#34;, Port: 6666} // prints: \u0026#34;t {Name:example Port:6666 State:Running}\u0026#34; fmt.Printf(\u0026#34;t %+v\\n\u0026#34;, t) } 看到 bug 了吗?State字段没有初始化,Go 默认使用对应类型的零值进行填充。由于State是一个整数,零值也就是0,但在我们的例子中它表示Running。\n那么如何知道 State 被初始化了?还是它真得是在Running模式?没有办法区分它们,那么这就会产生未知的、不可预测的 bug。不过,修复这个很容易,只要让 iota 从 +1 开始(http://play.golang.org/p/VyAq-3OItv):\n现在t变量将默认输出Unknown,不是吗? 🙂 :\nfunc main() { t := T{Name: \u0026#34;example\u0026#34;, Port: 6666} // 输出: \u0026#34;t {Name:example Port:6666 State:Unknown}\u0026#34; fmt.Printf(\u0026#34;t %+v\\n\u0026#34;, t) } 不过让 iota 从零值开始也是一种解决办法。例如,你可以引入一个新的状态叫做Unknown,将其修改为:\nconst ( Unknown State = iota Running Stopped Rebooting Terminated ) 7. 返回函数调用 我已经看过很多代码例如(http://play.golang.org/p/8Rz1EJwFTZ):\nfunc bar() (string, error) { v, err := foo() if err != nil { return \u0026#34;\u0026#34;, err } return v, nil } 然而,你只需要:\nfunc bar() (string, error) { return foo() } 更简单也更容易阅读(当然,除非你要对某些内部的值做一些记录)。\n8. 把 slice、map 等定义为自定义类型 将 slice 或 map 定义成自定义类型可以让代码维护起来更加容易。假设有一个Server类型和一个返回服务器列表的函数:\ntype Server struct { Name string } func ListServers() []Server { return []Server{ {Name: \u0026#34;Server1\u0026#34;}, {Name: \u0026#34;Server2\u0026#34;}, {Name: \u0026#34;Foo1\u0026#34;}, {Name: \u0026#34;Foo2\u0026#34;}, } } 现在假设需要获取某些特定名字的服务器。需要对 ListServers() 做一些改动,增加筛选条件:\n// ListServers 返回服务器列表。只会返回包含 name 的服务器。空的 name 将会返回所有服务器。 func ListServers(name string) []Server { servers := []Server{ {Name: \u0026#34;Server1\u0026#34;}, {Name: \u0026#34;Server2\u0026#34;}, {Name: \u0026#34;Foo1\u0026#34;}, {Name: \u0026#34;Foo2\u0026#34;}, } // 返回所有服务器 if name == \u0026#34;\u0026#34; { return servers } // 返回过滤后的结果 filtered := make([]Server, 0) for _, server := range servers { if strings.Contains(server.Name, name) { filtered = append(filtered, server) } } return filtered } 现在可以用这个来筛选有字符串Foo的服务器:\nfunc main() { servers := ListServers(\u0026#34;Foo\u0026#34;) // 输出:“servers [{Name:Foo1} {Name:Foo2}]” fmt.Printf(\u0026#34;servers %+v\\n\u0026#34;, servers) } 显然这个函数能够正常工作。不过它的弹性并不好。如果你想对服务器集合引入其他逻辑的话会如何呢?例如检查所有服务器的状态,为每个服务器创建一个数据库记录,用其他字段进行筛选等等……\n现在引入一个叫做Servers的新类型,并且修改原始版本的 ListServers() 返回这个新类型:\ntype Servers []Server // ListServers 返回服务器列表 func ListServers() Servers { return []Server{ {Name: \u0026#34;Server1\u0026#34;}, {Name: \u0026#34;Server2\u0026#34;}, {Name: \u0026#34;Foo1\u0026#34;}, {Name: \u0026#34;Foo2\u0026#34;}, } } 现在需要做的是只要为Servers类型添加一个新的Filter()方法:\n// Filter 返回包含 name 的服务器。空的 name 将会返回所有服务器。 func (s Servers) Filter(name string) Servers { filtered := make(Servers, 0) for _, server := range s { if strings.Contains(server.Name, name) { filtered = append(filtered, server) } } return filtered } 现在可以针对字符串Foo筛选服务器:\nfunc main() { servers := ListServers() servers = servers.Filter(\u0026#34;Foo\u0026#34;) fmt.Printf(\u0026#34;servers %+v\\n\u0026#34;, servers) } 哈!看到你的代码是多么的简单了吗?还想对服务器的状态进行检查?或者为每个服务器添加一条数据库记录?没问题,添加以下新方法即可:\nfunc (s Servers) Check() func (s Servers) AddRecord() func (s Servers) Len() ... 9. withContext 封装函数 有时对于函数会有一些重复劳动,例如锁/解锁,初始化一个新的局部上下文,准备初始化变量等等……这里有一个例子:\nfunc foo() { mu.Lock() defer mu.Unlock() // foo 相关的工作 } func bar() { mu.Lock() defer mu.Unlock() // bar 相关的工作 } func qux() { mu.Lock() defer mu.Unlock() // qux 相关的工作 } 如果你想要修改某个内容,你需要对所有的都进行修改。如果它是一个常见的任务,那么最好创建一个叫做withContext的函数。这个函数的输入参数是另一个函数,并用调用者提供的上下文来调用它:\nfunc withLockContext(fn func()) { mu.Lock defer mu.Unlock() fn() } 只需要将之前的函数用这个进行封装:\nfunc foo() { withLockContext(func() { // foo 相关工作 }) } func bar() { withLockContext(func() { // bar 相关工作 }) } func qux() { withLockContext(func() { // qux 相关工作 }) } 不要光想着加锁的情形。对此来说最好的用例是数据库链接。现在对 withContext 函数作一些小小的改动:\nfunc withDBContext(fn func(db DB) error) error { // 从连接池获取一个数据库连接 dbConn := NewDB() return fn(dbConn) } 如你所见,它获取一个连接,然后传递给提供的参数,并且在调用函数的时候返回错误。你需要做的只是:\nfunc foo() { withDBContext(func(db *DB) error { // foo 相关工作 }) } func bar() { withDBContext(func(db *DB) error { // bar 相关工作 }) } func qux() { withDBContext(func(db *DB) error { // qux 相关工作 }) } 你在考虑一个不同的场景,例如作一些预初始化?没问题,只需要将它们加到withDBContext就可以了。这对于测试也同样有效。\n这个方法有个缺陷,它增加了缩进并且更难阅读。再次提示,永远寻找最简单的解决方案。\n10. 为访问 map 增加 setter,getters 如果你重度使用 map 读写数据,那么就为其添加 getter 和 setter 吧。通过 getter 和 setter 你可以将逻辑封分别装到函数里。这里最常见的错误就是并发访问。如果你在某个 goroutein 里有这样的代码:\nm[\u0026#34;foo\u0026#34;] = bar 还有这个:\ndelete(m, \u0026#34;foo\u0026#34;) 会发生什么?你们中的大多数应当已经非常熟悉这样的竞态了。简单来说这个竞态是由于 map 默认并非线程安全。不过你可以用互斥量来保护它们:\nmu.Lock() m[\u0026#34;foo\u0026#34;] = \u0026#34;bar\u0026#34; mu.Unlock() 以及:\nmu.Lock() delete(m, \u0026#34;foo\u0026#34;) mu.Unlock() 假设你在其他地方也使用这个 map。你必须把互斥量放得到处都是!然而通过 getter 和 setter 函数就可以很容易的避免这个问题:\nfunc Put(key, value string) { mu.Lock() m[key] = value mu.Unlock() } func Delete(key string) { mu.Lock() delete(m, key) mu.Unlock() } 使用接口可以对这一过程做进一步的改进。你可以将实现完全隐藏起来。只使用一个简单的、设计良好的接口,然后让包的用户使用它们:\ntype Storage interface { Delete(key string) Get(key string) string Put(key, value string) } 这只是个例子,不过你应该能体会到。对于底层的实现使用什么都没关系。不光是使用接口本身很简单,而且还解决了暴露内部数据结构带来的大量的问题。\n但是得承认,有时只是为了同时对若干个变量加锁就使用接口会有些过分。理解你的程序,并且在你需要的时候使用这些改进。\n总结 抽象永远都不是容易的事情。有时,最简单的就是你已经实现的方法。要知道,不要让你的代码看起来很聪明。Go 天生就是个简单的语言,在大多数情况下只会有一种方法来作某事。简单是力量的源泉,也是为什么在人的层面它表现的如此有弹性。\n如果必要的话,使用这些基数。例如将[]Server转化为Servers是另一种抽象,仅在你有一个合理的理由的情况下这么做。不过有一些技术,如 iota 从 1 开始计数总是有用的。再次提醒,永远保持简单。\n特别感谢 Cihangir Savas、Andrew Gerrand、Ben Johnson 和 Damian Gryski 提供的极具价值的反馈和建议。\n","permalink":"https://jsharkc.github.io/post/ten-useful-go-tip/","summary":"十条有用的 Go 技术 转自 mikespook.com\n这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指: 某个应用需要适配一个灵活的环境。你不希望每过 3 到 4 个月就不得不将它们全部重构一遍。添加新的特性应当很容易。许多人参与开发该应用,它应当可以被理解,且维护简单。许多人使用该应用,bug 应该容易被发现并且可以快速的修复。我用了很长的时间学到了这些事情。其中的一些很微小,但对于许多事情都会有影响。所有这些都仅仅是建议,具体情况具体对待,并且如果有帮助的话务必告诉我。随时留言:)\n1. 使用单一的 GOPATH 多个 GOPATH 的情况并不具有弹性。GOPATH 本身就是高度自我完备的(通过导入路径)。有多个 GOPATH 会导致某些副作用,例如可能使用了给定的库的不同的版本。你可能在某个地方升级了它,但是其他地方却没有升级。而且,我还没遇到过任何一个需要使用多个 GOPATH 的情况。所以只使用单一的 GOPATH,这会提升你 Go 的开发进度。\n许多人不同意这一观点,接下来我会做一些澄清。像 etcd 或 camlistore 这样的大项目使用了像 godep 这样的工具,将所有依赖保存到某个目录中。也就是说,这些项目自身有一个单一的 GOPATH。它们只能在这个目录里找到对应的版本。除非你的项目很大并且极为重要,否则不要为每个项目使用不同的 GOPATH。如果你认为项目需要一个自己的 GOPATH 目录,那么就创建它,否则不要尝试使用多个 GOPATH。它只会拖慢你的进度。\n2. 将 for-select 封装到函数中 如果在某个条件下,你需要从 for-select 中退出,就需要使用标签。例如:\nfunc main() { L: for { select { case \u0026lt;-time.After(time.Second): fmt.Println(\u0026#34;hello\u0026#34;) default: break L } } fmt.Println(\u0026#34;ending\u0026#34;) } 如你所见,需要联合break使用标签。这有其用途,不过我不喜欢。这个例子中的 for 循环看起来很小,但是通常它们会更大,而判断break的条件也更为冗长。\n如果需要退出循环,我会将 for-select 封装到函数中:","title":"十条有用的 GO 技术"},{"content":"Golang 中不存在引用传参 \t原文链接\n\t翻译:Jsharkc\n什么是引用变量 ? 在 C++ 语言中,你可以为已存在的变量声明一个别名,这就是引用变量:\n#include \u0026lt;stdio.h\u0026gt; int main() { int a = 10; int \u0026amp;b = a; int \u0026amp;c = b; printf(\u0026#34;%p %p %p\\n\u0026#34;, \u0026amp;a, \u0026amp;b, \u0026amp;c); // 0x7ffe114f0b14 0x7ffe114f0b14 0x7ffe114f0b14 return 0; } 可以看到,a、b、c 三个变量的地址是相同的,也就是说它们是同一个内存地址的变量,只不过有三个别名。就好比你有一个大名,一个小名,不管别人叫大名还是小名叫的都是你,如果改变 a 变量,b、c 变量也会跟着变。当你声明一个引用变量在不同的函数作用域中这是非常有用的。\nGolang 没有引用变量 与 C++ 不同,在 Golang 中声明的每个变量都只能占用不同的内存空间的。\npackage main import \u0026#34;fmt\u0026#34; func main() { var a, b, c int fmt.Println(\u0026amp;a, \u0026amp;b, \u0026amp;c) // 0x1040a124 0x1040a128 0x1040a12c } 在 Golang 项目中不可能存在两个变量共享一块内存,但是可以创建两个变量指向相同的内存地址,但这和两个变量共享一块内存是不一样的。\npackage main import \u0026#34;fmt\u0026#34; func main() { var a int var b, c = \u0026amp;a, \u0026amp;a fmt.Println(b, c) // 0x1040a124 0x1040a124 fmt.Println(\u0026amp;b, \u0026amp;c) // 0x1040c108 0x1040c110 } Map 和 Channel 是引用吗? 不,Map 和 Channel 不是引用,如果是的话下面这个程序会打印 false。\npackage main import \u0026#34;fmt\u0026#34; func fn(m map[int]int) { m = make(map[int]int) } func main() { var m map[int]int fn(m) fmt.Println(m == nil) } 如果 map m 是和 C++ 风格一样的引用变量的话,那么 main 函数里声明的 m 与 fn 函数声明的 m 在内存中占用的应该是同一块内存。但是在 fn 中对 m 分配的值并没有影响到 main 中的 m,所以我们知道 map 和 channel 不是引用变量。\n结论 Golang 没有引用传参,因为 Golang 不存在引用变量。\n","permalink":"https://jsharkc.github.io/post/golang%E4%B8%8D%E5%AD%98%E5%9C%A8%E5%BC%95%E7%94%A8%E4%BC%A0%E5%80%BC/","summary":"Golang 中不存在引用传参 \t原文链接\n\t翻译:Jsharkc\n什么是引用变量 ? 在 C++ 语言中,你可以为已存在的变量声明一个别名,这就是引用变量:\n#include \u0026lt;stdio.h\u0026gt; int main() { int a = 10; int \u0026amp;b = a; int \u0026amp;c = b; printf(\u0026#34;%p %p %p\\n\u0026#34;, \u0026amp;a, \u0026amp;b, \u0026amp;c); // 0x7ffe114f0b14 0x7ffe114f0b14 0x7ffe114f0b14 return 0; } 可以看到,a、b、c 三个变量的地址是相同的,也就是说它们是同一个内存地址的变量,只不过有三个别名。就好比你有一个大名,一个小名,不管别人叫大名还是小名叫的都是你,如果改变 a 变量,b、c 变量也会跟着变。当你声明一个引用变量在不同的函数作用域中这是非常有用的。\nGolang 没有引用变量 与 C++ 不同,在 Golang 中声明的每个变量都只能占用不同的内存空间的。\npackage main import \u0026#34;fmt\u0026#34; func main() { var a, b, c int fmt.Println(\u0026amp;a, \u0026amp;b, \u0026amp;c) // 0x1040a124 0x1040a128 0x1040a12c } 在 Golang 项目中不可能存在两个变量共享一块内存,但是可以创建两个变量指向相同的内存地址,但这和两个变量共享一块内存是不一样的。","title":"Golang 中不存在引用传参"},{"content":"Docker Note 镜像 虚悬镜像 \t没有仓库名,也没有标签,均为\u0026lt;none\u0026gt;的为虚悬镜像:\n\u0026lt;none\u0026gt;\t\u0026lt;none\u0026gt;\t00285df0df87\t5 days ago\t342 MB \t由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为\u0026lt;none\u0026gt;,可用下面的命令专门显示这类镜像:\n$ docker images -f dangling=ture REPOSITORY\tTAG\tIMAGE ID CREATED\tSIZE \u0026lt;none\u0026gt;\t\u0026lt;none\u0026gt;\t00285df0df87\t5 days ago 342 MB \t一般来说,虚悬镜像已经失去了存在价值,可以随意删除,可用下面命令删除。\n$ docker rmi $(docker images -q -f dangling=ture) docker images ####—filter -f\n\tdocker images 还支持强大的过滤器参数 --filter\t,或者简写-f。上面我们看到使用过滤器来列出虚悬镜像的方法,它还有更多的用法。比如,我们希望看到在mongo:3.2之后建立的镜像,可以用下面的命令:\n$ docker images -f since=mongo:3.2 REPOSITORY\tTAG\tIMAGE ID CREATED\tSIZE redis\tlatest\t5f515359c7f8\t5 days ago\t183 MB nginx\tlatest\t05a60462f8ba\t5 days ago\t181 MB \t想查看某个位置之前的镜像也可以,只需要吧since换成before即可。\n####以特定格式显示镜像\n\t把所有的虚悬镜像的 ID 列出来\n$ docker images -q 5f515359c7f8 05a60462f8ba fe9198c04d62 00285df0df87 f753707788c5 f753707788c5 1e0c3dd64ccd \t以自己想要的格式显示,比如显示镜像 ID 和仓库名:\n$ docker images --format \u0026#34;{{.ID}}: {{.Repository}}\u0026#34; 5f515359c7f8: redis 05a60462f8ba: nginx fe9198c04d62: mongo 00285df0df87: \u0026lt;none\u0026gt; f753707788c5: ubuntu f753707788c5: ubuntu 1e0c3dd64ccd: ubuntu \t以表格等距显示,并且有标题行\n$ docker images --format \u0026#34;table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\u0026#34; IMAGE ID REPOSITORY TAG 5f515359c7f8 redis latest 05a60462f8ba nginx latest fe9198c04d62 mongo 3.2 00285df0df87 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; f753707788c5 ubuntu 16.04 f753707788c5 ubuntu latest 1e0c3dd64ccd ubuntu 14.04 Dockerfile 定制镜像 \tDockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。\nContext 上下文 \t当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。\nDockerfile 指令详解 ####COPY 复制文件\n格式:\n COPY \u0026lt;源路径\u0026gt;... \u0026lt;目标路径\u0026gt; COPY [\u0026quot;\u0026lt;源路径1\u0026gt;\u0026quot;,... \u0026quot;\u0026lt;目标路径\u0026gt;\u0026quot;] 和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。\nCOPY 指令将从构建上下文目录中 \u0026lt;源路径\u0026gt; 的文件/目录复制到新的一层的镜像内的 \u0026lt;目标路径\u0026gt; 位置。比如:\nCOPY package.json /usr/src/app/ \u0026lt;源路径\u0026gt; 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如:\nCOPY hom* /mydir/ COPY hom?.txt /mydir/ \u0026lt;目标路径\u0026gt; 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。\n此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。\n####CMD 容器启动命令\nCMD 指令的格式和 RUN 相似,也是两种格式:\n shell 格式:CMD \u0026lt;命令\u0026gt; exec 格式:CMD [\u0026quot;可执行文件\u0026quot;, \u0026quot;参数1\u0026quot;, \u0026quot;参数2\u0026quot;...] 参数列表格式:CMD [\u0026quot;参数1\u0026quot;, \u0026quot;参数2\u0026quot;...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。 之前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定__默认__的__容器主进程__的__启动命令__的。\n在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 \u0026quot;,而不要使用单引号。\n如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:\nCMD echo $HOME 在实际执行中,会将其变更为:\nCMD [ \u0026quot;sh\u0026quot;, \u0026quot;-c\u0026quot;, \u0026quot;echo $HOME\u0026quot; ] ENTRYPOINT 入口点 ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。\nENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。\n当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:\n\u0026lt;ENTRYPOINT\u0026gt; \u0026quot;\u0026lt;CMD\u0026gt;\u0026quot; ENV 设置环境变量 格式有两种:\n ENV \u0026lt;key\u0026gt; \u0026lt;value\u0026gt; ENV \u0026lt;key1\u0026gt;=\u0026lt;value1\u0026gt; \u0026lt;key2\u0026gt;=\u0026lt;value2\u0026gt;... 这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。\nENV VERSION=1.0 DEBUG=on \\ NAME=\u0026quot;Happy Feet\u0026quot; ARG 构建参数 格式:ARG \u0026lt;参数名\u0026gt;[=\u0026lt;默认值\u0026gt;]\n构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来__容器运行时__是__不会存在这些环境变量__的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。\nDockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg \u0026lt;参数名\u0026gt;=\u0026lt;值\u0026gt; 来覆盖。\n在 1.13 之前的版本,要求 --build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 --build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。\nVOLUME 定义匿名卷 格式为:\n VOLUME [\u0026quot;\u0026lt;路径1\u0026gt;\u0026quot;, \u0026quot;\u0026lt;路径2\u0026gt;\u0026quot;...] VOLUME \u0026lt;路径\u0026gt; 之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。\nVOLUME /data 这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:\ndocker run -d -v mydata:/data xxxx 在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。\nEXPOSE 声明端口 格式为 EXPOSE \u0026lt;端口1\u0026gt; [\u0026lt;端口2\u0026gt;...]。\nEXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P时,会自动随机映射 EXPOSE 的端口。\nWORKDIR 指定工作目录 格式为 WORKDIR \u0026lt;工作目录路径\u0026gt;。\n使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。\nONBUILD 为他人做嫁衣裳 格式:ONBUILD \u0026lt;其它指令\u0026gt;。\nONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。\n仓库 Docker Hub docker search 查找的时候通过 -s N 参数可以指定仅显示评价为 N 星以上的镜像(新版本Docker推荐使用--filter=stars=N参数)。\ndocker search centos --filter=stars=20 NAME DESCRIPTION STARS OFFICIAL AUTOMATED centos The official build of CentOS. 3631 [OK] ansible/centos7-ansible Ansible on Centos7 101 [OK] jdeathe/centos-ssh CentOS-6 6.9 ... 83 [OK] tutum/centos Simple CentOS docker 33 imagine10255/centos6-lnmp-php56 centos6-lnmp-php56 30 [OK] gluster/gluster-centos Official GlusterFS Image [ CentOS-7 + Glu... 20 [OK] 网络 容器互联 --link参数可以让容器之间安全的进行交互\n--link 参数的格式为 --link name:alias,其中 name 是要链接的容器的名称,alias 是这个连接的别名。\n","permalink":"https://jsharkc.github.io/post/docker-note/","summary":"Docker Note 镜像 虚悬镜像 \t没有仓库名,也没有标签,均为\u0026lt;none\u0026gt;的为虚悬镜像:\n\u0026lt;none\u0026gt;\t\u0026lt;none\u0026gt;\t00285df0df87\t5 days ago\t342 MB \t由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为\u0026lt;none\u0026gt;,可用下面的命令专门显示这类镜像:\n$ docker images -f dangling=ture REPOSITORY\tTAG\tIMAGE ID CREATED\tSIZE \u0026lt;none\u0026gt;\t\u0026lt;none\u0026gt;\t00285df0df87\t5 days ago 342 MB \t一般来说,虚悬镜像已经失去了存在价值,可以随意删除,可用下面命令删除。\n$ docker rmi $(docker images -q -f dangling=ture) docker images ####—filter -f\n\tdocker images 还支持强大的过滤器参数 --filter\t,或者简写-f。上面我们看到使用过滤器来列出虚悬镜像的方法,它还有更多的用法。比如,我们希望看到在mongo:3.2之后建立的镜像,可以用下面的命令:\n$ docker images -f since=mongo:3.2 REPOSITORY\tTAG\tIMAGE ID CREATED\tSIZE redis\tlatest\t5f515359c7f8\t5 days ago\t183 MB nginx\tlatest\t05a60462f8ba\t5 days ago\t181 MB \t想查看某个位置之前的镜像也可以,只需要吧since换成before即可。","title":"Docker Note"},{"content":"Linux防火墙\u0026ndash;iptables学习 转自简书\niptables是Linux系统提供的一个强大的防火墙工具,可以实现包过滤、包重定向、NAT转换等功能。iptables是免费的,iptables是一个工具,实际的功能是通过netfilter模块来实现的,在内核2.4版本后默认集成到了Linux内核中。\n一、 iptables的构成 1. 规则(rules) 规则是iptables对数据包进行操作的基本单元。即“当数据包符合规则定义的条件时,就按照规则中定义的动作去处理”。\n规则中定义的条件一般包括源地址/端口、目的地址/端口、传输协议(TCP/UDP/ICMP)等。而规则定义的动作一般有放行(ACCEPT)、拒绝(REJECT)和丢弃(DROP)等。\n配置iptables实际上就是增删修改这些规则。\n2. 链(chains) 链是数据包传播的路径,每条链中都有若干个规则。当一个数据包到达一条链时,iptables会按照规则的顺序,从该链的第一条规则开始往下检查,如果有条件匹配的规则,则按照规则定义的动作执行;否则继续检查下一条规则。如果该数据包和链中所有的规则都不匹配,则iptables会根据该链预先定义的默认策略来处理数据包。\n3. 表(tables) iptables内置了4个表,即filter表、nat表、mangle表、raw表,分别用于实现包过滤、网络地址转换、包修改和数据跟踪处理等功能。每个表中含有若干条链,具体的规则就是根据实现目的的不同,添加到不同表的不同链中。\n如下图所示,各个表相关的链:\nraw表有2条链:PREROUTING、OUTPUT\nmangle表有5条链:PREROUTING、POSTROUTING、INPUT、OUTPUT、FORWARD\nnat表有3条链:PREROUTING、POSTROUTING、OUTPUT\nfilter表有3条链:INPUT、FORWARD、OUTPUT\n4个表的优先级为:raw \u0026gt; mangle \u0026gt; nat \u0026gt; filter\n二、iptables的传输过程 当数据包到达网卡时,首先会进入PREROUTING链(注意,raw表处理完后就不会再进入nat表了),完成PREROUTING链中规则的匹配和执行后(比如DNAT),iptables根据数据包的目的IP是否为本机地址,判断是否需要将该数据包转发出去。\n 如果数据包的目的IP为本机地址,它就会进入INPUT链。可以在filter表的INPUT链中添加包过滤规则,限制哪些数据包可以访问本机;经过INPUT链中的规则处理后,剩下的数据包在本机上任何进程都可以收到,并根据需要对它们进行处理;当进程处理完后,需要发送出去的数据包会经过OUTPUT链的处理,然后到达POSTROUTING链,经过处理(比如SNAT)后输出。 \\2. 如果数据包的目的IP不是本机地址(比如做了DNAT、或者只是作为默认网关时走过来的包),并且系统内核开启了转发功能(ip_forward参数为1),则数据包会进入FORWARD链;此时可以在filter表的FORWARD链中设置相应的规则进行处理;经过FORWARD链的数据包再走到POSTROUTING链中处理(比如执行SNAT),最后输出。\n综上,数据包在iptables中的传输链路有两种情况:\n第一种:PREROUTING -\u0026gt; FORWARD -\u0026gt; POSTROUTING\n第二种:PREROUTING -\u0026gt; INPUT -\u0026gt; LOCALHOST -\u0026gt; OUTPUT -\u0026gt; PUSTROUTING\n所以,要想对数据包进行控制,主要可以在上面几条链路中添加规则。\n(可以发现LVS的NAT模式下,会经过PREROUTING链(nat表) -\u0026gt; FORWARD链 -\u0026gt; POSTROUTING链(nat表),请求的转发在PREROUTING链中进行DNAT,响应在POSTROUTING链中进行SNAT。无论时请求还是响应都不会走到INPUT链和OUTPUT链中)\n三、 iptables的使用 iptables对链的操作方法有:-I(插入),-A(追加),-R(替换),-D(删除),-L(显示)\n注意,-I是将规则插入到第一行,-A是将规则追加到链的最后一行。\niptables -F:清除所有的规则\niptables -F -t nat:清除nat表的规则\niptables -P INPUT DROP:配置INPUT链的默认规则为DROP\n示例:\niptables -t filter -A INPUT -s 192.168.1.10 -j ACCEPT\n这是一条简单的规则,表示在filter表的INPUT链中追加一条规则,规则的匹配条件是数据包的源IP为192.168.1.10,执行动作为允许(ACCEPT),即允许源IP为192.168.1.10地址的消息访问本机。\niptables中,默认表名就是filter,所以上面的规则也可以写成:\niptables -A INPUT -s 192.168.1.10 -j ACCEPT\niptables规则的匹配条件中常用的参数有:\n-s匹配源地址\n-d匹配目的地址\n\u0026ndash;sport匹配源端口\n\u0026ndash;dport匹配目的端口\n-p匹配协议\n-i匹配输入的网卡\n-o匹配输出的网卡\n!取反\n-j规则的执行动作\n示例:配置NAT转发实现内网节点访问公网\n环境假设:\n路由节点:\nLan口:192.168.1.10/24 eth0\nWan口:50.75.153.98/24 eth1\n内网节点:192.168.1.92\n\\1. 在内网节点上配置默认网关为路由节点的Lan口,确保内网节点的包都能走到路由节点。\nroute add default gw 192.168.1.10\n\\2. 在路由节点上打开linux的路由转发功能。\nsysctl -w net.ipv4.ip_forward=1\n\\3. 将FORWARD链的默认策略设置为DROP\niptables -P FORWARD DROP\n这样确保对内网的控制,只把允许访问公网的IP添加到规则中。\n\\4. 允许任意地址之间的确认包和关联包通过。\niptables -A FORWARD -m state \u0026ndash;state ESTABLISHED,RELATED -j ACCEPT\n这条规则很关键,否则即使后面添加了允许IP访问的规则也没用。\n\\5. 允许指定IP地址访问公网\niptables -A FORWARD -s 192.168.1.92/24 -j ACCEPT\n\\6. 配置一条SNAT规则,将内网节点的源IP转换为公网IP后,再将消息发出。\niptables -t nat -A POSTROUTING -s 192.168.1.92 -j SNAT \u0026ndash;to 50.75.153.98\n示例:配置NAT转发实现公网用户访问内网节点\n环境假设:\n路由节点:\nLan口:192.168.1.10/24 eth0\nWan口:50.75.153.98/24 eth1\n内网节点:192.168.1.92\n 在路由节点上打开linux的路由转发功能。 sysctl -w net.ipv4.ip_forward=1\n\\2. 将FORWARD链的默认策略设置为DROP\niptables -P FORWARD DROP\n这样确保对内网的控制,只把允许访问的IP添加到规则中。\n\\3. 允许任意地址之间的确认包和关联包通过。\niptables -A FORWARD -m state \u0026ndash;state ESTABLISHED,RELATED -j ACCEPT\n这条规则很关键,否则即使后面添加了允许IP访问的规则也没用。\n\\4. 允许访问指定的IP地址\niptables -A FORWARD -d 192.168.1.92 -j ACCEPT\n\\5. 配置一条DNAT规则,将访问路由节点的公网地址转换为访问内网节点的私网地址。\niptables -t nat -A POSTROUTING -d 50.75.153.98 -j DNAT \u0026ndash;to 192.168.1.92\nPS:用iptables的raw表解决ip_conntrack:table full, dropping packet的问题\nraw表中包含PREROUTING链和OUTPUT链,优先级最高,可以对数据包在进入NAT表的PREROUTING链之前对消息进行处理。当用户启用了RAW表,消息在经过RAW表的PREROUTING链处理后,将跳过NAT表和ip_conntrack处理,不再做地址转换和数据包的链接跟踪处理。\n所以raw表可以用在那些不需要做nat和链接跟踪的情况,提升系统性能。因为启用链接跟踪时,系统会建立一个链接跟踪表,每个消息进来时,都会去查询链接跟踪表,当系统业务量过大时,可能会引发系统CPU消耗过高。\n可以通过在RAW表的PREROUTING链中配置NOTRACK标记,使数据包不再进入nat表,达到不做链接跟踪的目的,如:\niptables -t raw -A PREROUTING -p tcp -d 192.168.1.10 \u0026ndash;dport 80 -j NOTRACK\niptables -A FORWARD -m state \u0026ndash;state UNTRACKED -j ACCEPT\n表示对访问192.168.1.10:80服务的消息在raw表的PREROUTING链中添加NORTRACK标记,以后访问这个服务的消息都不会再进行链接跟踪。\n另外,再附上鸟哥的iptables流程图,可以看到各个链也是根据不同的表区分优先级的。\n","permalink":"https://jsharkc.github.io/post/linux-iptables/","summary":"Linux防火墙\u0026ndash;iptables学习 转自简书\niptables是Linux系统提供的一个强大的防火墙工具,可以实现包过滤、包重定向、NAT转换等功能。iptables是免费的,iptables是一个工具,实际的功能是通过netfilter模块来实现的,在内核2.4版本后默认集成到了Linux内核中。\n一、 iptables的构成 1. 规则(rules) 规则是iptables对数据包进行操作的基本单元。即“当数据包符合规则定义的条件时,就按照规则中定义的动作去处理”。\n规则中定义的条件一般包括源地址/端口、目的地址/端口、传输协议(TCP/UDP/ICMP)等。而规则定义的动作一般有放行(ACCEPT)、拒绝(REJECT)和丢弃(DROP)等。\n配置iptables实际上就是增删修改这些规则。\n2. 链(chains) 链是数据包传播的路径,每条链中都有若干个规则。当一个数据包到达一条链时,iptables会按照规则的顺序,从该链的第一条规则开始往下检查,如果有条件匹配的规则,则按照规则定义的动作执行;否则继续检查下一条规则。如果该数据包和链中所有的规则都不匹配,则iptables会根据该链预先定义的默认策略来处理数据包。\n3. 表(tables) iptables内置了4个表,即filter表、nat表、mangle表、raw表,分别用于实现包过滤、网络地址转换、包修改和数据跟踪处理等功能。每个表中含有若干条链,具体的规则就是根据实现目的的不同,添加到不同表的不同链中。\n如下图所示,各个表相关的链:\nraw表有2条链:PREROUTING、OUTPUT\nmangle表有5条链:PREROUTING、POSTROUTING、INPUT、OUTPUT、FORWARD\nnat表有3条链:PREROUTING、POSTROUTING、OUTPUT\nfilter表有3条链:INPUT、FORWARD、OUTPUT\n4个表的优先级为:raw \u0026gt; mangle \u0026gt; nat \u0026gt; filter\n二、iptables的传输过程 当数据包到达网卡时,首先会进入PREROUTING链(注意,raw表处理完后就不会再进入nat表了),完成PREROUTING链中规则的匹配和执行后(比如DNAT),iptables根据数据包的目的IP是否为本机地址,判断是否需要将该数据包转发出去。\n 如果数据包的目的IP为本机地址,它就会进入INPUT链。可以在filter表的INPUT链中添加包过滤规则,限制哪些数据包可以访问本机;经过INPUT链中的规则处理后,剩下的数据包在本机上任何进程都可以收到,并根据需要对它们进行处理;当进程处理完后,需要发送出去的数据包会经过OUTPUT链的处理,然后到达POSTROUTING链,经过处理(比如SNAT)后输出。 \\2. 如果数据包的目的IP不是本机地址(比如做了DNAT、或者只是作为默认网关时走过来的包),并且系统内核开启了转发功能(ip_forward参数为1),则数据包会进入FORWARD链;此时可以在filter表的FORWARD链中设置相应的规则进行处理;经过FORWARD链的数据包再走到POSTROUTING链中处理(比如执行SNAT),最后输出。\n综上,数据包在iptables中的传输链路有两种情况:\n第一种:PREROUTING -\u0026gt; FORWARD -\u0026gt; POSTROUTING\n第二种:PREROUTING -\u0026gt; INPUT -\u0026gt; LOCALHOST -\u0026gt; OUTPUT -\u0026gt; PUSTROUTING\n所以,要想对数据包进行控制,主要可以在上面几条链路中添加规则。\n(可以发现LVS的NAT模式下,会经过PREROUTING链(nat表) -\u0026gt; FORWARD链 -\u0026gt; POSTROUTING链(nat表),请求的转发在PREROUTING链中进行DNAT,响应在POSTROUTING链中进行SNAT。无论时请求还是响应都不会走到INPUT链和OUTPUT链中)\n三、 iptables的使用 iptables对链的操作方法有:-I(插入),-A(追加),-R(替换),-D(删除),-L(显示)\n注意,-I是将规则插入到第一行,-A是将规则追加到链的最后一行。\niptables -F:清除所有的规则\niptables -F -t nat:清除nat表的规则\niptables -P INPUT DROP:配置INPUT链的默认规则为DROP","title":"Linux防火墙-iptables"},{"content":"初探 Cockroachdb 1.创建网桥 由于在单个主机上运行多个 Docker 容器,因此每个容器有一个 CockroachDB 节点,需要创建Docker所指的桥接网络。桥接网络将使容器能够作为单个群集进行通信,同时保持与外部网络的隔离。\ndocker network create -d bridge roachnet我们在 roachnet 这里和随后的步骤中使用了网络名称,但是请随时给您的网络任何您喜欢的名字。\n2.启动第一个节点 docker run -d \\ --name=roach1 \\ --hostname=roach1 \\ --net=roachnet \\ -p 26257:26257 -p 8080:8080 \\ -v \u0026#34;${PWD}/cockroach-data/roach1:/cockroach/cockroach-data\u0026#34; \\ cockroachdb/cockroach:v1.0.4 start --insecure此命令创建一个容器并启动其中的第一个 CockroachDB 节点。我们来看看每个部分:\ndocker run:Docker 命令启动一个新的容器。\n-d:这个标志在后台运行容器,所以你可以在同一个shell中继续下一步。\n--name:容器的名称。这是可选的,但是自定义名称使得在其他命令中引用容器更容易,例如在容器中打开Bash会话或停止容器时。\n--hostname:容器的主机名。您将使用它将其他容器/节点连接到集群。\n--net:用于容器加入的网桥。有关详细信息,请参阅步骤1。\n-p 26257:26257 -p 8080:8080:这些标志将用于节点间和客户端节点通信(26257)的默认端口和 8080 从容器到主机的管理UI()的 HTTP 请求的默认端口映射。这允许集装箱间通信,并可以从浏览器调用管理 UI。\n-v \u0026quot;${PWD}/cockroach-data/roach1:/cockroach/cockroach-data\u0026quot;:该标志挂载作为数据卷的主机目录。这意味着该节点的数据和日志将存储在 ${PWD}/cockroach-data/roach1 主机上,并在容器停止或删除后持续。有关更多详细信息,请参阅 Docker 将主机目录作为数据卷主题。 cockroachdb/cockroach:v1.0.4 start --insecure:CockroachDB 命令以不安全的方式启动容器中的一个节点。\n3.将节点添加到集群 在这一点上,您的群集是实时和可操作的。只需一个节点,您就可以连接一个 SQL 客户端并开始构建数据库。然而,在实际部署中,您总是希望3个或更多节点可以利用 CockroachDB 的自动复制,重新平衡和容错能力。\n要模拟真正的部署,通过添加另外两个节点来扩展集群:\n# Start the second container/node:docker run -d \\ --name=roach2 \\ --hostname=roach2 \\ --net=roachnet \\ -v \u0026#34;${PWD}/cockroach-data/roach2:/cockroach/cockroach-data\u0026#34; \\ cockroachdb/cockroach:v1.0.4 start --insecure --join=roach1# Start the third container/node:docker run -d \\ --name=roach3 \\ --hostname=roach3 \\ --net=roachnet \\ -v \u0026#34;${PWD}/cockroach-data/roach3:/cockroach/cockroach-data\u0026#34; \\ cockroachdb/cockroach:v1.0.4 start --insecure --join=roach1这些命令添加了两个容器,并在其中启动 CockroachDB 节点,并将它们连接到第一个节点。从步骤2中只需要注意几点:\n-v:该标志挂载作为数据卷的主机目录。数据和日志这些节点将被存储在 ${PWD}/cockroach-data/roach2与${PWD}/cockroach-data/roach3 主机上和容器停止或删除之后将继续存在。 --join:该标志使用第一个容器将新节点连接到集群 hostname。否则,所有 cockroach start 默认值都被接受。请注意,由于每个节点都在唯一的容器中,所以使用相同的默认端口不会引起冲突。\n4.测试集群 现在已经扩展到3个节点,可以使用任何节点作为集群的 SQL 网关。为了演示这一点,使用 docker exec 命令在第一个容器中启动内置的 SQL shell:\ndocker exec -it roach1 ./cockroach sql --insecure# Welcome to the cockroach SQL interface.# All statements must be terminated by a semicolon.# To exit: CTRL + D.运行一些基本的 CockroachDB SQL 语句:\n\u0026gt; CREATE DATABASE bank; \u0026gt; CREATE TABLE bank.accounts (id INT PRIMARY KEY, balance DECIMAL); \u0026gt; INSERT INTO bank.accounts VALUES (1, 1000.50); \u0026gt; SELECT * FROM bank.accounts; +----+---------+ | id | balance | +----+---------+ | 1 | 1000.5 | +----+---------+ (1 row) 退出节点1上的 SQL shell:\n\u0026gt; \\q 然后在第二个容器中启动 SQL shell:\ndocker exec -it roach2 ./cockroach sql --insecure# Welcome to the cockroach SQL interface.# All statements must be terminated by a semicolon.# To exit: CTRL + D.现在运行相同的 SELECT 查询:\n\u0026gt; SELECT * FROM bank.accounts; +----+---------+ | id | balance | +----+---------+ | 1 | 1000.5 | +----+---------+ \u0026gt; (1 row) 如您所见,节点1和节点2的行为与 SQL 网关相同。\n完成后,退出节点2上的 SQL shell:\n\u0026gt; \\q 5.监控集群 当启动第一个容器/节点时,将节点的默认HTTP端口映射8080到8080主机上的端口。要查看集群的管理UI,请将浏览器指向该端口localhost,即 http://localhost:8080。\n如前所述,CockroachDB会自动将您的数据复制到幕后。要验证上一步中写入的数据是否已成功复制,请向下滚动到每个商店的副本图表并将其悬停在该行上:\n每个节点上的副本计数相同,表示集群中的所有数据都被复制3次(默认值)。\n6.停止集群 使用 docker stop 和 docker rm 命令停止和删除容器(因此集群):\n# Stop the containers:docker stop roach1 roach2 roach3# Remove the containers:docker rm roach1 roach2 roach3","permalink":"https://jsharkc.github.io/post/cockroachdb/","summary":"初探 Cockroachdb 1.创建网桥 由于在单个主机上运行多个 Docker 容器,因此每个容器有一个 CockroachDB 节点,需要创建Docker所指的桥接网络。桥接网络将使容器能够作为单个群集进行通信,同时保持与外部网络的隔离。\ndocker network create -d bridge roachnet我们在 roachnet 这里和随后的步骤中使用了网络名称,但是请随时给您的网络任何您喜欢的名字。\n2.启动第一个节点 docker run -d \\ --name=roach1 \\ --hostname=roach1 \\ --net=roachnet \\ -p 26257:26257 -p 8080:8080 \\ -v \u0026#34;${PWD}/cockroach-data/roach1:/cockroach/cockroach-data\u0026#34; \\ cockroachdb/cockroach:v1.0.4 start --insecure此命令创建一个容器并启动其中的第一个 CockroachDB 节点。我们来看看每个部分:\ndocker run:Docker 命令启动一个新的容器。\n-d:这个标志在后台运行容器,所以你可以在同一个shell中继续下一步。\n--name:容器的名称。这是可选的,但是自定义名称使得在其他命令中引用容器更容易,例如在容器中打开Bash会话或停止容器时。\n--hostname:容器的主机名。您将使用它将其他容器/节点连接到集群。\n--net:用于容器加入的网桥。有关详细信息,请参阅步骤1。\n-p 26257:26257 -p 8080:8080:这些标志将用于节点间和客户端节点通信(26257)的默认端口和 8080 从容器到主机的管理UI()的 HTTP 请求的默认端口映射。这允许集装箱间通信,并可以从浏览器调用管理 UI。\n-v \u0026quot;${PWD}/cockroach-data/roach1:/cockroach/cockroach-data\u0026quot;:该标志挂载作为数据卷的主机目录。这意味着该节点的数据和日志将存储在 ${PWD}/cockroach-data/roach1 主机上,并在容器停止或删除后持续。有关更多详细信息,请参阅 Docker 将主机目录作为数据卷主题。 cockroachdb/cockroach:v1.0.4 start --insecure:CockroachDB 命令以不安全的方式启动容器中的一个节点。\n3.将节点添加到集群 在这一点上,您的群集是实时和可操作的。只需一个节点,您就可以连接一个 SQL 客户端并开始构建数据库。然而,在实际部署中,您总是希望3个或更多节点可以利用 CockroachDB 的自动复制,重新平衡和容错能力。","title":"初探 Cockroachdb"},{"content":"Go 程序的性能优化及 pprof 的使用 转自snowInPluto\n程序的性能优化无非就是对程序占用资源的优化。对于服务器而言,最重要的两项资源莫过于 CPU 和内存。性能优化,就是在对于不影响程序数据处理能力的情况下,我们通常要求程序的 CPU 的内存占用尽量低。反过来说,也就是当程序 CPU 和内存占用不变的情况下,尽量地提高程序的数据处理能力或者说是吞吐量。\nGo 的原生工具链中提供了非常多丰富的工具供开发者使用,其中包括 pprof。\n对于 pprof 的使用要分成下面两部分来说。\nWeb 程序使用 pprof 先写一个简单的 Web 服务程序。程序在 9876 端口上接收请求。\npackage main import ( \u0026quot;bytes\u0026quot; \u0026quot;io/ioutil\u0026quot; \u0026quot;log\u0026quot; \u0026quot;math/rand\u0026quot; \u0026quot;net/http\u0026quot; _ \u0026quot;net/http/pprof\u0026quot; ) func main() { http.HandleFunc(\u0026quot;/test\u0026quot;, handler) log.Fatal(http.ListenAndServe(\u0026quot;:9876\u0026quot;, nil)) } func handler(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if nil != err { w.Write([]byte(err.Error())) return } doSomeThingOne(10000) buff := genSomeBytes() b, err := ioutil.ReadAll(buff) if nil != err { w.Write([]byte(err.Error())) return } w.Write(b) } func doSomeThingOne(times int) { for i := 0; i \u0026lt; times; i++ { for j := 0; j \u0026lt; times; j++ { } } } func genSomeBytes() *bytes.Buffer { var buff bytes.Buffer for i := 1; i \u0026lt; 20000; i++ { buff.Write([]byte{'0' + byte(rand.Intn(10))}) } return \u0026amp;buff } 可以看到我们只是简单地引入了 net/http/pprof ,并未显示地使用。\n启动程序。\n我们用 wrk 来简单地模拟请求。\nwrk -c 400 -t 8 -d 3m http://localhost:9876/test\n这时我们打开 http://localhost:9876/debug/pprof,会显示如下页面:\n用户可以点击相应的链接浏览内容。不过这不是我们重点讲述的,而且这些内容看起来并不直观。\n我们打开链接 http://localhost:9876/debug/pprof/profile 稍后片刻,可以下载到文件 profile。\n使用 Go 自带的 pprof 工具打开。go tool pprof test profile。(proof 后跟的 test 为程序编译的可执行文件)\n输入 top 命令得到:\n可以看到 cpu 占用前 10 的函数,我们可以对此分析进行优化。\n只是这样可能还不是很直观。\n我们输入命令 web(需要事先安装 graphviz,macOS 下可以 brew install graphviz),会在浏览器中打开界面如下:\n可以看到 main.doSomeThingOne 占用了 92.46% 的 CPU 时间,需要对其进行优化。\nWeb 形式的 CPU 时间图对于优化已经完全够用,这边再介绍一下火焰图的生成。macOS 推荐使用 go-torch 工具。使用方法和 go tool pprof 相似。\ngo-torch test profile 会生成 torch.svg 文件。可以用浏览器打开,如图。\n刚才只是讲了 CPU 的占用分析文件的生成查看,其实内存快照的生成相似。http://localhost:9876/debug/pprof/heap,会下载得到 heap.gz 文件。\n我们同样可以使用 go tool pprof test heap.gz,然后输入 top 或 web 命令查看相关内容。\n通用程序使用 pprof 我们写的 Go 程序并非都是 Web 程序,这时候再使用上面的方法就不行了。\n我们仍然可以使用 pprof 工具,但引入的位置为 runtime/pprof 。\n这里贴出两个函数,作为示例:\n// 生成 CPU 报告 func cpuProfile() { f, err := os.OpenFile(\u0026quot;cpu.prof\u0026quot;, os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Fatal(err) } defer f.Close() log.Println(\u0026quot;CPU Profile started\u0026quot;) pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() time.Sleep(60 * time.Second) fmt.Println(\u0026quot;CPU Profile stopped\u0026quot;) } // 生成堆内存报告 func heapProfile() { f, err := os.OpenFile(\u0026quot;heap.prof\u0026quot;, os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Fatal(err) } defer f.Close() time.Sleep(30 * time.Second) pprof.WriteHeapProfile(f) fmt.Println(\u0026quot;Heap Profile generated\u0026quot;) } 两个函数分别会生成 cpu.prof 和 heap.prof 文件。仍然可以使用 go tool pprof 工具进行分析,在此就不赘述。\nTrace 报告 直接贴代码:\n// 生成追踪报告 func traceProfile() { f, err := os.OpenFile(\u0026quot;trace.out\u0026quot;, os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Fatal(err) } defer f.Close() log.Println(\u0026quot;Trace started\u0026quot;) trace.Start(f) defer trace.Stop() time.Sleep(60 * time.Second) fmt.Println(\u0026quot;Trace stopped\u0026quot;) } 使用工具 go tool trace 进行分析,会得到非常详细的追踪报告,供更深入的程序分析优化。由于报告内容比较复杂,且使用方法类似,就不继续了。读者可自行尝试。\n","permalink":"https://jsharkc.github.io/post/go%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96pprof/","summary":"Go 程序的性能优化及 pprof 的使用 转自snowInPluto\n程序的性能优化无非就是对程序占用资源的优化。对于服务器而言,最重要的两项资源莫过于 CPU 和内存。性能优化,就是在对于不影响程序数据处理能力的情况下,我们通常要求程序的 CPU 的内存占用尽量低。反过来说,也就是当程序 CPU 和内存占用不变的情况下,尽量地提高程序的数据处理能力或者说是吞吐量。\nGo 的原生工具链中提供了非常多丰富的工具供开发者使用,其中包括 pprof。\n对于 pprof 的使用要分成下面两部分来说。\nWeb 程序使用 pprof 先写一个简单的 Web 服务程序。程序在 9876 端口上接收请求。\npackage main import ( \u0026quot;bytes\u0026quot; \u0026quot;io/ioutil\u0026quot; \u0026quot;log\u0026quot; \u0026quot;math/rand\u0026quot; \u0026quot;net/http\u0026quot; _ \u0026quot;net/http/pprof\u0026quot; ) func main() { http.HandleFunc(\u0026quot;/test\u0026quot;, handler) log.Fatal(http.ListenAndServe(\u0026quot;:9876\u0026quot;, nil)) } func handler(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if nil != err { w.Write([]byte(err.Error())) return } doSomeThingOne(10000) buff := genSomeBytes() b, err := ioutil.","title":"Go 程序的性能优化及 pprof 的使用"},{"content":"跨域资源共享 CORS 详解 CORS是一个W3C标准,全称是\u0026quot;跨域资源共享\u0026quot;(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 本文详细介绍CORS的内部机制。\n一、简介 CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。\n整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。\n因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。\n二、两种请求 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。\n只要同时满足以下两大条件,就属于简单请求。\n 请求方法是以下三种方法之一: HEAD GET POST (2)HTTP的头信息不超出以下几种字段:\n Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 凡是不同时满足上面两个条件,就属于非简单请求。\n浏览器对这两种请求的处理,是不一样的。\n三、简单请求 3.1 基本流程 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。\n下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。\nGET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。\n如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。\n如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。\nAccess-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8 上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。\n(1)Access-Control-Allow-Origin\n该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。\n(2)Access-Control-Allow-Credentials\n该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。\n(3)Access-Control-Expose-Headers\n该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。\n3.2 withCredentials 属性 上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。\nAccess-Control-Allow-Credentials: true 另一方面,开发者必须在AJAX请求中打开withCredentials属性。\nvar xhr = new XMLHttpRequest(); xhr.withCredentials = true; 否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。\n但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。\nxhr.withCredentials = false; 需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。\n四、非简单请求 4.1 预检请求 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。\n非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为\u0026quot;预检\u0026quot;请求(preflight)。\n浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。\n下面是一段浏览器的JavaScript脚本。\nvar url = \u0026#39;http://api.alice.com/cors\u0026#39;; var xhr = new XMLHttpRequest(); xhr.open(\u0026#39;PUT\u0026#39;, url, true); xhr.setRequestHeader(\u0026#39;X-Custom-Header\u0026#39;, \u0026#39;value\u0026#39;); xhr.send(); 上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。\n浏览器发现,这是一个非简单请求,就自动发出一个\u0026quot;预检\u0026quot;请求,要求服务器确认可以这样请求。下面是这个\u0026quot;预检\u0026quot;请求的HTTP头信息。\nOPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... \u0026ldquo;预检\u0026quot;请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。\n除了Origin字段,\u0026ldquo;预检\u0026quot;请求的头信息包括两个特殊字段。\n(1)Access-Control-Request-Method\n该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。\n(2)Access-Control-Request-Headers\n该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。\n4.2 预检请求的回应 服务器收到\u0026quot;预检\u0026quot;请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。\nHTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain 上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。\nAccess-Control-Allow-Origin: * 如果浏览器否定了\u0026quot;预检\u0026quot;请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。\nXMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin. 服务器回应的其他CORS相关字段如下。\nAccess-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000 (1)Access-Control-Allow-Methods\n该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次\u0026quot;预检\u0026quot;请求。\n(2)Access-Control-Allow-Headers\n如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在\u0026quot;预检\u0026quot;中请求的字段。\n(3)Access-Control-Allow-Credentials\n该字段与简单请求时的含义相同。\n(4)Access-Control-Max-Age\n该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。\n4.3 浏览器的正常请求和回应 一旦服务器通过了\u0026quot;预检\u0026quot;请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。\n下面是\u0026quot;预检\u0026quot;请求之后,浏览器的正常CORS请求。\nPUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 上面头信息的Origin字段是浏览器自动添加的。\n下面是服务器正常的回应。\nAccess-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8 上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。\n五、与JSONP的比较 CORS与JSONP的使用目的相同,但是比JSONP更强大。\nJSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。\n(完)\n","permalink":"https://jsharkc.github.io/post/cors/","summary":"\u003ch1 id=\"跨域资源共享-cors-详解\"\u003e跨域资源共享 CORS 详解\u003c/h1\u003e\n\u003cp\u003eCORS是一个W3C标准,全称是\u0026quot;跨域资源共享\u0026quot;(Cross-origin resource sharing)。\n它允许浏览器向跨源服务器,发出\u003ccode\u003eXMLHttpRequest\u003c/code\u003e请求,从而克服了AJAX只能\u003cstrong\u003e同源\u003c/strong\u003e使用的限制。\n本文详细介绍CORS的内部机制。\u003c/p\u003e","title":"跨域资源共享 CORS 详解"},{"content":"Git专有名词解释: Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(或本地仓库) Remote:远程仓库 一、新建代码库 # 在当前目录新建一个Git代码库 $ git init # 新建一个目录,将其初始化为Git代码库 $ git init [project-name] # 下载一个项目和它的整个代码历史 $ git clone [url] 二、配置 Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)\n# 显示当前的Git配置 $ git config --list # 编辑Git配置文件 $ git config -e [--golbal] # 设置提交代码时的用户信息 $ git config [--global] user.name \u0026quot;[name]\u0026quot; $ git config [--global] user.email \u0026quot;[email address]\u0026quot; 三、增加 / 删除文件 # 添加制定文件到暂存区 $ git add [file1] [file2] ... # 添加制定目录到暂存区,包括子目录 $ git add [dir] # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 $ git add -p # 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file1] [file2]... # 停止追踪指定文件,但该文件会保留在工作区 $ git rm --cached [file] # 改名文件,并且将这个改名放入暂存区 $ git mv [file-original] [file-renamed] ","permalink":"https://jsharkc.github.io/post/git%E5%91%BD%E4%BB%A4%E6%B8%85%E5%8D%95/","summary":"Git专有名词解释: Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(或本地仓库) Remote:远程仓库 一、新建代码库 # 在当前目录新建一个Git代码库 $ git init # 新建一个目录,将其初始化为Git代码库 $ git init [project-name] # 下载一个项目和它的整个代码历史 $ git clone [url] 二、配置 Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)\n# 显示当前的Git配置 $ git config --list # 编辑Git配置文件 $ git config -e [--golbal] # 设置提交代码时的用户信息 $ git config [--global] user.name \u0026quot;[name]\u0026quot; $ git config [--global] user.email \u0026quot;[email address]\u0026quot; 三、增加 / 删除文件 # 添加制定文件到暂存区 $ git add [file1] [file2] ... # 添加制定目录到暂存区,包括子目录 $ git add [dir] # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 $ git add -p # 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file1] [file2].","title":"Git命令清单"},{"content":"Golang I/O 包的妙用 作者:icexin 转自简书\ngolang标准库对io的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。这篇文章结合一个实际的例子来和大家分享一下。\n背景 以一个RPC的协议包来说,每个包有如下结构\ntype Packet struct { TotalSize uint32 Magic [4]byte Payload []byte Checksum uint32 } 其中TotalSize是整个包除去TotalSize后的字节数, Magic是一个固定长度的字串,Payload是包的实际内容,包含业务逻辑的数据。 Checksum是对Magic和Payload的adler32校验和。\n编码(encode) 我们使用一个原型为func EncodePacket(w io.Writer, payload []byte) error的函数来把数据打包,结合encoding/binary我们很容易写出第一版,演示需要,错误处理方面就简化处理了。\nvar RPC_MAGIC = [4]byte{\u0026#39;p\u0026#39;, \u0026#39;y\u0026#39;, \u0026#39;x\u0026#39;, \u0026#39;i\u0026#39;} func EncodePacket(w io.Writer, payload []byte) error { // len(Magic) + len(Checksum) == 8 totalsize := uint32(len(payload) + 8) // write total size binary.Write(w, binary.BigEndian, totalsize) // write magic bytes binary.Write(w, binary.BigEndian, RPC_MAGIC) // write payload w.Write(payload) // calculate checksum var buf bytes.Buffer buf.Write(RPC_MAGIC[:]) buf.Write(payload) checksum := adler32.Checksum(buf.Bytes()) // write checksum return binary.Write(w, binary.BigEndian, checksum) } 在上面的实现中,为了计算checksum,我们使用了一个内存buffer来缓存数据,最后把所有的数据一次性读出来算checksum,考虑到计算checksum是一个不断update地过程,我们应该有方法直接略过内存buffer而计算checksum。\n查看hash/adler32我们得知,我们可以构造一个Hash32的对象,这个对象内嵌了一个Hash的接口,这个接口的定义如下:\ntype Hash interface { // Write (via the embedded io.Writer interface) adds more data to the running hash. // It never returns an error. io.Writer // Sum appends the current hash to b and returns the resulting slice. // It does not change the underlying hash state. Sum(b []byte) []byte // Reset resets the Hash to its initial state. Reset() // Size returns the number of bytes Sum will return. Size() int // BlockSize returns the hash\u0026#39;s underlying block size. // The Write method must be able to accept any amount // of data, but it may operate more efficiently if all writes // are a multiple of the block size. BlockSize() int } 这是一个通用的计算hash的接口,标准库里面所有计算hash的对象都实现了这个接口,比如md5, crc32等。由于Hash实现了io.Writer接口,因此我们可以把所有要计算的数据像写入文件一样写入到这个对象中,最后调用Sum(nil)就可以得到最终的hash的byte数组。利用这个思路,第二版可以这样写:\nfunc EncodePacket2(w io.Writer, payload []byte) error { // len(Magic) + len(Checksum) == 8 totalsize := uint32(len(RPC_MAGIC) + len(payload) + 4) // write total size binary.Write(w, binary.BigEndian, totalsize) // write magic bytes binary.Write(w, binary.BigEndian, RPC_MAGIC) // write payload w.Write(payload) // calculate checksum sum := adler32.New() sum.Write(RPC_MAGIC[:]) sum.Write(payload) checksum := sum.Sum32() // write checksum return binary.Write(w, binary.BigEndian, checksum) } 注意这次的变化,前面写入TotalSize,Magic,Payload部分没有变化,在计算checksum的时候去掉了bytes.Buffer,减少了一次内存申请和拷贝。\n考虑到sum和w都是io.Writer,利用神奇的io.MultiWriter,我们可以这样写\nfunc EncodePacket(w io.Writer, payload []byte) error { // len(Magic) + len(Checksum) == 8 totalsize := uint32(len(RPC_MAGIC) + len(payload) + 4) // write total size binary.Write(w, binary.BigEndian, totalsize) sum := adler32.New() ww := io.MultiWriter(sum, w) // write magic bytes binary.Write(ww, binary.BigEndian, RPC_MAGIC) // write payload ww.Write(payload) // calculate checksum checksum := sum.Sum32() // write checksum return binary.Write(w, binary.BigEndian, checksum) } 注意MultiWriter的使用,我们把w和sum利用MultiWriter绑在了一起创建了一个新的Writer,向这个Writer里面写入数据就同时向w和sum里面都写入数据,这样就完成了发送数据和计算checksum的同步进行,而对于binary.Write来说没有任何区别,因为它需要的是一个实现了Write方法的对象。\n解码(decode) 基于上面的思想,解码也可以把接收数据和计算checksum一起进行,完整代码如下\nfunc DecodePacket(r io.Reader) ([]byte, error) { var totalsize uint32 err := binary.Read(r, binary.BigEndian, \u0026amp;totalsize) if err != nil { return nil, errors.Annotate(err, \u0026#34;read total size\u0026#34;) } // at least len(magic) + len(checksum) if totalsize \u0026lt; 8 { return nil, errors.Errorf(\u0026#34;bad packet. header:%d\u0026#34;, totalsize) } sum := adler32.New() rr := io.TeeReader(r, sum) var magic [4]byte err = binary.Read(rr, binary.BigEndian, \u0026amp;magic) if err != nil { return nil, errors.Annotate(err, \u0026#34;read magic\u0026#34;) } if magic != RPC_MAGIC { return nil, errors.Errorf(\u0026#34;bad rpc magic:%v\u0026#34;, magic) } payload := make([]byte, totalsize-8) _, err = io.ReadFull(rr, payload) if err != nil { return nil, errors.Annotate(err, \u0026#34;read payload\u0026#34;) } var checksum uint32 err = binary.Read(r, binary.BigEndian, \u0026amp;checksum) if err != nil { return nil, errors.Annotate(err, \u0026#34;read checksum\u0026#34;) } if checksum != sum.Sum32() { return nil, errors.Errorf(\u0026#34;checkSum error, %d(calc) %d(remote)\u0026#34;, sum.Sum32(), checksum) } return payload, nil } 上面代码中,我们使用了io.TeeReader,这个函数的原型为func TeeReader(r Reader, w Writer) Reader,它返回一个Reader,这个Reader是参数r的代理,读取的数据还是来自r,不过同时把读取的数据写入到w里面。\n一切皆文件 unix下有一切皆文件的思想,golang把这个思想贯彻到更远,因为本质上我们对文件的抽象就是一个可读可写的一个对象,也就是实现了io.Writer和io.Reader的对象我们都可以称为文件,在上面的例子中无论是EncodePacket还是DecodePacket我们都没有假定编码后的数据是发送到socket,还是从内存读取数据解码,因此我们可以这样调用EncodePacket\nconn, _ := net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;127.0.0.1:8000\u0026#34;) EncodePacket(conn, []byte(\u0026#34;hello\u0026#34;)) 把数据直接发送到socket,也可以这样\nconn, _ := net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;127.0.0.1:8000\u0026#34;) bufconn := bufio.NewWriter(conn) EncodePacket(bufconn, []byte(\u0026#34;hello\u0026#34;)) 对socket加上一个buffer来增加吞吐量,也可以这样\nconn, _ := net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;127.0.0.1:8000\u0026#34;) zip := zlib.NewWriter(conn) bufconn := bufio.NewWriter(conn) EncodePacket(bufconn, []byte(\u0026#34;hello\u0026#34;)) 加上一个zip压缩,还可以利用加上crypto/aes来个AES加密\u0026hellip;\n在这个时候,文件已经不再局限于io,可以是一个内存buffer,也可以是一个计算hash的对象,甚至是一个计数器,流量限速器。golang灵活的接口机制为我们提供了无限可能。\n结尾 我一直认为一个好的语言一定有一个设计良好的标准库,golang的标准库是作者们多年系统编程的沉淀,值得我们细细品味\n","permalink":"https://jsharkc.github.io/post/golang_io%E5%8C%85_%E5%A6%99%E7%94%A8/","summary":"Golang I/O 包的妙用 作者:icexin 转自简书\ngolang标准库对io的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。这篇文章结合一个实际的例子来和大家分享一下。\n背景 以一个RPC的协议包来说,每个包有如下结构\ntype Packet struct { TotalSize uint32 Magic [4]byte Payload []byte Checksum uint32 } 其中TotalSize是整个包除去TotalSize后的字节数, Magic是一个固定长度的字串,Payload是包的实际内容,包含业务逻辑的数据。 Checksum是对Magic和Payload的adler32校验和。\n编码(encode) 我们使用一个原型为func EncodePacket(w io.Writer, payload []byte) error的函数来把数据打包,结合encoding/binary我们很容易写出第一版,演示需要,错误处理方面就简化处理了。\nvar RPC_MAGIC = [4]byte{\u0026#39;p\u0026#39;, \u0026#39;y\u0026#39;, \u0026#39;x\u0026#39;, \u0026#39;i\u0026#39;} func EncodePacket(w io.Writer, payload []byte) error { // len(Magic) + len(Checksum) == 8 totalsize := uint32(len(payload) + 8) // write total size binary.Write(w, binary.BigEndian, totalsize) // write magic bytes binary.","title":"Golang I/O 包的妙用"},{"content":"MongoDB 的正确打开方式 Part1 在关系型数据库中,处理关系时,总是会有一张关系表,不论关系的规模,但在 MongoDB 数据库中,需要考虑关系的规模,我们用一对多关系举例:\n 一对很少 一对许多 一对非常多 当关系规模不同时,处理也是有差别的。\n一对很少 针对个人需要保存多个地址进行建模的场景下使用内嵌文档是很合适,可以在person文档中嵌入addresses数组文档:\n这种设计具有内嵌文档设计中所有的优缺点。最主要的优点就是不需要单独执行一条语句去获取内嵌的内容。最主要的缺点是你无法把这些内嵌文档当做单独的实体去访问。\n例如,如果你是在对一个任务跟踪系统进行建模,每个用户将会被分配若干个任务。内嵌这些任务到用户文档在遇到“查询昨天所有的任务”这样的问题时将会非常困难。\n一对许多 以产品零件订货系统为例。每个商品有数百个可替换的零件,但是不会超过数千个。这个用例很适合使用间接引用\u0026mdash;将零件的objectid作为数组存放在商品文档中(在这个例子中的ObjectID我使用更加易读的2字节,现实世界中他们可能是由12个字节组成的)。\n每个零件都将有他们自己的文档对象\n每个产品的文档对象中 parts 数组中将会存放多个零件的 ObjectID:\n在获取特定产品中所有零件,需要一个应用层级别的join\n为了能快速的执行查询,必须确保products.catalog_number有索引。当然由于零件中parts._id一定是有索引的,所以这也会很高效。\n这种引用的方式是对内嵌优缺点的补充。每个零件是个单独的文档,可以很容易的独立去搜索和更新他们。需要一条单独的语句去获取零件的具体内容是使用这种建模方式需要考虑的一个问题(请仔细思考这个问题,在第二章反反范式化中,我们还会讨论这个问题)\n这种建模方式中的零件部分可以被多个产品使用,所以在多对多时不需要一张单独的连接表。\n一对非常多 我们用一个收集各种机器日志的例子来讨论一对非常多的问题。由于每个mongodb的文档有16M的大小限制,所以即使你是存储ObjectID也是不够的。我们可以使用很经典的处理方法“父级引用”\u0026mdash;用一个文档存储主机,在每个日志文档中保存这个主机的ObjectID。\n所以,即使这种简单的讨论也有能察觉出mongobd的建模和关系模型建模的不同之处。你必须要注意一下两个因素:\nWill the entities on the “N” side of the One-to-N ever need to stand alone?\n一对多中的多是否需要一个单独的实体。\nWhat is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?\n这个关系中集合的规模是一对很少,很多,还是非常多。\nBased on these factors, you can pick one of the three basic One-to-N schema designs:\n基于以上因素来决定采取一下三种建模的方式\n一对很少且不需要单独访问内嵌内容的情况下可以使用内嵌多的一方。\n一对多且多的一端内容因为各种理由需要单独存在的情况下可以通过数组的方式引用多的一方的。\n一对非常多的情况下,请将一的那端引用嵌入进多的一端对象中。\n","permalink":"https://jsharkc.github.io/post/mongodb%E6%AD%A3%E7%A1%AE%E6%89%93%E5%BC%80%E6%96%B9%E5%BC%8F/","summary":"MongoDB 的正确打开方式 Part1 在关系型数据库中,处理关系时,总是会有一张关系表,不论关系的规模,但在 MongoDB 数据库中,需要考虑关系的规模,我们用一对多关系举例:\n 一对很少 一对许多 一对非常多 当关系规模不同时,处理也是有差别的。\n一对很少 针对个人需要保存多个地址进行建模的场景下使用内嵌文档是很合适,可以在person文档中嵌入addresses数组文档:\n这种设计具有内嵌文档设计中所有的优缺点。最主要的优点就是不需要单独执行一条语句去获取内嵌的内容。最主要的缺点是你无法把这些内嵌文档当做单独的实体去访问。\n例如,如果你是在对一个任务跟踪系统进行建模,每个用户将会被分配若干个任务。内嵌这些任务到用户文档在遇到“查询昨天所有的任务”这样的问题时将会非常困难。\n一对许多 以产品零件订货系统为例。每个商品有数百个可替换的零件,但是不会超过数千个。这个用例很适合使用间接引用\u0026mdash;将零件的objectid作为数组存放在商品文档中(在这个例子中的ObjectID我使用更加易读的2字节,现实世界中他们可能是由12个字节组成的)。\n每个零件都将有他们自己的文档对象\n每个产品的文档对象中 parts 数组中将会存放多个零件的 ObjectID:\n在获取特定产品中所有零件,需要一个应用层级别的join\n为了能快速的执行查询,必须确保products.catalog_number有索引。当然由于零件中parts._id一定是有索引的,所以这也会很高效。\n这种引用的方式是对内嵌优缺点的补充。每个零件是个单独的文档,可以很容易的独立去搜索和更新他们。需要一条单独的语句去获取零件的具体内容是使用这种建模方式需要考虑的一个问题(请仔细思考这个问题,在第二章反反范式化中,我们还会讨论这个问题)\n这种建模方式中的零件部分可以被多个产品使用,所以在多对多时不需要一张单独的连接表。\n一对非常多 我们用一个收集各种机器日志的例子来讨论一对非常多的问题。由于每个mongodb的文档有16M的大小限制,所以即使你是存储ObjectID也是不够的。我们可以使用很经典的处理方法“父级引用”\u0026mdash;用一个文档存储主机,在每个日志文档中保存这个主机的ObjectID。\n所以,即使这种简单的讨论也有能察觉出mongobd的建模和关系模型建模的不同之处。你必须要注意一下两个因素:\nWill the entities on the “N” side of the One-to-N ever need to stand alone?\n一对多中的多是否需要一个单独的实体。\nWhat is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?\n这个关系中集合的规模是一对很少,很多,还是非常多。\nBased on these factors, you can pick one of the three basic One-to-N schema designs:","title":"MongoDB 的正确打开方式 Part1"},{"content":"Go \u0026gt;注意点\u0026lt; 总结 1. 布尔类型 布尔类型 不能 接受其他类型的赋值,不支持 自动或者强制的类型转换\nvar b bool b = 1 // 编译错误 b = bool(1) // 编译错误 2. int与int32 **注意:**int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动做类型转换\nvar value2 int32 value1:= 64 // value1将会被自动推导为int类型 value2 = value1 // 编译错误 // 可用强制转换解决这个编译错误: value2 = int32(value1) // 编译通过 3. 整数值比较 两种 不同 类型的整型数 不能 直接比较,比如 int8 类型的数和 int 类型的数不能直接比较,但各种类型的整型变量都可以直接与字面常量 ( literal ) 进行比较\n 4. ^x 表示对 x 取反 5. 字符串 字符串的内容可以用类似 数组下标 的方式获取,但不能在初始化后被修改\neg. str := “Hello world” str[0] = ‘X’ //编译错误 6. 一种特殊的switch for k,v := range m{ switch vv := v.(type) { case string: fmt.Println(k, \u0026#34;is string\u0026#34;, vv) case int: fmt.Println(k, \u0026#34;is int\u0026#34;, vv) case float64: fmt.Println(k,\u0026#34;is float64\u0026#34;,vv) case []interface{}: fmt.Println(k, \u0026#34;is an array:\u0026#34;) for i, u := range vv { fmt.Println(i, u) } default: fmt.Println(k, \u0026#34;is of a type I don\u0026#39;t know how to handle\u0026#34;) } } 7. make() 函数创建数组切片 // 创建一个初始元素个数为5的数组切片,元素初始值为0: mySlice1 := make([]int, 5) // 创建一个初始元素个数为5的数组切片, // 元素初始值为0,并预留10个元素的存储空间: mySlice2 := make([]int, 5, 10) // 直接创建并初始化包含5个元素的数组切片: mySlice3 := []int{1, 2, 3, 4, 5} // 直接创建并初始化包含5个元素的数组切片 : mySlice3 := []int{3:4, 5} // [0 0 0 4 5] 当然,事实上还会有一个匿名数组被创建出来,只是不需要我们来操心而已。\n 8. 数组切片追加 将一个数组切片 追加 到另一个数组切片的末尾:\nmySlice2 := []int{8,9,10} mySlice = append(mySlice,mySlice2...) **注意:**在第二个参数后面加三个点,不加会有编译错误,加省略号表示把mySlice2包含的元素打散后传入\n 9. 判断能否从 map 中获取一个值的做法 value, ok := map[key] if ok{ //找到了 //处理找到的value } 10. 创建并初始化类型的对象实例 rect1 := new(Rect) rect2 := \u0026amp;Rect{} rect3 := \u0026amp;Rect{0,0,100,200} rect4 := \u0026amp;Rect{width:100,height:200} 11.未显示初始化 Go语言中,未 显式初始化 的变量都会被初始化为该类型的 零值 ,bool 类型零值为 false , Int 类型的零值为 0,string 类型的零值为 空字符串, 接口或引用类型(包括 slice、map、chan 和函数)变量对应的零值是nil\n 12. 无构造函数 Go语言中 没有构造函数 的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX来命名,表示“构造函数”\n 13. go中最好用的超时机制——\u0026gt; select-case select默认是阻塞的,只有当监听的 channel 中有发送或接收可以进行时才会运行,当多个 channel 都准备好的时候,select 是随机的选择一个执行的。\ntimeout := make(chan bool, 1) go func() { time.Sleep(1e9) // 等待1秒钟 timeout \u0026lt;- true }() // 然后我们把timeout这个channel利用起来 select { case \u0026lt;-ch: // 从ch中读取到数据 case \u0026lt;-timeout: // 一直没有从ch中读取到数据,但从timeout中读取到了数据 } 另一种实现方法\nfunc main() { c := make(chan int) o := make(chan bool) go func() { for { select { case v := \u0026lt;- c: println(v) case \u0026lt;- time.After(5 * time.Second): println(\u0026#34;timeout\u0026#34;) o \u0026lt;- true break } } }() \u0026lt;- o } 在 select 里面还有 default 语法,select 其实就是类似switch 的功能,default 就是当监听的 channel 都没有准备好的时候,默认执行的( select 不再阻塞等待 channel )。\n 14. 单向channel变量的声明 var ch1 chan int //ch1是一个正常的channel,不是单向的 var ch2 chan\u0026lt;- float64 //ch2是单向channel,只用于写float64数据 var ch3 \u0026lt;-chan int //ch3是单向channel,只用于读取int了数据 15. 关闭和判断channel // 关闭 ch channel close(ch) // 判断一个channel是否关闭 x,ok := \u0026lt;-ch ####16. runtime goroutine\nRuntime 包中有几个处理 goroutine 的函数\n Goexit\n退出当前执行的 goroutine , 但是 defer 函数还会继续调用\n Gosched\n让出当前 goroutine 的执行权限, 调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复运行\n NumCPU\n返回 CPU 核数量\n NumGroutine\n返回正在执行和排队的任务总数\n GOMAXPROCS\n用来设置可以并行计算的CPU核数的最大值,并返回之前的值\n 17. 一些链接 Golang学习思维导图 http://yougg.github.io/static/gonote/GolangStudy.html\n 18. 函数中局部变量 在Go语言中,返回 函数中局部变量 的地址也是 安全 的。例如下面的代码,调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。\nvar p = f() func f() *int { v := 1 return \u0026amp;v } 19. flag 包小例子 \t早些的echo版本中,就包含了两个可选的命令行参数:-n用于忽略行尾的换行符,-s sep用于指定分隔字符(默认是空格)。\ngopl.io/ch2/echo4 // Echo4 prints its command-line arguments. package main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; ) var n = flag.Bool(\u0026#34;n\u0026#34;, false, \u0026#34;omit trailing newline\u0026#34;) var sep = flag.String(\u0026#34;s\u0026#34;, \u0026#34; \u0026#34;, \u0026#34;separator\u0026#34;) func main() { flag.Parse() fmt.Print(strings.Join(flag.Args(), *sep)) if !*n { fmt.Println() } } \t调用 flag.Bool 函数会创建一个新的对应布尔型标志参数的变量。它有三个属性:第一个是的命令行标志参数的名字“n”,然后是该标志参数的默认值(这里是false),最后是该标志参数对应的描述信息。如果用户在命令行输入了一个无效的标志参数,或者输入-h或-help参数,那么将打印所有标志参数的名字、默认值和描述信息。类似的,调用flag.String函数将于创建一个对应字符串类型的标志参数变量,同样包含命令行标志参数对应的参数名、默认值、和描述信息。程序中的sep和n变量分别是指向对应命令行标志参数变量的指针,因此必须用*sep和*n形式的指针语法间接引用它们。\n\t当程序运行时,必须在使用标志参数对应的变量之前调用先flag.Parse函数,用于更新每个标志参数对应变量的值(之前是默认值)。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问,返回值对应对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误,默认将打印相关的提示信息,然后调用os.Exit(2)终止程序。\n\t运行一些echo测试用例:\n$ go build gopl.io/ch2/echo4 $ ./echo4 a bc def a bc def $ ./echo4 -s / a bc def a/bc/def $ ./echo4 -n a bc def a bc def$ $ ./echo4 -help Usage of ./echo4: -n omit trailing newline -s string separator (default \u0026#34; \u0026#34;) 20. Rune import \u0026#34;unicode/utf8\u0026#34; s := \u0026#34;Hello, 世界\u0026#34; fmt.Println(len(s)) // \u0026#34;13\u0026#34; fmt.Println(utf8.RuneCountInString(s)) // \u0026#34;9\u0026#34; 21.内置函数 - 名称 - 说明 close 用于管道通信 len/cap new/make new 和 make 均是用于 分配内存 : new(T) 分配类型 T 的零值并返回其 地址,也就是指向类型 T 的指针。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的 值 copy/append 用于复制和连接切片 panic/recover 两者均用于错误处理机制 print/println 底层打印函数,在部署环境中建议使用 fmt 包 complex/realimag 用于创建和操作复数 22. for-range 结构 一般形式为:for ix, val := range coll { }\n要注意的是,val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(译者注:如果 val 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值)。一个字符串是 Unicode 编码的字符(或称之为 rune)集合,因此您也可以用它迭代字符串:\nfor pos, char := range str { ... } 每个 rune 字符和索引在 for-range 循环中是一一对应的。它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。\n 23. 从字符串生成字节切片 \t假设 s 是一个字符串(本质上是一个字节数组),那么就可以直接通过 c := []byte(s) 来获取一个字节的切片 c。另外,还可以通过 copy 函数来达到相同的目的:copy(dst []byte, src string)。\n\t可以通过代码 len([]int32(s)) 来获得字符串中字符的数量,但使用 utf8.RuneCountInString(s) 效率会更高一点。\n\t还可以将一个字符串追加到某一个字符数组的尾部:\nvar b []byte var s string b = append(b, s...) 24. 常量 常量 中的 数据类型 只可以是 布尔型、数字型(整数型、浮点型和复数)和字符串型。\n常量的定义格式:const identifier [type] = value,例如:\nconst Pi = 3.14159 在 Go 语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。\n 显式类型定义: const b string = \u0026quot;abc\u0026quot; 隐式类型定义: const b = \u0026quot;abc\u0026quot; 注:常量的值必须是能够在编译时就能够确定的\n 25. 方法 \t类型 和 作用在它上面定义的方法 必须在 同一个包里 定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法。试图在 int 类型上定义方法会得到一个编译错误\n 26. 反射 \t变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。\n\t两个简单的函数,reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的 类型 和 值 。例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回 \u0026lt;float64 Value\u0026gt;\n Value 有一个 Type 方法返回 reflect.Value 的 Type。 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等 。\nconst ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer ) \t变量 v 的 Interface() 方法可以得到还原(接口)值,所以可以这样打印 v 的值:fmt.Println(v.Interface())。\n\t通过 Type() 我们看到 v 现在的类型是 *float64 并且仍然是不可设置的。\n要想让其可设置我们需要使用 Elem() 函数,这间接的使用指针:v = v.Elem()\n 27. 一种限制速率的方法 time.Tick() 函数声明为 Tick(d Duration) \u0026lt;-chan Time,当你想返回一个通道而不必关闭它的时候这个函数非常有用:它以 d 为周期给返回的通道发送时间,d是纳秒数。如果需要像下边的代码一样,限制处理频率\nimport \u0026#34;time\u0026#34; rate_per_sec := 10 var dur Duration = 1e9 / rate_per_sec chRate := time.Tick(dur) // a tick every 1/10th of a second for req := range requests { \u0026lt;- chRate // rate limit our Service.Method RPC calls go client.Call(\u0026#34;Service.Method\u0026#34;, req, ...) } ","permalink":"https://jsharkc.github.io/post/gonote/","summary":"Go \u0026gt;注意点\u0026lt; 总结 1. 布尔类型 布尔类型 不能 接受其他类型的赋值,不支持 自动或者强制的类型转换\nvar b bool b = 1 // 编译错误 b = bool(1) // 编译错误 2. int与int32 **注意:**int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动做类型转换\nvar value2 int32 value1:= 64 // value1将会被自动推导为int类型 value2 = value1 // 编译错误 // 可用强制转换解决这个编译错误: value2 = int32(value1) // 编译通过 3. 整数值比较 两种 不同 类型的整型数 不能 直接比较,比如 int8 类型的数和 int 类型的数不能直接比较,但各种类型的整型变量都可以直接与字面常量 ( literal ) 进行比较\n 4. ^x 表示对 x 取反 5.","title":"Go注意点总结"}]