learning, progress, future.

skydh


  • 首页

  • 归档

go学习2

发表于 2020-07-07

基础包介绍

  • strings

strings主要针对utf-8 编码,实现一些简单函数,字符串的很多函数基本方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/是否包含指定的字符串中任意一个字符 有一个出现过 就返回true
fmt.Println(strings.ContainsAny(s1,"glass"))

//返回指定字符出现的次数
fmt.Println(strings.Count(s1,"g"))

//文本的开头
fmt.Println(strings.HasPrefix(s1,"ok"))
//文本的结尾
fmt.Println(strings.HasSuffix(s1,".txt"))

//查找指定字符在字符串中存在的位置 如果不存在返回-1
fmt.Println(strings.Index(s1,"g"))
//查找字符中任意一个字符出现在字符串中的位置
fmt.Println(strings.IndexAny(s1,"s"))
//查找指定字符出现在字符串中最后一个的位置
fmt.Println(strings.LastIndex(s1,"s"))

//字符串的拼接
s2:=[]string{"123n","abc","ss"}
s3:=strings.Join(s2,"_")
fmt.Println(s3)// 123n_abc_ss

//字符串的切割
s4:=strings.Split(s3,"_")
fmt.Println(s4)// 返回切片[]string{"123n","abc","ss"}

//字符串的替换
s5 := "okoletsgo"
s6 := strings.Replace(s5, "o", "*", 1)
fmt.Println(s6)//*koletsgo
//TODO 1 只替换1次, -1 全部替换

//字符串的截取
//str[start:end]包含start 不包含end
  • strconv :主要用于字符串和基本类型的数据类型的转换

  • time: time包操作的都是时间,时间的单位都包括年,月,日,时,分,秒,毫秒,微妙,纳秒,皮秒

    1
    2
    3
    4
    5
    6
    7
    t := time.Now()
    fmt.Println(t) //2020-03-31 21:26:01.7307507 +0800 CST m=+0.001999001
    //获取的时间后面的信息是时区

    //上面的时间看起来不是很方便 于是需要格式化时间
    s := t.Format("2006年1月3日 15:04:05")
    fmt.Println(s)

需要注意的室Go语言中时间的格式化,需要指定格式化时间的模板, 不管年月日的类型格式怎么写,但是具体的数值必须写成2006-01-02 15:04:05, 如果不是这个日期就不能够格式化,这个时间也是为了纪念Go语言诞生的时间。

包管理

早期的Golang被很多开发者所诟病的一个问题就是依赖包的管理。Golang 1.5 release版本的发布之前,只能通过设置多个GOPATH的方式来解决这个问题,例如:我两个工程都依赖了Beego,但A工程依赖的是Beego1.1,B工程依赖的是Beego1.7,我必须设置两个GOPATH来区分,并且在切换工程的时候GOPATH也得切换,无比痛苦。终于终于在Golang 1.5 release 开始支持除了GOROOT和GOPATH之外的依赖查询,那就是vender

但是现在模块管理,包管理一般都是使用mod,go官方推荐的。

环境变量中可以增加GOPROXY=https://goproxy.io 这样没有梯子的情况下可以正确的加载相应的包文件。

函数

我们自定义函数的时候,需要注意按照规则定义必须满足以下格式,函数的名字可以由字母和数字组成,但是不能是数字开头,函数的首字母区分大小写,如果是大写的表示公共的函数,其他包内可以调用到,相当于其他语言中的public 前提是在别的包中引入了当前包。如果是小写的,表示私有的函数,仅能够在本包中调用,相当于其他语言中的private。

函数可以定义多个返回值,并且返回值类型,返回值数量都必须是一一对应的,return是将结果返回到函数的调用处,结束函数的执行。 _ 空白标识符,舍弃返回的数据。

1
2
3
4
5
6
7
8
9
func main() {
s1:=[2]string{}
s3,_:=join(s1)
fmt.Println(s3)
}
func join(ele [2]string) ([2]string,int) {

return ele,12
}

函数也可以声明为一个变量。

1
2
c:=join
c(s1)

匿名函数

1
2
3
4
5
6
7
8
9
10
res:=func(a int,b int) int{
fmt.Println(a+b)
return a+b
}(12,13)
fmt.Println(res)

a:=func(a, b int) int {
return a + b
}
fmt.Println(a)

加了后面的(),才算调用,不然不算调用。

  • 匿名函数可以作为另一个函数的参数
  • 匿名函数可以作为另一个函数的返回值
  • 根据go语言的数据类型的特点,函数也是一种类型,所以可以将一个函数作为另一个函数的参数传递func1()和func2()是两个函数,将func1函数作为func2这个函数的参数,func2函数就叫做高阶函数,因为他接收了一个函数作为参数。所以func1叫做回调函数,他作为另一个函数的参数。

defer

defer 表示延时推迟的意思,在go语言中用来延时一个函数或者方法的执行。如果一个函数或者方法添加了defer关键字,表示则暂时不执行,等到主函数的所有方法都执行完后才开始执行。

当多个函数被defer的时候他们被添加到一个堆栈中,并且根据先进后出的原则执行。 即 Last In First Out(LIFO)

defer函数调用时候,参数已经传递了,只不过代码暂时不执行而已。等待主函数执行结束后,才会去执行。

闭包

一个外层函数当中有内层函数,这个内层函数会操作外层函数的局部变量。并且,外层函数把内层函数作为返回值,则这里内层函数和外层函数的局部变量,统称为 闭包结构 。 这个外层函数的局部变量的生命周期会随着发生改变,原本当一个函数执行结束后,函数内部的局部变量也会随之销毁。但是闭包结构内的局部变量不会随着外层函数的结束而销毁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func main() {
res := closure()
fmt.Println(res) //0x49a880 返回内层函数函数体地址
r1 := res() //执行closure函数返回的匿名函数
fmt.Println(r1) //1
r2 := res()
fmt.Println(r2) //2
//普通的函数应该返回1,而这里存在闭包结构所以返回2 。
//一个外层函数当中有内层函数,这个内层函数会操作外层函数的局部变量,并且外层函数把内层函数作为返回值,则这里内层函数和外层函数的局部变量,统称为闭包结构。这个外层函数的局部变量的生命周期会发生改变,不会随着外层函数的结束而销毁。
//所以上面打印的r2 是累计到2 。

res2 := closure() //再次调用则产生新的闭包结构 局部变量则新定义的
fmt.Println(res2)
r3 := res2()//1
fmt.Println(r3)
r4 := res()
fmt.Println(r4)//3
fmt.Println(res())//4
}

//定义一个闭包结构的函数 返回一个匿名函数
func closure() func() int { //外层函数
//定义局部变量a
a := 0 //外层函数的局部变量
//定义内层匿名函数 并直接返回
return func() int { //内层函数
a++ //在匿名函数中将变量自增。内层函数用到了外层函数的局部变量,此变量不会随着外层函数的结束销毁
return a
}
}

指针

指针是存储另一个变量的内存地址的变量。 例如: 变量B的值为100, 地址为0x1122。变量A的值为变量B的地址0x1122,那么A就拥有了变量B的地址,则A就成为指针。Go语言中通过&获取变量的地址。通过*获取指针所对应的变量存储的数值。

