微服务迁移K8S踩坑记录

遇到问题

  1. 阿里云免密拉取 deployment 配置不能创建serviceAccount 使用default

  2. 数据库白名单问题:按网段添加所有白名单解决

  3. jenkins 的构建队列 queueId 为短期数据,过期查询会404,要在构建发生时 使用 queueId 获取 buildNumber,后续api接口用 buildNumber 来调用

  4. 跨平台构建镜像,要使用docker buildx

  5. 基础镜像 alpine 时区问题,构建时使用 apk add –no-cache 避免 apk update 生成缓存

  6. 有些服务依赖时区信息,需要安装 apk add tzdata

  7. 虚机通过 nacos 不能正常对 pod 发起 rpc 调用

  8. 配置了多个域名解析 nslookup beta-cloud.**.com

  9. 老服务没有停,rpc 请求受到影响

  10. 阿里云slb 不支持挂ip、pod。新申请了内外网两个slb接域名请求,两个ecs部署nginx转发到 k8s

  11. 灰度服务也参与rpc请求,要及时清理

  12. pod 需要平滑退出 增加 preStop: exec: command: [“sleep”, “3”]

  13. accessModes有三种,ReadWriteOnce(被单个node读写), ReadOnlyMany(被多个nodes读), ReadWriteMany(被多个nodes读写)。之前阿里云 jenkins 报错存储冲突,应该和jenkins挂载配置的这个有关系 jenkins ReadWriteOnce模式的盘和 pod 里的 go-cache 发生了冲突。

  14. 部署多个Ingress Controller,限制内网nginx处理内网转发,外网处理外网 。通过 kubernetes.io/ingress.class 这个注释绑定

  15. nfs 读取大量小文件时性能差,可以开启缓存,或者考虑hostpath方式存储

  16. 某python程序采集不到标准输出里的日志 ,配置的 tty stdin 后设置启动命令 /bin/sh -c /app/start.sh 后正常采集

  17. 踩坑 jenkins 重启后,jenkins pvc 被删除了,导致服务启不来。重启后配置的域名丢失导致调不通

  18. 数据库密码改了,服务一直重启,always 策略频繁拉镜像造成docker 拉镜像的并发被用尽了。

高可用 prometheus 监控实施

机器配置

ip 服务 说明
10.0.40.100 grafana + prometheus 监控 报警
10.0.40.101 grafana + prometheus 监控 报警
10.0.40.102 victoriametrics slb 10.0.50.100 代理8428端口
10.0.40.103 victoriametrics slb 10.0.50.100 代理8428端口
10.0.10.11 loki 日志

方案说明

  • prometheus 设置远程写,到多个 victoriametrics

  • grafana 数据存入mysql,使用 slb 地址引入 victoriametrics 数据源做展示报警

  • 以上prometheus 、grafana 、victoriametrics 均可配置水平扩展

收益

  • 监控报警高可用
  • 引入了victoriametrics存储,数据查看由之前的最近15天扩展到3个月或更久

Read More

Golang 设计模式

策略模式

定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

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
75
// 消息发送渠道 opSender 和 mqSender 可替换
package main

import "fmt"

type AlertMessage struct {
ToUser string
Text string
}

// Sender 发送渠道
type Sender interface {
Send(msg *AlertMessage) error
}

// Alarm 报警结构体
type Alarm struct {
Sender
}

// NewAlarm 新建报警器
func NewAlarm(sender Sender) *Alarm {
return &Alarm{Sender: sender}
}

// OpSender 发送器
type OpSender struct {
api string
}

// Send 发送请求给ops平台
func (o *OpSender) Send(message *AlertMessage) error {
// 发消息到op
fmt.Printf("发送%s到web接口:%s\n", message.Text, o.api)
return nil
}

// MQSender 发送器
type MQSender struct {
queue string
}

// Send 发送到MQ
func (m *MQSender) Send(message *AlertMessage) error {
// 发消息到op
fmt.Printf("发送%s到mq队列:%s\n",message.Text,m.queue)
return nil
}

