每日新闻

每日新闻

GoCN每日新闻资讯
有问必答

有问必答

Go相关的问题,技术相关的问题
文章分享

文章分享

技术文章分享,让知识传播给更多的人
招聘应聘

招聘应聘

为Gopher服务的招聘应聘平台

【go 源码】sync.Once 详解

xmgee 发表了文章 • 0 个评论 • 1202 次浏览 • 2019-10-31 22:50 • 来自相关话题

# sync.Once 源码阅读## 1.Demo```package mainimport ( "fmt" "sync" "time")func m ...查看全部

# sync.Once 源码阅读

## 1.Demo

```
package main

import (
"fmt"
"sync"
"time"
)

func main() {
var once sync.Once

for i:=0;i<=10;i++{
go once.Do(func() {
fmt.Println("hello world")
})
}

time.Sleep(time.Second * 2)
}
```

## 2.介绍

sync.Once是sync包中的一个对象,它只有一个方法Do,这个方法很特殊,在程序运行过程中,无论被多少次调用,只会执行一次,就与结构体的名称一样,once(一次)。那它是如何做的呢?

## 3.使用场景

当程序运行过程中,在会被多次调用的地方却只想执行一次某代码块。就可以全局声明一个once,然后用once.Do()来之行此代码块。

## 4.源码

```
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sync

import (
"sync/atomic"
)

// Once is an object that will perform exactly one action.
type Once struct {
m Mutex
done uint32
}

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
// var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}

```

## 5.源码解析

可以看到once结构体中,有两个字段,m是了保证并发安全性的,done是标志是否已经执行过此方法,如果done是1则表示执行过,0表示未执行。

Do方法中,首先通过atomic.LoadUint32(&o.done),来取得done的值,看是否为1,如果为1就表示已经执行过了,直接返回,未执行则继续执行。

代码很简单,就不啰嗦了,值得注意的是 `defer atomic.StoreUint32(&o.done, 1)`很精髓,为了防止f()方法中panic,无法为done赋值,作者特地使用defer。值得学习。

----

项目地址:github.com/xmge,更多go源码阅读文章将在公众号发布:

![img](https://gosc.oss-cn-beijing.aliyuncs.com/gosc.jpg)

【Go】高效截取字符串的一些思考

qiyin 发表了文章 • 0 个评论 • 1188 次浏览 • 2019-10-31 08:33 • 来自相关话题

原文链接: ...查看全部

原文链接:https://blog.thinkeridea.com/201910/go/efficient_string_truncation.html

最近我在 Go Forum 中发现了 String size of 20 character 的问题,“hollowaykeanho” 给出了相关的答案,而我从中发现了截取字符串的方案并非最理想的方法,因此做了一系列实验并获得高效截取字符串的方法,这篇文章将逐步讲解我实践的过程。

字节切片截取

这正是 “hollowaykeanho” 给出的第一个方案,我想也是很多人想到的第一个方案,利用 go 的内置切片语法截取字符串:

s := "abcdef"
fmt.Println(s[1:4])

我们很快就了解到这是按字节截取,在处理 ASCII 单字节字符串截取,没有什么比这更完美的方案了,中文往往占多个字节,在 utf8 编码中是3个字节,如下程序我们将获得乱码数据:

s := "Go 语言"
fmt.Println(s[1:4])

杀手锏 - 类型转换 []rune

hollowaykeanho” 给出的第二个方案就是将字符串转换为 []rune,然后按切片语法截取,再把结果转成字符串。

s := "Go 语言"
rs := []rune(s)
fmt.Println(strings(rs[1:4]))

首先我们得到了正确的结果,这是最大的进步。不过我对类型转换一直比较谨慎,我担心它的性能问题,因此我尝试在搜索引擎和各大论坛查找答案,但是我得到最多的还是这个方案,似乎这已经是唯一的解。

我尝试写个性能测试评测它的性能:

package benchmark

import (
"testing"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRunes(s string, length int) string {
if utf8.RuneCountInString(s) > length {
rs := []rune(s)
return string(rs[:length])
}

return s
}

func BenchmarkSubStrRunes(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRunes(benchmarkSubString, benchmarkSubStringLength)
}
}

我得到了让我有些吃惊的结果:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRunes-8 872253 1363 ns/op 336 B/op 2 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 2.120s

对 69 个的字符串截取前 20 个字符需要大概 1.3 微秒,这极大的超出了我的心里预期,我发现因为类型转换带来了内存分配,这产生了一个新的字符串,并且类型转换需要大量的计算。

救命稻草 - utf8.DecodeRuneInString

我想改善类型转换带来的额外运算和内存分配,我仔细的梳理了一遍 strings 包,发现并没有相关的工具,这时我想到了 utf8 包,它提供了多字节计算相关的工具,实话说我对它并不熟悉,或者说没有主动(直接)使用过它,我查看了它所有的文档发现 utf8.DecodeRuneInString 函数可以转换单个字符,并给出字符占用字节的数量,我尝试了如此下的实验:

package benchmark

import (
"testing"
"unicode/utf8"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrDecodeRuneInString(s string, length int) string {
var size, n int
for i := 0; i < length && n < len(s); i++ {
_, size = utf8.DecodeRuneInString(s[n:])
n += size
}

return s[:n]
}

func BenchmarkSubStrDecodeRuneInString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrDecodeRuneInString(benchmarkSubString, benchmarkSubStringLength)
}
}

运行它之后我得到了令我惊喜的结果:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrDecodeRuneInString-8 10774401 105 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.250s

较 []rune 类型转换效率提升了 13倍,消除了内存分配,它的确令人激动和兴奋,我迫不及待的回复了 “hollowaykeanho” 告诉他我发现了一个更好的方法,并提供了相关的性能测试。

我有些小激动,兴奋的浏览着论坛里各种有趣的问题,在查看一个问题的帮助时 (忘记是哪个问题了-_-||) ,我惊奇的发现了另一个思路。

良药不一定苦 - range 字符串迭代

许多人似乎遗忘了 range 是按字符迭代的,并非字节。使用 range 迭代字符串时返回字符起始索引和对应的字符,我立刻尝试利用这个特性编写了如下用例:

package benchmark

import (
"testing"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRange(s string, length int) string {
var n, i int
for i = range s {
if n == length {
break
}

n++
}

return s[:i]
}

func BenchmarkSubStrRange(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRange(benchmarkSubString, benchmarkSubStringLength)
}
}

我尝试运行它,这似乎有着无穷的魔力,结果并没有令我失望。

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRange-8 12354991 91.3 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.233s

它仅仅提升了13%,但它足够的简单和易于理解,这似乎就是我苦苦寻找的那味良药。

如果你以为这就结束了,不、这对我来只是探索的开始。

终极时刻 - 自己造轮子

喝了 range 那碗甜的腻人的良药,我似乎冷静下来了,我需要造一个轮子,它需要更易用,更高效。

于是乎我仔细观察了两个优化方案,它们似乎都是为了查找截取指定长度字符的索引位置,如果我可以提供一个这样的方法,是否就可以提供用户一个简单的截取实现 s[:strIndex(20)] ,这个想法萌芽之后我就无法再度摆脱,我苦苦思索两天来如何来提供易于使用的接口。

之后我创造了 exutf8.RuneIndexInString 和 exutf8.RuneIndex 方法,分别用来计算字符串和字节切片中指定字符数量结束的索引位置。

我用 exutf8.RuneIndexInString 实现了一个字符串截取测试:

package benchmark

import (
"testing"
"unicode/utf8"

"github.com/thinkeridea/go-extend/exunicode/exutf8"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRuneIndexInString(s string, length int) string {
n, _ := exutf8.RuneIndexInString(s, length)
return s[:n]
}

func BenchmarkSubStrRuneIndexInString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRuneIndexInString(benchmarkSubString, benchmarkSubStringLength)
}
}

尝试运行它,我对结果感到十分欣慰:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneIndexInString-8 13546849 82.4 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.213s

性能较 range 提升了 10%,让我很欣慰可以再次获得新的提升,这证明它是有效的。

它足够的高效,但是却不够易用,我截取字符串需要两行代码,如果我想截取 10~20之间的字符就需要4行代码,这并不是用户易于使用的接口,我参考了其它语言的 sub_string 方法,我想我应该也设计一个这个样的接口给用户。

exutf8.RuneSubString 和 exutf8.RuneSub 是我认真思索后编写的方法:

