自go-zero走进微服务

自go-zero走进微服务

在我最初看来,go-zero 最核心的价值体现在两点:
1、使用 .api 定义接口协议(Contract First)
2、使用 goctl 自动生成工程骨架,让开发者专注于业务逻辑

在使用中,我发现 go-zero 的核心并不止于脚手架,
而是一整套围绕“可维护性、可扩展性”的工程化约束体系。

后来真正进入项目后,我才逐渐意识到:
RPC(zrpc + etcd)才是 go-zero 支撑微服务架构的第二个关键支点。
而中间件、熔断、限流、链路最终,是第三个核心支点

参考:go-zero文档

配置环境

安装 goctl(go-zero 的脚手架)

go install github.com/zeromicro/go-zero/tools/goctl@latest goctl -v 

生成代码

方法一:

(这种方式,是直接一键生成)
等环境配置完毕后。
直接进入go编辑器,在命令行中输入!(切记是命令行)

goctl api new firstdemo 

然后你就会非常完美的发现。骨架已经搭建完毕。
有了这个,大家可以非常丝滑的将一个框架搭起。

在这里插入图片描述
方法二:

你首先创建一个.api文件:

syntax ="v1" info ( title:"gozero-demo" desc:"first api" author:"you" version:"1.0")type( PingReq { name string`form:"name,optional"`} PingResp { message string`json:"message"`} CreateReq { title string`json:"title"` content string`json:"content"`} CreateResp { id int64`json:"id"`}) service demo-api { @handler Ping get /ping (PingReq) returns (PingResp) @handler CreatePost post /posts (CreateReq) returns (CreateResp)}

我简洁的解释一下
get/ping:GET 参数通常用 form:“”(对应 query)
post /posts:POST JSON 用 json:“”
@handler Xxx:生成的 handler/logic 名字,也就是生成对应的router、控制器、业务层代码…

然后执行命令:

goctl api go-api demo.api -dir .
go_zero_project/ ├── demo.api # API 协议定义(接口契约,最核心) │ ├── demo-api.go # 程序入口(main) │ └── main() │ └── 启动 HTTP Server │ ├── etc/ │ └── demo-api.yaml # 配置文件(端口 / DB / Redis / JWT 等) │ ├── internal/ │ │ │ ├── handler/ # HTTP 层(参数 → 逻辑) │ │ ├── pinghandler.go │ │ └── createposthandler.go │ │ │ ├── logic/ # 业务逻辑层(核心代码写这里) │ │ ├── pinglogic.go │ │ └── createpostlogic.go │ │ │ ├── types/ # 请求 / 响应结构体(由 .api 生成) │ │ ├── ping.go │ │ └── createpost.go │ │ │ └── svc/ │ └── servicecontext.go # 依赖注入(DB / Redis / RPC Client) │ └── go.mod # Go Module 定义 

热加载

咱们这里,先在命令行中下载:

go install github.com/air-verse/air@latest air -v 

下载完成之后,再次输入:

air 

你会在命令行中发现漂亮的:

在这里插入图片描述
作用:

这里重点说一下 air 起到作用的3个点:

  • 监听文件变化
  • 自动 go build
  • 杀掉旧进程 → 启动新进程

实现GET/POST/PUT/DELETE

现在把目录清空。
然后新创建一个first.api的文件,并依次把下方四种不同类型的粘贴进去。

get 查询
// 获取用户 type GetUserReq { id int64 `form:"id"` } type GetUserResp { id int64 `j son:"id"` name string `json:"name"` } service demo-api { @handler GetUser get /user (GetUserReq) returns (GetUserResp) } 
post 创建
// 创建用户 type CreateUserReq { name string `json:"name"` age int `json:"age"` } type CreateUserResp { id int64 `json:"id"` } service demo-api { @handler CreateUser post /user (CreateUserReq) returns (CreateUserResp) } 
put 更新
// 更新用户 type UpdateUserReq { id int64 `path:"id"` name string `json:"name"` } type UpdateUserResp { ok bool `json:"ok"` } service demo-api { @handler UpdateUser put /user/:id (UpdateUserReq) returns (UpdateUserResp) } 
delete 删除
// 删除用户 type DeleteUserReq { id int64 `path:"id"` } type DeleteUserResp { ok bool `json:"ok"` } service demo-api { @handler DeleteUser delete /user/:id (DeleteUserReq) returns (DeleteUserResp) } 

之后,运行命令:

 goctl api go -api firstdemo.api -dir . 

之后,你就会得到这样一个帅气的目录,拥有基础的增删改查

在这里插入图片描述

动态路由

在我看来,只要仅操作一个资源的,都可以考虑用动态路由。

动态路由很简单,只需要简单的3步走即可。

  • 路由路径利用 :变量名
get /articles/:id 