// apiHealthCheck api 健康检查
type apiHealthCheck struct {
// 定义为接口类型
alarmManager *Alarm
}

// SetAlarmManager 动态设置报警器
func (a *apiHealthCheck) SetAlarmManager(alarmManager *Alarm) {
a.alarmManager = alarmManager
}

func main() {
// 实例化发送器
opSender := &OpSender{api: "http://alarm.ops.net"}
// 报警器
alarmManager := NewAlarm(opSender)
apiHealthCheckIns := &apiHealthCheck{alarmManager: alarmManager}
apiHealthCheckIns.alarmManager.Send(&AlertMessage{Text: "测试报警信息1"})

// 实例化发送器
mqSender := &MQSender{queue:"sms.send"}
// 报警器
alarmManager = NewAlarm(mqSender)
apiHealthCheckIns.SetAlarmManager(alarmManager)
apiHealthCheckIns.alarmManager.Send(&AlertMessage{Text: "测试报警信息2"})
}

观察者模式

定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

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
type observers map[uint64]*Observer

type Observation struct {
Data interface{}
}
type FilterFn func(o *Observation) bool
var nextObserverID uint64
// Observer describes what to do with a given observation.
type Observer struct {
// 通知次数
numObserved uint64
// 通知放弃次数(非阻塞通知,如果channel没有准备好,放弃)
numDropped uint64
// channel receives observations.
channel chan Observation
// 阻塞还是非阻塞通知
blocking bool
// filter will be called to determine if an observation should be sent to
// the channel.
// 过滤函数
filter FilterFn
// id is the ID of this observer in the Raft map.
id uint64
}
// RegisterObserver registers a new observer.
func (r *Raft) RegisterObserver(or *Observer) {
r.observersLock.Lock()
defer r.observersLock.Unlock()
r.observers[or.id] = or
}

// DeregisterObserver deregisters an observer.
func (r *Raft) DeregisterObserver(or *Observer) {
r.observersLock.Lock()
defer r.observersLock.Unlock()
delete(r.observers, or.id)
}
// observe sends an observation to every observer.
func (r *Raft) observe(o interface{}) {
// In general observers should not block. But in any case this isn't
// disastrous as we only hold a read lock, which merely prevents
// registration / deregistration of observers.
r.observersLock.RLock()
defer r.observersLock.RUnlock()
for _, or := range r.observers {
// It's wasteful to do this in the loop, but for the common case
// where there are no observers we won't create any objects.
ob := Observation{Data: o}
if or.filter != nil && !or.filter(&ob) {
continue
}
if or.channel == nil {
continue
}
// 阻塞通知
if or.blocking {
or.channel <- ob
atomic.AddUint64(&or.numObserved, 1)
} else {
// 非阻塞通知
select {
case or.channel <- ob:
atomic.AddUint64(&or.numObserved, 1)
default:
atomic.AddUint64(&or.numDropped, 1)
}
}
}
}

装饰器模式

动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

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
// 使用checkHeader函数装饰hello处理器函数
package main

import (
"fmt"
"net/http"
"log"
)

func main() {
//注册回调函数
http.HandleFunc("/hello", checkHeader(hello))
fmt.Println("run server...")
http.ListenAndServe("127.0.0.1:8080", nil)
}

// 处理器函数
func hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello http server"))
}

// 装饰器函数
func checkHeader(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("check sign")
if sign := r.Header.Get("Sign"); sign == "" {
w.WriteHeader(401)
w.Write([]byte("验签失败!"))
return
}
f(w, r)
//log.Println("")
}
}

简单工厂

1
2
3
4
5
6
7
8
9
10
// 根据配置创建不同的broker
func BrokerFactory(cnf *config.Config) (brokeriface.Broker, error) {
if strings.HasPrefix(cnf.Broker, "amqp://") {
return amqpbroker.New(cnf), nil
}
if strings.HasPrefix(cnf.Broker, "redis://") {
return redisbroker.New(cnf), nil
}
return nil, fmt.Errorf("Factory failed with broker URL: %v", cnf.Broker)
}

