无心阁

记录生活、技术中的二三事.


  • 首页

  • 分类

  • 归档

  • 标签

关于istio

发表于 2018-06-09 | 分类于 技术

前言

在微服务大行其道的当下,随着服务数量的增多,语言的多样化,服务治理慢慢变成了一个大部分公司都在头痛的难题. 在这样的背景下,istio应运而生.

Istio是由Google/IBM/Lyft共同开发的新一代Service Mesh开源项目, 继承了服务的发现,负载,限流和熔断,链路追踪,网关等微服务必不可少的功能,把开发者从不断的维护开发环境,语言SDK中解救出来.

在研究过程中,笔者发现当前有一些热心的作者,已经翻译出了istio中文手册,但是由于操作API的变更,可能无法让对应配置生效. 但还是非常感谢他们的付出.

在当前时间(ps:2018-06-09), istio发布了0.8版本, 同时更换了新一版的alpha3操作API,老一版本的alpha2版本的api已经不建议使用,并且会在下一版本中删除.
官网中的提示:
This task uses the new v1alpha3 traffic management API. The old API has been deprecated and will be removed in the next Istio release. If you need to use the old version, follow the docs here.

这里推荐英文基础不好的同学可以结合两个版本一起来看,比较容易理解.

初探

k8s安装

首先,想要使用istio,我们需要先选择一个与他配合的基础设施. 由于istio对k8s的支持最为强大,所以这里我们选择了k8s为基础设施.

但是在安装k8s的时候发现这个集群搭起来还是有点费劲的. 想偷懒的同学可以去使用一个叫做minikube的开源软件,可以直接启动一个k8s集群.

由于在安装minikube的过程中发现由于一些GWF的不可描述原因,导致google的docker镜像库里面的镜像拉不回来, 有两个选择:

  1. 装一个不可描述的软件,来越过GWF,下载原汁原味的minikube
  2. 找到阿里的同学封装的一个国内定制版本: 传送门

pppppppps: 写到这里发现其实需要的篇幅还是蛮长的,抽空来写,撸代码去….

Consul快览

发表于 2018-06-02 | 分类于 技术

介绍

因为团队目前正在考虑服务化部署,所以了解下相关的技术堆栈。 作为微服务架构里重中之重的服务发现和集群一致性KV存储当然是首先要了解的.

现在市面上有3种比较常见的服务发现服务:

  • zookeeper
  • etcd
  • consul

在综合团队内部情况下,决定采用consul作为我们的服务发现中心和配置管理.

Consul快速入门

功能

  1. 服务发现
  2. Key->Value存储
  3. 分布式锁
  4. Watch变更
  5. 属于颜控的一个好看的UI

环境搭建

建议使用docker快速部署:

1
2
3
4
5
6
7
8
9
10
###
# -d 后台运行
# --name 为容器起一个名字
# -p 映射docker容器内端口和宿主机器
# -server consul的命令,意思是以server端启动,ps: 还有个client端
# -ui 可以打开一个自带的UI可视化界面
# -bootstrap-expect 最小启动节点数量
###

docker run -d --name=consul -p 8500:8500/tcp consul agent -server -ui -bootstrap-expect=1 -client=0.0.0.0

golang使用

这里提供一个golang的官方sdk: sdk

官方已经有一个详细的 example, 这里就不多说了,但是研究Watch的时候发现有一点坑. Api模块里并没有Watch相关功能操作,所以看了下consul的源码,在consul/watch目录下看到了大概的实现

consul 的 watch有几种实现方式

  1. 变更时执行shell脚本
  2. 变更时通过http触发
  3. 在源码中发现有一个hook接口,挂在handler上就会自动执行

这里因为避免部署上的操作,所以在应用启动中代码直接启动Watch. 下面是consul相关使用的实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package main

import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"

"github.com/hashicorp/consul/api" // 操作consul的api模块
"github.com/hashicorp/consul/watch" // api中未提及的watch模块
"github.com/marlonfan/go-library/util" // 一个自定义的常用的package,在github可以找到
)

var consulClient *api.Client

var (
serverID = flag.String("server-id", "nomarl", "service's id")
serverPort = flag.String("port", ":8080", "service's port")
)

func init() {
flag.Parse()
var err error
consulClient, err = api.NewClient(api.DefaultConfig())
util.CheckError(err)
}