func RuneSubString(s string, start, length int) string

它有三个参数:

  • s : 输入的字符串
  • start : 开始截取的位置,如果 start 是非负数,返回的字符串将从 string 的 start 位置开始,从 0 开始计算。例如,在字符串 “abcdef” 中,在位置 0 的字符是 “a”,位置 2 的字符串是 “c” 等等。 如果 start 是负数,返回的字符串将从 string 结尾处向前数第 start 个字符开始。 如果 string 的长度小于 start,将返回空字符串。
  • length:截取的长度,如果提供了正数的 length,返回的字符串将从 start 处开始最多包括 length 个字符(取决于 string 的长度)。 如果提供了负数的 length,那么 string 末尾处的 length 个字符将会被省略(若 start 是负数则从字符串尾部算起)。如果 start 不在这段文本中,那么将返回空字符串。 如果提供了值为 0 的 length,返回的子字符串将从 start 位置开始直到字符串结尾。

我为他们提供了别名,根据使用习惯大家更倾向去 strings 包寻找这类问题的解决方法,我创建了exstrings.SubString 和 exbytes.Sub 作为更易检索到的别名方法。

最后我需要再做一个性能测试,确保它的性能:

package benchmark

import (
"testing"

"github.com/thinkeridea/go-extend/exunicode/exutf8"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRuneSubString(s string, length int) string {
return exutf8.RuneSubString(s, 0, length)
}

func BenchmarkSubStrRuneSubString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRuneSubString(benchmarkSubString, benchmarkSubStringLength)
}
}

运行它,不会让我失望:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneSubString-8 13309082 83.9 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.215s

虽然相较 exutf8.RuneIndexInString 有所下降,但它提供了易于交互和使用的接口,我认为这应该是最实用的方案,如果你追求极致仍然可以使用 exutf8.RuneIndexInString,它依然是最快的方案。

总结

当看到有疑问的代码,即使它十分的简单,依然值得深究,并不停的探索它,这并不枯燥和乏味,反而会有极多收获。

从起初 []rune 类型转换到最后自己造轮子,不仅得到了16倍的性能提升,我还学习了utf8包、加深了range 遍历字符串的特性 以及为 go-extend 仓库收录了多个实用高效的解决方案,让更多 go-extend 的用户得到成果。

go-extend 是一个收录实用、高效方法的仓库,读者们如果好的函数和通用高效的解决方案,期待你们不吝啬给我发送 Pull request,你也可以使用这个仓库加快功能实现及提升性能。

转载:

本文作者: 戚银(thinkeridea

本文链接: https://blog.thinkeridea.com/201910/go/efficient_string_truncation.html

版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处!

DevOps 从理论到实践指南

CORNERSTONE 发表了文章 • 0 个评论 • 1138 次浏览 • 2019-10-30 15:40 • 来自相关话题

什么是 DevOps ...查看全部
什么是 DevOps


如今 DevOps 已经成为一个流行词,很多公司都在说自己在做 DevOps,但是每个人、每家公司理解的 DevOps 又不尽相同,从 DevOps 诞生的第一天起,如何定义 DevOps 就是一个争论不休的话题。
这篇文章,CORNERSTONE认为基本诠释了 DevOps 的定义:DevOps 是什么不是什么
如果你没有耐心把这篇文章看完,维基百科还给出了一个太长不读版:
DevOps (a clipped compound of “development” and “operations”) is a software development and delivery process that emphasizes communication and collaboration between product management, software development, and operations professionals.It seeks to automate the process of software integration, testing, deployment, and infrastructure changes by establishing a culture and environment where building, testing, and releasing software can happen rapidly, frequently, and more reliably.
归纳成三点:
  • DevOps 是一种强调沟通与协作的软件交付过程。它包括产品管理,软件开发及运营等各个方面。
  • DevOps 自动化软件集成,测试,部署以及基础设施的变更。
  • 它的目标是建立一种文化和环境,使得软件的构建、测试、交付更快,更频繁,更可靠。

DevOps 的由来
为什么要实践 DevOps
  • 更短的交付周期,生产环境部署频率越来越快,简化生产部署流程,且自动化不停机部署
  • 更高的价值,形成特性提出到运营数据、用户反馈验证的实验性交付闭环,基于实际用户反馈调整计划和需求
  • 更好的质量保障,在代码检查,功能和非功能验证,以及部署各方面建立较完善的质量保障体系,尤其是自动化测试集
  • 更高绩效的团队,包含业务,开发测试,和运维职能在内的一体化团队,以产品交付为共同目标紧密协作,共同承担责任

DevOps 在技术领域的实践
DevOps运作包括文化(全功能,自运维)和技术(自动化,度量反馈)两方面,而技术能力的改进主要关注以下六个领域:
内建质量体系
通过持续代码评审,静态分析,自动化测试,自动部署验证等手段构成一套有效的质量保障体系。
主要实践包括:
  • TDD:测试驱动开发的思想,保证代码质量和不偏离业务需求的技术实现
  • 结对编程和代码审查,依靠团队的自治性让团队成员互相监督和审查代码质量
  • 自动化测试,高自动化,且高频率运行的测试,保证测试用例质量的同时保证了交付软件的质量

持续部署


CORNERSTONE通过自动化的构建,部署过程快速频繁地将软件交付给用户,提高吞吐量;同时保障过程的安全,平滑,可视。
主要实践包括:
  • 在已经做到持续集成的情况下,引入持续部署,每次提交均会出发构建并执行部署
  • 蓝绿部署,用于实现零宕机发布新版本
  • 金丝雀发布,用于使应用发布流程具备快速试错的能力

持续监控


CORNERSTONE持续对运行环境在系统,应用层面进行监控,及时发现风险或问题,保障系统运行的稳定性。
主要实践包括:
  • 监控预警,在项目开始初期就引入监控,让整个团队实时能够收到关于产品各个维度数据的反馈
  • 日志聚合,便于错误追踪和展示
  • 分析,利用搜集到的数据实时分析,利用分析结果指导开发进度

度量与反馈


CORNERSTONE通过对用户行为或业务指标的度量或反馈收集,为产品的决策提供依据。
主要实践包括:
  • 持续集成反馈,对代码构建质量,代码质量审查的反馈
  • 测试反馈,对软件质量,功能性的测试,给到业务的反馈
  • 运营数据反馈,新功能上线后对业务影响的反馈,用于指导业务人员提新的需求

环境管理


CORNERSTONE通过对服务器环境的定义,自动化建立和配置、更新等提高基础设施管理的效率,一致性,并更有效利用资源,可伸缩的架构,保证服务的健壮性。
主要实践包括:
  • 弹性架构,保证服务的吞吐量和具备灵活变更的能力
  • 自动化部署脚本,想胶水一样,用于解决一些工程实践不够完善的流程之间的衔接
  • 基础设施即代码,用代码定义基础设施,便于环境管理,追踪变更,以及保证环境一致性

松耦合架构
对传统应用架构进行领域组件化,服务化,提升可测试性和可部署性。
主要实践包括:
  • 采用弹性基础设施,比如公有云服务或是 PaaS(Platform as a Service) 平台
  • 构建为服务应用
  • 引入契约测试


典型DevOps的持续交付流水线全景图
软件开发全生命周期的持续优化

抽空搭建了个国内版—The Go Playground

yeyuqiu 发表了文章 • 2 个评论 • 1299 次浏览 • 2019-10-26 14:56 • 来自相关话题

1. 基于 go 最新版本 1.13.32. tab 缩进改为 4 个字符宽度访问地址为 https://play.yeyuqiu.com ...查看全部

1. 基于 go 最新版本 1.13.3
2. tab 缩进改为 4 个字符宽度

访问地址为 https://play.yeyuqiu.com

一秒让你读懂 DevOps 的本质及行业现状与趋势

CORNERSTONE 发表了文章 • 0 个评论 • 1120 次浏览 • 2019-10-25 16:57 • 来自相关话题

手工编译,上传服务器文件,执行各种命令,启动和停止服务器,发现一个 BUG,改完再重复之前说的步骤...... 如今看起来啼笑皆非的重复劳动,在没有 DevOps 概念之前,全靠 IT 人员手工完成。不仅如此,DevOps 概念的提出,最初 ...查看全部

手工编译,上传服务器文件,执行各种命令,启动和停止服务器,发现一个 BUG,改完再重复之前说的步骤...... 如今看起来啼笑皆非的重复劳动,在没有 DevOps 概念之前,全靠 IT 人员手工完成。

不仅如此,DevOps 概念的提出,最初因传统模式运维之痛而生。众所周知,DevOps 一词的来自于 Development 和 Operations 的组合。字如其意,DevOps 就是让软件开发人员和运维人员更好的沟通合作,通过自动化流程来使得软件构建、测试、发布更加快捷、频繁和可靠。

image.png

《RightScale2018 年度云计算调查》报告数据显示,DevOps 的整体采用率从 74% 上升到了 78%,同时企业的 DevOps 采用率达到了 84%。30% 的企业正在全公司范围内采用 DevOps,同比增长 9%。

这些数字的提升不仅代表了 DevOps 可以带来实际效果的共识,也印证了 DevOps 这两年开始受到越来越多的企业重视。DevOps 概念的深入人心,离不开云计算、容器 /Docker、微服务、敏捷等相关概念和实施的成熟发展。其背后本质是企业 IT 的精益运营,以面对更快的业务试错与业务创新。


概念虽好,落地却难?

DevOps 的一个巨大好处就是可以高效交付。DevOps 理念指向“高度的自动化”,试图制定一条从开发到运行自动运行的流水线,最大程度地摆脱人工的束缚,达到企业生产力的升级。

比如,持续的集成与开发,实现从开发测试、上线运维的一体化自动流程;智能预警帮助用户自动监控集群运行状态,快速定位到问题具体发生的位置,及时通知用户以快速解决问题等。

DevOps 另外一个好处就是会改善公司组织文化、提高员工的参与感。员工们变得更高效,也更有满足和成就感。

由此可见,如果能采用 DevOps,公司就能够做更多的创新,缩短开发周期,将产品更加快速地推向市场;同时创造差别化的公司业务和价值,提高组织效率,而不是不停地修补旧问题。


技术分享

虽说 DevOps 优势多多,前途大好,企业对 DevOps 的评价也很高,但实际情况却是说的人多,做到的很少。

究其原因,在于 DevOps 并不是简单地将开发部门和运维部门合并,更是企业文化、组织结构的变革,是通过自动化的基础设施、合理的流程规范以及智能的自动运行系统测试来加强开发部门和运维部门之间的协作和沟通。

首先,这涉及到观念问题。DevOps 的最终目的是加强开发部门和运维部门之间的协作和沟通,如何把现在的文化朝 DevOps 思维模式扭转,并且在开发和运维之间找到共识?

对于传统企业,特别是大型分布式组织,在整体意义上的 DevOps 成功往往是不可能实现的。因为 DevOps 要求深层次的文化和组织变革,要改变的太多太多。这意味着大家要扔掉奉行了几十年的显规则和潜规则。你不得不告诉老部下们,大部分他们知道的和每天做的事物都已经过时了。

其次,想要为 DevOps 和应用灵活性而重塑团队,就要有打破 IT 分组壁垒的勇气,并且需要在团队成员筛选上做出艰难的决定。


困难重重,如何实现?

毫无疑问,DevOps 对组织是非常有价值的,但是需要注意的是,整个公司都需要参与到 DevOps 里才能成功。DevOps 需要高级领导层的支持,也需要和最终产品相关的所有人的参与,而不仅仅是开发和运维部门。

第一,在开发和运维之间找到共识,这需要强大的领导力来实现变革。当然,它也需要花费时间和金钱,并且需要在团队成员筛选上做出艰难的决定。

可以从小处开始,不要期望一开始就能让所有人都信服 DevOps。实际中,在特定项目的小型组织内赢得大家的支持,就赢得了会在公司其他地方帮助宣传 DevOps 的大使们,这会带来乘数效应。

第二,围绕业务系统而不是职责来组织工作,这就是 DevOps 打破 IT 分组壁垒的寓意。一个团队应该有开发人员创建代码,从用户界面到业务逻辑和数据结构,也应该有运维人员负责操作自动化和部署。团队待在一起,共同为他们的应用和系统负责。

当然,为了促进 DevOps 战略,调整考核和激励机制是必要的。应该奖励系统创建和运维的整体团队,并且根据团队工作的全部要素来确定奖励。

第三,团队需要选择最合适的工具。虽然 DevOps 是一个概念,但工具是实现 DevOps 的重要组成部分。近几年来如日中天的 CORNERSTONE 就是实现 DevOps 最合适的工具之一。

在不了解DevOps生命周期的情况下,对DevOps的理解也会片面化。我们以CORNERSTOENE一站式云端 DevOps平台为例让我们一起探讨DevOps的生命周期。


CORNERSTONE | DevOps全流程解决方案


一、持续开发:


这是DevOps生命周期中软件不断开发的阶段。与瀑布模型不同的是,软件可交付成果被分解为短开发周期的多个任务节点,在很短的时间内开发并交付。


image.png


CORNERSTONE任务模块里,任务分配给谁,就会是谁的责任。而且任务支持多责任人与子任务关联,所有的任务和状态都会体现在任务模块里,这个任务过程中的参与人员随时知道任务的状态和目前碰到的问题,可有效推进工作的解决。有了CORNERSTONE能够帮助我们追溯和监控,促进对于接任务的人员有效负起责任,并能够及时同步到信息。


二、持续测试:


在这个阶段,开发的软件将被持续地测试bug。CORNERSTONE平台覆盖完整的测试流程,可进行测试用例的编写,建立用例库,减少重复性操作,让研发团队的协作更高效,产品交付更快速。常用的两个功能为:


1)测试用例管理


