Golang

使用golang 实现JSON-RPC2.0


什么是RPC? #

远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。

远程过程调用是一个分布式计算的客户端-服务器(Client/Server)的例子,它简单而又广受欢迎。远程过程调用总是由客户端对服务器发出一个执行若干过程请求,并用客户端提供的参数。执行结果将返回给客户端。由于存在各式各样的变体和细节差异,对应地派生了各式远程过程调用协议,而且它们并不互相兼容。——————源自维基百科


什么又是JSON-RPC? #

JSON-RPC,是一个无状态且轻量级的远程过程调用(RPC)传送协议,其传递内容通过 JSON 为主。相较于一般的 REST 通过网址(如 GET /user)调用远程服务器,JSON-RPC 直接在内容中定义了欲调用的函数名称(如 {“method”: “getUser”}),这也令开发者不会陷于该使用 PUT 或者 PATCH 的问题之中。 更多JSON-RPC约定参见:https://zh.wikipedia.org/wiki/JSON-RPC

问题 #

服务端注册及调用 #

约定如**net/rpc**:

  • the method’s type is exported.
  • the method is exported.
  • the method has two arguments, both exported (or builtin) types.
  • the method’s second argument is a pointer.
  • the method has return type error.
// 这就是约定
func (t *T) MethodName(argType T1, replyType *T2) error

那么问题来了:

...

自己写一个手机菜谱APP

需要的技术及工具:

  • Python3 + Selenuium
  • Golang net/http
  • React-Native 相关(使用了react-navigation)
  • MongoDB
  • Redis

代码地址:

项目构思及构成 #

食谱类型的App,应用市场肯定有更好的的食谱APP。所以自己开发的目的,首先是写代码,其次是定制APP~ 好的,现在化身产品经理,设计一下APP有哪些功能:

  • 每日菜谱推荐,推荐可更换
  • 每天需要准备的材料提醒
  • 发现更多菜谱
  • 分类筛选菜谱
  • 搜索菜谱
  • 查看菜谱详情
  • 设置(不知道设置啥,提前准备吧)

设计稿?不存在的,随心所欲。

现在分析下我需要做的事情:

  1. 能跑起来的APP,与restful web api 交互。
  2. 能跑起来的web-api,提供菜谱数据,筛选,推荐,搜索等功能
  3. 能跑起来的简易spider,从网上获取菜谱信息。(这个爬虫能解析动态生成网站就够用了,姑且称之为爬虫吧)

没有考虑大量数据,因此爬虫并不通用,只适合特定XX网站。

实战爬虫 #

这个APP里面最重要的就是菜谱数据了,那么开发之前,需要明确的数据格式,如下:

{
    "name": "name",
    "cat": "cat",
    "img": "img_url",
    "mark_cnt": 19101,
    "view_cnt": 181891,
    "setps": [
        {
            "desc": "",
            "img": "",
        },
        // more step
    ],
    "material": {
        "ingredients": [
            {
                "name": "ingredients_name",
                "weight": "ingredients_weight",
            },
            // more ingredients
        ],
        "seasoning": [
            {
                "name": "seasoning_name",
                "weight": "seasoning_weight",
            },
            // more seasoning
        ],
    },
    "create_time": "2018xxxxxx",
    "update_time": "2018xxxxxx",
}

目标 #

前提:无法直接获取到该网站的服务API,才使用爬虫间接获取数据。

...

docker+jenkins+golang持续集成实践


起因 #

因为生产需要最近又重新折腾了一下Jenkins和docker。主要目的是想自动编译,打包,部署一些Golang的HttpServer。于是决定使用Jenkins来做这个持续集成的载体,选择Jenkins出于两点原因:

1. 以前就使用过,上手会更快 2. 社区比较成熟,插件和文档丰富


安装Docker和Pull Jenkins镜像 #

这一步,作为前置条件且不是本文主要要描述的步骤,因此略去。网上也有很多参考资料~


Jenkins & docker-compose配置 #

为了方便我才用了docker-compose这个工具,docker-compose 基础可以参见我的docker-compose上手。这里直接上配置:

version: '2'