1
2
3
4
5
6
7
8
a := 2
var i *int //声明一个int类型的指针
fmt.Println(&a) //0xc00000c1c8
i = &a //将a的地址取出来放到i里面
fmt.Println(&i) //0xc000006028
var a2 **int //声明一个指针类型的指针
a2 = &i //再把i的地址放进a2里面
fmt.Println(a2) //获取的是a2所对应的数值0xc000006028也就是i的地址

指针的指针,无线套娃。

1
2
3
4
5
6
7
8
9
//创建一个普通的数组
arr := [3]int{1, 2, 3}
fmt.Println(arr)

//创建一个指针 用来存储数组的地址 即:数组指针
var p *[3]int
p = &arr //将数组arr的地址,存储到数组指针p上。
fmt.Println(p[0]) //数组的指针 &[1 2 3] 后面跟数组的内容
fmt.Println((*p)[0]) //数组的指针 &[1 2 3] 后面跟数组的内容

直接传递数组是传递的数据,传递指针,才是传递真实数据

go学习1

发表于 2020-06-29

安装

https://golang.google.cn/dl/ 官网直接安装go。

打开cmd 输入 go version

然后安装ide,这边使用的是goland。

安装包和破解文件都在里面:直接安装,然后将jetbrains-agent-latest.zip拖到goland窗口,重启即可。

https://pan.baidu.com/s/1eyKoegOhHppqTxtNbHunpQ

w1m2

特性

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
fmt.Println("ok")

}

入门代码:输出一个ok

go有如下规则:

  • 每个源文件都有一个专门的package
  • 程序的入口依旧是main函数
  • main函数必须在main 这个package下。
  • 文件名称和package没有直接联系
  • 可以 go run这个go文件,也可以go build这个模块后,生成对应的可执行的机器文件,直接执行。

变量

Go语言是静态强类型语言,所以变量是有明确类型的。

1
2
var age string     //声明 未赋值默认为0
age = "18"

可以声明其类型,然后赋值

1
2
var name = "王钢蛋" 
var age = 10

也可以直接赋值。

Go语言支持根据数据推导数据类型的方法。所以在定义的时候可以不用写数据类型,直接根据你所初始化的值,来推导出定义的数据类型。

  • 变量在使用前必须先声明。
  • 变量名不能重复定义。
  • 如果是简短定义方式,左边至少有一个是新的变量。
  • 如果定义了变量,必须得使用,否则编译无法通过。
  • 全局变量可以不使用也能编译通过,定义的全局变量和局部变量名称如果相同,则会优先使用局部变量。
  • 简短定义方式不能定义全局变量,也就是不能声明在函数外部

常量

1
const name = 111

由var变成了const

  • 常量数值不能修改。
  • 常量定义后可以不使用。
  • 常量定义不能使用简短定义方式。
  • 常量中使用的数据类型只能是 整型、布尔、浮点、复数类型、字符串类型。

iota是常量里面的计数器,初始值默认值是0,可以被编译器自动修改,每定义一组常量时,iota逐行自增1。

1
2
3
4
5
6
7
8
9
10
const(
a=1+iota
b=3+iota
c
d
e="asd"
f
g=iota

)

输出

1
2
3
4
5
6
7
1
4
5
6
asd
asd
6

可以看到iota从0开始,每多一个变量,数字加一。每个常量都有这个属性。可以用其做一些骚操作。

如果下面没有声明常量的值,那么就和上面常量保持一致,如果第一行没有声明,那么报错。

数据类型

数据类型是一门高级语言的基础,Go属于又属于强类型静态编译语言。Go语言拥有两大数据类型,基本数据类型和复合数据类型。

整型

数据类型 说明 取值范围
有符号整数
int8 有符号 8位整数 -128到 127
int16 有符号 16位整数 -32768 到32767
int32 有符号 32位整数 -2147483648到2147483647
int64 有符号 64位整数 -9223372036854775808到9223372036854775807
无符号整数
uint8 无符号8位整数 0到255
uint16 无符号16位整数 0到65535
uint32 无符号32位整数 0到4294967295
uint64 无符号64位整数 0到18446744073709551615

在Go语言中 byte与uint8 是一样的,rune与int32是一样的,代表同一种数据类型。但是int和int64 不是同一种类型。

int不等于上面任意一个类型。

Go语言中int类型的大小与具体的平台有关系,一般来说,int在32位系统中是4字节,在64位系统中是8字节,使用简短定义自动推导类型初始化一个整数,默认为int类型。

字符串:

字符串的概念就是多个byte的集合,一个字符序列用双引号””,或者`` (esc下面的键) 表示。

浮点数

Go语言有两种精度的浮点数 float32 和 float64。浮点类型的数据取值范围可以从很小或者很巨大。

单精度 浮点类型 取值范围
float32 负数时 -3.402823E38 到 -1.401298E-45
float32 正数时 1.401298E-45 到 3.402823E38
双精度 浮点类型 取值范围
float64 -1.79E+308 到 +1.79E+308

1.79E-308 是 1.79 乘以 10的负308次方。 1.79E+308 是 1.79 乘以 10的308次方。

单精度双精度两者区别

在内存中占有的字节数不同

  • 单精度浮点数在机内占4个字节。
  • 双精度浮点数在机内占8个字节。

有效数字位数不同

  • 单精度浮点数 有效数字7位。
  • 双精度浮点数 有效数字16位。

对于float型数据,其长度是4个字节,右边23位用来表示小数点后面的数字,中间8位用来表示e,左边一位用来表示正负。

对于double型数据,其长度是8个字节,右边52位用来表示小数点后面的数字.中间11位表示e,左边一位用来表示正负。

数据类型转换

go 语言是静态语言,要求,定义、赋值、 运算、类型一致才能进行操作。所以要进行操作的时候必须保证数据类型一直。需要注意的是,只有兼容的数据类型才能够转换。 强制类型转换的语法 Type(value)

1
2
3
4
5
6
var a1 int=10
var a2 int32
a2= int32(10 + a1)
a1= int(10 + a2)
fmt.Println(a1)
fmt.Println(a2)

运算符

这个和java差不多,没啥区别。主要说几个没玩过的

  • &^ : a&^b 对于b上面的每个数值,如果为0,则去a上的数值,如果为1,则是0(很有趣不知道有什么用)
  • ^ :异或,不解释。
  • <<,>> 左移和右移,包含符号位的移动

赋值运算符

这块go是蛮有意思的了。

运算符 描述
= 把等号右侧的数值 赋给左边的变量
+= 自身加上后面的值 再赋给左边的变量
-= 自身减去后面的值 在赋给左边
/= 自身除后面的值 再赋值给左边
%= 自身与后面的值求余数后 再赋值给左边
<<= 左移后再赋值
>>= 右移后再赋值
&= 按位与后再赋值
!= 按位或 后再赋值
^= 按位异或后再赋值

这块赋值操作,让代码节省了很多,有趣。

流程控制

if模块

