玩转consul(4)-ACL机制要点

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 本文基于Consul v1.6.2 这篇文章并不是consul ACL的完全配置手册,感兴趣的读者请阅读参考资料1 2. Token存在的意义 Token其实用于权限控制中,表征使用者的身份 Consul的ACL中重要的3个实体 Token最终与一组Policy关联, 可以理解为RBAC(基于角色的访问控制)中的权限。 3. 能够细粒度控制的权限有哪些? 4. 如何使用Token 详见参考资料2 curl \ --header "X-Consul-Token: <consul token>" \ http://127.0.0.1:8500/v1/agent/members 其实也可以附加在Query中,但官方不推荐 5. 配置文件示例 5.1 server模式 { "acl": { "enabled": true, "default_policy": "deny", "enable_token_persistence": true }, "datacenter": "dc1", "data_dir": "/data/consul", "disable_update_check": true, "server": true, "ui": true, "bind_addr": "192.168.100.3", "client_addr": "0.0.0.0", "node_name": "s1", "bootstrap_expect": 2, "retry_join": ["192.168.100.3", "192.168.100.4", "192.168.100.5"] } 5.2 client 模式 client上设置default token之后, 在client使用CLI或者执行API请求,都可以自动附加token ...

January 13, 2020 · 1 min

玩转高性能日志库ZAP(5)-异步写日志

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在复杂的业务逻辑中,一个请求就有可能带来10 ~ 30条日志的写入。打日志造成的开销很大,是无法被忽略的一环。怎样才能提高日志的写入速度? 2. 实验 首先来看几个实验 完整程序见 vearne/golab/zaplog 中的log.go log2.go log3.go 2.1 打印到文件 打印到指定文件中,并做归档 package zaplog import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) func InitLogger() *zap.Logger { // 动态调整日志级别 alevel := zap.NewAtomicLevel() hook := lumberjack.Logger{ Filename: "/tmp/tt1.log", MaxSize: 1024, // megabytes MaxBackups: 3, MaxAge: 7, //days Compress: true, // disabled by default } w := zapcore.AddSync(&hook) alevel.SetLevel(zap.InfoLevel) encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder core := zapcore.NewCore( zapcore.NewConsoleEncoder(encoderConfig), w, alevel, ) return zap.New(core) } 2.2 打印到终端 这应该是官方推荐的方式,特别是对于使用docker容器的场景,应用开发者的配置非常简单,无需指定日志文件路径,由容器管理服务采集容器的标准输出并归档。 ...

January 2, 2020 · 3 min

一个简单的陌生人聊天系统

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 因为一直对IM的实现非常感兴趣,前段时间也自己手撸了一个。 萌叔觉得,这个版本实现比较简单,但能够从部分反映出IM的概貌,可能会其他的朋友有所帮助, 因此决定分享出来。 1. 功能说明 这个聊天系统被设计为陌生人自动匹配进行聊天,因此不需要注册。分为2个模块,在线体验地址 chat.vearne.cc 备注:如果匹配不上,可以多开几个窗口,自己和自己聊 1.1 客户端 vearne/chat-ui 客户端采用Vue开发 依赖组件 MatheusrdSantos/vue-quick-chat 1.2 服务端 vearne/chat 采用golang开发 重要框架 olahol/melody – websocket框架 grpc/grpc-go – grpc框架 架构图 客户与接入层的通讯采用websocket + 自定义的文本协议实现。 自定义的协议其实都是携带着各种命令的JSON字符串,很容易看懂 比如 创建一个新的账号(由客户端发出) request { "cmd": "CRT_ACCOUNT", "nickName": "XXXX" } response { "cmd": "CRT_ACCOUNT", "nickName": "XXXX", "accountId": 1111 } 又比如 匹配一个聊天对象 request { "cmd": "MATCH", "accountId": 1111 } response { "cmd": "MATCH", "partnerId": 1111, "partnerName": "xxxx", "sessionId": 10000 "code": 0 } 具体请看github相关页面 ...

December 19, 2019 · 1 min

聊聊短地址服务的实现

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 一个长URL地址,形如 “https://www.google.com.hk/search?q=%E7%9F%AD%E5%9C%B0%E5%9D%80%E6%9C%8D%E5%8A%A1&oq=%E7%9F%AD%E5%9C%B0%E5%9D%80%E6%9C%8D%E5%8A%A1&aqs=chrome..69i57j69i61.4824j1j7&sourceid=chrome&ie=UTF-8" , 由于不可避免的带有PATH、各种参数和追踪标识,这导致URL往往很长。当你要把这个URL发送给其它人时,可能会变得很不方便。比如短信/微博有字数限制。或者对方将链接粘贴到浏览器时,容易漏掉部分数据。 因此短链接服务就显得非常重要。(短链接还可以使的生成的二维码色块更大,提高识别速度) 国内常见的短地址服务有新浪 百度 那么他们是怎么实现的呢? 建议先阅读参考资料1 2. 实现&原理 2.1 实现 萌叔受到参考资料1的启发也实现了一版。 vearne/tinyurl 代码及使用步骤见README 你也可以 在线尝试 2.2 生成短地址 1) 将长地址与一个整数建立映射(一对多) 这里整数使用int64,保存映射关系。笔者为了简单使用是MySQL数据库,如果为了更好的并发存储,还可以NoSQL数据库或者数据分分库分表。 "https://github.com/vearne/tinyurl" -> 10363 这里主键id就是整数值 长地址存储在url字段中 +-------+---------------------------------+---------------------+ | id | url | created_at | +-------+---------------------------------+---------------------+ | 10000 | http://vearne.cc/archives/39217 | 2019-11-28 14:02:56 | +-------+---------------------------------+---------------------+ 提示 这里不能用哈希的原因是,哈希后的值如果太短则容易出现碰撞,如果太长则压缩的效率太低,没有意义。 2)base62编码 整数如果直接按字符串传输,长度还是太长。可以对整数进行base62编码 之所以是62,其实是 26个小写英文字母[a-z], 26个大写英文字母[A-Z], 以及阿拉伯数字[0-9]。有意的避开特殊符号和不可见字符。 3) 拼接上域名 就得形如, 短链接地址: http://st.vearne.cc/2h9 2.3 访问短地址 访问短地址得到长地址的过程与短地址的生成过程刚好相反 1)base62解码 base62解码得到整数 ...

November 29, 2019 · 1 min

简易的p2p文件分发系统

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 写了一个非常简单的p2p文件分发系统 主要目的是验证p2p下载的主要思想,所以并不是完全按照BT的公开协议来实现的,已经经过了初步的测试,但并没有在生产环境,进行大规模的使用,因此请谨慎的使用。 传送门 vearne/p2p-sharer 2.组件 2.1 制作种子文件 ./p2p-sharer gen --tracker 192.168.10.200:35330 --filePath /tmp/Motrix-1.4.1.dmg --seedPath ./ tracker tracker 地址 filePath 待分发的文件路径 seedPath 生成的种子文件的存放路径 种子文件示例 { "fileName": "Motrix-1.4.1.dmg", "trackerAddr": "192.168.10.200:35330", "length": 66112269, "pieces": [{ "index": 0, "length": 1048576, "checksum": "92465aae88444d799891ad730877f0f8593f77be" }, { "index": 1, "length": 1048576, "checksum": "7dae558c74d72d67d65533cdcecbf5f6ff5bcce1" }, ... ] } 2.2 tracker 接口文档 保存(供其他node查询)文件分片在整个集群的分布情况 维护node的上下线情况(通过心跳) 2.3 node 接口文档 接收并执行下载任务 与其它node共享文件分片 3. 使用 git clone git@github.com:vearne/p2p-sharer.git 3.1 build 3.1.1 mac env GOOS=darwin GOARCH=amd64 go build ./ 3.1.2 linux env GOOS=linux GOARCH=amd64 go build ./ 3.2 配置文件 请将配置文件放置在 ...

September 10, 2019 · 1 min

从状态机看熔断器

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 提到熔断器,大家总会提到Hystrix.但是Hystrix似乎给人一种云里雾里的感觉. 前段时间,我维护的服务也增加了熔断机制,本文中,我将结合我自己的理解来谈谈熔断。 我们选择的是rubyist/circuitbreaker, 这个库的代码可读性比Hystrix以及afex/hystrix-go要高不少,也推荐一下。 2. 关于熔断 如上图所示,熔断器把客户端和服务提供方隔离开来,在客户端调用服务提供方接口时,对服务提供方的服务质量进行监测。如果服务提供方出现问题,则将触发熔断。 这里引出了几个问题 问题1:什么样的情况,可以理解为服务提供方出现了问题? 问题2:触发熔断会怎么样? 问题3:熔断打开以后,如何关闭? 3. 熔断器与状态机 在熔断器内部有3种状态 1. Closed 熔断器关闭 客户端正常访问服务提供方 2. Open 熔断器打开 阻断客户端对服务提供方的访问 3. Half Open 熔断器半开 熔断器开始重新判断是否需要继续熔断 熔断器只是简单在状态机的状态之间切换,至于熔断之后,如何处理客户端的请求,则是更上层业务代码的事情 结合rubyist/circuitbreaker,我来谈谈状态之间的装换条件 3.1 Closed -> Open 服务提供方出现异常 连续发生threshold个错误,立即熔断(ConsecutiveTripFunc) 2)单位时间请求数达到minSamples个,错误率达到rate,即熔断(RateTripFunc) 3)单位时间发生threshold个错误,立即熔断(ThresholdTripFunc) 这里的错误完全是由业务系统来定义,可能是 a) 后端接口的响应严重超时 b) 后端服务返回异常的错误(如HTTP协议 5xx) c) RPC返回有异常的错误码 当熔断器处于Open状态,客户端对服务提供方的访问被阻断了。我们该如何响应客户端的请求? 通常而言,可以有这么几种做法 直接返回给客户端失败信息 将返回降级后的结果 比如针对读请求,可以返回固定值,或者cache中的历史数据 3.2 Open -> Half Open 在熔断一段时间后,服务提供方的服务可能已经恢复了。那么怎么感知到服务提供方的服务已经恢复。 rubyist/circuitbreaker的做法是使用定时器 (2种实现,ExponentialBackOff和ConstantBackOff),每当定时器的设定的时间到达,熔断器的状态从Open 切换到 Half Open ...