func main() {
go registorService()
go waitToUnRegistService()
go startWatch()

// 健康检查
http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("check status.")
fmt.Fprint(w, "status ok!")
})

fmt.Println("start listen...")
err := http.ListenAndServe(*serverPort, nil)
util.CheckError(err)
select {}
}

// watch功能实现
func startWatch() {
watchConfig := make(map[string]interface{})

watchConfig["type"] = "service"
watchConfig["service"] = "redis"
watchConfig["handler_type"] = "script"
watchPlan, err := watch.Parse(watchConfig)
util.CheckError(err)
watchPlan.Handler = func(lastIndex uint64, result interface{}) {
services := result.([]*api.ServiceEntry)
str, err := json.Marshal(services)
util.CheckError(err)
fmt.Println(string(str))
}
if err := watchPlan.Run("192.168.2.159:8500"); err != nil {
log.Fatalf("start watch error, error message: %s", err.Error())
}
}

// 注册服务实现
func registorService() {
var err error
client, err := api.NewClient(api.DefaultConfig())
util.CheckError(err)

service := &api.AgentServiceRegistration{
ID: *serverID,
Name: "redis",
Port: 12311,
Check: &api.AgentServiceCheck{
HTTP: "http://192.168.2.159" + *serverPort + "/status",
Interval: "1s",
Timeout: "1s",
}}

if err = client.Agent().ServiceRegister(service); err != nil {
log.Fatalf("registor failed, error message: %s", err.Error())
}
}


// 收到退出信号后的服务下线处理,如果不做,服务会显示健康检查失败,同时已经下线的服务还会在控制面板看到
func waitToUnRegistService() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, os.Kill)
<-quit

if consulClient == nil {
return
}

if err := consulClient.Agent().ServiceDeregister(*serverID); err != nil {
log.Fatal(err)
}
os.Exit(0)
}

全文完!

【译】在GO中如何拼接HTTP处理程序

发表于 2017-12-14 | 分类于 技术

你好,今天我想分享一下,在GO语言中拼接HTTP处理器.

在使用 GO 之前, 我使用 Nodejs + ExpressJS 去编写HTTP服务器应用. 这个框架提供了很简单的方法去使用中间件和拼接很多路由节点,因此,不必指定全部路由URL来为其添加处理程序.

图1

这个想法是通过分割你的路由和处理每一个部分,拼接到处理器,每个处理程序只负责一部分. 它理解起来非常简单且非常容易使用和维护,所以首先我尝试在 GO 中做一些类似的事情.

开箱既用, GO 提供了一个很棒的 http 包, 它包含了很多不同的工具, 当然, 还有 ListenAndServe 方法, 它在给定的端口上启动一个 HTTP 服务器并且通过处理程序处理它, 所以,这个处理器是什么?

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

处理器是接口,它有一个方法 - ServeHTTP 去处理传入的请求和编写响应

但是,如果我们想为每一个根路由定义一个处理程序, 例如 /api/, /home, /about 等, 要怎么做?

ServeMux - HTTP请求复用器, 可以帮助你处理这一点. 使用 ServeMux,我们可以指定处理器方法来服务任何给定的路由, 但问题是我们不能做任何嵌套的 ServeMux .

文档中的例子:

1
2
3
4
5
6
7
8
9
10
11
mux := http.NewServeMux()
mux.Handle("/api/", apiHandler{})
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
// The "/" pattern matches everything, so we need to check
// that we're at the root here.
if req.URL.Path != "/" {
http.NotFound(w, req)
return
}
fmt.Fprintf(w, "Welcome to the home page!")
})

我们可以看到, 在这个例子中为 /api/ 路由自定义了一个处理器并且定义了一个处理方法给根路由. 因此任何以 /api/* 开头的路由都将使用 apiHandler 处理器方法. 但是如果我们需要拼接一个 usersHandler 到 apiHandler, 不通过任何的头脑风暴和编码,我们无法做到这点.

为此我写了一个小库 - gosplitter, 它只提供一个公共方法 Match(url string, mux *http.ServeMux, http.Handler|http.HandlerFunc|interface{}) - 他匹配给定的路由部分 和处理器,处理方法或你给定的任何结构!

让我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 定义一个处理器类型
*/
type APIV1Handler struct {
mux *http.ServeMux
}