这个和java的语法没有区别。

1
2
3
4
5
if a3==a1{
fmt.Println("as")
}else{
fmt.Println("asd")
}

但是go存在特殊的一些写法。条件判断之间再加上一段执行语句,执行的结果再用作后面的条件判断。例如

1
2
3
4
5
if a4=a3; a3==a1{
fmt.Println("as")
}else{
fmt.Println("asd")
}

试了一下,只能加一个语句哈。

switch模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//switch语法一
switch 变量名{
case 数值1: 分支1
case 数值2: 分支2
case 数值3: 分支3
...
default:
最后一个分支
}


//语法二 省略变量 相当于作用在了bool 类型上

switch{
case true: 分支1
case false: 分支2
}

//语法三 case 后可以跟随多个数值, 满足其中一个就执行
switch num{
case 1,2,3:
fmt.Println("num符合其中某一个 执行代码")
case 4,5,6:
fmt.Println("执行此代码")
}

//语法四 可以添加初始化变量 作用于switch内部

switch name:="huangrong"; name{
case "guojing":
fmt.Println("shideguojing")
case "huangrong":
fmt.Println("shidehuangrong")
}

看看例子

1
2
3
4
5
6
7
var sd="asds"
switch sd {
case"1","asds":
fmt.Println("1sdf")
case" 2":
fmt.Println("12")
}

在switch 语句中,默认每个case后自带一个break,表示到此结束 不向下执行了,跳出整个switch。fallthrough 表示强制执行后面的没有执行的下一个case代码。

for

语法: for init; condition;post{ }

  • init 初始化 只执行一次

  • condition bool类型 执行条件 如果满足继续执行后面的循环体 如果不满足 则终止执行

  • {} 循环体

  • post 表达式 将在循环体执行结束之后执行
1
2
3
for i:=0;i<3;i++{
fmt.Println(i)
}

break默认是结束当前循环,如果当前多重循环,那么可以如下:

1
2
3
4
5
6
7
8
9
10
flag:
for i := 1; i < 10; i++ {
for j := 1; j < i; j++ {
fmt.Println(i, j)
if j == 5 {
break flag
}
}
fmt.Println(i)
}

给循环体加个标签,然后break掉。

goto模块

1
2
3
4
5
6
Test:
var as int =1
Test2:
fmt.Println(as)
goto Test2
goto Test

给语句加个标签,然后goto到那里直接执行。

复合数据类型

数组声明的语法格式为: var 数组名称 [长度]数据类型

  • 数组只能用来存储一组相同类型的数据结构。
  • 数组需要通过下标来访问,并且有长度和容量 。
1
2
3
4
5
6
var sd [5] int
sd[1]=11
arr:=[5]int{1,2}
arr1:=[5]int{1:2,3:5}
arr3:=[...]int{1,2}
arr4:=[...]int{1:2,3:5}

数组不足补0处理。

数组的遍历,除了上面说的for,还可以

1
2
3
for index,value:=range arr1{
fmt.Println(index,value)
}

slice

1
2
3
4
var slice [] int
slice =make([]int,3,8)
slice[0]=1
slice=append(slice,1,2,3)

通过make函数定义slice,然后通过append扩容进去。前面的3是长度,后面的8是容量,扩容时基于容量计算的。容量就是真实的数组长度,而长度则是当前slice元素数目。

除了用make创建slice,还可以通过new创建。但是不一样,看看效果。

1
2
3
4
5
6
7
8
9
slice1 := new([]int)
fmt.Println(slice1) //输出的是一个地址 &[]

//使用make创建切片
slice2 := make([]int, 5)
fmt.Println(slice2)//输出初始值都为0的数组, [0 0 0 0 0]

fmt.Println(slice1[0])//结果出错 slice1是一个空指针 invalid operation: slice1[0] (type *[]int does not support indexing)
fmt.Println(slice2[0])//结果为 0 因为已经初始化了

如果实现了扩容, 地址就会发生改变成新的地址,旧的则自动销毁。

删除数据

1
2
3
4
5
6
7
8
9
slice:=[]int{1,2,3,4,5,6}
slice1:=[]int{1,2,3,4,5,6}
//i := 2 // 要删除的下标为2
//slice = append(slice[:i], slice[i+1:]...) // 删除中间1个元素
//fmt.Println(slice) //结果[1 2 4]
slice=slice[:3]
slice1=slice1[3:]
fmt.Println(slice) //结果[1 2 4]
fmt.Println(slice1) //结果[1 2 4]

:n,表示获取这个slice前面n个元素,n:,表示获取获取第三个元素后面的其余元素。

1
2
3
i := 2      // 要删除的下标为2
slice = append(slice[:i], slice[i+1:]...) // 删除中间1个元素
fmt.Println(slice) //结果[1 2 4]

相当于删除了第i个元素,其逻辑是,获取前面2个元素,然后再把第三个元素后面的元素添加进去。

总结一下

  • 每一个切片都引用了一个底层数组。
  • 切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。
  • 当切片添加数据时候,如果没有超过容量,直接进行添加,如果超出容量自动扩容成倍增长。
  • 切片一旦扩容,指向一个新的底层数组内存地址也就随之改变。
  • 声明了长度的就是数组,没有声明就是slice,快速定义模式的,如果是make声明长度的则是slice
  • 数组,slice都是声明的变量,实际数据存储在内存。

map

和java里面的map一样,创建和删除语法如下:

1
2
3
4
5
6
7
8
9
10
11
var map1 map[string]string
if map1==nil{
map1= map[string]string{}
}
var map2=make(map[int]string)
map2[13]="sad"
val :=map2[13]
fmt.Print(val)
delete(map2,12)
map3:=map[int]string{12:"asd"}
fmt.Print(map3[12])

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
map1:=make(map[int]string)
map1[1]="asd"
map1[2]="asd2"
map1[3]="asd3"
for key,value:=range map1{
fmt.Println(key,value)
}
map1 := make(map[string]string)
map1["name"] = "1name"
map1["age"] = "1age"
map2 := make(map[string]string)
map2["name"] = "2name"
map2["age"] = "2age"
s1:=make([]map[string]string,0,2)
s1=append(s1,map1,map2)
for key,value:=range s1{
fmt.Println(key,value)
}

map在Go语言并发编程中,如果仅用于读取数据时候是安全的,但是在读写操作的时候是不安全的,在Go语言1.9版本后提供了一种并发安全的,sync.Map是Go语言提供的内置map,不同于基本的map,他提供了特有的方法,不需要初始化操作实现增删改查的操作。

1
2
3
var syncmap sync.Map
syncmap.Store("as",12)
fmt.Println(syncmap.Load("as"))

ElasticSearch 增删改查原理解析

发表于 2020-06-29

参考文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/distributed-search.html

es写数据原理

  • 客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node (协调节点)。
  • coordinating node 对 document 进行路由,将请求转发给对应的 node(有 primary shard)。
  • 实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node 。
  • coordinating node 如果发现 primary node 和所有 replica node 都搞定之后,就返回响应结果给客户端。

es 读数据过程