services:
  jenkins:
    container_name: jenkins-lts
    ports:
      - 9001:8080
      - 50000:50000
    image: jenkins/jenkins:lts
    volumes:
      - /home/worker/jenkins/jenkins_home:/var/jenkins_home

配置也是官方的示例配置。

Note: 将宿主机的/home/worker/jenkins/jenkins_home挂载为容器的/var/jenkins_home目录。这样做的目的是,如果容器被不小心删除也不至于Jenkins的数据丢失。

到这里,我们只需要执行docker-compose up -d便可以将Jenkins容器跑起来了,再配置一下Nginx,便可以直接访问到Jenkins页面了,并进行初始化。

我的目录结构如下:

➜  jenkins ll
total 8.0K
-rw-rw-r--  1 worker worker  220 May  2 17:19 docker-compose.yml
drwxrwxr-x 19 worker worker 4.0K May  3 15:53 jenkins_home
➜  jenkins pwd
/home/worker/jenkins
➜  jenkins docker-compose up -d # 运行

Publish Over SSH配置 #

Publish Over SSH配置,由于我们是通过docker运行的Jenkins,因此要特别配置一下SSH,方便Jenkins部署项目。这里先列出步骤:

...

gorm使用记录

关于Gorm #

gorm文档

遇见问题 #

无法通过结构体的方式更新或查询零值 #

这里零值是说,各个类型的默认值。

关于这一点是在这里中注明了的,也提供了解决方案:

WARNING when update with struct, GORM will only update those fields that with non blank value

For below Update, nothing will be updated as “”, 0, false are blank values of their types

NOTE When query with struct, GORM will only query with those fields has non-zero value, that means if your field’s value is 0, ‘’, false or other zero values, it won’t be used to build query conditions,

...

Golang学习笔记

目录 #

Channel #

一开始是在看channel的源码,结果发现里面含有一些抽象的描述(可能也就是我觉得。。。毕竟没有深入)

Do not change another G’s status while holding this lock (in particular, do not ready a G), as this can deadlock with stack shrinking.

其中G是啥?我看着是很懵逼的,去google了一下,其实是goroutine相关的知识,那就把goroutine理解了先。

2020-04-13 填坑

channel in go

Goroutine #

G: 表示goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等;另外G对象是可以重用的。 P: 表示逻辑processor,P的数量决定了系统内最大可并行的G的数量(前提:系统的物理cpu核数>=P的数量);P的最大作用还是其拥有的各种G对象队列、链表、一些cache和状态。 M: 代表着真正的执行计算资源。在绑定有效的p后,进入schedule循环;而schedule循环的机制大致是从各种队列、p的本地队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到m,如此反复。M并不保留G状态,这是G可以跨M调度的基础。M必须关联了P才能执行Go代码。

结合下图更方便理解: –源于Tonybai的博客,见参考资料。 Goroutine调度原理图

参考资料 #

Stack实现O(1)的Min和Max

栈(Stack)Pop和Push操作只需要对栈顶元素进行操作,就不多加描述了。那么对于Max和Min操作,怎么保证O(1)的时间复杂度?最直接想到的就是设置两个标记位,最小值的最大值,在push和pop的时候更新两个值。那么怎么更新呢,怎么保证最大值和最小值弹出之后还能正确获取到当前所有元素中的最大值和最小值呢?请看下文:

辅助最大值栈SM #

算法描述 #

type Stack Struct{
	data []int
}

var SMax *Stack = new(Stack)
  • push: 如果当前元素大于等于辅助栈的栈顶元素或者辅助栈为空,那么当前元素push到辅助栈中
  • pop: 如果当前元素等于辅助栈的栈顶元素,那么从辅助栈中弹出当前元素

举个例子 #

如果有1,3,6,1,12,512,12,5121,121,412数据放入栈中

Step-1. 元素1入栈,当前SM栈为空,SM栈也同步更新

Stack: 1
SMax: 1

Step-2. 元素3入栈,3 > 1,SMax栈也同步更新

Stack: 1, 3
SMax: 1, 3

Step-3. 元素6入栈,6>3,SMax栈也同步更新