type ColorsHandler struct {
mux *http.ServeMux
}

/**
* Start - 绑定父级到子级
*/
func (a *APIV1Handler) Start() {
var colorsHandler = ColorsHandler{
mux: a.mux,
}
gosplitter.Match("/ping", a.mux, a.HandlePing())
gosplitter.Match("/colors", a.mux, colorsHandler)
colorsHandler.Start()
}
func (c *ColorsHandler) Start() {
gosplitter.Match("/black", c.mux, c.HandleBlack())
}
/**
* 简单的HTTP处理器方法
*/
func (a *APIV1Handler) HandlePing() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
}
}

func (c *ColorsHandler) HandleBlack() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("#000000"))
}
}

func main() {
var mux = http.NewServeMux()
var apiV1 = APIV1Handler{
mux: mux,
}

/**
* 绑定api处理器到根目录
*/
gosplitter.Match("/api/v1", mux, apiV1)
/**
* 开始api的处理
*/
apiV1.Start()
}

举个例子:

1
2
3
4
5
6
7
8
9
10
/**
* 定义处理器类型
*/
type APIV1Handler struct {
mux *http.ServeMux
}

type ColorsHandler struct {
mux *http.ServeMux
}

这里我们定义了一个我们的处理器,它是一个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Start - 绑定api处理器到根目录
*/
func (a *APIV1Handler) Start() {
var colorsHandler = ColorsHandler{
mux: a.mux,
}
gosplitter.Match("/ping", a.mux, a.HandlePing())
gosplitter.Match("/colors", a.mux, colorsHandler)
colorsHandler.Start()
}
func (c *ColorsHandler) Start() {
gosplitter.Match("/black", c.mux, c.HandleBlack())
}

添加一个 Start 方法到我们的处理器程序, 去激活处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 简单的HTTP处理器方法
*/
func (a *APIV1Handler) HandlePing() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
}
}



func (c *ColorsHandler) HandleBlack() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("#000000"))
}
}

添加 HandlePing 和 HandleBlack 到我们的 APIV1Handler 他响应了 pong 和 #000000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
var mux = http.NewServeMux()
var apiV1 = APIV1Handler{
mux: mux,
}

/**
* 绑定API处理器到根路由
*/
gosplitter.Match("/api/v1", mux, apiV1)
/**
* 启动API处理器
*/
apiV1.Start()
}

我们在main方法中创建了一个新的 ServeMux 然后创建了一个APIV1Handler的实例, 把他绑定到了/api/v1路由,然后启动了它

所以在所有这些简单的操作之后我们拥有了两个工作中的路由: /api/v1/ping 和 /api/v1/colors/black会响应 pong 和 #000000.

使用起来不是很容易么? 我认为是这样, 现在在我的项目中使用这个库来方便的进行路由分割和拼接处理器 :)

感谢阅读, 欢迎提出任何建议和批评!


via: https://medium.com/@cashalot/how-to-chain-http-handlers-in-go-33c96396b397

作者:goncharovnikita
译者:MarlonFan

golang多线程

发表于 2017-11-28 | 分类于 技术

引言

最近在试着用golang做点东西,总结下golang的多线程

什么是多线程

一个多线程程序包含两个或多个能并发运行的部分。 程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。 这里定义和线程相关的另一个术语- 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。

关于过去一年的总结,以及我的2017

发表于 2017-03-06 | 分类于 技术

这边总结已经想写很久了,却迟迟没有动笔. 或许是对过去的2016还有些没做完的事情不甘心,又或许不想太着急面对2017年23岁的自己.

这一年,在自己的工作上想沉淀更多,也慢慢走出了当时离开第一家公司时的偏执的想法. 为自己之后的路有了一个清晰的规划.
这一年,在自身方面有了一些新的想法,或许可以使自己有一个新的视野和一些机会.
我一直认为没有目标的人彷似行尸走肉. 在这一年里,终于找到一些新的东西,值的花更多的时间去追求.

关于这一年做的事情.

  • 关于自己的快签计划

是的, 它做着做着就死了. 因为我发现有一个比他更好的产品正在推广使用. PS: 可以安丽下,滴答清单

  • 关于前端方面的扩展