工厂方法

定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

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
// redisBroker实例的创建由子类redisType的newBroker实现,之后执行父类consume方法
package main

import (
"fmt"
"errors"
)


// 接口,也可以定义为结构体类型,嵌入到redisBroker、mqBroker由他们重写部分方法
type broker interface {
consumeStep1()
consumeStep2()
consumeStep3()
}

// 父类broker属性为接口类型
type commonBroker struct {
broker
}

// 此方法需要子类来实现
func (c *commonBroker) createBroker() (broker,error) {
return nil,errors.New("Not implemented")
}

// 父类定义了消费方法(没有final声明怎么防止被子类覆盖)
func (c *commonBroker) consume() {
c.broker.consumeStep1()
c.broker.consumeStep2()
c.broker.consumeStep3()
}

// redisBroker
type redisBroker struct{}

// 实现broker接口
func (r *redisBroker) consumeStep1() {
fmt.Println("step 1")
}
func (r *redisBroker) consumeStep2() {
fmt.Println("step 2")
}
func (r *redisBroker) consumeStep3() {
fmt.Println("step 3")
}

// 子类
type redisType struct {
// 嵌入父类
commonBroker
}

// 实例化对应的broker对象
func (r *redisType) createBroker() (broker,error) {
return &redisBroker{},nil
}

func main() {
// 创建子类
redisType := &redisType{}
// 子类创建对象
redisBroker,_ := redisType.createBroker()
// 子类嵌入父类对象并设置父类属性
redisType.commonBroker = commonBroker{redisBroker}
// 调用父类方法
redisType.consume()
}

单例模式

确保一个类只有一个实例,并提供一个全局访问点。

1
2
3
4
5
6
7
8
9
// 从连接池里获取一个连接
func (b *Broker) open() redis.Conn {
b.redisOnce.Do(func() {
b.pool = b.NewPool(b.socketPath, b.host, b.password, b.db, b.GetConfig().Redis, b.GetConfig().TLSConfig)
b.redsync = redsync.New(redsyncredis.NewPool(b.pool))
})

return b.pool.Get()
}

命令模式

将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 大汉三通语音、apppush消息分别封装成命令,包含接收着 大汉三通、极光,被smsHandle通过setCommand命令设置后,通过execute调用
package main

import (
"fmt"
)

// Ivoker
type smsHandle struct {
commander
}

func (s *smsHandle) setCommand(cmd commander) {
s.commander = cmd
}

// Execute
func (s *smsHandle) execute(params interface{}) {
s.commander.execute(params)
}

// Receiver
type receiver interface {
}

// dhstReceiver
type dhstReveiver struct {
account map[string]string
host string
port string
path map[string]string
}

func (d *dhstReveiver) sendVoice(msg string, phone []string) error {
fmt.Printf("给手机号%v发送语音短信:%s\n", phone, msg)
return nil
}

// jpushReveiver
type jpushReveiver struct {
account map[string]string
host string
port string
path map[string]string
}

func (d *jpushReveiver) appPush(msg string, phone []string) error {
fmt.Printf("给手机号%v推送消息:%s\n", phone, msg)
return nil
}

type commander interface {
// 为什么不把setReceiver作为接口的方法?因为每个command的Receiver是不同的类型,不具备统一性
execute(params interface{}) error
}

type withName struct {
name string
}

// dhstSendVoiceCommand
type dhstSendVoiceCommand struct {
withName
receiver *dhstReveiver
}

// execute
func (c *dhstSendVoiceCommand) execute(params interface{}) error {
data, _ := params.(*struct {
phone []string
msg string
})
c.receiver.sendVoice(data.msg, data.phone)
return nil
}

// appPushCommand
type appPushCommand struct {
withName
receiver *jpushReveiver
}

// execute
func (c *appPushCommand) execute(params interface{}) error {
data, _ := params.(*struct {
phone []string
msg string
})
c.receiver.appPush(data.msg, data.phone)
return nil
}