通过编写测试⽤例,制定测试计划并执⾏,测试结果可直接关联到缺陷,方便对问题进行跟踪处理,实现对迭代质量的全程把控。


 Clipboard Image.png


2)缺陷管理


强大的缺陷管理与统计功能,通过分组、解决状态、优先级等列表对缺陷进行全方位记录与跟踪,同时明确缺陷责任人,及时跟进解决缺陷;同时支持导入导出功能,导出时支持任意格式,不受模板限制。


Clipboard Image.png


三、持续集成:


这是支持新功能的代码与现有代码集成的阶段。由于软件在不断地开发,更新后的代码需要不断地集成,并顺利地与系统集成,以反映对最终用户的需求更改。更改后的代码,还应该确保运行时环境中没有错误,允许我们测试更改并检查它如何与其他更改发生反应。


image.png


CORNERSTONE⽀持将持续集成的结果部署到对应的测试环境,所有部署版本在测试环境中可随时访 问,⽀持灰度发布到⽣产环境中。


四、持续部署:


它是将代码部署到生产环境的阶段。在这里,我们确保在所有服务器上正确部署代码。如果添加了任何功能或引入了新功能,那么应该准备好迎接更多的网站流量。因此,系统运维人员还有责任扩展服务器以容纳更多用户。


Clipboard Image.png


CORNERSTONE支持依赖脚本pipeline实现的DevOps,支持持续集成与自动化部署,可直接在可视化的服务器上进行操作,同时满足多种开发语言,彻底解决敏捷开发在运维层面的瓶颈,方便开发人员对项目开发生命周期进行全盘管理。


五、持续监控:


这是DevOps生命周期中非常关键的阶段,旨在通过监控软件的性能来提高软件的质量。这种做法涉及运营团队的参与,他们将监视用户活动中的错误/系统的任何不正当行为。这也可以通过使用专用监控工具来实现,该工具将持续监控应用程序性能并突出问题。


Clipboard Image.png


  CORNERSTONE嵌⼊一体化监控运维平台,实现IT环境的数字化、标准化,直接运维分析的基础,减少    人工干预,降低⼈工成本。

最后,好工具得有能人掌控才能发挥其威力。即使找到了好用的工具,也需要有熟悉这个工具链,拥有相应技能的 IT 人员来提供技术支持,才能完成实现自动化的使命。



写在最后

总体而言,DevOps 作为一种理念,推动开发和运维之间的合作,有效回应了当前的商业需求。它的实现是新观念、新工具、新技能的三者叠加。虽然实现起来还有种种问题,但 DevOps 是大势所趋,作为企业的高层和开发运维人员,对这样的变革不能视而不见。

CORNERSTONE | DevOps平台是如何实现开发效率的双倍提升?

CORNERSTONE 发表了文章 • 0 个评论 • 1048 次浏览 • 2019-10-23 17:03 • 来自相关话题