可以通过 doc id 来查询,会根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个 shard 去查询。

  • 客户端发送请求到任意一个 node,成为 coordinate node 。
  • coordinate node 对 doc id 进行哈希路由,将请求转发到对应的 node,此时会使用 round-robin 随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡。
  • 接收请求的 node 返回 document 给 coordinate node 。
  • coordinate node 返回 document 给客户端。

es 搜索数据过程

你根据 java 关键词来搜索,将包含 java 的 document 给搜索出来。

  • 客户端发送请求到一个 coordinate node 。
  • 协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard ,都可以。
  • query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
  • fetch phase:接着由协调节点根据 doc id 去各个节点上拉取实际的 document 数据,最终返回给客户端。

ElasticSearch 聚合查询原理

发表于 2020-06-29
原理分析

参考:https://www.cnblogs.com/huangying2124/p/12717369.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_limiting_memory_usage.html

聚合使用一个叫 doc values 的数据结构(在 Doc Values 介绍 里简单介绍)。 Doc values 可以使聚合更快、更高效并且内存友好,所以理解它的工作方式十分有益。

Doc values 的存在是因为倒排索引只对某些操作是高效的。 倒排索引的优势 在于查找包含某个项的文档,而对于从另外一个方向的相反操作并不高效,即:确定哪些项是否存在单个文档里,聚合需要这种次级的访问模式。

在 Elasticsearch 中,Doc Values 就是一种列式存储结构。

Doc Values 是在索引时与 倒排索引 同时生成。也就是说 Doc Values 和 倒排索引 一样,基于 Segement 生成并且是不可变的。同时 Doc Values 和 倒排索引 一样序列化到磁盘,这样对性能和扩展性有很大帮助。

因此我们可以通过禁用某些字段的doc_values来节约磁盘空间。

我们对字符串做聚合时,如何时默认的,那么久会出现很坑的点。demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
POST /agg_analysis/data/_bulk
{ "index": {}}
{ "state" : "New York" }
{ "index": {}}
{ "state" : "New Jersey" }

GET /agg_analysis/data/_search
{
"size" : 0,
"aggs" : {
"states" : {
"terms" : {
"field" : "state"
}
}
}
}

{
...
"aggregations": {
"states": {
"buckets": [
{
"key": "new",
"doc_count": 2
},
{
"key": "york",
"doc_count": 1
},
{
"key": "jersey",
"doc_count": 1
},

]
}
}
}

我们对state聚合,结果出现了很多奇怪的桶,这个就是对string这个字段每个词都做了桶,为了禁止这个可以直接加keyword,来处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
GET /data1/data1/_search
{
"size" : 0,
"aggs" : {
"states" : {
"terms" : {
"field" : "state.keyword"
}
}
}
}

"aggregations" : {
"states" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "New York",
"doc_count" : 3
},
{
"key" : "New Jersey",
"doc_count" : 1
},
{
"key" : "New Mexico",
"doc_count" : 1
}
]
}

对于常规字段和text字段的keyword,都是直接走doc values的,这个列索引。效率很高,如果需要对这个字段进行桶聚合,那么就要开启fielddata 。

analyzed字符串的字段,字段分词后占用空间很大,正排索引不能很有效的表示多值字符串,所以正排索引不支持此类字段。

fielddata结构与正排索引类似,是另外一份数据,构建和管理100%在内存中,并常驻于JVM内存堆,极易引起OOM问题。

Fielddata 是 延迟 加载。如果你从来没有聚合一个分析字符串,就不会加载 fielddata 到内存中。此外,fielddata 是基于字段加载的, 这意味着只有很活跃地使用字段才会增加 fielddata 的负担。

实际情况是,fielddata 会加载索引中(针对该特定字段的) 所有的 文档,而不管查询的特异性。逻辑是这样:如果查询会访问文档 X、Y 和 Z,那很有可能会在下一个查询中访问其他文档。因此会把所有的文档全部加载进来。

与 doc values 不同,fielddata 结构不会在索引时创建。相反,它是在查询运行时,动态填充。这可能是一个比较复杂的操作,可能需要一些时间。 将所有的信息一次加载,再将其维持在内存中的方式要比反复只加载一个 fielddata 的部分代价要低。

因此很少使用。

由于fielddata 使用的是jvm堆内存,因此这里扩展下elasticSearch内存分配以及jvm内存。

这里参考另外一篇文档ElasticSearch 内存配置及其原因里面详细描述了elasticSearch的分配策略以及原理。

indices.fielddata.cache.size 控制为 fielddata 分配的堆空间大小。 当你发起一个查询,如果这些字符串之前没有被加载过,分析字符串的聚合将会被加载到 fielddata,加载过,直接取用,前面说过了,这个是全局加载。

默认情况下,设置都是 unbounded ,Elasticsearch 永远都不会从 fielddata 中回收数据。

设想我们正在对日志进行索引,每天使用一个新的索引。通常我们只对过去一两天的数据感兴趣,尽管我们会保留老的索引,但我们很少需要查询它们。不过如果采用默认设置,旧索引的 fielddata 永远不会从缓存中回收! fieldata 会保持增长直到 fielddata 发生断熔(请参阅 断路器),这样我们就无法载入更多的 fielddata。

为了防止发生这样的事情,可以通过在 config/elasticsearch.yml 文件中增加配置为 fielddata 设置一个上限:

1
indices.fielddata.cache.size:  20%

有了这个设置,最久未使用(LRU)的 fielddata 会被回收为新数据腾出空间。

ElasticSearch 聚合查询语法

发表于 2020-06-29

参考资料:https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations.html

创建数据

我们先通过模拟的数据来看看有哪些语法。

由于es集群禁止了自动创建索引,我们只能先创建索引。
  • 创建索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
PUT /test?pretty
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mapping": {
"test": {
"properties": {
"price": {
"type": "text"
},
"color": {
"type": "text"
},
"make": {
"type": "text"
},
"sold": {
"type": "text"
}
}
}
}
}
  • 新增数据

    批量插入部分数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
post /test/test/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

了解聚合概念

  • 桶(Buckets)

    满足特定条件的文档的集合

  • 指标(Metrics)

    对桶内的文档进行统计计算。

      聚合是由桶和指标组成的。 聚合可能只有一个桶,可能只有一个指标,或者可能两个都有。也有可能有一些桶嵌套在其他桶里面。例如,我们可以通过所属国家来划分文档(桶),然后计算每个国家的平均薪酬(指标)。
    
    另外聚合计算是实时的。一旦文档可以被搜到,它就能被聚合。这也就意味着我们可以直接将聚合的结果源源不断的传入前端,让其动态显示变化。
    
     5.x后对排序,聚合这些操作用单独的数据结构(fielddata)缓存到内存里了,需要单独开启。对哪个字段聚合就要开启哪个字段。比如我就开启了color字段,方便后续的聚合。
    
1
2
3
4
5
6
7
8
9
PUT test/_mapping/test/
{
"properties": {
"color": {
"type": "text",
"fielddata": true
}
}
}

根据颜色分组,参考mysql的groupby

1
2
3
4
5
6
7
8
9
10
11
GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}