func main() {
// handle
smsHandle := &smsHandle{}
// 发语音短信
dhstReceiver := &dhstReveiver{}
dhstSendVoiceCmd := &dhstSendVoiceCommand{withName{"发送语音短信"}, dhstReceiver}
smsHandle.setCommand(dhstSendVoiceCmd)
smsHandle.execute(&struct {
phone []string
msg string
}{[]string{"17610668100"}, "验证码12345"})
// app push
jpushReveiver := &jpushReveiver{}
appPushCmd := &appPushCommand{withName{"发送语音短信"}, jpushReveiver}
smsHandle.setCommand(appPushCmd)
smsHandle.execute(&struct {
phone []string
msg string
}{[]string{"17610668100"}, "app push 内容测试"})
}

适配器模式

将一个类的接口,转换成客户期望的另一个接口 ,适配器让原本接口不兼容的类可以合作无间。

1
2


外观模式

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

1
2


模板方法模式

在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

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
package main

import "fmt"

// 父类咖啡因饮料
type caffeineBeverage struct {
}

// 制作
func (c *caffeineBeverage) prepareRecipe(brew func(), addCondiments func()) {
c.boilWater()
brew()
c.pourInCup()
addCondiments()
}

// 步骤一
func (c *caffeineBeverage) boilWater() {
fmt.Println("把水煮沸")
}

// 步骤三
func (c *caffeineBeverage) pourInCup() {
fmt.Println("把饮料倒入杯子")
}

// 子类 茶
type tea struct {
*caffeineBeverage
}

// 步骤二
func (t *tea) brew() {
fmt.Println("用沸水泡茶叶")
}

// 步骤三
func (t *tea) addCondiments() {
fmt.Println("加柠檬")
}

// 子类 茶
type coffee struct {
*caffeineBeverage
}

// 步骤二
func (c *coffee) brew() {
fmt.Println("用沸水冲泡咖啡")
}

// 步骤三
func (c *coffee) addCondiments() {
fmt.Println("加糖和牛奶")
}

func main() {
caffeineBeverageIns := &caffeineBeverage{}
teaIns := &tea{caffeineBeverageIns}
teaIns.prepareRecipe(teaIns.brew, teaIns.addCondiments)

fmt.Println()

coffeeIns := &coffee{caffeineBeverageIns}
coffeeIns.prepareRecipe(coffeeIns.brew, coffeeIns.addCondiments)

}

状态模式

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// 改变订单的行为形成为orderState接口,各种状态实现orderState接口,通过setState改变订单的状态
package main

import (
"fmt"
"math/rand"
"time"
)

// 订单状态接口
type orderState interface {
pay() error
refund() error
close() error
}

// OrderStateNotPay 未支付订单状态
type OrderStateNotPay struct {
*order
}

func (o *OrderStateNotPay) pay() error {
fmt.Println("发起支付")
fmt.Println("支付完成,转为支付成功状态")
o.order.setState(o.order.stateSuccess)
return nil
}
func (o *OrderStateNotPay) refund() error {
return fmt.Errorf("%s", "未支付订单不能发起退款")

}
func (o *OrderStateNotPay) close() error {
fmt.Println("关闭订单")
fmt.Println("关闭订单成功,转为已关闭状态")
o.order.setState(o.order.stateClosed)
return nil

}

// OrderStateClosed 已关闭
type OrderStateClosed struct {
*order
}

func (o *OrderStateClosed) pay() error {
return fmt.Errorf("%s", "已关闭订单不能发起支付")

}
func (o *OrderStateClosed) refund() error {
return fmt.Errorf("%s", "已关闭订单不能发起退款")

}
func (o *OrderStateClosed) close() error {
fmt.Println("重复关闭订单")
return nil

}

// OrderStateSuccess 支付成功
type OrderStateSuccess struct {
*order
}

func (o *OrderStateSuccess) pay() error {
return fmt.Errorf("%s", "订单已经是支付成功状态,不能再次发起支付")
}
func (o *OrderStateSuccess) refund() error {
fmt.Println("发起退款")
fmt.Println("退款成功,转为退款状态")
o.order.setState(o.order.stateRefund)
return nil

}
func (o *OrderStateSuccess) close() error {
return fmt.Errorf("%s", "不能关闭已成功支付订单")
}