随着企业业务对软件系统日益依赖,IT管理与研发模式也随之对“敏捷”模式产生了需求,也就是今天人们时常提起的DevOps。提升效率,是DevOps实践的核心内容之一。就让我们来一起从软件生命周期的业务流与工作流,探讨DevOps实践效率提升的方向与方法吧。 ...查看全部

随着企业业务对软件系统日益依赖,IT管理与研发模式也随之对“敏捷”模式产生了需求,也就是今天人们时常提起的DevOps。提升效率,是DevOps实践的核心内容之一。就让我们来一起从软件生命周期的业务流与工作流,探讨DevOps实践效率提升的方向与方法吧。


一、CORNERSTONE | DevOps之“流”分析

软件工程将软件的生命周期定义为问题定义、需求分析、软件设计、程序编码、软件测试、运行维护等过程,无论是对于传统模式、敏捷模式还是DevOps模式,软件生命周期过程基本一致,如下图所示。
image.png


软件生命周期各个过程也组成了软件工程的“业务流”,而在不同团队采用相应地开发模式中,具体执行的开发及相关的活动,我们则成为工作流”。


CORNERSTONE,DevOps实践中,最主要改进的内容,就是对于这些 “工作流”的活动进行“关停并转”,从而实现整体与局部上对于效率的提升。


这些工作,也就是需要开展的活动,可以分为以下几类:


人与人的互动

这类活动交互的双方均为自然人,如业务需求收集,活动的特点是具备高度的不规则与不规律性。


人与机的互动

这类活动交互的一方为自然人,一方为依托于计算机的程序,如编码活动、人工审核/审批等,活动的特点是人的活动必须依循计算机相关主题的规则,部分活动可以抽取为规范化的过程。


机与机的互动

这类活动的特点是交互的双方都是依托于计算机的程序,如编译构建、自动化测试,活动的过程高度规范化。不同的作业类型,在效率提升的优化中,需要采用的方法各有不同。


二、CORNERSTONE | DevOps效率提升之协作

协作的本质是在不同的主体之间进行快速、有效的信息共享,从而进一步协调各主体进行步调一致、有序的工作执行,实现整体上的一致性与顺畅性,协作是DevOps实践中效率提升的重要方向和内容之一。

DevOps实践中的协作更多需要是从软件生命周期整体系统化考虑与设计,协作设计上面主要包括以下两个方面。

01、信息共享

传统的模式中,相关业务信息仅共享于各阶段内部,而在CORNERSTONE中,则更强调信息的跨阶段共享,面向产品的全生命周期,共享信息包括:

业务类信息
即业务目标、业务背景、业务需求、业务限制等信息。

执行类信息
即软件开发、编译、测试、部署等执行的相关信息,如开始时间、结束时间、执行时长、执行操作记录等。

反馈类信息
即各步骤、阶段执行的信息反馈,如需求拆分反馈、任务执行反馈、代码编译结果、测试结果、发布验证结果等。

CORNERSTONE为以上信息提供统一的信息管理与分析平台。对于代码编写之前的阶段提供如敏捷协同的工作协同管理模块,以记录需求、任务分配、需求完成进展等信息,对于代码编写之后的阶段,则提供相对完整的执行记录信息以及必要的通知信息,以构建及时的反馈。

02、协作调度

协作调度是DevOps协作实践中另外一项关键内容。通过CORNERSTONE平台,可实现对于“机与机的活动”全自动协作调度,对于“人与机的活动”简化协作调度,对于“人与人的活动”事件驱动协作调度,进而实现优化协作调度的效率,提升协作效果。

全自动协作调度
全自动的协作调度主要是通过CORNERSTONE平台的流水线引擎实现,通过流水线编排的实现指定作业流自动执行,执行过程中自动完成不同阶段的信息交互,过程无需人工参与。

简化的协作调度
简化的协作调度也是通过CORNERSTONE平台的流水线引擎实现,在流水线作业流中编排需要人工干预的节点,但仅需要人工给出通过/终止等简单的指令型信息即可。

基于事件的协作调度
基于事件驱动的协作调度,主要是用于“人与人的活动”,也可以用于“人与机的活动”,其通过通知、待办等事件方式,实现精准的信息共享与推送,驱动协作的下游方快速接受和推进事务工作。

CORNERSTONE中的协作调度的效果可以通过研发效能来进行初步的评估与衡量,通过衡量,我们可以较为清晰的获知哪个阶段的协调调度是关键阻碍点或可以进一步优化。

三、CORNERSTONE | DevOps效率提升之自动化

自动化是DevOps的核心理念,也是效率提升的最重要手段。通过CORNERSTONE一站式云端DevOps平台,实现软件过程自动化以及软件过程的支撑工作自动化。

CORNERSTONE | DevOps全流程解决方案

01、软件过程自动化

软件过程自动化是指在软件的开发、测试、部署等过程中,引入自动化的手段,从而实现快速的软件质量检查,以及软件应用发布。


开发过程自动化


CORNERSTONE的代码助手可帮助编程人员以最快的速度完成编程工作,比如当需要对外部的某个窗口进行操作时,CORNERSTONE的代码助手可进行探测,获取相关的窗口信息,再对其它进行操作等。

image.png


测试过程自动化

CORNERSTONE平台覆盖完整的测试流程,可进行测试用例的编写,建立用例库,减少重复性操作,让研发团队的协作更高效,产品交付更快速。常用的两个功能为:


1)测试用例管理


通过编写测试⽤例,制定测试计划并执⾏,测试结果可直接关联到缺陷,方便对问题进行跟踪处理,实现对迭代质量的全程把控。


 Clipboard Image.png


2)缺陷管理


强大的缺陷管理与统计功能,通过分组、解决状态、优先级等列表对缺陷进行全方位记录与跟踪,同时明确缺陷责任人,及时跟进解决缺陷;同时支持导入导出功能,导出时支持任意格式,不受模板限制。


Clipboard Image.png


部署过程自动化


CORNERSTONE支持依赖脚本pipeline实现的DevOps,支持持续集成与自动化部署,可直接在可视化的服务器上进行操作,同时满足多种开发语言,彻底解决敏捷开发在运维层面的瓶颈,方便开发人员对项目开发生命周期进行全盘管理。


Clipboard Image.png


通过流水线引擎,实现以上内容的自由、可视化编排,以及按需执行。


02、过程支撑自动化

软件过程支撑主要是指面向软件工程过程的支撑,实现自动化包括:


编译构建环境自动化

编译构建环境包括基于DevOps平台的自管理编译构建环境,按需生成编译构建环境,编译构建完成后自动销毁,以及特定编译构建环境的快速接入等。


测试环境自动化

测试环境自动化是指自动化测试执行所需的能力环境,如接口/UI测试脚本所需的执行环境,可以根据测试任务的需要,实现测试环境的弹性伸缩自管理。


环境部署自动化

环境部署自动化是指对于开发、测试、生产等所需要的基础环境,可以根据流水线自动完成环境的使用前的生成、使用后的回收等,实现资源即代码,无需人工参与。


CORNERSTONE中,通过大量的过程及支撑自动化,可以极大的减少开发、测试、运维等工作的人工参与时间,降低人工成本,并能实现人工无法完成的工作,例如快速对10000台服务器上的应用进行更新。但前期的建设需要涉及的技术点较多,成本也较为巨大,如何建设落地自动化,除了考虑效率之外,还需着重考虑业务平台的自主可控与可持续发展等方面。


四、CORNERSTONE | DevOps效率提升之持续优化

持续优化,是CORNERSTONE效率提升的第三个主要方面,也是践行DevOps理念的重要实践。持续优化需要解决优化什么、如何优化等问题。这些问题的解决,需要应用DevOps精益分析的理念实践。精益分析,本质就是对数据的统计、分析与挖掘。


01、数据获取

精益分析所涉及的数据应从需求提出到用户访问形成一个端到端闭环。数据的获取需要从业务系统本身以及支撑业务系统的CORNERSTONE平台两个方向获取。早期可以以CORNERSTONE平台相关数据的获取为主要来源,后续可持续集成来自业务系统埋点获取的数据。在整个过程中,需要做到数据的及时性、准确性与完整性。

02、数据分析

数据分析需要有明确的目标和针对性,如针对业务需求提出到上线的平均周期、开发返工趋势等,通过数据分析,可以快速找到当前影响效率的关键点,从而实现针对性的改善。

image.png

