在服务中添加简单业务
2.3 承载业务
直入主题。
无论那个类型的业务都可以加入,为了方便理解,现在假设我们现在有一些濒临倒闭的动物园,我们设计一个系统方便推广,帮助他们提升收入。
如果这些动物园有三种类型动物: 亚洲猫头鹰 asia-owl,非洲老虎 tiger,非洲大象 elephant
他们的信息分别如下:
{
{"name": "asia_owl_01", "title": "asia_owl_01", "id": "en00029", "founded": 1672729697872, "price": 10, "times": "180min", "url": "zoo.asdaliyun.com/oss/owl"},
{"name": "africa_tiger_001", "title": "africa_tiger_001", "id": "en00028", "founded": 1672729697872, "price": 15, "times": "170min", "url": "zoo.asdaliyun.com/oss/tiger"},
{"name": "africa_elephants_001", "title": "africa_elephants_001", "id": "en00030", "founded": 1672729697872, "price": 20, "times": "170min", "url": "zoo.asdaliyun.com/oss/ele"},
}
我们如何存数据呢。
2.3.1 数据的结构设计
我们项目不大,使用缓存保持数据是可行的。
使用缓存时,不推荐使用太大的key,我们将其分别拆分,为总数字段,信息字段,我们将它与预知的信息对应。
type ZoosInfo struct {
ID string `db:"id" json:"id" form:"id"`
Title string `db:"title" json:"title" form:"title"`
Url string `db:"url" json:"url" form:"url"`
Introduction string `db:"introduction" json:"introduction" form:"introduction"`
CreatedAt int64 `db:"created_at" json:"created_at" form:"created_at"`
Founded int64 `db:"founded" json:"founded" form:"founded"`
Price int64 `db:"price" json:"price" form:"price"`
Visits int64 `db:"visits json:"visits" form:"visits"`
}
在缓存中,我们使用有序集 zset:{zoos:id} 存放主键信息,使用{zoos:id} 计算总预定数。然后创建管道,一次将一个对象信息存入 并返回保存结果:
func PipelinedCreate(priKeys, zsetKeys string, price int64, ex map[string]string, dest []string, options ...Option) ([]any, error)
...
rdb := c.GetConn()
pipe := rdb.Pipeline()
...
setrst = pipe.Set(Ctx, k, val, 0)
setrsts = append(setrsts, setrst)
...
接口实现
正如上一节所讲,我们在api也就是 control实现接口的参数接收和校验,在views层实现数据的处理封装,并在此处使用model层的预定。
其过程涉及数据的拆分,和组合,不过有现成的连接库可以使用,比如go-redis的v8,当然也可以自己实现。
在返回前,我们对其进行处理,如果失败则返回 没有内容的错误
helpers.JSONs(ctx, http.StatusNoContent, gin.H{"message": "failed", "data": err})
如果成功则返回 200
helpers.JSONs(ctx, http.StatusOK, datas)
完成后我们的接口实际返回信息如下,这将在下一节的示例程序中实际看到。
查询详细信息 search/en00029
{"code":200,
"data":
{"Visits":0,
"created_at":1672818977504,
"founded":1672729697872,
"id":"en00029",
"introduction":"new one to visitor.",
"price":10,
"title":"asia_owl_01",
"url":"zoo.asdaliyun.com/oss/owl"},
"message":"Success"}
预定票据 booked 的动作
1672818978.270390 [0 192.168.30.1:11457] "exists" "{zoo:id}"
1672818978.271737 [0 192.168.30.1:11457] "incr" "{zoo:id}"
1672818978.274423 [0 192.168.30.1:11457] "zadd" "zset:{zoo:id}" "nx" "ch" "1" "{zoo:id}:1"
1672818978.275343 [0 192.168.30.1:11457] "set" "{zoo:id}:1:name" "asia_owl_01"
1672818978.275360 [0 192.168.30.1:11457] "set" "{zoo:id}:1:spec" ""
1672818978.275366 [0 192.168.30.1:11457] "set" "{zoo:id}:1:command" ""
预定信息返回
{"code":200,
"data":
{
"command":"1672818977509",
"id":1,
"name":"asia_owl_01",
"pri_key":"id",
"spec":"zoo.asdaliyun.com/oss/owl",
"table_name":"zoo"},
"message":"Success"}
同时我们使用订阅系统,以保证实时接收用户订阅信息。 此次共返回了3条消息
第一条返回消息类型消息类型,长度为7,
第二条长度为17,频道字符长度,
第三条长度为28,具体消息信息
Message{Id:"M:1672912846090", Text:"*3"} //此次共返回了3条消息
Message{Id:"M:1672912846091", Text:"$7"} //第一条长度为7
Message{Id:"M:1672912846092", Text:"message"} //消息类型
Message{Id:"M:1672912846092", Text:"$17"} //第二条长度为17,频道字符长度
Message{Id:"M:1672912846093", Text:"boards:zoo:visits"} //频道名称
Message{Id:"M:1672912846093", Text:"$28"} //第三条长度为28,具体消息信息
Message{Id:"M:1672912846093", Text:"1672912845088:33:385:en00028"}
每次有用户新增订阅时,我们的后台管理程序就可以实时地观测到新增的订阅信息,以及现在的总售卖数额:33:385,表示售出:33票,总价 $385 元。
Message{Id:"M:1672912846093", Text:"1672912845088:33:385:en00028"}
2.3.2 优雅地服务
当监控服务于主服务在一起时,我们可以通过分别定义,在一个管理器中启动,并且实现优雅的停止,避免误操作关闭。
子服务:
/*
子服务入口,集成启动函数.
*/
func SubSericeRun() error {
ser := Sers.Start()
infos := make(chan service.Cmds)
go Sers.TcpServer(infos)
...
return fmt.Errorf("Sers Running is false: %v", Sers.Running)
}
主服务入口:
创建侦听来自操作系统的中断信号的上下文,阻止第一次 的误操作退出
func MainService() error {
...
监听一次中断信号
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
fmt.Printf("Server start Port at:%#v \n", configs.ServicePorts)
logger.Fatalf(Server.ListenAndServe().Error())
}()
<-ctx.Done()
stop()
再次检测到信号将退出
fmt.Println("Press Ctrl+C again to force,shutting down gracefully")
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
}
然后在 errorGroup中监听管理
Gep = errgroup.Group{}
idelClosed := make(chan struct{})
启动服务
Gep.Go(func() error {
return MainService()
})
Gep.Go(func() error {
return SubSericeRun()
})
关闭服务
<-idelClosed
if err := Gep.Wait(); err != nil {
logger.Fatalf("Listener Shutdown:", err)
}
2.3.3 程序C/S结构
综合服务和客户端,我们的最终结构是这样的。 下一节,我们完成客户端程序和监控程序,并执行演示。
下一节我们模拟成群的客户去搜索和预定。
3 小结
我们现在只使用基本的接口服务,并没有任何队列实现流量管控,或授权或认证等。
基于缓存数据的服务让我们可以较快地响应客户。
下一节我们假设有客户群预定,并实时的显示用户预定信息。
- 点赞
- 收藏
- 关注作者
评论(0)