核心结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"aggregations" : {
"popular_colors" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "red",
"doc_count" : 4
},
{
"key" : "blue",
"doc_count" : 2
},
{
"key" : "green",
"doc_count" : 2
}
]
}

对桶内的数据计算一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /test/test/_search
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"aggregations" : {
"colors" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "red",
"doc_count" : 4,
"avg_price" : {
"value" : 32500.0
}
},
{
"key" : "blue",
"doc_count" : 2,
"avg_price" : {
"value" : 20000.0
}
},
{
"key" : "green",
"doc_count" : 2,
"avg_price" : {
"value" : 21000.0
}
}
]
}

希望求出每个颜色里面制造厂商的分布情况,那就里面在嵌套一个桶。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /test/test/_search
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"make": {
"terms": {
"field": "make"
}
}
}
}
}
}

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"aggregations" : {
"colors" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "red",
"doc_count" : 4,
"avg_price" : {
"value" : 32500.0
},
"make" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "honda",
"doc_count" : 3
},
{
"key" : "bmw",
"doc_count" : 1
}
]
}
},
......

条形图

这个关键字histogram,这个关键字要求字段必须是数值类型,然后interval定义了区间,然后可以在加几个指标,来获取这个区间的信息,比如最大值,最小值,和,平均值巴拉巴拉的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /test/test/_search
{
"size" : 0,
"aggs":{
"pricesss":{
"histogram":{
"field": "price",
"interval": 20000
},
"aggs":{
"revenue": {
"max": {
"field" : "price"
}
}
}
}
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"aggregations" : {
"pricesss" : {
"buckets" : [
{
"key" : 0.0,
"doc_count" : 3,
"revenue" : {
"value" : 15000.0
}
},
{
"key" : 20000.0,
"doc_count" : 4,
"revenue" : {
"value" : 30000.0
}
},
{
"key" : 40000.0,
"doc_count" : 0,
"revenue" : {
"value" : null
}
},
{
"key" : 60000.0,
"doc_count" : 0,
"revenue" : {
"value" : null
}
},
{
"key" : 80000.0,
"doc_count" : 1,
"revenue" : {
"value" : 80000.0
}
}
]
}

根据时间来聚合数据,首先命名为sales,然后使用关键字date_histogram,选择字段,和时间粒度即可。中间没有的月份也会显示,只是数目为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /test/test/_search
{
"size" : 0,
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "year",
"format": "yyyy-MM-dd"
}
}
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"aggregations" : {
"sales" : {
"buckets" : [
{
"key_as_string" : "2014-01-01",
"key" : 1388534400000,
"doc_count" : 1
},
{
"key_as_string" : "2014-02-01",
"key" : 1391212800000,
"doc_count" : 1
},
{
"key_as_string" : "2014-03-01",
"key" : 1393632000000,
"doc_count" : 0
},
{
"key_as_string" : "2014-04-01",
"key" : 1396310400000,
"doc_count" : 0
},
......

另外由于elasticSearch版本差异太大,2.x版本date_histogram ,histogram 时间聚合和数值聚合返回的buckets是没有空值的,也就是某个月份数据为0或者某个区间值为0,那么返回值里面是没有的(当然可以设置参数让其有),但是目前线上版本是6.x的,默认已经有空值了

下面来一个复杂的聚合,我来解释下其逻辑。

  • 首先根据每个季度创建一个桶,然后在每个桶里面在进行聚合计算。
  • 在季度每个桶里面第一个指标算出,这个季度一共卖了多少钱。
  • 在季度这个桶里面再根据制造厂商创建桶,同时计算出每个厂商卖了多少钱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET /test/test/_search
{
"size" : 0,
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "quarter",
"format": "yyyy-MM-dd"
},
"aggs": {
"per_make_sum": {
"terms": {
"field": "make"
},
"aggs": {
"sum_price": {
"sum": { "field": "price" }
}
}
},
"total_sum": {
"sum": { "field": "price" }
}
}
}
}
}

这是一个稍微复杂点的聚合逻辑,理解了他基本也就理解了全部的概念

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
"aggregations" : {
"sales" : {
"buckets" : [
{
"key_as_string" : "2014-01-01",
"key" : 1388534400000,
"doc_count" : 2,
"per_make_sum" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "bmw",
"doc_count" : 1,
"sum_price" : {
"value" : 80000.0
}
},
{
"key" : "ford",
"doc_count" : 1,
"sum_price" : {
"value" : 25000.0
}
}
]
},
"total_sum" : {
"value" : 105000.0
}
},
{
"key_as_string" : "2014-04-01",
"key" : 1396310400000,
"doc_count" : 1,
"per_make_sum" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "ford",
"doc_count" : 1,
"sum_price" : {
"value" : 30000.0
}
}
]
},
"total_sum" : {
"value" : 30000.0
}
},
{
"key_as_string" : "2014-07-01",
"key" : 1404172800000,
"doc_count" : 2,
"per_make_sum" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "toyota",
"doc_count" : 2,
"sum_price" : {
"value" : 27000.0
}
}
]
},
"total_sum" : {
"value" : 27000.0
}
},
......

我们可以将查询和聚合一起来使用,前面的聚合查询基本都相当于加了

1
2
3
4
5
"query" : {
"match" : {
"make" : "ford"
}
}

下面是个简单例子,是聚合和查询集合的例子。查出ford厂商的卖的车的颜色聚合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /test/test/_search
{
"query" : {
"match" : {
"make" : "ford"
}
},
"aggs" : {
"colors" : {
"terms" : {
"field" : "color"
}
}
}
}

我们不仅可以在查询条件内做聚合,还可以全局聚合,同时使用,用法是关键字all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /test/test/_search
{
"size" : 0,
"query" : {
"match" : {
"make" : "ford"
}
},
"aggs" : {
"single_avg_price": {
"avg" : { "field" : "price" }
},
"all": {
"global" : {},
"aggs" : {
"avg_price": {
"avg" : { "field" : "price" }
}

}
}
}
}

结果如下:算出了查询值得平均值,还算出了全部文档的平均值。

1
2
3
4
5
6
7
8
9
10
11
"aggregations" : {
"all" : {
"doc_count" : 8,
"avg_price" : {
"value" : 26500.0
}
},
"single_avg_price" : {
"value" : 27500.0
}
}

聚合也可以和过滤一起使用。比如这个语句查出大于15000销售额的平均值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /test/test/_search
{

"query" : {
"constant_score": {
"filter": {
"range": {
"price": {
"gte": 15000
}
}
}
}
},
"aggs" : {
"single_avg_price": {
"avg" : { "field" : "price" }
}
}
}

结果如下:

1
2
3
4
5
"aggregations" : {
"single_avg_price" : {
"value" : 31666.666666666668
}
}

过滤桶,对聚合结果过滤。

先选出ford厂商的车,然后在这个结果里面再进行过滤选出上个月的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GET /test/test/_search
{
"size" : 0,
"query":{
"match": {
"make": "ford"
}
},
"aggs":{
"recent_sales": {
"filter": {
"range": {
"sold": {
"from": "now-1M"
}
}
},
"aggs": {
"average_price":{
"avg": {
"field": "price"
}
}
}
}
}
}

