简单博客

flutter Widget Element

May 29, 2023
Flutter, Widget, Element
Flutter

Element #

abstract class Element extends DiagnosticableTree implements BuildContext

package:flutter/src/widgets/framework.dart

An instantiation of a [Widget] at a particular location in the tree. – 在树里的特定位置上的一个Widget的实例。

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An [Element] represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location. – Widget描述了如何配置一棵子树,但同一个Widget可以被用来配置多棵相似的子树,因为Widget是不可变的。一个Element代表了一个Widget配置在树里的特定位置的使用。随着时间变化,每个Widget与一个可以改变的Element关联。

...

mysqlrouter使用

May 16, 2023
Mysql, Router
Mysqlrouter

What #

mysqlrouter是一个代理,可以将查询转发到配置好的数据库服务里。

Why #

在办公室网络环境下基于win10 wsl2开发应用时,需要连接到主机所在局域网的其它机器上的数据库服务。

也就是说,存在机器:wsl2、主机、其它机器。

wsl2通过NAT网络模式与主机互通,并且wsl2可以访问外网。

但是wsl2不能访问到其它机器上的数据库服务,不知道是不是办公室网络环境存在限制。

为了使得wsl2能访问到其它机器上的数据库服务成立,在主机启动mysqlrouter充当代理,然后wsl2通过访问代理来访问其它机器

Install #

可以使用mysql installer选择安装。

简单模式 #

配置文件(mysqlrouter.conf):

[DEFAULT]
logging_folder = D:/Data/mysqlrouter/log
plugin_folder = C:/Program Files/MySQL/MySQL Router 8.0/lib # 这里是插件所在目录,必须是mysqlrouter安装路径下的目录,否则报错找不到插件
config_folder = D:/Data/mysqlrouter/etc # 启动配置默认查找目录,会在目录里寻找mysqlrouter.conf文件
runtime_folder = D:/Data/mysqlrouter/run
data_folder = D:/Data/mysqlrouter/data

[logger]
level = DEBUG

[routing:primary]
bind_address=172.20.96.1 # 主机ip地址
bind_port=6446 # 主机监听端口
destinations = 172.17.39.239:3306 # 目标机器,也就是实际执行查询的数据库服务所在机器的地址
mode = read-write
connect_timeout = 10

启动:mysqlrouter -c D:\Data\mysqlrouter\etc\mysqlrouter.conf

...

NATS

April 24, 2023
Go, NATS
NATS

是什么? #

Home, Github

NATS 是一个简单、安全和高性能的通信系统,适用于数字系统、服务和设备。

NATS 是一种允许以消息形式分段的数据交换的基础架构。

基于主题 #

发布者将消息发到主题;订阅者订阅主题,在有消息到来时消费该消息。

主题命名规则:

基本字符:a to z, A to Z and 0 to 9 (区分大小写,不能包含空白字符).

特殊字符: . (分割符,分割不同部分,每部分视为一个token); * 和 > (通配符,*表示匹配一个token,>表示匹配一或多个token).

保留主题名称: 以 $ 开头的用在系统内部 (如:$SYS, $JS, $KV …)

发布-订阅 #

Core NATS: 一个主题,存在一个发布者,多个订阅者。

消息会复制到多个订阅者。

请求-响应 #

A request is sent, and the application either waits on the response with a certain timeout, or receives a response asynchronously.

– 请求发出后,应用要不等待响应超时,要不就异步收到一个响应。

...

Go Generic Join

January 6, 2023
Go, Generic, Join
Join

// NestedJoin like nested loop join
func NestedJoin[J, K, R any](
	left []J,
	right []K,
	match func(J, K) bool,
	mapper func(J, K) R,
) []R {
	var r = make([]R, 0, len(left))

	for _, j := range left {
		for _, k := range right {
			if !match(j, k) {
				continue
			}
			r = append(r, mapper(j, k))
		}
	}

	return r
}

// HashJoin like hash join
func HashJoin[K comparable, LE, RE, R any](
	left []LE,
	right []RE,
	lk func(item LE) K,
	rk func(item RE) K,
	mapper func(LE, RE) R,
) []R {
	var r = make([]R, 0, len(left))

	rm := KeyBy(right, rk)

	for _, le := range left {
		k := lk(le)
		re := rm[k]
		r = append(r, mapper(le, re))
	}

	return r
}

Code From

...

Jump Table

July 20, 2022
Structure
Jump Table

What’s Jump Table? #

