系统架构基础 #
系统架构 #
简介 #
系统架构描述了设计和构应用程序的模式和技术。是构建应用程序的起点或路线图,但开发者需要根据自己的实际情况,选择对应的编程语言实现。
在决定为新的应用程序适应那种架构或评估当前架构时,软件开发者或架构师应该先确定战略目标,再设计支持该目标的系统架构,不应先选择系统架构,再尝试使应用程序适用于该软件架构。
如何选择 #
-
选择标准
-
结合具体产品的功能需求进行选择
每个软件架构都包含一个用于完成常见软件任务的基本结构。开发者需要选择一种能够解决所需问题的架构,而非容易实现的架构
-
结合开发者的实际情况
-
-
不好的架构
- 不好的架构会使软件开发项目复杂化,增加软件开发工作的工作量,不利于公司节省成本
- 在选择架构前,需要考虑软件产品顶级组件的整体视图,以及是否符合开发者的实际要求
MVC架构 #
简介 #
MVC架构通常用于开发用户界面,将相关的程序逻辑划分为相互关联的3部分,从而将信息的内部表示与向用户呈现信息、接收信息的方式分开。
- 模型:主要用于管理数据和业务逻辑。模型对应于用户使用的所有数据相关逻辑。模型可以在视图和控制器之间传输数据。
- 视图:主要用于处理布局和显示相关的业务,以及处理与应用程序有关的UI逻辑。
- 控制器:主要用于将命令路由到模型和视图。将控制器作为模型和视图之间的接口,用于处理所有业务逻辑和传入的请求,使用模型操作数据并与视图进行交互,从而呈现最终输出。
注意事项 #
- 包名不一定是模型、视图或控制器
- 不要将应用程序分解成太多的包
实现 #
- 创建模型包models及其代码
package models
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
//用户模型
type User struct {
Id int
Name string
Phone string
}
//定义一个全局变量
var u User
//初始化数据库连接
func init() {
db, _ = sql.Open("mysql",
"root:a123456@tcp(127.0.0.1:3306)/goDesignPattern")
}
//获取用户信息
func GetUserInfo(id int) *User {
var param int
if id > 0 {
param = id
} else {
param = 1
}
// 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
err := db.QueryRow("select id,name,phone from `user` where id=?", param).Scan(&u.Id, &u.Name, &u.Phone)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return nil
}
return &u
}
- 创建视图包views及其代码
<html>
<head>
<title>{{.Name}}-mvc</title>
</head>
<body>
<h2>Hello,{{.Name}}, This is a mvc userInfo:</h2>
<p>{{.Name}}</p>
<p>{{.Phone}}</p>
</body>
</html>
- 创建控制器包cotrollers及其代码
package controllers
import (
"fmt"
"models"
"html/template"
"net/http"
"os"
"strconv"
)
//定义控制器函数
func Index(w http.ResponseWriter, r *http.Request) {
param := r.URL.Query().Get("id")
id, err := strconv.Atoi(param)
userInfo := models.GetUserInfo(id)
type PageData struct {
Name string
Phone string
}
pageData := PageData{
Name: userInfo.Name,
Phone: userInfo.Phone,
}
fmt.Fprintf(os.Stdout, "[+] from %s Method is %s.\n", r.URL.Path, r.Method)
//指定模版
tpl, err := template.ParseFiles("views/html/index/index.html")
if err != nil {
fmt.Fprintf(w, fmt.Sprintf("%s", err))
}
//解析模版
tpl.Execute(w, pageData)
}
优点 #
- MVC架构降低了代码的耦合度。通过将模型层、视图层和控制器层分开,更改其中一层的代码不会影响另外两层,从而降低代码的耦合度。
- 提高了代码的重用性
- 降低了软件维护成本
- 架构部署更快
- 有利于代码工程化管理
缺点 #
- 因为没有明确的边界含义,更难理解
- 不适合小规模、中等规模开发
- 提高系统结构和实现的复杂性
- 视图与控制器之间的连接过于紧密
- 降低视图对模型数据的访问效率
RPC架构 #
简介 #
RPC是一种用于构建基于客户端-服务端的分布式应用程序的技术。RPC基于对传统本地过程调用的扩展,其被调用进程不必与调用进程存在于同一个地址空间中。这两个进程可能在同一个系统上,也可能在不同的系统上,它们通过网络进行连接。
RPC架构主要分为3部分:
- 服务器端:提供服务接口定义与服务实现类
- 注册中心:运行在服务器端,负责将本地服务发布为远程服务,管理远程服务,以供服务消费者使用
- 客户端:通过远程代理对象调用远程服务
实现 #
net/rpc包提供了通过网络或其他I/O连接对一个对象的导出方法的访问方法。
- 创建服务器端
package server
import (
"errors"
"net"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
"strconv"
"time"
"common"
)
// Server 持有用于启动的配置 一个 RPC 服务器
type Server struct {
Port uint
UseHttp bool
UseJson bool
Sleep time.Duration
listener net.Listener
}
// Close 优雅地终止服务器侦听器
func (s *Server) Close() (err error) {
if s.listener != nil {
err = s.listener.Close()
}
return
}
// 初始化 RPC 服务器
func (s *Server) Start() (err error) {
if s.Port <= 0 {
err = errors.New("port must be specified")
return
}
rpc.Register(&common.Handler{
Sleep: s.Sleep,
})
s.listener, err = net.Listen("tcp", ":"+strconv.Itoa(int(s.Port)))
if err != nil {
return
}
if s.UseHttp {
rpc.HandleHTTP()
http.Serve(s.listener, nil)
} else if s.UseJson {
var conn net.Conn
for {
conn, err = s.listener.Accept()
if err != nil {
return
}
jsonrpc.ServeConn(conn)
}
} else {
rpc.Accept(s.listener)
}
return
}
- 创建核心处理公共包common
package common
import (
"errors"
"time"
)
//响应
type Response struct {
Message string
Ok bool
}
//请求
type Request struct {
Name string
}
// HandlerName 提供者的唯一名称
const HandlerName = "Handler.Execute"
//Handler是一个处理器
type Handler struct {
// Sleep 用于模拟一个耗时的方法执行操作
Sleep time.Duration
}
// Execute() 是 RPC 客户端可以调用的方法,通过使用 HandlerName 调用 RPC 服务器
// 如果没有错误,则它接受一个请求并产生一个响应发生;如果Sleep不为0,则服务器端和客户端处于休眠状态
func (h *Handler) Execute(req Request, res *Response) (err error) {
if req.Name == "" {
err = errors.New("A name must be specified")
return
}
if h.Sleep != 0 {
time.Sleep(h.Sleep)
}
res.Ok = true
res.Message = "Hello " + req.Name
return
}
- 创建客户端
package client
import (
"context"
"errors"
"common"
"net/rpc"
"net/rpc/jsonrpc"
"strconv"
)
// Client 包含以下配置选项与 RPC 服务器通信
type Client struct {
Port uint
UseHttp bool
UseJson bool
client *rpc.Client
}
//Init 初始化底层 RPC 客户端
//负责获取编解码器并编写 RPC服务器
func (c *Client) Init() (err error) {
if c.Port == 0 {
err = errors.New("client: port must be specified")
return
}
addr := "127.0.0.1:" + strconv.Itoa(int(c.Port))
if c.UseHttp {
c.client, err = rpc.DialHTTP("tcp", addr)
} else if c.UseJson {
c.client, err = jsonrpc.Dial("tcp", addr)
} else {
c.client, err = rpc.Dial("tcp", addr)
}
if err != nil {
return
}
return
}
// Close 优雅地终止底层客户端
func (c *Client) Close() (err error) {
if c.client != nil {
err = c.client.Close()
return
}
return
}
// 通过client.Call()方法进行RPC调用
func (c *Client) Execute(ctx context.Context, name string) (msg string, err error) {
var (
request = &common.Request{Name: name}
response = new(common.Response)
)
err = c.client.Call(common.HandlerName, request, response)
if err != nil {
return
}
msg = response.Message
return
}
- 创建一个根据命令行运行服务器端和客户端的main()函数
import (
"context"
"flag"
cli "client"
serv "server"
"log"
"os"
"os/signal"
"syscall"
)
var (
port = flag.Uint("port", 1337, "port to listen or connect to for rpc calls")
isServer = flag.Bool("server", false, "activates server mode")
json = flag.Bool("json", false, "whether it should use json-rpc")
serverSleep = flag.Duration("server.sleep", 0, "time for the server to sleep on requests")
http = flag.Bool("http", false, "whether it should use HTTP")
)
// handleSignals 是一个等待终止或中断的阻塞函数
func handleSignals() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
<-signals
log.Println("signal received")
}
// must 在错误的情况下记录
func must(err error) {
if err == nil {
return
}
log.Panicln(err)
}
//启动服务器,进行服务器监听
func runServer() {
server := &serv.Server{
UseHttp: *http,
UseJson: *json,
Sleep: *serverSleep,
Port: *port,
}
defer server.Close()
go func() {
handleSignals()
server.Close()
os.Exit(0)
}()
must(server.Start())
return
}
//解析命令行标志,然后启动客户端执行RPC调用
func runClient() {
client := &cli.Client{
UseHttp: *http,
UseJson: *json,
Port: *port,
}
defer client.Close()
must(client.Init())
var con context.Context
response, err := client.Execute(con, "Shirdon")
must(err)
log.Println(response)
}
func main() {
flag.Parse()
if *isServer {
log.Println("starting server")
log.Printf("will listen on port %d\n", *port)
runServer()
return
}
log.Println("starting client")
log.Printf("will connect to port %d\n", *port)
runClient()
return
}
优点 #
- 开发者可以获得唯一的传输地址(端口)。服务器可以绑定到任意一个端口并将该端口注册到其他RPC服务器,客户端会联系这个RPC服务器并请求与其需要的程序相对应的端口号。
- 在RPC架构中,客户端的应用程序只需要知道一个传输地址:RPC服务器的传输地址。
- 可以使用函数调用接口代替套接字提供的发送/接收接口
- RPC架构提升了系统的可扩展性、可维护性和持续交付能力
- RPC架构可以帮助客户端通过传统的高级编程语言中的过程调用与服务端进行通信
- 可以在分布式环境中使用
缺点 #
- 客户端和服务器端各自使用不同的执行环境,不太适合传输大量数据
- 极易发生故障
- 没有统一的标准
- 基于交互的,在硬件架构方面不具有灵活性
- 对初学者来说难度较高
三层架构 #
简介 #
三层架构将应用程序组织成3个架构层次,分别是表示层、业务逻辑层、数据访问层。
- 表示层是应用程序的用户界面和通信层,可以使用户与应用程序进行交互,主要用于向用户显示信息并收集用户信息。
- 业务逻辑层主要用于对具体问题进行逻辑判断与执行操作,在接收到表示层的用户指令后,会连接数据访问层。
- 数据访问层是数据库的主要操控系统层,主要用于对数据进行添加、删除、修改和查询的地方,并将操作结构反馈到业务逻辑层。
实现 #
- 创建基础部分
(1)编写SQL语句,插入数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`age` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO `users` VALUES (1, 'Barry', 18);
INSERT INTO `users` VALUES (2, 'Eric', 20);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
(2)创建实体包entities
//用户实体
type User struct {
ID int
Name string
Age int
}
(3)创建程序驱动包driver
type MySQLConfig struct {
Host string
User string
Password string
Port string
Db string
}
// 接受 MySQL 配置,形成连接字符串并连接到 MySQL
func ConnectToMySQL(conf MySQLConfig) (*sql.DB, error) {
connectionString := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v", conf.User, conf.Password, conf.Host, conf.Port, conf.Db)
db, err := sql.Open("mysql", connectionString)
if err != nil {
return nil, err
}
return db, nil
}
- 创建表示层
(1) 创建index.html文件
<html>
<head>
<title>{{.Name}}-threeTier</title>
</head>
<body>
<h2>Hello,{{.Name}}, This is a threeTier user info:</h2>
<p>{{.Name}}</p>
<p>{{.Age}}</p>
</body>
</html>
(2)创建notfound.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>not found</title>
</head>
<body>
404 Not Found
</body>
</html>
- 创建数据访问层
(1)创建数据访问层接口
type User interface {
Get(id int) (entities.User, error)
Create(entities.User) (entities.User, error)
}
(2)创建数据访问层用户存储对象
//用户存储
type UserStore struct {
db *sql.DB
}
//创建用户存储对象
func New(db *sql.DB) UserStore {
return UserStore{db: db}
}
//根据ID从数据库获取用户数据
func (a UserStore) Get(id int) ([]entities.User, error) {
var (
rows *sql.Rows
err error
)
if id != 0 {
rows, err = a.db.Query("SELECT * FROM users where id = ?", id)
} else {
rows, err = a.db.Query("SELECT * FROM users")
}
if err != nil {
return nil, err
}
defer rows.Close()
var users []entities.User
for rows.Next() {
var a entities.User
_ = rows.Scan(&a.ID, &a.Name, &a.Age)
users = append(users, a)
}
return users, nil
}
//根据用户对象插入数据到数据库
func (a UserStore) Create(user entities.User) (entities.User, error) {
res, err := a.db.Exec("INSERT INTO users (name,age) VALUES(?,?)", user.Name, user.Age)
if err != nil {
return entities.User{}, err
}
id, _ := res.LastInsertId()
user.ID = int(id)
return user, nil
}
- 创建业务逻辑层
//用户处理器
type UserHandler struct {
dataAccess user.UserStore
}
//创建用户处理器实例
func New(user user.UserStore) UserHandler {
return UserHandler{dataAccess: user}
}
//处理HTTP请求,根据不同的请求类型调用不同的处理器函数
func (u UserHandler) Handler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
u.get(w, r)
case http.MethodPost:
u.create(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
//根据id获取用户信息
func (u UserHandler) get(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
i, err := strconv.Atoi(id)
if err != nil {
_, _ = w.Write([]byte("no or invalid parameter id"))
w.WriteHeader(http.StatusBadRequest)
return
}
resp, err := u.dataAccess.Get(i)
if err != nil {
_, _ = w.Write([]byte("could not find the user"))
w.WriteHeader(http.StatusInternalServerError)
return
}
type PageData struct {
Name string
Age int
}
if len(resp) > 0 {
pageData := PageData{
Name: resp[0].Name,
Age: resp[0].Age,
}
//指定模版
tpl, err := template.ParseFiles("presentation/html/index/index.html")
if err != nil {
fmt.Fprintf(w, fmt.Sprintf("%s", err))
}
//解析模版
tpl.Execute(w, pageData)
}
//返回JSON格式的数据
body, _ := json.Marshal(resp)
_, _ = w.Write(body)
}
func (u UserHandler) create(w http.ResponseWriter, r *http.Request) {
var user entities.User
body, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(body, &user)
if err != nil {
fmt.Println(err)
_, _ = w.Write([]byte("invalid body"))
w.WriteHeader(http.StatusBadRequest)
return
}
resp, err := u.dataAccess.Create(user)
if err != nil {
_, _ = w.Write([]byte("could not create user"))
w.WriteHeader(http.StatusInternalServerError)
return
}
body, _ = json.Marshal(resp)
_, _ = w.Write(body)
}
- 创建服务器
func main() {
// 设置配置文件
conf := driver.MySQLConfig{
Host: "127.0.0.1",
User: "root",
Password: "a123456",
Port: "3306",
Db: "chapter4",
}
var err error
db, err := driver.ConnectToMySQL(conf)
if err != nil {
log.Println("could not connect to sql, err:", err)
return
}
userStore := user.New(db)
handler := handlerUser.New(userStore)
http.HandleFunc("/user", handler.Handler)
fmt.Println(http.ListenAndServe(":8099", nil))
}
优点 #
- 有利于系统分散开发
- 可以很容易的用新的实现替代原有层的实现,有利于标准化
- 有利于各层逻辑的代码复用,降低层与层之间的依赖
- 避免表示层直接访问数据访问层,提高数据安全性
- 可以很方便的进行系统移植
- 项目结构清除、分工明确,极大的降低了后期的维护成本
- 代码更容易维护
- 具有独立的层,写单元测试比较简单
缺点 #
- 有时会导致级联修改,这种修改尤其是体现在自上而下的方向
- 使用三层或多层的应用程序运行效率低、代码量大、难度高
- 降低了系统性能
微服务架构 #
简介 #
微服务架构是一种可以独立开发、部署和维护一系列服务的软件架构,每个微服务都是独立的服务,每个微服务都可以通过简单的接口与其他服务通信,用于解决业务问题
特征:
- 微服务体积小、独立且松耦合,即使小型开发团队,也可以编写和维护服务
- 每个服务都是一个单独的代码库
- 服务可以独立部署
- 服务负责存储自己的数据或外部状态,这与传统模型不同
- 服务通过使用API进行通信
- 支持多语言编程
在微服务架构中,除了使用服务,还包含一些其他组件
- 管理软件组件
- API网关
使用API网关的优点
- API网关可以将客户端与微服务端分离,无须更新所有客户端,即可对服务进行版本控制或重构
- 服务可以使用对web不友好的消息传递协议
- API网关可以实现其他横切功能,如身份验证、日志记录、ssl终止、负载平衡
- 采用开箱即用 的策略
微服务的优势:
- 敏捷:由于微服务是独立部署的,因此更容易进行错误修复和功能发布
- 小而专注的开发团队:
- 小代码库
- 混合技术,开发者可以选择最适合其服务的技术,并且酌情使用技术堆栈组合
- 故障隔离
- 可扩展性
- 数据隔离
微服务的挑战
- 复杂性
- 开发和测试
- 缺乏治理
- 网络拥塞和延迟
- 数据一致性
- 管理
- 版本控制
- 团队对技术要求更高
实现 #
用Go kit实现一个简单微服务,业务逻辑是创建一个简易的字符串服务,并且允许开发者操作字符串。
(1)业务逻辑
//服务接口提供对字符串的操作
type Service interface {
UpperString(string) (string, error)
Reverse(string) string
}
type StringService struct {
log log.Logger
}
//创建字符串服务
func NewStringService(log log.Logger) *StringService {
return &StringService{log}
}
//实现UpperString()方法
func (svc *StringService) UpperString(s string) (string, error) {
reverse := svc.Reverse(s)
if strings.ToLower(s) == "" {
return "", errors.New("empty string")
}
return strings.ToUpper(reverse), nil
}
//实现Reverse()方法
func (svc *StringService) Reverse(s string) string {
//转换为 rune
rns := []rune(s)
for i, j := 0, len(rns)-1; i < j; i, j = i+1, j-1 {
// 交换字符串的字母
rns[i], rns[j] = rns[j], rns[i]
}
// 返回反转的字符串
return strings.ToLower(string(rns))
}
(2)创建请求和相应
//大写字符串请求
type UpperStringRequest struct {
Word string `json:"word"`
}
//逆转字符串请求
type ReverseRequest struct {
Word string `json:"word"`
}
//响应
type UpperStringResponse struct {
Message string `json:"message"`
}
//逆转字符串响应
type ReverseResponse struct {
Word string `json:"reversed_word"`
}
(3)创建端点
//端点
type Endpoints struct {
GetUpperStringindrome endpoint.Endpoint
GetReverse endpoint.Endpoint
}
//创建端点
func MakeEndpoints(svc Service, logger log.Logger,
middlewares []endpoint.Middleware) Endpoints {
return Endpoints{
GetUpperStringindrome: wrapEndpoint(
makeGetUpperStringindromeEndpoint(svc, logger), middlewares),
GetReverse: wrapEndpoint(makeGetReverseEndpoint(svc,
logger), middlewares),
}
}
//创建获取大写字符串字符串端点
func makeGetUpperStringindromeEndpoint(svc Service,
logger log.Logger) endpoint.Endpoint {
return func(ctx context.Context,
request interface{}) (interface{}, error) {
req, ok := request.(UpperStringRequest)
fmt.Println(req)
if !ok {
return nil, errors.New("invalid request")
}
str, _ := svc.UpperString(req.Word)
fmt.Println(str)
return &UpperStringResponse{
Message: str,
}, nil
}
}
//创建获取逆转字符串端点
func makeGetReverseEndpoint(svc Service,
logger log.Logger) endpoint.Endpoint {
return func(ctx context.Context,
request interface{}) (interface{}, error) {
req, ok := request.(ReverseRequest)
if !ok {
return nil, errors.New("invalid request")
}
str, _ := svc.UpperString(req.Word)
return &ReverseResponse{
Word: str,
}, nil
}
}
//打包处理端点
func wrapEndpoint(e endpoint.Endpoint,
middlewares []endpoint.Middleware) endpoint.Endpoint {
for _, m := range middlewares {
e = m(e)
}
return e
}
(4)传输文件
//创建大写字符串控制器
func GetUpperStringHandler(ep endpoint.Endpoint, options []httptransport.ServerOption) *httptransport.Server {
return httptransport.NewServer(
ep,
decodeGetUpperStringRequest,
encodeGetUpperStringResponse,
options...,
)
}
//解码请求
func decodeGetUpperStringRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req UpperStringRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
//编码响应
func encodeGetUpperStringResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
resp, ok := response.(*UpperStringResponse)
if !ok {
return errors.New("error decoding")
}
return json.NewEncoder(w).Encode(resp)
}
//创建逆转控制器
func GetReverseHandler(ep endpoint.Endpoint, options []httptransport.ServerOption) *httptransport.Server {
return httptransport.NewServer(
ep,
decodeGetReverseRequest,
encodeGetReverseResponse,
options...,
)
}
//解码获取逆转字符串请求
func decodeGetReverseRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req ReverseRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
//编码获取逆转字符串响应
func encodeGetReverseResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
resp, ok := response.(*ReverseResponse)
if !ok {
return errors.New("error decoding")
}
return json.NewEncoder(w).Encode(resp)
}
(5)创建客户端并进行调用
func main() {
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
//声明中间件
var middlewares []endpoint.Middleware
//声明服务器可选参数
var options []httptransport.ServerOption
//创建一个字符串服务对象
svc := pkg.NewStringService(logger)
//创建端点
eps := pkg.MakeEndpoints(svc, logger, middlewares)
//创建路由器
r := mux.NewRouter()
//指定控制器
r.Methods(http.MethodGet).Path("/upperstring").
Handler(pkg.GetUpperStringHandler(eps.GetUpperStringindrome, options))
r.Methods(http.MethodGet).Path("/reverse").
Handler(pkg.GetReverseHandler(eps.GetReverse, options))
level.Info(logger).Log("status", "listening", "port", "8082")
svr := http.Server{
Addr: "127.0.0.1:8082",
Handler: r,
}
level.Error(logger).Log(svr.ListenAndServe())
}
优点 #
- 可以独立部署
- 可以快速启动
- 适合敏捷开发
- 职责专一,由专门的团队负责专门的服务
- 服务可以按需动态扩容
- 代码可以复用
缺点 #
- 分布式部署,调用的复杂性高
- 独立的数据库,分布式事务挑战性高
- 测试难度提高
- 运维难度提高
事件驱动架构 #
简介 #
对事件驱动架构而言,事件的捕获、通信、处理和持久保留是解决方案的核心结构。
事件是指系统硬件或软件的状态出现的重大改变,事件的来源可能是内部,也可能是外部。
事件驱动架构由事件发起者和事件使用者组成。事件的发起者会检测或感知事件,并且以消息的形式表示事件,它并不知道事件使用者或事件引起的结果。
在检测到事件后,系统会通过事件通道从事件发起者传输给事件使用者,而事件处理平台会在该事件中以异步方式处理事件。
事件处理平台会对事件做出正确的响应,并且将活动下发给相应的事件使用者。通过这种下发操作,我们可以看到事件的结果。
事件驱动架构模型:
-
发布/订阅模型
-
事件流模型
借助事件流模型,事件会被写入日志。
实现 #
(1)定义事件
var UserCreated userCreated
// 定义事件所需的有效负载
type UserCreatedPayload struct {
Email string
Time time.Time
}
type userCreated struct {
handlers []interface{ Handle(UserCreatedPayload) }
}
// 为此事件添加事件处理程序
func (u *userCreated) Register(handler interface {
Handle(UserCreatedPayload)
}) {
u.handlers = append(u.handlers, handler)
}
// 触发器发送带有有效负载的事件
func (u userCreated) Trigger(payload UserCreatedPayload) {
fmt.Println(u.handlers)
for _, handler := range u.handlers {
handler.Handle(payload)
}
}
func Handle(payload UserCreatedPayload) {
fmt.Println("handle:", payload)
}
var UserCreated userCreated
// 定义事件所需的有效负载
type UserCreatedPayload struct {
Email string
Time time.Time
}
type userCreated struct {
handlers []interface{ Handle(UserCreatedPayload) }
}
// 为此事件添加事件处理程序
func (u *userCreated) Register(handler interface {
Handle(UserCreatedPayload)
}) {
u.handlers = append(u.handlers, handler)
}
// 触发器发送带有有效负载的事件
func (u userCreated) Trigger(payload UserCreatedPayload) {
fmt.Println(u.handlers)
for _, handler := range u.handlers {
handler.Handle(payload)
}
}
func Handle(payload UserCreatedPayload) {
fmt.Println("handle:", payload)
}
(2)监听事件
func init() {
createNotifier := UserCreatedNotifier{
AdminEmail: "test1@example.com",
}
events.UserCreated.Register(createNotifier)
}
type UserCreatedNotifier struct {
AdminEmail string
}
func (u UserCreatedNotifier) NotifyAdmin(email string, time time.Time) {
// 向管理员发送一条消息,说明用户已创建
fmt.Println("Notify Created Admin Email:", email, time.Unix())
}
func (u UserCreatedNotifier) Handle(payload events.UserCreatedPayload) {
// 发送消息
u.NotifyAdmin(payload.Email, payload.Time)
}
func init() {
deleteNotifier := UserDeletedNotifier{
AdminEmail: "jack@example.com",
}
events.UserDeleted.Register(deleteNotifier)
}
type UserDeletedNotifier struct {
AdminEmail string
}
func (u UserDeletedNotifier) NotifyAdmin(email string, time time.Time) {
// 向管理员发送一条消息,说明用户已创建
fmt.Println("Notify Deleted Admin Email:", email, time.Unix())
}
func (u UserDeletedNotifier) Handle(payload events.UserDeletedPayload) {
// 发送消息
u.NotifyAdmin(payload.Email, payload.Time)
}
(3)触发事件
func CreateUser() {
// ...
//声明通知对象
createNotifier := notifier.UserCreatedNotifier{
AdminEmail: "shirdon@example.com",
}
////注册通知对象
events.UserCreated.Register(createNotifier)
//触发事件
events.UserCreated.Trigger(events.UserCreatedPayload{
Email: "barry@example.com",
Time: time.Now(),
})
// ...
}
func DeleteUser() {
// ...
//声明通知对象
deleteNotifier := notifier.UserDeletedNotifier{
AdminEmail: "jack@example.com",
}
//注册通知对象
events.UserDeleted.Register(deleteNotifier)
//触发事件
events.UserDeleted.Trigger(events.UserDeletedPayload{
Email: "jack@example.com",
Time: time.Now(),
})
//触发事件
events.UserDeleted.Trigger(events.UserDeletedPayload{
Email: "steve@example.com",
Time: time.Now(),
})
// ...
}
(4)客户端测试
func main() {
//创建用户
auth.CreateUser()
//删除用户
auth.DeleteUser()
}
优点 #
- 松耦合,服务不需要相互依赖,这应用了不同的因素,如传输协议、可用性和正在发送的数据。
- 可扩展性强。由于服务不再耦合,服务1的吞吐量不再需要满足服务2的吞吐量。
- 支持异步性。由于服务不再依赖于同步返回的结果,因此可以即发即弃。
- 可以按时间点恢复。如果事件由队列支持或维护某种历史记录,则可以重播事件。
缺点 #
- 事件驱动架构会导致过度设计流程,有时候从一个服务到另一个服务只需要简单的调用就够了。
- 事件驱动架构不支持ACID事务,难以测试和调试:由于流程现在依赖于最终一致性,通常不支持ACID事务,因此重复处理或乱序事件的处理会使服务代码更加复杂,并且难以测试和调试所有情况。