Stack: 1, 3, 6
SMax: 1, 3, 6

…此处省略更多步骤

最大值标记法 #

第一种方式利用辅助栈来标记当前最大值和上一个最大值,并利用栈来实现O(1)复杂度。但是根据上述的例子,可以看到如果插入的元素是依次增大,那么耗费2N+1空间才能实现栈的最大值和最小值在O(1)复杂度。现在介绍的方法,能够很好的减少空间耗费,并保证O(1)时间复杂度。

算法描述 #

type Stack struct {
	data []int
	max  int // default = math.MinInt32
}
  • push: 将(当前元素-Max)放入栈中;如果当前元素大于Max,用当前元素替换Max
  • pop: 如果栈顶元素>0,弹出Max,用Max-栈顶元素替换Max;否则弹出Max+栈顶元素

再举个例子 #

如果有5, 23, 12, 499, 45, 20, 60入栈

Step-1. 元素5-Max入栈,5 > math.MinInt32, 更新Max=5

...

ShortURL系统实现

在知乎上看了一个很有启发的回答,因此实际动手来实现短URL生成系统。贴上链接: 知乎 - 短URL系统是如何设计的。其中提到了,要实现短URL生成系统要解决的问题有:

  • 如何优雅的实现?
  • 怎么基本实现长对短、一对一?
  • 如何实现分布式,高并发,高可用?
  • 储存选用?

基本原理 #

数据库自增ID转换62进制

  1. 使用自增ID不会产生重复的短链接。
  2. 为了解决自增ID超长和不便记忆,对ID进行62进制编码。所谓62进制就是0-9,a-z,A-Z。

简单计算下:

62 ^ 4 = 14,776,336
62 ^ 5 = 916,132,832
62 ^ 6 = 56,800,235,584 // 已经足够使用了

总体结构及处理流程 #

服务结构

长链接处理流程 #

  1. 获取参数,调用shortURL服务
  2. 尝试从缓存中获取,如果命中,则读取短链接(重置过期时间)。跳转第4步
  3. 将长链接存储到Mysql数据库,根据ID进行base62编码,组装Domain+Encoded字符串并更新数据库
  4. 返回生成的短链接

短链接处理流程 #

  1. 解析短链接为ID
  2. 查询ID对应的长链接
  3. 以301方式跳转到长链接

长链接与短链接的对应关系 #

一对多,一个长链接可能对应多个短链接。数据表存储结构如下:

+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int(64)      | NO   | PRI | NULL    | auto_increment |
| long_url  | varchar(100) | NO   |     | NULL    |                |
| short_url | varchar(40)  | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+

分布式和高并发设计 #

###注:这部分未实现。我的思路如下:

...

Golang服务端技术笔记

总结使用Golang开发服务端时,使用的基础的工具和部署方式。用于思考不足并优化,提升编码效率。 总体上采用MVCS的软件模式,如下图:

MVCS模式

从图中可以看出,MVCS是从MVC进化而来,相比于MVC,增加了Service层。把业务逻辑从Controller层中抽离出来,这样做的好处在于,项目日益庞大之后,将某些功能独立出来。

Golang工具 #

“gvt” 依赖管理工具 “httprouter” 路由及中间件配置 “schema” 解析请求参数到结构体 “beego/validation” 结构体校验工具 “github.com/go-redis/redis” redis操作库 “github.com/go-sql-driver/mysql” Mysql Driver

文件结构 #

--Golang Project
    |-sh            # shell脚本,包括数据库脚本
    |-config        # 配置文件
    |-logs          # 日志文件
    |-vendor        # 项目源码及依赖
    |  |-github.com #
    |  |-mainfest   # gvt 依赖管理文件
    |  |-app
    |     |-utils
    |     |-controllers
    |     |-models
    |     |-route
    |     |-services
    |-Dockerfile    # docker构建镜像配置文件
    |-docker-compose.yml # docker-compose.yml文件
    `-entry.go      # web服务入口文件

部署方式 #

采用docker来部署应用。分别编写Dockerfile和docker-composer.yml文件,实例如下:

...

访问量 访客数