July 14, 2019 · 1 min

玩转Prometheus(5)-监控Redis和MySQL的工具包(业务层)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 对于高可用的服务,监控的粒度往往都会非常细。如果恰好你也在使用 Prometheus, 也需要在业务层对Redis连接池和MySQL连接池进行监控。那么此篇文章对你而言将是一种福利。 Redis Client go-redis/redis MySQL Client jinzhu/gorm 2. 样例代码 go get github.com/vearne/golib main.go package main import ( "github.com/go-redis/redis" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/vearne/golib/metric" "log" "net/http" "time" ) func main() { // init redis client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", PoolSize: 100, }) // ***监控Redis连接池*** metric.AddRedis(client, "car") // init mysql DSN := "test:xxxx@tcp(localhost:6379)/somebiz?charset=utf8&loc=Asia%2FShanghai&parseTime=true" mysqldb, err := gorm.Open("mysql", DSN) if err != nil { panic(err) } mysqldb.DB().SetMaxIdleConns(50) mysqldb.DB().SetMaxOpenConns(100) mysqldb.DB().SetConnMaxLifetime(5 * time.Minute) // ***监控MySQL连接池*** metric.AddMySQL(mysqldb, "car") // do some thing for i := 0; i < 30; i++ { go func() { for { client.Get("a").String() time.Sleep(200 * time.Millisecond) mysqldb.Exec("show tables") } }() } http.Handle("/metrics", promhttp.Handler()) log.Fatal(http.ListenAndServe(":9090", nil)) log.Println("starting...") } func AddRedis(client RedisClient, role string) func AddMySQL(client *gorm.DB, role string) role 仅用于区分不同的Redis实例 ...