A jump table can be either an array of pointers to functions or an array of machine code jump instructions. If you have a relatively static set of functions (such as system calls or virtual functions for a class) then you can create this table once and call the functions using a simple index into the array. This would mean retrieving the pointer and calling a function or jumping to the machine code depending on the type of table used.

...

数字计算之分摊

July 20, 2022
Number
Calculate

背景:分多次把一批货全部出清。

要求:需要确保这批货多次出清跟一次出清收的钱一样。

现有三个数字(可整数,可小数):a b c,其中:a 为数量,b 为价格,c 为折扣。

则总额为: t, t = a*b*c

假设分三次,每次数量为:a1 a2 a3,则有:a = a1 + a2 + a3

  1. 直接计算:

第 1 次.

a1*b*c

第 2 次.

a2*b*c

第 3 次.

a3*b*c

(a1+a2+a3)bc 不就等于 abc 了吗?

但是,如果考虑到小数乘法计算时的精度,比如:1.22*2.33 相乘后再取精度(保留两位小数),不就会导致数量误差了吗?

那如果取精度导致结果误差,那我不取精度,直接用所有小数位数来计算呢。

虽说可以,但小数位数是有可能非常多的,占用的空间也是一笔不小的开销。

  1. 引入中间量(可称为’余额’): x y z
x = a
y = x*b
z = y*c

第 1 次.

x1 = (x-a1)
y1 = (y-y*a1/x)
z1 = (z-z*a1/x)
t1 = z*a1/x

第 2 次.

x2 = (x1-a2)
y2 = (y1-y1*a2/x1)
z2 = (z1-z1*a2/x1)
t2 = z1*a2/x1

第 3 次.

...

泛型

May 30, 2022
Go, TypeScript
Generic

泛型 #

是什么? #

Type parameter, 类型参数。func Add[T Number](x, y T) (r T),其中的T就是类型参数,它被接口Number所约束。

type Number interface {
    int | float32
}

调用方除了可自行决定参数值之外,还可以自行决定参数类型。Add[int](1, 2),在调用时指定T的类型为int,同时传入参数值1,2必须是int类型。

这样使得代码更灵活,更有扩展性,同时更安全。

Go泛型 #

为什么? #

静态语言,类型固定,比如这个函数:func Add(x, y int) int就要求参数和结果都必须是整型。

那如果后来又需要一个浮点数的加法呢?

固定类型

那使用interface{}不也可以吗?

固定类型

试看:

// 准确的描述出了参数和返回值的类型,非常方便
func Add(x, y int) int

// 但也限制了Add函数的参数类型--只能接收`int`
// Add(0.1, 0.2) // can't do that

// 那怎么办呢?再写一个针对float64的呗
func AddFloat64(x, y float64) float64

AddFloat64(0.1, 0.2) // it's ok

// 如果还要支持其它类型呢?再加一个吗,每多一种类型,就多加一个。。。
func AddInt8(x, y int8) int8
func AddInt32(x, y int32) int32
func AddFloat32(x, y float32) float32
// more...

// emm.

// how about interface{}?
func AddAny(x, y interface{}) interface{} {
    switch x.(type) {
        case int:
        case int8:
        case int32:
        case float32:
        case float64:
        // more...
        default:
        panic("not support type")
    }
}
// interface{}表示可以接收任意类型的值,并且返回任意类型的值
// 换言之,参数的类型和返回值的类型没有必然联系--从签名看来,它们可以一样,也可以不一样
// 所以,使用interface{}不够安全。

func AddGeneric1[T any](x, y T) T // 看起来跟AddAny差不多,但是参数类型和返回值类型必然是相同的

// 但any并不一定支持+运算符,所以需要用更细粒度的约束
type Number interface {
    ~int|~int8|~int32|~float32|~float64
}
func AddGeneric2[T Number](x, y T) T // 通过Number约束,确保类型参数可加

// 泛型的存在,使得函数的类型集比any小,比int大;使得返回值和参数的类型能够动态联系。

func Map[T, E any](list []T, f func(T) E) []E {
    r := make([]E, len(list))
    for i := range list {
        r[i] = f(list[i])
    }
    return r
}

类型参数

...

k8s是怎么维持pod的运行的呢?

May 13, 2022
K8s
Pod

k8s是怎么维持pod的运行的呢? #

当接收了yaml配置的信息后,是怎么维持pod根据声明一直运行的呢?

让我们沿着命令执行的过程来一睹为快:kubectl apply -f pod.yaml.