有一种做法,先查询,然后聚合,再过滤一部分数据,这个过滤不能影响聚合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /test/test/_search
{

"query": {
"match": {
"make": "ford"
}
},
"post_filter": {
"term" : {
"color" : "green"
}
},
"aggs" : {
"all_colors": {
"terms" : { "field" : "color" }
}
}
}

上面的聚合可以不断嵌套聚合。从而组合成更加复杂的结果

排序的话,下面这个技巧很有意思,可以参考了解下,业务中很有可能用的到。

  • 先根据颜色分组,然后算出每一组的平均值,然后再根据平均值排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"colors" : {
"terms" : {
"field" : "color",
"order": {
"avg_price" : "asc"
}
},
"aggs": {
"avg_price": {
"avg": {"field": "price"}
}
}
}
}
}

统计唯一值

1
2
3
4
5
6
7
8
9
10
11
GET /test/test/_search
{
"size" : 0,
"aggs" : {
"distinct_colors" : {
"cardinality" : {
"field" : "color"
}
}
}
}

这个是统计颜色的种类,es使用了cardinality算法,也就是 HyperLogLog++ 算法做的,他不需要统计全部的数据,然后汇总计算,有误差,但是可以接受,性能可以。

如果统计唯一值得时候对速度要求很高,这块可以优化,参考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/cardinality.html。就是给这些字段建立索引。

java线程,线程池系列

发表于 2020-03-25

线程通信

wait,notify

这2个方法,是Object类的方法,必须要先加锁才可以使用。顺序使用有要求

park,unpark

这2个方法,对顺序没有要求,但是不能加锁使用在同步代码块里面。LockSupport类的静态方法。

ps,这个方法的通信条件不要使用if来进行判断,可能出现伪唤醒。使用while来代替。

线程池数量判断规则

1.计算类任务
cpu的1-2倍,比如cpu8核,那么16个线程
2.io类任务
因为有io阻塞,要大一点。最佳线程数量 = ((线程等待时间+线程CPU时间)/ 线程CPU时间)* CPU个数

终极规则:
看看你的cpu使用情况>80%是比较合理的,不断调整

线程加锁

synchronized关键字加锁
偏向锁 -轻量级锁-重量级锁,这是java虚拟机做的优化。

第一个线程第一次加锁(通过偏向锁标识位),那么就会采用偏向锁的机制,修改对象头的标识位,将第一个线程的线程id加进去。
当第二个线程访问这个对象时,判断是否偏向锁,是则第一步,不是则判断是否有线程持有锁,若是没有持有锁,那么cas操作尝试加锁,切将锁升级为轻量级锁,cas操作是,将加锁标识位修改,然后把markword里面的信息,存放到线程栈的私有信息里面去,将其替代为加锁线程的地址。若是有锁,那么自旋多次cas后,还是没有成功,则将其升级为重量级锁。
总量级锁通过monitor来实现,每个对象都有一个monitor。
monitor里面有entrylist,里面是争抢锁的。owner则是锁的持有者,waitset则是owner调用wait后进入的集合。通过notify进入到entrylist里面。

lock api
ReentrantLock:独享锁,可重入锁,支持公平和不公平
ReadWriteLock:读写锁,其包含2把锁,创建这个读写锁对象后,我们可以通过这个对象获取到2把锁,一个读锁,一个写锁,读锁可以多个线程同时持有,写锁,一个线程持有,在加了读锁后,不能加写锁。(可以用这个特点去做锁降级)。

aqs,本质上就是通过cas,链表,park,unpark来处理的。

Semaphore,信号量,创建对象时,指定一个信号量。维护一个原子变量,有2个方法,一个是
acquire:获取一个许可,没有就等待
release:释放一个许可
场景:代码限流,维护一个资源只被限定的线程数量访问
CountDownLatch:到计数器,创建对象时,指定一个计数器。维护一个原子变量,有2个方法,一个是
await():方法等待计数器变为0,在此之前,线程进入等待状态。
countdown():方法使得计数器建一。
场景:统计线程执行情况。看看哪些线程没有执行完。
使用多个线程来计算数据,实现并发处理。
多个线程相互通信。

CyclicBarrier:线程栅栏。创建对象时,指定一个栅栏线程大小。
。维护一个原子变量,同时可以加一个Runnable接口,作为满足条件后的执行的方法
核心方法是
await():线程调用await方法后,判断当前调用await方法的线程数目是否达到了创建对象所指定的数量,没有达到进入等待状态,达到了,执行后续的方法。
和前面countdownlatch的区别是可以多次执行。每满足一次数量后,就会执行一次调用await方法线程的后续代码和Runnable接口的方法,可以多次

场景:拼多多拼团购物。
批量插入数据。

查看jvm运行时参数

发表于 2020-03-18

PrintFlagsFinal

java -XX:+PrintFlagsFinal -version
查看虚拟机默认参数

jps

专门查看java进程的
jps
jps -l查看完全参数

jinfo

jinfo是查看运行时进程的详细信息。比如
jinfo -flag MaxHeapSize 11956
查看这个java进程的最大堆大小
jinfo -flag UseG1GC 11956
查看是否使用了G1垃圾回收器
输出是XX:-UseG1GC,其中-表示没有使用

jstat

jstat查看jvm的统计信息
类加载,垃圾回收信息,jit编译信息

jstat -class 11956
Loaded  Bytes  Unloaded  Bytes     Time
28968 58273.9      301   462.6      39.13


jstat -gc 11956
S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
512.0  1024.0  0.0    0.0   145920.0  2871.8   453120.0   246336.3  193100.0 179678.6 26240.0 21961.4   1578   12.603 1484   410.614  423.217

S0C,S0U,S1U,S0U,s0,s1区的使用量和总量
EC,EU:Eden区的总量和使用
OC,OU,Old区的总量和使用
MC,MU,Metaspace区总量和使用,其中这个metaspace是存放Class对象的。
CCSC,CCSU,压缩类空间总量和使用。
YGC,YGCT,YoungGC的次数和时间
FGC,FGCT:FullGC的次数和时间
GCT,总GC的时间。

ps 通过官网 https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html#BEHHGFAE来查询

如何导出内存镜像文件

1,内存溢出是自动导出,加上2个参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
2,Jmap导出
jmap -dump:format=b,file=heap.hprof 11956
导出的文件用mat打开,可以查看到对象的数量和大小。可以分析内存溢出的问题。

jstack

打印jvm里面所有的线程,看看cpu暴增的原因之类的。java线程的状态有new,runnable,blocked,waiting,timed_waiting,terminated,
cpu飙高,可能是死循环。或者死锁。cpu过大可能导致新的请求无法处理。
方案:
1,查看cpu那个进程高。ps auxw | head -1;ps auxw |sort -rn -k3 |head -11
2,打印jstack 11569 ->11.txt.
3,top -p 7792 -H,打印所有的线程。找出高的线程,然后将其从10进制转换为16进制,得到一个id
4,在打印的文件里面找到这个id对应的线程,然后分析。具体代码。

