2018年4月19日星期四

golang slice函数值传递的“隐患”

    直接上现场,看看你能不能一眼识破“隐患”根因!

    下文函数基本功能是针对一个分片silce的数据,设置将前toCount个数据更新为100以内随机数,如果toCount大于当前的slice总长度,则扩大到toCount大小并填满100以内的随机数。
    代码如下:
package main
import ( "fmt" "math/rand")
func getRand() int {
return rand.Int() % 100
}
func fillInRandom1(ori []int, toCount int) {
fillInRandom2(&ori, toCount)}
func fillInRandom2(ori *[]int, toCount int) {
if toCount <= len(*ori) {
for i:=0; i< toCount; i++ {
(*ori)[i] = getRand() }
} else {
for i:=0; i< len(*ori); i++ {
(*ori)[i] = getRand() }
for i:=0; i< toCount - len(*ori); i++ {
*ori = append(*ori, getRand()) }
return }
}
func printSlice(s []int) {
for _, i := range s {
fmt.Printf("%v ", i) }
fmt.Println()}
func main() {
fmt.Println("slice参数值传递效果:") nums := make([]int, 4, 5) fillInRandom1(nums, 2) printSlice(nums) fillInRandom1(nums, 4) printSlice(nums) fillInRandom1(nums, 8) printSlice(nums)
fmt.Println("slice指针传递效果:") nums = make([]int, 4, 5) fillInRandom2(&nums, 2) printSlice(nums) fillInRandom2(&nums, 4) printSlice(nums) fillInRandom2(&nums, 8) printSlice(nums)
}
输出如下:

golang struct中内部类型embedding Type用法

    Golang通过内部类型或者嵌入类型embedding的方式来实现一种代码的复用以及重定义,类似与Java中的继承和重载。
     这种内部类有时候也被称为匿名属性(anonymous field,形式上,与其他属性相比,就是没有名字~),这里统一用“内部类型”来指代。
    内部类的方式,可以让外部类直接访问和使用内部类中的属性和方法(只要这些语法上是允许访问的!)
    我们结合代码来看看内部类型的用法。

1.同package下内部类

package main
import (
    "fmt"
)
type user struct {
    name string
    email string
}
func (u *user) notify() {
    fmt.Printf("Send email to %s@<%s>\n", u.name, u.email)
}
type admin struct {
    user //设置内部类型
    level string //什么外部类的属性
}
func main() {
    u1 := admin{
         user: user{
            "haha",
            "haha@evertrain.cn",
         },
         //name: "lala",
         level: "high",
    }
    u1.user.notify()  //既可以通过内部类型调用方法。内部类的属性和方法始终可以通过内部类名进行访问
    u1.notify()       //也可以直接通过外部类的对象访问
    fmt.Println(u1.user.name)
    fmt.Println(u1.name)
}
输出是
Send email to haha@<haha@evertrain.cn>
Send email to haha@<haha@evertrain.cn>
haha
haha

2.覆盖内部类的属性和方法

如果在外部类中重新定义name和notify方法,如下:
...同上部分省略...
type admin struct {
    user //设置内部类型
    name string  //外部类新定义
    level string //什么外部类的属性
}
func (a *admin) notify() {
    fmt.Printf("Send admin email to %s@<%s>\n", a.name, a.email)
}
func main() {
    u1 := admin{
         user: user{
            "haha",
            "haha@evertrain.cn",
         },
         name: "lala",
         level: "high",
    }
    u1.user.notify()  //既可以通过内部类型调用方法。内部类的属性和方法始终可以通过内部类名进行访问
    u1.notify()       //也可以直接通过外部类的对象访问
    fmt.Println(u1.user.name)
    fmt.Println(u1.name)
}
则输出
Send email to haha@<haha@evertrain.cn>
Send admin email to lala@<haha@evertrain.cn>
haha
lala

3.使用跨package的内部类

    在考虑跨package的情况,将user定义放到package user中去,由于小写开头的user变成了user包的私有类,外部包无法访问,将其变成公开类User,其余不变(属性和方法仍然为私有)。
admin定义如下:
import (
    "fmt"
    u "./user"
)
type admin struct {
    u.User //设置内部类型
    name string  //外部类新定义
    level string //什么外部类的属性
}
func (a *admin) notify() {
    fmt.Printf("Send admin email to %s\n", a.name)
}
func main() {
    u1 := admin{
         User: u.User{
            "haha",
            "haha@evertrain.cn",
         },
         name: "lala",
         level: "high",
    }
    //u1.User.notify()  //无法访问其私有方法
    u1.notify()       //也可以直接通过外部类的对象访问
    //fmt.Println(u1.User.name) //无法访问其私有属性
    fmt.Println(u1.name)
}
其中u1创建的时候就报错了,类似如下:
implicit assignment of unexported field 'name' in user.User literal
无法通过这种形式来指定赋值私有变量。为User类新加一个公开的NewUser方法
func NewUser(name string, email string) *User {
    return &User{
        name,
        email,
    }
}
然后admin的创建改成:
func main() {
    u1 := admin{
         User: *u.NewUser("haha", "haha@evertrain.cn"),
         name: "lala",
         level: "high",
    }
    //u1.User.notify()  //无法访问其私有方法
    u1.notify()       //也可以直接通过外部类的对象访问
    //fmt.Println(u1.User.name) //无法访问其私有属性
    fmt.Println(u1.name)
}
则输出变成了
Send admin email to lala
lala
可以看到,跨package后私有属性、方法都不再可见,需要将应该公开的属性和方法改成公开才能达到复用的效果。