源码位置:cmd/kubectl/kubectl.go -> staging/src/k8s.io/kubectl/pkg/cmd/cmd.go -> staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go

最终的执行方法:

func (o *ApplyOptions) Run() error {
    // 预处理
	if o.PreProcessorFn != nil {
		klog.V(4).Infof("Running apply pre-processor function")
		if err := o.PreProcessorFn(); err != nil {
			return err
		}
	}

	// Enforce CLI specified namespace on server request.
	if o.EnforceNamespace {
		o.VisitedNamespaces.Insert(o.Namespace)
	}

	// Generates the objects using the resource builder if they have not
	// already been stored by calling "SetObjects()" in the pre-processor.
	errs := []error{}
	infos, err := o.GetObjects()
	if err != nil {
		errs = append(errs, err)
	}
	if len(infos) == 0 && len(errs) == 0 {
		return fmt.Errorf("no objects passed to apply")
	}
	// Iterate through all objects, applying each one.
	for _, info := range infos {
		if err := o.applyOneObject(info); err != nil {
			errs = append(errs, err)
		}
	}
	// If any errors occurred during apply, then return error (or
	// aggregate of errors).
	if len(errs) == 1 {
		return errs[0]
	}
	if len(errs) > 1 {
		return utilerrors.NewAggregate(errs)
	}

	if o.PostProcessorFn != nil {
		klog.V(4).Infof("Running apply post-processor function")
		if err := o.PostProcessorFn(); err != nil {
			return err
		}
	}

	return nil
}

// applyOneObject里会调用以下方法
func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte, options *metav1.PatchOptions) (runtime.Object, error) {
	if options == nil {
		options = &metav1.PatchOptions{}
	}
	if m.ServerDryRun {
		options.DryRun = []string{metav1.DryRunAll}
	}
	if m.FieldManager != "" {
		options.FieldManager = m.FieldManager
	}
	if m.FieldValidation != "" {
		options.FieldValidation = m.FieldValidation
	}
	return m.RESTClient.Patch(pt).
		NamespaceIfScoped(namespace, m.NamespaceScoped).
		Resource(m.Resource).
		Name(name).
		SubResource(m.Subresource).
		VersionedParams(options, metav1.ParameterCodec).
		Body(data).
		Do(context.TODO()). // 调用api,把apply请求发到主节点,记录信息到etcd之后,再创建出相应的pod
		Get()
}

// 那么,接收并处理这个Patch请求的代码在哪里呢?

// NewStreamWatcher creates a StreamWatcher from the given decoder.
func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher {
	sw := &StreamWatcher{
		source:   d,
		reporter: r,
		// It's easy for a consumer to add buffering via an extra
		// goroutine/channel, but impossible for them to remove it,
		// so nonbuffered is better.
		result: make(chan Event),
		// If the watcher is externally stopped there is no receiver anymore
		// and the send operations on the result channel, especially the
		// error reporting might block forever.
		// Therefore a dedicated stop channel is used to resolve this blocking.
		done: make(chan struct{}),
	}
	go sw.receive() // 接收请求,然后通过chan发送出去,再由其它代码来处理?
	return sw
}

// TODO:

apimachinery共享库

...

Go enum

May 12, 2022
Go
Enum

Go enum #

Go是没有内置枚举类型的,那么,当需要使用枚举时,该怎么办呢?

枚举说白了,就是一连串互斥的值,每个值代表一样事物或一个类型。

比如,现在需要一个颜色枚举,可以这样定义:

const (
    Red = "Red" // 红色
    Blue = "Blue" // 蓝色
    Green = "Green" // 绿色
)

也有这样定义的:

type Color string // 定义一个特定类型

// 枚举常量均声明为该类型
const (
    Red     Color = "Red" // 红色
    Blue    Color = "Blue" // 蓝色
    Green   Color = "Green" // 绿色
)

这样做的好处是可以通过这个类型来更明显的标记出枚举字段来:

type Car struct {
    Name string
    Color Color // 颜色字段声明为Color类型,在阅读代码的时候就能知道这个字段正常的可选值范围
}

但是,上面的做法都需要面临一个问题,就是我需要一个返回全部枚举值的集合时,需要这样做:

func All() []Color {
    return []Color{
        Red,
        Blue,
        Green,
    }
}

func (color Color) Name() string {
    switch color {
    case Red:
        return "红色"
    case Blue:
        return "蓝色"
    case Green:
        return "绿色"
    }
    return ""
}

当在定义处新增值时,AllName也要同步添加,对于开发人员来说,非常容易遗漏。

...