deadlock。

JVisuaIVM

可视化监控工具,可以监控远程和本地。
里面有很多功能,和jstack,jmap类似,几乎包含了上面所有的命令以及mat,里面有个抽样的功能,

其中cpu抽样,可以看到哪个方法的执行时间。从而针对性的优化代码

内存抽样,可以看到每个类占用的内存实例数等信息。可以看到那些对象可能导致内存泄漏。

Btrace

在程序不重启,正在运行的时候修改动态的修改字节码。
下载btrace,然后加入到环境变量,写好脚本,然后执行 btrace pid 脚本,执行完,字节码就加入到运行的环境里面了。只要是java代码都可以拦截。

参数,返回值,异常都可以获取到,还可以判断某一行代码是否执行

注意点:1.只能本地执行。不能远程使用。
2.修改了字节码,退出时不会回复

tomcat

1,tomcat远程debug
基于JDWP协议,定义了调试器和java虚拟机之间的协议。
主要是修改服务器上tomcat的配置,然后调试。如果是个普通的java进程需要修改这个java进程的启动脚本,加上一些配置。

2.tomcat-manager
tomcat自带的,还是修改tomact配置,增加用户,增加配置。可以查看tomcat很多信息,比如有哪些应用。且对其进行处理。查看线程信息之类的。以及线程处理时间之类的。

3.psi-probe
先下载probe这个项目源码,然后打包,将其war包放入到tomcat的webapp下面,即可,启动tomcat即可。tomcat的配置和上面一样。

4,tomcat优化
1,内存优化
2,线程优化
主要需要配置maxConnections最大连接数。acceptCount:连接满了之后的新的连接进入队列,超出队列,则拒绝请求。maxThread:工作线程数,和cpu相关…
3.配置优化
autoDeploy,周期性检查是否有新的应用加了进来,但是会浪费一个线程资源。默认为true,建议为false.
enableLookups:开启后会对请求做一个dns查询,浪费性能。
reloadable:监控web-inf/classes和web-inf/lib目录,浪费性能。默认没有开启。
还有设置协议为nio,
还可以禁用session,存到redis里面。

nginx

可以通过ngxtop,动态查看访问最多的ip,访问最多的返回码等信息。
Mginx-rrd是个图形化工具,这个工具统计的信息可能滞后,因为它是通过定时器来统计的。
优化:
增加工作线程,增加并发连接数,默认是一个工作线程,1024个连接。
一般多少个cpu,就多少个工作线程,启用epoll网络模型。
启用长连接。浏览器和nginx之间是有长连接的,但是Nginx和后端服务之间是没有长连接的,需要配置。
启用缓存,压缩。
操作系统优化。

jvm调优

运行时数据区(这是一个规范)

jvm内存结构
程序计数器:这个就是线程私有的,存放的是线程正在执行指令的地址。
java虚拟机栈:线程私有的,生命周期和线程一样,里面的元素是栈帧,每一个栈帧的入栈到出栈意味着一个方法的执行调用和结束。栈帧存放的是方法的局部变量表,返回值信息,方法出口灯
java堆,分为新生代,老年代,线程共享的,每一个实例都是在堆里面分配内存的。
java方法区,线程公有的,存放,常量。被虚拟机加载的类信息,编译器编译后的代码。(运行时常量池:各种常量信息,在类加载后放进去存放。)

上面的是java虚拟机规范的jvm内存结构

下面是实际的jvm内存结构(实现)

堆:和上面的一样

非堆区(metaspace)

存放字节码文件,class,package,method,常量池,符号引用。

ccs:压缩类空间,只有你启用了这个才会存在(java堆中每个对象都有一个指针指向自己的Class,因为在64位虚拟机下面,每个指针64位长度,为了压缩,采用32位,那么数据就会存到ccs里面)

codeCache:jit即时编译的代码,jvm执行的本地代码。如果没有采用jit,没有使用native方法,那么就是空的。

ps:java本地代码,就是采用编译方式启动java进程,然后代码会被jit编译为机器码,放入到metaspace里面

判断对象是否回收的算法
可达性分析算法,从根节点到这个对象是否可达。
根节点很多:类加载器,Thread,虚拟机栈的本地变量表,常量应用,垃圾回收的时候,通过这些根节点来进行可达性分析。

垃圾回收算法
标记-清除:做可达性分析之后,标记所有可回收的对象,标记完成后统一回收。缺点效率不高,产生大量内存碎片。
复制算法,吧内存划分为大小一样的2块,每次使用其中一块,当其中一块无法分配内存后,进行垃圾回收,吧可达的对象复制到另外一块上,再把这块用的空间一次性清除。
优点,简单高效
缺点,内存利用不高
标记-整理。先判断哪些对象是可达的,然后统一向一端移动,然后清除掉边界外单位内存
相对于标记清除,少了内存碎片,但是更加耗时。

一般来说,yong区使用复制算法,old区采用标记清除或者标记整理算法。

对象分配:
对象优先分配到Eden区,大对象直接到老年代,存活时间长的对象进入老年代。

垃圾回收器
串行收集器Serial:Serial,Serial Old,单线程的垃圾回收器。当内存不够时,虚拟机会启动一个单线程的垃圾回收器。
并行收集器Parallel: Prallel Scavenge,Parallel Old,吞吐量优先
并行:指多条垃圾回收线程并行工作,用户线程仍然处于等待状态。适合科学计算,后台处理等弱交互场景,

并发收集器Concurrent:CMS,G1,停顿时间短优先。

并发:指多条垃圾回收线程和用户线程并行,也许是交替进行,垃圾回收的时候不会停止用户程序的运行,适合web,对相应时间有要求的应用

停顿时间:垃圾收集器做垃圾回收中断应用执行的时间。XX:MaxGCPauseMillis

吞吐量:花在垃圾回收的时间和花在应用时间的占比-XXGCRimeRatio=,垃圾收集时间栈:1/1+n

-XX:+UseSerialGX -XX:+UseSerialOldGC
使用串行,一般不使用

-XX:+UseParallelGC,-XX:+UseParallelGC
使用并行,吞吐量优先
Server模式下默认收集器。cms是old区的,

并发
CMS:XX:+UseConcMarkSweepGC -XX:+UseParNewGC
G1:-XX+UseG1GC

由于虚拟机采用分带算法
new old
Serial SerialOld
parNew SerialOld
ParaLLelScavenge SerialOld
Serial CMS
ParNew CMS
G1 G1

其中CMS可能退化为SerialOld,在担保分配空间失败后

如何选择垃圾回收器

1.调整堆的大小让服务器自己选择
2.如果内存小于100,串行
3,如果单核,且没有停顿时间的要求,串行或者jvm自己选。
4,如果允许停止时间超过一秒,选择并行或者jvm自己选。
5,响应时间很重要,不能超过一秒,使用并发垃圾回收器。

使用并行垃圾回收器,
-XX:+UseParallelGC手动开启
-XX:ParallelGCThread=,多少个GC线程。
cpu>=8 N=5
Cpu<8 N=CPU

并行垃圾回收器有个自适应的特性。内存不够了会自动变大。
因此一般不使用。