03、数据呈现

数据呈现即为数据应用,数据呈现可以采用两种方式进行。

协同管理
将数据获取/分析的结果,在CORNERSTONE的协同管理平台实时的反馈和呈现,从而推动PO/开发团队/干系人等根据反馈信息快速推进效率优化,通过量变引发质变,通过团队内自我优化的方式实现效率的提升。

度量分析
针对于与效率相关的重点指标,通过可视化图表等方式,进行专项的度量分析,并在管理与项目团队共享指标信息以及指标的变化趋势,通过全局监督的方式推进效率的提升。

五、结论

文化上的协同打破了流程与部门的屏障,共享了信息,协作了调度;过程中的自动化消除了重复性的工作,降低人为风险;业务系统与CORNERSTONE平台的数据支持精准提供优化的方向。DevOps之所以能为企业提升效率在于DevOps的实践实现软件生命周期的业务流与作业流的一致与顺畅。

写了一个100%Go语言的Web-Term-SSH 堡垒机项目欢迎大家拍砖

回复

jjjjerk 发起了问题 • 1 人关注 • 0 个回复 • 346 次浏览 • 2019-10-22 15:53 • 来自相关话题

Go语言自定义自己的SSH-Server

jjjjerk 发表了文章 • 0 个评论 • 1155 次浏览 • 2019-10-22 15:31 • 来自相关话题

原文地址  ...查看全部

Golang-Docker ChromeDP浏览器模拟和截图微服务

jjjjerk 发表了文章 • 0 个评论 • 1075 次浏览 • 2019-10-21 15:58 • 来自相关话题

原文地址  ...查看全部

用 Go 語言實作 Job Queue 機制

appleboy 发表了文章 • 1 个评论 • 1136 次浏览 • 2019-10-20 09:03 • 来自相关话题

很高興可以在  ...查看全部

很高興可以在 Mopcon 分享『用 Go 語言實現 Job Queue 機制』,透過簡單的 goroutine  channel 就可以實現簡單 Queue 機制,並且限制同時可以執行多少個 Job,才不會讓系統超載。最後透過編譯放進 Docker 容器內,就可以跑在各種環境上,加速客戶安裝及部署。

議程大綱

本次大致上整理底下幾個重點:

  1. What is the different unbuffered and buffered channel?
  2. How to implement a job queue in golang?
  3. How to stop the worker in a container?
  4. Shutdown with Sigterm Handling.
  5. Canceling Workers without Context.
  6. Graceful shutdown with worker.
  7. How to auto-scaling build agent?
  8. How to cancel the current Job?

由於在投影片內也許寫得不夠詳細,所以我打算錄製一份影片放在 Udemy 教學影片上,如果有興趣可以參考底下影片連結:

之前的教學影片也可以直接參考底下連結:

投影片

https://www.slideshare.net/appleboy/job-queue-in-golang-184064840

袋鼠存储 v1.3 正式支持移动端

fabsnail 发表了文章 • 0 个评论 • 1055 次浏览 • 2019-10-19 17:39 • 来自相关话题

自袋鼠存储(roostore.com)发布以来,感谢各位的认可,下载使用和积极反馈袋鼠存储致力于为用户提供一种新的存储解决方案,使得用户对 ...查看全部

自袋鼠存储(roostore.com)发布以来,感谢各位的认可,下载使用和积极反馈

袋鼠存储致力于为用户提供一种新的存储解决方案,使得用户对服务可控,对数据可控还有对成本可控

现正式推出 v1.3 版本,该版本包含移动端安卓版,更进一步方便用户使用:

kstore

1. 支持嵌入式提供服务,为移动端提供集成使用
2. 支持跨节点在线查看文件,为移动端提供在线查看图片,播放视频等
3. 提供注册登陆口令控制项,确保公网中的私人节点只允许有口令的用户使用
4. 修复浏览文件夹时滚动条可能不显示问题
5. 修复某些浏览器中点击下载文件无响应问题
6. 修复协程上下文资源未及时释放问题
7. 修复自动登陆或浏览文件夹时可能出现的阻塞问题
8. 修复长时间运行过程中周期性查找更多可用连接节点时可能出现的并发崩溃问题


移动端(安卓版首发)
1. 支持在指定的服务器上进行注册,登陆
2. 支持使用集成服务,满足无自部署 kstore 服务时使用
3. 提供断点上传手机中的文件,支持暂停、恢复,取消和清空(已完成)任务
4. 提供断点下载文件到手机,支持暂停,恢复,取消和清空(已完成)任务
5. 提供将下载的文件分享发送给其他应用
6. 提供文件浏览,创建删除文件(夹)
7. 提供文件剪切,复制,粘贴,重命名以及查看文件详细信息等
8. 支持下拉刷新文件列表
9. 支持查看图片(jpg, jpeg, png, gif, svg等格式),包括查看其他 kstore 节点中的图片(不存储文件到本地)
10. 支持播放视频(mp4, mov),包括查看其他 kstore 节点中的视频(不存储文件到本地)
11. 支持 admin 后台管理(目前仅支持网络管理部分)
12. 支持是否显示目录大小选项设置
13. 提供帮助与反馈渠道
14. 提供将 kstore-app 推荐给好友


感兴趣的朋友,欢迎通过官网 roostore.com 下载使用

注:iPhone 版本正在努力攻关一些关键技术问题,相信很快也会与大家见面

go 学习笔记之解读什么是defer延迟函数

snowdreams1006 发表了文章 • 0 个评论 • 929 次浏览 • 2019-10-18 19:23 • 来自相关话题

Go 语言中有个 defer 关键字,常用于实现延迟函数来保证关键代码的最终执行,常言道: "未雨绸缪方可有备无患".


延迟函数就是这么一种机制,无论程序是正常返回还是异常报错,只要存在延迟函数都能保证这部分关键逻辑最终执行,所以用来做些资源清理等操作再合适不过了.


go-error-about-defer.jpg
go-error-about-defer.jpg

出入成双有始有终


日常开发编程中,有些操作总是成双成对出现的,有开始就有结束,有打开就要关闭,还有一些连续依赖关系等等.


一般来说,我们需要控制结束语句,在合适的位置和时机控制结束语句,手动保证整个程序有始有终,不遗漏清理收尾操作.


最常见的拷贝文件操作大致流程如下:



  1. 打开源文件


srcFile, err := os.Open("fib.txt")
if err != nil {
t.Error(err)
return
}


  1. 创建目标文件


dstFile, err := os.Create("fib.txt.bak")
if err != nil {
t.Error(err)
return
}


  1. 拷贝源文件到目标文件


io.Copy(dstFile, srcFile)


  1. 关闭目标文件


dstFile.Close()
srcFile.Close()


  1. 关闭源文件


srcFile.Close()

值得注意的是: 这种拷贝文件的操作需要特别注意操作顺序而且也不要忘记释放资源,比如先打开再关闭等等!


func TestCopyFileWithoutDefer(t *testing.T) {
srcFile, err := os.Open("fib.txt")
if err != nil {
t.Error(err)
return
}

dstFile, err := os.Create("fib.txt.bak")
if err != nil {
t.Error(err)
return
}

io.Copy(dstFile, srcFile)

dstFile.Close()
srcFile.Close()
}


「雪之梦技术驿站」: 上述代码逻辑还是清晰简单的,可能不会忘记释放资源也能保证操作顺序,但是如果逻辑代码比较复杂的情况,这时候就有一定的实现难度了!



可能是为了简化类似代码的逻辑,Go 语言引入了 defer 关键字,创造了"延迟函数"的概念.



  • defer 的文件拷贝


func TestCopyFileWithoutDefer(t *testing.T) {
if srcFile, err := os.Open("fib.txt"); err != nil {
t.Error(err)
return
} else {
if dstFile,err := os.Create("fib.txt.bak");err != nil{
t.Error(err)
return
}else{
io.Copy(dstFile,srcFile)

dstFile.Close()
srcFile.Close()
}
}
}


  • defer 的文件拷贝


func TestCopyFileWithDefer(t *testing.T) {
if srcFile, err := os.Open("fib.txt"); err != nil {
t.Error(err)
return
} else {
defer srcFile.Close()

if dstFile, err := os.Create("fib.txt.bak"); err != nil {
t.Error(err)
return
} else {
defer dstFile.Close()

io.Copy(dstFile, srcFile)
}
}
}