:id 表示这里是一个占位符

  • 请求体里用 path:"变量名"
type GetArticleReq { id int64 `path:"id"` } 

切记 path:“id” 必须和 :id 一模一样!否则解析不到值。

  • method + handler 正确绑定
@handler GetArticle get /articles/:id (GetArticleReq) returns (GetArticleResp) 
多个参数

可能就会有人问了,多个参数了怎么办?
简直太好办了

GET /users/:userId/articles/:articleId 
type GetUserArticleReq { userId int64 `path:"userId"` articleId int64 `path:"articleId"` } 

中间件

1、首先你要了解什么是中间件?
2、go-zero中的中件长什么样?
3、如何挂在到路由上?
4、他的职责是什么?
请带着这些疑问来阅读接下来的内容。

无中间件:

一条没有中间件的访问路径大致是这样:

HTTP Request ↓ handler ↓ logic ↓ response 

但现实项目里,你经常要做些和业务无关、但每个接口都要的事:
比如:
1、校验登录态 / Token
2、打日志
3、统一鉴权(验证权限)
4、限流(限制并发的)
5、统计耗时
等等…
如果你把这些都写进 logic 或 handler,结果是:
造成的后果,不仅是每个接口重复写、业务逻辑被污染,
更是使后期根本维护不动!

挂载上中间件

中间件 = 在请求进入 handler 之前 / 返回响应之前,插一段统一逻辑。
如下:

HTTP Request ↓ Middleware(Auth / Log / RateLimit) ↓ handler ↓ logic ↓ response 
在框架中的位置
internal/ ├── middleware/ ← 中间件目录 │ └── authmiddleware.go │ ├── handler/ ← HTTP 参数解析 ├── logic/ ← 业务逻辑 ├── svc/ │ └── servicecontext.go ← 依赖注入(中间件从这拿资源) 

记住:中间件 ≠ handler ≠ logic
它是作为一个独立层存在的。

中间件的样子
// Code scaffolded by goctl. Safe to edit.// goctl 1.9.2package middleware import"net/http"type AuthMiddleware struct{}funcNewAuthMiddleware()*AuthMiddleware {return&AuthMiddleware{}}func(m *AuthMiddleware)Handle(next http.HandlerFunc) http.HandlerFunc {returnfunc(w http.ResponseWriter, r *http.Request){// TODO generate middleware implement function, delete after code implementation// Passthrough to next handler if neednext(w, r)}}

如何生成?

我新建了一个文件:auth.api