4.指针Point形式的内部类型与值Value形式的内部类型的差别

admin还可以用这样的方式定义:
type admin struct {
    *u.User //设置内部类型,指针类型
    name string  //外部类新定义
    level string //什么外部类的属性
}
则稍作修改后,返回同样的结果
u1 := admin{
         User: u.NewUser("haha", "haha@evertrain.cn"), //保持指针
         name: "lala",
         level: "high",
    }
指针类型的内部类型,保留了指针Pointer的特点,初始化不指定的话,就是nil,所以必须明确的指定。
Pointer形式下,外部类访问内部类的属性和方法的方式与Value一致。不同的是,Pointer形式下,是根据指针操作,操作的是对象本身。而如果是Value形式的,例如赋值就是变成一份拷贝,再更新属性和原对象就已经无关了。示例如下:
package main
import (
    "fmt"
)
type user struct {
    name string
    email string
}
func (u *user) setName(n string) {
    u.name = n
}
type admin struct {
    user //Value形式内部类
    level string
}
type operator struct {
    *user  //Pointer形式内部类
    level string
}
func main() {
    u1 := user{"haha1","haha1@evertrain.cn"}
    u2 := user{"haha2","haha2@evertrain.cn"}
    a1 := admin{
         user: u1,
         level: "high",
    }
    o1 := operator{
        user: &u2,
        level: "middle",
    }
    a1.setName("new_haha1")
    o1.setName("new_haha2")
    fmt.Println(a1.user.name)
    fmt.Println(a1.name)
    fmt.Println(u1.name) //此处无变化,因为a1中的user是u1的一份拷贝,其上的更新不会作用到u1
    fmt.Println(o1.user.name)
    fmt.Println(o1.name)
    fmt.Println(u2.name)
}
输出:
new_haha1
new_haha1
haha1
new_haha2
new_haha2
new_haha2

参考


  • https://groups.google.com/forum/#!topic/golang-nuts/ctRUq62cgME
  • 《Manning Go in Action》

2018年4月10日星期二

分区与文件系统问题两例


MySQL服务器划盘经常会遇到各种各样的问题,究其原因还是重装主机的时候,盘并没有进行完整彻底的格式化,记录下今天遇到的两例问题.

1)分区删除

为了解决装机时偶尔有分区信息残留导致后续初始化失败,脚本中加入了如下代码进行前置操作,无脑清理:
fdisk ${device} <<EOF    d
    w
EOF
今天某台机器处理时开始报这样的问题:
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): Selected partition 1
Partition 1 is deleted
Command (m for help): The partition table has been altered!
Calling ioctl() to re-read partition table.
WARNING: Re-reading the partition table failed with error 22: Invalid argument.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
可以看出意思是分区表更新后需要重读,内核信息没有更新。同时也给出了解决方案(很棒!):重启或者运行partprobe或者kpartx
了解了一下partprobe命令,目标很明确,就是用来通知OS分区表了变化。
partprobe ${device} 即能解决设备分区表更新后的信息同步问题。

2)创建文件系统报错

mkfs.ext4 -m 1 /dev/vgdata/volume2
mke2fs 1.43.5 (04-Aug-2017)Discarding device blocks: done                            Creating filesystem with 937500672 4k blocks and 234381312 inodes
Filesystem UUID: 0b7bf05c-92cf-42ea-9028-a053753ea1ed
Superblock backups stored on blocks:         32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,         4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,         102400000, 214990848, 512000000, 550731776, 644972544
Allocating group tables: done                            Writing inode tables: done                            ext2fs_update_bb_inode: Cannot iterate data blocks of an inode containing inline data while setting bad block inode
最后的报错也导致无法进行挂载。报错信息看,应该是块损坏之类的报错,因为是服务器初始化,理论上因为完全是空白盘才对。(/dev/vgdata/volume2是划出一个LV作为MySQL的日志盘。

解决方案:使用ddlv上的头部残留信息归0
dd if=/dev/zero of=/dev/vgdata/volume1 bs=1M count=10
然后继续 mkfs.ext4 -m 1 /dev/vgdata/volume2,成功。

参考:




2018年4月8日星期日

OOM的触发条件与配置

    OOM(Out of Memory)指Linux在无可用内存时的一个最后策略。当系统中可用内存过少时,OOM killer会选择kill某些进程,来释放这些进程所占用的内存。

1.何时触发oom killer

现代内核通常在分配内存时,允许申请的内存量超过实际可分配的free内存,这种技术称为Overcommit,可以认为是一种内存超卖的策略。开启了Overcommit,其实是基于一个普遍的规律——大部分应用并不会将其申请的内存全部用满——来尽量提升内存的利用率,可以允许系统分配出的内存总和超过系统能提供之和(物理内存+Swap空间),但是这种做法也导致OOM的风险。