July 1, 2019 · 2 min

玩转CONSUL(2)–分布式锁

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 分布式锁的场景,大家应该都有遇到过。比如对可靠性有较高要求的系统中,我们需要做主备切换。这时我们可以利用分布式锁,来做选主动作,抢到锁作为主,执行对应的任务,剩余的实例作为备份 redis和zookeeper都可以用来做分布式锁,典型的如redis,可以使用SETNX命令来实现分布式锁。本文将介绍基于consul的分布式锁实现 2. 例子 测试例子 test_lock.go package main import ( "github.com/hashicorp/consul/api" "log" "strconv" "sync" "time" ) func main() { wg := &sync.WaitGroup{} for i := 0; i < 3; i++ { wg.Add(1) go tryLock("mylock", "session"+strconv.Itoa(i), wg) } wg.Wait() } func tryLock(key string, sessionName string, wg *sync.WaitGroup) { defer wg.Done() // Get a new client config := &api.Config{ Address: "dev1:8500", Scheme: "http", } client, err := api.NewClient(config) if err != nil { panic(err) } opts := &api.LockOptions{ Key: key, SessionName: sessionName, } lock, err := client.LockOpts(opts) log.Println(sessionName, "try to get lock obj") for i := 0; i < 3; i++ { leaderCh, err := lock.Lock(nil) if err != nil { log.Println("err", err, sessionName) } if leaderCh == nil{ log.Println("err", err, sessionName) continue } log.Println(sessionName, "lock and sleep") time.Sleep(5 * time.Second) err = lock.Unlock() if err != nil { log.Fatal("err", err) } log.Println(sessionName, "unlock") time.Sleep(5 * time.Second) } } 3. 原理 consul中锁的主要是依赖KV Store和Session相关API ...

May 1, 2019 · 2 min

gin 统计请求状态信息

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 gin-gonic/gin 是Golang API 开发中最常用到的web框架。我们可以轻松的写一个gin的中间件获取HTTP的状态码, 然后暴露给phrometheus。但是如果我想获取的body体中的错误码呢? 2. 官方的例子 package main import ( "time" "log" "github.com/gin-gonic/gin" ) func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // before request c.Next() // after request latency := time.Since(t) log.Println("latency", latency) // access the status we are sending status := c.Writer.Status() log.Println("status_code", status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } 3. 读取body中的错误码 假定我们的服务在请求处理失败的情况下,返回如下结构体 ...

October 16, 2018 · 2 min

一次电视盒子(Android)后端API的测试经历

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 最近笔者在为电视盒子APP开发API后端,API服务的开发不复杂,可是如何进行测试确实让我伤透了脑筋。在此把最终的方案记录下来,以享众人。 1. 解决域名指向问题 1.1 最初,我们希望的做法是用Charles或是Fiddler,在盒子上设置代理,然后把盒子的流量引到我的测试机上。默认盒子无法配置网络代理,可能需要使用如下命令调出Android的原生设置界面 自行下载对应版本的Settings.apk(小米盒子无需安装) install adb install Settings.apk 3)启动Settings adb shell am start -n com.android.settings/com.android.settings.Settings 但是发现很多流量并没有通过Charles。原来在Android设备上设置网络代理,并无法确保所有的服务都使用这个代理,APP可以选择使用这个代理。 1.2 最后我们直接使用极路由(路由器) 的自定义hosts插件 修改完成以后,可以连上设备,验证一下域名的解析修改是否生效 adb shell ping abc.com PS:另外也可以通过让设备root然后直接修改盒子的/etc/hosts文件,其实我们还曾经想过使用dnsmasq污染DNS的方式来修改域名指向,但是在盒子上貌似并没有生效。 2. 解决HTTPS证书问题 APP对后端的调用大部分接口走的都是HTTPS。对于测试场景,我们不应该使用公司真实的SSL证书,因此我选择自签名SSL证书。 自签名的证书,因为加密算法或者摘要算法选择不当仍然被浏览器或者设备认为是不安全的。 这里推荐一个库 Fishdrowned/ssl 生成完毕,需要在nginx配置域名的证书和私钥,同时需要将根证书导入设备 adb push ca.perm /sdcard/download/ 导入后,需要在打开盒子界面,安装此根证书 adb shell am start -n com.android.settings/com.android.settings.Settings 总结 本文介绍了如何针对移动设备(Android)后端API服务进行测试,希望能对大家有所启发。 请我喝瓶饮料

August 31, 2018 · 1 min