syntax = "v1" type GetUserReq { id int64 `path:"id"` } type GetUserResp { id int64 `json:"id"` } @server ( group: user middleware: Auth // 这里 ) service user-api { @handler GetUser get /users/:id (GetUserReq) returns (GetUserResp) } 

通过运行

goctl api go -api auth.api -dir . 

@doc与import

@doc是什么?

@doc 是写在路由上的“接口说明元数据”,用于生成文档/让接口更可读。

@doc "获取文章详情" @handler GetArticle get /articles/:id (GetArticleReq) returns (GetArticleResp) 
import 是什么?

当你的项目接口多了,一个 .api 文件会越来越大,变成“几千行的屎山”。
import 的作用是:
把 api 文件拆分成多个小文件,再由一个入口 api 汇总。

如下的布局方式:

api/ ├── auth.api # 入口(import 其它 api) ├── user.api # 用户相关 └── types.api # 公共 type(可选) 

auth.api

syntax = "v1" import "types.api" import "user.api" 

user.api

syntax = "v1" @server( group: user ) service user-api { @doc "获取用户详情" @handler GetUser get /users/:id (GetUserReq) returns (GetUserResp) } 

types.api

syntax = "v1" type GetUserReq { id int64 `path:"id"` } type GetUserResp { id int64 `json:"id"` name string `json:"name"` } 

最后命令行一生成:

 goctl api go -api auth.api -dir . 
在这里插入图片描述


在这里插入图片描述

RPC服务

首先,我们要先明确:
RPC = 远程函数调用(像本地函数一样调用远程服务)
但注意!RPC 的核心不是“远程”,而是:

  • 强接口约束(proto)
  • 高性能(HTTP/2 + protobuf)
  • 面向服务而不是面向资源

在go-zero里

API 服务 ——(rpc client)——> RPC 服务 ——> 业务逻辑 / DB 

在rpc与go-zero集合的项目中,往往少不了以下这四步:
1、 *.proto
→ 接口契约(定义有哪些方法、参数、返回值)

2、 goctl rpc new / protoc
→ 生成 RPC 服务骨架

3、etc/*.yaml
→ RPC 监听端口、服务名

4、internal/logic
→ 你真正写业务的地方

大家可以手动创建一个user的包,输入:

goctl rpc new 

你将会得到:

user/ ├── user.proto ├── user.go ├── etc/ │ └── user.yaml ├── internal/ │ ├── config/ │ ├── logic/ │ ├── server/ │ └── svc/ └── go.mod 

这样一个骨架。
此时你会看到,这样一份协议

syntax = "proto3"; package user; option go_package="./user"; message Request { string ping = 1; } message Response { string pong = 1; } service User { rpc Ping(Request) returns(Response); } 

与user.go中的,启动函数。

package main import("flag""fmt""go_zero_project/gozero-rpc-demo/user/internal/config""go_zero_project/gozero-rpc-demo/user/internal/server""go_zero_project/gozero-rpc-demo/user/internal/svc""go_zero_project/gozero-rpc-demo/user/user""github.com/zeromicro/go-zero/core/conf""github.com/zeromicro/go-zero/core/service""github.com/zeromicro/go-zero/zrpc""google.golang.org/grpc""google.golang.org/grpc/reflection")var configFile = flag.String("f","etc/user.yaml","the config file")funcmain(){ flag.Parse()var c config.Config conf.MustLoad(*configFile,&c) ctx := svc.NewServiceContext(c) s := zrpc.MustNewServer(c.RpcServerConf,func(grpcServer *grpc.Server){ user.RegisterUserServer(grpcServer, server.NewUserServer(ctx))if c.Mode == service.DevMode || c.Mode == service.TestMode { reflection.Register(grpcServer)}})defer s.Stop() fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) s.Start()}

请你点开的小手。
在终端中输入go mod tidy,更新拉取一下。
run一下,即可启动!

什么?你说你运行失败了?
etcd

如果运行失败了,大概率是 etcd 尚未启动,这是新手最常见的问题之一。
因为你没有安装etdc。

什么是etdc?
etcd 你可以将其当成一个高可用的分布式 KV 存储(Key-Value),核心用途在微服务里是:
服务发现:服务启动把自己的地址写进去(注册)
配置中心/开关:把配置、feature flag 放进去
分布式协调:租约、锁、选主....
如果你是第一次接触 etcd,可以先“功能上”把它理解为:

一个专门给微服务用的「注册中心 / 配置中心」

!!但要注意:

  • etcd ≠ Redis
  • etcd 具有强一致(Raft)
  • Redis 更偏缓存、高吞吐

一、下载

https://github.com/etcd-io/etcd/releases 
在这里插入图片描述


二、配置
把你含有以下,两个文件的cmd路径,配置到全局PATH路径内。

 - etcd.exe - etcdctl.exe 
在这里插入图片描述


三、运行
直接在cmd内,输入以下四个英文单词。

etcd 

启动!

在这里插入图片描述


四、检测

netstat -ano | findstr :2379 

看一看是否在监听!

在这里插入图片描述


此时,你在回去运行一下项目,自然就跑通了。
五、为什么要用这个?
这是很多人的疑惑!
看etc内的配置文件:

Name: user.rpc ListenOn: 0.0.0.0:8080Etcd:Hosts:- 127.0.0.1:2379Key: user.rpc 

小傻蛋们,因为配置文件用到了etcd。

调用rpc服务

初步调用

首先启动服务。

之后在apifox中操作的,创建基于grpc的接口管理!
(当然你换成其他也操作函数)
然后把我的.proto文件放置进去。
在user.User /Ping接口上,点击调用。

{"pong":"hello"}
修改

插入这些,
然后在,service User 里加一条:
方法名:GetUser
请求:GetUserRequest
响应:GetUserResponse
并新增 2 个 message:
GetUserRequest { int64 id = 1; }
GetUserResponse { int64 id = 1; string name = 2; }

syntax = "proto3"; package user; option go_package="./user"; message Request { string ping = 1; } message Response { string pong = 1; } message GetUserRequest { int64 id = 1; } message GetUserResponse { int64 id = 1; string name = 2; } service User { rpc Ping(Request) returns(Response); rpc GetUser(GetUserRequest) returns(GetUserResponse); } 

最关键的一步:

goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=. 

同个这个,生成rpc代码框架。
这里就是微服务中,被远程调用部分。
也是go-zero中的,两个最重要的核心之一!
而另一个就是api,也就是对外!同时调用rpc的部分!

API

其实这个主要就两个步骤

一、生成 API 项目骨架

(一般,新启一个项目时用)

goctl api new userapi 
二、写 .api 协议后生成代码

(更新项目时使用)

goctl api go -api userapi.api -dir . 

其实api还有很多组件需要掌握:
gorm、auth、jwt…
但是这些其实都与gin框架大差不差。

这次就先记录到这里,如果想要了解更多、更详细的,可以直接到官网了解,文档写的还是非常简洁通透的:
go-zero文档

Read more

用现代 C++ 封装 FFmpeg:从摄像头采集到 H.264 编码的完整实践

一、前言         在音视频开发领域,ffmpeg是常用的标准库,使用中用到大量的内存管理,不过结合c++对其封装,可以不在过度操心于内存管理,而更关注于处理逻辑。小编这次做的是使用ffmpeg的标准库在虚拟机上(ubuntu20.04)启用摄像头录制视频,保存的录制文件是mp4格式。         ffmpeg类似于胶水,在linux上底层实际上是通过v4l2跟内核通信从而驱动摄像头,后续小编有时间再出一个比较大的物体检测的项目,到时候会直接使用v4l2,不在通过ffmpeg,因为对于资源受限的嵌入式设备来说,ffmpeg还是比较庞大的(v4l2仅仅几kb,而ffmpeg则多达几mb),同时v4l2支持直接内存映射(mmap),无需拷贝数据,这对检测物体的实时性比较友好。         废话不再多说,我们回归整体。小编这里顺嘴带一下摄像头的工作原理,光线进入透镜成缩小倒立的像(也就是初中的物理学凸透镜成像)打到图像传感器(CMOS或CCD),传感器表面覆盖着由红、绿、蓝滤色片组成的拜耳阵列,每个像素点根据接收到的光照强度产生相应的电荷,经模数转换和色彩插值处理后生成完

By Ne0inhk
mpc模型预测控制从原理到代码实现 mpc模型预测控制详细原理推导 matlab和c++两种编程实现

mpc模型预测控制从原理到代码实现 mpc模型预测控制详细原理推导 matlab和c++两种编程实现

mpc模型预测控制从原理到代码实现 mpc模型预测控制详细原理推导 matlab和c++两种编程实现 四个实际控制工程案例: 双积分控制系统 倒立摆控制系统 车辆运动学跟踪控制系统 车辆动力学跟踪控制系统 包含上述所有的文档和代码。 一、代码整体架构与核心目标 本文档所分析的代码基于C++语言实现,围绕模型预测控制(MPC) 技术在车辆控制场景的应用展开,构建了一套从车辆状态仿真、MPC控制指令计算到控制效果可视化的完整解决方案。代码整体采用模块化设计,划分为车辆控制核心模块、可视化模块、外部依赖模块三大核心部分,支持车辆动力学模型与运动学模型两种控制模式,可通过多项式变道轨迹案例验证MPC控制算法的有效性,最终通过图形化界面直观呈现实际轨迹与期望轨迹的跟踪效果。 二、核心模块功能解析 2.1 车辆控制核心模块 该模块是整个系统的“大脑”,负责实现车辆状态仿真、MPC控制逻辑计算与控制指令生成,包含两个核心控制示例程序,分别对应不同的车辆模型。 2.1.1 车辆动力学控制(VehicleDynControl_mpc.cpp) * 核心功能:基于车辆动力学模型,实现高

By Ne0inhk
理解 C 与 C++ 中的 const 常量与数组大小的关系

理解 C 与 C++ 中的 const 常量与数组大小的关系

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳]本文专栏: C语言 文章目录 * 💯前言 * 💯数组大小的常量要求 * 💯C 语言中的数组大小要求 * 💯C++ 中的数组大小要求 * 💯为什么 C++ 中 `const` 变量可以作为数组大小 * 💯进一步的探讨:C++ 中的变长数组(VLA) * 💯总结 💯前言 在 C 语言和 C++ 中,数组的大小通常要求是一个编译时常量,也就是说数组的长度必须在编译阶段就能够确定。这对于程序的性能优化和内存管理是至关重要的。在很多初学者学习这两门语言时,对于数组大小的常量有时会感到困惑,尤其是在遇到 const 关键字时。在此篇文章中,我们将详细探讨 C 和 C++ 中数组大小的常量要求,分析 const 在这两种语言中的作用,解答为什么在 C 中常量 const int

By Ne0inhk
【C++】第二十五节—C++11 (上) | 详解列表初始化+右值引用和移动语义

【C++】第二十五节—C++11 (上) | 详解列表初始化+右值引用和移动语义

嗨,好久不见,我是云边有个稻草人,偶尔中二的C++领域博主与你分享专业知识^(* ̄(oo) ̄)^ 《C++》本篇文章所属专栏—持续更新中—欢迎订阅~ 最近的富文本编辑器给我整不会了,ε=(´-`*)))唉,多了横虚线,点某个位置老是会跳到别的位置或者出现了选中文字或图片的情况 目录 一、C++11的发展历史 二、列表初始化 1. C++98传统的{ } 2. C++11中的{} 3. C++11中的std::initializer_list 三、右值引用和移动语义(重点) 1. 左值和右值 2. 左值引用和右值引用 3. 引用延长生命周期 4. 左值和右值的参数匹配 5. 右值引用和移动语义的使用场景 (1)左值引用主要使用场景回顾 (2)移动构造和移动赋值

By Ne0inhk