// OrderStateRefund 转入退款
type OrderStateRefund struct {
*order
}

func (o *OrderStateRefund) pay() error {
return fmt.Errorf("%s", "已转入退款订单不能发起支付")
}
func (o *OrderStateRefund) refund() error {
return fmt.Errorf("%s", "已转入退款订单不能发起退款")

}
func (o *OrderStateRefund) close() error {
return fmt.Errorf("%s", "已转入退款订单不能关闭")
}

//
type order struct {
orderId int64
stateClosed *OrderStateClosed
stateSuccess *OrderStateSuccess
stateRefund *OrderStateRefund
stateNotPay *OrderStateNotPay
currentState orderState
}

// NewOrder 新建订单
func NewOrder(orderId int64) *order {
orderIns := &order{orderId: orderId}

orderIns.stateClosed = &OrderStateClosed{order: orderIns}
orderIns.stateSuccess = &OrderStateSuccess{order: orderIns}
orderIns.stateRefund = &OrderStateRefund{order: orderIns}
orderIns.stateNotPay = &OrderStateNotPay{order: orderIns}

orderIns.setStateByOrderId(orderId)

return orderIns
}

// 设置订单当前state对象
func (o *order) setState(state orderState) {
o.currentState = state
}

// 根据订单状态设置当前state对象
func (o *order) setStateByOrderId(orderId int64) {
stateInt := orderId % 7
switch stateInt {
case 2:
o.currentState = o.stateClosed
fmt.Println("当前订单为关闭状态")
case 3:
o.currentState = o.stateSuccess
fmt.Println("当前订单为支付成功状态")
case 6:
o.currentState = o.stateRefund
fmt.Println("当前订单为退款中状态")
default:
o.currentState = o.stateNotPay
fmt.Println("当前订单为未支付状态")
}
}

// 支付
func (o *order) pay() error {
return o.currentState.pay()
}

// 退款
func (o *order) refund() error {
return o.currentState.refund()
}

// 关闭订单
func (o *order) close() error {
return o.currentState.close()
}

func main() {
var err error

rand.Seed(time.Now().Unix())
orderId := int64(rand.Intn(13)) + 1
order := NewOrder(orderId)

// 支付
if err = order.pay(); err != nil {
fmt.Println(err)
//return
}
// 支付
if err = order.pay(); err != nil {
fmt.Println(err)
//return
}
// 退款
if err = order.refund(); err != nil {
fmt.Println(err)
//return
}
// 关闭
if err = order.close(); err != nil {
fmt.Println(err)
//return
}
}

好莱坞原则

别调用我们,我们会调用你

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
// 通过闭包发送短信

package main

import "fmt"

type sender interface {
sendText(msg string) error
sendVoice(msg string) error
}

type opalarm struct {
}

func (o *opalarm) sendText(msg string) error {
fmt.Printf("发送文字报警:%s\n", msg)
return nil

}
func (o *opalarm) sendVoice(msg string) error {
fmt.Printf("发送语音报警:%s\n", msg)
return nil

}

type senderObj struct {
sender
}

// 闭包发文字短信
func sendTextMsg(msg string) func(sender sender) error {
return func(sender sender) error {
return sender.sendText(msg)
}
}

// 闭包发语音短信
func sendVoiceMsg(msg string) func(sender sender) error {
return func(sender sender) error {
return sender.sendVoice(msg)
}
}

// 发信息函数
func sendMsg(sender sender, f func(sender sender) error) {
f(sender)
}

func main() {

senderObj := &senderObj{sender: &opalarm{}}

//senderObj.sendText("报警通知")
//senderObj.sendVoice("报警通知")

// 不要通知我,由我来通知你
sendMsg(senderObj, sendTextMsg("报警通知"))
sendMsg(senderObj, sendVoiceMsg("报警通知"))

}

go-mysql-elasticsearch 源码解读