上述示例代码简单展示了 defer 关键字的基本使用方式,显著的好处在于 Open/Close 是一对操作,不会因为写到最后而忘记 Close 操作,而且连续依赖时也能正常保证延迟时机.


简而言之,如果函数内部存在连续依赖关系,也就是说创建顺序是 A->B->C 而销毁顺序是 C->B->A.这时候使用 defer 关键字最合适不过.


懒人福音延迟函数



官方文档相关表述见 Defer statements[1]



如果没有 defer 延迟函数前,普通函数正常运行:


func TestFuncWithoutDefer(t *testing.T) {
// 「雪之梦技术驿站」: 正常顺序
t.Log("「雪之梦技术驿站」: 正常顺序")

// 1 2
t.Log(1)
t.Log(2)
}

当添加 defer 关键字实现延迟后,原来的 1 被推迟到 2 后面而不是之前的 1 2 顺序.


func TestFuncWithDefer(t *testing.T) {
// 「雪之梦技术驿站」: 正常顺序执行完毕后才执行 defer 代码
t.Log(" 「雪之梦技术驿站」: 正常顺序执行完毕后才执行 defer 代码")

// 2 1
defer t.Log(1)
t.Log(2)
}

如果存在多个 defer 关键字,执行顺序可想而知,越往后的越先执行,这样才能保证按照依赖顺序依次释放资源.


func TestFuncWithMultipleDefer(t *testing.T) {
// 「雪之梦技术驿站」: 猜测 defer 底层实现数据结构可能是栈,先进后出.
t.Log(" 「雪之梦技术驿站」: 猜测 defer 底层实现数据结构可能是栈,先进后出.")

// 3 2 1
defer t.Log(1)
defer t.Log(2)
t.Log(3)
}

相信你已经明白了多个 defer 语句的执行顺序,那就测试一下吧!


func TestFuncWithMultipleDeferOrder(t *testing.T) {
// 「雪之梦技术驿站」: defer 底层实现数据结构类似于栈结构,依次倒叙执行多个 defer 语句
t.Log(" 「雪之梦技术驿站」: defer 底层实现数据结构类似于栈结构,依次倒叙执行多个 defer 语句")

// 2 3 1
defer t.Log(1)
t.Log(2)
defer t.Log(3)
}

初步认识了 defer 延迟函数的使用情况后,我们再结合文档详细解读一下相关定义.



  • 英文原版文档



A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns,either because the surrounding function executed a return statement,reached the end of its function body,or because the corresponding goroutine is panicking.




  • 中文翻译文档



"defer"语句调用一个函数,该函数的执行被推迟到周围函数返回的那一刻,这是因为周围函数执行了一个return语句,到达了函数体的末尾,或者是因为相应的协程正在惊慌.



具体来说,延迟函数的执行时机大概分为三种情况:


周围函数执行 return



because the surrounding function executed a return statement



return 后面的 t.Log(4) 语句自然是不会运行的,程序最终输出结果为 3 2 1 说明了 defer 语句会在周围函数执行 return 前依次逆序执行.


func funcWithMultipleDeferAndReturn() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
return
fmt.Println(4)
}

func TestFuncWithMultipleDeferAndReturn(t *testing.T) {
// 「雪之梦技术驿站」: defer 延迟函数会在包围函数正常return之前逆序执行.
t.Log(" 「雪之梦技术驿站」: defer 延迟函数会在包围函数正常return之前逆序执行.")

// 3 2 1
funcWithMultipleDeferAndReturn()
}

周围函数到达函数体



reached the end of its function body



周围函数的函数体运行到结尾前逆序执行多个 defer 语句,即先输出 3 后依次输出 2 1.
最终函数的输出结果是 3 2 1 ,也就说是没有 return 声明也能保证结束前执行完 defer 延迟函数.


func funcWithMultipleDeferAndEnd() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}

func TestFuncWithMultipleDeferAndEnd(t *testing.T) {
// 「雪之梦技术驿站」: defer 延迟函数会在包围函数到达函数体结尾之前逆序执行.
t.Log(" 「雪之梦技术驿站」: defer 延迟函数会在包围函数到达函数体结尾之前逆序执行.")

// 3 2 1
funcWithMultipleDeferAndEnd()
}

当前协程正惊慌失措



because the corresponding goroutine is panicking



周围函数万一发生 panic 时也会先运行前面已经定义好的 defer 语句,而 panic 后续代码因为没有特殊处理,所以程序崩溃了也就无法运行.


函数的最终输出结果是 3 2 1 panic ,如此看来 defer 延迟函数还是非常尽忠职守的,虽然心里很慌但还是能保证老弱病残先行撤退!


func funcWithMultipleDeferAndPanic() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
panic("panic")
fmt.Println(4)
}

func TestFuncWithMultipleDeferAndPanic(t *testing.T) {
// 「雪之梦技术驿站」: defer 延迟函数会在包围函数panic惊慌失措之前逆序执行.
t.Log(" 「雪之梦技术驿站」: defer 延迟函数会在包围函数panic惊慌失措之前逆序执行.")

// 3 2 1
funcWithMultipleDeferAndPanic()
}

通过解读 defer 延迟函数的定义以及相关示例,相信已经讲清楚什么是 defer 延迟函数了吧?


简单地说,延迟函数就是一种未雨绸缪的规划机制,帮助开发者编程程序时及时做好收尾善后工作,提前做好预案以准备随时应对各种情况.



  • 当周围函数正常执行到到达函数体结尾时,如果发现存在延迟函数自然会逆序执行延迟函数.

  • 当周围函数正常执行遇到 return 语句准备返回给调用者时,存在延迟函数时也会执行,同样满足善后清理的需求.

  • 当周围函数异常运行不小心 panic 惊慌失措时,程序存在延迟函数也不会忘记执行,提前做好预案发挥了作用.


所以不论是正常运行还是异常运行,提前做好预案总是没错的,基本上可以保证万无一失,所以不妨考虑考虑 defer 延迟函数?


go-error-about-lovely.png
go-error-about-lovely.png

延迟函数应用场景


基本上成双成对的操作都可以使用延迟函数,尤其是申请的资源前后存在依赖关系时更应该使用 defer 关键字来简化处理逻辑.


下面举两个常见例子来说明延迟函数的应用场景.



  • Open/Close


文件操作一般会涉及到打开和开闭操作,尤其是文件之间拷贝操作更是有着严格的顺序,只需要按照申请资源的顺序紧跟着defer 就可以满足资源释放操作.


func readFileWithDefer(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}


  • Lock/Unlock


锁的申请和释放是保证同步的一种重要机制,需要申请多个锁资源时可能存在依赖关系,不妨尝试一下延迟函数!


var mu sync.Mutex
var m = make(map[string]int)
func lookupWithDefer(key string) int {
mu.Lock()
defer mu.Unlock()
return m[key]
}

总结以及下节预告


defer 延迟函数是保障关键逻辑正常运行的一种机制,如果存在多个延迟函数的话,一般会按照逆序的顺序运行,类似于栈结构.


延迟函数的运行时机一般有三种情况:



  • 周围函数遇到返回时


func funcWithMultipleDeferAndReturn() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
return
fmt.Println(4)
}


  • 周围函数函数体结尾处


func funcWithMultipleDeferAndEnd() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}


  • 当前协程惊慌失措中


func funcWithMultipleDeferAndPanic() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
panic("panic")
fmt.Println(4)
}

本文主要介绍了什么是 defer 延迟函数,通过解读官方文档并配套相关代码认识了延迟函数,但是延迟函数中存在一些可能令人比较迷惑的地方.


go-error-about-question.png
go-error-about-question.png

读者不妨看一下下面的代码,将心里的猜想和实际运行结果比较一下,我们下次再接着分享,感谢你的阅读.


func deferFuncWithAnonymousReturnValue() int {
var retVal int
defer func() {
retVal++
}()
return 0
}

func deferFuncWithNamedReturnValue() (retVal int) {
defer func() {
retVal++
}()
return 0
}

延伸阅读参考文档



  • Defer_statements[2]

  • go 语言的 defer 语句[3]

  • Go defer 实现原理剖析[4]

  • go 语言 defer 你不知道的秘密![5]

  • Go 语言中 defer 的一些坑[6]

  • go defer (go 延迟函数)[7]



如果本文对你有所帮助,不用赞赏,点赞鼓励一下就是最大的认可,顺便也可以关注下微信公众号「 雪之梦技术驿站 」哟!



