首页
友链
统计
留言
更多
直播
壁纸
推荐
我的毛线
哔哔点啥
院长科技
Search
1
pxe 自动化安装系统
644 阅读
2
本站官方群:894703859------|诚邀各位大佬的入驻!
638 阅读
3
软件安装
465 阅读
4
新款螺旋帽子编织#夏凉帽#合股线夏凉帽编织
402 阅读
5
10 个Linux Awk文本处理经典案例
376 阅读
linux
yaml
iptables
shell
ansible
ssl
awk
sed
pxe
prometheus
Nginx
k8s
fish
dev
go占位符
clickhouse
html标签
vue基础
html表格
vue项目
vscode
css基础
css定位
css精灵图
code
html5
project
js
jQuery
面向对象
编织
编织视频
常用工具
微软
登录
/
注册
Search
标签搜索
基础
js
Nginx
css
webapi
jQuery
面向对象
command
项目
ansible
用户权限
go
html
文件管理
命令
k8s
shell
pxe
awk
vscode
JustDoIt
累计撰写
114
篇文章
累计收到
4
条评论
首页
栏目
linux
yaml
iptables
shell
ansible
ssl
awk
sed
pxe
prometheus
Nginx
k8s
fish
dev
go占位符
clickhouse
html标签
vue基础
html表格
vue项目
vscode
css基础
css定位
css精灵图
code
html5
project
js
jQuery
面向对象
编织
编织视频
常用工具
微软
页面
友链
统计
留言
直播
壁纸
推荐
我的毛线
哔哔点啥
院长科技
搜索到
114
篇与
的结果
2022-03-02
反射reflect
一、引入先看官方Doc中Rob Pike给出的关于反射的定义:Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion. (在计算机领域,反射是一种让程序——主要是通过类型——理解其自身结构的一种能力。它是元编程的组成之一,同时它也是一大引人困惑的难题。) 维基百科中的定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。 不同语言的反射模型不尽相同,有些语言还不支持反射。《Go 语言圣经》中是这样定义反射的:Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。为什么要用反射需要反射的 2 个常见场景: 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。 但是对于反射,还是有几点不太建议使用反射的理由: 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。 Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。 二、相关基础反射是如何实现的?我们以前学习过 interface,它是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。在进行更加详细的了解之前,我们需要重新温习一下Go语言相关的一些特性,所谓温故知新,从这些特性中了解其反射机制是如何使用的。 特点 说明 go语言是静态类型语言。 编译时类型已经确定,比如对已基本数据类型的再定义后的类型,反射时候需要确认返回的是何种类型。 空接口interface{} go的反射机制是要通过接口来进行的,而类似于Java的Object的空接口可以和任何类型进行交互,因此对基本数据类型等的反射也直接利用了这一特点 Go语言的类型: 变量包括(type, value)两部分 理解这一点就知道为什么nil != nil了 type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型 类型断言能否成功,取决于变量的concrete type,而不是static type。因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer。 Go是静态类型语言。每个变量都拥有一个静态类型,这意味着每个变量的类型在编译时都是确定的:int,float32, *AutoType, []byte, chan []int 诸如此类。在反射的概念中, 编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型。 静态类型静态类型就是变量声明时的赋予的类型。比如: type MyInt int // int 就是静态类型 type A struct{ Name string // string就是静态 } var i *int // *int就是静态类型 动态类型动态类型:运行时给这个变量赋值时,这个值的类型(如果值为nil的时候没有动态类型)。一个变量的动态类型在运行时可能改变,这主要依赖于它的赋值(前提是这个变量是接口类型)。 var A interface{} // 静态类型interface{} A = 10 // 静态类型为interface{} 动态为int A = "String" // 静态类型为interface{} 动态为string var M *int A = M // A的值可以改变 Go语言的反射就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:(value, type) value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。例如,创建类型为*os.File的变量,然后将其赋给一个接口变量r:tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) var r io.Reader r = tty 接口变量r的pair中将记录如下信息:(tty, *os.File),这个pair在接口变量的连续赋值过程中是不变的,将接口变量r赋给另一个接口变量w:var w io.Writer w = r.(io.Writer) 接口变量w的pair与r的pair相同,都是:(tty, *os.File),即使w是空接口类型,pair也是不变的。interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。所以我们要理解两个基本概念 Type 和 Value,它们也是 Go语言包中 reflect 空间里最重要的两个类型。三、Type和Value我们一般用到的包是reflect包。既然反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。那么在Golang的reflect反射包中有什么样的方式可以让我们直接获取到变量内部的信息呢? 它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf() 和 reflect.TypeOf(),看看官方的解释// ValueOf returns a new Value initialized to the concrete value // stored in the interface i. ValueOf(nil) returns the zero func ValueOf(i interface{}) Value {...} 翻译一下:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0 // TypeOf returns the reflection Type that represents the dynamic type of i. // If i is a nil interface value, TypeOf returns nil. func TypeOf(i interface{}) Type {...} 翻译一下:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value。首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数。t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值 示例代码:package main import ( "fmt" "reflect" ) func main() { //反射操作:通过反射,可以获取一个接口类型变量的 类型和数值 var x float64 =3.4 fmt.Println("type:",reflect.TypeOf(x)) //type: float64 fmt.Println("value:",reflect.ValueOf(x)) //value: 3.4 fmt.Println("-------------------") //根据反射的值,来获取对应的类型和数值 v := reflect.ValueOf(x) fmt.Println("kind is float64: ",v.Kind() == reflect.Float64) fmt.Println("type : ",v.Type()) fmt.Println("value : ",v.Float()) } 运行结果:type: float64 value: 3.4 ------------------- kind is float64: true type : float64 value : 3.4 说明 reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型 reflect.ValueOf:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值 也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是reflect.Type和reflect.Value这两种 Type 和 Value 都包含了大量的方法,其中第一个有用的方法应该是 Kind,这个方法返回该类型的具体信息:Uint、Float64 等。Value 类型还包含了一系列类型方法,比如 Int(),用于返回对应的值。以下是Kind的种类:// A Kind represents the specific kind of type that a Type represents. // The zero Kind is not a valid kind. type Kind uint const ( 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 ) 四、反射的规则其实反射的操作步骤非常的简单,就是通过实体对象获取反射对象(Value、Type),然后操作相应的方法即可。下图描述了实例、Value、Type 三者之间的转换关系:反射 API 的分类总结如下:1) 从实例到 Value通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数。例如:func ValueOf(i interface {}) Value 2) 从实例到 Type通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数。例如:func TypeOf(i interface{}) Type 3) 从 Type 到 ValueType 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。reflect 包提供了两种方法,示例如下://New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型 func New(typ Type) Value //Zero 返回的是一个 typ 类型的零佳,注意返回的 Value 不能寻址,位不可改变 func Zero(typ Type) Value 如果知道一个类型值的底层存放地址,则还有一个函数是可以依据 type 和该地址值恢复出 Value 的。例如:func NewAt(typ Type, p unsafe.Pointer) Value 4) 从 Value 到 Type从反射对象 Value 到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到 Type 类型的指针。例如:func (v Value) Type() Type 5) 从 Value 到实例Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value 到实例的转换。例如://该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例 //可以使用接口类型查询去还原为具体的类型 func (v Value) Interface() (i interface{}) //Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic func (v Value) Bool () bool func (v Value) Float() float64 func (v Value) Int() int64 func (v Value) Uint() uint64 6) 从 Value 的指针到值从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。//如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如采 v 类型是指针,则返回指针值的 Value,否则引起 panic func (v Value) Elem() Value //如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic func Indirect(v Value) Value 7) Type 指针和值的相互转换指针类型 Type 到值类型 Type。例如://t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic //Elem 返回的是其内部元素的 Type t.Elem() Type 值类型 Type 到指针类型 Type。例如://PtrTo 返回的是指向 t 的指针型 Type func PtrTo(t Type) Type 8) Value 值的可修改性Value 值的修改涉及如下两个方法://通过 CanSet 判断是否能修改 func (v Value ) CanSet() bool //通过 Set 进行修改 func (v Value ) Set(x Value) Value 值在什么情况下可以修改?我们知道实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法 reflect.ValueOf() 传进去的是一个值类型变量, 则获得的 Value 实际上是原对象的一个副本,这个 Value 是无论如何也不能被修改的。根据 Go 官方关于反射的博客,反射有三大定律: Reflection goes from interface value to reflection object. Reflection goes from reflection object to interface value. To modify a reflection object, the value must be settable.第一条是最基本的:反射可以从接口值得到反射对象。 反射是一种检测存储在 interface中的类型和值机制。这可以通过 TypeOf函数和 ValueOf函数得到。第二条实际上和第一条是相反的机制,反射可以从反射对象获得接口值。 它将 ValueOf的返回值通过 Interface()函数反向转变成 interface变量。前两条就是说 接口型变量和 反射类型对象可以相互转化,反射类型对象实际上就是指的前面说的 reflect.Type和 reflect.Value。第三条不太好懂:如果需要操作一个反射变量,则其值必须可以修改。 反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。 五、反射的使用5.1 从relfect.Value中获取接口interface的信息当执行reflect.ValueOf(interface)之后,就得到了一个类型为”relfect.Value”变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,我们可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明。已知原有类型已知类型后转换为其对应的类型的做法如下,直接通过Interface方法然后强制转换,如下:realValue := value.Interface().(已知的类型) 示例代码:package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 pointer := reflect.ValueOf(&num) value := reflect.ValueOf(num) // 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic // Golang 对类型要求非常严格,类型一定要完全符合 // 如下两个,一个是*float64,一个是float64,如果弄混,则会panic convertPointer := pointer.Interface().(*float64) convertValue := value.Interface().(float64) fmt.Println(convertPointer) fmt.Println(convertValue) } 运行结果:0xc000098000 1.2345 说明 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格! 转换的时候,要区分是指针还是指 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量” 未知原有类型很多情况下,我们可能并不知道其具体类型,那么这个时候,该如何做呢?需要我们进行遍历探测其Filed来得知,示例如下:package main import ( "fmt" "reflect" ) type Person struct { Name string Age int Sex string } func (p Person)Say(msg string) { fmt.Println("hello,",msg) } func (p Person)PrintInfo() { fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex) } func main() { p1 := Person{"王二狗",30,"男"} DoFiledAndMethod(p1) } // 通过接口来获取任意参数 func DoFiledAndMethod(input interface{}) { getType := reflect.TypeOf(input) //先获取input的类型 fmt.Println("get Type is :", getType.Name()) // Person fmt.Println("get Kind is : ", getType.Kind()) // struct getValue := reflect.ValueOf(input) fmt.Println("get all Fields is:", getValue) //{王二狗 30 男} // 获取方法字段 // 1. 先获取interface的reflect.Type,然后通过NumField进行遍历 // 2. 再通过reflect.Type的Field获取其Field // 3. 最后通过Field的Interface()得到对应的value for i := 0; i < getType.NumField(); i++ { field := getType.Field(i) value := getValue.Field(i).Interface() //获取第i个值 fmt.Printf("字段名称:%s, 字段类型:%s, 字段数值:%v \n", field.Name, field.Type, value) } // 通过反射,操作方法 // 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历 // 2. 再公国reflect.Type的Method获取其Method for i := 0; i < getType.NumMethod(); i++ { method := getType.Method(i) fmt.Printf("方法名称:%s, 方法类型:%v \n", method.Name, method.Type) } } 运行结果:get Type is : Person get Kind is : struct get all Fields is: {王二狗 30 男} 字段名称:Name, 字段类型:string, 字段数值:王二狗 字段名称:Age, 字段类型:int, 字段数值:30 字段名称:Sex, 字段类型:string, 字段数值:男 方法名称:PrintInfo, 方法类型:func(main.Person) 方法名称:Say, 方法类型:func(main.Person, string) 说明通过运行结果可以得知获取未知类型的interface的具体变量及其类型的步骤为: 先获取interface的reflect.Type,然后通过NumField进行遍历 再通过reflect.Type的Field获取其Field 最后通过Field的Interface()得到对应的value 通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为: 先获取interface的reflect.Type,然后通过NumMethod进行遍历 再分别通过reflect.Type的Method获取对应的真实的方法(函数) 最后对结果取其Name和Type得知具体的方法名 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量” struct 或者 struct 的嵌套都是一样的判断处理方式 如果是struct的话,可以使用Elem()tag := t.Elem().Field(0).Tag //获取定义在struct里面的Tag属性 name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值 5.2 通过reflect.Value设置实际变量的值reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。这里需要一个方法:解释起来就是:Elem返回接口v包含的值或指针v指向的值。如果v的类型不是interface或ptr,它会恐慌。如果v为零,则返回零值。package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 fmt.Println("old value of pointer:", num) // 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值 pointer := reflect.ValueOf(&num) newValue := pointer.Elem() fmt.Println("type of pointer:", newValue.Type()) fmt.Println("settability of pointer:", newValue.CanSet()) // 重新赋值 newValue.SetFloat(77) fmt.Println("new value of pointer:", num) //////////////////// // 如果reflect.ValueOf的参数不是指针,会如何? //pointer = reflect.ValueOf(num) //newValue = pointer.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value” } 运行结果:old value of pointer: 1.2345 type of pointer: float64 settability of pointer: true new value of pointer: 77 说明 需要传入的参数是* float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。 如果传入的参数不是指针,而是变量,那么 通过Elem获取原始值对应的对象则直接panic 通过CanSet方法查询是否可以设置返回false newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。 reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】 struct 或者 struct 的嵌套都是一样的判断处理方式 5.3 通过reflect.Value来进行方法的调用这算是一个高级用法了,前面我们只说到对类型、变量的几种反射的用法,包括如何获取其值、其类型、以及如何重新设置新值。但是在项目应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法【函数】的调用。比如我们要做框架工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么我们通过什么手段来扩展让用户能够自定义呢?关键点在于用户的自定义方法是未可知的,因此我们可以通过reflect来搞定。Call()方法:通过反射,调用方法。先获取结构体对象,然后示例代码:package main import ( "fmt" "reflect" ) type Person struct { Name string Age int Sex string } func (p Person)Say(msg string) { fmt.Println("hello,",msg) } func (p Person)PrintInfo() { fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex) } func (p Person) Test(i,j int,s string){ fmt.Println(i,j,s) } // 如何通过反射来进行方法的调用? // 本来可以用结构体对象.方法名称()直接调用的, // 但是如果要通过反射, // 那么首先要将方法注册,也就是MethodByName,然后通过反射调动mv.Call func main() { p2 := Person{"Ruby",30,"男"} // 1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value, // 得到“反射类型对象”后才能做下一步处理 getValue := reflect.ValueOf(p2) // 2.一定要指定参数为正确的方法名 // 先看看没有参数的调用方法 methodValue1 := getValue.MethodByName("PrintInfo") fmt.Printf("Kind : %s, Type : %s\n",methodValue1.Kind(),methodValue1.Type()) methodValue1.Call(nil) //没有参数,直接写nil args1 := make([]reflect.Value, 0) //或者创建一个空的切片也可以 methodValue1.Call(args1) // 有参数的方法调用 methodValue2 := getValue.MethodByName("Say") fmt.Printf("Kind : %s, Type : %s\n",methodValue2.Kind(),methodValue2.Type()) args2 := []reflect.Value{reflect.ValueOf("反射机制")} methodValue2.Call(args2) methodValue3 := getValue.MethodByName("Test") fmt.Printf("Kind : %s, Type : %s\n",methodValue3.Kind(),methodValue3.Type()) args3 := []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200),reflect.ValueOf("Hello")} methodValue3.Call(args3) } 运行结果:Kind : func, Type : func() 姓名:Ruby,年龄:30,性别:男 姓名:Ruby,年龄:30,性别:男 Kind : func, Type : func(string) hello, 反射机制 Kind : func, Type : func(int, int, string) 100 200 Hello 通过反射,调用函数。首先我们要先确认一点,函数像普通的变量一样,之前的章节中我们在讲到函数的本质的时候,是可以把函数作为一种变量类型的,而且是引用类型。如果说Fun()是一个函数,那么f1 := Fun也是可以的,那么f1也是一个函数,如果直接调用f1(),那么运行的就是Fun()函数。那么我们就先通过ValueOf()来获取函数的反射对象,可以判断它的Kind,是一个func,那么就可以执行Call()进行函数的调用。示例代码:package main import ( "fmt" "reflect" ) func main() { //函数的反射 f1 := fun1 value := reflect.ValueOf(f1) fmt.Printf("Kind : %s , Type : %s\n",value.Kind(),value.Type()) //Kind : func , Type : func() value2 := reflect.ValueOf(fun2) fmt.Printf("Kind : %s , Type : %s\n",value2.Kind(),value2.Type()) //Kind : func , Type : func(int, string) //通过反射调用函数 value.Call(nil) value2.Call([]reflect.Value{reflect.ValueOf(100),reflect.ValueOf("hello")}) } func fun1(){ fmt.Println("我是函数fun1(),无参的。。") } func fun2(i int, s string){ fmt.Println("我是函数fun2(),有参数。。",i,s) } 说明 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理 reflect.Value.MethodByName这个MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。 []reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。 reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,如果reflect.Value.Kind不是一个方法,那么将直接panic。 本来可以用对象访问方法直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调用methodValue.Call 本文参照:http://www.sohu.com/a/313420275_657921https://studygolang.com/articles/12348?fr=sidebarhttp://c.biancheng.net/golang/源代码:https://github.com/rubyhan1314/go_reflect
2022年03月02日
127 阅读
0 评论
0 点赞
2022-03-01
软件安装
vscode 软件安装 VSCode软件 能够安装 VS Code 能够熟练使用 VS Code 软件 能够安装 VS Code 最常用的插件VSCode 简介Visual Studio Code (简称 VS Code / VSC) 是微软公司推出的一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、GIT 等特性,支持插件扩展等等。推荐理由: 比 sublime 开源,比 webstorm 更轻 智能提示很强大 自带 emmet 插件安装非常方便 自带强大的调试功能 软件跨平台支持 Win、Mac 以及 Linux。 VSCode 安装官网下载地址: https://code.visualstudio.com/安装步骤:傻瓜式安装,直接下一步即可。VSCode 使用文件目录管理 选择File,然后选择Open Folder,打开文件夹。 界面主要分为EXPLORER(资源管理界面)和代码编辑页面。 颜色主题 设置首选项按钮 -- Color Theme 在弹出的选择主题界面,选择喜欢的主题界面即可。其中Monokai是与sublime一致的风格。 其他操作 放大缩小视图:ctrl + 加号 和 ctrl + 减号 向上复制一行:alt+shift+↑ 向下复制一行:alt+shift+↓ 当光标点击到某一行时,默认选中全行,可以直接复制粘贴 VSCode 插件安装安装方法点击左侧《扩展》图标,在搜索框输入需要安装的插件名称,点击install进行安装即可。安装完毕,需要重新加载软件使插件生效。推荐安装的插件 插件 作用 Chinese (Simplified) Language Pack for VS Code 中文(简体)语言包 Open in Browser 右击选择浏览器打开html文件 JS-CSS-HTML Formatter 每次保存,都会自动格式化js css 和html 代码 Auto Rename Tag 自动重命名配对的HTML / XML标签 CSS Peek 追踪至样式 注意:插件安装需要联网。禁用或卸载已安装的插件在扩展界面,点击“更多操作”(三个点),选择“显示安装的扩展”,在列表中找到需要禁用的插件,点击“禁用”或者“卸载”即可。同样操作完毕需要重新加载生效。
2022年03月01日
465 阅读
0 评论
1 点赞
2022-02-27
Prometheus之存储及WAL
一、整体介绍Prometheus 2.x 采用自定义的存储格式将样本数据保存在本地磁盘当中。 如下所示,按照两个小时(最少时间)为一个时间窗口, 将两小时内产生的数据存储在一个块(Block)中, 每一个块中包含该时间窗口内的所有样本数据(chunks), 元数据文件(meta.json)以及索引文件(index) 二、block├── 01E2MA5GDWMP69GVBVY1W5AF1X │ ├── chunks # 保存压缩后的时序数据,每个chunks大小为512M,超过会生成新的chunks │ │ └── 000001 │ ├── index # chunks中的偏移位置 │ ├── meta.json # 记录block块元信息,比如 样本的起始时间、chunks数量和数据量大小等 │ └── tombstones # 通过API方式对数据进行软删除,将删除记录存储在此处(API的删除方式,并不是立即将数据从chunks文件中移除) ├── 01E2MH175FV0JFB7EGCRZCX8NF │ ├── chunks │ │ └── 000001 │ ├── index │ ├── meta.json │ └── tombstones ├── 01E2MQWYDFQAXXPB3M1HK6T20A │ ├── chunks │ │ └── 000001 │ ├── index │ ├── meta.json │ └── tombstones ├── lock ├── queries.active └── wal #防止数据丢失(数据收集上来暂时是存放在内存中,wal记录了这些信息) ├── 00000366 #每个数据段最大为128M,存储默认存储两个小时的数据量。 ├── 00000367 ├── 00000368 ├── 00000369 └── checkpoint.000365 └── 00000000 2.1 解析TSDB将存储的监控数据按照时间分成多个block存储, 默认最小的block保存时间为2h, 后台程序还会将小块合并成大块, 减少内存中block的数量, 便于索引查找数据, 可以通过meta.json查看, 可以看到01E2MA5GDWMP69GVBVY1W5AF1X被压缩1次, source有3个block, 那么2*3=6小时的数据量。 2.2 关于block压缩* 最初的两个小时的块最终会在后台压缩为更长时间的块; * 压缩的最大时间块为数据保留时间的10%或者31天,取两者的较小者。 2.3 head block* head block中的数据是被存储在内存中的并且可以被任意修改; * head block和后续的block初始设定保存2h数据,当head block超过3h时,会被拆分为2h+1h,2h block会变成只读块写入磁盘.(通过观察服务器上prometheus存储目录,每次压缩合并小块时间都比块内部时间多三个小时,为head block) 三、WAL(Write-ahead logging, 预写日志)Prometheus为了防止丢失暂存在内存中的还未被写入磁盘的监控数据,引入了WAL机制。 WAL被分割成默认大小为128M的文件段(segment), 之前版本默认大小是256M, 文件段以数字命名, 长度为8位的整形。 WAL的写入单位是页(page),每页的大小为32KB,所以每个段大小必须是页的大小的整数倍。 如果WAL一次性写入的页数超过一个段的空闲页数,就会创建一个新的文件段来保存这些页,从而确保一次性写入的页不会跨段存储。 3.1 数据流向prometheus将周期性采集到的数据通过Add接口添加到head block, 但是这些数据暂时没有持久化, TSDB通过WAL将数据保存到磁盘上(保存的数据没有压缩,占用内存较大),当出现宕机,启动多协程读取WAL,恢复数据。 四、和存储相关的启动参数–storage.tsdb.path: This determines where Prometheus writes its database. Defaults to data/. –storage.tsdb.retention.time: This determines when to remove old data. Defaults to 15d. Overrides storage.tsdb.retention if this flag is set to anything other than default. –storage.tsdb.retention.size: [EXPERIMENTAL] This determines the maximum number of bytes that storage blocks can use (note that this does not include the WAL size, which can be substantial). The oldest data will be removed first. Defaults to 0 or disabled. This flag is experimental and can be changed in future releases. Units supported: KB, MB, GB, PB. Ex: “512MB” –storage.tsdb.retention: This flag has been deprecated in favour of storage.tsdb.retention.time. –storage.tsdb.wal -compression: This flag enables compression of the write-ahead log (WAL). Depending on your data, you can expect the WAL size to be halved with little extra cpu load. Note that if you enable this flag and subsequently downgrade Prometheus to a version below 2.11.0 you will need to delete your WAL as it will be unreadable. PS: 以上有两个参数storage.tsdb.retention.size和storage.tsdb.retention.time,两个同时设置时,两者无优先级,谁先触发就执行删除操作。 其它启动参数参考promethes#promethes 第五章节启动参数部分)五、总结5.1 需要解决的几个问题1.远程存储节点长时间挂掉(默认blocK大小为2小时, 实际大于六小时,prometheus2.15经测试验证非官方文档说的两个小时), 刷盘到prometheus的数据库中的数据还能不能同步到远程? 5.2 WAL的缓存数据的时间可不可以调整?1.根据远程写参数优化可知,prometheus本地存储和远程存储并无影响。 因为远程存储是通过将WAL中的数据缓存到多个内存队列(shards)中,然后写到远程存储设备,其直接与WAL打交道。 而prometheus只是用WAL来防止数据丢失,其存储的一系列动作都与WAL没关系。 所以当内存中缓存的数据达到刷盘的阈值,WAL中没有写到远程存储的数据就会丢失, 当重新启动远程存储服务,原来那部分没有写入远程存储服务的数据已经丢失, 只能从最新的数据开始写入远程存储 1. 可以调整,准确来说是间接调整。wal保留数据的长短与prometheus最小压缩block大小有关系, 2. 由于wal中至少保留当前时间正在写入的文件之外的三个文件(每个文件保存一个block大小的数据量 3. 所以当增大block大小的时候就会相应的增大wal保存的数据量,但是,block的大小调整会直接影响内存的使用,需要根据现有的环境进行相应的调优。 当我设置–storage.tsdb.min-block-duration=4h(prometheus的启动参数)时,wal中当前保留的文件(存在的数据时间范围:2022.02.20 20:00:00–2022.02.21 13.52),其中每个文件保留4个小时的数据量。
2022年02月27日
157 阅读
0 评论
0 点赞
2022-02-20
pxe 自动化安装系统
1.需求公司机房需要reinstall os 2.pxe原理2.1 原理与概念事实上把PXE称作是一种引导方式而不是安装方式似乎更加准确, PXE(Pre-boot Execution Environment)是由Intel设计的协议, 它可以使计算机通过网络启动, 但是有一个前提条件是计算机的网卡必须具有引导功能, 这个网卡中要有一个PXE客户端。 当计算机POST自检成功以后,BIOS把网卡中ROM的PXE客户端调入内存执行, PXE客户端通过网络中的DHCP服务器获取一个IP地址, 拿到IP地址以后PXE继续引导计算机与网络中的TFTP客户端建立连接, 从而从TFTP服务器中获取开机引导文件之后请求并下载安装需要的文件。 在这个过程中需要一台服务器来提供启动文件、安装文件、 以及安装过程中的自动应答文件等 2.2 pxe工作流程图原理介绍 Client向PXE Server上的DHCP发送IP地址请求消息,DHCP检测Client是否合法(主要是检测Client的网卡MAC地址),如果合法则返回Client的IP地址,同时将启动文件pxelinux.0的位置信息一并传送给Client Client向PXE Server上的TFTP发送获取pxelinux.0请求消息,TFTP接收到消息之后再向Client发送pxelinux.0大小信息,试探Client是否满意,当TFTP收到Client发回的同意大小信息之后,正式向Client发送pxelinux.0 Client执行接收到的pxelinux.0文件 Client向TFTP Server发送针对本机的配置信息文件(在TFTP服务的pxelinux.cfg目录下,这是系统菜单文件,格式和isolinux.cfg格式一样,功能也是类似),TFTP将配置文件发回Client,继而Client根据配置文件执行后续操作。 Client向TFTP发送Linux内核请求信息,TFTP接收到消息之后将内核文件发送给Client Client向TFTP发送根文件请求信息,TFTP接收到消息之后返回Linux根文件系统 Client启动Linux内核 Client下载安装源文件,读取自动化安装脚本 3 cobbler3.1 cobbler工作流程 client裸机配置了从网络启动后,开机后会广播包请求DHCP服务器 (cobbler server)发送其分配好的一个IP DHCP服务器(cobbler server)收到请求后发送responese,包括其ip地址 client裸机拿到ip后再向cobbler server发送请求OS引导文件的请求 cobbler server告诉裸机OS引导文件的名字和TFTP server的ip和 port client裸机通过上面告知的TFTP server地址通信,下载引导文件 client裸机执行执行该引导文件,确定加载信息,选择要安装的os, 期间会再向cobbler server请求kickstart文件和os image cobbler server发送请求的kickstart和os iamge client裸机加载kickstart文件 client裸机接收os image,安装该os image 3.3 Cobbler集成的服务PXE服务支持 DHCP服务管理 DNS服务管理(可选bind,dnsmasq) 电源管理 Kickstart服务支持 YUM仓库管理 TFTP(PXE启动时需要) Apache(提供kickstart的安装源,并提供定制化的kickstart配置) 3.4 配置目录配置文件目录: /etc/cobbler /etc/cobbler/settings : cobbler 主配置文件 /etc/cobbler/iso/: iso模板配置文件 /etc/cobbler/pxe: pxe模板文件 /etc/cobbler/power: 电源配置文件 /etc/cobbler/user.conf: web服务授权配置文件 /etc/cobbler/users.digest: web访问的用户名密码配置文件 /etc/cobbler/dhcp.template : dhcp服务器的的配置末班 /etc/cobbler/dnsmasq.template : dns服务器的配置模板 /etc/cobbler/tftpd.template : tftp服务的配置模板 /etc/cobbler/modules.conf : 模块的配置文件 数据目录: /var/lib/cobbler/config/: 用于存放distros,system,profiles 等信 息配置文件 /var/lib/cobbler/triggers/: 用于存放用户定义的cobbler命令 /var/lib/cobbler/kickstart/: 默认存放kickstart文件 /var/lib/cobbler/loaders/: 存放各种引导程序 镜像目录 /var/www/cobbler/ks_mirror/: 导入的发行版系统的所有数据 /var/www/cobbler/images/ : 导入发行版的kernel和initrd镜像用于 远程网络启动 /var/www/cobbler/repo_mirror/: yum 仓库存储目录 日志目录: /var/log/cobbler/installing: 客户端安装日志 /var/log/cobbler/cobbler.log : cobbler日志 3.5 命令介绍cobbler commands介绍 cobbler check 核对当前设置是否有问题 cobbler list 列出所有的cobbler元素 cobbler report 列出元素的详细信息 cobbler sync 同步配置到数据目录,更改配置最好都要执行下 cobbler reposync 同步yum仓库 cobbler distro 查看导入的发行版系统信息 cobbler system 查看添加的系统信息 cobbler profile 查看配置信息 3.6 /etc/cobbler/settings中重要的参数设置default_password_crypted: "$1$gEc7ilpP$pg5iSOj/mlxTxEslhRvyp/" manage_dhcp:1 manage_tftpd:1 pxe_just_once:1 next_server:< tftp服务器的 IP 地址> server: 4. cobbler install4.1 系统信息[root@cobbler ~]# getenforce Disabled [root@cobbler ~]# systemctl status firewalld.service ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled) Active: inactive (dead) Docs: man:firewalld(1) [root@cobbler ~]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) [root@cobbler ~]# ip r default via 10.0.153.1 dev eth0 proto static metric 100 10.0.153.1 dev eth0 proto static scope link metric 100 10.0.153.116 dev eth0 proto kernel scope link src 10.0.153.116 metric 100 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 [root@cobbler ~]# hostname cobbler 4.1.1 ks文件ks.cfg文件组成大致分为3段 命令段 键盘类型,语言,安装方式等系统的配置,有必选项和可选项,如果缺少某项必选项,安装时会中断并提示用户选择此项的选项 软件包段 %packages @groupname:指定安装的包组 package_name:指定安装的包 -package_name:指定不安装的包 在安装过程中默认安装的软件包,安装软件时会自动分析依赖关系。 脚本段(可选) %pre:安装系统前执行的命令或脚本(由于只依赖于启动镜像,支持的命令很少) %post:安装系统后执行的命令或脚本(基本支持所有命令) 关键字 含义 install 告知安装程序,这是一次全新安装,而不是升级upgrade。 url --url=" " 通过FTP或HTTP从远程服务器上的安装树中安装。url --url="http://10.0.153.118/CentOS-7/"url --url ftp://:@/ nfs 从指定的NFS服务器安装。nfs --server=nfsserver.example.com --dir=/tmp/install-tree text|graphical tesxt:使用文本模式安装。 graphical:在图形模式下根据kickstart执行安装,默认该选项 lang 设置在安装过程中使用的语言以及系统的缺省语言。lang en_US.UTF-8 keyboard 设置系统键盘类型。keyboard us zerombr 清除mbr引导信息。 bootloader 系统引导相关配置。bootloader --location=mbr --driveorder=sda --append="crashkernel=auto rhgb quiet"--location=,指定引导记录被写入的位置.有效的值如下:mbr(缺省),partition(在包含内核的分区的第一个扇区安装引导装载程序)或none(不安装引导装载程序)。--driveorder,指定在BIOS引导顺序中居首的驱动器。--append=,指定内核参数.要指定多个参数,使用空格分隔它们。 network 为通过网络的kickstart安装以及所安装的系统配置联网信息。network --bootproto=dhcp --device=eth0 --onboot=yes --noipv6 --hostname=CentOS6--bootproto=[dhcp/bootp/static]中的一种,缺省值是dhcp。bootp和dhcp被认为是相同的。static方法要求在kickstart文件里输入所有的网络信息。network --bootproto=static --ip=10.0.0.100 --netmask=255.255.255.0 --gateway=10.0.0.2 --nameserver=10.0.0.2请注意所有配置信息都必须在一行上指定,不能使用反斜线来换行。--ip=,要安装的机器的IP地址.--gateway=,IP地址格式的默认网关.--netmask=,安装的系统的子网掩码.--hostname=,安装的系统的主机名.--onboot=,是否在引导时启用该设备.--noipv6=,禁用此设备的IPv6.--nameserver=,配置dns解析. timezone 设置系统时区。timezone --utc Asia/Shanghai authconfig 系统认证信息。authconfig --enableshadow --passalgo=sha512设置密码加密方式为sha512 启用shadow文件。 rootpw root密码 clearpart 清空分区。clearpart --all --initlabel--all 从系统中清除所有分区,--initlable 初始化磁盘标签 part 磁盘分区。part /boot --fstype=ext4 --asprimary --size=200 centos7 是--fstype=xfspart swap --size=1024part / --fstype=ext4 --grow --asprimary --size=200--fstype=,为分区设置文件系统类型.有效的类型为ext2,ext3,swap和vfat。--asprimary,强迫把分区分配为主分区,否则提示分区失败。--size=,以MB为单位的分区最小值.在此处指定一个整数值,如500.不要在数字后面加MB。--grow,告诉分区使用所有可用空间(若有),或使用设置的最大值。 firstboot 负责协助配置redhat一些重要的信息。firstboot --disable selinux 关闭selinux。selinux --disabled firewall 关闭防火墙。firewall --disabled logging 设置日志级别。logging --level=info reboot 设定安装完成后重启,此选项必须存在,不然kickstart显示一条消息,并等待用户按任意键后才重新引导,也可以选择halt关机。 4.2 配置yum源curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo yum makecache fast 4.3 此次使用docker部署常规部署参考 https://www.cnblogs.com/linuxliu/p/7668048.html 4.3.1 构建镜像FROM centos:7.2.1511 MAINTAINER 595265578@qq.com RUN yum -y install epel-release vim net-tools RUN yum -y install httpd tftp cobbler cobbler-web dhcp xinetd syslinux pykickstart bind && yum clean all RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; \ do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \ rm -f /lib/systemd/system/multi-user.target.wants/*;\ rm -f /etc/systemd/system/*.wants/*;\ rm -f /lib/systemd/system/local-fs.target.wants/*; \ rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ rm -f /lib/systemd/system/basic.target.wants/*;\ rm -f /lib/systemd/system/anaconda.target.wants/*; VOLUME [ "/sys/fs/cgroup" ] RUN systemctl enable cobblerd;systemctl enable httpd;systemctl enable dhcpd RUN sed -i -e 's/\(^.*disable.*=\) yes/\1 no/' /etc/xinetd.d/tftp RUN touch /etc/xinetd.d/rsync EXPOSE 69 EXPOSE 80 EXPOSE 443 EXPOSE 25151 CMD ["/sbin/init"] 4.3.2 builddocker build . -t cobbler:1.0 4.3.3 运行镜像启动容器前我们要先修改配置文件settings和dhcp.template,下文的10.0.153.118为docker宿主机的IP地址。 将容器内部settings dhcp.template文件拷贝至/opt 目录settings文件中需要修改的内容为:server: 192.168.101.100 #cobbler的服务器地址 next_server: 10.0.153.118 #tftp服务器地址 manage_dhcp: 1 #dhcpg管理设置为1,启用dhcp dhcp.template文件中需要修改的内容为: subnet 10.0.153.118 netmask 255.255.255.0 { #修改网段 option routers 10.0.153.1; #指定网关 option domain-name-servers 10.0.153.118; #指定dns option subnet-mask 255.255.255.0; #指定子网掩码 range dynamic-bootp 10.0.153.120 10.0.153.200; #指定地址池 修改完成后保存文件,并使用如下命令启动容器:docker run \ -d \ --privileged \ --net host \ -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ -v /etc/selinux:/etc/selinux \ -v /opt/settings:/etc/cobbler/settings \ -v /opt/dhcp.template:/etc/cobbler/dhcp.template \ -p 69:69 \ -p 80:80 \ -p 443:443 \ -p 25151:25151 \ --name cobbler1.0 cobbler:1.0 4.3.4 打开浏览器,确认cobbler_web可以访问账号和密码 cobbler/cobbler4.3.5 上传镜像vmware挂在iso镜像系统执行命令mount /dev/cdrom /mnt 拷贝镜像到容器内docker cp /mnt cobbler1.0:/opt/iso7 出现如上提示说明上传完成,之后点击Configuration模块的Distros,检查刚刚上传的镜像。4.3.6 ks文件服务器密码123456配置方法 修改settings文件 default_password_crypted[root@cobbler cobbler]# cat settings |grep pass # what install (root) password is set up for those # The simplest way to change the password is to run # openssl passwd -1 default_password_crypted: "$1$random-p$mzxQ/Sx848sXgvfwJCoZM0" # boot menu. Adding a password to the boot menus templates ldap_search_passwd: '' # This setting is also used by the code that supports using Spacewalk/Satellite users/passwords # URL will be passed directly to the kickstarting system, thus bypassing [root@cobbler cobbler]# openssl passwd -1 -salt 'random-phrase-here' '123456' $1$random-p$mzxQ/Sx848sXgvfwJCoZM0 ks文件模版 install url --url=$tree text lang en_US.UTF-8 keyboard us zerombr bootloader --location=mbr --driveorder=sda --append="crashkernel=auto rhgb quiet" #Network information $SNIPPET('network_config') #network --bootproto=dhcp --device=eth0 --onboot=yes --noipv6 --hostname=CentOS7 timezone --utc Asia/Shanghai authconfig --enableshadow --passalgo=sha512 rootpw --iscrypted $default_password_crypted clearpart --all --initlabel part /boot --asprimary --fstype="ext4" --size=200 part / --fstype="ext4" --grow --size=1 firstboot --disable selinux --disabled firewall --disabled logging --level=info reboot %pre $SNIPPET('log_ks_pre') $SNIPPET('kickstart_start') $SNIPPET('pre_install_network_config') # Enable installation monitoring $SNIPPET('pre_anamon') %end %packages @^minimal @compat-libraries @core @debugging @development bash-completion chrony dos2unix kexec-tools lrzsz nmap sysstat telnet tree vim wget net-tools %end %post systemctl disable postfix.service curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo %end 5 使用koan实现重新安装系统5.1 在客户端安装koan[root@localhost ~]# rpm -ivh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-10.noarch.rpm 如何不能使用 请参考上面的阿里云源即可 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [root@localhost ~]# yum install koan -y 5.1 查看cobbler上的配置文件1 [root@localhost ~]# koan --server=10.0.153.118 --list=profiles 2 - looking for Cobbler at http://10.0.0.101:80/cobbler_api 3 centos7-x86_64 重新安装客户端系统[root@localhost ~]# koan --replace-self --server=10.0.153.118 --profile=centos7-x86_64 重启系统后会自动重装系统6 通过dhcp 识别mac地址 自动安装需要关闭vmware dhcp服务 新创建的虚拟机为桥接模式cobbler配置[root@cobbler ~]# egrep -v "^#|^$" /etc/cobbler/settings --- allow_duplicate_hostnames: 0 allow_duplicate_ips: 0 allow_duplicate_macs: 0 allow_dynamic_settings: 0 anamon_enabled: 0 authn_pam_service: "login" auth_token_expiration: 3600 build_reporting_enabled: 0 build_reporting_sender: "" build_reporting_email: [ 'root@localhost' ] build_reporting_smtp_server: "localhost" build_reporting_subject: "" build_reporting_ignorelist: [ "" ] cheetah_import_whitelist: - "random" - "re" - "time" createrepo_flags: "-c cache -s sha" default_kickstart: /var/lib/cobbler/kickstarts/default.ks default_name_servers: [] default_ownership: - "admin" default_password_crypted: "$1$random-p$mzxQ/Sx848sXgvfwJCoZM0" default_template_type: "cheetah" default_virt_bridge: xenbr0 default_virt_file_size: 5 default_virt_ram: 512 default_virt_type: xenpv enable_gpxe: 0 enable_menu: 1 func_auto_setup: 0 func_master: overlord.example.org http_port: 80 kernel_options: ksdevice: bootif lang: ' ' text: ~ kernel_options_s390x: RUNKS: 1 ramdisk_size: 40000 root: /dev/ram0 ro: ~ ip: off vnc: ~ ldap_server: "ldap.example.com" ldap_base_dn: "DC=example,DC=com" ldap_port: 389 ldap_tls: 1 ldap_anonymous_bind: 1 ldap_search_bind_dn: '' ldap_search_passwd: '' ldap_search_prefix: 'uid=' ldap_tls_cacertfile: '' ldap_tls_keyfile: '' ldap_tls_certfile: '' mgmt_classes: [] mgmt_parameters: from_cobbler: 1 puppet_auto_setup: 0 sign_puppet_certs_automatically: 0 puppetca_path: "/usr/bin/puppet" remove_old_puppet_certs_automatically: 0 manage_dhcp: 1 manage_dns: 0 bind_chroot_path: "" bind_master: 127.0.0.1 manage_genders: 0 bind_manage_ipmi: 0 manage_tftpd: 1 manage_rsync: 0 manage_forward_zones: [] manage_reverse_zones: [] next_server: 10.0.153.118 power_management_default_type: 'ipmitool' power_template_dir: "/etc/cobbler/power" pxe_just_once: 1 pxe_template_dir: "/etc/cobbler/pxe" consoles: "/var/consoles" redhat_management_type: "off" redhat_management_server: "xmlrpc.rhn.redhat.com" redhat_management_key: "" redhat_management_permissive: 0 register_new_installs: 0 reposync_flags: "-l -n -d" restart_dns: 1 restart_dhcp: 1 run_install_triggers: 1 scm_track_enabled: 0 scm_track_mode: "git" server: 10.0.153.118 client_use_localhost: 0 client_use_https: 0 snippetsdir: /var/lib/cobbler/snippets template_remote_kickstarts: 0 virt_auto_boot: 1 webdir: /var/www/cobbler xmlrpc_port: 25151 yum_post_install_mirror: 1 yum_distro_priority: 1 yumdownloader_flags: "--resolve" serializer_pretty_json: 0 replicate_rsync_options: "-avzH" replicate_repo_rsync_options: "-avzH" always_write_dhcp_entries: 0 proxy_url_ext: "" proxy_url_int: "" [root@cobbler ~]# egrep -v "^#|^$" /etc/cobbler/dhcp.template ddns-update-style interim; allow booting; allow bootp; ignore client-updates; set vendorclass = option vendor-class-identifier; option pxe-system-type code 93 = unsigned integer 16; subnet 10.0.153.0 netmask 255.255.255.0 { option routers 10.0.153.1; option domain-name-servers 10.0.153.118; option subnet-mask 255.255.255.0; range dynamic-bootp 10.0.153.120 10.0.153.200; default-lease-time 21600; max-lease-time 43200; next-server $next_server; class "pxeclients" { match if substring (option vendor-class-identifier, 0, 9) = "PXEClient"; if option pxe-system-type = 00:02 { filename "ia64/elilo.efi"; } else if option pxe-system-type = 00:06 { filename "grub/grub-x86.efi"; } else if option pxe-system-type = 00:07 { filename "grub/grub-x86_64.efi"; } else if option pxe-system-type = 00:09 { filename "grub/grub-x86_64.efi"; } else { filename "pxelinux.0"; } } } ## group could be subnet if your dhcp tags line up with your subnets ## or really any valid dhcpd.conf construct ... if you only use the ## default dhcp tag in cobbler, the group block can be deleted for a ## flat configuration group { #for mac in $dhcp_tags[$dhcp_tag].keys(): #set iface = $dhcp_tags[$dhcp_tag][$mac] host $iface.name { #if $iface.interface_type == "infiniband": option dhcp-client-identifier = $mac; #else hardware ethernet $mac; #end if #if $iface.ip_address: fixed-address $iface.ip_address; #end if #if $iface.hostname: option host-name "$iface.hostname"; #end if #if $iface.netmask: option subnet-mask $iface.netmask; #end if #if $iface.gateway: option routers $iface.gateway; #end if #if $iface.enable_gpxe: if exists user-class and option user-class = "gPXE" { filename "http://$cobbler_server/cblr/svc/op/gpxe/system/$iface.owner"; } else if exists user-class and option user-class = "iPXE" { filename "http://$cobbler_server/cblr/svc/op/gpxe/system/$iface.owner"; } else { filename "undionly.kpxe"; } #else filename "$iface.filename"; #end if ## Cobbler defaults to $next_server, but some users ## may like to use $iface.system.server for proxied setups next-server $next_server; ## next-server $iface.next_server; } #end for } cobbler docker 打开tftp 服务 dhcp服务systemctl start tftp dhcpd配置好mac地址以下仅供参考 常规部署4.3.1 install cobblervim /etc/yum.conf 打开keepcache缓存改为1 yum -y install httpd dhcp tftp python-ctypes cobbler xinetd cobbler-web 4.3.2 start cobblersystemctl start httpd systemctl enable httpd systemctl start cobblerd.service systemctl enable cobblerd.service 4.3.3 cobbler check[root@cobbler ~]# cobbler check The following are potential configuration items that you may want to fix: 1 : The 'server' field in /etc/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it. 2 : For PXE to be functional, the 'next_server' field in /etc/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network. 3 : change 'disable' to 'no' in /etc/xinetd.d/tftp 4 : Some network boot-loaders are missing from /var/lib/cobbler/loaders. If you only want to handle x86/x86_64 netbooting, you may ensure that you have installed a *recent* version of the syslinux package installed and can ignore this message entirely. Files in this directory, should you want to support all architectures, should include pxelinux.0, menu.c32, elilo.efi, and yaboot. 5 : enable and start rsyncd.service with systemctl 6 : debmirror package is not installed, it will be required to manage debian deployments and repositories 7 : The default password used by the sample templates for newly installed machines (default_password_crypted in /etc/cobbler/settings) is still set to 'cobbler' and should be changed, try: "openssl passwd -1 -salt 'random-phrase-here' 'your-password-here'" to generate new one 8 : fencing tools were not found, and are required to use the (optional) power management features. install cman or fence-agents to use them Restart cobblerd and then run 'cobbler sync' to apply changes. 按照提示一个一个的解决问题:sed -i 's/^server: 127.0.0.1/server: 10.0.153.116/' /etc/cobbler/settings # 修改server的ip地址为本机ip sed -i 's/^next_server: 127.0.0.1/next_server: 10.0.153.116/' /etc/cobbler/settings # TFTP Server 的IP地址 service tftp { socket_type = dgram protocol = udp wait = yes user = root server = /usr/sbin/in.tftpd server_args = -s /var/lib/tftpboot disable = no # 修改为no per_source = 11 cps = 100 2 flags = IPv4 } [root@localhost ~]# cobbler get-loaders # 下载缺失的文件 task started: 2017-10-15_113824_get_loaders task started (id=Download Bootloader Content, time=Sun Oct 15 11:38:24 2017) downloading https://cobbler.github.io/loaders/README to /var/lib/cobbler/loaders/README downloading https://cobbler.github.io/loaders/COPYING.elilo to /var/lib/cobbler/loaders/COPYING.elilo downloading https://cobbler.github.io/loaders/COPYING.yaboot to /var/lib/cobbler/loaders/COPYING.yaboot downloading https://cobbler.github.io/loaders/COPYING.syslinux to /var/lib/cobbler/loaders/COPYING.syslinux downloading https://cobbler.github.io/loaders/elilo-3.8-ia64.efi to /var/lib/cobbler/loaders/elilo-ia64.efi downloading https://cobbler.github.io/loaders/yaboot-1.3.17 to /var/lib/cobbler/loaders/yaboot downloading https://cobbler.github.io/loaders/pxelinux.0-3.86 to /var/lib/cobbler/loaders/pxelinux.0 downloading https://cobbler.github.io/loaders/menu.c32-3.86 to /var/lib/cobbler/loaders/menu.c32 downloading https://cobbler.github.io/loaders/grub-0.97-x86.efi to /var/lib/cobbler/loaders/grub-x86.efi downloading https://cobbler.github.io/loaders/grub-0.97-x86_64.efi to /var/lib/cobbler/loaders/grub-x86_64.efi *** TASK COMPLETE *** 添加rsync到自启动并启动rsync systemctl enable rsyncd systemctl start rsyncd 修改密码为123456 ,salt后面是常用的加盐方式加密 [root@cobbler ~]# openssl passwd -1 -salt '123456' '123456' $1$123456$wOSEtcyiP2N/IfIl15W6Z0 vim /etc/cobbler/settings # 修改settings配置文件中下面位置,把新生成的密码加进去 default_password_crypted: "$1$123456$wOSEtcyiP2N/IfIl15W6Z0 再次执行cobbler check[root@cobbler ~]# cobbler check The following are potential configuration items that you may want to fix: 1 : Some network boot-loaders are missing from /var/lib/cobbler/loaders. If you only want to handle x86/x86_64 netbooting, you may ensure that you have installed a *recent* version of the syslinux package installed and can ignore this message entirely. Files in this directory, should you want to support all architectures, should include pxelinux.0, menu.c32, elilo.efi, and yaboot. 2 : debmirror package is not installed, it will be required to manage debian deployments and repositories 3 : fencing tools were not found, and are required to use the (optional) power management features. install cman or fence-agents to use them Restart cobblerd and then run 'cobbler sync' to apply changes. ks#platform=x86, AMD64, or Intel EM64T #version=DEVEL # Install OS instead of upgrade install # Keyboard layouts keyboard 'us' # Root password rootpw --iscrypted $1$m1pE0DG6$vALBphGGynqvUzfJaWZ6U1 # Use network installation url --url="$tree" # System language lang en_US # Firewall configuration firewall --disabled # System authorization information auth --useshadow --passalgo=sha512 # Use graphical install graphical firstboot --disable # SELinux configuration selinux --disabled # Network information network --bootproto=dhcp --device=eth0 network --bootproto=dhcp --device=eth1 # Reboot after installation reboot # System timezone timezone Asia/Shanghai # System bootloader configuration bootloader --location=mbr # Clear the Master Boot Record zerombr # Partition clearing information clearpart --all --initlabel # Disk partitioning information part /boot --asprimary --fstype="ext4" --size=200 part / --fstype="ext4" --grow --size=1 %packages @base @core @compat-libraries @debugging @development @gnome-desktop @X Window System %end
2022年02月20日
644 阅读
2 评论
0 点赞
2022-02-17
10 个Linux Awk文本处理经典案例
awk是Linux系统下一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等,应用非常广泛。 基本的命令语法:awk option 'pattern {action}' file 其中pattern表示awk在数据中查找的内容,而action是在找到匹配内容时所执行的一系列命令。花括号用于根据特定的模式对一系列指令进行分组。 awk处理的工作方式与数据库类似,支持对记录和字段处理,这也是grep和sed不能实现的。 在awk中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的某一部分作为记录中的一个字段。用1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0表示整个行。 下面根据工作经验总结了10个实用的awk案例,面试笔试题也经常会出,供朋友们参考学习 1、分析访问日志(Nginx为例)日志格式: '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"' 1.1统计访问IP次数: # awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log 统计访问访问大于100次的IP: # awk '{a[$1]++}END{for(v ina){if(a[v]>100)print v,a[v]}}' access.log 统计访问IP次数并排序取前10: # awk '{a[$1]++}END{for(v in a)print v,a[v]|"sort -k2 -nr |head -10"}' access.log 统计时间段访问最多的IP: # awk'$4>="[02/Jan/2017:00:02:00" &&$4<="[02/Jan/2017:00:03:00"{a[$1]++}END{for(v in a)print v,a[v]}'access.log 统计上一分钟访问量: # date=$(date -d '-1 minute'+%d/%d/%Y:%H:%M) # awk -vdate=$date '$4~date{c++}END{printc}' access.log 统计访问最多的10个页面: # awk '{a[$7]++}END{for(vin a)print v,a[v]|"sort -k1 -nr|head -n10"}' access.log 统计每个URL数量和返回内容总大小: # awk '{a[$7]++;size[$7]+=$10}END{for(v ina)print a[v],v,size[v]}' access.log 统计每个IP访问状态码数量: # awk '{a[$1" "$9]++}END{for(v ina)print v,a[v]}' access.log 统计访问IP是404状态次数: # awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log 2、两个文件差异对比文件内容: # seq 1 5 > a # seq 3 7 > b 找出b文件在a文件相同记录 2.1方法1# awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' a b 3 4 5 # awk 'FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}' a b b 3 b 4 b 5 # awk 'FNR==NR{a[$0]}NR>FNR{if($0 ina)print $0}' a b 3 4 5 # awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b # a[$0]是通过b文件每行获取值,如果是1说明有 # awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b 3 4 5 2.2 方法2# awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b 3 4 5 2.3方法3# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b 3 4 5 3.找出b文件在a文件不同记录3.1方法1# awk 'FNR==NR{a[$0];next}!($0 in a)' a b 6 7 # awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b # awk'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b 6 7 3.2 方法2# awk'FILENAME=="a"{a[$0]=1}FILENAME=="b" && a[$0]!=1' a b 3.3 方法3# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b 4、合并两个文件文件内容: # cat a zhangsan 20 lisi 23 wangwu 29 # cat b zhangsan man lisi woman wangwu man 将a文件合并到b文件 4.1 方法1:# awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' a b zhangsan 20 man lisi 23 woman wangwu 29 man 4.2 方法2# awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' a b zhangsan 20 man lisi 23 woman wangwu 29 man 将a文件相同IP的服务名合并: # cat a 192.168.1.1: httpd 192.168.1.1: tomcat 192.168.1.2: httpd 192.168.1.2: postfix 192.168.1.3: mysqld 192.168.1.4: httpd # awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a 192.168.1.4: httpd 192.168.1.1: httpd tomcat 192.168.1.2: httpd postfix 192.168.1.3: mysqld 解读: 数组a存储是$1=a[$1] $2,第一个a[$1]是以第一个字段为下标,值是a[$1] $2,也就是$1=a[$1] $2,值的a[$1]是用第一个字段为下标获取对应的值,但第一次数组a还没有元素,那么a[$1]是空值,此时数组存储是192.168.1.1=httpd,再遇到192.168.1.1时,a[$1]通过第一字段下标获得上次数组的httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标192.168.1.1的新值。此时数组存储是192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值 5、将第一列合并到一行# cat file 1 2 3 4 5 6 7 8 9 # awk '{for(i=1;i<=NF;i++)a[i]=a[i]$i" "}END{for(vin a)print a[v]}' file 1 4 7 2 5 8 3 6 9 解读: for循环是遍历每行的字段,NF等于3,循环3次。 读取第一行时: 第一个字段:a[1]=a[1]1" " 值a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此a[1]=1 。 第二个字段:a[2]=a[2]2" " 值a[2]数组a已经定义,但没有2这个下标,也获取不到对应的值,为空,因此a[2]=2 。 第三个字段:a[3]=a[3]3" " 值a[2]与上面一样,为空,a[3]=3 。 读取第二行时: 第一个字段:a[1]=a[1]4" " 值a[2]获取数组a的2为下标对应的值,上面已经有这个下标了,对应的值是1,因此a[1]=1 4 第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5 第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6 读取第三行时处理方式同上,数组最后还是三个下标,分别是1=1 4 7,2=2 5 8,3=36 9。最后for循环输出所有下标值。 6、字符串拆分字符串拆分 6.1 方法1# echo "hello" |awk -F '''{for(i=1;i<=NF;i++)print $i}' h e l l o 6.2 方法2# echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}' l o h e l 7、统计出现的次数统计字符串中每个字母出现的次数: # echo "a.b.c,c.d.e" |awk -F'[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}' a 1 b 1 c 2 d 1 e 1 8、费用统计得出每个员工出差总费用及次数: # cat a zhangsan 8000 1 zhangsan 5000 1 lisi 1000 1 lisi 2000 1 wangwu 1500 1 zhaoliu 6000 1 zhaoliu 2000 1 zhaoliu 3000 1 # awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print v,cost[v],number[v]}' a zhangsan 5000 1 lisi 3000 2 wangwu 1500 1 zhaoliu 11000 3 9、获取某列数字最大数# cat a a b 1 c d 2 e f 3 g h 3 i j 2 获取第三字段最大值: # awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' a 3 打印第三字段最大行: # awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}'a g h 3 e f 3 10、去除文本第一行和最后一行# seq 5 |awk'NR>2{print s}{s=$0}' 2 3 4 解读: 读取第一行,NR=1,不执行print s,s=1 读取第二行,NR=2,不执行print s,s=2 (大于为真) 读取第三行,NR=3,执行print s,此时s是上一次p赋值内容2,s=3 最后一行,执行print s,打印倒数第二行,s=最后一行 11、获取Nginx upstream块内后端IP和端口# cat a upstream example-servers1 { server 127.0.0.1:80 weight=1 max_fails=2fail_timeout=30s; } upstream example-servers2 { server 127.0.0.1:80 weight=1 max_fails=2fail_timeout=30s; server 127.0.0.1:82 backup; } # awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' a 127.0.0.1:80 # awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' a # awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' a 127.0.0.1:80 解读: 读取第一行,i初始值为0,0>1为假,不执行print s,x=example-servers1,i=1 读取第二行,i=1,1>1为假,不执行prints,s=127.0.0.1:80,i=2 读取第三行,i=2,2>1为真,执行prints,此时s是上一次s赋值内容127.0.0.1:80,i=3 最后一行,执行print s,打印倒数第二行,s=最后一行。 这种方式与上面一样,只是用i++作为计数器
2022年02月17日
376 阅读
0 评论
3 点赞
2022-02-15
新款螺旋帽子编织#夏凉帽#合股线夏凉帽编织
隐藏内容,请前往内页查看详情{bilibili bvid="BV1Xf4y177mr" page=""/}样例
2022年02月15日
402 阅读
0 评论
0 点赞
1
...
9
10