这一年刻意试着从事了下前端相关的工作,与同事合作为前公司提供了一套基于内部管理平台的React组件. 由于当时使用不是特别广泛,也自己踩了一些坑.

  • 关于后端相关

了解公司内部的后端架构,尝试的提供一些优化开发、提高效率又优雅的事情. 没错,我说的就是推广Laravel的使用…

  • 关于个人

这一年还算是相对稳定的一年,寻找新的目标新的方向,休息沉淀完毕,整装待发走向下一程.

关于未来的计划

哈哈,总是在新的一年列一堆一堆的新的计划,但是只有9个月的时间了. 做了一个新的决定,换了一家公司. 想挑战下其他的方面.

在技术方面希望对后端有更加深入的了解,在一些常应用场景下的一些架构思路.

在个人方面,今年还是想相对稳定点吧,想把重心放在工作和其他更加有趣的事情上

END

2017早已开始,一直在路上.

laravel 环境部署的两个推荐

发表于 2016-09-07 | 分类于 技术

在使用laravel的过程中,不少同学因为环境部署这一步卡住后面无法进行,还有一些同学因为环境部署的复杂而望而却步
在之前laravel的作者开发了基于vagrant的自动化环境部署方案homestead
但是这一诟病是必须在机子上安装虚拟机,对于一些同学来说可能无法接受.
今天Marlon就给大家推荐两种另外的部署环境方案.

Laravel Valet

这个开发工具也是有laravel的创造者Jeffrey写的. 内置服务器和对应的映射命令,还有基于Ngork的公网发布. but,这个工具目前仅仅支持在mac环境下使用

Laravel Wagon

这个是一个台湾的工程师为了简化在windows下的环境部署,定制的一套开发环境.


ps: 具体使用就让同学们自己研究去啦~~

关于gitlab-ci的一些探索

发表于 2016-09-07 | 分类于 技术

因为团队中想使用PHPCS来检查代码风格,所以考虑了两个方案.一个是基于GIT HOOKS的行为触发,一个是基于GITLAB-CI的一系列部署.考虑再三使用了gitlab-ci的解决方案.

了解gitlab-ci的运行机制.

要想使用gitlab-ci,首先要明白它的组成. 这个东西有两个东西来支撑:

  1. gitlab-ci server
  2. gitlab-ci-runner

gitlab-ci server负责调度、触发Runner,以及获取返回结果. 而gitlab-ci-runner则是主要负责来跑自动化CI的一个宿主机子.

那么我们总结一下流程,其实是这个样子的:

2016-07-22_13:48:39.jpg

Runner的概念

runner可以想象成一个守护进程,来守护你注册好的service和gitlab-ci绑定. 一个宿主机里的runner可以维护多个不同的service. 而gitlab-ci在收到需要build的请求时,会通知service执行你在.gitlab-ci.yml里面指定好的脚本,然后根据命令行的返回结果来决定这次build的成功还是失败.

在了解完了这些概念以后我们就可以很轻松的搭建一个runner了.

GITLAB-CI搭配Runner的使用

安装Runner

  • 首先要找一台服务器来创建Runner,因为是要跟你的gitlab服务关联,所以服务器要可以访问你的gitlab服务。

  • 安装gitlab-CI-multi-runner

  • gitlab-ci-multi-runner是CI runner的运行程序,这里有多种安装方式(见这里),这里我们使用了第一种:在linux中安装软件。

1
友情提示: 在安装时由于网络问题不好安装,可以直接找到deb包下载后安装.

gitlab-ci-multi-runner命令介绍

  • 执行gitlab-ci-multi-runner help可以看到所有命令的简介,在每个命令加--help可以看到更加具体的参数,比如gitlab-ci-multi-runner start --help,命令的执行顺序为:register(注册runner)-->install(安装服务)-->start(运行服务).

GITLAB-CI配置

