首页
关于
留言
接口
搜索
首页
登录
登录
搜索
KAKA 梦很美
累计撰写
47
篇文章
累计收到
0
条评论
首页
栏目
首页
登录
页面
首页
关于
留言
接口
包含标签 【Golang】 的文章
2023-1-20
深入理解Golang中的chan特性之上篇
Golang 的一大特色就是其简单高效的 天然并发机制。 channel 是 Golang 语言中的一个 核心数据类型, 可以把他看成通道(管道), 所以他非常重要。主要用来解决go程的同步问题以及协程之间数据共享(数据传递)的问题。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。 Golang 中使用 goroutine 和 channel 实现了 CSP (Communicating Sequential Processes) 模型, channel 在 goroutine 的通信和同步中承担着重要的角色。 goroutine 运行在相同的地址空间,因此访问共享内存必须做好同步。 goroutine 奉行通过通信来共享内存,而不是共享内存来通信。 引用类型 channel 可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。 Channel 的定义与使用 和 map 类似,channel 也是一个对应 make 创建的底层数据结构的引用。 当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel 引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。 定义一个channel时,也需要定义发送到channel的值的类型。channel可以使用内置的make()函数来创建。 我们看一段代码: package main import ( "fmt" ) func main() { ch := make(chan int) // 这里就是创建一个无缓冲的 channel go func() { for i := 0; i <= 6; i++ { ch <- i // 循环写入管道数据 fmt.Println("写入管道数据", i) } }() for j := 0; j <= 6; j++ { value := <- ch // 循环读出管道数据 fmt.Println("读出管道数据", value) } } 我们在代码中 先创建了一个匿名函数的子go程,和main的主go程一起争夺 CPU,但是我们在里面创建了一个管道,无缓冲管道有一个规则那就是必须读写同时操作才会有效果,如果只进行读或者只进行写那么会被阻塞,被暂时停顿等待另外一方的操作,在这里我们定义了一个容量为0的通道,这就是无缓冲通道。 我们再看下面的代码加深 无缓冲通道 的印象 var c = make(chan int) go func() { // 等待 3秒后循环写入值到通道 time.Sleep(3 * time.Second) for i := 0; i < 5; i++ { c <- i } }() select { case value := <-c: fmt.Println(value) } fmt.Println("到此为止") // 输出结果: select 阻塞了 channel 通道, 将会先等待 3秒后打印 0, 然后打印 '到此为止' var c = make(chan int) go func() { // 等待 3秒后循环写入值到通道 time.Sleep(3 * time.Second) for i := 0; i < 5; i++ { c <- i } }() select { case value := <-c: fmt.Println(value) default: fmt.Println("我不等了") } fmt.Println("到此为止") // 输出结果: select 阻塞了 channel 通道, 但是有带 default, 将会直接打印 '我不等了', 然后打印 '到此为止' var c = make(chan int) go func() { // 等待 3秒后循环写入值到通道 time.Sleep(3 * time.Second) for i := 0; i < 5; i++ { c <- i } }() fmt.Println("等待中") <-c fmt.Println("到此为止") // 输出结果: 先打印 '等待中', 等待 3秒后打印 '到此为止' var c = make(chan int) go func() { // 等待 3秒后循环写入值到通道 time.Sleep(3 * time.Second) for i := 0; i < 5; i++ { c <- i } }() fmt.Println("等待中") value := <-c fmt.Println(value) fmt.Println("到此为止") // 输出结果: 先打印 '等待中', 等待 3秒后打印 0, 然后打印 '到此为止' 使用无缓冲的通道在 Goroutine 之间同步数据 (一次只能传输一个数据) 总结一下就是无缓冲特性: 同一时刻,同时有 读、写两端把持 channel 如果只有读端,没有写端,那么 “读端”阻塞 如果只有写端,没有读端,那么 “写端”阻塞 读channel: <- channel 写channel: channel <- 数据 使用有缓冲的通道在 Goroutine 之间同步数据 (读写数据可以同时进行) 我来描述下上面的步骤: 第一步 | 右侧的 goroutine 正在从通道接收一个值。 第二步 | 右侧的这个 goroutine 独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新值到通道里。 第三步 | 左侧的 goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。 第四步 | 所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。 有缓冲通道就是图中所示,一方可以写入很多数据,不用等对方的操作,而另外一方也可以直接拿出数据,不需要等对方写,但是注意一点(如果写入的一方把channel写满了,那么如果要继续写就要等对方取数据后才能继续写入,这也是一种阻塞,读出数据也是一样,如果里面没有数据则不能取,就要等对方写入)。 有缓冲channel的定义很简单: ch := make(chan int, 10) // chan int 只能写入读入int类型的数据, 10代表容量。 总结一下就是有缓冲特性: 写数据时, 直到缓冲区被填满后,“写端”才会阻塞 读数据时, 缓冲区被读空,“读端”才会阻塞 len: 代表缓冲区中,剩余元素个数 cap: 代表缓冲区的容量 最后, 举个简单的例子再次帮助理解 channel: 同步通信 (无缓冲channel): 数据发送端,和数据接收端,必须同时在线。 比如打电话, 只有等对方接收才会通,要不然只能阻塞。 异步通信 (有缓冲channel): 数据发送端 发送完数据后立即返回。数据接收端 有可能立即读取,也可能延迟处理。 不用等对方接受, 只需发送过去就行, 比如发短信, 发邮件...
2023年-1月-20日
222 阅读
0 评论
GoLang
2022-8-11
简单实现Go+PHP gRPC通信
背景 工作中难免会遇到单语言无法解决的问题(谨慎点描述就是 单语言实现起来比较麻烦、吃力、复杂), 导致产品需求的实现没这么完美。这时候,我们可以给它添上一双翅膀,大家调侃最多的应该就是Golang赋予PHP能量了吧。 题外话, 其实我认为: 语言之争没有意义,语言只是工具, 它只是为了帮助我们更好地解决问题。当单语言无法满足需求的时候,可以根据业务和成本来决定是否利用其他语言来实现。 多一门技术就多一条门路,只局限于一种语言,您可能就无法前行。 举个例子: 公司3个业务部门都使用PHP语言, 另外一个中台服务部门使用Java, Golang, Python。此时公司想要打通这3个业务部门的用户体系,将各业务部门的用户关联起来形成互通。这个串联工作肯定需要中台服务部门来开发,一切的规范由中台定义。由于跨语言,在中台招聘PHP开发也不太现实,业务部门抽离人员去支持中台也不可能(那要中台干嘛?)。这个时候有两个选择: HTTP 协议传输 gRPC 远程调用 如果您的业务调用服务频率较高,采用 HTTP 可能不太行,这个时候我们需要 gRPC 通信。 至于什么是gRPC, 可以 点击查阅官方文档 说了这么多, 如果您了解 gRPC, 上面的内容可以忽略, 当然 您可能已经读完了, 嘿嘿。废话说完, 我们开始实现 Golang+PHP 吧, 这里只是实现简单案例: 通过 Go 获取系统Cpu、内存、磁盘信息。 实现 如果您本地没有安装 protoc, 请先安装。 我使用的是 Mac 环境 brew install protobuf 验证是否安装成功 protoc --version 如果提示没有 protoc 命令, 你可能需要将 protoc 加入到环境变量(查找 protoc 执行文件 find / -name protoc), 加入环境变量后执行 source ~/.bash_profile 如果您的 PHP 没有安装gRPC 扩展, 请先安装。 pecl install grpc 我们先编写 Golang 程序, 在这里我默认您会安装 Go, 如果不会安装并且您是 Mac 操作系统 可以参考Mac-brew-安装-Golang 新建项目 mkdir go-grpc 确保环境变量已开启 Module, 可项目根目录执行 export GO111MODULE=on 创建 go.mod (包管理,类似 composer) go mod init go-grpc 安装 grpc 和 protobuf 包 go get google.golang.org/grpc go get google.golang.org/protobuf/reflect/protoreflect@v1.25.0 创建 proto 文件 创建 system/system.proto 文件, 写入内容 syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本 package system; // 表示生成的go文件的存放地址,会自动生成目录。 option go_package = "./system"; // 定义服务 service System { // 获取系统信息接口 rpc GetSystemInfo (GetSystemInfoRequest) returns (GetSystemInfoResponse) {} } // 获取系统信息接口请求参数 message GetSystemInfoRequest {} // 获取系统信息接口返回值 message GetSystemInfoResponse { double cpuPercent = 1; // CPU使用率 double memPercent = 2; // 内存使用率 double diskPercent = 3; // 磁盘使用率 string cpuGHz = 4; // CPU主频 int32 cpuCounts = 5; // CPU核数 string memTotal = 6; // 总内存 string memUsed = 7; // 剩余内存 string diskTotal = 8; // 磁盘总大小 string diskUsed = 9; // 磁盘剩余大小 } 编写 shell 文件 创建system/system_rpc.sh 文件, 写入内容 #! /bin/sh # 系统服务 - 该文件目录下执行生成GO RPC文件命令 protoc -I $(pwd)/ $(pwd)/system.proto --go_out=plugins=grpc:./rpc 给文件赋予可执行权限 chmod -R 755 system/system_rpc.sh 生成 GO 代码 进入 cd system 目录 新建 rpc 目录 mkdir rpc 执行 ./system_rpc.sh 此时, 在 system/rpc 目录已生成 system/system.pb.go 文件 安装 gopsutil 包获取系统信息 go get github.com/shirou/gopsutil go get github.com/tklauser/go-sysconf 编写获取系统信息代码 新建 internal/utils/system.go 文件, 写入内容 package utils import ( "fmt" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/mem" "time" ) // 获取 cpu 使用率 func GetCpuPercent() float64 { percent, _ := cpu.Percent(time.Second, false) return percent[0] } // 获取 内存 使用率 func GetMemPercent() float64 { memInfo, _ := mem.VirtualMemory() return memInfo.UsedPercent } // 获取 磁盘 使用率 func GetDiskPercent() float64 { diskInfo, _ := disk.Usage("/") return diskInfo.UsedPercent } // 字节的单位转换 保留两位小数 func FormatFileSize(fileSize int64) (size string) { if fileSize < 1024 { // return strconv.FormatInt(fileSize, 10) + "B" return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1)) } else if fileSize < (1024 * 1024) { return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024)) } else if fileSize < (1024 * 1024 * 1024) { return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024)) } else if fileSize < (1024 * 1024 * 1024 * 1024) { return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024)) } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) { return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024)) } else { // if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024) return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024)) } } 新建 rpc/logic/systeminfo.go 文件, 写入内容 package logic import ( "context" "fmt" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/mem" "go-grpc/system/internal/utils" "go-grpc/system/rpc/system" ) type SystemInfoLogic struct { ctx context.Context } func NewSystemInfoLogic(ctx context.Context) *SystemInfoLogic { return &SystemInfoLogic{ ctx: ctx, } } // 获取系统信息 func (l *SystemInfoLogic) GetSystemInfo(request *system.GetSystemInfoRequest) (*system.GetSystemInfoResponse, error) { var ( cpuGHz = "0GHz" ) c, _ := cpu.Info() for _, v := range c { if v.Mhz > 0 { cpuGHz = fmt.Sprintf("%v0GHz", v.Mhz/1000) } } cpuCounts, _ := cpu.Counts(true) m, _ := mem.VirtualMemory() w, _ := disk.Usage("/") return &system.GetSystemInfoResponse{ CpuPercent: utils.GetCpuPercent(), MemPercent: utils.GetMemPercent(), DiskPercent: utils.GetDiskPercent(), CpuGHz: cpuGHz, CpuCounts: int32(cpuCounts), MemTotal: utils.FormatFileSize(int64(m.Total)), MemUsed: utils.FormatFileSize(int64(m.Used)), DiskTotal: utils.FormatFileSize(int64(w.Total)), DiskUsed: utils.FormatFileSize(int64(w.Total) - int64(w.Free)), }, nil } 新建服务端文件 进入 cd rpc 目录 新建 server 目录 mkdir server 进入 server 目录, 新建 system.go 文件, 写入内容 package server import ( "context" "go-grpc/system/rpc/logic" "go-grpc/system/rpc/system" ) // 系统信息服务 type System struct {} // 获取系统信息 func (server *System) GetSystemInfo(ctx context.Context, request *system.GetSystemInfoRequest) (*system.GetSystemInfoResponse, error) { l := logic.NewSystemInfoLogic(ctx) return l.GetSystemInfo(request) } 编写服务启动文件 system 目录下新建 server.go 文件, 写入内容 package main import ( "fmt" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "net" "go-grpc/system/rpc/server" "go-grpc/system/rpc/system" ) func main() { // 监听本地的 10000 端口 lis, err := net.Listen("tcp", ":10000") if err != nil { fmt.Printf("failed to listen: %v", err) return } s := grpc.NewServer() // 创建 GRPC 服务器 system.RegisterSystemServer(s, &server.System{}) // 在 GRPC 服务端注册服务 reflection.Register(s) // 在给定的 GRPC 服务器上注册服务器反射服务 // Serve 方法在 lis 上接受传入连接,为每个连接创建一个 ServerTransport 和 server 的 goroutine。 // 该 goroutine 读取 GRPC 请求,然后调用已注册的处理程序来响应它们。 err = s.Serve(lis) if err != nil { fmt.Printf("failed to serve: %v", err) return } } 项目目录结构 . ├── go.mod ├── go.sum └── system ├── internal │ └── utils │ └── system.go ├── rpc │ ├── logic │ │ └── systeminfo.go │ ├── server │ │ └── system.go │ └── system │ └── system.pb.go ├── server.go ├── system.proto └── system_rpc.sh 启动服务 go run server.go 此时, Golang 服务已经编写完成!!! 接下来, 我们编写 PHP 代码, 在这里我默认您用了composer 包管理。 导入 grpc 和 protobuf 包 composer require grpc/grpc composer require google/protobuf 新建 gRPC 目录 为了方便演示, 我们在项目根目录下新建 mkdir -p grpc/system 文件夹, 然后进入到该目录cd grpc/system 创建 touch system.proto 文件, 写入内容 syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本 package System; // 系统服务 service System { // 获取系统信息接口 rpc GetSystemInfo (GetSystemInfoRequest) returns (GetSystemInfoResponse) {} } // 获取系统信息接口请求参数 message GetSystemInfoRequest {} // 获取系统信息接口返回值 message GetSystemInfoResponse { double cpuPercent = 1; // CPU使用率 double memPercent = 2; // 内存使用率 double diskPercent = 3; // 磁盘使用率 string cpuGHz = 4; // CPU主频 int32 cpuCounts = 5; // CPU核数 string memTotal = 6; // 总内存 string memUsed = 7; // 剩余内存 string diskTotal = 8; // 磁盘总大小 string diskUsed = 9; // 磁盘剩余大小 } 编写 shell 文件 创建 system.sh 文件, 写入内容 #! /bin/sh # 系统服务 - 该目录文件下执行生成 PHP 文件命令 protoc -I $(pwd)/ $(pwd)/system.proto --php_out=../ 给文件赋予可执行权限chmod -R 755 system.sh 添加自动加载命名空间 在 composer.json 文件的 autoload 配置增加如下 "autoload":{ "psr-4":{ "GPBMetadata\\":"grpc/GPBMetadata/", "System\\":"grpc/system/" } }, 然后执行 composer dump-autoload 生成 PHP 代码 在 grpc/system目录下执行 ./system.sh 生成 PHP 代码文件, 此时我们 Tree 看看目录结构 . ├── composer.json ├── composer.lock ├── vendor └── grpc ├── GPBMetadata │ └── System.php ├── system │ ├── GetSystemInfoRequest.php │ ├── GetSystemInfoRequest.php │ └── system.proto │ └── system.sh 编写客户端文件 我们进入到 grpc/system 目录新建客户端文件 SystemClient.php 调用系统服务接口, 写入内容 <?php namespace System; class SystemClient extends \Grpc\BaseStub { public function __construct($hostname, $opts, $channel = null) { parent::__construct($hostname, $opts, $channel); } public function getSystemInfo(\System\GetSystemInfoRequest $argument, $metadata = [], $options = []) { return $this->_simpleRequest( '/system.System/GetSystemInfo', $argument, [\System\GetSystemInfoResponse::class, 'decode'], $metadata, $options ); } } 编写调用服务文件 在根目录新建 index.php 文件, 写入内容 <?php require 'vendor/autoload.php'; // 创建客户端实例 $client = new \System\SystemClient('127.0.0.1:10000', [ 'credentials' => \Grpc\ChannelCredentials::createInsecure() ]); $request = new \System\GetSystemInfoRequest(); $reponse = $client->getSystemInfo($request)->wait(); list($reply, $status) = $reponse; $data['system'] = [ 'cpuPercent' => '0%', 'memPercent' => '0%', 'diskPercent' => '0%', 'cpuGHz' => '0GHz', 'cpuCounts' => 0, 'memTotal' => '0GB', 'memUsed' => '0GB', 'diskTotal' => '0GB', 'diskUsed' => '0GB', ]; if ($status->code === 0) { $data['system']['cpuPercent'] = floatval(sprintf("%.2f", $reply->getCpuPercent())) . '%'; $data['system']['memPercent'] = floatval(sprintf("%.2f", $reply->getMemPercent())) . '%'; $data['system']['diskPercent'] = floatval(sprintf("%.2f", $reply->getDiskPercent())) . '%'; $data['system']['cpuGHz'] = $reply->getCpuGHz(); $data['system']['cpuCounts'] = $reply->getCpuCounts(); $data['system']['memTotal'] = $reply->getMemTotal(); $data['system']['memUsed'] = $reply->getMemUsed(); $data['system']['diskTotal'] = $reply->getDiskTotal(); $data['system']['diskUsed'] = $reply->getDiskUsed(); } var_dump($data); 运行调用 根目录执行 php index.php 输出内容如下: array(1) { ["system"]=> array(9) { ["cpuPercent"]=> string(6) "10.97%" ["memPercent"]=> string(6) "69.17%" ["diskPercent"]=> string(6) "26.89%" ["cpuGHz"]=> string(7) "2.80GHz" ["cpuCounts"]=> int(8) ["memTotal"]=> string(7) "16.00GB" ["memUsed"]=> string(7) "11.13GB" ["diskTotal"]=> string(8) "233.47GB" ["diskUsed"]=> string(8) "175.94GB" } } 到这里整个功能完成, 贴上 PHP 端最终目录结构 . ├── index.php ├── composer.json ├── composer.lock ├── vendor └── grpc ├── GPBMetadata │ └── System.php ├── system │ ├── GetSystemInfoRequest.php │ ├── GetSystemInfoRequest.php │ └── system.proto │ └── system.sh │ └── SystemClient.php 以上使用的是原生态 PHP, 在此推荐使用 Hyperf 实现 gRPC 客户端。
2022年-8月-11日
219 阅读
0 评论
服务架构
2022-1-18
Mac brew 安装 Golang
最近分析了一下主流市场的编程语言,发现 Golang 语言还算比较火热(有上升势头),准备开始探索一下。 安装 brew install go 安装过程大同小异,会自动安装 Golang 的最新稳定版本,因为我已经安装好了,没有截图,所以剽窃了一张别人的,好让大家有个参考 出现上面的结果就表示安装成功了,我们在终端输入 go version 查看我们的安装版本,我的显示 go version go1.12.5 darwin/amd64,表示我安装的是 v1.12.5 版本。 从安装提示中可以看出需要设置 GOPATH 和 GOROOT 的环境变量,以及设置 PATH. 温馨提示: Go >1.13 版本之后支持通过设置环境变量 GOPROXY 来修改代理地址, 默认代理服务器 https://proxy.golang.org 在国内访问经常出现 timeout 可以通过设置国内代理来加速下载, 在终端执行即可: go env -w GOPROXY=https://goproxy.cn,direct 配置GOPATH 查看 Golang 的环境变量设置的命令 go env 我们需要配置我们的环境变量,在 ~ 目录下使用 ll -a 命令查看是否有 .bash_profile 文件,如果没有,则创建一个,有就 vi 编辑,下面给大家两种示例(我的源码库没有跟安装目录放在一起): 1) 单源码库环境变量配置 # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export GOPATH=/Users/linshan/Web/golang/go export GOBIN=$GOPATH/bin export PATH=$PATH:$GOBIN 2) 单源码库环境变量配置 # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export GOPATH=/Users/linshan/Web/golang/go (:自由添加目录,其他不变) export GOBIN=$GOPATH/bin export PATH=$PATH:${GOPATH//://bin:}/bin 使修改立刻生效: source ~/.bash_profile 再执行 go env 就可以看到环境变量配置了。 实例说明 GOPATH:为我们开发常用的目录,建议不要和 Go 的安装目录一致,在该文件夹下又有三个文件夹:src、pkg、bin。 这里src是自己新建的, pkg和bin是后面生成的 src:主要存放我们的源代码 bin:存放编译后生成的可执行文件,可以自己执行 pkg: 编译后生成的文件(.a文件)(非 main 函数的文件在 go install 后生成) GOBIN:是 GOPATH 下的bin目录 PATH:环境变量,需要go-bin目录加入到path路径下,生成可执行文件就可以直接运行了。 基础 Golang 安装到此啦~
2022年-1月-18日
225 阅读
0 评论
操作系统