CMS并发
低停顿,低延迟
老年代

收集过程。
1,初始标记,标记GCroot,这个过程需要停止应用程序。
2,并发标记。
3,并发预处理
4,重新标记,也是需要停止应用的,
5,并发清除
6,并发重置

缺点,cpu敏感,浮动垃圾,空间碎片。标记清除算法。

CMS参数优化
-XX:ConcGCThreads,并发的GC线程数
-XX:+UseCMSCompactAtFullCollection:fullGC之后会做压缩,减少内存碎片。
可视化GC日志分析工具
-XX:CMSFullGCsBeforCompaction:多少次FullGC之后压缩一次。
-XX:CMSInitiatingOccupancyFraction:触发FullGC,老年代空间使用了多少比例后会触发这个fullGC,92%
-XX:+CMSScavengeBeforRemark:fullGC之前先做YGC。

G1垃圾回收器
大内存,小停顿,同时是新生代和老年代。
G1里面没有物理意义的新生代和老年代,他是一个个的region,多个region构成了逻辑上的新生代和老年代。
StaB,GC开始的时候存活对象的快照。
RSet:记录了其他region里面引用了本region对象的关系。
G1 YoungGC
新生代进入Eden区
存活对象进入su区
存活时间长的对象进入Old区。
和其他一样

没有fuLLGC 只有MixedGC
回收Young区和部分Old区。不是回收全部的old区。

global concurent marking(全局标记)
1,标记GC root,stw
2,标记存活Region
3.标记存活对象。
4.重新标记,stw
5.部分stw

根据参数来设定,InitiatingHeapOccupancyPercent,默认45%,就会触发global concurent marking

G1HeapWastePercent默认5%。
再global concurent marking之后,我们可以判断有多少对象需要回收,也就是说要判断要回收的对象是否超过了5%,如果超过了就要回收。在每次YGC之后和Mixed GC之前,会检查参数是否达到了这个值,如果达到了就会MixedGC

是否需要切换到G1
1.50%堆被存活对象占用
2,对象分配速度快
3,垃圾回收时间长

GC日志简析
1吞吐量(应用线程时间和总时间的比例)和停顿时间

通过工具分析
1.在线 gceasy.io
2.GCViewer

具体的分析都可以在orcal官网查看
首先要打印gc日志,输入一些参数,会让gc的时候把文件打印出来。在线工具更加方便

ParallelGC调优原则
1.不要设置最大对内存
2.优先设置吞吐量目标
3.吞吐量达不到要求,调大最大内存,不要让os使用swap,如果还达不到降低吞吐量。
4.吞吐量能达到,GC时间长,设置停顿时间目标

就是不断尝试。分析gc次数。

G1最佳实践
1.年轻代:避免使用-XMn,-XX:NewRatio,等显示设置young区大小,会覆盖暂停时间目标。
2.暂停时间目标:不要太苛刻,其吞吐量目标是90%的应用时间和10%的垃圾回收时间,太严格影响吞吐量。
3.MixGC

分析gC的原因。

字节码

1,先编译一个java文件,生成class文件
2,找到其所在的目录
3,执行 javap -verbose aaa.class->test1.txt

intern区别

1.6 常量池没有,加进去,有,不管
1.7 常量池没有,加个指向堆对象的引用。

spring常用注解

发表于 2019-12-26

@lazy

  这个注解和spring 单例结合使用,当你需要用这个bean时才会去选择创建。

@Configuration

  这个类似于我们之前的bean配置文件,在里面,我们通过@Bean注解注入bean到bena容器

@Conditional

  这个注解,按照条件来注入bean。这个注解里面有个参数,就是一个Conditional接口数组,里面有个方法返回布尔。

@import

  里面有个参数是Class数组,如果是普通的类直接注入进去,如果是实现了ImportSelector接口的,则将实现这个接口方法的返回值注入进去,实现这个接口注入的bean的id都是类全限定名。第三个就是实现了ImportBeanDefinitionRegistrar接口的bean,在这个方法里面可以主动注册bean到bean容器里面。

factorybean

  这个bean比较特殊,这类bean是实现了factorybean接口的类,里面有个getObject方法,当我们尝试将这个bean获取的时候,返回的对象是其getObject方法返回的对象。如果要获取这个bean本身,那么我们调用getBean时,需要加个&符号就可以了

bean的生命周期

  生命周期为bean的创建,bean的初始化,bean的销毁。初始化方法是在对象初始化装配结束后执行的。
  我们可以在在@Bean注解上面指定初始化方法和销毁方法这个方法必须在你的bean定义里面。bean的销毁是在容器关闭的时候进行,初始化则是在容器创建后执行。
  或者实现InitializingBean,DisposableBean方法来实现初始化和销毁方法。
  或者通过注解@PostConstruct ,@Predestroy放到类对应方法上。
  或者通过接口BeanPostProcessor有2和方法,第一个方法,是在初始化方法调用执行前执行,一个是初始化执行后调用。将这个实现了这个接口的方法加入到bean,就可以在所有的bean的初始化方法调用前后执行。

java 并发001

发表于 2019-12-03

CSA原理

  CAS,(Compare-And-Swap,比较和替换)。其具有三个操作数:内存地址V,旧的预期值A,新的预期值B。当V中的值和A相同时,则用新值B替换V中的值,否则不执行更新。(PS:上述的操作是原子性的,因为过程是:要么执行更新,要么不更新),这个操作是CPU内部执行的,是原子的。类sun.misc.Unsafe提供了大量cas操作的方法。

不同类型的锁

阻塞锁

  让线程进入阻塞状态,等待唤醒信号,唤醒后才可以进入到就绪状态,通过竞争,进入运行状态。支持阻塞锁的方法有,synchronized关键字,ReentrantLock的lock和unlock方法,以及Object类的wait和notify方法。

自旋锁

  当一个线程获取锁时,但是锁被其他线程获取了,该线程就不断自循环来不断获取锁。

非公平锁

  1.当有线程竞争锁时,该线程会优先尝试获得锁,这对那些已经在队列等待的线程来说不公平,这就是不公平锁,但是会极大的提高性能。如果没有获取到锁,那么就会添加到对列尾部。

AQS

  AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

深入理解kafka客户端

发表于 2019-11-14

# 分区分配策略

RangeAssignor

  默认策略,先把这个消费组的消费者按照字典排序。然后主题的分区数目均匀分布到消费者上面,无法均匀的话,字典顺序靠前的多一点。缺点就是可能不均匀

RoundRobinAssignor

  还是先把消费者和消费者订阅的分区按照字典排序,然后轮训的方式一个一个分配节点,相对前面的来说解决了均匀问题,但是在订阅不均匀的情况下,分配可能极度不均匀。

StickAssignor

  前期和RoundRobinAssignor一样,后期则是不一样,发生变化时,RoundRobinAssignor是全部重新弄,StickAssignor则是更具当前的重新进行分配。相对RoundRobinAssignor做了优化,尽量减少了变动。

12…13

skydh

skydh

126 日志
© 2020 skydh
本站访客数:
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.3