项目中使用到了 go-mysql-elasticsearch 来做mysql和es的数据同步,所以花些时间了解下源码。

中间件主要用到了 github.com/siddontang/go-mysql/canal 这个包来实现 binlog 的处理

canal示例

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
cfg := canal.NewDefaultConfig()
cfg.Addr = "172.16.30.127:3308"
cfg.User = "root"

cfg.Password = "admin"

//只同步test库canal_test表
cfg.IncludeTableRegex = make([]string, 1)
cfg.IncludeTableRegex[0] = "test\\.canal_test"

c, _ := canal.NewCanal(cfg)

type MyEventHandler struct {
DummyEventHandler
}

func (h *MyEventHandler) OnRow(e *RowsEvent) error {
log.Infof("%s %v\n", e.Action, e.Rows)
return nil
}

func (h *MyEventHandler) String() string {
return "MyEventHandler"
}

// Register a handler to handle RowsEvent
c.SetEventHandler(&MyEventHandler{})
c.RunFrom(mysql.Position{"mysql_bin.000001", 0})

Rule

根据配置文件的规则,go-mysql-elasticsearch 把mysql 数据组装成 es 格式数据写入es,规则支持:

  1. 同步test库t表到 index:test type:t
  2. 同步test库t_[0-9]{4}表到 index:test type:t
  3. 同步test库tfield表到 index:test type:tfield 字段映射: id:”es_id”,tags:”es_tags,list”,keywords:”,list”
  4. 同步test库tfilter表到 index:test type:tfilter 只同步 id、 name 列
  5. 同步test库tid_[0-9]{4}表到 index:test type:t id由原表的 id + tag 生成

Read More

ldap 常用命令

ldapsearch

1
ldapsearch -x -D 'cn=Admin,dc=apple,dc=com' -w '******' -H ldap://0.0.0.0:9981 -b "cn=dong.tian,cn=apple,cn=People,dc=apple,dc=com"

//搜索 cn=haozhe.wei,cn=apple,cn=People,dc=apple,dc=com 过滤 mail cn sn givenname字段

1
ldapsearch -x -D 'cn=Admin,dc=apple,dc=com' -w '******' -H ldap://0.0.0.0:9981 -b "cn=haozhe.wei,cn=apple,cn=People,dc=apple,dc=com"  "(objectClass=*)" mail cn sn givenname

// 在cn=apple,cn=People,dc=apple,dc=com下搜索 carLicense=IVFISMVKUFXNV6ZF 的记录

1
ldapsearch -x  -D 'cn=Admin,dc=apple,dc=com' -w '******' -H ldap://0.0.0.0:9981 -b "dc=apple,dc=com"  "(carLicense=IVFISMVKUFXNV6ZF)"

// 导出所有数据

1
ldapsearch -x -D 'cn=Admin,dc=apple,dc=com' -w '******'  -H ldap://0.0.0.0:9981 -b 'dc=apple,dc=com' -LLL > /tmp/ldapdb.ldif

-l 5 搜索时间限制为五秒,-z 5 大小限制为五

ldapmodify

1
ldapmodify  -x  -D 'cn=Admin,dc=apple,dc=com' -w '******' -H ldap://0.0.0.0:9981 -f /tmp/ldapm

文件内容:

1
2
3
4
dn:cn=haozhe.wei,cn=apple,cn=People,dc=apple,dc=com 
changetype: modify
replace: mobile
mobile: 15101646884

ldapdelete

1
ldapdelete -x -D 'cn=Admin,dc=apple,dc=com' -w '******' -H ldap://0.0.0.0:9981 -v "cn=haozhe.wei,cn=apple,cn=People,dc=apple,dc=com" -v

ldapdadd

1
ldapadd -x -D "cn=admin,dc=apple,dc=com" -w ****** -H ldap://0.0.0.0:9981  -f /tmp/add.ldif

Read More

K3S

在线安装

1
curl -sfL https://docs.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -

离线安装

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
#下载离线镜像
wget https://github.com/rancher/k3s/releases/download/v1.18.9%2Bk3s1/k3s-airgap-images-amd64.tar
docker load < k3s-airgap-images-amd64.tar

#下载k3s 可执行文件
wget https://github.com/rancher/k3s/releases/download/v1.18.9%2Bk3s1/k3s

#下载 install.sh
https://github.com/rancher/k3s/blob/master/install.sh

sudo mkdir -p /var/lib/rancher/k3s/agent/images/
#下载的离线镜像包复制,格式如下
#sudo cp ./k3s-airgap-images-$ARCH.tar /var/lib/rancher/k3s/agent/images/
sudo cp ./k3s-airgap-images-amd64.tar /var/lib/rancher/k3s/agent/images/

#授权
chmod 755 k3s
#下载的K3S的bin包,格式如下
sudo cp ./k3s /usr/local/bin && sudo chmod 755 /usr/local/bin/k3s

#授权
chmod +x ./install.sh
#跳过镜像下载,使用docker作为运行时
INSTALL_K3S_SKIP_DOWNLOAD=true INSTALL_K3S_EXEC="--docker" ./install.sh

# 可能会遇到 k3s.server 换行引起的启动失败问题,修复后systemctl reload && systemctl start k3s

设置kubeconfig文件

1
2
3
4
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
chown runner:runner -R /etc/rancher/k3s/k3s.yaml
kubectl get pods --all-namespaces
helm ls --all-namespaces

网络

支持创建 LoadBalancer 类型的 Service

重启

systemd 下手动重启:

1
sudo systemctl restart k3s

openrc 下手动重启:

1
sudo service k3s restart

k3s默认使用containerd, 两者命令对比表:

id containerd 命令 docker 命令 备注
1 ctr image ls docker images 获取image信息
2 ctr image pull nginx docker pull nginx pull 一个nginx的image
3 ctr image tag nginx nginx-test docker tag nginx nginx-test tag 一个nginx的image
4 ctr image push nginx-test docker push nginx-test push nginx-test的image
5 ctr image pull nginx docker pull nginx pull 一个nginx的image
6 ctr image import nginx.tar docker load<nginx.tar.gz 导入本地镜像ctr不支持压缩
7 ctr run -d –env 111 nginx-test nginx docker run -d –name=nginx nginx-test 运行的一个容器
8 ctr task ls docker ps 查看运行的容器

参考:

https://docs.rancher.cn/k3s/

k3s containerd和docker 命令对比

离线安装K3S

离线安装K3S Server

clickhouse 多分片集群搭建

ck分布式集群,采用本地表分布式表 形式实现。本地表作为分片,分布式表作为数据集合。对于分布式表的插入会随机分配到配置的分片上,查询则会得到所有的数据集合。

本地表采用 Replicated 引擎,实现主备之间的数据同步,可以参考上篇文章 clickhouse单分片三副本高可用搭建,分布表采用 Distributed 引擎。

由于只有3台机器 a001、a002、a003,采用每台机器上起两个服务、一主一备,备服务 config.xml 端口配置

1
2
3
<http_port>8124</http_port>
<tcp_port>9001</tcp_port>
<interserver_http_port>9010</interserver_http_port>