雪之梦技术驿站.png
雪之梦技术驿站.png

参考资料



[1]

Defer statements: https://golang.google.cn/ref/spec#Defer_statements



[2]

Defer_statements: https://golang.google.cn/ref/spec#Defer_statements



[3]

go语言的defer语句: https://www.jianshu.com/p/5b0b36f398a2



[4]

Go defer实现原理剖析: https://studygolang.com/articles/16067



[5]

go语言 defer 你不知道的秘密!: https://www.cnblogs.com/baizx/p/5024547.html



[6]

Go语言中defer的一些坑: https://www.jianshu.com/p/79c029c0bd58



[7]

go defer (go延迟函数): https://www.cnblogs.com/ysherlock/p/8150726.html



为什么大公司一定要使用 DevOps?

回复

CORNERSTONE 发起了问题 • 1 人关注 • 0 个回复 • 246 次浏览 • 2019-10-18 16:02 • 来自相关话题

Nebula 架构剖析系列(一)图数据库的存储设计

NebulaGraph 发表了文章 • 0 个评论 • 663 次浏览 • 2019-10-15 17:05 • 来自相关话题

摘要在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的部分。每个数据库都有其独有的存储、计算方式,今天就和图图来学习下图数据库 Nebula ...查看全部

摘要

在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的部分。每个数据库都有其独有的存储、计算方式,今天就和图图来学习下图数据库 Nebula Graph 的存储部分。

Nebula 的 Storage 包含两个部分, 一是 meta 相关的存储, 我们称之为 Meta Service ,另一个是 data 相关的存储, 我们称之为 Storage Service。 这两个服务是两个独立的进程,数据也完全隔离,当然部署也是分别部署, 不过两者整体架构相差不大,本文最后会提到这点。 如果没有特殊说明,本文中 Storage Service 代指 data 的存储服务。接下来,大家就随我一起看一下 Storage Service 的整个架构。 Let's go~

Architecture

 图一  storage service 架构图

如图1 所示,Storage Service 共有三层,最底层是 Store Engine,它是一个单机版 local store engine,提供了对本地数据的 get / put / scan / delete 操作,相关的接口放在 KVStore / KVEngine.h 文件里面,用户完全可以根据自己的需求定制开发相关 local store plugin,目前 Nebula 提供了基于 RocksDB 实现的  Store Engine。

在 local store engine 之上,便是我们的 Consensus 层,实现了 Multi Group Raft,每一个 Partition 都对应了一组 Raft Group,这里的 Partition 便是我们的数据分片。目前 Nebula 的分片策略采用了 静态 Hash 的方式,具体按照什么方式进行 Hash,在下一个章节 schema 里会提及。用户在创建 SPACE 时需指定 Partition 数,Partition 数量一旦设置便不可更改,一般来讲,Partition 数目要能满足业务将来的扩容需求。

在 Consensus 层上面也就是 Storage Service 的最上层,便是我们的 Storage interfaces,这一层定义了一系列和图相关的 API。 这些 API 请求会在这一层被翻译成一组针对相应 Partition 的 kv 操作。正是这一层的存在,使得我们的存储服务变成了真正的图存储,否则,Storage Service 只是一个 kv 存储罢了。而 Nebula 没把 kv 作为一个服务单独提出,其最主要的原因便是图查询过程中会涉及到大量计算,这些计算往往需要使用图的 schema,而 kv 层是没有数据 schema 概念,这样设计会比较容易实现计算下推。

Schema & Partition

图存储的主要数据是点和边,但 Nebula 存储的数据是一张属性图,也就是说除了点和边以外,Nebula 还存储了它们对应的属性,以便更高效地使用属性过滤。

对于点来说,我们使用不同的 Tag 表示不同类型的点,同一个 VertexID 可以关联多个 Tag,而每一个 Tag 都有自己对应的属性。对应到 kv 存储里面,我们使用 vertexID + TagID 来表示 key,  我们把相关的属性编码后放在 value 里面,具体 key 的 format 如图2 所示:

 图二 Vertex Key Format

  • Type :  1 个字节,用来表示 key 类型,当前的类型有 data, index, system 等
  • Part ID : 3 个字节,用来表示数据分片 Partition,此字段主要用于 Partition 重新分布(balance) 时方便根据前缀扫描整个 Partition 数据
  • Vertex ID : 4 个字节, 用来表示点的 ID
  • Tag ID : 4 个字节, 用来表示关联的某个 tag
  • Timestamp : 8 个字节,对用户不可见,未来实现分布式事务 ( MVCC ) 时使用

在一个图中,每一条逻辑意义上的边,在 Nebula Graph 中会建模成两个独立的 key-value,分别称为 out-key 和in-key。out-key 与这条边所对应的起点存储在同一个 partition 上,in-key 与这条边所对应的终点存储在同一个partition 上。通常来说,out-key 和 in-key 会分布在两个不同的 Partition 中。

两个点之间可能存在多种类型的边,Nebula 用 Edge Type 来表示边类型。而同一类型的边可能存在多条,比如,定义一个 edge type "转账",用户 A 可能多次转账给 B, 所以 Nebula 又增加了一个 Rank 字段来做区分,表示 A 到 B 之间多次转账记录。 Edge key 的 format 如图3 所示:

 图三 Edge Key Format

  • Type :  1 个字节,用来表示 key 的类型,当前的类型有 data, index, system 等。
  • Part ID : 3 个字节,用来表示数据分片 Partition,此字段主要用于 Partition 重新分布(balance) 时方便根据前缀扫描整个 Partition 数据
  • Vertex ID : 4 个字节, 出边里面用来表示源点的 ID, 入边里面表示目标点的 ID。
  • Edge Type : 4 个字节, 用来表示这条边的类型,如果大于 0 表示出边,小于 0 表示入边。
  • Rank : 4 个字节,用来处理同一种类型的边存在多条的情况。用户可以根据自己的需求进行设置,这个字段可_存放交易时间_、交易流水号、或_某个排序权重_
  • Vertex ID : 4 个字节,  出边里面用来表示目标点的 ID, 入边里面表示源点的 ID。
  • Timestamp : 8 个字节,对用户不可见,未来实现分布式做事务的时候使用。

针对 Edge Type 的值,若如果大于 0 表示出边,则对应的 edge key format 如图4 所示;若 Edge Type 的值小于 0,则对应的 edge key format 如图5 所示

 图4 出边的 Key Format  图5 入边的 Key Format

对于点或边的属性信息,有对应的一组 kv pairs,Nebula 将它们编码后存在对应的 value 里。由于 Nebula 使用强类型 schema,所以在解码之前,需要先去 Meta Service 中取具体的 schema 信息。另外,为了支持在线变更 schema,在编码属性时,会加入对应的 schema 版本信息,具体的编解码细节在这里不作展开,后续会有专门的文章讲解这块内容。

OK,到这里我们基本上了解了 Nebula 是如何存储数据的,那数据是如何进行分片呢?很简单,对 Vertex ID 取模 即可。通过对 Vertex ID 取模,同一个点的所有_出边_,_入边_以及这个点上所有关联的 _Tag 信息_都会被分到同一个 Partition,这种方式大大地提升了查询效率。对于在线图查询来讲,最常见的操作便是从一个点开始向外 BFS(广度优先)拓展,于是拿一个点的出边或者入边是最基本的操作,而这个操作的性能也决定了整个遍历的性能。BFS 中可能会出现按照某些属性进行剪枝的情况,Nebula 通过将属性与点边存在一起,来保证整个操作的高效。当前许多的图数据库通过 Graph 500 或者 Twitter 的数据集试来验证自己的高效性,这并没有代表性,因为这些数据集没有属性,而实际的场景中大部分情况都是属性图,并且实际中的 BFS 也需要进行大量的剪枝操作。

KVStore

为什么要自己做 KVStore,这是我们无数次被问起的问题。理由很简单,当前开源的 KVStore 都很难满足我们的要求:

  • 性能性能性能:Nebula 的需求很直接:高性能 pure kv;
  • 以 library 的形式提供:对于强 schema 的 Nebula 来讲,计算下推需要 schema 信息,而计算下推实现的好坏,是 Nebula 是否高效的关键;
  • 数据强一致:这是分布式系统决定的;
  • 使用 C++实现:这由团队的技术特点决定;