打开网址(比如你的gitlab服务地址是: http://gitlab.your.company/,那gitlab CI的地址就是:http://gitlab.your.company/ci),找到想要配置CI的项目,点击后面的按钮Add project to CI
,给项目配置CI功能.进入CI项目,进入Runners标签页面,可以看到CI的url和token,这2个值是待会用命令注册runner时所需要的。

在runner的服务器上注册runner,执行命令gitlab-ci-multi-runner register --user="你的用户",下面是执行命令后的交互信息。

1
友情提示:如果你用的是docker的执行方式,可以先把对应的docker的image下载下来,不然第一次执行CI会比较慢。
  • 安装服务

执行命令gitlab-ci-multi-runner install -n "服务名",后面的服务名是自己定义的名称,用来后面启动命名使用,与其相对的命令是uninstall.

启动服务,执行命令gitlab-ci-multi-runner start -n "服务名",与其相类似的命令有stop
和restart.

验证runner,执行gitlab-ci-multi-runner verify,可以看到runner的运行情况.

1
2
3
4
5
6
7
8
root@cloudeye:~# gitlab-ci-multi-runner verify

aliveINFO[0000] 79bf814a Veryfing runner... is
aliveINFO[0000] 207a4b34 Veryfing runner... is
aliveINFO[0000] 20f849f7 Veryfing runner... is
aliveINFO[0000] 6e07e13a Veryfing runner... is
aliveINFO[0000] 23be6deb Veryfing runner... is
aliveINFO[0000] 4e348964 Veryfing runner... is

启动服务后,可以在刚才的CI runners页面看到已经有runner出现了。

gitlab-ci.yaml文件

配置好了runner,要让CI跑起来,还需要在项目根目录放一个.gitlab-ci.yml文件,在这个文件里面可以定制CI的任务,下面是简单的示例文件,更多的用法可以看官方文档

1
2
3
4
5
6
7
jobName:
script:
- ls
- php command.php
...(比喻,使用直接删掉本行)
only:
develp

自己磕磕碰碰总结出来的,有不对的地方希望可以指正.

走走停停回头看

发表于 2016-07-12 | 分类于 随笔

忙忙碌碌的工作做太多,快到来不及反思自己.

上午闲暇时间看了一眼harry哥的github,扫了几眼,过了两三年了,还是觉得好优雅,有很多自己可以学习的地方.

这样也好,总要给自己设置一个又一个的目标,不断前行的.

Linda-2016-03-01 学习笔记

发表于 2016-03-01 | 分类于 技术

等价类划分

概念

等价列划分设计方法是把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每一个子集中选取少量具有代表性的数据作为测试用例。

例子

  • 登陆框用户名为手机

案例:

1
2
3
4
1. 用户名肯定是一个手机号
2. 手机号肯定是没有除数字以外的
3. 手机号是11位数字
4. 158、139、152、185

考虑测试用例写法:

1
2
3
4
5
6
7
1. 提供一个正常的手机号测试是否通过
2. 提供一个不正常的手机号是否通过
3. 提供一个带字母的数字是否通过
4. 提供一个纯数字的手机号是否通过
5. 提供一个非11位的数字是否通过
6. 提供158、139、152、185号段之外的手机号是否通过
7. 提供158、139、152、185号段内的手机号是否通过

测试准备(测试数据准备):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
---
- 18535663805

---
- 08535663805
- 51010000101

---
- x5101000010
- 1a510100001

---
- 1231413124
- 42523

---
- 12314131241231413124

---
- 13333333333
- 13333335555

---
- 15822222222
- 18522222222
- 13922222222

边界值分析

概念

使用边界值分析方法设计测试用例,首先:应确定边界情况。通常输入和输出等价类的边界,就是应着重测试的边界情况。其次,应但选取正好等于、刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值作为测试数据。

例子

  • 登陆框用户名为手机
  • 银行卡密码
  • qq个签

约定

发表于 2016-03-01 | 分类于 随笔

从梦中醒来时,梦里的情景历历在目,清晰可见。但在两三分钟内,这些场景就会一点点的湮灭。无论怎么努力,还是很难像记正常的事那样记住梦境,只能眼睁睁的看着它们从记忆里消失,不留痕迹。

这是每天都在发生的记忆擦除,每个人的记忆都在那一刻失效,但每个人都不介意。我们不但忘记了梦,我们还忘记了忘记。

F9E9A49B-0E24-4177-8E32-B734D39EB7DC.png

12…9
Marlon Fan

Marlon Fan

希望有一天可以用自己的一生讲一个完美的故事.
84 日志
2 分类
27 标签
晋ICP备20002733号 © 2012 – 2020 Marlon Fan
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.1.1