技术总结

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调度原理图

参考资料 #

aliyun-rds数据备份方案

本文主要是总结下在使用aliyun-rds数据备份方案过程中的心得。

高可用一直都是线上服务维护用户体验的关键之一。为了达到高可用,业界已经有了很多方案。最典型的就是“冗余备份+自动故障转移”。冗余备份是说,当一个节点服务不可用时,有其他服务能够代替其工作。除此之外,如果服务出现了必须人工介入解决的故障,也会影响系统的高可用特性。

本文着重介绍数据的高可用方案

数据库冗余 #

如果是单节点的数据库,还用的着说吗?要保证服务高可用,除了主-从数据库之外,还需要从备份数据库,当然不能保证说一定不会遇到所有的备份数据库,都挂掉的情况…。阿里云提供了RDS-高可用版本RDS-单机版,两者的区别见下图: 基础版和高可用 这就算最基本的冗余了,没有主从复制,没有读写分离。但是能保证主库在换掉的时候,还能使用备库提供服务。如果服务对于数据库性能和可用性有一定要求,那么可以在这个基础上升个级,见下图: 灾备方案

数据故障自动转移 #

已经有了冗余的数据库节点了,那么接下来要做的事情就是怎么感知数据库异常,并实现自动切换到备份实例中? 阿里云灾备方案的文档是这样描述的:

主实例和灾备实例均搭建主备高可用架构,当主实例所在区域发生突发性自然灾害等状况,主节点(Master)和备节点(Slave)均无法连接时,可将异地灾备实例切换为主实例,在应用端修改数据库链接地址后,即可快速恢复应用的业务访问。

对于主节点全部不可用的情况对应用服务是可见的,因此应用服务可以通过指定一些异常判断,在判定主节点不可用的时候,主动切换数据库连接地址来获取数据,提供服务。

// sql-detect.go

package main

import (
    "database/sql"
    "fmt"
    "sync"
    "time"

    _ "github.com/go-sql-driver/mysql"
    _ "github.com/mxk/go-sqlite"
)

var (
    mysqlAvailable bool    = true
    mutex                  = sync.Mutex{}
    db             *sql.DB = nil
)

func MysqlDetection(db *sql.DB, ticker *time.Ticker) {
    for {
        select {
        case <-ticker.C:
            if e := db.Ping(); e != nil {
                fmt.Println("got error", e)

                mutex.Lock()
                mysqlAvailable = false
                mutex.Unlock()

            } else {
                fmt.Println("status ok")
            }
        }
    }
}

func MysqlSwitch() {
    for {
        mutex.Lock()
        if !mysqlAvailable {
            fmt.Println("Switch Sqlite3")
            db, _ = sql.Open("sqlite3", "./foo.db")
        }
        mutex.Unlock()

        time.Sleep(time.Second * 4)
    }
}

func main() {
    c := make(chan bool)

    db, _ = sql.Open("mysql", "yeqiang:yeqiang@/test_yeqiang")
    ticker := time.NewTicker(time.Second * 2)

    go MysqlDetection(db, ticker)
    go MysqlSwitch()

    <-c
}

测试截图:

...

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文件,实例如下:

...

docker-compose上手

docker compose 用于快速在集群中部署分布式应用。按我的理解也可以用于简化部署单个应用。譬如我要使用dock er启动一个nginx服务,需要做端口映射,挂载数据文件,指定镜像…等等,这种情况下,可以将启动容器的命令整合到docker-compose.yml文件中,可以在多个服务器上运行,瞬间就完成了nginx的安装及配置,再也不用去编译,解决环境依赖了,这种感觉实在是太爽了!!!

安装 #

  1. 使用pip pip install docker-compose
  2. 从官方Github Release下载二进制包文件
  3. 其他方法略去

使用场景 #

在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

实战场景 #

需要部署的项目,只有两个docker容器,一个server,一个db。一般的部署方式是,分别启动两个容器,容器间通过互联的方式通信:

sudo docker run --rm -p 5433:5432 --name postgres -e POSTGRES_PASSWORD=minepwd -e POSTGRES_USER=mineusr -d postgres

sudo docker run --rm -p 9091:9091 --link postgres:postgres --name mineserver -d me/mineserver

这两条命令还是有挺麻烦的,如果记不住,当然可以用shell脚本来运行,可以如果其中某一个服务无法如期运行。。。就很监介了。这时候就可以引入docker-compose了。

编写docker-compose.yml来部署项目 #

version: "2" # 指定docker-compose版本
services:    # 项目依赖的服务
  postgres:  # 服务名字
    image: postgres # 服务需要的docker镜像与docker run命令中的镜像指定方式一致
    volumes:        # 挂载卷,这里的主要目的是,方便同步数据库和数据脚本
      - ./postgres:/var/lib/postgresql/data
      - ./sh:/usr/src/sh
    ports:          # 端口绑定
      - 5433:5432
    container_name: postgres
    environment:    # 设置环境变量
      POSTGRES_PASSWORD: "minepwd"
      POSTGRES_USER: "mineusr"
      POSTGRES_DB: "minedb"
  mineserver:
    image: me/mineserver
    volumes:        # 挂载卷,方便查看输出日志
      - ./logs:/usr/src/mineserver/logs
    ports:
      - 9091:9091
    container_name: mineserver
    links:          # 容器互联
      - postgres:postgres

在编写docker-compose.yml的时候,需要注意的是各个选项的数据类型,不过docker-compose会有提示,也很方便

...

RN历险记

RN

讲述配置ReactNative的心酸历程

程序猿长征第一步 #

根据官方文档来安装RN, 以及巨大无比的Xcode Ver9.0.0

错误一:Build Fail #

可能描述不太一致, 但是原因都差不多, 文件缺失。 菜鸟想必看到这些个报错, 两眼一懵逼, 啥子情况, 怎么和官方的描述不一致, 一个及其简单的RN-Demo, 我就是想跑一下的喂!

我遇到的情况, 分为两种:

其一是安装很慢, 之后失败 其二是安装很快, 然后失败

经过反复的“瞎子”调整, 在多次更换react, react-native版本, 求助Google大叔无果之后。我开始了阅读输出日志的漫漫长路, 终于发现了build失败的元凶, boost/xxx.hpp not found为啥找不到呢, 去文件夹一看, 才发现这些文件真的不存在……

好了知道错误, 就再Google下咯(其实我还去改过这些#includ<boost/config/user.hpp> 0<~>0)这里就直接给出我找的结果吧: http://cdn2.jianshu.io/p/2ef019a7e82a

总的说来, 就是自动下载的的第三方库是残缺的

错误二:CFBundleIdentifier not Found #

第二错误也是困扰了比较多人, 我遇到的只是导致这个情况的其中之一

通过查看输出日志, 并没有发现什么有用的信息, 提示的是Command Fail , balabala… 手动搜索了一下PlistBuddy, 了解了下用法, 然后我手动执行了下命令, 居然可以!!!!什么情况, 那为什么提示错误信息?果断进入到文件夹中查看,果然文件是存在的那么为啥一个可以, 一个不可以呢?到这里, 大致猜到原因了, 没有找到文件

再次以此为点求助Google大叔: http://blog.csdn.net/ohyeahhhh/article/details/54691512

这个坑就是, Xcode编译保存的路径和react-native-cli寻找的路径不一致, 通过修改路径就OK啦, 还有其他原因导致的这个fail 请参阅链接, 先搞清楚react-native run-ios做了啥事.

...

访问量 访客数