基于上述要求,Nebula 实现了自己的 KVStore。当然,对于性能完全不敏感且不太希望搬迁数据的用户来说,Nebula 也提供了整个KVStore 层的 plugin,直接将 Storage Service 搭建在第三方的 KVStore 上面,目前官方提供的是 HBase 的 plugin。

Nebula KVStore 主要采用 RocksDB 作为本地的存储引擎,对于多硬盘机器,为了充分利用多硬盘的并发能力,Nebula 支持自己管理多块盘,用户只需配置多个不同的数据目录即可。分布式 KVStore 的管理由 Meta Service 来统一调度,它记录了所有 Partition 的分布情况,以及当前机器的状态,当用户增减机器时,只需要通过 console 输入相应的指令,Meta Service 便能够生成整个 balance plan 并执行。(之所以没有采用完全自动 balance 的方式,主要是为了减少数据搬迁对于线上服务的影响,balance 的时机由用户自己控制。)

为了方便对于 WAL 进行定制,Nebula KVStore 实现了自己的 WAL 模块,每个 partition 都有自己的 WAL,这样在追数据时,不需要进行 wal split 操作, 更加高效。 另外,为了实现一些特殊的操作,专门定义了 Command Log 这个类别,这些 log 只为了使用 Raft 来通知所有 replica 执行某一个特定操作,并没有真正的数据。除了 Command Log 外,Nebula 还提供了一类日志来实现针对某个 Partition 的 atomic operation,例如 CAS,read-modify-write,  它充分利用了Raft 串行的特性。

关于多图空间(space)的支持:一个 Nebula KVStore 集群可以支持多个 space,每个 space 可设置自己的 partition 数和 replica 数。不同 space 在物理上是完全隔离的,而且在同一个集群上的不同 space 可支持不同的 store engine 及分片策略。

Raft

作为一个分布式系统,KVStore 的 replication,scale out 等功能需 Raft 的支持。当前,市面上讲 Raft 的文章非常多,具体原理性的内容,这里不再赘述,本文主要说一些 Nebula Raft 的一些特点以及工程实现。

Multi Raft Group

由于 Raft 的日志不允许空洞,几乎所有的实现都会采用 Multi Raft Group 来缓解这个问题,因此 partition 的数目几乎决定了整个 Raft Group 的性能。但这也并不是说 Partition 的数目越多越好:每一个 Raft Group 内部都要存储一系列的状态信息,并且每一个 Raft Group 有自己的 WAL 文件,因此 Partition 数目太多会增加开销。此外,当 Partition 太多时, 如果负载没有足够高,batch 操作是没有意义的。比如,一个有 1w tps 的线上系统单机,它的单机 partition 的数目超过 1w,可能每个 Partition 每秒的 tps 只有 1,这样 batch 操作就失去了意义,还增加了 CPU 开销。 实现 Multi Raft Group 的最关键之处有两点,** 第一是共享 Transport 层**,因为每一个 Raft Group 内部都需要向对应的 peer  发送消息,如果不能共享 Transport 层,连接的开销巨大;第二是线程模型,Mutli Raft Group 一定要共享一组线程池,否则会造成系统的线程数目过多,导致大量的 context switch 开销。

Batch

对于每个 Partition来说,由于串行写 WAL,为了提高吞吐,做 batch 是十分必要的。一般来讲,batch 并没有什么特别的地方,但是 Nebula 利用每个 part 串行的特点,做了一些特殊类型的 WAL,带来了一些工程上的挑战。

举个例子,Nebula 利用 WAL 实现了无锁的 CAS 操作,而每个 CAS 操作需要之前的 WAL 全部 commit 之后才能执行,所以对于一个 batch,如果中间夹杂了几条 CAS 类型的 WAL, 我们还需要把这个 batch 分成粒度更小的几个 group,group 之间保证串行。还有,command 类型的 WAL 需要它后面的 WAL 在其 commit 之后才能执行,所以整个 batch 划分 group 的操作工程实现上比较有特色。

Learner

Learner 这个角色的存在主要是为了 应对扩容 时,新机器需要"追"相当长一段时间的数据,而这段时间有可能会发生意外。如果直接以 follower 的身份开始追数据,就会使得整个集群的 HA 能力下降。 Nebula 里面 learner 的实现就是采用了上面提到的 command wal,leader 在写 wal 时如果碰到 add learner 的 command, 就会将 learner 加入自己的 peers,并把它标记为 learner,这样在统计多数派的时候,就不会算上 learner,但是日志还是会照常发送给它们。当然 learner 也不会主动发起选举。

Transfer Leadership

Transfer leadership 这个操作对于 balance 来讲至关重要,当我们把某个 Paritition 从一台机器挪到另一台机器时,首先便会检查 source 是不是 leader,如果是的话,需要先把他挪到另外的 peer 上面;在搬迁数据完毕之后,通常还要把 leader 进行一次 balance,这样每台机器承担的负载也能保证均衡。

实现 transfer leadership, 需要注意的是 leader 放弃自己的 leadership,和 follower 开始进行 leader election 的时机。对于 leader 来讲,当 transfer leadership command 在 commit 的时候,它放弃 leadership;而对于 follower 来讲,当收到此 command 的时候就要开始进行 leader election, 这套实现要和 Raft 本身的 leader election 走一套路径,否则很容易出现一些难以处理的 corner case。

Membership change

为了避免脑裂,当一个 Raft Group 的成员发生变化时,需要有一个中间状态, 这个状态下 old group 的多数派与 new group 的多数派总是有 overlap,这样就防止了 old group 或者新 group 单方面做出决定,这就是论文中提到的 joint consensus 。为了更加简化,Diego Ongaro 在自己的博士论文中提出每次增减一个 peer 的方式以保证 old group 的多数派总是与 new group 的多数派有 overlap。 Nebula 的实现也采用了这个方式,只不过 add member 与 remove member 的实现有所区别,具体实现方式本文不作讨论,有兴趣的同学可以参考 Raft Part class 里面 addPeer  /  removePeer  的实现。

Snapshot

Snapshot 如何与 Raft 流程结合起来,论文中并没有细讲,但是这一部分我认为是一个 Raft 实现里最容易出错的地方,因为这里会产生大量的 corner case。

举一个例子,当 leader 发送 snapshot 过程中,如果 leader 发生了变化,该怎么办? 这个时候,有可能 follower 只接到了一半的 snapshot 数据。 所以需要有一个 Partition 数据清理过程,由于多个 Partition 共享一份存储,因此如何清理数据又是一个很麻烦的问题。另外,snapshot 过程中,会产生大量的 IO,为了性能考虑,我们不希望这个过程与正常的 Raft 共用一个 IO threadPool,并且整个过程中,还需要使用大量的内存,如何优化内存的使用,对于性能十分关键。由于篇幅原因,我们并不会在本文对这些问题展开讲述,有兴趣的同学可以参考 SnapshotManager  的实现。

Storage Service

在 KVStore 的接口之上,Nebula 封装有图语义接口,主要的接口如下:

  • getNeighbors :  查询一批点的出边或者入边,返回边以及对应的属性,并且需要支持条件过滤;
  • Insert vertex/edge :  插入一条点或者边及其属性;
  • getProps : 获取一个点或者一条边的属性;

这一层会将图语义的接口转化成 kv 操作。为了提高遍历的性能,还要做并发操作。

Meta Service

在 KVStore 的接口上,Nebula 也同时封装了一套 meta 相关的接口。Meta Service 不但提供了图 schema 的增删查改的功能,还提供了集群的管理功能以及用户鉴权相关的功能。Meta Service 支持单独部署,也支持使用多副本来保证数据的安全。

总结

这篇文章给大家大致介绍了 Nebula Storage 层的整体设计, 由于篇幅原因, 很多细节没有展开讲, 欢迎大家到我们的微信群里提问,加入 Nebula Graph 交流群,请联系 Nebula Graph 官方小助手微信号:NebulaGraphbot。

相关阅读

附录

Nebula Graph:一个开源的分布式图数据库。

GitHub:https://github.com/vesoft-inc/nebula

知乎:https://www.zhihu.com/org/nebulagraph/posts

微博:https://weibo.com/nebulagraph

HTTP-Reverse-Proxy反向代理Nginx硬件指纹校验

jjjjerk 发表了文章 • 0 个评论 • 547 次浏览 • 2019-10-15 15:06 • 来自相关话题

原文地址 https://moj ...查看全部