metrika.xml配置,下面是环形备份,实现一台机器挂了,分布式表查询不受影响

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
75
76
77
78
79
80
81
82
83
84
85
86
<yandex>
<!-- 集群配置 -->
<clickhouse_remote_servers>
<!-- 3分片2备份 -->
<cluster_3shards_2replicas>
<!-- 数据分片1 -->
<shard>
<weight>1</weight>
<!-- 是否只写入所有replica中的一台,与ZooKeeper配合进行复制 -->
<internal_replication>true</internal_replication>
<replica>
<host>a001</host>
<port>9000</port>
</replica>
<replica>
<host>a002</host>
<port>9001</port>
</replica>
</shard>
<!-- 数据分片2 -->
<shard>
<weight>1</weight>
<internal_replication>true</internal_replication>
<replica>
<host>a002</host>
<port>9000</port>
</replica>
<replica>
<host>a003</host>
<port>9001</port>
</replica>
</shard>
<!-- 数据分片3 -->
<shard>
<weight>1</weight>
<internal_replication>true</internal_replication>
<replica>
<host>a003</host>
<port>9000</port>
</replica>
<replica>
<host>a001</host>
<port>9001</port>
</replica>
</shard>
</cluster_3shards_2replicas>
<!-- 1分片3备份 -->
<cluster_1shards_3replicas>
<!-- 数据分片1 -->
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>a001</host>
<port>9000</port>
</replica>
<replica>
<host>a002</host>
<port>9000</port>
</replica>
<replica>
<host>a003</host>
<port>9000</port>
</replica>
</shard>
</cluster_1shards_3replicas>
</clickhouse_remote_servers>
<zookeeper-servers>
<node index="1">
<host>172.2.5.4</host>
<port>2181</port>
</node>
<node index="2">
<host>172.2.5.5</host>
<port>2181</port>
</node>
<node index="3">
<host>172.2.5.6</host>
<port>2181</port>
</node>
</zookeeper-servers>
<macros> <!--a001作为第3分片备份a003-->
<layer>01</layer>
<shard>03</shard>
<replica>cluster01-03-a001</replica>
</macros>
</yandex>

Read More

vue 入门笔记

和 Angularjs 比较

组件开发有更好的生态圈,如Element UI

指令

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
v-bind:class="[classA,classB]" 绑定属性 简写 :class="[classA,classB]"
# 对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
# 绑定对象,也可以设置成变量
:class="{current:true,focus:false}"
# 绑定style
:style="{color:'red',fontSize:'18px'}"
<span v-html="rawHtml"></span>

# 返回对象的计算属性。这是一个常用且强大的模式:
<div v-bind:class="classObject"></div>

v-if
v-else-if
v-else
// 对一组元素进行if判断
<template v-if="">
<div></div>
<div></div>
</template>

<p v-for="{(num,index) in items}" :key="index">{{k + '-' +v}}</p>
<p v-for="{(value,key,index) in obj}" :key="index">{{k + '-' +v}}</p>
类似于 v-if,你也可以利用带有 v-for 的 <template> 来循环渲染一段包含多个元素的内容。

v-on:click="add" 简写 @:click="add"
this.$set(newLlist,0,{}) 修改数组0索引值
<div v-once></div> 确保这些内容只计算一次

Read More

小米mysql中间件shaza源码注释

main.go

1
2
3
4
5
//45 init config of shazam proxy
cfg, err := models.ParseProxyConfigFromFile(*configFile)

//57 init manager
mgr, err := server.LoadAndCreateManager(cfg)
1
2
3
4
5
6
7
manager.go
//46
mgr, err := CreateManager(cfg, namespaceConfigs)
//154 init namespace
m.namespaces[current] = CreateNamespaceManager(namespaceConfigs)
//422
namespace, err := NewNamespace(config)
1
2
3
4
5
6
namespace.go
//82 NewNamespace init namespace
func NewNamespace(namespaceConfig *models.Namespace) (*Namespace, error) {
//151 init router
namespace.router, err = router.NewRouter(namespaceConfig)
}
1
2
3
4
5
router.go
//29
func NewRouter(namespace *models.Namespace) (*Router, error) {
//63
rule, err := parseRule(shard)
1
2
3
4
5
6
7
8
9
10
11
rule.go
//310 生成初始化数据subTableIndexs、tableToSlice等
// subTableIndexs 所有子表index,走全表查询时候用到; tableToSlice 根据index获取slice ,再根据 index获取database,或者tableindex获取database
subTableIndexs, tableToSlice, shard, err := parseRuleSliceInfos(cfg)
//400 判断是mycatmod规则
case MycatModRuleType:
subTableIndexs, tableToSlice, err := parseMycatHashRuleSliceInfos(cfg.Locations, cfg.Slices, cfg.Databases)
//479
if len(locations) != len(slices) {
return nil, nil, errors.ErrLocationsCount
}

Read More