Showing preview only (779K chars total). Download the full file or copy to clipboard to get everything.
Repository: geektutu/7days-golang
Branch: master
Commit: cf3644382101
Files: 396
Total size: 692.2 KB
Directory structure:
gitextract_0lo96hoo/
├── .gitignore
├── LICENSE
├── README.md
├── demo-wasm/
│ ├── .gitignore
│ ├── callback/
│ │ ├── Makefile
│ │ ├── index.html
│ │ └── main.go
│ ├── hello-world/
│ │ ├── Makefile
│ │ ├── index.html
│ │ └── main.go
│ ├── manipulate-dom/
│ │ ├── Makefile
│ │ ├── index.html
│ │ └── main.go
│ └── register-functions/
│ ├── Makefile
│ ├── index.html
│ └── main.go
├── gee-bolt/
│ ├── day1-pages/
│ │ ├── go.mod
│ │ ├── meta.go
│ │ └── page.go
│ ├── day2-mmap/
│ │ ├── db.go
│ │ └── go.mod
│ └── day3-tree/
│ ├── go.mod
│ ├── meta.go
│ ├── node.go
│ └── page.go
├── gee-cache/
│ ├── day1-lru/
│ │ └── geecache/
│ │ ├── go.mod
│ │ └── lru/
│ │ ├── lru.go
│ │ └── lru_test.go
│ ├── day2-single-node/
│ │ └── geecache/
│ │ ├── byteview.go
│ │ ├── cache.go
│ │ ├── geecache.go
│ │ ├── geecache_test.go
│ │ ├── go.mod
│ │ └── lru/
│ │ ├── lru.go
│ │ └── lru_test.go
│ ├── day3-http-server/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ └── lru/
│ │ │ ├── lru.go
│ │ │ └── lru_test.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day4-consistent-hash/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ └── lru/
│ │ │ ├── lru.go
│ │ │ └── lru_test.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day5-multi-nodes/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ ├── lru/
│ │ │ │ ├── lru.go
│ │ │ │ └── lru_test.go
│ │ │ └── peers.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── run.sh
│ ├── day6-single-flight/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ ├── lru/
│ │ │ │ ├── lru.go
│ │ │ │ └── lru_test.go
│ │ │ ├── peers.go
│ │ │ └── singleflight/
│ │ │ ├── singleflight.go
│ │ │ └── singleflight_test.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── run.sh
│ ├── day7-proto-buf/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── geecachepb/
│ │ │ │ ├── geecachepb.pb.go
│ │ │ │ └── geecachepb.proto
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ ├── lru/
│ │ │ │ ├── lru.go
│ │ │ │ └── lru_test.go
│ │ │ ├── peers.go
│ │ │ └── singleflight/
│ │ │ ├── singleflight.go
│ │ │ └── singleflight_test.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── run.sh
│ └── doc/
│ ├── geecache-day1.md
│ ├── geecache-day2.md
│ ├── geecache-day3.md
│ ├── geecache-day4.md
│ ├── geecache-day5.md
│ ├── geecache-day6.md
│ ├── geecache-day7.md
│ └── geecache.md
├── gee-orm/
│ ├── day1-database-sql/
│ │ ├── cmd_test/
│ │ │ └── main.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ └── raw_test.go
│ ├── day2-reflect-schema/
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day3-save-query/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day4-chain-operation/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day5-hooks/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── hooks.go
│ │ ├── hooks_test.go
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day6-transaction/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── hooks.go
│ │ ├── hooks_test.go
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ ├── table_test.go
│ │ └── transaction.go
│ ├── day7-migrate/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── hooks.go
│ │ ├── hooks_test.go
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ ├── table_test.go
│ │ └── transaction.go
│ ├── doc/
│ │ ├── geeorm-day1.md
│ │ ├── geeorm-day2.md
│ │ ├── geeorm-day3.md
│ │ ├── geeorm-day4.md
│ │ ├── geeorm-day5.md
│ │ ├── geeorm-day6.md
│ │ ├── geeorm-day7.md
│ │ └── geeorm.md
│ └── run_test.sh
├── gee-rpc/
│ ├── day1-codec/
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ └── server.go
│ ├── day2-client/
│ │ ├── client.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ └── server.go
│ ├── day3-service/
│ │ ├── client.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── day4-timeout/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── day5-http-debug/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── debug.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── day6-load-balance/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── debug.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ ├── service_test.go
│ │ └── xclient/
│ │ ├── discovery.go
│ │ └── xclient.go
│ ├── day7-registry/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── debug.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── registry/
│ │ │ └── registry.go
│ │ ├── server.go
│ │ ├── service.go
│ │ ├── service_test.go
│ │ └── xclient/
│ │ ├── discovery.go
│ │ ├── discovery_gee.go
│ │ └── xclient.go
│ └── doc/
│ ├── geerpc-day1.md
│ ├── geerpc-day2.md
│ ├── geerpc-day3.md
│ ├── geerpc-day4.md
│ ├── geerpc-day5.md
│ ├── geerpc-day6.md
│ ├── geerpc-day7.md
│ └── geerpc.md
├── gee-web/
│ ├── README.md
│ ├── day1-http-base/
│ │ ├── base1/
│ │ │ ├── go.mod
│ │ │ └── main.go
│ │ ├── base2/
│ │ │ ├── go.mod
│ │ │ └── main.go
│ │ └── base3/
│ │ ├── gee/
│ │ │ ├── gee.go
│ │ │ └── go.mod
│ │ ├── go.mod
│ │ └── main.go
│ ├── day2-context/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── go.mod
│ │ │ └── router.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day3-router/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── go.mod
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day4-group/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day5-middleware/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── logger.go
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day6-template/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── logger.go
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ ├── static/
│ │ │ ├── css/
│ │ │ │ └── geektutu.css
│ │ │ └── file1.txt
│ │ └── templates/
│ │ ├── arr.tmpl
│ │ ├── css.tmpl
│ │ └── custom_func.tmpl
│ ├── day7-panic-recover/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── logger.go
│ │ │ ├── recovery.go
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ └── doc/
│ ├── gee-day1.md
│ ├── gee-day2.md
│ ├── gee-day3.md
│ ├── gee-day4.md
│ ├── gee-day5.md
│ ├── gee-day6.md
│ ├── gee-day7.md
│ └── gee.md
└── questions/
└── 7days-golang-q1.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
.idea
.vscode
tmp
*.db
*.sum
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Dai Jie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# 7 days golang programs from scratch
[](https://github.com/geektutu/7days-golang)
[](https://mit-license.org/)
<details>
<summary><strong>README 中文版本</strong></summary>
<div>
## 7天用Go从零实现系列
7天能写什么呢?类似 gin 的 web 框架?类似 groupcache 的分布式缓存?或者一个简单的 Python 解释器?希望这个仓库能给你答案。
推荐先阅读 **[Go 语言简明教程](https://geektutu.com/post/quick-golang.html)**,一篇文章了解Go的基本语法、并发编程,依赖管理等内容。
推荐 **[Go 语言笔试面试题](https://geektutu.com/post/qa-golang.html)**,加深对 Go 语言的理解。
推荐 **[Go 语言高性能编程](https://geektutu.com/post/high-performance-go.html)**([项目地址](https://github.com/geektutu/high-performance-go)),写出高性能的 Go 代码。
期待关注我的「[知乎专栏](https://zhuanlan.zhihu.com/geekgo)」和「[微博](http://weibo.com/geektutu)」,查看最近的文章和动态。
### 7天用Go从零实现Web框架 - Gee
[Gee](https://geektutu.com/post/gee.html) 是一个模仿 [gin](https://github.com/gin-gonic/gin) 实现的 Web 框架,[Go Gin简明教程](https://geektutu.com/post/quick-go-gin.html)可以快速入门。
- 第一天:[前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html) | [Code](gee-web/day1-http-base)
- 第二天:[上下文设计(Context)](https://geektutu.com/post/gee-day2.html) | [Code](gee-web/day2-context)
- 第三天:[Trie树路由(Router)](https://geektutu.com/post/gee-day3.html) | [Code](gee-web/day3-router)
- 第四天:[分组控制(Group)](https://geektutu.com/post/gee-day4.html) | [Code](gee-web/day4-group)
- 第五天:[中间件(Middleware)](https://geektutu.com/post/gee-day5.html) | [Code](gee-web/day5-middleware)
- 第六天:[HTML模板(Template)](https://geektutu.com/post/gee-day6.html) | [Code](gee-web/day6-template)
- 第七天:[错误恢复(Panic Recover)](https://geektutu.com/post/gee-day7.html) | [Code](gee-web/day7-panic-recover)
### 7天用Go从零实现分布式缓存 GeeCache
[GeeCache](https://geektutu.com/post/geecache.html) 是一个模仿 [groupcache](https://github.com/golang/groupcache) 实现的分布式缓存系统
- 第一天:[LRU 缓存淘汰策略](https://geektutu.com/post/geecache-day1.html) | [Code](gee-cache/day1-lru)
- 第二天:[单机并发缓存](https://geektutu.com/post/geecache-day2.html) | [Code](gee-cache/day2-single-node)
- 第三天:[HTTP 服务端](https://geektutu.com/post/geecache-day3.html) | [Code](gee-cache/day3-http-server)
- 第四天:[一致性哈希(Hash)](https://geektutu.com/post/geecache-day4.html) | [Code](gee-cache/day4-consistent-hash)
- 第五天:[分布式节点](https://geektutu.com/post/geecache-day5.html) | [Code](gee-cache/day5-multi-nodes)
- 第六天:[防止缓存击穿](https://geektutu.com/post/geecache-day6.html) | [Code](gee-cache/day6-single-flight)
- 第七天:[使用 Protobuf 通信](https://geektutu.com/post/geecache-day7.html) | [Code](gee-cache/day7-proto-buf)
### 7天用Go从零实现ORM框架 GeeORM
[GeeORM](https://geektutu.com/post/geeorm.html) 是一个模仿 [gorm](https://github.com/jinzhu/gorm) 和 [xorm](https://github.com/go-xorm/xorm) 的 ORM 框架
gorm 准备推出完全重写的 v2 版本(目前还在开发中),相对 gorm-v1 来说,xorm 的设计更容易理解,所以 geeorm 接口设计上主要参考了 xorm,一些细节实现上参考了 gorm。
- 第一天:[database/sql 基础](https://geektutu.com/post/geeorm-day1.html) | [Code](gee-orm/day1-database-sql)
- 第二天:[对象表结构映射](https://geektutu.com/post/geeorm-day2.html) | [Code](gee-orm/day2-reflect-schema)
- 第三天:[记录新增和查询](https://geektutu.com/post/geeorm-day3.html) | [Code](gee-orm/day3-save-query)
- 第四天:[链式操作与更新删除](https://geektutu.com/post/geeorm-day4.html) | [Code](gee-orm/day4-chain-operation)
- 第五天:[实现钩子(Hooks)](https://geektutu.com/post/geeorm-day5.html) | [Code](gee-orm/day5-hooks)
- 第六天:[支持事务(Transaction)](https://geektutu.com/post/geeorm-day6.html) | [Code](gee-orm/day6-transaction)
- 第七天:[数据库迁移(Migrate)](https://geektutu.com/post/geeorm-day7.html) | [Code](gee-orm/day7-migrate)
### 7天用Go从零实现RPC框架 GeeRPC
[GeeRPC](https://geektutu.com/post/geerpc.html) 是一个基于 [net/rpc](https://github.com/golang/go/tree/master/src/net/rpc) 开发的 RPC 框架
GeeRPC 是基于 Go 语言标准库 `net/rpc` 实现的,添加了协议交换、服务注册与发现、负载均衡等功能,代码约 1k。
- 第一天 - [服务端与消息编码](https://geektutu.com/post/geerpc-day1.html) | [Code](gee-rpc/day1-codec)
- 第二天 - [支持并发与异步的客户端](https://geektutu.com/post/geerpc-day2.html) | [Code](gee-rpc/day2-client)
- 第三天 - [服务注册(service register)](https://geektutu.com/post/geerpc-day3.html) | [Code](gee-rpc/day3-service )
- 第四天 - [超时处理(timeout)](https://geektutu.com/post/geerpc-day4.html) | [Code](gee-rpc/day4-timeout )
- 第五天 - [支持HTTP协议](https://geektutu.com/post/geerpc-day5.html) | [Code](gee-rpc/day5-http-debug)
- 第六天 - [负载均衡(load balance)](https://geektutu.com/post/geerpc-day6.html) | [Code](gee-rpc/day6-load-balance)
- 第七天 - [服务发现与注册中心(registry)](https://geektutu.com/post/geerpc-day7.html) | [Code](gee-rpc/day7-registry)
### WebAssembly 使用示例
具体的实践过程记录在 [Go WebAssembly 简明教程](https://geektutu.com/post/quick-go-wasm.html)。
- 示例一:Hello World | [Code](demo-wasm/hello-world)
- 示例二:注册函数 | [Code](demo-wasm/register-functions)
- 示例三:操作 DOM | [Code](demo-wasm/manipulate-dom)
- 示例四:回调函数 | [Code](demo-wasm/callback)
</div>
</details>
What can be accomplished in 7 days? A gin-like web framework? A distributed cache like groupcache? Or a simple Python interpreter? Hope this repo can give you the answer.
## Web Framework - Gee
[Gee](https://geektutu.com/post/gee.html) is a [gin](https://github.com/gin-gonic/gin)-like framework
- Day 1 - http.Handler Interface Basic [Code](gee-web/day1-http-base)
- Day 2 - Design a Flexiable Context [Code](gee-web/day2-context)
- Day 3 - Router with Trie-Tree Algorithm [Code](gee-web/day3-router)
- Day 4 - Group Control [Code](gee-web/day4-group)
- Day 5 - Middleware Mechanism [Code](gee-web/day5-middleware)
- Day 6 - Embeded Template Support [Code](gee-web/day6-template)
- Day 7 - Panic Recover & Make it Robust [Code](gee-web/day7-panic-recover)
## Distributed Cache - GeeCache
[GeeCache](https://geektutu.com/post/geecache.html) is a [groupcache](https://github.com/golang/groupcache)-like distributed cache
- Day 1 - LRU (Least Recently Used) Caching Strategy [Code](gee-cache/day1-lru)
- Day 2 - Single Machine Concurrent Cache [Code](gee-cache/day2-single-node)
- Day 3 - Launch a HTTP Server [Code](gee-cache/day3-http-server)
- Day 4 - Consistent Hash Algorithm [Code](gee-cache/day4-consistent-hash)
- Day 5 - Communication between Distributed Nodes [Code](gee-cache/day5-multi-nodes)
- Day 6 - Cache Breakdown & Single Flight | [Code](gee-cache/day6-single-flight)
- Day 7 - Use Protobuf as RPC Data Exchange Type | [Code](gee-cache/day7-proto-buf)
## Object Relational Mapping - GeeORM
[GeeORM](https://geektutu.com/post/geeorm.html) is a [gorm](https://github.com/jinzhu/gorm)-like and [xorm](https://github.com/go-xorm/xorm)-like object relational mapping library
Xorm's desgin is easier to understand than gorm-v1, so the main designs references xorm and some detailed implementions references gorm-v1.
- Day 1 - database/sql Basic | [Code](gee-orm/day1-database-sql)
- Day 2 - Object Schame Mapping | [Code](gee-orm/day2-reflect-schema)
- Day 3 - Insert and Query | [Code](gee-orm/day3-save-query)
- Day 4 - Chain, Delete and Update | [Code](gee-orm/day4-chain-operation)
- Day 5 - Support Hooks | [Code](gee-orm/day5-hooks)
- Day 6 - Support Transaction | [Code](gee-orm/day6-transaction)
- Day 7 - Migrate Database | [Code](gee-orm/day7-migrate)
## RPC Framework - GeeRPC
[GeeRPC](https://geektutu.com/post/geerpc.html) is a [net/rpc](https://github.com/golang/go/tree/master/src/net/rpc)-like RPC framework
Based on golang standard library `net/rpc`, GeeRPC implements more features. eg, protocol exchange, service registration and discovery, load balance, etc.
- Day 1 - Server Message Codec | [Code](gee-rpc/day1-codec)
- Day 2 - Concurrent Client | [Code](gee-rpc/day2-client)
- Day 3 - Service Register | [Code](gee-rpc/day3-service )
- Day 4 - Timeout Processing | [Code](gee-rpc/day4-timeout )
- Day 5 - Support HTTP Protocol | [Code](gee-rpc/day5-http-debug)
- Day 6 - Load Balance | [Code](gee-rpc/day6-load-balance)
- Day 7 - Discovery and Registry | [Code](gee-rpc/day7-registry)
## Golang WebAssembly Demo
- Demo 1 - Hello World [Code](demo-wasm/hello-world)
- Demo 2 - Register Functions [Code](demo-wasm/register-functions)
- Demo 3 - Manipulate DOM [Code](demo-wasm/manipulate-dom)
- Demo 4 - Callback [Code](demo-wasm/callback)
================================================
FILE: demo-wasm/.gitignore
================================================
*.wasm
static
================================================
FILE: demo-wasm/callback/Makefile
================================================
all: static/main.wasm static/wasm_exec.js
ifeq (, $(shell which goexec))
go get -u github.com/shurcooL/goexec
endif
goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))'
static/wasm_exec.js:
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static
static/main.wasm: main.go
GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm .
================================================
FILE: demo-wasm/callback/index.html
================================================
<html>
<head>
<script src="static/wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("static/main.wasm"), go.importObject)
.then((result) => go.run(result.instance));
</script>
</head>
<body>
<input id="num" type="number" />
<button id="btn" onclick="fibFunc(num.value * 1, (v)=> ans.innerHTML=v)">Click</button>
<p id="ans"></p>
</body>
</html>
================================================
FILE: demo-wasm/callback/main.go
================================================
// main.go
package main
import (
"syscall/js"
"time"
)
func fib(i int) int {
if i == 0 || i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}
func fibFunc(this js.Value, args []js.Value) interface{} {
callback := args[len(args)-1]
go func() {
time.Sleep(3 * time.Second)
v := fib(args[0].Int())
callback.Invoke(v)
}()
js.Global().Get("ans").Set("innerHTML", "Waiting 3s...")
return nil
}
func main() {
done := make(chan int, 0)
js.Global().Set("fibFunc", js.FuncOf(fibFunc))
<-done
}
================================================
FILE: demo-wasm/hello-world/Makefile
================================================
all: static/main.wasm static/wasm_exec.js
ifeq (, $(shell which goexec))
go get -u github.com/shurcooL/goexec
endif
goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))'
static/wasm_exec.js:
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static
static/main.wasm: main.go
GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm .
================================================
FILE: demo-wasm/hello-world/index.html
================================================
<html>
<head>
<script src="static/wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("static/main.wasm"), go.importObject)
.then((result) => go.run(result.instance));
</script>
</head>
</html>
================================================
FILE: demo-wasm/hello-world/main.go
================================================
// main.go
package main
import "syscall/js"
func main() {
alert := js.Global().Get("alert")
alert.Invoke("Hello World!")
}
================================================
FILE: demo-wasm/manipulate-dom/Makefile
================================================
all: static/main.wasm static/wasm_exec.js
ifeq (, $(shell which goexec))
go get -u github.com/shurcooL/goexec
endif
goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))'
static/wasm_exec.js:
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static
static/main.wasm: main.go
GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm .
================================================
FILE: demo-wasm/manipulate-dom/index.html
================================================
<html>
<head>
<script src="static/wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("static/main.wasm"), go.importObject)
.then((result) => go.run(result.instance));
</script>
</head>
<body>
<input id="num" type="number" />
<button id="btn">Click</button>
<p id="ans">1</p>
</body>
</html>
================================================
FILE: demo-wasm/manipulate-dom/main.go
================================================
package main
import (
"strconv"
"syscall/js"
)
func fib(i int) int {
if i == 0 || i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}
var (
document = js.Global().Get("document")
numEle = document.Call("getElementById", "num")
ansEle = document.Call("getElementById", "ans")
btnEle = js.Global().Get("btn")
)
func fibFunc(this js.Value, args []js.Value) interface{} {
v := numEle.Get("value")
if num, err := strconv.Atoi(v.String()); err == nil {
ansEle.Set("innerHTML", js.ValueOf(fib(num)))
}
return nil
}
func main() {
done := make(chan int, 0)
btnEle.Call("addEventListener", "click", js.FuncOf(fibFunc))
<-done
}
================================================
FILE: demo-wasm/register-functions/Makefile
================================================
all: static/main.wasm static/wasm_exec.js
ifeq (, $(shell which goexec))
go get -u github.com/shurcooL/goexec
endif
goexec 'http.ListenAndServe(`:9999`, http.FileServer(http.Dir(`.`)))'
static/wasm_exec.js:
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" static
static/main.wasm: main.go
GO111MODULE=auto GOOS=js GOARCH=wasm go build -o static/main.wasm .
================================================
FILE: demo-wasm/register-functions/index.html
================================================
<html>
<head>
<script src="static/wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("static/main.wasm"), go.importObject)
.then((result) => go.run(result.instance));
</script>
</head>
<body>
<input id="num" type="number" />
<button id="btn" onclick="ans.innerHTML=fibFunc(num.value * 1)">Click</button>
<p id="ans">1</p>
</body>
</html>
================================================
FILE: demo-wasm/register-functions/main.go
================================================
// main.go
package main
import "syscall/js"
func fib(i int) int {
if i == 0 || i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}
func fibFunc(this js.Value, args []js.Value) interface{} {
return js.ValueOf(fib(args[0].Int()))
}
func main() {
done := make(chan int, 0)
js.Global().Set("fibFunc", js.FuncOf(fibFunc))
<-done
}
================================================
FILE: gee-bolt/day1-pages/go.mod
================================================
module geebolt
go 1.13
================================================
FILE: gee-bolt/day1-pages/meta.go
================================================
package geebolt
import (
"errors"
"hash/fnv"
"unsafe"
)
// Represent a marker value to indicate that a file is a gee-bolt DB
const magic uint32 = 0xED0CDAED
type meta struct {
magic uint32
pageSize uint32
pgid uint64
checksum uint64
}
func (m *meta) sum64() uint64 {
var h = fnv.New64a()
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
return h.Sum64()
}
func (m *meta) validate() error {
if m.magic != magic {
return errors.New("invalid magic number")
}
if m.checksum != m.sum64() {
return errors.New("invalid checksum")
}
return nil
}
================================================
FILE: gee-bolt/day1-pages/page.go
================================================
package geebolt
import (
"fmt"
"reflect"
"unsafe"
)
const pageHeaderSize = unsafe.Sizeof(page{})
const branchPageElementSize = unsafe.Sizeof(branchPageElement{})
const leafPageElementSize = unsafe.Sizeof(leafPageElement{})
const maxKeysPerPage = 1024
const (
branchPageFlag uint16 = iota
leafPageFlag
metaPageFlag
freelistPageFlag
)
type page struct {
id uint64
flags uint16
count uint16
overflow uint32
}
type leafPageElement struct {
pos uint32
ksize uint32
vsize uint32
}
type branchPageElement struct {
pos uint32
ksize uint32
pgid uint64
}
func (p *page) typ() string {
switch p.flags {
case branchPageFlag:
return "branch"
case leafPageFlag:
return "leaf"
case metaPageFlag:
return "meta"
case freelistPageFlag:
return "freelist"
}
return fmt.Sprintf("unknown<%02x>", p.flags)
}
func (p *page) meta() *meta {
return (*meta)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + pageHeaderSize))
}
func (p *page) dataPtr() unsafe.Pointer {
return unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)) + pageHeaderSize,
Len: int(p.count),
Cap: int(p.count),
})
}
func (p *page) leafPageElement(index uint16) *leafPageElement {
off := pageHeaderSize + uintptr(index)*leafPageElementSize
return (*leafPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + off))
}
func (p *page) leafPageElements() []leafPageElement {
if p.count == 0 {
return nil
}
return *(*[]leafPageElement)(p.dataPtr())
}
func (p *page) branchPageElement(index uint16) *branchPageElement {
off := pageHeaderSize + uintptr(index)*branchPageElementSize
return (*branchPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + off))
}
func (p *page) branchPageElements() []branchPageElement {
if p.count == 0 {
return nil
}
return *(*[]branchPageElement)(p.dataPtr())
}
================================================
FILE: gee-bolt/day2-mmap/db.go
================================================
package geebolt
import "os"
type DB struct {
data []byte
file *os.File
}
const maxMapSize = 1 << 31
func (db *DB) mmap(sz int) error {
b, err := syscall.Mmap()
}
func Open(path string) {
}
================================================
FILE: gee-bolt/day2-mmap/go.mod
================================================
module geebolt
go 1.13
================================================
FILE: gee-bolt/day3-tree/go.mod
================================================
module geebolt
go 1.13
================================================
FILE: gee-bolt/day3-tree/meta.go
================================================
package geebolt
import (
"errors"
"hash/fnv"
"unsafe"
)
// Represent a marker value to indicate that a file is a gee-bolt DB
const magic uint32 = 0xED0CDAED
type meta struct {
magic uint32
pageSize uint32
pgid uint64
checksum uint64
}
func (m *meta) sum64() uint64 {
var h = fnv.New64a()
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
return h.Sum64()
}
func (m *meta) validate() error {
if m.magic != magic {
return errors.New("invalid magic number")
}
if m.checksum != m.sum64() {
return errors.New("invalid checksum")
}
return nil
}
================================================
FILE: gee-bolt/day3-tree/node.go
================================================
package geebolt
import (
"bytes"
"sort"
)
type kv struct {
key []byte
value []byte
}
type node struct {
isLeaf bool
key []byte
parent *node
children []*node
kvs []kv
}
func (n *node) root() *node {
if n.parent == nil {
return n
}
return n.parent.root()
}
func (n *node) index(key []byte) (index int, exact bool) {
index = sort.Search(len(n.kvs), func(i int) bool {
return bytes.Compare(n.kvs[i].key, key) != -1
})
exact = len(n.kvs) > 0 && index < len(n.kvs) && bytes.Equal(n.kvs[index].key, key)
return
}
func (n *node) put(oldKey, newKey, value []byte) {
index, exact := n.index(oldKey)
if !exact {
n.kvs = append(n.kvs, kv{})
copy(n.kvs[index+1:], n.kvs[index:])
}
kv := &n.kvs[index]
kv.key = newKey
kv.value = value
}
func (n *node) del(key []byte) {
index, exact := n.index(key)
if exact {
n.kvs = append(n.kvs[:index], n.kvs[index+1:]...)
}
}
================================================
FILE: gee-bolt/day3-tree/page.go
================================================
package geebolt
import (
"fmt"
"reflect"
"unsafe"
)
const pageHeaderSize = unsafe.Sizeof(page{})
const branchPageElementSize = unsafe.Sizeof(branchPageElement{})
const leafPageElementSize = unsafe.Sizeof(leafPageElement{})
const maxKeysPerPage = 1024
const (
branchPageFlag uint16 = iota
leafPageFlag
metaPageFlag
freelistPageFlag
)
type page struct {
id uint64
flags uint16
count uint16
overflow uint32
}
type leafPageElement struct {
pos uint32
ksize uint32
vsize uint32
}
type branchPageElement struct {
pos uint32
ksize uint32
pgid uint64
}
func (p *page) typ() string {
switch p.flags {
case branchPageFlag:
return "branch"
case leafPageFlag:
return "leaf"
case metaPageFlag:
return "meta"
case freelistPageFlag:
return "freelist"
}
return fmt.Sprintf("unknown<%02x>", p.flags)
}
func (p *page) meta() *meta {
return (*meta)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + pageHeaderSize))
}
func (p *page) dataPtr() unsafe.Pointer {
return unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)) + pageHeaderSize,
Len: int(p.count),
Cap: int(p.count),
})
}
func (p *page) leafPageElement(index uint16) *leafPageElement {
off := pageHeaderSize + uintptr(index)*leafPageElementSize
return (*leafPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + off))
}
func (p *page) leafPageElements() []leafPageElement {
if p.count == 0 {
return nil
}
return *(*[]leafPageElement)(p.dataPtr())
}
func (p *page) branchPageElement(index uint16) *branchPageElement {
off := pageHeaderSize + uintptr(index)*branchPageElementSize
return (*branchPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + off))
}
func (p *page) branchPageElements() []branchPageElement {
if p.count == 0 {
return nil
}
return *(*[]branchPageElement)(p.dataPtr())
}
================================================
FILE: gee-cache/day1-lru/geecache/go.mod
================================================
module geecache
go 1.13
================================================
FILE: gee-cache/day1-lru/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day1-lru/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day2-single-node/geecache/byteview.go
================================================
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
================================================
FILE: gee-cache/day2-single-node/geecache/cache.go
================================================
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
================================================
FILE: gee-cache/day2-single-node/geecache/geecache.go
================================================
package geecache
import (
"fmt"
"log"
"sync"
)
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
}
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
func (g *Group) load(key string) (value ByteView, err error) {
return g.getLocally(key)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
================================================
FILE: gee-cache/day2-single-node/geecache/geecache_test.go
================================================
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Fatal("callback failed")
}
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
================================================
FILE: gee-cache/day2-single-node/geecache/go.mod
================================================
module geecache
go 1.13
================================================
FILE: gee-cache/day2-single-node/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day2-single-node/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day3-http-server/geecache/byteview.go
================================================
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
================================================
FILE: gee-cache/day3-http-server/geecache/cache.go
================================================
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
================================================
FILE: gee-cache/day3-http-server/geecache/geecache.go
================================================
package geecache
import (
"fmt"
"log"
"sync"
)
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
}
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
func (g *Group) load(key string) (value ByteView, err error) {
return g.getLocally(key)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
================================================
FILE: gee-cache/day3-http-server/geecache/geecache_test.go
================================================
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Fatal("callback failed")
}
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
================================================
FILE: gee-cache/day3-http-server/geecache/go.mod
================================================
module geecache
go 1.13
================================================
FILE: gee-cache/day3-http-server/geecache/http.go
================================================
package geecache
import (
"fmt"
"log"
"net/http"
"strings"
)
const defaultBasePath = "/_geecache/"
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
}
// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
return &HTTPPool{
self: self,
basePath: defaultBasePath,
}
}
// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, p.basePath) {
panic("HTTPPool serving unexpected path: " + r.URL.Path)
}
p.Log("%s %s", r.Method, r.URL.Path)
// /<basepath>/<groupname>/<key> required
parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
if len(parts) != 2 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
groupName := parts[0]
key := parts[1]
group := GetGroup(groupName)
if group == nil {
http.Error(w, "no such group: "+groupName, http.StatusNotFound)
return
}
view, err := group.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}
================================================
FILE: gee-cache/day3-http-server/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day3-http-server/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day3-http-server/go.mod
================================================
module example
go 1.13
require geecache v0.0.0
replace geecache => ./geecache
================================================
FILE: gee-cache/day3-http-server/main.go
================================================
package main
/*
$ curl http://localhost:9999/_geecache/scores/Tom
630
$ curl http://localhost:9999/_geecache/scores/kkk
kkk not exist
*/
import (
"fmt"
"geecache"
"log"
"net/http"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func main() {
geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
addr := "localhost:9999"
peers := geecache.NewHTTPPool(addr)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr, peers))
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/byteview.go
================================================
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/cache.go
================================================
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash.go
================================================
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
// Hash maps bytes to uint32
type Hash func(data []byte) uint32
// Map constains all hashed keys
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
// New creates a Map instance
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
// Add adds some keys to the hash.
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
// Get gets the closest item in the hash to the provided key.
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash_test.go
================================================
package consistenthash
import (
"strconv"
"testing"
)
func TestHashing(t *testing.T) {
hash := New(3, func(key []byte) uint32 {
i, _ := strconv.Atoi(string(key))
return uint32(i)
})
// Given the above hash function, this will give replicas with "hashes":
// 2, 4, 6, 12, 14, 16, 22, 24, 26
hash.Add("6", "4", "2")
testCases := map[string]string{
"2": "2",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
// Adds 8, 18, 28
hash.Add("8")
// 27 should now map to 8.
testCases["27"] = "8"
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/geecache.go
================================================
package geecache
import (
"fmt"
"log"
"sync"
)
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
}
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
func (g *Group) load(key string) (value ByteView, err error) {
return g.getLocally(key)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/geecache_test.go
================================================
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Fatal("callback failed")
}
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/go.mod
================================================
module geecache
go 1.13
================================================
FILE: gee-cache/day4-consistent-hash/geecache/http.go
================================================
package geecache
import (
"fmt"
"log"
"net/http"
"strings"
)
const defaultBasePath = "/_geecache/"
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
}
// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
return &HTTPPool{
self: self,
basePath: defaultBasePath,
}
}
// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, p.basePath) {
panic("HTTPPool serving unexpected path: " + r.URL.Path)
}
p.Log("%s %s", r.Method, r.URL.Path)
// /<basepath>/<groupname>/<key> required
parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
if len(parts) != 2 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
groupName := parts[0]
key := parts[1]
group := GetGroup(groupName)
if group == nil {
http.Error(w, "no such group: "+groupName, http.StatusNotFound)
return
}
view, err := group.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day4-consistent-hash/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day4-consistent-hash/go.mod
================================================
module example
go 1.13
require geecache v0.0.0
replace geecache => ./geecache
================================================
FILE: gee-cache/day4-consistent-hash/main.go
================================================
package main
/*
$ curl http://localhost:9999/_geecache/scores/Tom
630
$ curl http://localhost:9999/_geecache/scores/kkk
kkk not exist
*/
import (
"fmt"
"geecache"
"log"
"net/http"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func main() {
geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
addr := "localhost:9999"
peers := geecache.NewHTTPPool(addr)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr, peers))
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/byteview.go
================================================
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/cache.go
================================================
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash.go
================================================
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
// Hash maps bytes to uint32
type Hash func(data []byte) uint32
// Map constains all hashed keys
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
// New creates a Map instance
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
// Add adds some keys to the hash.
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
// Get gets the closest item in the hash to the provided key.
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash_test.go
================================================
package consistenthash
import (
"strconv"
"testing"
)
func TestHashing(t *testing.T) {
hash := New(3, func(key []byte) uint32 {
i, _ := strconv.Atoi(string(key))
return uint32(i)
})
// Given the above hash function, this will give replicas with "hashes":
// 2, 4, 6, 12, 14, 16, 22, 24, 26
hash.Add("6", "4", "2")
testCases := map[string]string{
"2": "2",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
// Adds 8, 18, 28
hash.Add("8")
// 27 should now map to 8.
testCases["27"] = "8"
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/geecache.go
================================================
package geecache
import (
"fmt"
"log"
"sync"
)
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
}
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
// RegisterPeers registers a PeerPicker for choosing remote peer
func (g *Group) RegisterPeers(peers PeerPicker) {
if g.peers != nil {
panic("RegisterPeerPicker called more than once")
}
g.peers = peers
}
func (g *Group) load(key string) (value ByteView, err error) {
if g.peers != nil {
if peer, ok := g.peers.PickPeer(key); ok {
if value, err = g.getFromPeer(peer, key); err == nil {
return value, nil
}
log.Println("[GeeCache] Failed to get from peer", err)
}
}
return g.getLocally(key)
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
bytes, err := peer.Get(g.name, key)
if err != nil {
return ByteView{}, err
}
return ByteView{b: bytes}, nil
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/geecache_test.go
================================================
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Fatal("callback failed")
}
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/go.mod
================================================
module geecache
go 1.13
================================================
FILE: gee-cache/day5-multi-nodes/geecache/http.go
================================================
package geecache
import (
"fmt"
"geecache/consistenthash"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"sync"
)
const (
defaultBasePath = "/_geecache/"
defaultReplicas = 50
)
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
mu sync.Mutex // guards peers and httpGetters
peers *consistenthash.Map
httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
}
// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
return &HTTPPool{
self: self,
basePath: defaultBasePath,
}
}
// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, p.basePath) {
panic("HTTPPool serving unexpected path: " + r.URL.Path)
}
p.Log("%s %s", r.Method, r.URL.Path)
// /<basepath>/<groupname>/<key> required
parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
if len(parts) != 2 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
groupName := parts[0]
key := parts[1]
group := GetGroup(groupName)
if group == nil {
http.Error(w, "no such group: "+groupName, http.StatusNotFound)
return
}
view, err := group.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}
// Set updates the pool's list of peers.
func (p *HTTPPool) Set(peers ...string) {
p.mu.Lock()
defer p.mu.Unlock()
p.peers = consistenthash.New(defaultReplicas, nil)
p.peers.Add(peers...)
p.httpGetters = make(map[string]*httpGetter, len(peers))
for _, peer := range peers {
p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
}
}
// PickPeer picks a peer according to key
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
p.mu.Lock()
defer p.mu.Unlock()
if peer := p.peers.Get(key); peer != "" && peer != p.self {
p.Log("Pick peer %s", peer)
return p.httpGetters[peer], true
}
return nil, false
}
var _ PeerPicker = (*HTTPPool)(nil)
type httpGetter struct {
baseURL string
}
func (h *httpGetter) Get(group string, key string) ([]byte, error) {
u := fmt.Sprintf(
"%v%v/%v",
h.baseURL,
url.QueryEscape(group),
url.QueryEscape(key),
)
res, err := http.Get(u)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned: %v", res.Status)
}
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %v", err)
}
return bytes, nil
}
var _ PeerGetter = (*httpGetter)(nil)
================================================
FILE: gee-cache/day5-multi-nodes/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day5-multi-nodes/geecache/peers.go
================================================
package geecache
// PeerPicker is the interface that must be implemented to locate
// the peer that owns a specific key.
type PeerPicker interface {
PickPeer(key string) (peer PeerGetter, ok bool)
}
// PeerGetter is the interface that must be implemented by a peer.
type PeerGetter interface {
Get(group string, key string) ([]byte, error)
}
================================================
FILE: gee-cache/day5-multi-nodes/go.mod
================================================
module example
go 1.13
require geecache v0.0.0
replace geecache => ./geecache
================================================
FILE: gee-cache/day5-multi-nodes/main.go
================================================
package main
/*
$ curl "http://localhost:9999/api?key=Tom"
630
$ curl "http://localhost:9999/api?key=kkk"
kkk not exist
*/
import (
"flag"
"fmt"
"geecache"
"log"
"net/http"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func createGroup() *geecache.Group {
return geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
}
func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
peers := geecache.NewHTTPPool(addr)
peers.Set(addrs...)
gee.RegisterPeers(peers)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr[7:], peers))
}
func startAPIServer(apiAddr string, gee *geecache.Group) {
http.Handle("/api", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
view, err := gee.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}))
log.Println("fontend server is running at", apiAddr)
log.Fatal(http.ListenAndServe(apiAddr[7:], nil))
}
func main() {
var port int
var api bool
flag.IntVar(&port, "port", 8001, "Geecache server port")
flag.BoolVar(&api, "api", false, "Start a api server?")
flag.Parse()
apiAddr := "http://localhost:9999"
addrMap := map[int]string{
8001: "http://localhost:8001",
8002: "http://localhost:8002",
8003: "http://localhost:8003",
}
var addrs []string
for _, v := range addrMap {
addrs = append(addrs, v)
}
gee := createGroup()
if api {
go startAPIServer(apiAddr, gee)
}
startCacheServer(addrMap[port], addrs, gee)
}
================================================
FILE: gee-cache/day5-multi-nodes/run.sh
================================================
#!/bin/bash
trap "rm server;kill 0" EXIT
go build -o server
./server -port=8001 &
./server -port=8002 &
./server -port=8003 -api=1 &
sleep 2
echo ">>> start test"
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
wait
================================================
FILE: gee-cache/day6-single-flight/geecache/byteview.go
================================================
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
================================================
FILE: gee-cache/day6-single-flight/geecache/cache.go
================================================
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
================================================
FILE: gee-cache/day6-single-flight/geecache/consistenthash/consistenthash.go
================================================
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
// Hash maps bytes to uint32
type Hash func(data []byte) uint32
// Map constains all hashed keys
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
// New creates a Map instance
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
// Add adds some keys to the hash.
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
// Get gets the closest item in the hash to the provided key.
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
================================================
FILE: gee-cache/day6-single-flight/geecache/consistenthash/consistenthash_test.go
================================================
package consistenthash
import (
"strconv"
"testing"
)
func TestHashing(t *testing.T) {
hash := New(3, func(key []byte) uint32 {
i, _ := strconv.Atoi(string(key))
return uint32(i)
})
// Given the above hash function, this will give replicas with "hashes":
// 2, 4, 6, 12, 14, 16, 22, 24, 26
hash.Add("6", "4", "2")
testCases := map[string]string{
"2": "2",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
// Adds 8, 18, 28
hash.Add("8")
// 27 should now map to 8.
testCases["27"] = "8"
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}
================================================
FILE: gee-cache/day6-single-flight/geecache/geecache.go
================================================
package geecache
import (
"fmt"
"geecache/singleflight"
"log"
"sync"
)
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
// use singleflight.Group to make sure that
// each key is only fetched once
loader *singleflight.Group
}
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
loader: &singleflight.Group{},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
// RegisterPeers registers a PeerPicker for choosing remote peer
func (g *Group) RegisterPeers(peers PeerPicker) {
if g.peers != nil {
panic("RegisterPeerPicker called more than once")
}
g.peers = peers
}
func (g *Group) load(key string) (value ByteView, err error) {
// each key is only fetched once (either locally or remotely)
// regardless of the number of concurrent callers.
viewi, err := g.loader.Do(key, func() (interface{}, error) {
if g.peers != nil {
if peer, ok := g.peers.PickPeer(key); ok {
if value, err = g.getFromPeer(peer, key); err == nil {
return value, nil
}
log.Println("[GeeCache] Failed to get from peer", err)
}
}
return g.getLocally(key)
})
if err == nil {
return viewi.(ByteView), nil
}
return
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
bytes, err := peer.Get(g.name, key)
if err != nil {
return ByteView{}, err
}
return ByteView{b: bytes}, nil
}
================================================
FILE: gee-cache/day6-single-flight/geecache/geecache_test.go
================================================
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Fatal("callback failed")
}
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
================================================
FILE: gee-cache/day6-single-flight/geecache/go.mod
================================================
module geecache
go 1.13
================================================
FILE: gee-cache/day6-single-flight/geecache/http.go
================================================
package geecache
import (
"fmt"
"geecache/consistenthash"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"sync"
)
const (
defaultBasePath = "/_geecache/"
defaultReplicas = 50
)
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
mu sync.Mutex // guards peers and httpGetters
peers *consistenthash.Map
httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
}
// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
return &HTTPPool{
self: self,
basePath: defaultBasePath,
}
}
// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, p.basePath) {
panic("HTTPPool serving unexpected path: " + r.URL.Path)
}
p.Log("%s %s", r.Method, r.URL.Path)
// /<basepath>/<groupname>/<key> required
parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
if len(parts) != 2 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
groupName := parts[0]
key := parts[1]
group := GetGroup(groupName)
if group == nil {
http.Error(w, "no such group: "+groupName, http.StatusNotFound)
return
}
view, err := group.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}
// Set updates the pool's list of peers.
func (p *HTTPPool) Set(peers ...string) {
p.mu.Lock()
defer p.mu.Unlock()
p.peers = consistenthash.New(defaultReplicas, nil)
p.peers.Add(peers...)
p.httpGetters = make(map[string]*httpGetter, len(peers))
for _, peer := range peers {
p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
}
}
// PickPeer picks a peer according to key
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
p.mu.Lock()
defer p.mu.Unlock()
if peer := p.peers.Get(key); peer != "" && peer != p.self {
p.Log("Pick peer %s", peer)
return p.httpGetters[peer], true
}
return nil, false
}
var _ PeerPicker = (*HTTPPool)(nil)
type httpGetter struct {
baseURL string
}
func (h *httpGetter) Get(group string, key string) ([]byte, error) {
u := fmt.Sprintf(
"%v%v/%v",
h.baseURL,
url.QueryEscape(group),
url.QueryEscape(key),
)
res, err := http.Get(u)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned: %v", res.Status)
}
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %v", err)
}
return bytes, nil
}
var _ PeerGetter = (*httpGetter)(nil)
================================================
FILE: gee-cache/day6-single-flight/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day6-single-flight/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day6-single-flight/geecache/peers.go
================================================
package geecache
// PeerPicker is the interface that must be implemented to locate
// the peer that owns a specific key.
type PeerPicker interface {
PickPeer(key string) (peer PeerGetter, ok bool)
}
// PeerGetter is the interface that must be implemented by a peer.
type PeerGetter interface {
Get(group string, key string) ([]byte, error)
}
================================================
FILE: gee-cache/day6-single-flight/geecache/singleflight/singleflight.go
================================================
package singleflight
import "sync"
// call is an in-flight or completed Do call
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
// Group represents a class of work and forms a namespace in which
// units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
================================================
FILE: gee-cache/day6-single-flight/geecache/singleflight/singleflight_test.go
================================================
package singleflight
import (
"testing"
)
func TestDo(t *testing.T) {
var g Group
v, err := g.Do("key", func() (interface{}, error) {
return "bar", nil
})
if v != "bar" || err != nil {
t.Errorf("Do v = %v, error = %v", v, err)
}
}
================================================
FILE: gee-cache/day6-single-flight/go.mod
================================================
module example
go 1.13
require geecache v0.0.0
replace geecache => ./geecache
================================================
FILE: gee-cache/day6-single-flight/main.go
================================================
package main
/*
$ curl "http://localhost:9999/api?key=Tom"
630
$ curl "http://localhost:9999/api?key=kkk"
kkk not exist
*/
import (
"flag"
"fmt"
"geecache"
"log"
"net/http"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func createGroup() *geecache.Group {
return geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
}
func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
peers := geecache.NewHTTPPool(addr)
peers.Set(addrs...)
gee.RegisterPeers(peers)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr[7:], peers))
}
func startAPIServer(apiAddr string, gee *geecache.Group) {
http.Handle("/api", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
view, err := gee.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}))
log.Println("fontend server is running at", apiAddr)
log.Fatal(http.ListenAndServe(apiAddr[7:], nil))
}
func main() {
var port int
var api bool
flag.IntVar(&port, "port", 8001, "Geecache server port")
flag.BoolVar(&api, "api", false, "Start a api server?")
flag.Parse()
apiAddr := "http://localhost:9999"
addrMap := map[int]string{
8001: "http://localhost:8001",
8002: "http://localhost:8002",
8003: "http://localhost:8003",
}
var addrs []string
for _, v := range addrMap {
addrs = append(addrs, v)
}
gee := createGroup()
if api {
go startAPIServer(apiAddr, gee)
}
startCacheServer(addrMap[port], addrs, gee)
}
================================================
FILE: gee-cache/day6-single-flight/run.sh
================================================
#!/bin/bash
trap "rm server;kill 0" EXIT
go build -o server
./server -port=8001 &
./server -port=8002 &
./server -port=8003 -api=1 &
sleep 2
echo ">>> start test"
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
wait
================================================
FILE: gee-cache/day7-proto-buf/geecache/byteview.go
================================================
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/cache.go
================================================
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash.go
================================================
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
// Hash maps bytes to uint32
type Hash func(data []byte) uint32
// Map constains all hashed keys
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
// New creates a Map instance
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
// Add adds some keys to the hash.
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
// Get gets the closest item in the hash to the provided key.
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash_test.go
================================================
package consistenthash
import (
"strconv"
"testing"
)
func TestHashing(t *testing.T) {
hash := New(3, func(key []byte) uint32 {
i, _ := strconv.Atoi(string(key))
return uint32(i)
})
// Given the above hash function, this will give replicas with "hashes":
// 2, 4, 6, 12, 14, 16, 22, 24, 26
hash.Add("6", "4", "2")
testCases := map[string]string{
"2": "2",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
// Adds 8, 18, 28
hash.Add("8")
// 27 should now map to 8.
testCases["27"] = "8"
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/geecache.go
================================================
package geecache
import (
"fmt"
pb "geecache/geecachepb"
"geecache/singleflight"
"log"
"sync"
)
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
// use singleflight.Group to make sure that
// each key is only fetched once
loader *singleflight.Group
}
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
loader: &singleflight.Group{},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
// RegisterPeers registers a PeerPicker for choosing remote peer
func (g *Group) RegisterPeers(peers PeerPicker) {
if g.peers != nil {
panic("RegisterPeerPicker called more than once")
}
g.peers = peers
}
func (g *Group) load(key string) (value ByteView, err error) {
// each key is only fetched once (either locally or remotely)
// regardless of the number of concurrent callers.
viewi, err := g.loader.Do(key, func() (interface{}, error) {
if g.peers != nil {
if peer, ok := g.peers.PickPeer(key); ok {
if value, err = g.getFromPeer(peer, key); err == nil {
return value, nil
}
log.Println("[GeeCache] Failed to get from peer", err)
}
}
return g.getLocally(key)
})
if err == nil {
return viewi.(ByteView), nil
}
return
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
req := &pb.Request{
Group: g.name,
Key: key,
}
res := &pb.Response{}
err := peer.Get(req, res)
if err != nil {
return ByteView{}, err
}
return ByteView{b: res.Value}, nil
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/geecache_test.go
================================================
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Fatal("callback failed")
}
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/geecachepb/geecachepb.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: geecachepb.proto
package geecachepb
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Request struct {
Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"`
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_889d0a4ad37a0d42, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetGroup() string {
if m != nil {
return m.Group
}
return ""
}
func (m *Request) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
type Response struct {
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_889d0a4ad37a0d42, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func init() {
proto.RegisterType((*Request)(nil), "geecachepb.Request")
proto.RegisterType((*Response)(nil), "geecachepb.Response")
}
func init() { proto.RegisterFile("geecachepb.proto", fileDescriptor_889d0a4ad37a0d42) }
var fileDescriptor_889d0a4ad37a0d42 = []byte{
// 148 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x4f, 0x4d, 0x4d,
0x4e, 0x4c, 0xce, 0x48, 0x2d, 0x48, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
0x28, 0x19, 0x72, 0xb1, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x89, 0x70, 0xb1, 0xa6,
0x17, 0xe5, 0x97, 0x16, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x41, 0x38, 0x42, 0x02, 0x5c,
0xcc, 0xd9, 0xa9, 0x95, 0x12, 0x4c, 0x60, 0x31, 0x10, 0x53, 0x49, 0x81, 0x8b, 0x23, 0x28, 0xb5,
0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x15, 0xa4, 0xa7, 0x2c, 0x31, 0xa7, 0x34, 0x15, 0xac, 0x87, 0x27,
0x08, 0xc2, 0x31, 0xb2, 0xe3, 0xe2, 0x72, 0x07, 0x69, 0x76, 0x06, 0x59, 0x22, 0x64, 0xc0, 0xc5,
0xec, 0x9e, 0x5a, 0x22, 0x24, 0xac, 0x87, 0xe4, 0x10, 0xa8, 0x9d, 0x52, 0x22, 0xa8, 0x82, 0x10,
0x53, 0x93, 0xd8, 0xc0, 0xee, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x5c, 0xd5, 0xdd, 0x09,
0xbb, 0x00, 0x00, 0x00,
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/geecachepb/geecachepb.proto
================================================
syntax = "proto3";
package geecachepb;
message Request {
string group = 1;
string key = 2;
}
message Response {
bytes value = 1;
}
service GroupCache {
rpc Get(Request) returns (Response);
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/go.mod
================================================
module geecache
go 1.13
require github.com/golang/protobuf v1.3.3
================================================
FILE: gee-cache/day7-proto-buf/geecache/http.go
================================================
package geecache
import (
"fmt"
"geecache/consistenthash"
pb "geecache/geecachepb"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"sync"
"github.com/golang/protobuf/proto"
)
const (
defaultBasePath = "/_geecache/"
defaultReplicas = 50
)
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
mu sync.Mutex // guards peers and httpGetters
peers *consistenthash.Map
httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
}
// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
return &HTTPPool{
self: self,
basePath: defaultBasePath,
}
}
// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, p.basePath) {
panic("HTTPPool serving unexpected path: " + r.URL.Path)
}
p.Log("%s %s", r.Method, r.URL.Path)
// /<basepath>/<groupname>/<key> required
parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
if len(parts) != 2 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
groupName := parts[0]
key := parts[1]
group := GetGroup(groupName)
if group == nil {
http.Error(w, "no such group: "+groupName, http.StatusNotFound)
return
}
view, err := group.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Write the value to the response body as a proto message.
body, err := proto.Marshal(&pb.Response{Value: view.ByteSlice()})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(body)
}
// Set updates the pool's list of peers.
func (p *HTTPPool) Set(peers ...string) {
p.mu.Lock()
defer p.mu.Unlock()
p.peers = consistenthash.New(defaultReplicas, nil)
p.peers.Add(peers...)
p.httpGetters = make(map[string]*httpGetter, len(peers))
for _, peer := range peers {
p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
}
}
// PickPeer picks a peer according to key
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
p.mu.Lock()
defer p.mu.Unlock()
if peer := p.peers.Get(key); peer != "" && peer != p.self {
p.Log("Pick peer %s", peer)
return p.httpGetters[peer], true
}
return nil, false
}
var _ PeerPicker = (*HTTPPool)(nil)
type httpGetter struct {
baseURL string
}
func (h *httpGetter) Get(in *pb.Request, out *pb.Response) error {
u := fmt.Sprintf(
"%v%v/%v",
h.baseURL,
url.QueryEscape(in.GetGroup()),
url.QueryEscape(in.GetKey()),
)
res, err := http.Get(u)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("server returned: %v", res.Status)
}
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("reading response body: %v", err)
}
if err = proto.Unmarshal(bytes, out); err != nil {
return fmt.Errorf("decoding response body: %v", err)
}
return nil
}
var _ PeerGetter = (*httpGetter)(nil)
================================================
FILE: gee-cache/day7-proto-buf/geecache/lru/lru.go
================================================
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/lru/lru_test.go
================================================
package lru
import (
"reflect"
"testing"
)
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
func TestAdd(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key", String("1"))
lru.Add("key", String("111"))
if lru.nbytes != int64(len("key")+len("111")) {
t.Fatal("expected 6 but got", lru.nbytes)
}
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/peers.go
================================================
package geecache
import pb "geecache/geecachepb"
// PeerPicker is the interface that must be implemented to locate
// the peer that owns a specific key.
type PeerPicker interface {
PickPeer(key string) (peer PeerGetter, ok bool)
}
// PeerGetter is the interface that must be implemented by a peer.
type PeerGetter interface {
Get(in *pb.Request, out *pb.Response) error
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/singleflight/singleflight.go
================================================
package singleflight
import "sync"
// call is an in-flight or completed Do call
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
// Group represents a class of work and forms a namespace in which
// units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
================================================
FILE: gee-cache/day7-proto-buf/geecache/singleflight/singleflight_test.go
================================================
package singleflight
import (
"testing"
)
func TestDo(t *testing.T) {
var g Group
v, err := g.Do("key", func() (interface{}, error) {
return "bar", nil
})
if v != "bar" || err != nil {
t.Errorf("Do v = %v, error = %v", v, err)
}
}
================================================
FILE: gee-cache/day7-proto-buf/go.mod
================================================
module example
go 1.13
require geecache v0.0.0
replace geecache => ./geecache
================================================
FILE: gee-cache/day7-proto-buf/main.go
================================================
package main
/*
$ curl "http://localhost:9999/api?key=Tom"
630
$ curl "http://localhost:9999/api?key=kkk"
kkk not exist
*/
import (
"flag"
"fmt"
"geecache"
"log"
"net/http"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func createGroup() *geecache.Group {
return geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
}
func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
peers := geecache.NewHTTPPool(addr)
peers.Set(addrs...)
gee.RegisterPeers(peers)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr[7:], peers))
}
func startAPIServer(apiAddr string, gee *geecache.Group) {
http.Handle("/api", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
view, err := gee.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}))
log.Println("fontend server is running at", apiAddr)
log.Fatal(http.ListenAndServe(apiAddr[7:], nil))
}
func main() {
var port int
var api bool
flag.IntVar(&port, "port", 8001, "Geecache server port")
flag.BoolVar(&api, "api", false, "Start a api server?")
flag.Parse()
apiAddr := "http://localhost:9999"
addrMap := map[int]string{
8001: "http://localhost:8001",
8002: "http://localhost:8002",
8003: "http://localhost:8003",
}
var addrs []string
for _, v := range addrMap {
addrs = append(addrs, v)
}
gee := createGroup()
if api {
go startAPIServer(apiAddr, gee)
}
startCacheServer(addrMap[port], addrs, gee)
}
================================================
FILE: gee-cache/day7-proto-buf/run.sh
================================================
#!/bin/bash
trap "rm server;kill 0" EXIT
go build -o server
./server -port=8001 &
./server -port=8002 &
./server -port=8003 -api=1 &
sleep 2
echo ">>> start test"
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
wait
================================================
FILE: gee-cache/doc/geecache-day1.md
================================================
---
title: 动手写分布式缓存 - GeeCache第一天 LRU 缓存淘汰策略
date: 2020-02-11 22:00:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了常用的三种缓存淘汰(失效)算法:先进先出(FIFO),最少使用(LFU) 和 最近最少使用(LRU),并实现 LRU 算法和相应的测试代码。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- 分布式缓存
- LRU
- 缓存失效
image: post/geecache-day1/lru_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day1 LRU 缓存淘汰策略
---
本文是[7天用Go从零实现分布式缓存GeeCache教程系列](https://geektutu.com/post/geecache.html)的第一篇。
- 介绍常用的三种缓存淘汰(失效)算法:FIFO,LFU 和 LRU
- 实现 LRU 缓存淘汰算法,**代码约80行**
## 1 FIFO/LFU/LRU 算法简介
GeeCache 的缓存全部存储在内存中,内存是有限的,因此不可能无限制地添加数据。假定我们设置缓存能够使用的内存大小为 N,那么在某一个时间点,添加了某一条缓存记录之后,占用内存超过了 N,这个时候就需要从缓存中移除一条或多条数据了。那移除谁呢?我们肯定希望尽可能移除“没用”的数据,那如何判定数据“有用”还是“没用”呢?
### 1.1 FIFO(First In First Out)
先进先出,也就是淘汰缓存中最老(最早添加)的记录。FIFO 认为,最早添加的记录,其不再被使用的可能性比刚添加的可能性大。这种算法的实现也非常简单,创建一个队列,新增记录添加到队尾,每次内存不够时,淘汰队首。但是很多场景下,部分记录虽然是最早添加但也最常被访问,而不得不因为呆的时间太长而被淘汰。这类数据会被频繁地添加进缓存,又被淘汰出去,导致缓存命中率降低。
### 1.2 LFU(Least Frequently Used)
最少使用,也就是淘汰缓存中访问频率最低的记录。LFU 认为,如果数据过去被访问多次,那么将来被访问的频率也更高。LFU 的实现需要维护一个按照访问次数排序的队列,每次访问,访问次数加1,队列重新排序,淘汰时选择访问次数最少的即可。LFU 算法的命中率是比较高的,但缺点也非常明显,维护每个记录的访问次数,对内存的消耗是很高的;另外,如果数据的访问模式发生变化,LFU 需要较长的时间去适应,也就是说 LFU 算法受历史数据的影响比较大。例如某个数据历史上访问次数奇高,但在某个时间点之后几乎不再被访问,但因为历史访问次数过高,而迟迟不能被淘汰。
### 1.3 LRU(Least Recently Used)
最近最少使用,相对于仅考虑时间因素的 FIFO 和仅考虑访问频率的 LFU,LRU 算法可以认为是相对平衡的一种淘汰算法。LRU 认为,如果数据最近被访问过,那么将来被访问的概率也会更高。LRU 算法的实现非常简单,维护一个队列,如果某条记录被访问了,则移动到队尾,那么队首则是最近最少访问的数据,淘汰该条记录即可。
## 2 LRU 算法实现
### 2.1 核心数据结构

这张图很好地表示了 LRU 算法最核心的 2 个数据结构
- 绿色的是字典(map),存储键和值的映射关系。这样根据某个键(key)查找对应的值(value)的复杂是`O(1)`,在字典中插入一条记录的复杂度也是`O(1)`。
- 红色的是双向链表(double linked list)实现的队列。将所有的值放到双向链表中,这样,当访问到某个值时,将其移动到队尾的复杂度是`O(1)`,在队尾新增一条记录以及删除一条记录的复杂度均为`O(1)`。
接下来我们创建一个包含字典和双向链表的结构体类型 Cache,方便实现后续的增删查改操作。
[day1-lru/geecache/lru/lru.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day1-lru/geecache/lru)
```go
package lru
import "container/list"
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
maxBytes int64
nbytes int64
ll *list.List
cache map[string]*list.Element
// optional and executed when an entry is purged.
OnEvicted func(key string, value Value)
}
type entry struct {
key string
value Value
}
// Value use Len to count how many bytes it takes
type Value interface {
Len() int
}
```
- 在这里我们直接使用 Go 语言标准库实现的双向链表`list.List`。
- 字典的定义是 `map[string]*list.Element`,键是字符串,值是双向链表中对应节点的指针。
- `maxBytes` 是允许使用的最大内存,`nbytes` 是当前已使用的内存,`OnEvicted` 是某条记录被移除时的回调函数,可以为 nil。
- 键值对 `entry` 是双向链表节点的数据类型,在链表中仍保存每个值对应的 key 的好处在于,淘汰队首节点时,需要用 key 从字典中删除对应的映射。
- 为了通用性,我们允许值是实现了 `Value` 接口的任意类型,该接口只包含了一个方法 `Len() int`,用于返回值所占用的内存大小。
方便实例化 `Cache`,实现 `New()` 函数:
```go
// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}
```
### 2.2 查找功能
查找主要有 2 个步骤,第一步是从字典中找到对应的双向链表的节点,第二步,将该节点移动到队尾。
```go
// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}
```
- 如果键对应的链表节点存在,则将对应节点移动到队尾,并返回查找到的值。
- `c.ll.MoveToFront(ele)`,即将链表中的节点 `ele` 移动到队尾(双向链表作为队列,队首队尾是相对的,在这里约定 front 为队尾)
### 2.3 删除
这里的删除,实际上是缓存淘汰。即移除最近最少访问的节点(队首)
```go
// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}
```
- `c.ll.Back()` 取到队首节点,从链表中删除。
- `delete(c.cache, kv.key)`,从字典中 `c.cache` 删除该节点的映射关系。
- 更新当前所用的内存 `c.nbytes`。
- 如果回调函数 `OnEvicted` 不为 nil,则调用回调函数。
### 2.4 新增/修改
```go
// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}
```
- 如果键存在,则更新对应节点的值,并将该节点移到队尾。
- 不存在则是新增场景,首先队尾添加新节点 `&entry{key, value}`, 并字典中添加 key 和节点的映射关系。
- 更新 `c.nbytes`,如果超过了设定的最大值 `c.maxBytes`,则移除最少访问的节点。
最后,为了方便测试,我们实现 `Len()` 用来获取添加了多少条数据。
```go
// Len the number of cache entries
func (c *Cache) Len() int {
return c.ll.Len()
}
```
## 3 测试
例如,我们可以尝试添加几条数据,测试 `Get` 方法
[day1-lru/geecache/lru/lru_test.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day1-lru/geecache/lru)
```go
type String string
func (d String) Len() int {
return len(d)
}
func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}
```
测试,当使用内存超过了设定值时,是否会触发“无用”节点的移除:
```go
func TestRemoveoldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))
if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("Removeoldest key1 failed")
}
}
```
测试回调函数能否被调用:
```go
func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))
expect := []string{"key1", "k2"}
if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}
```
## 附 推荐阅读
- [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
- [list 官方文档 - golang.org](https://golang.org/pkg/container/list/)
================================================
FILE: gee-cache/doc/geecache-day2.md
================================================
---
title: 动手写分布式缓存 - GeeCache第二天 单机并发缓存
date: 2020-02-12 22:00:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了 sync.Mutex 互斥锁的使用,并发控制 LRU 缓存。实现 GeeCache 核心数据结构 Group,缓存不存在时,调用回调函数(callback)获取源数据。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- 分布式缓存
- 互斥锁
- sync.Mutex
image: post/geecache-day2/concurrent_cache_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day2 单机并发缓存
---

本文是[7天用Go从零实现分布式缓存GeeCache](https://geektutu.com/post/geecache.html)的第二篇。
- 介绍 sync.Mutex 互斥锁的使用,并实现 LRU 缓存的并发控制。
- 实现 GeeCache 核心数据结构 Group,缓存不存在时,调用回调函数获取源数据,**代码约150行**
## 1 sync.Mutex
多个协程(goroutine)同时读写同一个变量,在并发度较高的情况下,会发生冲突。确保一次只有一个协程(goroutine)可以访问该变量以避免冲突,这称之为`互斥`,互斥锁可以解决这个问题。
> sync.Mutex 是一个互斥锁,可以由不同的协程加锁和解锁。
`sync.Mutex` 是 Go 语言标准库提供的一个互斥锁,当一个协程(goroutine)获得了这个锁的拥有权后,其它请求锁的协程(goroutine) 就会阻塞在 `Lock()` 方法的调用上,直到调用 `Unlock()` 锁被释放。
接下来举一个简单的例子,假设有10个并发的协程打印了同一个数字`100`,为了避免重复打印,实现了`printOnce(num int)` 函数,使用集合 set 记录已打印过的数字,如果数字已打印过,则不再打印。
```go
var set = make(map[int]bool, 0)
func printOnce(num int) {
if _, exist := set[num]; !exist {
fmt.Println(num)
}
set[num] = true
}
func main() {
for i := 0; i < 10; i++ {
go printOnce(100)
}
time.Sleep(time.Second)
}
```
我们运行 `go run .` 会发生什么情况呢?
```bash
$ go run .
100
100
```
有时候打印 2 次,有时候打印 4 次,有时候还会触发 panic,因为对同一个数据结构`set`的访问冲突了。接下来用互斥锁的`Lock()`和`Unlock()` 方法将冲突的部分包裹起来:
```go
var m sync.Mutex
var set = make(map[int]bool, 0)
func printOnce(num int) {
m.Lock()
if _, exist := set[num]; !exist {
fmt.Println(num)
}
set[num] = true
m.Unlock()
}
func main() {
for i := 0; i < 10; i++ {
go printOnce(100)
}
time.Sleep(time.Second)
}
```
```bash
$ go run .
100
```
相同的数字只会被打印一次。当一个协程调用了 `Lock()` 方法时,其他协程被阻塞了,直到`Unlock()`调用将锁释放。因此被包裹部分的代码就能够避免冲突,实现互斥。
`Unlock()`释放锁还有另外一种写法:
```go
func printOnce(num int) {
m.Lock()
defer m.Unlock()
if _, exist := set[num]; !exist {
fmt.Println(num)
}
set[num] = true
}
```
## 2 支持并发读写
上一篇文章 [GeeCache 第一天](https://geektutu.com/post/geecache-day1.html) 实现了 LRU 缓存淘汰策略。接下来我们使用 `sync.Mutex` 封装 LRU 的几个方法,使之支持并发的读写。在这之前,我们抽象了一个只读数据结构 `ByteView` 用来表示缓存值,是 GeeCache 主要的数据结构之一。
[day2-single-node/geecache/byteview.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day2-single-node/geecache)
```go
package geecache
// A ByteView holds an immutable view of bytes.
type ByteView struct {
b []byte
}
// Len returns the view's length
func (v ByteView) Len() int {
return len(v.b)
}
// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
return string(v.b)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
```
- ByteView 只有一个数据成员,`b []byte`,b 将会存储真实的缓存值。选择 byte 类型是为了能够支持任意的数据类型的存储,例如字符串、图片等。
- 实现 `Len() int` 方法,我们在 lru.Cache 的实现中,要求被缓存对象必须实现 Value 接口,即 `Len() int` 方法,返回其所占的内存大小。
- `b` 是只读的,使用 `ByteSlice()` 方法返回一个拷贝,防止缓存值被外部程序修改。
接下来就可以为 lru.Cache 添加并发特性了。
[day2-single-node/geecache/cache.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day2-single-node/geecache)
```go
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
func (c *cache) add(key string, value ByteView) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
c.lru = lru.New(c.cacheBytes, nil)
}
c.lru.Add(key, value)
}
func (c *cache) get(key string) (value ByteView, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.lru == nil {
return
}
if v, ok := c.lru.Get(key); ok {
return v.(ByteView), ok
}
return
}
```
- `cache.go` 的实现非常简单,实例化 lru,封装 get 和 add 方法,并添加互斥锁 mu。
- 在 `add` 方法中,判断了 `c.lru` 是否为 nil,如果等于 nil 再创建实例。这种方法称之为延迟初始化(Lazy Initialization),一个对象的延迟初始化意味着该对象的创建将会延迟至第一次使用该对象时。主要用于提高性能,并减少程序内存要求。
## 3 主体结构 Group
Group 是 GeeCache 最核心的数据结构,负责与用户的交互,并且控制缓存值存储和获取的流程。
```bash
是
接收 key --> 检查是否被缓存 -----> 返回缓存值 ⑴
| 否 是
|-----> 是否应当从远程节点获取 -----> 与远程节点交互 --> 返回缓存值 ⑵
| 否
|-----> 调用`回调函数`,获取值并添加到缓存 --> 返回缓存值 ⑶
```
我们将在 `geecache.go` 中实现主体结构 Group,那么 GeeCache 的代码结构的雏形已经形成了。
```bash
geecache/
|--lru/
|--lru.go // lru 缓存淘汰策略
|--byteview.go // 缓存值的抽象与封装
|--cache.go // 并发控制
|--geecache.go // 负责与外部交互,控制缓存存储和获取的主流程
```
接下来我们将实现流程 ⑴ 和 ⑶,远程交互的部分后续再实现。
### 3.1 回调 Getter
我们思考一下,如果缓存不存在,应从数据源(文件,数据库等)获取数据并添加到缓存中。GeeCache 是否应该支持多种数据源的配置呢?不应该,一是数据源的种类太多,没办法一一实现;二是扩展性不好。如何从源头获取数据,应该是用户决定的事情,我们就把这件事交给用户好了。因此,我们设计了一个回调函数(callback),在缓存不存在时,调用这个函数,得到源数据。
[day2-single-node/geecache/geecache.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day2-single-node/geecache)
```go
// A Getter loads data for a key.
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
```
- 定义接口 Getter 和 回调函数 `Get(key string)([]byte, error)`,参数是 key,返回值是 []byte。
- 定义函数类型 GetterFunc,并实现 Getter 接口的 `Get` 方法。
- 函数类型实现某一个接口,称之为接口型函数,方便使用者在调用时既能够传入函数作为参数,也能够传入实现了该接口的结构体作为参数。
> 了解接口型函数的使用场景,可以参考 [Go 接口型函数的使用场景 - 7days-golang Q & A](https://geektutu.com/post/7days-golang-q1.html)
我们可以写一个测试用例来保证回调函数能够正常工作。
```go
func TestGetter(t *testing.T) {
var f Getter = GetterFunc(func(key string) ([]byte, error) {
return []byte(key), nil
})
expect := []byte("key")
if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {
t.Errorf("callback failed")
}
}
```
- 在这个测试用例中,我们借助 GetterFunc 的类型转换,将一个匿名回调函数转换成了接口 `f Getter`。
- 调用该接口的方法 `f.Get(key string)`,实际上就是在调用匿名回调函数。
> 定义一个函数类型 F,并且实现接口 A 的方法,然后在这个方法中调用自己。这是 Go 语言中将其他函数(参数返回值定义与 F 一致)转换为接口 A 的常用技巧。
### 3.2 Group 的定义
接下来是最核心数据结构 Group 的定义:
[day2-single-node/geecache/geecache.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day2-single-node/geecache)
```go
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
}
groups[name] = g
return g
}
// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
```
- 一个 Group 可以认为是一个缓存的命名空间,每个 Group 拥有一个唯一的名称 `name`。比如可以创建三个 Group,缓存学生的成绩命名为 scores,缓存学生信息的命名为 info,缓存学生课程的命名为 courses。
- 第二个属性是 `getter Getter`,即缓存未命中时获取源数据的回调(callback)。
- 第三个属性是 `mainCache cache`,即一开始实现的并发缓存。
- 构建函数 `NewGroup` 用来实例化 Group,并且将 group 存储在全局变量 `groups` 中。
- `GetGroup` 用来特定名称的 Group,这里使用了只读锁 `RLock()`,因为不涉及任何冲突变量的写操作。
### 3.3 Group 的 Get 方法
接下来是 GeeCache 最为核心的方法 `Get`:
```go
// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
func (g *Group) load(key string) (value ByteView, err error) {
return g.getLocally(key)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
```
- Get 方法实现了上述所说的流程 ⑴ 和 ⑶。
- 流程 ⑴ :从 mainCache 中查找缓存,如果存在则返回缓存值。
- 流程 ⑶ :缓存不存在,则调用 load 方法,load 调用 getLocally(分布式场景下会调用 getFromPeer 从其他节点获取),getLocally 调用用户回调函数 `g.getter.Get()` 获取源数据,并且将源数据添加到缓存 mainCache 中(通过 populateCache 方法)
至此,这一章节的单机并发缓存就已经完成了。
## 4 测试
可以写测试用例,也可以写 main 函数来测试这一章节实现的功能。那我们通过测试用例来看一下,如何使用我们实现的单机并发缓存吧。
首先,用一个 map 模拟耗时的数据库。
```go
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
```
创建 group 实例,并测试 `Get` 方法
```go
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(db))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key] += 1
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
for k, v := range db {
if view, err := gee.Get(k); err != nil || view.String() != v {
t.Fatal("failed to get value of Tom")
} // load from callback function
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
} // cache hit
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
```
- 在这个测试用例中,我们主要测试了 2 种情况
- 1)在缓存为空的情况下,能够通过回调函数获取到源数据。
- 2)在缓存已经存在的情况下,是否直接从缓存中获取,为了实现这一点,使用 `loadCounts` 统计某个键调用回调函数的次数,如果次数大于1,则表示调用了多次回调函数,没有缓存。
测试结果如下:
```bash
$ go test -run TestGet
2020/02/11 22:07:31 [SlowDB] search key Sam
2020/02/11 22:07:31 [GeeCache] hit
2020/02/11 22:07:31 [SlowDB] search key Tom
2020/02/11 22:07:31 [GeeCache] hit
2020/02/11 22:07:31 [SlowDB] search key Jack
2020/02/11 22:07:31 [GeeCache] hit
2020/02/11 22:07:31 [SlowDB] search key unknown
PASS
ok geecache 0.008s
```
可以很清晰地看到,缓存为空时,调用了回调函数,第二次访问时,则直接从缓存中读取。
## 附 推荐阅读
- [Go 语言简明教程 - 并发编程](https://geektutu.com/post/quick-golang.html#7-并发编程-goroutine)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
- [sync 官方文档 - golang.org](https://golang.org/pkg/sync/)
================================================
FILE: gee-cache/doc/geecache-day3.md
================================================
---
title: 动手写分布式缓存 - GeeCache第三天 HTTP 服务端
date: 2020-02-12 23:00:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了如何使用标准库 http 搭建 HTTP Server,为 GeeCache 单机节点搭建 HTTP 服务,并进行相关的测试。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- 分布式缓存
- HTTP Server
image: post/geecache-day3/http_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day3 HTTP 服务端
---

本文是[7天用Go从零实现分布式缓存GeeCache](https://geektutu.com/post/geecache.html)的第三篇。
- 介绍如何使用 Go 语言标准库 `http` 搭建 HTTP Server
- 并实现 main 函数启动 HTTP Server 测试 API,**代码约60行**
## 1 http 标准库
Go 语言提供了 `http` 标准库,可以非常方便地搭建 HTTP 服务端和客户端。比如我们可以实现一个服务端,无论接收到什么请求,都返回字符串 "Hello World!"
```go
package main
import (
"log"
"net/http"
)
type server int
func (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.Path)
w.Write([]byte("Hello World!"))
}
func main() {
var s server
http.ListenAndServe("localhost:9999", &s)
}
```
- 创建任意类型 server,并实现 `ServeHTTP` 方法。
- 调用 `http.ListenAndServe` 在 9999 端口启动 http 服务,处理请求的对象为 `s server`。
接下来我们执行 `go run .` 启动服务,借助 curl 来测试效果:
```bash
$ curl http://localhost:9999
Hello World!
$ curl http://localhost:9999/abc
Hello World!
```
Go 程序日志输出
```bash
2020/02/11 22:56:32 /
2020/02/11 22:56:34 /abc
```
> `http.ListenAndServe` 接收 2 个参数,第一个参数是服务启动的地址,第二个参数是 Handler,任何实现了 `ServeHTTP` 方法的对象都可以作为 HTTP 的 Handler。
在标准库中,http.Handler 接口的定义如下:
```go
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
```
## 2 GeeCache HTTP 服务端
分布式缓存需要实现节点间通信,建立基于 HTTP 的通信机制是比较常见和简单的做法。如果一个节点启动了 HTTP 服务,那么这个节点就可以被其他节点访问。今天我们就为单机节点搭建 HTTP Server。
不与其他部分耦合,我们将这部分代码放在新的 `http.go` 文件中,当前的代码结构如下:
```bash
geecache/
|--lru/
|--lru.go // lru 缓存淘汰策略
|--byteview.go // 缓存值的抽象与封装
|--cache.go // 并发控制
|--geecache.go // 负责与外部交互,控制缓存存储和获取的主流程
|--http.go // 提供被其他节点访问的能力(基于http)
```
首先我们创建一个结构体 `HTTPPool`,作为承载节点间 HTTP 通信的核心数据结构(包括服务端和客户端,今天只实现服务端)。
[day3-http-server/geecache/http.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day3-http-server/geecache)
```go
package geecache
import (
"fmt"
"log"
"net/http"
"strings"
)
const defaultBasePath = "/_geecache/"
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
}
// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
return &HTTPPool{
self: self,
basePath: defaultBasePath,
}
}
```
- `HTTPPool` 只有 2 个参数,一个是 self,用来记录自己的地址,包括主机名/IP 和端口。
- 另一个是 basePath,作为节点间通讯地址的前缀,默认是 `/_geecache/`,那么 http://example.com/_geecache/ 开头的请求,就用于节点间的访问。因为一个主机上还可能承载其他的服务,加一段 Path 是一个好习惯。比如,大部分网站的 API 接口,一般以 `/api` 作为前缀。
接下来,实现最为核心的 `ServeHTTP` 方法。
```go
// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, p.basePath) {
panic("HTTPPool serving unexpected path: " + r.URL.Path)
}
p.Log("%s %s", r.Method, r.URL.Path)
// /<basepath>/<groupname>/<key> required
parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
if len(parts) != 2 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
groupName := parts[0]
key := parts[1]
group := GetGroup(groupName)
if group == nil {
http.Error(w, "no such group: "+groupName, http.StatusNotFound)
return
}
view, err := group.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}
```
- ServeHTTP 的实现逻辑是比较简单的,首先判断访问路径的前缀是否是 `basePath`,不是返回错误。
- 我们约定访问路径格式为 `/<basepath>/<groupname>/<key>`,通过 groupname 得到 group 实例,再使用 `group.Get(key)` 获取缓存数据。
- 最终使用 `w.Write()` 将缓存值作为 httpResponse 的 body 返回。
到这里,HTTP 服务端已经完整地实现了。接下来,我们将在单机上启动 HTTP 服务,使用 curl 进行测试。
## 3 测试
实现 main 函数,实例化 group,并启动 HTTP 服务。
[day3-http-server/main.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day3-http-server)
```go
package main
import (
"fmt"
"geecache"
"log"
"net/http"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func main() {
geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
addr := "localhost:9999"
peers := geecache.NewHTTPPool(addr)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr, peers))
}
```
- 同样地,我们使用 map 模拟了数据源 db。
- 创建一个名为 scores 的 Group,若缓存为空,回调函数会从 db 中获取数据并返回。
- 使用 http.ListenAndServe 在 9999 端口启动了 HTTP 服务。
> 需要注意的点:
> main.go 和 geecache/ 在同级目录,但 go modules 不再支持 import <相对路径>,相对路径需要在 go.mod 中声明:
> require geecache v0.0.0
> replace geecache => ./geecache
接下来,运行 main 函数,使用 curl 做一些简单测试:
```bash
$ curl http://localhost:9999/_geecache/scores/Tom
630
$ curl http://localhost:9999/_geecache/scores/kkk
kkk not exist
```
GeeCache 的日志输出如下:
```bash
2020/02/11 23:28:39 geecache is running at localhost:9999
2020/02/11 23:29:08 [Server localhost:9999] GET /_geecache/scores/Tom
2020/02/11 23:29:08 [SlowDB] search key Tom
2020/02/11 23:29:16 [Server localhost:9999] GET /_geecache/scores/kkk
2020/02/11 23:29:16 [SlowDB] search key kkk
```
节点间的相互通信不仅需要 HTTP 服务端,还需要 HTTP 客户端,这就是我们下一步需要做的事情。
## 附 推荐阅读
- [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
- [Go http.Handler 基础](https://geektutu.com/post/gee-day1.html)
- [http 官方文档 - golang.org](https://golang.org/pkg/http)
================================================
FILE: gee-cache/doc/geecache-day4.md
================================================
---
title: 动手写分布式缓存 - GeeCache第四天 一致性哈希(hash)
date: 2020-02-16 20:00:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了一致性哈希(consistent hashing)的原理、实现以及相关测试用例,一致性哈希为什么能避免缓存雪崩,虚拟节点为什么能解决数据倾斜的问题。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- 一致性hash
- consistent hash
image: post/geecache-day4/hash_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day4 一致性哈希
---

本文是[7天用Go从零实现分布式缓存GeeCache](https://geektutu.com/post/geecache.html)的第四篇。
- 一致性哈希(consistent hashing)的原理以及为什么要使用一致性哈希。
- 实现一致性哈希代码,添加相应的测试用例,**代码约60行**
## 1 为什么使用一致性哈希
今天我们要实现的是一致性哈希算法,一致性哈希算法是 GeeCache 从单节点走向分布式节点的一个重要的环节。那你可能要问了,
> 童鞋,一致性哈希算法是啥?为什么要使用一致性哈希算法?这和分布式有什么关系?
### 1.1 我该访问谁?
对于分布式缓存来说,当一个节点接收到请求,如果该节点并没有存储缓存值,那么它面临的难题是,从谁那获取数据?自己,还是节点1, 2, 3, 4... 。假设包括自己在内一共有 10 个节点,当一个节点接收到请求时,随机选择一个节点,由该节点从数据源获取数据。
假设第一次随机选取了节点 1 ,节点 1 从数据源获取到数据的同时缓存该数据;那第二次,只有 1/10 的可能性再次选择节点 1, 有 9/10 的概率选择了其他节点,如果选择了其他节点,就意味着需要再一次从数据源获取数据,一般来说,这个操作是很耗时的。这样做,一是缓存效率低,二是各个节点上存储着相同的数据,浪费了大量的存储空间。
那有什么办法,对于给定的 key,每一次都选择同一个节点呢?使用 hash 算法也能够做到这一点。那把 key 的每一个字符的 ASCII 码加起来,再除以 10 取余数可以吗?当然可以,这可以认为是自定义的 hash 算法。

从上面的图可以看到,任意一个节点任意时刻请求查找键 `Tom` 对应的值,都会分配给节点 2,有效地解决了上述的问题。
### 1.2 节点数量变化了怎么办?
简单求取 Hash 值解决了缓存性能的问题,但是没有考虑节点数量变化的场景。假设,移除了其中一台节点,只剩下 9 个,那么之前 `hash(key) % 10` 变成了 `hash(key) % 9`,也就意味着几乎缓存值对应的节点都发生了改变。即几乎所有的缓存值都失效了。节点在接收到对应的请求时,均需要重新去数据源获取数据,容易引起 `缓存雪崩`。
> 缓存雪崩:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。常因为缓存服务器宕机,或缓存设置了相同的过期时间引起。
那如何解决这个问题呢?一致性哈希算法可以。
## 2 算法原理
### 2.1 步骤
一致性哈希算法将 key 映射到 2^32 的空间中,将这个数字首尾相连,形成一个环。
- 计算节点/机器(通常使用节点的名称、编号和 IP 地址)的哈希值,放置在环上。
- 计算 key 的哈希值,放置在环上,顺时针寻找到的第一个节点,就是应选取的节点/机器。

环上有 peer2,peer4,peer6 三个节点,`key11`,`key2`,`key27` 均映射到 peer2,`key23` 映射到 peer4。此时,如果新增节点/机器 peer8,假设它新增位置如图所示,那么只有 `key27` 从 peer2 调整到 peer8,其余的映射均没有发生改变。
也就是说,一致性哈希算法,在新增/删除节点时,只需要重新定位该节点附近的一小部分数据,而不需要重新定位所有的节点,这就解决了上述的问题。
### 2.2 数据倾斜问题
如果服务器的节点过少,容易引起 key 的倾斜。例如上面例子中的 peer2,peer4,peer6 分布在环的上半部分,下半部分是空的。那么映射到环下半部分的 key 都会被分配给 peer2,key 过度向 peer2 倾斜,缓存节点间负载不均。
为了解决这个问题,引入了虚拟节点的概念,一个真实节点对应多个虚拟节点。
假设 1 个真实节点对应 3 个虚拟节点,那么 peer1 对应的虚拟节点是 peer1-1、 peer1-2、 peer1-3(通常以添加编号的方式实现),其余节点也以相同的方式操作。
- 第一步,计算虚拟节点的 Hash 值,放置在环上。
- 第二步,计算 key 的 Hash 值,在环上顺时针寻找到应选取的虚拟节点,例如是 peer2-1,那么就对应真实节点 peer2。
虚拟节点扩充了节点的数量,解决了节点较少的情况下数据容易倾斜的问题。而且代价非常小,只需要增加一个字典(map)维护真实节点与虚拟节点的映射关系即可。
## 3 Go语言实现
我们在 geecache 目录下新建 package `consistenthash`,用来实现一致性哈希算法。
[day4-consistent-hash/geecache/consistenthash/consistenthash.go](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day4-consistent-hash/geecache/consistenthash)
```go
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
// Hash maps bytes to uint32
type Hash func(data []byte) uint32
// Map constains all hashed keys
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
// New creates a Map instance
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
```
- 定义了函数类型 `Hash`,采取依赖注入的方式,允许用于替换成自定义的 Hash 函数,也方便测试时替换,默认为 `crc32.ChecksumIEEE` 算法。
- `Map` 是一致性哈希算法的主数据结构,包含 4 个成员变量:Hash 函数 `hash`;虚拟节点倍数 `replicas`;哈希环 `keys`;虚拟节点与真实节点的映射表 `hashMap`,键是虚拟节点的哈希值,值是真实节点的名称。
- 构造函数 `New()` 允许自定义虚拟节点倍数和 Hash 函数。
接下来,实现添加真实节点/机器的 `Add()` 方法。
```go
// Add adds some keys to the hash.
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
```
- `Add` 函数允许传入 0 或 多个真实节点的名称。
- 对每一个真实节点 `key`,对应创建 `m.replicas` 个虚拟节点,虚拟节点的名称是:`strconv.Itoa(i) + key`,即通过添加编号的方式区分不同虚拟节点。
- 使用 `m.hash()` 计算虚拟节点的哈希值,使用 `append(m.keys, hash)` 添加到环上。
- 在 `hashMap` 中增加虚拟节点和真实节点的映射关系。
- 最后一步,环上的哈希值排序。
最后一步,实现选择节点的 `Get()` 方法。
```go
// Get gets the closest item in the hash to the provided key.
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
```
- 选择节点就非常简单了,第一步,计算 key 的哈希值。
- 第二步,顺时针找到第一个匹配的虚拟节点的下标 `idx`,从 m.keys 中获取到对应的哈希值。如果 `idx == len(m.keys)`,说明应选择 `m.keys[0]`,因为 `m.keys` 是一个环状结构,所以用取余数的方式来处理这种情况。
- 第三步,通过 `hashMap` 映射得到真实的节点。
至此,整个一致性哈希算法就实现完成了。
## 4 测试
最后呢,需要测试用例来验证我们的实现是否有问题。
[day4-consistent-hash/geecache/consistenthash/consistenthash_test.go](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day4-consistent-hash/geecache/consistenthash)
```go
package consistenthash
import (
"strconv"
"testing"
)
func TestHashing(t *testing.T) {
hash := New(3, func(key []byte) uint32 {
i, _ := strconv.Atoi(string(key))
return uint32(i)
})
// Given the above hash function, this will give replicas with "hashes":
// 2, 4, 6, 12, 14, 16, 22, 24, 26
hash.Add("6", "4", "2")
testCases := map[string]string{
"2": "2",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
// Adds 8, 18, 28
hash.Add("8")
// 27 should now map to 8.
testCases["27"] = "8"
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}
```
如果要进行测试,那么我们需要明确地知道每一个传入的 key 的哈希值,那使用默认的 `crc32.ChecksumIEEE` 算法显然达不到目的。所以在这里使用了自定义的 Hash 算法。自定义的 Hash 算法只处理数字,传入字符串表示的数字,返回对应的数字即可。
- 一开始,有 2/4/6 三个真实节点,对应的虚拟节点的哈希值是 02/12/22、04/14/24、06/16/26。
- 那么用例 2/11/23/27 选择的虚拟节点分别是 02/12/24/02,也就是真实节点 2/2/4/2。
- 添加一个真实节点 8,对应虚拟节点的哈希值是 08/18/28,此时,用例 27 对应的虚拟节点从 `02` 变更为 `28`,即真实节点 8。
## 附 推荐阅读
- [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
================================================
FILE: gee-cache/doc/geecache-day5.md
================================================
---
title: 动手写分布式缓存 - GeeCache第五天 分布式节点
date: 2020-02-16 21:30:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了为 GeeCache 添加了注册节点与选择节点的功能,并实现了 HTTP 客户端,与远程节点的服务端通信。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- HTTP客户端
- 分布式节点
image: post/geecache-day5/dist_nodes_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day5 分布式节点
---

本文是[7天用Go从零实现分布式缓存GeeCache](https://geektutu.com/post/geecache.html)的第五篇。
- 注册节点(Register Peers),借助一致性哈希算法选择节点。
- 实现 HTTP 客户端,与远程节点的服务端通信,**代码约90行**
## 1 流程回顾
```bash
是
接收 key --> 检查是否被缓存 -----> 返回缓存值 ⑴
| 否 是
|-----> 是否应当从远程节点获取 -----> 与远程节点交互 --> 返回缓存值 ⑵
| 否
|-----> 调用`回调函数`,获取值并添加到缓存 --> 返回缓存值 ⑶
```
我们在[GeeCache 第二天](https://geektutu.com/post/geecache-day2.html) 中描述了 geecache 的流程。在这之前已经实现了流程 ⑴ 和 ⑶,今天实现流程 ⑵,从远程节点获取缓存值。
我们进一步细化流程 ⑵:
```bash
使用一致性哈希选择节点 是 是
|-----> 是否是远程节点 -----> HTTP 客户端访问远程节点 --> 成功?-----> 服务端返回返回值
| 否 ↓ 否
|----------------------------> 回退到本地节点处理。
```
## 2 抽象 PeerPicker
[day5-multi-nodes/geecache/peers.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day5-multi-nodes/geecache)
```go
package geecache
// PeerPicker is the interface that must be implemented to locate
// the peer that owns a specific key.
type PeerPicker interface {
PickPeer(key string) (peer PeerGetter, ok bool)
}
// PeerGetter is the interface that must be implemented by a peer.
type PeerGetter interface {
Get(group string, key string) ([]byte, error)
}
```
- 在这里,抽象出 2 个接口,PeerPicker 的 `PickPeer()` 方法用于根据传入的 key 选择相应节点 PeerGetter。
- 接口 PeerGetter 的 `Get()` 方法用于从对应 group 查找缓存值。PeerGetter 就对应于上述流程中的 HTTP 客户端。
## 3 节点选择与 HTTP 客户端
在 [GeeCache 第三天](https://geektutu.com/post/geecache-day3.html) 中我们为 `HTTPPool` 实现了服务端功能,通信不仅需要服务端还需要客户端,因此,我们接下来要为 `HTTPPool` 实现客户端的功能。
首先创建具体的 HTTP 客户端类 `httpGetter`,实现 PeerGetter 接口。
[day5-multi-nodes/geecache/http.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day5-multi-nodes/geecache)
```go
type httpGetter struct {
baseURL string
}
func (h *httpGetter) Get(group string, key string) ([]byte, error) {
u := fmt.Sprintf(
"%v%v/%v",
h.baseURL,
url.QueryEscape(group),
url.QueryEscape(key),
)
res, err := http.Get(u)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned: %v", res.Status)
}
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %v", err)
}
return bytes, nil
}
var _ PeerGetter = (*httpGetter)(nil)
```
- baseURL 表示将要访问的远程节点的地址,例如 `http://example.com/_geecache/`。
- 使用 `http.Get()` 方式获取返回值,并转换为 `[]bytes` 类型。
第二步,为 HTTPPool 添加节点选择的功能。
```go
const (
defaultBasePath = "/_geecache/"
defaultReplicas = 50
)
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
// this peer's base URL, e.g. "https://example.net:8000"
self string
basePath string
mu sync.Mutex // guards peers and httpGetters
peers *consistenthash.Map
httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
}
```
- 新增成员变量 `peers`,类型是一致性哈希算法的 `Map`,用来根据具体的 key 选择节点。
- 新增成员变量 `httpGetters`,映射远程节点与对应的 httpGetter。每一个远程节点对应一个 httpGetter,因为 httpGetter 与远程节点的地址 `baseURL` 有关。
第三步,实现 PeerPicker 接口。
```go
// Set updates the pool's list of peers.
func (p *HTTPPool) Set(peers ...string) {
p.mu.Lock()
defer p.mu.Unlock()
p.peers = consistenthash.New(defaultReplicas, nil)
p.peers.Add(peers...)
p.httpGetters = make(map[string]*httpGetter, len(peers))
for _, peer := range peers {
p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
}
}
// PickPeer picks a peer according to key
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
p.mu.Lock()
defer p.mu.Unlock()
if peer := p.peers.Get(key); peer != "" && peer != p.self {
p.Log("Pick peer %s", peer)
return p.httpGetters[peer], true
}
return nil, false
}
var _ PeerPicker = (*HTTPPool)(nil)
```
- `Set()` 方法实例化了一致性哈希算法,并且添加了传入的节点。
- 并为每一个节点创建了一个 HTTP 客户端 `httpGetter`。
- `PickerPeer()` 包装了一致性哈希算法的 `Get()` 方法,根据具体的 key,选择节点,返回节点对应的 HTTP 客户端。
至此,HTTPPool 既具备了提供 HTTP 服务的能力,也具备了根据具体的 key,创建 HTTP 客户端从远程节点获取缓存值的能力。
## 4 实现主流程
最后,我们需要将上述新增的功能集成在主流程(geecache.go)中。
[day5-multi-nodes/geecache/geecache.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day5-multi-nodes/geecache)
```go
// A Group is a cache namespace and associated data loaded spread over
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
}
// RegisterPeers registers a PeerPicker for choosing remote peer
func (g *Group) RegisterPeers(peers PeerPicker) {
if g.peers != nil {
panic("RegisterPeerPicker called more than once")
}
g.peers = peers
}
func (g *Group) load(key string) (value ByteView, err error) {
if g.peers != nil {
if peer, ok := g.peers.PickPeer(key); ok {
if value, err = g.getFromPeer(peer, key); err == nil {
return value, nil
}
log.Println("[GeeCache] Failed to get from peer", err)
}
}
return g.getLocally(key)
}
func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
bytes, err := peer.Get(g.name, key)
if err != nil {
return ByteView{}, err
}
return ByteView{b: bytes}, nil
}
```
- 新增 `RegisterPeers()` 方法,将 实现了 PeerPicker 接口的 HTTPPool 注入到 Group 中。
- 新增 `getFromPeer()` 方法,使用实现了 PeerGetter 接口的 httpGetter 从访问远程节点,获取缓存值。
- 修改 load 方法,使用 `PickPeer()` 方法选择节点,若非本机节点,则调用 `getFromPeer()` 从远程获取。若是本机节点或失败,则回退到 `getLocally()`。
## 5 main 函数测试。
[day5-multi-nodes/main.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day5-multi-nodes)
```go
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
func createGroup() *geecache.Group {
return geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))
}
func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
peers := geecache.NewHTTPPool(addr)
peers.Set(addrs...)
gee.RegisterPeers(peers)
log.Println("geecache is running at", addr)
log.Fatal(http.ListenAndServe(addr[7:], peers))
}
func startAPIServer(apiAddr string, gee *geecache.Group) {
http.Handle("/api", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
view, err := gee.Get(key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(view.ByteSlice())
}))
log.Println("fontend server is running at", apiAddr)
log.Fatal(http.ListenAndServe(apiAddr[7:], nil))
}
func main() {
var port int
var api bool
flag.IntVar(&port, "port", 8001, "Geecache server port")
flag.BoolVar(&api, "api", false, "Start a api server?")
flag.Parse()
apiAddr := "http://localhost:9999"
addrMap := map[int]string{
8001: "http://localhost:8001",
8002: "http://localhost:8002",
8003: "http://localhost:8003",
}
var addrs []string
for _, v := range addrMap {
addrs = append(addrs, v)
}
gee := createGroup()
if api {
go startAPIServer(apiAddr, gee)
}
startCacheServer(addrMap[port], []string(addrs), gee)
}
```
main 函数的代码比较多,但是逻辑是非常简单的。
- `startCacheServer()` 用来启动缓存服务器:创建 HTTPPool,添加节点信息,注册到 gee 中,启动 HTTP 服务(共3个端口,8001/8002/8003),用户不感知。
- `startAPIServer()` 用来启动一个 API 服务(端口 9999),与用户进行交互,用户感知。
- `main()` 函数需要命令行传入 `port` 和 `api` 2 个参数,用来在指定端口启动 HTTP 服务。
为了方便,我们将启动的命令封装为一个 `shell` 脚本:
```bash
#!/bin/bash
trap "rm server;kill 0" EXIT
go build -o server
./server -port=8001 &
./server -port=8002 &
./server -port=8003 -api=1 &
sleep 2
echo ">>> start test"
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
wait
```
- `trap` 命令用于在 shell 脚本退出时,删掉临时文件,结束子进程。
```bash
$ ./run.sh
2020/02/16 21:17:43 geecache is running at http://localhost:8001
2020/02/16 21:17:43 geecache is running at http://localhost:8002
2020/02/16 21:17:43 geecache is running at http://localhost:8003
2020/02/16 21:17:43 fontend server is running at http://localhost:9999
>>> start test
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
...
630630630
```
此时,我们可以打开一个新的 shell,进行测试:
```bash
$ curl "http://localhost:9999/api?key=Tom"
630
$ curl "http://localhost:9999/api?key=kkk"
kkk not exist
```
测试的时候,我们并发了 3 个请求 `?key=Tom`,从日志中可以看到,三次均选择了节点 `8001`,这是一致性哈希算法的功劳。但是有一个问题在于,同时向 `8001` 发起了 3 次请求。试想,假如有 10 万个在并发请求该数据呢?那就会向 `8001` 同时发起 10 万次请求,如果 `8001` 又同时向数据库发起 10 万次查询请求,很容易导致缓存被击穿。
三次请求的结果是一致的,对于相同的 key,能不能只向 `8001` 发起一次请求?这个问题下一次解决。
## 附 推荐阅读
- [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
================================================
FILE: gee-cache/doc/geecache-day6.md
================================================
---
title: 动手写分布式缓存 - GeeCache第六天 防止缓存击穿
date: 2020-02-16 23:00:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了缓存雪崩、缓存击穿与缓存穿透的概念,使用 singleflight 防止缓存击穿,实现与测试。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- HTTP客户端
- 分布式节点
image: post/geecache-day6/singleflight_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day6 防止缓存击穿
---

本文是[7天用Go从零实现分布式缓存GeeCache](https://geektutu.com/post/geecache.html)的第六篇。
- 缓存雪崩、缓存击穿与缓存穿透的概念简介。
- 使用 singleflight 防止缓存击穿,实现与测试。**代码约70行**
## 1 缓存雪崩、缓存击穿与缓存穿透
[GeeCache 第五天](https://geektutu.com/post/geecache-day5.html) 提到了缓存雪崩和缓存击穿,在这里做下总结:
> **缓存雪崩**:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。
> **缓存击穿**:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
> **缓存穿透**:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。
## 2 singleflight 的实现
还记得 [GeeCache 第五天](https://geektutu.com/post/geecache-day5.html) 最后的测试结果吗?
```bash
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
```
我们并发了 N 个请求 `?key=Tom`,8003 节点向 8001 同时发起了 N 次请求。假设对数据库的访问没有做任何限制的,很可能向数据库也发起 N 次请求,容易导致缓存击穿和穿透。即使对数据库做了防护,HTTP 请求是非常耗费资源的操作,针对相同的 key,8003 节点向 8001 发起三次请求也是没有必要的。那这种情况下,我们如何做到只向远端节点发起一次请求呢?
geecache 实现了一个名为 singleflight 的 package 来解决这个问题。
[day6-single-flight/geecache/singleflight/singleflight.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day6-single-flight/geecache/singleflight)
首先创建 `call` 和 `Group` 类型。
```go
package singleflight
import "sync"
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
type Group struct {
mu sync.Mutex // protects m
m map[string]*call
}
```
- `call` 代表正在进行中,或已经结束的请求。使用 `sync.WaitGroup` 锁避免重入。
- `Group` 是 singleflight 的主数据结构,管理不同 key 的请求(call)。
实现 `Do` 方法
```go
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
```
- Do 方法,接收 2 个参数,第一个参数是 `key`,第二个参数是一个函数 `fn`。Do 的作用就是,针对相同的 key,无论 Do 被调用多少次,函数 `fn` 都只会被调用一次,等待 fn 调用结束了,返回返回值或错误。
`g.mu` 是保护 Group 的成员变量 `m` 不被并发读写而加上的锁。为了便于理解 `Do` 函数,我们将 `g.mu` 暂时去掉。并且把 `g.m` 延迟初始化的部分去掉,延迟初始化的目的很简单,提高内存使用效率。
剩下的逻辑就很清晰了:
```go
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
if c, ok := g.m[key]; ok {
c.wg.Wait() // 如果请求正在进行中,则等待
return c.val, c.err // 请求结束,返回结果
}
c := new(call)
c.wg.Add(1) // 发起请求前加锁
g.m[key] = c // 添加到 g.m,表明 key 已经有对应的请求在处理
c.val, c.err = fn() // 调用 fn,发起请求
c.wg.Done() // 请求结束
delete(g.m, key) // 更新 g.m
return c.val, c.err // 返回结果
}
```
并发协程之间不需要消息传递,非常适合 `sync.WaitGroup`。
- wg.Add(1) 锁加1。
- wg.Wait() 阻塞,直到锁被释放。
- wg.Done() 锁减1。
## 3 singleflight 的使用
[day6-single-flight/geecache/geecache.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day6-single-flight/geecache)
```go
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
// use singleflight.Group to make sure that
// each key is only fetched once
loader *singleflight.Group
}
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
// ...
g := &Group{
// ...
loader: &singleflight.Group{},
}
return g
}
func (g *Group) load(key string) (value ByteView, err error) {
// each key is only fetched once (either locally or remotely)
// regardless of the number of concurrent callers.
viewi, err := g.loader.Do(key, func() (interface{}, error) {
if g.peers != nil {
if peer, ok := g.peers.PickPeer(key); ok {
if value, err = g.getFromPeer(peer, key); err == nil {
return value, nil
}
log.Println("[GeeCache] Failed to get from peer", err)
}
}
return g.getLocally(key)
})
if err == nil {
return viewi.(ByteView), nil
}
return
}
```
- 修改 `geecache.go` 中的 `Group`,添加成员变量 loader,并更新构建函数 `NewGroup`。
- 修改 `load` 函数,将原来的 load 的逻辑,使用 `g.loader.Do` 包裹起来即可,这样确保了并发场景下针对相同的 key,`load` 过程只会调用一次。
## 4 测试
执行 `run.sh` 就可以看到效果了。
```bash
$ ./run.sh
2020/02/16 22:36:00 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 22:36:00 [Server http://localhost:8001] GET /_geecache/scores/Tom
2020/02/16 22:36:00 [SlowDB] search key Tom
630630630
```
可以看到,向 API 发起了三次并发请求,但8003 只向 8001 发起了一次请求,就搞定了。
如果并发度不够高,可能仍会看到向 8001 请求三次的场景。这种情况下三次请求是串行执行的,并没有触发 `singleflight` 的锁机制工作,可以加大并发数量再测试。即,将 `run.sh` 中的 `curl` 命令复制 N 次。
## 附 推荐
- [Go 语言简明教程#并发编程](https://geektutu.com/post/quick-golang.html#7-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-goroutine)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
================================================
FILE: gee-cache/doc/geecache-day7.md
================================================
---
title: 动手写分布式缓存 - GeeCache第七天 使用 Protobuf 通信
date: 2020-02-17 00:30:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了使用 protobuf(protocol buffer) 进行节点间通信,编码报文,提高效率
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现
- HTTP客户端
- 分布式节点
image: post/geecache-day7/protobuf_logo.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day7 使用 Protobuf 通信
---

本文是[7天用Go从零实现分布式缓存GeeCache](https://geektutu.com/post/geecache.html)的第七篇。
- 为什么要使用 protobuf?
- 使用 protobuf 进行节点间通信,编码报文,提高效率。**代码约50行**
## 1 为什么要使用 protobuf
> protobuf 即 Protocol Buffers,Google 开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。protobuf 以二进制方式存储,占用空间小。
protobuf 的安装和使用教程请移步 [Go Protobuf 简明教程](https://geektutu.com/post/quick-go-protobuf.html),这篇文章就不再赘述了。protobuf 广泛地应用于远程过程调用(RPC) 的二进制传输,使用 protobuf 的目的非常简单,为了获得更高的性能。传输前使用 protobuf 编码,接收方再进行解码,可以显著地降低二进制传输的大小。另外一方面,protobuf 可非常适合传输结构化数据,便于通信字段的扩展。
使用 protobuf 一般分为以下 2 步:
- 按照 protobuf 的语法,在 `.proto` 文件中定义数据结构,并使用 `protoc` 生成 Go 代码(`.proto` 文件是跨平台的,还可以生成 C、Java 等其他源码文件)。
- 在项目代码中引用生成的 Go 代码。
## 2 使用 protobuf 通信
新建 package `geecachepb`,定义 `geecachepb.proto`
[day7-proto-buf/geecache/geecachepb/geecachepb.proto - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day7-proto-buf/geecache/geecachepb)
```go
syntax = "proto3";
package geecachepb;
message Request {
string group = 1;
string key = 2;
}
message Response {
bytes value = 1;
}
service GroupCache {
rpc Get(Request) returns (Response);
}
```
- `Request` 包含 2 个字段, group 和 cache,这与我们之前定义的接口 `/_geecache/<group>/<name>` 所需的参数吻合。
- `Response` 包含 1 个字段,bytes,类型为 byte 数组,与之前吻合。
生成 `geecache.pb.go`
```bash
$ protoc --go_out=. *.proto
$ ls
geecachepb.pb.go geecachepb.proto
```
可以看到 `geecachepb.pb.go` 中有如下数据类型:
```go
type Request struct {
Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"`
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
...
}
type Response struct {
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
}
```
接下来,修改 `peers.go` 中的 `PeerGetter` 接口,参数使用 `geecachepb.pb.go` 中的数据类型。
[day7-proto-buf/geecache/peers.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day7-proto-buf/geecache)
```go
import pb "geecache/geecachepb"
type PeerGetter interface {
Get(in *pb.Request, out *pb.Response) error
}
```
最后,修改 `geecache.go` 和 `http.go` 中使用了 `PeerGetter` 接口的地方。
[day7-proto-buf/geecache/geecache.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day7-proto-buf/geecache)
```go
import (
// ...
pb "geecache/geecachepb"
)
func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
req := &pb.Request{
Group: g.name,
Key: key,
}
res := &pb.Response{}
err := peer.Get(req, res)
if err != nil {
return ByteView{}, err
}
return ByteView{b: res.Value}, nil
}
```
[day7-proto-buf/geecache/http.go - github](https://github.com/geektutu/7days-golang/tree/master/gee-cache/day7-proto-buf/geecache)
```go
import (
// ...
pb "geecache/geecachepb"
"github.com/golang/protobuf/proto"
)
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
// Write the value to the response body as a proto message.
body, err := proto.Marshal(&pb.Response{Value: view.ByteSlice()})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(body)
}
func (h *httpGetter) Get(in *pb.Request, out *pb.Response) error {
u := fmt.Sprintf(
"%v%v/%v",
h.baseURL,
url.QueryEscape(in.GetGroup()),
url.QueryEscape(in.GetKey()),
)
res, err := http.Get(u)
// ...
if err = proto.Unmarshal(bytes, out); err != nil {
return fmt.Errorf("decoding response body: %v", err)
}
return nil
}
```
- `ServeHTTP()` 中使用 `proto.Marshal()` 编码 HTTP 响应。
- `Get()` 中使用 `proto.Unmarshal()` 解码 HTTP 响应。
至此,我们已经将 HTTP 通信的中间载体替换成了 protobuf。运行 `run.sh` 即可以测试 GeeCache 能否正常工作。
## 总结
到这一篇为止,7 天用 Go 动手写/从零实现分布式缓存 GeeCache 这个系列就完成了。简单回顾下。第一天,为了解决资源限制的问题,实现了 LRU 缓存淘汰算法;第二天实现了单机并发,并给用户提供了自定义数据源的回调函数;第三天实现了 HTTP 服务端;第四天实现了一致性哈希算法,解决远程节点的挑选问题;第五天创建 HTTP 客户端,实现了多节点间的通信;第六天实现了 singleflight 解决缓存击穿的问题;第七天,使用 protobuf 库,优化了节点间通信的性能。如果看到这里,还没有动手写的话呢,赶紧动手写起来吧。一天差不多只需要实现 100 行代码呢。
## 附 推荐
- [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
- [Go Protobuf 简明教程](https://geektutu.com/post/quick-go-protobuf.html)
================================================
FILE: gee-cache/doc/geecache.md
================================================
---
title: 7天用Go从零实现分布式缓存GeeCache
date: 2020-02-08 01:00:00
description: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。功能包括单机/分布式缓存,LRU (Least Recently Used) 缓存策略,防止缓存击穿、一致性哈希(Consistent Hash),protobuf 通信等。
tags:
- Go
nav: 从零实现
categories:
- 分布式缓存 - GeeCache
keywords:
- Go语言
- 从零实现分布式缓存
- 动手写分布式缓存
image: post/geecache/geecache_sm.jpg
github: https://github.com/geektutu/7days-golang
book: 七天用Go从零实现系列
book_title: Day0 序言
---

## 1 谈谈分布式缓存
第一次请求时将一些耗时操作的结果暂存,以后遇到相同的请求,直接返回暂存的数据。我想这是大部分童鞋对于缓存的理解。在计算机系统中,缓存无处不在,比如我们访问一个网页,网页和引用的 JS/CSS 等静态文件,根据不同的策略,会缓存在浏览器本地或是 CDN 服务器,那在第二次访问的时候,就会觉得网页加载的速度快了不少;比如微博的点赞的数量,不可能每个人每次访问,都从数据库中查找所有点赞的记录再统计,数据库的操作是很耗时的,很难支持那么大的流量,所以一般点赞这类数据是缓存在 Redis 服务集群中的。
> 商业世界里,现金为王;架构世界里,缓存为王。
缓存中最简单的莫过于存储在内存中的键值对缓存了。说到键值对,很容易想到的是字典(dict)类型,Go 语言中称之为 map。那直接创建一个 map,每次有新数据就往 map 中插入不就好了,这不就是键值对缓存么?这样做有什么问题呢?
1)内存不够了怎么办?
那就随机删掉几条数据好了。随机删掉好呢?还是按照时间顺序好呢?或者是有没有其他更好的淘汰策略呢?不同数据的访问频率是不一样的,优先删除访问频率低的数据是不是更好呢?数据的访问频率可能随着时间变化,那优先删除最近最少访问的数据可能是一个更好的选择。我们需要实现一个合理的淘汰策略。
2)并发写入冲突了怎么办?
对缓存的访问,一般不可能是串行的。map 是没有并发保护的,应对并发的场景,修改操作(包括新增,更新和删除)需要加锁。
3)单机性能不够怎么办?
单台计算机的资源是有限的,计算、存储等都是有限的。随着业务量和访问量的增加,单台机器很容易遇到瓶颈。如果利用多台计算机的资源,并行处理提高性能就要缓存应用能够支持分布式,这称为水平扩展(scale horizontally)。与水平扩展相对应的是垂直扩展(scale vertically),即通过增加单个节点的计算、存储、带宽等,来提高系统的性能,硬件的成本和性能并非呈线性关系,大部分情况下,分布式系统是一个更优的选择。
4)...
## 2 关于 GeeCache
设计一个分布式缓存系统,需要考虑资源控制、淘汰策略、并发、分布式节点通信等各个方面的问题。而且,针对不同的应用场景,还需要在不同的特性之间权衡,例如,是否需要支持缓存更新?还是假定缓存在淘汰之前是不允许改变的。不同的权衡对应着不同的实现。
[groupcache](https://github.com/golang/groupcache) 是 Go 语言版的 memcached,目的是在某些特定场合替代 memcached。groupcache 的作者也是 memcached 的作者。无论是了解单机缓存还是分布式缓存,深入学习这个库的实现都是非常有意义的。
`GeeCache` 基本上模仿了 [groupcache](https://github.com/golang/groupcache) 的实现,为了将代码量限制在 500 行左右(groupcache 约 3000 行),裁剪了部分功能。但总体实现上,还是与 groupcache 非常接近的。支持特性有:
- 单机缓存和基于 HTTP 的分布式缓存
- 最近最少访问(Least Recently Used, LRU) 缓存策略
- 使用 Go 锁机制防止缓存击穿
- 使用一致性哈希选择节点,实现负载均衡
- 使用 protobuf 优化节点间二进制通信
- ...
`GeeCache` 分7天实现,每天完成的部分都是可以独立运行和测试的,就像搭积木一样,每天实现的特性组合在一起就是最终的分布式缓存系统。每天的代码在 100 行左右。
## 3 目录
- 第一天:[LRU 缓存淘汰策略](https://geektutu.com/post/geecache-day1.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day1-lru)
- 第二天:[单机并发缓存](https://geektutu.com/post/geecache-day2.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day2-single-node)
- 第三天:[HTTP 服务端](https://geektutu.com/post/geecache-day3.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day3-http-server)
- 第四天:[一致性哈希(Hash)](https://geektutu.com/post/geecache-day4.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day4-consistent-hash)
- 第五天:[分布式节点](https://geektutu.com/post/geecache-day5.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day5-multi-nodes)
- 第六天:[防止缓存击穿](https://geektutu.com/post/geecache-day6.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day6-single-flight)
- 第七天:[使用 Protobuf 通信](https://geektutu.com/post/geecache-day7.html) | [Code - Github](https://github.com/geektutu/7days-golang/blob/master/gee-cache/day7-proto-buf)
## 附 推荐阅读
- [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
- [Go Test 单元测试简明教程](https://geektutu.com/post/quick-go-test.html)
- [Go Protobuf 简明教程](https://geektutu.com/post/quick-go-protobuf.html)
================================================
FILE: gee-orm/day1-database-sql/cmd_test/main.go
================================================
package main
import (
"fmt"
"geeorm"
_ "github.com/mattn/go-sqlite3"
)
func main() {
engine, _ := geeorm.NewEngine("sqlite3", "gee.db")
defer engine.Close()
s := engine.NewSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec()
count, _ := result.RowsAffected()
fmt.Printf("Exec success, %d affected\n", count)
}
================================================
FILE: gee-orm/day1-database-sql/geeorm.go
================================================
package geeorm
import (
"database/sql"
"geeorm/log"
"geeorm/session"
)
// Engine is the main struct of geeorm, manages all db sessions and transactions.
type Engine struct {
db *sql.DB
}
// NewEngine create a instance of Engine
// connect database and ping it to test whether it's alive
func NewEngine(driver, source string) (e *Engine, err error) {
db, err := sql.Open(driver, source)
if err != nil {
log.Error(err)
return
}
// Send a ping to make sure the database connection is alive.
if err = db.Ping(); err != nil {
log.Error(err)
return
}
e = &Engine{db: db}
log.Info("Connect database success")
return
}
// Close database connection
func (engine *Engine) Close() {
if err := engine.db.Close(); err != nil {
log.Error("Failed to close database")
}
log.Info("Close database success")
}
// NewSession creates a new session for next operations
func (engine *Engine) NewSession() *session.Session {
return session.New(engine.db)
}
================================================
FILE: gee-orm/day1-database-sql/geeorm_test.go
================================================
package geeorm
import (
_ "github.com/mattn/go-sqlite3"
"testing"
)
func OpenDB(t *testing.T) *Engine {
t.Helper()
engine, err := NewEngine("sqlite3", "gee.db")
if err != nil {
t.Fatal("failed to connect", err)
}
return engine
}
func TestNewEngine(t *testing.T) {
engine := OpenDB(t)
defer engine.Close()
}
================================================
FILE: gee-orm/day1-database-sql/go.mod
================================================
module geeorm
go 1.13
require github.com/mattn/go-sqlite3 v2.0.3+incompatible
================================================
FILE: gee-orm/day1-database-sql/log/log.go
================================================
package log
import (
"io/ioutil"
"log"
"os"
"sync"
)
var (
errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile)
infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile)
loggers = []*log.Logger{errorLog, infoLog}
mu sync.Mutex
)
// log methods
var (
Error = errorLog.Println
Errorf = errorLog.Printf
Info = infoLog.Println
Infof = infoLog.Printf
)
// log levels
const (
InfoLevel = iota
ErrorLevel
Disabled
)
// SetLevel controls log level
func SetLevel(level int) {
mu.Lock()
defer mu.Unlock()
for _, logger := range loggers {
logger.SetOutput(os.Stdout)
}
if ErrorLevel < level {
errorLog.SetOutput(ioutil.Discard)
}
if InfoLevel < level {
infoLog.SetOutput(ioutil.Discard)
}
}
================================================
FILE: gee-orm/day1-database-sql/log/log_test.go
================================================
package log
import (
"os"
"testing"
)
func TestSetLevel(t *testing.T) {
SetLevel(ErrorLevel)
if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout {
t.Fatal("failed to set log level")
}
SetLevel(Disabled)
if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout {
t.Fatal("failed to set log level")
}
}
================================================
FILE: gee-orm/day1-database-sql/session/raw.go
================================================
package session
import (
"database/sql"
"geeorm/log"
"strings"
)
// Session keep a pointer to sql.DB and provides all execution of all
// kind of database operations.
type Session struct {
db *sql.DB
sql strings.Builder
sqlVars []interface{}
}
// New creates a instance of Session
func New(db *sql.DB) *Session {
return &Session{db: db}
}
// Clear initialize the state of a session
func (s *Session) Clear() {
s.sql.Reset()
s.sqlVars = nil
}
// DB returns *sql.DB
func (s *Session) DB() *sql.DB {
return s.db
}
// Exec raw sql with sqlVars
func (s *Session) Exec() (result sql.Result, err error) {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
if result, err = s.DB().Exec(s.sql.String(), s.sqlVars...); err != nil {
log.Error(err)
}
return
}
// QueryRow gets a record from db
func (s *Session) QueryRow() *sql.Row {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
return s.DB().QueryRow(s.sql.String(), s.sqlVars...)
}
// QueryRows gets a list of records from db
func (s *Session) QueryRows() (rows *sql.Rows, err error) {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
if rows, err = s.DB().Query(s.sql.String(), s.sqlVars...); err != nil {
log.Error(err)
}
return
}
// Raw appends sql and sqlVars
func (s *Session) Raw(sql string, values ...interface{}) *Session {
s.sql.WriteString(sql)
s.sql.WriteString(" ")
s.sqlVars = append(s.sqlVars, values...)
return s
}
================================================
FILE: gee-orm/day1-database-sql/session/raw_test.go
================================================
package session
import (
"database/sql"
"os"
"testing"
_ "github.com/mattn/go-sqlite3"
)
var TestDB *sql.DB
func TestMain(m *testing.M) {
TestDB, _ = sql.Open("sqlite3", "../gee.db")
code := m.Run()
_ = TestDB.Close()
os.Exit(code)
}
func NewSession() *Session {
return New(TestDB)
}
func TestSession_Exec(t *testing.T) {
s := NewSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec()
if count, err := result.RowsAffected(); err != nil || count != 2 {
t.Fatal("expect 2, but got", count)
}
}
func TestSession_QueryRows(t *testing.T) {
s := NewSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
row := s.Raw("SELECT count(*) FROM User").QueryRow()
var count int
if err := row.Scan(&count); err != nil || count != 0 {
t.Fatal("failed to query db", err)
}
}
================================================
FILE: gee-orm/day2-reflect-schema/dialect/dialect.go
================================================
package dialect
import "reflect"
var dialectsMap = map[string]Dialect{}
// Dialect is an interface contains methods that a dialect has to implement
type Dialect interface {
DataTypeOf(typ reflect.Value) string
TableExistSQL(tableName string) (string, []interface{})
}
// RegisterDialect register a dialect to the global variable
func RegisterDialect(name string, dialect Dialect) {
dialectsMap[name] = dialect
}
// Get the dialect from global variable if it exists
func GetDialect(name string) (dialect Dialect, ok bool) {
dialect, ok = dialectsMap[name]
return
}
================================================
FILE: gee-orm/day2-reflect-schema/dialect/sqlite3.go
================================================
package dialect
import (
"fmt"
"reflect"
"time"
)
type sqlite3 struct{}
var _ Dialect = (*sqlite3)(nil)
func init() {
RegisterDialect("sqlite3", &sqlite3{})
}
// Get Data Type for sqlite3 Dialect
func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
switch typ.Kind() {
case reflect.Bool:
return "bool"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
return "integer"
case reflect.Int64, reflect.Uint64:
return "bigint"
case reflect.Float32, reflect.Float64:
return "real"
case reflect.String:
return "text"
case reflect.Array, reflect.Slice:
return "blob"
case reflect.Struct:
if _, ok := typ.Interface().(time.Time); ok {
return "datetime"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind()))
}
// TableExistSQL returns SQL that judge whether the table exists in database
func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) {
args := []interface{}{tableName}
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
}
================================================
FILE: gee-orm/day2-reflect-schema/dialect/sqlite3_test.go
================================================
package dialect
import (
"reflect"
"testing"
)
func TestDataTypeOf(t *testing.T) {
dial := &sqlite3{}
cases := []struct {
Value interface{}
Type string
}{
{"Tom", "text"},
{123, "integer"},
{1.2, "real"},
{[]int{1, 2, 3}, "blob"},
}
for _, c := range cases {
if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type {
t.Fatalf("expect %s, but got %s", c.Type, typ)
}
}
}
================================================
FILE: gee-orm/day2-reflect-schema/geeorm.go
================================================
package geeorm
import (
"database/sql"
"geeorm/dialect"
"geeorm/log"
"geeorm/session"
)
// Engine is the main struct of geeorm, manages all db sessions and transactions.
type Engine struct {
db *sql.DB
dialect dialect.Dialect
}
// NewEngine create a instance of Engine
// connect database and ping it to test whether it's alive
func NewEngine(driver, source string) (e *Engine, err error) {
db, err := sql.Open(driver, source)
if err != nil {
log.Error(err)
return
}
// Send a ping to make sure the database connection is alive.
if err = db.Ping(); err != nil {
log.Error(err)
return
}
// make sure the specific dialect exists
dial, ok := dialect.GetDialect(driver)
if !ok {
log.Errorf("dialect %s Not Found", driver)
return
}
e = &Engine{db: db, dialect: dial}
log.Info("Connect database success")
return
}
// Close database connection
func (engine *Engine) Close() {
if err := engine.db.Close(); err != nil {
log.Error("Failed to close database")
}
log.Info("Close database success")
}
// NewSession creates a new session for next operations
func (engine *Engine) NewSession() *session.Session {
return session.New(engine.db, engine.dialect)
}
================================================
FILE: gee-orm/day2-reflect-schema/geeorm_test.go
================================================
package geeorm
import (
_ "github.com/mattn/go-sqlite3"
"testing"
)
func OpenDB(t *testing.T) *Engine {
t.Helper()
engine, err := NewEngine("sqlite3", "gee.db")
if err != nil {
t.Fatal("failed to connect", err)
}
return engine
}
func TestNewEngine(t *testing.T) {
engine := OpenDB(t)
defer engine.Close()
}
================================================
FILE: gee-orm/day2-reflect-schema/go.mod
================================================
module geeorm
go 1.13
require github.com/mattn/go-sqlite3 v2.0.3+incompatible
================================================
FILE: gee-orm/day2-reflect-schema/log/log.go
================================================
package log
import (
"io/ioutil"
"log"
"os"
"sync"
)
var (
errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile)
infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile)
loggers = []*log.Logger{errorLog, infoLog}
mu sync.Mutex
)
// log methods
var (
Error = errorLog.Println
Errorf = errorLog.Printf
Info = infoLog.Println
Infof = infoLog.Printf
)
// log levels
const (
InfoLevel = iota
ErrorLevel
Disabled
)
// SetLevel controls log level
func SetLevel(level int) {
mu.Lock()
defer mu.Unlock()
for _, logger := range loggers {
logger.SetOutput(os.Stdout)
}
if ErrorLevel < level {
errorLog.SetOutput(ioutil.Discard)
}
if InfoLevel < level {
infoLog.SetOutput(ioutil.Discard)
}
}
================================================
FILE: gee-orm/day2-reflect-schema/log/log_test.go
================================================
package log
import (
"os"
"testing"
)
func TestSetLevel(t *testing.T) {
SetLevel(ErrorLevel)
if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout {
t.Fatal("failed to set log level")
}
SetLevel(Disabled)
if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout {
t.Fatal("failed to set log level")
}
}
================================================
FILE: gee-orm/day2-reflect-schema/schema/schema.go
================================================
package schema
import (
"geeorm/dialect"
"go/ast"
"reflect"
)
// Field represents a column of database
type Field struct {
Name string
Type string
Tag string
}
// Schema represents a table of database
type Schema struct {
Model interface{}
Name string
Fields []*Field
FieldNames []string
fieldMap map[string]*Field
}
// GetField returns field by name
func (schema *Schema) GetField(name string) *Field {
return schema.fieldMap[name]
}
// Values return the values of dest's member variables
func (schema *Schema) RecordValues(dest interface{}) []interface{} {
destValue := reflect.Indirect(reflect.ValueOf(dest))
var fieldValues []interface{}
for _, field := range schema.Fields {
fieldValues = append(fieldValues, destValue.FieldByName(field.Name).Interface())
}
return fieldValues
}
type ITableName interface {
TableName() string
}
// Parse a struct to a Schema instance
func Parse(dest interface{}, d dialect.Dialect) *Schema {
modelType := reflect.Indirect(reflect.ValueOf(dest)).Type()
var tableName string
t, ok := dest.(ITableName)
if !ok {
tableName = modelType.Name()
} else {
tableName = t.TableName()
}
schema := &Schema{
Model: dest,
Name: tableName,
fieldMap: make(map[string]*Field),
}
for i := 0; i < modelType.NumField(); i++ {
p := modelType.Field(i)
if !p.Anonymous && ast.IsExported(p.Name) {
field := &Field{
Name: p.Name,
Type: d.DataTypeOf(reflect.Indirect(reflect.New(p.Type))),
}
if v, ok := p.Tag.Lookup("geeorm"); ok {
field.Tag = v
}
schema.Fields = append(schema.Fields, field)
schema.FieldNames = append(schema.FieldNames, p.Name)
schema.fieldMap[p.Name] = field
}
}
return schema
}
================================================
FILE: gee-orm/day2-reflect-schema/schema/schema_test.go
================================================
package schema
import (
"geeorm/dialect"
"testing"
)
type User struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
var TestDial, _ = dialect.GetDialect("sqlite3")
func TestParse(t *testing.T) {
schema := Parse(&User{}, TestDial)
if schema.Name != "User" || len(schema.Fields) != 2 {
t.Fatal("failed to parse User struct")
}
if schema.GetField("Name").Tag != "PRIMARY KEY" {
t.Fatal("failed to parse primary key")
}
}
func TestSchema_RecordValues(t *testing.T) {
schema := Parse(&User{}, TestDial)
values := schema.RecordValues(&User{"Tom", 18})
name := values[0].(string)
age := values[1].(int)
if name != "Tom" || age != 18 {
t.Fatal("failed to get values")
}
}
type UserTest struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
func (u *UserTest) TableName() string {
return "ns_user_test"
}
func TestSchema_TableName(t *testing.T) {
schema := Parse(&UserTest{}, TestDial)
if schema.Name != "ns_user_test" || len(schema.Fields) != 2 {
t.Fatal("failed to parse User struct")
}
}
================================================
FILE: gee-orm/day2-reflect-schema/session/raw.go
================================================
package session
import (
"database/sql"
"geeorm/dialect"
"geeorm/log"
"geeorm/schema"
"strings"
)
// Session keep a pointer to sql.DB and provides all execution of all
// kind of database operations.
type Session struct {
db *sql.DB
dialect dialect.Dialect
refTable *schema.Schema
sql strings.Builder
sqlVars []interface{}
}
// New creates a instance of Session
func New(db *sql.DB, dialect dialect.Dialect) *Session {
return &Session{
db: db,
dialect: dialect,
}
}
// Clear initialize the state of a session
func (s *Session) Clear() {
s.sql.Reset()
s.sqlVars = nil
}
// DB returns *sql.DB
func (s *Session) DB() *sql.DB {
return s.db
}
// Exec raw sql with sqlVars
func (s *Session) Exec() (result sql.Result, err error) {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
if result, err = s.DB().Exec(s.sql.String(), s.sqlVars...); err != nil {
log.Error(err)
}
return
}
// QueryRow gets a record from db
func (s *Session) QueryRow() *sql.Row {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
return s.DB().QueryRow(s.sql.String(), s.sqlVars...)
}
// QueryRows gets a list of records from db
func (s *Session) QueryRows() (rows *sql.Rows, err error) {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
if rows, err = s.DB().Query(s.sql.String(), s.sqlVars...); err != nil {
log.Error(err)
}
return
}
// Raw appends sql and sqlVars
func (s *Session) Raw(sql string, values ...interface{}) *Session {
s.sql.WriteString(sql)
s.sql.WriteString(" ")
s.sqlVars = append(s.sqlVars, values...)
return s
}
================================================
FILE: gee-orm/day2-reflect-schema/session/raw_test.go
================================================
package session
import (
"database/sql"
"os"
"testing"
"geeorm/dialect"
_ "github.com/mattn/go-sqlite3"
)
var (
TestDB *sql.DB
TestDial, _ = dialect.GetDialect("sqlite3")
)
func TestMain(m *testing.M) {
TestDB, _ = sql.Open("sqlite3", "../gee.db")
code := m.Run()
_ = TestDB.Close()
os.Exit(code)
}
func NewSession() *Session {
return New(TestDB, TestDial)
}
func TestSession_Exec(t *testing.T) {
s := NewSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec()
if count, err := result.RowsAffected(); err != nil || count != 2 {
t.Fatal("expect 2, but got", count)
}
}
func TestSession_QueryRows(t *testing.T) {
s := NewSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
row := s.Raw("SELECT count(*) FROM User").QueryRow()
var count int
if err := row.Scan(&count); err != nil || count != 0 {
t.Fatal("failed to query db", err)
}
}
================================================
FILE: gee-orm/day2-reflect-schema/session/table.go
================================================
package session
import (
"fmt"
"geeorm/log"
"reflect"
"strings"
"geeorm/schema"
)
// Model assigns refTable
func (s *Session) Model(value interface{}) *Session {
// nil or different model, update refTable
if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) {
s.refTable = schema.Parse(value, s.dialect)
}
return s
}
// RefTable returns a Schema instance that contains all parsed fields
func (s *Session) RefTable() *schema.Schema {
if s.refTable == nil {
log.Error("Model is not set")
}
return s.refTable
}
// CreateTable create a table in database with a model
func (s *Session) CreateTable() error {
table := s.RefTable()
var columns []string
for _, field := range table.Fields {
columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag))
}
desc := strings.Join(columns, ",")
_, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec()
return err
}
// DropTable drops a table with the name of model
func (s *Session) DropTable() error {
_, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec()
return err
}
// HasTable returns true of the table exists
func (s *Session) HasTable() bool {
sql, values := s.dialect.TableExistSQL(s.RefTable().Name)
row := s.Raw(sql, values...).QueryRow()
var tmp string
_ = row.Scan(&tmp)
return tmp == s.RefTable().Name
}
================================================
FILE: gee-orm/day2-reflect-schema/session/table_test.go
================================================
package session
import (
"testing"
)
type User struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
func TestSession_CreateTable(t *testing.T) {
s := NewSession().Model(&User{})
_ = s.DropTable()
_ = s.CreateTable()
if !s.HasTable() {
t.Fatal("Failed to create table User")
}
}
func TestSession_Model(t *testing.T) {
s := NewSession().Model(&User{})
table := s.RefTable()
s.Model(&Session{})
if table.Name != "User" || s.RefTable().Name != "Session" {
t.Fatal("Failed to change model")
}
}
================================================
FILE: gee-orm/day3-save-query/clause/clause.go
================================================
package clause
import (
"strings"
)
// Clause contains SQL conditions
type Clause struct {
sql map[Type]string
sqlVars map[Type][]interface{}
}
// Type is the type of Clause
type Type int
// Support types for Clause
const (
INSERT Type = iota
VALUES
SELECT
LIMIT
WHERE
ORDERBY
)
// Set adds a sub clause of specific type
func (c *Clause) Set(name Type, vars ...interface{}) {
if c.sql == nil {
c.sql = make(map[Type]string)
c.sqlVars = make(map[Type][]interface{})
}
sql, vars := generators[name](vars...)
c.sql[name] = sql
c.sqlVars[name] = vars
}
// Build generate the final SQL and SQLVars
func (c *Clause) Build(orders ...Type) (string, []interface{}) {
var sqls []string
var vars []interface{}
for _, order := range orders {
if sql, ok := c.sql[order]; ok {
sqls = append(sqls, sql)
vars = append(vars, c.sqlVars[order]...)
}
}
return strings.Join(sqls, " "), vars
}
================================================
FILE: gee-orm/day3-save-query/clause/clause_test.go
================================================
package clause
import (
"reflect"
"testing"
)
func TestClause_Set(t *testing.T) {
var clause Clause
clause.Set(INSERT, "User", []string{"Name", "Age"})
sql := clause.sql[INSERT]
vars := clause.sqlVars[INSERT]
t.Log(sql, vars)
if sql != "INSERT INTO User (Name,Age)" || len(vars) != 0 {
t.Fatal("failed to get clause")
}
}
func testSelect(t *testing.T) {
var clause Clause
clause.Set(LIMIT, 3)
clause.Set(SELECT, "User", []string{"*"})
clause.Set(WHERE, "Name = ?", "Tom")
clause.Set(ORDERBY, "Age ASC")
sql, vars := clause.Build(SELECT, WHERE, ORDERBY, LIMIT)
t.Log(sql, vars)
if sql != "SELECT * FROM User WHERE Name = ? ORDER BY Age ASC LIMIT ?" {
t.Fatal("failed to build SQL")
}
if !reflect.DeepEqual(vars, []interface{}{"Tom", 3}) {
t.Fatal("failed to build SQLVars")
}
}
func TestClause_Build(t *testing.T) {
t.Run("select", func(t *testing.T) {
testSelect(t)
})
}
================================================
FILE: gee-orm/day3-save-query/clause/generator.go
================================================
package clause
import (
"fmt"
"strings"
)
type generator func(values ...interface{}) (string, []interface{})
var generators map[Type]generator
func init() {
generators = make(map[Type]generator)
generators[INSERT] = _insert
generators[VALUES] = _values
generators[SELECT] = _select
generators[LIMIT] = _limit
generators[WHERE] = _where
generators[ORDERBY] = _orderBy
}
func genBindVars(num int) string {
var vars []string
for i := 0; i < num; i++ {
vars = append(vars, "?")
}
return strings.Join(vars, ", ")
}
func _insert(values ...interface{}) (string, []interface{}) {
// INSERT INTO $tableName ($fields)
tableName := values[0]
fields := strings.Join(values[1].([]string), ",")
return fmt.Sprintf("INSERT INTO %s (%v)", tableName, fields), []interface{}{}
}
func _values(values ...interface{}) (string, []interface{}) {
// VALUES ($v1), ($v2), ...
var bindStr string
var sql strings.Builder
var vars []interface{}
sql.WriteString("VALUES ")
for i, value := range values {
v := value.([]interface{})
if bindStr == "" {
bindStr = genBindVars(len(v))
}
sql.WriteString(fmt.Sprintf("(%v)", bindStr))
if i+1 != len(values) {
sql.WriteString(", ")
}
vars = append(vars,
gitextract_0lo96hoo/
├── .gitignore
├── LICENSE
├── README.md
├── demo-wasm/
│ ├── .gitignore
│ ├── callback/
│ │ ├── Makefile
│ │ ├── index.html
│ │ └── main.go
│ ├── hello-world/
│ │ ├── Makefile
│ │ ├── index.html
│ │ └── main.go
│ ├── manipulate-dom/
│ │ ├── Makefile
│ │ ├── index.html
│ │ └── main.go
│ └── register-functions/
│ ├── Makefile
│ ├── index.html
│ └── main.go
├── gee-bolt/
│ ├── day1-pages/
│ │ ├── go.mod
│ │ ├── meta.go
│ │ └── page.go
│ ├── day2-mmap/
│ │ ├── db.go
│ │ └── go.mod
│ └── day3-tree/
│ ├── go.mod
│ ├── meta.go
│ ├── node.go
│ └── page.go
├── gee-cache/
│ ├── day1-lru/
│ │ └── geecache/
│ │ ├── go.mod
│ │ └── lru/
│ │ ├── lru.go
│ │ └── lru_test.go
│ ├── day2-single-node/
│ │ └── geecache/
│ │ ├── byteview.go
│ │ ├── cache.go
│ │ ├── geecache.go
│ │ ├── geecache_test.go
│ │ ├── go.mod
│ │ └── lru/
│ │ ├── lru.go
│ │ └── lru_test.go
│ ├── day3-http-server/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ └── lru/
│ │ │ ├── lru.go
│ │ │ └── lru_test.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day4-consistent-hash/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ └── lru/
│ │ │ ├── lru.go
│ │ │ └── lru_test.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day5-multi-nodes/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ ├── lru/
│ │ │ │ ├── lru.go
│ │ │ │ └── lru_test.go
│ │ │ └── peers.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── run.sh
│ ├── day6-single-flight/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ ├── lru/
│ │ │ │ ├── lru.go
│ │ │ │ └── lru_test.go
│ │ │ ├── peers.go
│ │ │ └── singleflight/
│ │ │ ├── singleflight.go
│ │ │ └── singleflight_test.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── run.sh
│ ├── day7-proto-buf/
│ │ ├── geecache/
│ │ │ ├── byteview.go
│ │ │ ├── cache.go
│ │ │ ├── consistenthash/
│ │ │ │ ├── consistenthash.go
│ │ │ │ └── consistenthash_test.go
│ │ │ ├── geecache.go
│ │ │ ├── geecache_test.go
│ │ │ ├── geecachepb/
│ │ │ │ ├── geecachepb.pb.go
│ │ │ │ └── geecachepb.proto
│ │ │ ├── go.mod
│ │ │ ├── http.go
│ │ │ ├── lru/
│ │ │ │ ├── lru.go
│ │ │ │ └── lru_test.go
│ │ │ ├── peers.go
│ │ │ └── singleflight/
│ │ │ ├── singleflight.go
│ │ │ └── singleflight_test.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── run.sh
│ └── doc/
│ ├── geecache-day1.md
│ ├── geecache-day2.md
│ ├── geecache-day3.md
│ ├── geecache-day4.md
│ ├── geecache-day5.md
│ ├── geecache-day6.md
│ ├── geecache-day7.md
│ └── geecache.md
├── gee-orm/
│ ├── day1-database-sql/
│ │ ├── cmd_test/
│ │ │ └── main.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ └── raw_test.go
│ ├── day2-reflect-schema/
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day3-save-query/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day4-chain-operation/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day5-hooks/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── hooks.go
│ │ ├── hooks_test.go
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ └── table_test.go
│ ├── day6-transaction/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── hooks.go
│ │ ├── hooks_test.go
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ ├── table_test.go
│ │ └── transaction.go
│ ├── day7-migrate/
│ │ ├── clause/
│ │ │ ├── clause.go
│ │ │ ├── clause_test.go
│ │ │ └── generator.go
│ │ ├── dialect/
│ │ │ ├── dialect.go
│ │ │ ├── sqlite3.go
│ │ │ └── sqlite3_test.go
│ │ ├── geeorm.go
│ │ ├── geeorm_test.go
│ │ ├── go.mod
│ │ ├── log/
│ │ │ ├── log.go
│ │ │ └── log_test.go
│ │ ├── schema/
│ │ │ ├── schema.go
│ │ │ └── schema_test.go
│ │ └── session/
│ │ ├── hooks.go
│ │ ├── hooks_test.go
│ │ ├── raw.go
│ │ ├── raw_test.go
│ │ ├── record.go
│ │ ├── record_test.go
│ │ ├── table.go
│ │ ├── table_test.go
│ │ └── transaction.go
│ ├── doc/
│ │ ├── geeorm-day1.md
│ │ ├── geeorm-day2.md
│ │ ├── geeorm-day3.md
│ │ ├── geeorm-day4.md
│ │ ├── geeorm-day5.md
│ │ ├── geeorm-day6.md
│ │ ├── geeorm-day7.md
│ │ └── geeorm.md
│ └── run_test.sh
├── gee-rpc/
│ ├── day1-codec/
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ └── server.go
│ ├── day2-client/
│ │ ├── client.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ └── server.go
│ ├── day3-service/
│ │ ├── client.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── day4-timeout/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── day5-http-debug/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── debug.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── day6-load-balance/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── debug.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── server.go
│ │ ├── service.go
│ │ ├── service_test.go
│ │ └── xclient/
│ │ ├── discovery.go
│ │ └── xclient.go
│ ├── day7-registry/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── codec/
│ │ │ ├── codec.go
│ │ │ └── gob.go
│ │ ├── debug.go
│ │ ├── go.mod
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── registry/
│ │ │ └── registry.go
│ │ ├── server.go
│ │ ├── service.go
│ │ ├── service_test.go
│ │ └── xclient/
│ │ ├── discovery.go
│ │ ├── discovery_gee.go
│ │ └── xclient.go
│ └── doc/
│ ├── geerpc-day1.md
│ ├── geerpc-day2.md
│ ├── geerpc-day3.md
│ ├── geerpc-day4.md
│ ├── geerpc-day5.md
│ ├── geerpc-day6.md
│ ├── geerpc-day7.md
│ └── geerpc.md
├── gee-web/
│ ├── README.md
│ ├── day1-http-base/
│ │ ├── base1/
│ │ │ ├── go.mod
│ │ │ └── main.go
│ │ ├── base2/
│ │ │ ├── go.mod
│ │ │ └── main.go
│ │ └── base3/
│ │ ├── gee/
│ │ │ ├── gee.go
│ │ │ └── go.mod
│ │ ├── go.mod
│ │ └── main.go
│ ├── day2-context/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── go.mod
│ │ │ └── router.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day3-router/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── go.mod
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day4-group/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day5-middleware/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── logger.go
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ ├── day6-template/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── logger.go
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ ├── main.go
│ │ ├── static/
│ │ │ ├── css/
│ │ │ │ └── geektutu.css
│ │ │ └── file1.txt
│ │ └── templates/
│ │ ├── arr.tmpl
│ │ ├── css.tmpl
│ │ └── custom_func.tmpl
│ ├── day7-panic-recover/
│ │ ├── gee/
│ │ │ ├── context.go
│ │ │ ├── gee.go
│ │ │ ├── gee_test.go
│ │ │ ├── go.mod
│ │ │ ├── logger.go
│ │ │ ├── recovery.go
│ │ │ ├── router.go
│ │ │ ├── router_test.go
│ │ │ └── trie.go
│ │ ├── go.mod
│ │ └── main.go
│ └── doc/
│ ├── gee-day1.md
│ ├── gee-day2.md
│ ├── gee-day3.md
│ ├── gee-day4.md
│ ├── gee-day5.md
│ ├── gee-day6.md
│ ├── gee-day7.md
│ └── gee.md
└── questions/
└── 7days-golang-q1.md
SYMBOL INDEX (1839 symbols across 295 files)
FILE: demo-wasm/callback/main.go
function fib (line 9) | func fib(i int) int {
function fibFunc (line 16) | func fibFunc(this js.Value, args []js.Value) interface{} {
function main (line 28) | func main() {
FILE: demo-wasm/hello-world/main.go
function main (line 6) | func main() {
FILE: demo-wasm/manipulate-dom/main.go
function fib (line 8) | func fib(i int) int {
function fibFunc (line 22) | func fibFunc(this js.Value, args []js.Value) interface{} {
function main (line 30) | func main() {
FILE: demo-wasm/register-functions/main.go
function fib (line 6) | func fib(i int) int {
function fibFunc (line 13) | func fibFunc(this js.Value, args []js.Value) interface{} {
function main (line 17) | func main() {
FILE: gee-bolt/day1-pages/meta.go
constant magic (line 10) | magic uint32 = 0xED0CDAED
type meta (line 12) | type meta struct
method sum64 (line 19) | func (m *meta) sum64() uint64 {
method validate (line 25) | func (m *meta) validate() error {
FILE: gee-bolt/day1-pages/page.go
constant pageHeaderSize (line 9) | pageHeaderSize = unsafe.Sizeof(page{})
constant branchPageElementSize (line 10) | branchPageElementSize = unsafe.Sizeof(branchPageElement{})
constant leafPageElementSize (line 11) | leafPageElementSize = unsafe.Sizeof(leafPageElement{})
constant maxKeysPerPage (line 12) | maxKeysPerPage = 1024
constant branchPageFlag (line 15) | branchPageFlag uint16 = iota
constant leafPageFlag (line 16) | leafPageFlag
constant metaPageFlag (line 17) | metaPageFlag
constant freelistPageFlag (line 18) | freelistPageFlag
type page (line 21) | type page struct
method typ (line 40) | func (p *page) typ() string {
method meta (line 54) | func (p *page) meta() *meta {
method dataPtr (line 58) | func (p *page) dataPtr() unsafe.Pointer {
method leafPageElement (line 66) | func (p *page) leafPageElement(index uint16) *leafPageElement {
method leafPageElements (line 71) | func (p *page) leafPageElements() []leafPageElement {
method branchPageElement (line 78) | func (p *page) branchPageElement(index uint16) *branchPageElement {
method branchPageElements (line 83) | func (p *page) branchPageElements() []branchPageElement {
type leafPageElement (line 28) | type leafPageElement struct
type branchPageElement (line 34) | type branchPageElement struct
FILE: gee-bolt/day2-mmap/db.go
type DB (line 5) | type DB struct
method mmap (line 12) | func (db *DB) mmap(sz int) error {
constant maxMapSize (line 10) | maxMapSize = 1 << 31
function Open (line 16) | func Open(path string) {
FILE: gee-bolt/day3-tree/meta.go
constant magic (line 10) | magic uint32 = 0xED0CDAED
type meta (line 12) | type meta struct
method sum64 (line 19) | func (m *meta) sum64() uint64 {
method validate (line 25) | func (m *meta) validate() error {
FILE: gee-bolt/day3-tree/node.go
type kv (line 8) | type kv struct
type node (line 13) | type node struct
method root (line 21) | func (n *node) root() *node {
method index (line 28) | func (n *node) index(key []byte) (index int, exact bool) {
method put (line 36) | func (n *node) put(oldKey, newKey, value []byte) {
method del (line 47) | func (n *node) del(key []byte) {
FILE: gee-bolt/day3-tree/page.go
constant pageHeaderSize (line 9) | pageHeaderSize = unsafe.Sizeof(page{})
constant branchPageElementSize (line 10) | branchPageElementSize = unsafe.Sizeof(branchPageElement{})
constant leafPageElementSize (line 11) | leafPageElementSize = unsafe.Sizeof(leafPageElement{})
constant maxKeysPerPage (line 12) | maxKeysPerPage = 1024
constant branchPageFlag (line 15) | branchPageFlag uint16 = iota
constant leafPageFlag (line 16) | leafPageFlag
constant metaPageFlag (line 17) | metaPageFlag
constant freelistPageFlag (line 18) | freelistPageFlag
type page (line 21) | type page struct
method typ (line 40) | func (p *page) typ() string {
method meta (line 54) | func (p *page) meta() *meta {
method dataPtr (line 58) | func (p *page) dataPtr() unsafe.Pointer {
method leafPageElement (line 66) | func (p *page) leafPageElement(index uint16) *leafPageElement {
method leafPageElements (line 71) | func (p *page) leafPageElements() []leafPageElement {
method branchPageElement (line 78) | func (p *page) branchPageElement(index uint16) *branchPageElement {
method branchPageElements (line 83) | func (p *page) branchPageElements() []branchPageElement {
type leafPageElement (line 28) | type leafPageElement struct
type branchPageElement (line 34) | type branchPageElement struct
FILE: gee-cache/day1-lru/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day1-lru/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day2-single-node/geecache/byteview.go
type ByteView (line 4) | type ByteView struct
method Len (line 9) | func (v ByteView) Len() int {
method ByteSlice (line 14) | func (v ByteView) ByteSlice() []byte {
method String (line 19) | func (v ByteView) String() string {
function cloneBytes (line 23) | func cloneBytes(b []byte) []byte {
FILE: gee-cache/day2-single-node/geecache/cache.go
type cache (line 8) | type cache struct
method add (line 14) | func (c *cache) add(key string, value ByteView) {
method get (line 23) | func (c *cache) get(key string) (value ByteView, ok bool) {
FILE: gee-cache/day2-single-node/geecache/geecache.go
type Group (line 10) | type Group struct
method Get (line 60) | func (g *Group) Get(key string) (ByteView, error) {
method load (line 73) | func (g *Group) load(key string) (value ByteView, err error) {
method getLocally (line 77) | func (g *Group) getLocally(key string) (ByteView, error) {
method populateCache (line 88) | func (g *Group) populateCache(key string, value ByteView) {
type Getter (line 17) | type Getter interface
type GetterFunc (line 22) | type GetterFunc
method Get (line 25) | func (f GetterFunc) Get(key string) ([]byte, error) {
function NewGroup (line 35) | func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
function GetGroup (line 52) | func GetGroup(name string) *Group {
FILE: gee-cache/day2-single-node/geecache/geecache_test.go
function TestGetter (line 16) | func TestGetter(t *testing.T) {
function TestGet (line 27) | func TestGet(t *testing.T) {
function TestGetGroup (line 56) | func TestGetGroup(t *testing.T) {
FILE: gee-cache/day2-single-node/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day2-single-node/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day3-http-server/geecache/byteview.go
type ByteView (line 4) | type ByteView struct
method Len (line 9) | func (v ByteView) Len() int {
method ByteSlice (line 14) | func (v ByteView) ByteSlice() []byte {
method String (line 19) | func (v ByteView) String() string {
function cloneBytes (line 23) | func cloneBytes(b []byte) []byte {
FILE: gee-cache/day3-http-server/geecache/cache.go
type cache (line 8) | type cache struct
method add (line 14) | func (c *cache) add(key string, value ByteView) {
method get (line 23) | func (c *cache) get(key string) (value ByteView, ok bool) {
FILE: gee-cache/day3-http-server/geecache/geecache.go
type Group (line 10) | type Group struct
method Get (line 60) | func (g *Group) Get(key string) (ByteView, error) {
method load (line 73) | func (g *Group) load(key string) (value ByteView, err error) {
method getLocally (line 77) | func (g *Group) getLocally(key string) (ByteView, error) {
method populateCache (line 88) | func (g *Group) populateCache(key string, value ByteView) {
type Getter (line 17) | type Getter interface
type GetterFunc (line 22) | type GetterFunc
method Get (line 25) | func (f GetterFunc) Get(key string) ([]byte, error) {
function NewGroup (line 35) | func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
function GetGroup (line 52) | func GetGroup(name string) *Group {
FILE: gee-cache/day3-http-server/geecache/geecache_test.go
function TestGetter (line 16) | func TestGetter(t *testing.T) {
function TestGet (line 27) | func TestGet(t *testing.T) {
function TestGetGroup (line 56) | func TestGetGroup(t *testing.T) {
FILE: gee-cache/day3-http-server/geecache/http.go
constant defaultBasePath (line 10) | defaultBasePath = "/_geecache/"
type HTTPPool (line 13) | type HTTPPool struct
method Log (line 28) | func (p *HTTPPool) Log(format string, v ...interface{}) {
method ServeHTTP (line 33) | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
function NewHTTPPool (line 20) | func NewHTTPPool(self string) *HTTPPool {
FILE: gee-cache/day3-http-server/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day3-http-server/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day3-http-server/main.go
function main (line 24) | func main() {
FILE: gee-cache/day4-consistent-hash/geecache/byteview.go
type ByteView (line 4) | type ByteView struct
method Len (line 9) | func (v ByteView) Len() int {
method ByteSlice (line 14) | func (v ByteView) ByteSlice() []byte {
method String (line 19) | func (v ByteView) String() string {
function cloneBytes (line 23) | func cloneBytes(b []byte) []byte {
FILE: gee-cache/day4-consistent-hash/geecache/cache.go
type cache (line 8) | type cache struct
method add (line 14) | func (c *cache) add(key string, value ByteView) {
method get (line 23) | func (c *cache) get(key string) (value ByteView, ok bool) {
FILE: gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash.go
type Hash (line 10) | type Hash
type Map (line 13) | type Map struct
method Add (line 34) | func (m *Map) Add(keys ...string) {
method Get (line 46) | func (m *Map) Get(key string) string {
function New (line 21) | func New(replicas int, fn Hash) *Map {
FILE: gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash_test.go
function TestHashing (line 8) | func TestHashing(t *testing.T) {
FILE: gee-cache/day4-consistent-hash/geecache/geecache.go
type Group (line 10) | type Group struct
method Get (line 60) | func (g *Group) Get(key string) (ByteView, error) {
method load (line 73) | func (g *Group) load(key string) (value ByteView, err error) {
method getLocally (line 77) | func (g *Group) getLocally(key string) (ByteView, error) {
method populateCache (line 88) | func (g *Group) populateCache(key string, value ByteView) {
type Getter (line 17) | type Getter interface
type GetterFunc (line 22) | type GetterFunc
method Get (line 25) | func (f GetterFunc) Get(key string) ([]byte, error) {
function NewGroup (line 35) | func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
function GetGroup (line 52) | func GetGroup(name string) *Group {
FILE: gee-cache/day4-consistent-hash/geecache/geecache_test.go
function TestGetter (line 16) | func TestGetter(t *testing.T) {
function TestGet (line 27) | func TestGet(t *testing.T) {
function TestGetGroup (line 56) | func TestGetGroup(t *testing.T) {
FILE: gee-cache/day4-consistent-hash/geecache/http.go
constant defaultBasePath (line 10) | defaultBasePath = "/_geecache/"
type HTTPPool (line 13) | type HTTPPool struct
method Log (line 28) | func (p *HTTPPool) Log(format string, v ...interface{}) {
method ServeHTTP (line 33) | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
function NewHTTPPool (line 20) | func NewHTTPPool(self string) *HTTPPool {
FILE: gee-cache/day4-consistent-hash/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day4-consistent-hash/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day4-consistent-hash/main.go
function main (line 24) | func main() {
FILE: gee-cache/day5-multi-nodes/geecache/byteview.go
type ByteView (line 4) | type ByteView struct
method Len (line 9) | func (v ByteView) Len() int {
method ByteSlice (line 14) | func (v ByteView) ByteSlice() []byte {
method String (line 19) | func (v ByteView) String() string {
function cloneBytes (line 23) | func cloneBytes(b []byte) []byte {
FILE: gee-cache/day5-multi-nodes/geecache/cache.go
type cache (line 8) | type cache struct
method add (line 14) | func (c *cache) add(key string, value ByteView) {
method get (line 23) | func (c *cache) get(key string) (value ByteView, ok bool) {
FILE: gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash.go
type Hash (line 10) | type Hash
type Map (line 13) | type Map struct
method Add (line 34) | func (m *Map) Add(keys ...string) {
method Get (line 46) | func (m *Map) Get(key string) string {
function New (line 21) | func New(replicas int, fn Hash) *Map {
FILE: gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash_test.go
function TestHashing (line 8) | func TestHashing(t *testing.T) {
FILE: gee-cache/day5-multi-nodes/geecache/geecache.go
type Group (line 10) | type Group struct
method Get (line 61) | func (g *Group) Get(key string) (ByteView, error) {
method RegisterPeers (line 75) | func (g *Group) RegisterPeers(peers PeerPicker) {
method load (line 82) | func (g *Group) load(key string) (value ByteView, err error) {
method populateCache (line 95) | func (g *Group) populateCache(key string, value ByteView) {
method getLocally (line 99) | func (g *Group) getLocally(key string) (ByteView, error) {
method getFromPeer (line 110) | func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, er...
type Getter (line 18) | type Getter interface
type GetterFunc (line 23) | type GetterFunc
method Get (line 26) | func (f GetterFunc) Get(key string) ([]byte, error) {
function NewGroup (line 36) | func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
function GetGroup (line 53) | func GetGroup(name string) *Group {
FILE: gee-cache/day5-multi-nodes/geecache/geecache_test.go
function TestGetter (line 16) | func TestGetter(t *testing.T) {
function TestGet (line 27) | func TestGet(t *testing.T) {
function TestGetGroup (line 56) | func TestGetGroup(t *testing.T) {
FILE: gee-cache/day5-multi-nodes/geecache/http.go
constant defaultBasePath (line 15) | defaultBasePath = "/_geecache/"
constant defaultReplicas (line 16) | defaultReplicas = 50
type HTTPPool (line 20) | type HTTPPool struct
method Log (line 38) | func (p *HTTPPool) Log(format string, v ...interface{}) {
method ServeHTTP (line 43) | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method Set (line 75) | func (p *HTTPPool) Set(peers ...string) {
method PickPeer (line 87) | func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
function NewHTTPPool (line 30) | func NewHTTPPool(self string) *HTTPPool {
type httpGetter (line 99) | type httpGetter struct
method Get (line 103) | func (h *httpGetter) Get(group string, key string) ([]byte, error) {
FILE: gee-cache/day5-multi-nodes/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day5-multi-nodes/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day5-multi-nodes/geecache/peers.go
type PeerPicker (line 5) | type PeerPicker interface
type PeerGetter (line 10) | type PeerGetter interface
FILE: gee-cache/day5-multi-nodes/main.go
function createGroup (line 25) | func createGroup() *geecache.Group {
function startCacheServer (line 36) | func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
function startAPIServer (line 44) | func startAPIServer(apiAddr string, gee *geecache.Group) {
function main (line 62) | func main() {
FILE: gee-cache/day6-single-flight/geecache/byteview.go
type ByteView (line 4) | type ByteView struct
method Len (line 9) | func (v ByteView) Len() int {
method ByteSlice (line 14) | func (v ByteView) ByteSlice() []byte {
method String (line 19) | func (v ByteView) String() string {
function cloneBytes (line 23) | func cloneBytes(b []byte) []byte {
FILE: gee-cache/day6-single-flight/geecache/cache.go
type cache (line 8) | type cache struct
method add (line 14) | func (c *cache) add(key string, value ByteView) {
method get (line 23) | func (c *cache) get(key string) (value ByteView, ok bool) {
FILE: gee-cache/day6-single-flight/geecache/consistenthash/consistenthash.go
type Hash (line 10) | type Hash
type Map (line 13) | type Map struct
method Add (line 34) | func (m *Map) Add(keys ...string) {
method Get (line 46) | func (m *Map) Get(key string) string {
function New (line 21) | func New(replicas int, fn Hash) *Map {
FILE: gee-cache/day6-single-flight/geecache/consistenthash/consistenthash_test.go
function TestHashing (line 8) | func TestHashing(t *testing.T) {
FILE: gee-cache/day6-single-flight/geecache/geecache.go
type Group (line 11) | type Group struct
method Get (line 66) | func (g *Group) Get(key string) (ByteView, error) {
method RegisterPeers (line 80) | func (g *Group) RegisterPeers(peers PeerPicker) {
method load (line 87) | func (g *Group) load(key string) (value ByteView, err error) {
method populateCache (line 109) | func (g *Group) populateCache(key string, value ByteView) {
method getLocally (line 113) | func (g *Group) getLocally(key string) (ByteView, error) {
method getFromPeer (line 124) | func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, er...
type Getter (line 22) | type Getter interface
type GetterFunc (line 27) | type GetterFunc
method Get (line 30) | func (f GetterFunc) Get(key string) ([]byte, error) {
function NewGroup (line 40) | func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
function GetGroup (line 58) | func GetGroup(name string) *Group {
FILE: gee-cache/day6-single-flight/geecache/geecache_test.go
function TestGetter (line 16) | func TestGetter(t *testing.T) {
function TestGet (line 27) | func TestGet(t *testing.T) {
function TestGetGroup (line 56) | func TestGetGroup(t *testing.T) {
FILE: gee-cache/day6-single-flight/geecache/http.go
constant defaultBasePath (line 15) | defaultBasePath = "/_geecache/"
constant defaultReplicas (line 16) | defaultReplicas = 50
type HTTPPool (line 20) | type HTTPPool struct
method Log (line 38) | func (p *HTTPPool) Log(format string, v ...interface{}) {
method ServeHTTP (line 43) | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method Set (line 75) | func (p *HTTPPool) Set(peers ...string) {
method PickPeer (line 87) | func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
function NewHTTPPool (line 30) | func NewHTTPPool(self string) *HTTPPool {
type httpGetter (line 99) | type httpGetter struct
method Get (line 103) | func (h *httpGetter) Get(group string, key string) ([]byte, error) {
FILE: gee-cache/day6-single-flight/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day6-single-flight/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day6-single-flight/geecache/peers.go
type PeerPicker (line 5) | type PeerPicker interface
type PeerGetter (line 10) | type PeerGetter interface
FILE: gee-cache/day6-single-flight/geecache/singleflight/singleflight.go
type call (line 6) | type call struct
type Group (line 14) | type Group struct
method Do (line 23) | func (g *Group) Do(key string, fn func() (interface{}, error)) (interf...
FILE: gee-cache/day6-single-flight/geecache/singleflight/singleflight_test.go
function TestDo (line 7) | func TestDo(t *testing.T) {
FILE: gee-cache/day6-single-flight/main.go
function createGroup (line 25) | func createGroup() *geecache.Group {
function startCacheServer (line 36) | func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
function startAPIServer (line 44) | func startAPIServer(apiAddr string, gee *geecache.Group) {
function main (line 62) | func main() {
FILE: gee-cache/day7-proto-buf/geecache/byteview.go
type ByteView (line 4) | type ByteView struct
method Len (line 9) | func (v ByteView) Len() int {
method ByteSlice (line 14) | func (v ByteView) ByteSlice() []byte {
method String (line 19) | func (v ByteView) String() string {
function cloneBytes (line 23) | func cloneBytes(b []byte) []byte {
FILE: gee-cache/day7-proto-buf/geecache/cache.go
type cache (line 8) | type cache struct
method add (line 14) | func (c *cache) add(key string, value ByteView) {
method get (line 23) | func (c *cache) get(key string) (value ByteView, ok bool) {
FILE: gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash.go
type Hash (line 10) | type Hash
type Map (line 13) | type Map struct
method Add (line 34) | func (m *Map) Add(keys ...string) {
method Get (line 46) | func (m *Map) Get(key string) string {
function New (line 21) | func New(replicas int, fn Hash) *Map {
FILE: gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash_test.go
function TestHashing (line 8) | func TestHashing(t *testing.T) {
FILE: gee-cache/day7-proto-buf/geecache/geecache.go
type Group (line 12) | type Group struct
method Get (line 67) | func (g *Group) Get(key string) (ByteView, error) {
method RegisterPeers (line 81) | func (g *Group) RegisterPeers(peers PeerPicker) {
method load (line 88) | func (g *Group) load(key string) (value ByteView, err error) {
method populateCache (line 110) | func (g *Group) populateCache(key string, value ByteView) {
method getLocally (line 114) | func (g *Group) getLocally(key string) (ByteView, error) {
method getFromPeer (line 125) | func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, er...
type Getter (line 23) | type Getter interface
type GetterFunc (line 28) | type GetterFunc
method Get (line 31) | func (f GetterFunc) Get(key string) ([]byte, error) {
function NewGroup (line 41) | func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
function GetGroup (line 59) | func GetGroup(name string) *Group {
FILE: gee-cache/day7-proto-buf/geecache/geecache_test.go
function TestGetter (line 16) | func TestGetter(t *testing.T) {
function TestGet (line 27) | func TestGet(t *testing.T) {
function TestGetGroup (line 56) | func TestGetGroup(t *testing.T) {
FILE: gee-cache/day7-proto-buf/geecache/geecachepb/geecachepb.pb.go
constant _ (line 21) | _ = proto.ProtoPackageIsVersion3
type Request (line 23) | type Request struct
method Reset (line 31) | func (m *Request) Reset() { *m = Request{} }
method String (line 32) | func (m *Request) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 33) | func (*Request) ProtoMessage() {}
method Descriptor (line 34) | func (*Request) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 38) | func (m *Request) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 41) | func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, e...
method XXX_Merge (line 44) | func (m *Request) XXX_Merge(src proto.Message) {
method XXX_Size (line 47) | func (m *Request) XXX_Size() int {
method XXX_DiscardUnknown (line 50) | func (m *Request) XXX_DiscardUnknown() {
method GetGroup (line 56) | func (m *Request) GetGroup() string {
method GetKey (line 63) | func (m *Request) GetKey() string {
type Response (line 70) | type Response struct
method Reset (line 77) | func (m *Response) Reset() { *m = Response{} }
method String (line 78) | func (m *Response) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 79) | func (*Response) ProtoMessage() {}
method Descriptor (line 80) | func (*Response) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 84) | func (m *Response) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 87) | func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, ...
method XXX_Merge (line 90) | func (m *Response) XXX_Merge(src proto.Message) {
method XXX_Size (line 93) | func (m *Response) XXX_Size() int {
method XXX_DiscardUnknown (line 96) | func (m *Response) XXX_DiscardUnknown() {
method GetValue (line 102) | func (m *Response) GetValue() []byte {
function init (line 109) | func init() {
function init (line 114) | func init() { proto.RegisterFile("geecachepb.proto", fileDescriptor_889d...
FILE: gee-cache/day7-proto-buf/geecache/http.go
constant defaultBasePath (line 18) | defaultBasePath = "/_geecache/"
constant defaultReplicas (line 19) | defaultReplicas = 50
type HTTPPool (line 23) | type HTTPPool struct
method Log (line 41) | func (p *HTTPPool) Log(format string, v ...interface{}) {
method ServeHTTP (line 46) | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method Set (line 85) | func (p *HTTPPool) Set(peers ...string) {
method PickPeer (line 97) | func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
function NewHTTPPool (line 33) | func NewHTTPPool(self string) *HTTPPool {
type httpGetter (line 109) | type httpGetter struct
method Get (line 113) | func (h *httpGetter) Get(in *pb.Request, out *pb.Response) error {
FILE: gee-cache/day7-proto-buf/geecache/lru/lru.go
type Cache (line 6) | type Cache struct
method Add (line 36) | func (c *Cache) Add(key string, value Value) {
method Get (line 53) | func (c *Cache) Get(key string) (value Value, ok bool) {
method RemoveOldest (line 63) | func (c *Cache) RemoveOldest() {
method Len (line 77) | func (c *Cache) Len() int {
type entry (line 15) | type entry struct
type Value (line 21) | type Value interface
function New (line 26) | func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
FILE: gee-cache/day7-proto-buf/geecache/lru/lru_test.go
type String (line 8) | type String
method Len (line 10) | func (d String) Len() int {
function TestGet (line 14) | func TestGet(t *testing.T) {
function TestRemoveoldest (line 25) | func TestRemoveoldest(t *testing.T) {
function TestOnEvicted (line 39) | func TestOnEvicted(t *testing.T) {
function TestAdd (line 57) | func TestAdd(t *testing.T) {
FILE: gee-cache/day7-proto-buf/geecache/peers.go
type PeerPicker (line 7) | type PeerPicker interface
type PeerGetter (line 12) | type PeerGetter interface
FILE: gee-cache/day7-proto-buf/geecache/singleflight/singleflight.go
type call (line 6) | type call struct
type Group (line 14) | type Group struct
method Do (line 23) | func (g *Group) Do(key string, fn func() (interface{}, error)) (interf...
FILE: gee-cache/day7-proto-buf/geecache/singleflight/singleflight_test.go
function TestDo (line 7) | func TestDo(t *testing.T) {
FILE: gee-cache/day7-proto-buf/main.go
function createGroup (line 25) | func createGroup() *geecache.Group {
function startCacheServer (line 36) | func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
function startAPIServer (line 44) | func startAPIServer(apiAddr string, gee *geecache.Group) {
function main (line 62) | func main() {
FILE: gee-orm/day1-database-sql/cmd_test/main.go
function main (line 9) | func main() {
FILE: gee-orm/day1-database-sql/geeorm.go
type Engine (line 11) | type Engine struct
method Close (line 34) | func (engine *Engine) Close() {
method NewSession (line 42) | func (engine *Engine) NewSession() *session.Session {
function NewEngine (line 17) | func NewEngine(driver, source string) (e *Engine, err error) {
FILE: gee-orm/day1-database-sql/geeorm_test.go
function OpenDB (line 8) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 17) | func TestNewEngine(t *testing.T) {
FILE: gee-orm/day1-database-sql/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day1-database-sql/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day1-database-sql/session/raw.go
type Session (line 11) | type Session struct
method Clear (line 23) | func (s *Session) Clear() {
method DB (line 29) | func (s *Session) DB() *sql.DB {
method Exec (line 34) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 44) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 51) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 61) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 18) | func New(db *sql.DB) *Session {
FILE: gee-orm/day1-database-sql/session/raw_test.go
function TestMain (line 13) | func TestMain(m *testing.M) {
function NewSession (line 20) | func NewSession() *Session {
function TestSession_Exec (line 24) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 34) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day2-reflect-schema/dialect/dialect.go
type Dialect (line 8) | type Dialect interface
function RegisterDialect (line 14) | func RegisterDialect(name string, dialect Dialect) {
function GetDialect (line 19) | func GetDialect(name string) (dialect Dialect, ok bool) {
FILE: gee-orm/day2-reflect-schema/dialect/sqlite3.go
type sqlite3 (line 9) | type sqlite3 struct
method DataTypeOf (line 18) | func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
method TableExistSQL (line 42) | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface...
function init (line 13) | func init() {
FILE: gee-orm/day2-reflect-schema/dialect/sqlite3_test.go
function TestDataTypeOf (line 8) | func TestDataTypeOf(t *testing.T) {
FILE: gee-orm/day2-reflect-schema/geeorm.go
type Engine (line 11) | type Engine struct
method Close (line 41) | func (engine *Engine) Close() {
method NewSession (line 49) | func (engine *Engine) NewSession() *session.Session {
function NewEngine (line 18) | func NewEngine(driver, source string) (e *Engine, err error) {
FILE: gee-orm/day2-reflect-schema/geeorm_test.go
function OpenDB (line 8) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 17) | func TestNewEngine(t *testing.T) {
FILE: gee-orm/day2-reflect-schema/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day2-reflect-schema/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day2-reflect-schema/schema/schema.go
type Field (line 10) | type Field struct
type Schema (line 17) | type Schema struct
method GetField (line 26) | func (schema *Schema) GetField(name string) *Field {
method RecordValues (line 31) | func (schema *Schema) RecordValues(dest interface{}) []interface{} {
type ITableName (line 40) | type ITableName interface
function Parse (line 45) | func Parse(dest interface{}, d dialect.Dialect) *Schema {
FILE: gee-orm/day2-reflect-schema/schema/schema_test.go
type User (line 8) | type User struct
function TestParse (line 15) | func TestParse(t *testing.T) {
function TestSchema_RecordValues (line 25) | func TestSchema_RecordValues(t *testing.T) {
type UserTest (line 37) | type UserTest struct
method TableName (line 42) | func (u *UserTest) TableName() string {
function TestSchema_TableName (line 46) | func TestSchema_TableName(t *testing.T) {
FILE: gee-orm/day2-reflect-schema/session/raw.go
type Session (line 13) | type Session struct
method Clear (line 30) | func (s *Session) Clear() {
method DB (line 36) | func (s *Session) DB() *sql.DB {
method Exec (line 41) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 51) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 58) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 68) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 22) | func New(db *sql.DB, dialect dialect.Dialect) *Session {
FILE: gee-orm/day2-reflect-schema/session/raw_test.go
function TestMain (line 18) | func TestMain(m *testing.M) {
function NewSession (line 25) | func NewSession() *Session {
function TestSession_Exec (line 29) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 39) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day2-reflect-schema/session/table.go
method Model (line 13) | func (s *Session) Model(value interface{}) *Session {
method RefTable (line 22) | func (s *Session) RefTable() *schema.Schema {
method CreateTable (line 30) | func (s *Session) CreateTable() error {
method DropTable (line 42) | func (s *Session) DropTable() error {
method HasTable (line 48) | func (s *Session) HasTable() bool {
FILE: gee-orm/day2-reflect-schema/session/table_test.go
type User (line 7) | type User struct
function TestSession_CreateTable (line 11) | func TestSession_CreateTable(t *testing.T) {
function TestSession_Model (line 20) | func TestSession_Model(t *testing.T) {
FILE: gee-orm/day3-save-query/clause/clause.go
type Clause (line 8) | type Clause struct
method Set (line 27) | func (c *Clause) Set(name Type, vars ...interface{}) {
method Build (line 38) | func (c *Clause) Build(orders ...Type) (string, []interface{}) {
type Type (line 14) | type Type
constant INSERT (line 18) | INSERT Type = iota
constant VALUES (line 19) | VALUES
constant SELECT (line 20) | SELECT
constant LIMIT (line 21) | LIMIT
constant WHERE (line 22) | WHERE
constant ORDERBY (line 23) | ORDERBY
FILE: gee-orm/day3-save-query/clause/clause_test.go
function TestClause_Set (line 8) | func TestClause_Set(t *testing.T) {
function testSelect (line 19) | func testSelect(t *testing.T) {
function TestClause_Build (line 35) | func TestClause_Build(t *testing.T) {
FILE: gee-orm/day3-save-query/clause/generator.go
type generator (line 8) | type generator
function init (line 12) | func init() {
function genBindVars (line 22) | func genBindVars(num int) string {
function _insert (line 30) | func _insert(values ...interface{}) (string, []interface{}) {
function _values (line 37) | func _values(values ...interface{}) (string, []interface{}) {
function _select (line 58) | func _select(values ...interface{}) (string, []interface{}) {
function _limit (line 65) | func _limit(values ...interface{}) (string, []interface{}) {
function _where (line 70) | func _where(values ...interface{}) (string, []interface{}) {
function _orderBy (line 76) | func _orderBy(values ...interface{}) (string, []interface{}) {
FILE: gee-orm/day3-save-query/dialect/dialect.go
type Dialect (line 8) | type Dialect interface
function RegisterDialect (line 14) | func RegisterDialect(name string, dialect Dialect) {
function GetDialect (line 19) | func GetDialect(name string) (dialect Dialect, ok bool) {
FILE: gee-orm/day3-save-query/dialect/sqlite3.go
type sqlite3 (line 9) | type sqlite3 struct
method DataTypeOf (line 18) | func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
method TableExistSQL (line 42) | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface...
function init (line 13) | func init() {
FILE: gee-orm/day3-save-query/dialect/sqlite3_test.go
function TestDataTypeOf (line 8) | func TestDataTypeOf(t *testing.T) {
FILE: gee-orm/day3-save-query/geeorm.go
type Engine (line 11) | type Engine struct
method Close (line 41) | func (engine *Engine) Close() {
method NewSession (line 49) | func (engine *Engine) NewSession() *session.Session {
function NewEngine (line 18) | func NewEngine(driver, source string) (e *Engine, err error) {
FILE: gee-orm/day3-save-query/geeorm_test.go
function OpenDB (line 8) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 17) | func TestNewEngine(t *testing.T) {
FILE: gee-orm/day3-save-query/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day3-save-query/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day3-save-query/schema/schema.go
type Field (line 10) | type Field struct
type Schema (line 17) | type Schema struct
method GetField (line 26) | func (schema *Schema) GetField(name string) *Field {
method RecordValues (line 31) | func (schema *Schema) RecordValues(dest interface{}) []interface{} {
type ITableName (line 40) | type ITableName interface
function Parse (line 45) | func Parse(dest interface{}, d dialect.Dialect) *Schema {
FILE: gee-orm/day3-save-query/schema/schema_test.go
type User (line 8) | type User struct
function TestParse (line 15) | func TestParse(t *testing.T) {
function TestSchema_RecordValues (line 25) | func TestSchema_RecordValues(t *testing.T) {
type UserTest (line 37) | type UserTest struct
method TableName (line 42) | func (u *UserTest) TableName() string {
function TestSchema_TableName (line 46) | func TestSchema_TableName(t *testing.T) {
FILE: gee-orm/day3-save-query/session/raw.go
type Session (line 14) | type Session struct
method Clear (line 32) | func (s *Session) Clear() {
method DB (line 39) | func (s *Session) DB() *sql.DB {
method Exec (line 44) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 54) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 61) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 71) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 24) | func New(db *sql.DB, dialect dialect.Dialect) *Session {
FILE: gee-orm/day3-save-query/session/raw_test.go
function TestMain (line 18) | func TestMain(m *testing.M) {
function NewSession (line 25) | func NewSession() *Session {
function TestSession_Exec (line 28) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 38) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day3-save-query/session/record.go
method Insert (line 9) | func (s *Session) Insert(values ...interface{}) (int64, error) {
method Find (line 28) | func (s *Session) Find(values interface{}) error {
FILE: gee-orm/day3-save-query/session/record_test.go
function testRecordInit (line 11) | func testRecordInit(t *testing.T) *Session {
function TestSession_Insert (line 23) | func TestSession_Insert(t *testing.T) {
function TestSession_Find (line 31) | func TestSession_Find(t *testing.T) {
FILE: gee-orm/day3-save-query/session/table.go
method Model (line 13) | func (s *Session) Model(value interface{}) *Session {
method RefTable (line 22) | func (s *Session) RefTable() *schema.Schema {
method CreateTable (line 30) | func (s *Session) CreateTable() error {
method DropTable (line 42) | func (s *Session) DropTable() error {
method HasTable (line 48) | func (s *Session) HasTable() bool {
FILE: gee-orm/day3-save-query/session/table_test.go
type User (line 7) | type User struct
function TestSession_CreateTable (line 12) | func TestSession_CreateTable(t *testing.T) {
function TestSession_Model (line 21) | func TestSession_Model(t *testing.T) {
FILE: gee-orm/day4-chain-operation/clause/clause.go
type Clause (line 8) | type Clause struct
method Set (line 30) | func (c *Clause) Set(name Type, vars ...interface{}) {
method Build (line 41) | func (c *Clause) Build(orders ...Type) (string, []interface{}) {
type Type (line 14) | type Type
constant INSERT (line 18) | INSERT Type = iota
constant VALUES (line 19) | VALUES
constant SELECT (line 20) | SELECT
constant LIMIT (line 21) | LIMIT
constant WHERE (line 22) | WHERE
constant ORDERBY (line 23) | ORDERBY
constant UPDATE (line 24) | UPDATE
constant DELETE (line 25) | DELETE
constant COUNT (line 26) | COUNT
FILE: gee-orm/day4-chain-operation/clause/clause_test.go
function TestClause_Set (line 8) | func TestClause_Set(t *testing.T) {
function testSelect (line 19) | func testSelect(t *testing.T) {
function testUpdate (line 35) | func testUpdate(t *testing.T) {
function testDelete (line 49) | func testDelete(t *testing.T) {
function TestClause_Build (line 64) | func TestClause_Build(t *testing.T) {
FILE: gee-orm/day4-chain-operation/clause/generator.go
type generator (line 8) | type generator
function init (line 12) | func init() {
function genBindVars (line 25) | func genBindVars(num int) string {
function _insert (line 33) | func _insert(values ...interface{}) (string, []interface{}) {
function _values (line 40) | func _values(values ...interface{}) (string, []interface{}) {
function _select (line 61) | func _select(values ...interface{}) (string, []interface{}) {
function _limit (line 68) | func _limit(values ...interface{}) (string, []interface{}) {
function _where (line 73) | func _where(values ...interface{}) (string, []interface{}) {
function _orderBy (line 79) | func _orderBy(values ...interface{}) (string, []interface{}) {
function _update (line 83) | func _update(values ...interface{}) (string, []interface{}) {
function _delete (line 95) | func _delete(values ...interface{}) (string, []interface{}) {
function _count (line 99) | func _count(values ...interface{}) (string, []interface{}) {
FILE: gee-orm/day4-chain-operation/dialect/dialect.go
type Dialect (line 8) | type Dialect interface
function RegisterDialect (line 14) | func RegisterDialect(name string, dialect Dialect) {
function GetDialect (line 19) | func GetDialect(name string) (dialect Dialect, ok bool) {
FILE: gee-orm/day4-chain-operation/dialect/sqlite3.go
type sqlite3 (line 9) | type sqlite3 struct
method DataTypeOf (line 18) | func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
method TableExistSQL (line 42) | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface...
function init (line 13) | func init() {
FILE: gee-orm/day4-chain-operation/dialect/sqlite3_test.go
function TestDataTypeOf (line 8) | func TestDataTypeOf(t *testing.T) {
FILE: gee-orm/day4-chain-operation/geeorm.go
type Engine (line 11) | type Engine struct
method Close (line 41) | func (engine *Engine) Close() {
method NewSession (line 49) | func (engine *Engine) NewSession() *session.Session {
function NewEngine (line 18) | func NewEngine(driver, source string) (e *Engine, err error) {
FILE: gee-orm/day4-chain-operation/geeorm_test.go
function OpenDB (line 8) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 17) | func TestNewEngine(t *testing.T) {
FILE: gee-orm/day4-chain-operation/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day4-chain-operation/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day4-chain-operation/schema/schema.go
type Field (line 10) | type Field struct
type Schema (line 17) | type Schema struct
method GetField (line 26) | func (schema *Schema) GetField(name string) *Field {
method RecordValues (line 31) | func (schema *Schema) RecordValues(dest interface{}) []interface{} {
type ITableName (line 40) | type ITableName interface
function Parse (line 45) | func Parse(dest interface{}, d dialect.Dialect) *Schema {
FILE: gee-orm/day4-chain-operation/schema/schema_test.go
type User (line 8) | type User struct
function TestParse (line 15) | func TestParse(t *testing.T) {
function TestSchema_RecordValues (line 25) | func TestSchema_RecordValues(t *testing.T) {
type UserTest (line 37) | type UserTest struct
method TableName (line 42) | func (u *UserTest) TableName() string {
function TestSchema_TableName (line 46) | func TestSchema_TableName(t *testing.T) {
FILE: gee-orm/day4-chain-operation/session/raw.go
type Session (line 14) | type Session struct
method Clear (line 32) | func (s *Session) Clear() {
method DB (line 39) | func (s *Session) DB() *sql.DB {
method Exec (line 44) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 54) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 61) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 71) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 24) | func New(db *sql.DB, dialect dialect.Dialect) *Session {
FILE: gee-orm/day4-chain-operation/session/raw_test.go
function TestMain (line 18) | func TestMain(m *testing.M) {
function NewSession (line 25) | func NewSession() *Session {
function TestSession_Exec (line 29) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 39) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day4-chain-operation/session/record.go
method Insert (line 10) | func (s *Session) Insert(values ...interface{}) (int64, error) {
method Find (line 29) | func (s *Session) Find(values interface{}) error {
method First (line 56) | func (s *Session) First(value interface{}) error {
method Limit (line 70) | func (s *Session) Limit(num int) *Session {
method Where (line 76) | func (s *Session) Where(desc string, args ...interface{}) *Session {
method OrderBy (line 83) | func (s *Session) OrderBy(desc string) *Session {
method Update (line 91) | func (s *Session) Update(kv ...interface{}) (int64, error) {
method Delete (line 109) | func (s *Session) Delete() (int64, error) {
method Count (line 120) | func (s *Session) Count() (int64, error) {
FILE: gee-orm/day4-chain-operation/session/record_test.go
function testRecordInit (line 11) | func testRecordInit(t *testing.T) *Session {
function TestSession_Insert (line 23) | func TestSession_Insert(t *testing.T) {
function TestSession_Find (line 31) | func TestSession_Find(t *testing.T) {
function TestSession_First (line 39) | func TestSession_First(t *testing.T) {
function TestSession_Limit (line 48) | func TestSession_Limit(t *testing.T) {
function TestSession_Where (line 57) | func TestSession_Where(t *testing.T) {
function TestSession_OrderBy (line 68) | func TestSession_OrderBy(t *testing.T) {
function TestSession_Update (line 78) | func TestSession_Update(t *testing.T) {
function TestSession_DeleteAndCount (line 89) | func TestSession_DeleteAndCount(t *testing.T) {
FILE: gee-orm/day4-chain-operation/session/table.go
method Model (line 13) | func (s *Session) Model(value interface{}) *Session {
method RefTable (line 22) | func (s *Session) RefTable() *schema.Schema {
method CreateTable (line 30) | func (s *Session) CreateTable() error {
method DropTable (line 42) | func (s *Session) DropTable() error {
method HasTable (line 48) | func (s *Session) HasTable() bool {
FILE: gee-orm/day4-chain-operation/session/table_test.go
type User (line 7) | type User struct
function TestSession_CreateTable (line 12) | func TestSession_CreateTable(t *testing.T) {
function TestSession_Model (line 21) | func TestSession_Model(t *testing.T) {
FILE: gee-orm/day5-hooks/clause/clause.go
type Clause (line 8) | type Clause struct
method Set (line 30) | func (c *Clause) Set(name Type, vars ...interface{}) {
method Build (line 41) | func (c *Clause) Build(orders ...Type) (string, []interface{}) {
type Type (line 14) | type Type
constant INSERT (line 18) | INSERT Type = iota
constant VALUES (line 19) | VALUES
constant SELECT (line 20) | SELECT
constant LIMIT (line 21) | LIMIT
constant WHERE (line 22) | WHERE
constant ORDERBY (line 23) | ORDERBY
constant UPDATE (line 24) | UPDATE
constant DELETE (line 25) | DELETE
constant COUNT (line 26) | COUNT
FILE: gee-orm/day5-hooks/clause/clause_test.go
function TestClause_Set (line 8) | func TestClause_Set(t *testing.T) {
function testSelect (line 19) | func testSelect(t *testing.T) {
function testUpdate (line 35) | func testUpdate(t *testing.T) {
function testDelete (line 49) | func testDelete(t *testing.T) {
function TestClause_Build (line 64) | func TestClause_Build(t *testing.T) {
FILE: gee-orm/day5-hooks/clause/generator.go
type generator (line 8) | type generator
function init (line 12) | func init() {
function genBindVars (line 25) | func genBindVars(num int) string {
function _insert (line 33) | func _insert(values ...interface{}) (string, []interface{}) {
function _values (line 40) | func _values(values ...interface{}) (string, []interface{}) {
function _select (line 61) | func _select(values ...interface{}) (string, []interface{}) {
function _limit (line 68) | func _limit(values ...interface{}) (string, []interface{}) {
function _where (line 73) | func _where(values ...interface{}) (string, []interface{}) {
function _orderBy (line 79) | func _orderBy(values ...interface{}) (string, []interface{}) {
function _update (line 83) | func _update(values ...interface{}) (string, []interface{}) {
function _delete (line 95) | func _delete(values ...interface{}) (string, []interface{}) {
function _count (line 99) | func _count(values ...interface{}) (string, []interface{}) {
FILE: gee-orm/day5-hooks/dialect/dialect.go
type Dialect (line 8) | type Dialect interface
function RegisterDialect (line 14) | func RegisterDialect(name string, dialect Dialect) {
function GetDialect (line 19) | func GetDialect(name string) (dialect Dialect, ok bool) {
FILE: gee-orm/day5-hooks/dialect/sqlite3.go
type sqlite3 (line 9) | type sqlite3 struct
method DataTypeOf (line 18) | func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
method TableExistSQL (line 42) | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface...
function init (line 13) | func init() {
FILE: gee-orm/day5-hooks/dialect/sqlite3_test.go
function TestDataTypeOf (line 8) | func TestDataTypeOf(t *testing.T) {
FILE: gee-orm/day5-hooks/geeorm.go
type Engine (line 11) | type Engine struct
method Close (line 41) | func (engine *Engine) Close() {
method NewSession (line 49) | func (engine *Engine) NewSession() *session.Session {
function NewEngine (line 18) | func NewEngine(driver, source string) (e *Engine, err error) {
FILE: gee-orm/day5-hooks/geeorm_test.go
function OpenDB (line 8) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 17) | func TestNewEngine(t *testing.T) {
FILE: gee-orm/day5-hooks/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day5-hooks/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day5-hooks/schema/schema.go
type Field (line 10) | type Field struct
type Schema (line 17) | type Schema struct
method GetField (line 26) | func (schema *Schema) GetField(name string) *Field {
method RecordValues (line 31) | func (schema *Schema) RecordValues(dest interface{}) []interface{} {
type ITableName (line 40) | type ITableName interface
function Parse (line 45) | func Parse(dest interface{}, d dialect.Dialect) *Schema {
FILE: gee-orm/day5-hooks/schema/schema_test.go
type User (line 8) | type User struct
function TestParse (line 15) | func TestParse(t *testing.T) {
function TestSchema_RecordValues (line 25) | func TestSchema_RecordValues(t *testing.T) {
type UserTest (line 37) | type UserTest struct
method TableName (line 42) | func (u *UserTest) TableName() string {
function TestSchema_TableName (line 46) | func TestSchema_TableName(t *testing.T) {
FILE: gee-orm/day5-hooks/session/hooks.go
constant BeforeQuery (line 10) | BeforeQuery = "BeforeQuery"
constant AfterQuery (line 11) | AfterQuery = "AfterQuery"
constant BeforeUpdate (line 12) | BeforeUpdate = "BeforeUpdate"
constant AfterUpdate (line 13) | AfterUpdate = "AfterUpdate"
constant BeforeDelete (line 14) | BeforeDelete = "BeforeDelete"
constant AfterDelete (line 15) | AfterDelete = "AfterDelete"
constant BeforeInsert (line 16) | BeforeInsert = "BeforeInsert"
constant AfterInsert (line 17) | AfterInsert = "AfterInsert"
method CallMethod (line 21) | func (s *Session) CallMethod(method string, value interface{}) {
FILE: gee-orm/day5-hooks/session/hooks_test.go
type Account (line 8) | type Account struct
method BeforeInsert (line 13) | func (account *Account) BeforeInsert(s *Session) error {
method AfterQuery (line 19) | func (account *Account) AfterQuery(s *Session) error {
function TestSession_CallMethod (line 25) | func TestSession_CallMethod(t *testing.T) {
FILE: gee-orm/day5-hooks/session/raw.go
type Session (line 14) | type Session struct
method Clear (line 32) | func (s *Session) Clear() {
method DB (line 39) | func (s *Session) DB() *sql.DB {
method Exec (line 44) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 54) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 61) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 71) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 24) | func New(db *sql.DB, dialect dialect.Dialect) *Session {
FILE: gee-orm/day5-hooks/session/raw_test.go
function TestMain (line 18) | func TestMain(m *testing.M) {
function NewSession (line 25) | func NewSession() *Session {
function TestSession_Exec (line 29) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 39) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day5-hooks/session/record.go
method Insert (line 10) | func (s *Session) Insert(values ...interface{}) (int64, error) {
method Find (line 30) | func (s *Session) Find(values interface{}) error {
method First (line 59) | func (s *Session) First(value interface{}) error {
method Limit (line 73) | func (s *Session) Limit(num int) *Session {
method Where (line 79) | func (s *Session) Where(desc string, args ...interface{}) *Session {
method OrderBy (line 86) | func (s *Session) OrderBy(desc string) *Session {
method Update (line 94) | func (s *Session) Update(kv ...interface{}) (int64, error) {
method Delete (line 114) | func (s *Session) Delete() (int64, error) {
method Count (line 127) | func (s *Session) Count() (int64, error) {
FILE: gee-orm/day5-hooks/session/record_test.go
function testRecordInit (line 11) | func testRecordInit(t *testing.T) *Session {
function TestSession_Insert (line 23) | func TestSession_Insert(t *testing.T) {
function TestSession_Find (line 31) | func TestSession_Find(t *testing.T) {
function TestSession_First (line 39) | func TestSession_First(t *testing.T) {
function TestSession_Limit (line 48) | func TestSession_Limit(t *testing.T) {
function TestSession_Where (line 57) | func TestSession_Where(t *testing.T) {
function TestSession_OrderBy (line 68) | func TestSession_OrderBy(t *testing.T) {
function TestSession_Update (line 78) | func TestSession_Update(t *testing.T) {
function TestSession_DeleteAndCount (line 89) | func TestSession_DeleteAndCount(t *testing.T) {
FILE: gee-orm/day5-hooks/session/table.go
method Model (line 13) | func (s *Session) Model(value interface{}) *Session {
method RefTable (line 22) | func (s *Session) RefTable() *schema.Schema {
method CreateTable (line 30) | func (s *Session) CreateTable() error {
method DropTable (line 42) | func (s *Session) DropTable() error {
method HasTable (line 48) | func (s *Session) HasTable() bool {
FILE: gee-orm/day5-hooks/session/table_test.go
type User (line 7) | type User struct
function TestSession_CreateTable (line 12) | func TestSession_CreateTable(t *testing.T) {
function TestSession_Model (line 21) | func TestSession_Model(t *testing.T) {
FILE: gee-orm/day6-transaction/clause/clause.go
type Clause (line 8) | type Clause struct
method Set (line 30) | func (c *Clause) Set(name Type, vars ...interface{}) {
method Build (line 41) | func (c *Clause) Build(orders ...Type) (string, []interface{}) {
type Type (line 14) | type Type
constant INSERT (line 18) | INSERT Type = iota
constant VALUES (line 19) | VALUES
constant SELECT (line 20) | SELECT
constant LIMIT (line 21) | LIMIT
constant WHERE (line 22) | WHERE
constant ORDERBY (line 23) | ORDERBY
constant UPDATE (line 24) | UPDATE
constant DELETE (line 25) | DELETE
constant COUNT (line 26) | COUNT
FILE: gee-orm/day6-transaction/clause/clause_test.go
function TestClause_Set (line 8) | func TestClause_Set(t *testing.T) {
function testSelect (line 19) | func testSelect(t *testing.T) {
function testUpdate (line 35) | func testUpdate(t *testing.T) {
function testDelete (line 49) | func testDelete(t *testing.T) {
function TestClause_Build (line 64) | func TestClause_Build(t *testing.T) {
FILE: gee-orm/day6-transaction/clause/generator.go
type generator (line 8) | type generator
function init (line 12) | func init() {
function genBindVars (line 25) | func genBindVars(num int) string {
function _insert (line 33) | func _insert(values ...interface{}) (string, []interface{}) {
function _values (line 40) | func _values(values ...interface{}) (string, []interface{}) {
function _select (line 61) | func _select(values ...interface{}) (string, []interface{}) {
function _limit (line 68) | func _limit(values ...interface{}) (string, []interface{}) {
function _where (line 73) | func _where(values ...interface{}) (string, []interface{}) {
function _orderBy (line 79) | func _orderBy(values ...interface{}) (string, []interface{}) {
function _update (line 83) | func _update(values ...interface{}) (string, []interface{}) {
function _delete (line 95) | func _delete(values ...interface{}) (string, []interface{}) {
function _count (line 99) | func _count(values ...interface{}) (string, []interface{}) {
FILE: gee-orm/day6-transaction/dialect/dialect.go
type Dialect (line 8) | type Dialect interface
function RegisterDialect (line 14) | func RegisterDialect(name string, dialect Dialect) {
function GetDialect (line 19) | func GetDialect(name string) (dialect Dialect, ok bool) {
FILE: gee-orm/day6-transaction/dialect/sqlite3.go
type sqlite3 (line 9) | type sqlite3 struct
method DataTypeOf (line 18) | func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
method TableExistSQL (line 42) | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface...
function init (line 13) | func init() {
FILE: gee-orm/day6-transaction/dialect/sqlite3_test.go
function TestDataTypeOf (line 8) | func TestDataTypeOf(t *testing.T) {
FILE: gee-orm/day6-transaction/geeorm.go
type Engine (line 11) | type Engine struct
method Close (line 41) | func (engine *Engine) Close() {
method NewSession (line 49) | func (engine *Engine) NewSession() *session.Session {
method Transaction (line 58) | func (engine *Engine) Transaction(f TxFunc) (result interface{}, err e...
function NewEngine (line 18) | func NewEngine(driver, source string) (e *Engine, err error) {
type TxFunc (line 55) | type TxFunc
FILE: gee-orm/day6-transaction/geeorm_test.go
function OpenDB (line 11) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 20) | func TestNewEngine(t *testing.T) {
type User (line 25) | type User struct
function transactionRollback (line 30) | func transactionRollback(t *testing.T) {
function transactionCommit (line 45) | func transactionCommit(t *testing.T) {
function TestEngine_Transaction (line 62) | func TestEngine_Transaction(t *testing.T) {
FILE: gee-orm/day6-transaction/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day6-transaction/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day6-transaction/schema/schema.go
type Field (line 10) | type Field struct
type Schema (line 17) | type Schema struct
method GetField (line 26) | func (schema *Schema) GetField(name string) *Field {
method RecordValues (line 31) | func (schema *Schema) RecordValues(dest interface{}) []interface{} {
type ITableName (line 40) | type ITableName interface
function Parse (line 45) | func Parse(dest interface{}, d dialect.Dialect) *Schema {
FILE: gee-orm/day6-transaction/schema/schema_test.go
type User (line 8) | type User struct
function TestParse (line 15) | func TestParse(t *testing.T) {
function TestSchema_RecordValues (line 25) | func TestSchema_RecordValues(t *testing.T) {
type UserTest (line 37) | type UserTest struct
method TableName (line 42) | func (u *UserTest) TableName() string {
function TestSchema_TableName (line 46) | func TestSchema_TableName(t *testing.T) {
FILE: gee-orm/day6-transaction/session/hooks.go
constant BeforeQuery (line 10) | BeforeQuery = "BeforeQuery"
constant AfterQuery (line 11) | AfterQuery = "AfterQuery"
constant BeforeUpdate (line 12) | BeforeUpdate = "BeforeUpdate"
constant AfterUpdate (line 13) | AfterUpdate = "AfterUpdate"
constant BeforeDelete (line 14) | BeforeDelete = "BeforeDelete"
constant AfterDelete (line 15) | AfterDelete = "AfterDelete"
constant BeforeInsert (line 16) | BeforeInsert = "BeforeInsert"
constant AfterInsert (line 17) | AfterInsert = "AfterInsert"
method CallMethod (line 21) | func (s *Session) CallMethod(method string, value interface{}) {
FILE: gee-orm/day6-transaction/session/hooks_test.go
type Account (line 8) | type Account struct
method BeforeInsert (line 13) | func (account *Account) BeforeInsert(s *Session) error {
method AfterQuery (line 19) | func (account *Account) AfterQuery(s *Session) error {
function TestSession_CallMethod (line 25) | func TestSession_CallMethod(t *testing.T) {
FILE: gee-orm/day6-transaction/session/raw.go
type Session (line 14) | type Session struct
method Clear (line 33) | func (s *Session) Clear() {
method DB (line 50) | func (s *Session) DB() CommonDB {
method Exec (line 58) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 68) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 75) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 85) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 25) | func New(db *sql.DB, dialect dialect.Dialect) *Session {
type CommonDB (line 40) | type CommonDB interface
FILE: gee-orm/day6-transaction/session/raw_test.go
function TestMain (line 18) | func TestMain(m *testing.M) {
function NewSession (line 25) | func NewSession() *Session {
function TestSession_Exec (line 29) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 39) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day6-transaction/session/record.go
method Insert (line 10) | func (s *Session) Insert(values ...interface{}) (int64, error) {
method Find (line 30) | func (s *Session) Find(values interface{}) error {
method First (line 59) | func (s *Session) First(value interface{}) error {
method Limit (line 73) | func (s *Session) Limit(num int) *Session {
method Where (line 79) | func (s *Session) Where(desc string, args ...interface{}) *Session {
method OrderBy (line 86) | func (s *Session) OrderBy(desc string) *Session {
method Update (line 94) | func (s *Session) Update(kv ...interface{}) (int64, error) {
method Delete (line 114) | func (s *Session) Delete() (int64, error) {
method Count (line 127) | func (s *Session) Count() (int64, error) {
FILE: gee-orm/day6-transaction/session/record_test.go
function testRecordInit (line 11) | func testRecordInit(t *testing.T) *Session {
function TestSession_Insert (line 23) | func TestSession_Insert(t *testing.T) {
function TestSession_Find (line 31) | func TestSession_Find(t *testing.T) {
function TestSession_First (line 39) | func TestSession_First(t *testing.T) {
function TestSession_Limit (line 48) | func TestSession_Limit(t *testing.T) {
function TestSession_Where (line 57) | func TestSession_Where(t *testing.T) {
function TestSession_OrderBy (line 68) | func TestSession_OrderBy(t *testing.T) {
function TestSession_Update (line 78) | func TestSession_Update(t *testing.T) {
function TestSession_DeleteAndCount (line 89) | func TestSession_DeleteAndCount(t *testing.T) {
FILE: gee-orm/day6-transaction/session/table.go
method Model (line 13) | func (s *Session) Model(value interface{}) *Session {
method RefTable (line 22) | func (s *Session) RefTable() *schema.Schema {
method CreateTable (line 30) | func (s *Session) CreateTable() error {
method DropTable (line 42) | func (s *Session) DropTable() error {
method HasTable (line 48) | func (s *Session) HasTable() bool {
FILE: gee-orm/day6-transaction/session/table_test.go
type User (line 7) | type User struct
function TestSession_CreateTable (line 12) | func TestSession_CreateTable(t *testing.T) {
function TestSession_Model (line 21) | func TestSession_Model(t *testing.T) {
FILE: gee-orm/day6-transaction/session/transaction.go
method Begin (line 6) | func (s *Session) Begin() (err error) {
method Commit (line 16) | func (s *Session) Commit() (err error) {
method Rollback (line 25) | func (s *Session) Rollback() (err error) {
FILE: gee-orm/day7-migrate/clause/clause.go
type Clause (line 8) | type Clause struct
method Set (line 30) | func (c *Clause) Set(name Type, vars ...interface{}) {
method Build (line 41) | func (c *Clause) Build(orders ...Type) (string, []interface{}) {
type Type (line 14) | type Type
constant INSERT (line 18) | INSERT Type = iota
constant VALUES (line 19) | VALUES
constant SELECT (line 20) | SELECT
constant LIMIT (line 21) | LIMIT
constant WHERE (line 22) | WHERE
constant ORDERBY (line 23) | ORDERBY
constant UPDATE (line 24) | UPDATE
constant DELETE (line 25) | DELETE
constant COUNT (line 26) | COUNT
FILE: gee-orm/day7-migrate/clause/clause_test.go
function TestClause_Set (line 8) | func TestClause_Set(t *testing.T) {
function testSelect (line 19) | func testSelect(t *testing.T) {
function testUpdate (line 35) | func testUpdate(t *testing.T) {
function testDelete (line 49) | func testDelete(t *testing.T) {
function TestClause_Build (line 64) | func TestClause_Build(t *testing.T) {
FILE: gee-orm/day7-migrate/clause/generator.go
type generator (line 8) | type generator
function init (line 12) | func init() {
function genBindVars (line 25) | func genBindVars(num int) string {
function _insert (line 33) | func _insert(values ...interface{}) (string, []interface{}) {
function _values (line 40) | func _values(values ...interface{}) (string, []interface{}) {
function _select (line 61) | func _select(values ...interface{}) (string, []interface{}) {
function _limit (line 68) | func _limit(values ...interface{}) (string, []interface{}) {
function _where (line 73) | func _where(values ...interface{}) (string, []interface{}) {
function _orderBy (line 79) | func _orderBy(values ...interface{}) (string, []interface{}) {
function _update (line 83) | func _update(values ...interface{}) (string, []interface{}) {
function _delete (line 95) | func _delete(values ...interface{}) (string, []interface{}) {
function _count (line 99) | func _count(values ...interface{}) (string, []interface{}) {
FILE: gee-orm/day7-migrate/dialect/dialect.go
type Dialect (line 8) | type Dialect interface
function RegisterDialect (line 14) | func RegisterDialect(name string, dialect Dialect) {
function GetDialect (line 19) | func GetDialect(name string) (dialect Dialect, ok bool) {
FILE: gee-orm/day7-migrate/dialect/sqlite3.go
type sqlite3 (line 9) | type sqlite3 struct
method DataTypeOf (line 18) | func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
method TableExistSQL (line 42) | func (s *sqlite3) TableExistSQL(tableName string) (string, []interface...
function init (line 13) | func init() {
FILE: gee-orm/day7-migrate/dialect/sqlite3_test.go
function TestDataTypeOf (line 8) | func TestDataTypeOf(t *testing.T) {
FILE: gee-orm/day7-migrate/geeorm.go
type Engine (line 13) | type Engine struct
method Close (line 43) | func (engine *Engine) Close() {
method NewSession (line 52) | func (engine *Engine) NewSession() *session.Session {
method Transaction (line 61) | func (engine *Engine) Transaction(f TxFunc) (result interface{}, err e...
method Migrate (line 95) | func (engine *Engine) Migrate(value interface{}) error {
function NewEngine (line 20) | func NewEngine(driver, source string) (e *Engine, err error) {
type TxFunc (line 58) | type TxFunc
function difference (line 81) | func difference(a []string, b []string) (diff []string) {
FILE: gee-orm/day7-migrate/geeorm_test.go
function OpenDB (line 12) | func OpenDB(t *testing.T) *Engine {
function TestNewEngine (line 21) | func TestNewEngine(t *testing.T) {
type User (line 26) | type User struct
function transactionRollback (line 31) | func transactionRollback(t *testing.T) {
function transactionCommit (line 46) | func transactionCommit(t *testing.T) {
function TestEngine_Transaction (line 63) | func TestEngine_Transaction(t *testing.T) {
function TestEngine_Migrate (line 72) | func TestEngine_Migrate(t *testing.T) {
FILE: gee-orm/day7-migrate/log/log.go
constant InfoLevel (line 27) | InfoLevel = iota
constant ErrorLevel (line 28) | ErrorLevel
constant Disabled (line 29) | Disabled
function SetLevel (line 33) | func SetLevel(level int) {
FILE: gee-orm/day7-migrate/log/log_test.go
function TestSetLevel (line 8) | func TestSetLevel(t *testing.T) {
FILE: gee-orm/day7-migrate/schema/schema.go
type Field (line 10) | type Field struct
type Schema (line 17) | type Schema struct
method GetField (line 26) | func (schema *Schema) GetField(name string) *Field {
method RecordValues (line 31) | func (schema *Schema) RecordValues(dest interface{}) []interface{} {
type ITableName (line 40) | type ITableName interface
function Parse (line 45) | func Parse(dest interface{}, d dialect.Dialect) *Schema {
FILE: gee-orm/day7-migrate/schema/schema_test.go
type User (line 8) | type User struct
function TestParse (line 15) | func TestParse(t *testing.T) {
function TestSchema_RecordValues (line 25) | func TestSchema_RecordValues(t *testing.T) {
type UserTest (line 37) | type UserTest struct
method TableName (line 42) | func (u *UserTest) TableName() string {
function TestSchema_TableName (line 46) | func TestSchema_TableName(t *testing.T) {
FILE: gee-orm/day7-migrate/session/hooks.go
constant BeforeQuery (line 10) | BeforeQuery = "BeforeQuery"
constant AfterQuery (line 11) | AfterQuery = "AfterQuery"
constant BeforeUpdate (line 12) | BeforeUpdate = "BeforeUpdate"
constant AfterUpdate (line 13) | AfterUpdate = "AfterUpdate"
constant BeforeDelete (line 14) | BeforeDelete = "BeforeDelete"
constant AfterDelete (line 15) | AfterDelete = "AfterDelete"
constant BeforeInsert (line 16) | BeforeInsert = "BeforeInsert"
constant AfterInsert (line 17) | AfterInsert = "AfterInsert"
method CallMethod (line 21) | func (s *Session) CallMethod(method string, value interface{}) {
FILE: gee-orm/day7-migrate/session/hooks_test.go
type Account (line 8) | type Account struct
method BeforeInsert (line 13) | func (account *Account) BeforeInsert(s *Session) error {
method AfterQuery (line 19) | func (account *Account) AfterQuery(s *Session) error {
function TestSession_CallMethod (line 25) | func TestSession_CallMethod(t *testing.T) {
FILE: gee-orm/day7-migrate/session/raw.go
type Session (line 14) | type Session struct
method Clear (line 33) | func (s *Session) Clear() {
method DB (line 50) | func (s *Session) DB() CommonDB {
method Exec (line 58) | func (s *Session) Exec() (result sql.Result, err error) {
method QueryRow (line 68) | func (s *Session) QueryRow() *sql.Row {
method QueryRows (line 75) | func (s *Session) QueryRows() (rows *sql.Rows, err error) {
method Raw (line 85) | func (s *Session) Raw(sql string, values ...interface{}) *Session {
function New (line 25) | func New(db *sql.DB, dialect dialect.Dialect) *Session {
type CommonDB (line 40) | type CommonDB interface
FILE: gee-orm/day7-migrate/session/raw_test.go
function TestMain (line 18) | func TestMain(m *testing.M) {
function NewSession (line 25) | func NewSession() *Session {
function TestSession_Exec (line 29) | func TestSession_Exec(t *testing.T) {
function TestSession_QueryRows (line 39) | func TestSession_QueryRows(t *testing.T) {
FILE: gee-orm/day7-migrate/session/record.go
method Insert (line 10) | func (s *Session) Insert(values ...interface{}) (int64, error) {
method Find (line 30) | func (s *Session) Find(values interface{}) error {
method First (line 59) | func (s *Session) First(value interface{}) error {
method Limit (line 73) | func (s *Session) Limit(num int) *Session {
method Where (line 79) | func (s *Session) Where(desc string, args ...interface{}) *Session {
method OrderBy (line 86) | func (s *Session) OrderBy(desc string) *Session {
method Update (line 94) | func (s *Session) Update(kv ...interface{}) (int64, error) {
method Delete (line 114) | func (s *Session) Delete() (int64, error) {
method Count (line 127) | func (s *Session) Count() (int64, error) {
FILE: gee-orm/day7-migrate/session/record_test.go
function testRecordInit (line 11) | func testRecordInit(t *testing.T) *Session {
function TestSession_Insert (line 23) | func TestSession_Insert(t *testing.T) {
function TestSession_Find (line 31) | func TestSession_Find(t *testing.T) {
function TestSession_First (line 39) | func TestSession_First(t *testing.T) {
function TestSession_Limit (line 48) | func TestSession_Limit(t *testing.T) {
function TestSession_Where (line 57) | func TestSession_Where(t *testing.T) {
function TestSession_OrderBy (line 68) | func TestSession_OrderBy(t *testing.T) {
function TestSession_Update (line 78) | func TestSession_Update(t *testing.T) {
function TestSession_DeleteAndCount (line 89) | func TestSession_DeleteAndCount(t *testing.T) {
FILE: gee-orm/day7-migrate/session/table.go
method Model (line 13) | func (s *Session) Model(value interface{}) *Session {
method RefTable (line 22) | func (s *Session) RefTable() *schema.Schema {
method CreateTable (line 30) | func (s *Session) CreateTable() error {
method DropTable (line 42) | func (s *Session) DropTable() error {
method HasTable (line 48) | func (s *Session) HasTable() bool {
FILE: gee-orm/day7-migrate/session/table_test.go
type User (line 7) | type User struct
function TestSession_CreateTable (line 12) | func TestSession_CreateTable(t *testing.T) {
function TestSession_Model (line 21) | func TestSession_Model(t *testing.T) {
FILE: gee-orm/day7-migrate/session/transaction.go
method Begin (line 6) | func (s *Session) Begin() (err error) {
method Commit (line 16) | func (s *Session) Commit() (err error) {
method Rollback (line 25) | func (s *Session) Rollback() (err error) {
FILE: gee-rpc/day1-codec/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day1-codec/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day1-codec/main/main.go
function startServer (line 13) | func startServer(addr chan string) {
function main (line 24) | func main() {
FILE: gee-rpc/day1-codec/server.go
constant MagicNumber (line 18) | MagicNumber = 0x3bef5c
type Option (line 20) | type Option struct
type Server (line 31) | type Server struct
method ServeConn (line 43) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 65) | func (server *Server) serveCodec(cc codec.Codec) {
method readRequestHeader (line 91) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method readRequest (line 102) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 117) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 125) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 136) | func (server *Server) Accept(lis net.Listener) {
function NewServer (line 34) | func NewServer() *Server {
type request (line 86) | type request struct
function Accept (line 149) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
FILE: gee-rpc/day2-client/client.go
type Call (line 19) | type Call struct
method done (line 28) | func (call *Call) done() {
type Client (line 36) | type Client struct
method Close (line 53) | func (client *Client) Close() error {
method IsAvailable (line 64) | func (client *Client) IsAvailable() bool {
method registerCall (line 70) | func (client *Client) registerCall(call *Call) (uint64, error) {
method removeCall (line 82) | func (client *Client) removeCall(seq uint64) *Call {
method terminateCalls (line 90) | func (client *Client) terminateCalls(err error) {
method send (line 102) | func (client *Client) send(call *Call) {
method receive (line 132) | func (client *Client) receive() {
method Go (line 163) | func (client *Client) Go(serviceMethod string, args, reply interface{}...
method Call (line 181) | func (client *Client) Call(serviceMethod string, args, reply interface...
function parseOptions (line 186) | func parseOptions(opts ...*Option) (*Option, error) {
function NewClient (line 202) | func NewClient(conn net.Conn, opt *Option) (*Client, error) {
function newClientCodec (line 218) | func newClientCodec(cc codec.Codec, opt *Option) *Client {
function Dial (line 230) | func Dial(network, address string, opts ...*Option) (client *Client, err...
FILE: gee-rpc/day2-client/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day2-client/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day2-client/main/main.go
function startServer (line 12) | func startServer(addr chan string) {
function main (line 23) | func main() {
FILE: gee-rpc/day2-client/server.go
constant MagicNumber (line 18) | MagicNumber = 0x3bef5c
type Option (line 20) | type Option struct
type Server (line 31) | type Server struct
method ServeConn (line 43) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 65) | func (server *Server) serveCodec(cc codec.Codec) {
method readRequestHeader (line 91) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method readRequest (line 102) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 117) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 125) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 136) | func (server *Server) Accept(lis net.Listener) {
function NewServer (line 34) | func NewServer() *Server {
type request (line 86) | type request struct
function Accept (line 149) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
FILE: gee-rpc/day3-service/client.go
type Call (line 19) | type Call struct
method done (line 28) | func (call *Call) done() {
type Client (line 36) | type Client struct
method Close (line 53) | func (client *Client) Close() error {
method IsAvailable (line 64) | func (client *Client) IsAvailable() bool {
method registerCall (line 70) | func (client *Client) registerCall(call *Call) (uint64, error) {
method removeCall (line 82) | func (client *Client) removeCall(seq uint64) *Call {
method terminateCalls (line 90) | func (client *Client) terminateCalls(err error) {
method send (line 102) | func (client *Client) send(call *Call) {
method receive (line 132) | func (client *Client) receive() {
method Go (line 163) | func (client *Client) Go(serviceMethod string, args, reply interface{}...
method Call (line 181) | func (client *Client) Call(serviceMethod string, args, reply interface...
function parseOptions (line 186) | func parseOptions(opts ...*Option) (*Option, error) {
function NewClient (line 202) | func NewClient(conn net.Conn, opt *Option) (*Client, error) {
function newClientCodec (line 217) | func newClientCodec(cc codec.Codec, opt *Option) *Client {
function Dial (line 229) | func Dial(network, address string, opts ...*Option) (client *Client, err...
FILE: gee-rpc/day3-service/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day3-service/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day3-service/main/main.go
type Foo (line 11) | type Foo
method Sum (line 15) | func (f Foo) Sum(args Args, reply *int) error {
type Args (line 13) | type Args struct
function startServer (line 20) | func startServer(addr chan string) {
function main (line 35) | func main() {
FILE: gee-rpc/day3-service/server.go
constant MagicNumber (line 19) | MagicNumber = 0x3bef5c
type Option (line 21) | type Option struct
type Server (line 32) | type Server struct
method ServeConn (line 46) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 68) | func (server *Server) serveCodec(cc codec.Codec) {
method readRequestHeader (line 96) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method findService (line 107) | func (server *Server) findService(serviceMethod string) (svc *service,...
method readRequest (line 127) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 152) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 160) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 173) | func (server *Server) Accept(lis net.Listener) {
method Register (line 194) | func (server *Server) Register(rcvr interface{}) error {
function NewServer (line 37) | func NewServer() *Server {
type request (line 89) | type request struct
function Accept (line 186) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
function Register (line 203) | func Register(rcvr interface{}) error { return DefaultServer.Register(rc...
FILE: gee-rpc/day3-service/service.go
type methodType (line 10) | type methodType struct
method NumCalls (line 17) | func (m *methodType) NumCalls() uint64 {
method newArgv (line 21) | func (m *methodType) newArgv() reflect.Value {
method newReplyv (line 32) | func (m *methodType) newReplyv() reflect.Value {
type service (line 44) | type service struct
method registerMethods (line 63) | func (s *service) registerMethods() {
method call (line 87) | func (s *service) call(m *methodType, argv, replyv reflect.Value) error {
function newService (line 51) | func newService(rcvr interface{}) *service {
function isExportedOrBuiltinType (line 97) | func isExportedOrBuiltinType(t reflect.Type) bool {
FILE: gee-rpc/day3-service/service_test.go
type Foo (line 9) | type Foo
method Sum (line 13) | func (f Foo) Sum(args Args, reply *int) error {
method sum (line 19) | func (f Foo) sum(args Args, reply *int) error {
type Args (line 11) | type Args struct
function _assert (line 24) | func _assert(condition bool, msg string, v ...interface{}) {
function TestNewService (line 30) | func TestNewService(t *testing.T) {
function TestMethodType_Call (line 38) | func TestMethodType_Call(t *testing.T) {
FILE: gee-rpc/day4-timeout/client.go
type Call (line 21) | type Call struct
method done (line 30) | func (call *Call) done() {
type Client (line 38) | type Client struct
method Close (line 55) | func (client *Client) Close() error {
method IsAvailable (line 66) | func (client *Client) IsAvailable() bool {
method registerCall (line 72) | func (client *Client) registerCall(call *Call) (uint64, error) {
method removeCall (line 84) | func (client *Client) removeCall(seq uint64) *Call {
method terminateCalls (line 92) | func (client *Client) terminateCalls(err error) {
method send (line 104) | func (client *Client) send(call *Call) {
method receive (line 134) | func (client *Client) receive() {
method Go (line 165) | func (client *Client) Go(serviceMethod string, args, reply interface{}...
method Call (line 183) | func (client *Client) Call(ctx context.Context, serviceMethod string, ...
function parseOptions (line 194) | func parseOptions(opts ...*Option) (*Option, error) {
function NewClient (line 210) | func NewClient(conn net.Conn, opt *Option) (client *Client, err error) {
function newClientCodec (line 225) | func newClientCodec(cc codec.Codec, opt *Option) *Client {
type clientResult (line 236) | type clientResult struct
type newClientFunc (line 241) | type newClientFunc
function dialTimeout (line 243) | func dialTimeout(f newClientFunc, network, address string, opts ...*Opti...
function Dial (line 276) | func Dial(network, address string, opts ...*Option) (*Client, error) {
FILE: gee-rpc/day4-timeout/client_test.go
type Bar (line 11) | type Bar
method Timeout (line 13) | func (b Bar) Timeout(argv int, reply *int) error {
function startServer (line 18) | func startServer(addr chan string) {
function TestClient_dialTimeout (line 27) | func TestClient_dialTimeout(t *testing.T) {
function TestClient_Call (line 46) | func TestClient_Call(t *testing.T) {
FILE: gee-rpc/day4-timeout/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day4-timeout/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day4-timeout/main/main.go
type Foo (line 12) | type Foo
method Sum (line 16) | func (f Foo) Sum(args Args, reply *int) error {
type Args (line 14) | type Args struct
function startServer (line 21) | func startServer(addr chan string) {
function main (line 36) | func main() {
FILE: gee-rpc/day4-timeout/server.go
constant MagicNumber (line 21) | MagicNumber = 0x3bef5c
type Option (line 23) | type Option struct
type Server (line 37) | type Server struct
method ServeConn (line 51) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 73) | func (server *Server) serveCodec(cc codec.Codec, opt *Option) {
method readRequestHeader (line 101) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method findService (line 112) | func (server *Server) findService(serviceMethod string) (svc *service,...
method readRequest (line 132) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 157) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 165) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 198) | func (server *Server) Accept(lis net.Listener) {
method Register (line 219) | func (server *Server) Register(rcvr interface{}) error {
function NewServer (line 42) | func NewServer() *Server {
type request (line 94) | type request struct
function Accept (line 211) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
function Register (line 228) | func Register(rcvr interface{}) error { return DefaultServer.Register(rc...
FILE: gee-rpc/day4-timeout/service.go
type methodType (line 10) | type methodType struct
method NumCalls (line 17) | func (m *methodType) NumCalls() uint64 {
method newArgv (line 21) | func (m *methodType) newArgv() reflect.Value {
method newReplyv (line 32) | func (m *methodType) newReplyv() reflect.Value {
type service (line 44) | type service struct
method registerMethods (line 63) | func (s *service) registerMethods() {
method call (line 87) | func (s *service) call(m *methodType, argv, replyv reflect.Value) error {
function newService (line 51) | func newService(rcvr interface{}) *service {
function isExportedOrBuiltinType (line 97) | func isExportedOrBuiltinType(t reflect.Type) bool {
FILE: gee-rpc/day4-timeout/service_test.go
type Foo (line 9) | type Foo
method Sum (line 13) | func (f Foo) Sum(args Args, reply *int) error {
method sum (line 19) | func (f Foo) sum(args Args, reply *int) error {
type Args (line 11) | type Args struct
function _assert (line 24) | func _assert(condition bool, msg string, v ...interface{}) {
function TestNewService (line 30) | func TestNewService(t *testing.T) {
function TestMethodType_Call (line 38) | func TestMethodType_Call(t *testing.T) {
FILE: gee-rpc/day5-http-debug/client.go
type Call (line 24) | type Call struct
method done (line 33) | func (call *Call) done() {
type Client (line 41) | type Client struct
method Close (line 58) | func (client *Client) Close() error {
method IsAvailable (line 69) | func (client *Client) IsAvailable() bool {
method registerCall (line 75) | func (client *Client) registerCall(call *Call) (uint64, error) {
method removeCall (line 87) | func (client *Client) removeCall(seq uint64) *Call {
method terminateCalls (line 95) | func (client *Client) terminateCalls(err error) {
method send (line 107) | func (client *Client) send(call *Call) {
method receive (line 137) | func (client *Client) receive() {
method Go (line 168) | func (client *Client) Go(serviceMethod string, args, reply interface{}...
method Call (line 186) | func (client *Client) Call(ctx context.Context, serviceMethod string, ...
function parseOptions (line 197) | func parseOptions(opts ...*Option) (*Option, error) {
function NewClient (line 213) | func NewClient(conn net.Conn, opt *Option) (*Client, error) {
function newClientCodec (line 229) | func newClientCodec(cc codec.Codec, opt *Option) *Client {
type clientResult (line 240) | type clientResult struct
type newClientFunc (line 245) | type newClientFunc
function dialTimeout (line 247) | func dialTimeout(f newClientFunc, network, address string, opts ...*Opti...
function Dial (line 280) | func Dial(network, address string, opts ...*Option) (*Client, error) {
function NewHTTPClient (line 285) | func NewHTTPClient(conn net.Conn, opt *Option) (*Client, error) {
function DialHTTP (line 302) | func DialHTTP(network, address string, opts ...*Option) (*Client, error) {
function XDial (line 310) | func XDial(rpcAddr string, opts ...*Option) (*Client, error) {
FILE: gee-rpc/day5-http-debug/client_test.go
type Bar (line 13) | type Bar
method Timeout (line 15) | func (b Bar) Timeout(argv int, reply *int) error {
function startServer (line 20) | func startServer(addr chan string) {
function TestClient_dialTimeout (line 29) | func TestClient_dialTimeout(t *testing.T) {
function TestClient_Call (line 48) | func TestClient_Call(t *testing.T) {
function TestXDial (line 71) | func TestXDial(t *testing.T) {
FILE: gee-rpc/day5-http-debug/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day5-http-debug/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day5-http-debug/debug.go
constant debugText (line 13) | debugText = `<html>
type debugHTTP (line 35) | type debugHTTP struct
method ServeHTTP (line 45) | func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Req...
type debugService (line 39) | type debugService struct
FILE: gee-rpc/day5-http-debug/main/main.go
type Foo (line 13) | type Foo
method Sum (line 17) | func (f Foo) Sum(args Args, reply *int) error {
type Args (line 15) | type Args struct
function startServer (line 22) | func startServer(addrCh chan string) {
function call (line 31) | func call(addrCh chan string) {
function main (line 53) | func main() {
FILE: gee-rpc/day5-http-debug/server.go
constant MagicNumber (line 22) | MagicNumber = 0x3bef5c
type Option (line 24) | type Option struct
type Server (line 38) | type Server struct
method ServeConn (line 52) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 74) | func (server *Server) serveCodec(cc codec.Codec, opt *Option) {
method readRequestHeader (line 102) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method findService (line 113) | func (server *Server) findService(serviceMethod string) (svc *service,...
method readRequest (line 133) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 158) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 166) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 199) | func (server *Server) Accept(lis net.Listener) {
method Register (line 220) | func (server *Server) Register(rcvr interface{}) error {
method ServeHTTP (line 238) | func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Reque...
method HandleHTTP (line 257) | func (server *Server) HandleHTTP() {
function NewServer (line 43) | func NewServer() *Server {
type request (line 95) | type request struct
function Accept (line 212) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
function Register (line 229) | func Register(rcvr interface{}) error { return DefaultServer.Register(rc...
constant connected (line 232) | connected = "200 Connected to Gee RPC"
constant defaultRPCPath (line 233) | defaultRPCPath = "/_geeprc_"
constant defaultDebugPath (line 234) | defaultDebugPath = "/debug/geerpc"
function HandleHTTP (line 264) | func HandleHTTP() {
FILE: gee-rpc/day5-http-debug/service.go
type methodType (line 10) | type methodType struct
method NumCalls (line 17) | func (m *methodType) NumCalls() uint64 {
method newArgv (line 21) | func (m *methodType) newArgv() reflect.Value {
method newReplyv (line 32) | func (m *methodType) newReplyv() reflect.Value {
type service (line 44) | type service struct
method registerMethods (line 63) | func (s *service) registerMethods() {
method call (line 87) | func (s *service) call(m *methodType, argv, replyv reflect.Value) error {
function newService (line 51) | func newService(rcvr interface{}) *service {
function isExportedOrBuiltinType (line 97) | func isExportedOrBuiltinType(t reflect.Type) bool {
FILE: gee-rpc/day5-http-debug/service_test.go
type Foo (line 9) | type Foo
method Sum (line 13) | func (f Foo) Sum(args Args, reply *int) error {
method sum (line 19) | func (f Foo) sum(args Args, reply *int) error {
type Args (line 11) | type Args struct
function _assert (line 24) | func _assert(condition bool, msg string, v ...interface{}) {
function TestNewService (line 30) | func TestNewService(t *testing.T) {
function TestMethodType_Call (line 38) | func TestMethodType_Call(t *testing.T) {
FILE: gee-rpc/day6-load-balance/client.go
type Call (line 24) | type Call struct
method done (line 33) | func (call *Call) done() {
type Client (line 41) | type Client struct
method Close (line 58) | func (client *Client) Close() error {
method IsAvailable (line 69) | func (client *Client) IsAvailable() bool {
method registerCall (line 75) | func (client *Client) registerCall(call *Call) (uint64, error) {
method removeCall (line 87) | func (client *Client) removeCall(seq uint64) *Call {
method terminateCalls (line 95) | func (client *Client) terminateCalls(err error) {
method send (line 107) | func (client *Client) send(call *Call) {
method receive (line 137) | func (client *Client) receive() {
method Go (line 168) | func (client *Client) Go(serviceMethod string, args, reply interface{}...
method Call (line 186) | func (client *Client) Call(ctx context.Context, serviceMethod string, ...
function parseOptions (line 197) | func parseOptions(opts ...*Option) (*Option, error) {
function NewClient (line 213) | func NewClient(conn net.Conn, opt *Option) (*Client, error) {
function newClientCodec (line 229) | func newClientCodec(cc codec.Codec, opt *Option) *Client {
type clientResult (line 240) | type clientResult struct
type newClientFunc (line 245) | type newClientFunc
function dialTimeout (line 247) | func dialTimeout(f newClientFunc, network, address string, opts ...*Opti...
function Dial (line 280) | func Dial(network, address string, opts ...*Option) (*Client, error) {
function NewHTTPClient (line 285) | func NewHTTPClient(conn net.Conn, opt *Option) (*Client, error) {
function DialHTTP (line 302) | func DialHTTP(network, address string, opts ...*Option) (*Client, error) {
function XDial (line 310) | func XDial(rpcAddr string, opts ...*Option) (*Client, error) {
FILE: gee-rpc/day6-load-balance/client_test.go
type Bar (line 13) | type Bar
method Timeout (line 15) | func (b Bar) Timeout(argv int, reply *int) error {
function startServer (line 20) | func startServer(addr chan string) {
function TestClient_dialTimeout (line 29) | func TestClient_dialTimeout(t *testing.T) {
function TestClient_Call (line 48) | func TestClient_Call(t *testing.T) {
function TestXDial (line 71) | func TestXDial(t *testing.T) {
FILE: gee-rpc/day6-load-balance/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day6-load-balance/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day6-load-balance/debug.go
constant debugText (line 13) | debugText = `<html>
type debugHTTP (line 35) | type debugHTTP struct
method ServeHTTP (line 45) | func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Req...
type debugService (line 39) | type debugService struct
FILE: gee-rpc/day6-load-balance/main/main.go
type Foo (line 13) | type Foo
method Sum (line 17) | func (f Foo) Sum(args Args, reply *int) error {
method Sleep (line 22) | func (f Foo) Sleep(args Args, reply *int) error {
type Args (line 15) | type Args struct
function startServer (line 28) | func startServer(addrCh chan string) {
function foo (line 37) | func foo(xc *xclient.XClient, ctx context.Context, typ, serviceMethod st...
function call (line 53) | func call(addr1, addr2 string) {
function broadcast (line 69) | func broadcast(addr1, addr2 string) {
function main (line 87) | func main() {
FILE: gee-rpc/day6-load-balance/server.go
constant MagicNumber (line 22) | MagicNumber = 0x3bef5c
type Option (line 24) | type Option struct
type Server (line 38) | type Server struct
method ServeConn (line 52) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 74) | func (server *Server) serveCodec(cc codec.Codec, opt *Option) {
method readRequestHeader (line 102) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method findService (line 113) | func (server *Server) findService(serviceMethod string) (svc *service,...
method readRequest (line 133) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 158) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 166) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 199) | func (server *Server) Accept(lis net.Listener) {
method Register (line 220) | func (server *Server) Register(rcvr interface{}) error {
method ServeHTTP (line 238) | func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Reque...
method HandleHTTP (line 257) | func (server *Server) HandleHTTP() {
function NewServer (line 43) | func NewServer() *Server {
type request (line 95) | type request struct
function Accept (line 212) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
function Register (line 229) | func Register(rcvr interface{}) error { return DefaultServer.Register(rc...
constant connected (line 232) | connected = "200 Connected to Gee RPC"
constant defaultRPCPath (line 233) | defaultRPCPath = "/_geeprc_"
constant defaultDebugPath (line 234) | defaultDebugPath = "/debug/geerpc"
function HandleHTTP (line 264) | func HandleHTTP() {
FILE: gee-rpc/day6-load-balance/service.go
type methodType (line 10) | type methodType struct
method NumCalls (line 17) | func (m *methodType) NumCalls() uint64 {
method newArgv (line 21) | func (m *methodType) newArgv() reflect.Value {
method newReplyv (line 32) | func (m *methodType) newReplyv() reflect.Value {
type service (line 44) | type service struct
method registerMethods (line 63) | func (s *service) registerMethods() {
method call (line 87) | func (s *service) call(m *methodType, argv, replyv reflect.Value) error {
function newService (line 51) | func newService(rcvr interface{}) *service {
function isExportedOrBuiltinType (line 97) | func isExportedOrBuiltinType(t reflect.Type) bool {
FILE: gee-rpc/day6-load-balance/service_test.go
type Foo (line 9) | type Foo
method Sum (line 13) | func (f Foo) Sum(args Args, reply *int) error {
method sum (line 19) | func (f Foo) sum(args Args, reply *int) error {
type Args (line 11) | type Args struct
function _assert (line 24) | func _assert(condition bool, msg string, v ...interface{}) {
function TestNewService (line 30) | func TestNewService(t *testing.T) {
function TestMethodType_Call (line 38) | func TestMethodType_Call(t *testing.T) {
FILE: gee-rpc/day6-load-balance/xclient/discovery.go
type SelectMode (line 11) | type SelectMode
constant RandomSelect (line 14) | RandomSelect SelectMode = iota
constant RoundRobinSelect (line 15) | RoundRobinSelect
type Discovery (line 18) | type Discovery interface
type MultiServersDiscovery (line 29) | type MultiServersDiscovery struct
method Refresh (line 37) | func (d *MultiServersDiscovery) Refresh() error {
method Update (line 42) | func (d *MultiServersDiscovery) Update(servers []string) error {
method Get (line 50) | func (d *MultiServersDiscovery) Get(mode SelectMode) (string, error) {
method GetAll (line 70) | func (d *MultiServersDiscovery) GetAll() ([]string, error) {
function NewMultiServerDiscovery (line 80) | func NewMultiServerDiscovery(servers []string) *MultiServersDiscovery {
FILE: gee-rpc/day6-load-balance/xclient/xclient.go
type XClient (line 11) | type XClient struct
method Close (line 25) | func (xc *XClient) Close() error {
method dial (line 36) | func (xc *XClient) dial(rpcAddr string) (*Client, error) {
method call (line 56) | func (xc *XClient) call(rpcAddr string, ctx context.Context, serviceMe...
method Call (line 67) | func (xc *XClient) Call(ctx context.Context, serviceMethod string, arg...
method Broadcast (line 76) | func (xc *XClient) Broadcast(ctx context.Context, serviceMethod string...
function NewXClient (line 21) | func NewXClient(d Discovery, mode SelectMode, opt *Option) *XClient {
FILE: gee-rpc/day7-registry/client.go
type Call (line 24) | type Call struct
method done (line 33) | func (call *Call) done() {
type Client (line 41) | type Client struct
method Close (line 58) | func (client *Client) Close() error {
method IsAvailable (line 69) | func (client *Client) IsAvailable() bool {
method registerCall (line 75) | func (client *Client) registerCall(call *Call) (uint64, error) {
method removeCall (line 87) | func (client *Client) removeCall(seq uint64) *Call {
method terminateCalls (line 95) | func (client *Client) terminateCalls(err error) {
method send (line 107) | func (client *Client) send(call *Call) {
method receive (line 137) | func (client *Client) receive() {
method Go (line 168) | func (client *Client) Go(serviceMethod string, args, reply interface{}...
method Call (line 186) | func (client *Client) Call(ctx context.Context, serviceMethod string, ...
function parseOptions (line 197) | func parseOptions(opts ...*Option) (*Option, error) {
function NewClient (line 213) | func NewClient(conn net.Conn, opt *Option) (*Client, error) {
function newClientCodec (line 229) | func newClientCodec(cc codec.Codec, opt *Option) *Client {
type clientResult (line 240) | type clientResult struct
type newClientFunc (line 245) | type newClientFunc
function dialTimeout (line 247) | func dialTimeout(f newClientFunc, network, address string, opts ...*Opti...
function Dial (line 280) | func Dial(network, address string, opts ...*Option) (*Client, error) {
function NewHTTPClient (line 285) | func NewHTTPClient(conn net.Conn, opt *Option) (*Client, error) {
function DialHTTP (line 302) | func DialHTTP(network, address string, opts ...*Option) (*Client, error) {
function XDial (line 310) | func XDial(rpcAddr string, opts ...*Option) (*Client, error) {
FILE: gee-rpc/day7-registry/client_test.go
type Bar (line 13) | type Bar
method Timeout (line 15) | func (b Bar) Timeout(argv int, reply *int) error {
function startServer (line 20) | func startServer(addr chan string) {
function TestClient_dialTimeout (line 29) | func TestClient_dialTimeout(t *testing.T) {
function TestClient_Call (line 48) | func TestClient_Call(t *testing.T) {
function TestXDial (line 71) | func TestXDial(t *testing.T) {
FILE: gee-rpc/day7-registry/codec/codec.go
type Header (line 7) | type Header struct
type Codec (line 13) | type Codec interface
type NewCodecFunc (line 20) | type NewCodecFunc
type Type (line 22) | type Type
constant GobType (line 25) | GobType Type = "application/gob"
constant JsonType (line 26) | JsonType Type = "application/json"
function init (line 31) | func init() {
FILE: gee-rpc/day7-registry/codec/gob.go
type GobCodec (line 10) | type GobCodec struct
method ReadHeader (line 29) | func (c *GobCodec) ReadHeader(h *Header) error {
method ReadBody (line 33) | func (c *GobCodec) ReadBody(body interface{}) error {
method Write (line 37) | func (c *GobCodec) Write(h *Header, body interface{}) (err error) {
method Close (line 55) | func (c *GobCodec) Close() error {
function NewGobCodec (line 19) | func NewGobCodec(conn io.ReadWriteCloser) Codec {
FILE: gee-rpc/day7-registry/debug.go
constant debugText (line 13) | debugText = `<html>
type debugHTTP (line 35) | type debugHTTP struct
method ServeHTTP (line 45) | func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Req...
type debugService (line 39) | type debugService struct
FILE: gee-rpc/day7-registry/main/main.go
type Foo (line 15) | type Foo
method Sum (line 19) | func (f Foo) Sum(args Args, reply *int) error {
method Sleep (line 24) | func (f Foo) Sleep(args Args, reply *int) error {
type Args (line 17) | type Args struct
function startRegistry (line 30) | func startRegistry(wg *sync.WaitGroup) {
function startServer (line 37) | func startServer(registryAddr string, wg *sync.WaitGroup) {
function foo (line 47) | func foo(xc *xclient.XClient, ctx context.Context, typ, serviceMethod st...
function call (line 63) | func call(registry string) {
function broadcast (line 79) | func broadcast(registry string) {
function main (line 97) | func main() {
FILE: gee-rpc/day7-registry/registry/registry.go
type GeeRegistry (line 15) | type GeeRegistry struct
method putServer (line 41) | func (r *GeeRegistry) putServer(addr string) {
method aliveServers (line 52) | func (r *GeeRegistry) aliveServers() []string {
method ServeHTTP (line 68) | func (r *GeeRegistry) ServeHTTP(w http.ResponseWriter, req *http.Reque...
method HandleHTTP (line 87) | func (r *GeeRegistry) HandleHTTP(registryPath string) {
type ServerItem (line 21) | type ServerItem struct
constant defaultPath (line 27) | defaultPath = "/_geerpc_/registry"
constant defaultTimeout (line 28) | defaultTimeout = time.Minute * 5
function New (line 32) | func New(timeout time.Duration) *GeeRegistry {
function HandleHTTP (line 92) | func HandleHTTP() {
function Heartbeat (line 98) | func Heartbeat(registry, addr string, duration time.Duration) {
function sendHeartbeat (line 115) | func sendHeartbeat(registry, addr string) error {
FILE: gee-rpc/day7-registry/server.go
constant MagicNumber (line 22) | MagicNumber = 0x3bef5c
type Option (line 24) | type Option struct
type Server (line 38) | type Server struct
method ServeConn (line 52) | func (server *Server) ServeConn(conn io.ReadWriteCloser) {
method serveCodec (line 74) | func (server *Server) serveCodec(cc codec.Codec, opt *Option) {
method readRequestHeader (line 102) | func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header...
method findService (line 113) | func (server *Server) findService(serviceMethod string) (svc *service,...
method readRequest (line 133) | func (server *Server) readRequest(cc codec.Codec) (*request, error) {
method sendResponse (line 158) | func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, bo...
method handleRequest (line 166) | func (server *Server) handleRequest(cc codec.Codec, req *request, send...
method Accept (line 199) | func (server *Server) Accept(lis net.Listener) {
method Register (line 220) | func (server *Server) Register(rcvr interface{}) error {
method ServeHTTP (line 238) | func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Reque...
method HandleHTTP (line 257) | func (server *Server) HandleHTTP() {
function NewServer (line 43) | func NewServer() *Server {
type request (line 95) | type request struct
function Accept (line 212) | func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
function Register (line 229) | func Register(rcvr interface{}) error { return DefaultServer.Register(rc...
constant connected (line 232) | connected = "200 Connected to Gee RPC"
constant defaultRPCPath (line 233) | defaultRPCPath = "/_geeprc_"
constant defaultDebugPath (line 234) | defaultDebugPath = "/debug/geerpc"
function HandleHTTP (line 264) | func HandleHTTP() {
FILE: gee-rpc/day7-registry/service.go
type methodType (line 10) | type methodType struct
method NumCalls (line 17) | func (m *methodType) NumCalls() uint64 {
method newArgv (line 21) | func (m *methodType) newArgv() reflect.Value {
method newReplyv (line 32) | func (m *methodType) newReplyv() reflect.Value {
type service (line 44) | type service struct
method registerMethods (line 63) | func (s *service) registerMethods() {
method call (line 87) | func (s *service) call(m *methodType, argv, replyv reflect.Value) error {
function newService (line 51) | func newService(rcvr interface{}) *service {
function isExportedOrBuiltinType (line 97) | func isExportedOrBuiltinType(t reflect.Type) bool {
FILE: gee-rpc/day7-registry/service_test.go
type Foo (line 9) | type Foo
method Sum (line 13) | func (f Foo) Sum(args Args, reply *int) error {
method sum (line 19) | func (f Foo) sum(args Args, reply *int) error {
type Args (line 11) | type Args struct
function _assert (line 24) | func _assert(condition bool, msg string, v ...interface{}) {
function TestNewService (line 30) | func TestNewService(t *testing.T) {
function TestMethodType_Call (line 38) | func TestMethodType_Call(t *testing.T) {
FILE: gee-rpc/day7-registry/xclient/discovery.go
type SelectMode (line 11) | type SelectMode
constant RandomSelect (line 14) | RandomSelect SelectMode = iota
constant RoundRobinSelect (line 15) | RoundRobinSelect
type Discovery (line 18) | type Discovery interface
type MultiServersDiscovery (line 29) | type MultiServersDiscovery struct
method Refresh (line 37) | func (d *MultiServersDiscovery) Refresh() error {
method Update (line 42) | func (d *MultiServersDiscovery) Update(servers []string) error {
method Get (line 50) | func (d *MultiServersDiscovery) Get(mode SelectMode) (string, error) {
method GetAll (line 70) | func (d *MultiServersDiscovery) GetAll() ([]string, error) {
function NewMultiServerDiscovery (line 80) | func NewMultiServerDiscovery(servers []string) *MultiServersDiscovery {
FILE: gee-rpc/day7-registry/xclient/discovery_gee.go
type GeeRegistryDiscovery (line 10) | type GeeRegistryDiscovery struct
method Update (line 19) | func (d *GeeRegistryDiscovery) Update(servers []string) error {
method Refresh (line 27) | func (d *GeeRegistryDiscovery) Refresh() error {
method Get (line 50) | func (d *GeeRegistryDiscovery) Get(mode SelectMode) (string, error) {
method GetAll (line 57) | func (d *GeeRegistryDiscovery) GetAll() ([]string, error) {
constant defaultUpdateTimeout (line 17) | defaultUpdateTimeout = time.Second * 10
function NewGeeRegistryDiscovery (line 64) | func NewGeeRegistryDiscovery(registerAddr string, timeout time.Duration)...
FILE: gee-rpc/day7-registry/xclient/xclient.go
type XClient (line 11) | type XClient struct
method Close (line 25) | func (xc *XClient) Close() error {
method dial (line 36) | func (xc *XClient) dial(rpcAddr string) (*Client, error) {
method call (line 56) | func (xc *XClient) call(rpcAddr string, ctx context.Context, serviceMe...
method Call (line 67) | func (xc *XClient) Call(ctx context.Context, serviceMethod string, arg...
method Broadcast (line 76) | func (xc *XClient) Broadcast(ctx context.Context, serviceMethod string...
function NewXClient (line 21) | func NewXClient(d Discovery, mode SelectMode, opt *Option) *XClient {
FILE: gee-web/day1-http-base/base1/main.go
function main (line 15) | func main() {
function indexHandler (line 22) | func indexHandler(w http.ResponseWriter, req *http.Request) {
function helloHandler (line 27) | func helloHandler(w http.ResponseWriter, req *http.Request) {
FILE: gee-web/day1-http-base/base2/main.go
type Engine (line 18) | type Engine struct
method ServeHTTP (line 20) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function main (line 33) | func main() {
FILE: gee-web/day1-http-base/base3/gee/gee.go
type HandlerFunc (line 10) | type HandlerFunc
type Engine (line 13) | type Engine struct
method addRoute (line 22) | func (engine *Engine) addRoute(method string, pattern string, handler ...
method GET (line 29) | func (engine *Engine) GET(pattern string, handler HandlerFunc) {
method POST (line 34) | func (engine *Engine) POST(pattern string, handler HandlerFunc) {
method Run (line 39) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 43) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 18) | func New() *Engine {
FILE: gee-web/day1-http-base/base3/main.go
function main (line 18) | func main() {
FILE: gee-web/day2-context/gee/context.go
type H (line 9) | type H
type Context (line 11) | type Context struct
method PostForm (line 31) | func (c *Context) PostForm(key string) string {
method Query (line 35) | func (c *Context) Query(key string) string {
method Status (line 39) | func (c *Context) Status(code int) {
method SetHeader (line 44) | func (c *Context) SetHeader(key string, value string) {
method String (line 48) | func (c *Context) String(code int, format string, values ...interface{...
method JSON (line 54) | func (c *Context) JSON(code int, obj interface{}) {
method Data (line 63) | func (c *Context) Data(code int, data []byte) {
method HTML (line 68) | func (c *Context) HTML(code int, html string) {
function newContext (line 22) | func newContext(w http.ResponseWriter, req *http.Request) *Context {
FILE: gee-web/day2-context/gee/gee.go
type HandlerFunc (line 9) | type HandlerFunc
type Engine (line 12) | type Engine struct
method addRoute (line 21) | func (engine *Engine) addRoute(method string, pattern string, handler ...
method GET (line 27) | func (engine *Engine) GET(pattern string, handler HandlerFunc) {
method POST (line 32) | func (engine *Engine) POST(pattern string, handler HandlerFunc) {
method Run (line 37) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 41) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 17) | func New() *Engine {
FILE: gee-web/day2-context/gee/router.go
type router (line 7) | type router struct
method addRoute (line 15) | func (r *router) addRoute(method string, pattern string, handler Handl...
method handle (line 20) | func (r *router) handle(c *Context) {
function newRouter (line 11) | func newRouter() *router {
FILE: gee-web/day2-context/main.go
function main (line 31) | func main() {
FILE: gee-web/day3-router/gee/context.go
type H (line 9) | type H
type Context (line 11) | type Context struct
method Param (line 32) | func (c *Context) Param(key string) string {
method PostForm (line 37) | func (c *Context) PostForm(key string) string {
method Query (line 41) | func (c *Context) Query(key string) string {
method Status (line 45) | func (c *Context) Status(code int) {
method SetHeader (line 50) | func (c *Context) SetHeader(key string, value string) {
method String (line 54) | func (c *Context) String(code int, format string, values ...interface{...
method JSON (line 60) | func (c *Context) JSON(code int, obj interface{}) {
method Data (line 69) | func (c *Context) Data(code int, data []byte) {
method HTML (line 74) | func (c *Context) HTML(code int, html string) {
function newContext (line 23) | func newContext(w http.ResponseWriter, req *http.Request) *Context {
FILE: gee-web/day3-router/gee/gee.go
type HandlerFunc (line 9) | type HandlerFunc
type Engine (line 12) | type Engine struct
method addRoute (line 21) | func (engine *Engine) addRoute(method string, pattern string, handler ...
method GET (line 27) | func (engine *Engine) GET(pattern string, handler HandlerFunc) {
method POST (line 32) | func (engine *Engine) POST(pattern string, handler HandlerFunc) {
method Run (line 37) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 41) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 17) | func New() *Engine {
FILE: gee-web/day3-router/gee/router.go
type router (line 8) | type router struct
method addRoute (line 36) | func (r *router) addRoute(method string, pattern string, handler Handl...
method getRoute (line 48) | func (r *router) getRoute(method string, path string) (*node, map[stri...
method getRoutes (line 76) | func (r *router) getRoutes(method string) []*node {
method handle (line 86) | func (r *router) handle(c *Context) {
function newRouter (line 13) | func newRouter() *router {
function parsePattern (line 21) | func parsePattern(pattern string) []string {
FILE: gee-web/day3-router/gee/router_test.go
function newTestRouter (line 9) | func newTestRouter() *router {
function TestParsePattern (line 19) | func TestParsePattern(t *testing.T) {
function TestGetRoute (line 28) | func TestGetRoute(t *testing.T) {
function TestGetRoute2 (line 48) | func TestGetRoute2(t *testing.T) {
function TestGetRoutes (line 64) | func TestGetRoutes(t *testing.T) {
FILE: gee-web/day3-router/gee/trie.go
type node (line 8) | type node struct
method String (line 15) | func (n *node) String() string {
method insert (line 19) | func (n *node) insert(pattern string, parts []string, height int) {
method search (line 34) | func (n *node) search(parts []string, height int) *node {
method travel (line 55) | func (n *node) travel(list *([]*node)) {
method matchChild (line 64) | func (n *node) matchChild(part string) *node {
method matchChildren (line 73) | func (n *node) matchChildren(part string) []*node {
FILE: gee-web/day3-router/main.go
function main (line 35) | func main() {
FILE: gee-web/day4-group/gee/context.go
type H (line 9) | type H
type Context (line 11) | type Context struct
method Param (line 32) | func (c *Context) Param(key string) string {
method PostForm (line 37) | func (c *Context) PostForm(key string) string {
method Query (line 41) | func (c *Context) Query(key string) string {
method Status (line 45) | func (c *Context) Status(code int) {
method SetHeader (line 50) | func (c *Context) SetHeader(key string, value string) {
method String (line 54) | func (c *Context) String(code int, format string, values ...interface{...
method JSON (line 60) | func (c *Context) JSON(code int, obj interface{}) {
method Data (line 69) | func (c *Context) Data(code int, data []byte) {
method HTML (line 74) | func (c *Context) HTML(code int, html string) {
function newContext (line 23) | func newContext(w http.ResponseWriter, req *http.Request) *Context {
FILE: gee-web/day4-group/gee/gee.go
type HandlerFunc (line 9) | type HandlerFunc
type RouterGroup (line 13) | type RouterGroup struct
method Group (line 37) | func (group *RouterGroup) Group(prefix string) *RouterGroup {
method addRoute (line 48) | func (group *RouterGroup) addRoute(method string, comp string, handler...
method GET (line 55) | func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
method POST (line 60) | func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
type Engine (line 20) | type Engine struct
method Run (line 65) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 69) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 28) | func New() *Engine {
FILE: gee-web/day4-group/gee/gee_test.go
function TestNestedGroup (line 5) | func TestNestedGroup(t *testing.T) {
FILE: gee-web/day4-group/gee/router.go
type router (line 8) | type router struct
method addRoute (line 36) | func (r *router) addRoute(method string, pattern string, handler Handl...
method getRoute (line 48) | func (r *router) getRoute(method string, path string) (*node, map[stri...
method getRoutes (line 76) | func (r *router) getRoutes(method string) []*node {
method handle (line 86) | func (r *router) handle(c *Context) {
function newRouter (line 13) | func newRouter() *router {
function parsePattern (line 21) | func parsePattern(pattern string) []string {
FILE: gee-web/day4-group/gee/router_test.go
function newTestRouter (line 9) | func newTestRouter() *router {
function TestParsePattern (line 19) | func TestParsePattern(t *testing.T) {
function TestGetRoute (line 28) | func TestGetRoute(t *testing.T) {
function TestGetRoute2 (line 48) | func TestGetRoute2(t *testing.T) {
function TestGetRoutes (line 64) | func TestGetRoutes(t *testing.T) {
FILE: gee-web/day4-group/gee/trie.go
type node (line 8) | type node struct
method String (line 15) | func (n *node) String() string {
method insert (line 19) | func (n *node) insert(pattern string, parts []string, height int) {
method search (line 34) | func (n *node) search(parts []string, height int) *node {
method travel (line 55) | func (n *node) travel(list *([]*node)) {
method matchChild (line 64) | func (n *node) matchChild(part string) *node {
method matchChildren (line 73) | func (n *node) matchChildren(part string) []*node {
FILE: gee-web/day4-group/main.go
function main (line 43) | func main() {
FILE: gee-web/day5-middleware/gee/context.go
type H (line 9) | type H
type Context (line 11) | type Context struct
method Next (line 36) | func (c *Context) Next() {
method Fail (line 44) | func (c *Context) Fail(code int, err string) {
method Param (line 49) | func (c *Context) Param(key string) string {
method PostForm (line 54) | func (c *Context) PostForm(key string) string {
method Query (line 58) | func (c *Context) Query(key string) string {
method Status (line 62) | func (c *Context) Status(code int) {
method SetHeader (line 67) | func (c *Context) SetHeader(key string, value string) {
method String (line 71) | func (c *Context) String(code int, format string, values ...interface{...
method JSON (line 77) | func (c *Context) JSON(code int, obj interface{}) {
method Data (line 86) | func (c *Context) Data(code int, data []byte) {
method HTML (line 91) | func (c *Context) HTML(code int, html string) {
function newContext (line 26) | func newContext(w http.ResponseWriter, req *http.Request) *Context {
FILE: gee-web/day5-middleware/gee/gee.go
type HandlerFunc (line 10) | type HandlerFunc
type RouterGroup (line 14) | type RouterGroup struct
method Group (line 38) | func (group *RouterGroup) Group(prefix string) *RouterGroup {
method Use (line 50) | func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
method addRoute (line 54) | func (group *RouterGroup) addRoute(method string, comp string, handler...
method GET (line 61) | func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
method POST (line 66) | func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
type Engine (line 21) | type Engine struct
method Run (line 71) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 75) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 29) | func New() *Engine {
FILE: gee-web/day5-middleware/gee/gee_test.go
function TestNestedGroup (line 5) | func TestNestedGroup(t *testing.T) {
FILE: gee-web/day5-middleware/gee/logger.go
function Logger (line 8) | func Logger() HandlerFunc {
FILE: gee-web/day5-middleware/gee/router.go
type router (line 8) | type router struct
method addRoute (line 36) | func (r *router) addRoute(method string, pattern string, handler Handl...
method getRoute (line 48) | func (r *router) getRoute(method string, path string) (*node, map[stri...
method getRoutes (line 76) | func (r *router) getRoutes(method string) []*node {
method handle (line 86) | func (r *router) handle(c *Context) {
function newRouter (line 13) | func newRouter() *router {
function parsePattern (line 21) | func parsePattern(pattern string) []string {
FILE: gee-web/day5-middleware/gee/router_test.go
function newTestRouter (line 9) | func newTestRouter() *router {
function TestParsePattern (line 19) | func TestParsePattern(t *testing.T) {
function TestGetRoute (line 28) | func TestGetRoute(t *testing.T) {
function TestGetRoute2 (line 48) | func TestGetRoute2(t *testing.T) {
function TestGetRoutes (line 64) | func TestGetRoutes(t *testing.T) {
FILE: gee-web/day5-middleware/gee/trie.go
type node (line 8) | type node struct
method String (line 15) | func (n *node) String() string {
method insert (line 19) | func (n *node) insert(pattern string, parts []string, height int) {
method search (line 34) | func (n *node) search(parts []string, height int) *node {
method travel (line 55) | func (n *node) travel(list *([]*node)) {
method matchChild (line 64) | func (n *node) matchChild(part string) *node {
method matchChildren (line 73) | func (n *node) matchChildren(part string) []*node {
FILE: gee-web/day5-middleware/main.go
function onlyForV2 (line 30) | func onlyForV2() gee.HandlerFunc {
function main (line 41) | func main() {
FILE: gee-web/day6-template/gee/context.go
type H (line 9) | type H
type Context (line 11) | type Context struct
method Next (line 38) | func (c *Context) Next() {
method Fail (line 46) | func (c *Context) Fail(code int, err string) {
method Param (line 51) | func (c *Context) Param(key string) string {
method PostForm (line 56) | func (c *Context) PostForm(key string) string {
method Query (line 60) | func (c *Context) Query(key string) string {
method Status (line 64) | func (c *Context) Status(code int) {
method SetHeader (line 69) | func (c *Context) SetHeader(key string, value string) {
method String (line 73) | func (c *Context) String(code int, format string, values ...interface{...
method JSON (line 79) | func (c *Context) JSON(code int, obj interface{}) {
method Data (line 88) | func (c *Context) Data(code int, data []byte) {
method HTML (line 95) | func (c *Context) HTML(code int, name string, data interface{}) {
function newContext (line 28) | func newContext(w http.ResponseWriter, req *http.Request) *Context {
FILE: gee-web/day6-template/gee/gee.go
type HandlerFunc (line 12) | type HandlerFunc
type RouterGroup (line 16) | type RouterGroup struct
method Group (line 42) | func (group *RouterGroup) Group(prefix string) *RouterGroup {
method Use (line 54) | func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
method addRoute (line 58) | func (group *RouterGroup) addRoute(method string, comp string, handler...
method GET (line 65) | func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
method POST (line 70) | func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
method createStaticHandler (line 75) | func (group *RouterGroup) createStaticHandler(relativePath string, fs ...
method Static (line 91) | func (group *RouterGroup) Static(relativePath string, root string) {
type Engine (line 23) | type Engine struct
method SetFuncMap (line 99) | func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
method LoadHTMLGlob (line 103) | func (engine *Engine) LoadHTMLGlob(pattern string) {
method Run (line 108) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 112) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 33) | func New() *Engine {
FILE: gee-web/day6-template/gee/gee_test.go
function TestNestedGroup (line 5) | func TestNestedGroup(t *testing.T) {
FILE: gee-web/day6-template/gee/logger.go
function Logger (line 8) | func Logger() HandlerFunc {
FILE: gee-web/day6-template/gee/router.go
type router (line 8) | type router struct
method addRoute (line 36) | func (r *router) addRoute(method string, pattern string, handler Handl...
method getRoute (line 48) | func (r *router) getRoute(method string, path string) (*node, map[stri...
method getRoutes (line 76) | func (r *router) getRoutes(method string) []*node {
method handle (line 86) | func (r *router) handle(c *Context) {
function newRouter (line 13) | func newRouter() *router {
function parsePattern (line 21) | func parsePattern(pattern string) []string {
FILE: gee-web/day6-template/gee/router_test.go
function newTestRouter (line 9) | func newTestRouter() *router {
function TestParsePattern (line 19) | func TestParsePattern(t *testing.T) {
function TestGetRoute (line 28) | func TestGetRoute(t *testing.T) {
function TestGetRoute2 (line 48) | func TestGetRoute2(t *testing.T) {
function TestGetRoutes (line 64) | func TestGetRoutes(t *testing.T) {
FILE: gee-web/day6-template/gee/trie.go
type node (line 8) | type node struct
method String (line 15) | func (n *node) String() string {
method insert (line 19) | func (n *node) insert(pattern string, parts []string, height int) {
method search (line 34) | func (n *node) search(parts []string, height int) *node {
method travel (line 55) | func (n *node) travel(list *([]*node)) {
method matchChild (line 64) | func (n *node) matchChild(part string) *node {
method matchChildren (line 73) | func (n *node) matchChildren(part string) []*node {
FILE: gee-web/day6-template/main.go
type student (line 45) | type student struct
function FormatAsDate (line 50) | func FormatAsDate(t time.Time) string {
function main (line 55) | func main() {
FILE: gee-web/day7-panic-recover/gee/context.go
type H (line 9) | type H
type Context (line 11) | type Context struct
method Next (line 38) | func (c *Context) Next() {
method Fail (line 46) | func (c *Context) Fail(code int, err string) {
method Param (line 51) | func (c *Context) Param(key string) string {
method PostForm (line 56) | func (c *Context) PostForm(key string) string {
method Query (line 60) | func (c *Context) Query(key string) string {
method Status (line 64) | func (c *Context) Status(code int) {
method SetHeader (line 69) | func (c *Context) SetHeader(key string, value string) {
method String (line 73) | func (c *Context) String(code int, format string, values ...interface{...
method JSON (line 79) | func (c *Context) JSON(code int, obj interface{}) {
method Data (line 88) | func (c *Context) Data(code int, data []byte) {
method HTML (line 95) | func (c *Context) HTML(code int, name string, data interface{}) {
function newContext (line 28) | func newContext(w http.ResponseWriter, req *http.Request) *Context {
FILE: gee-web/day7-panic-recover/gee/gee.go
type HandlerFunc (line 12) | type HandlerFunc
type RouterGroup (line 16) | type RouterGroup struct
method Group (line 49) | func (group *RouterGroup) Group(prefix string) *RouterGroup {
method Use (line 61) | func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
method addRoute (line 65) | func (group *RouterGroup) addRoute(method string, comp string, handler...
method GET (line 72) | func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
method POST (line 77) | func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
method createStaticHandler (line 82) | func (group *RouterGroup) createStaticHandler(relativePath string, fs ...
method Static (line 98) | func (group *RouterGroup) Static(relativePath string, root string) {
type Engine (line 23) | type Engine struct
method SetFuncMap (line 106) | func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
method LoadHTMLGlob (line 110) | func (engine *Engine) LoadHTMLGlob(pattern string) {
method Run (line 115) | func (engine *Engine) Run(addr string) (err error) {
method ServeHTTP (line 119) | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function New (line 33) | func New() *Engine {
function Default (line 41) | func Default() *Engine {
FILE: gee-web/day7-panic-recover/gee/gee_test.go
function TestNestedGroup (line 5) | func TestNestedGroup(t *testing.T) {
FILE: gee-web/day7-panic-recover/gee/logger.go
function Logger (line 8) | func Logger() HandlerFunc {
FILE: gee-web/day7-panic-recover/gee/recovery.go
function trace (line 12) | func trace(message string) string {
function Recovery (line 26) | func Recovery() HandlerFunc {
FILE: gee-web/day7-panic-recover/gee/router.go
type router (line 8) | type router struct
method addRoute (line 36) | func (r *router) addRoute(method string, pattern string, handler Handl...
method getRoute (line 48) | func (r *router) getRoute(method string, path string) (*node, map[stri...
method getRoutes (line 76) | func (r *router) getRoutes(method string) []*node {
method handle (line 86) | func (r *router) handle(c *Context) {
function newRouter (line 13) | func newRouter() *router {
function parsePattern (line 21) | func parsePattern(pattern string) []string {
FILE: gee-web/day7-panic-recover/gee/router_test.go
function newTestRouter (line 9) | func newTestRouter() *router {
function TestParsePattern (line 19) | func TestParsePattern(t *testing.T) {
function TestGetRoute (line 28) | func TestGetRoute(t *testing.T) {
function TestGetRoute2 (line 48) | func TestGetRoute2(t *testing.T) {
function TestGetRoutes (line 64) | func TestGetRoutes(t *testing.T) {
FILE: gee-web/day7-panic-recover/gee/trie.go
type node (line 8) | type node struct
method String (line 15) | func (n *node) String() string {
method insert (line 19) | func (n *node) insert(pattern string, parts []string, height int) {
method search (line 34) | func (n *node) search(parts []string, height int) *node {
method travel (line 55) | func (n *node) travel(list *([]*node)) {
method matchChild (line 64) | func (n *node) matchChild(part string) *node {
method matchChildren (line 73) | func (n *node) matchChildren(part string) []*node {
FILE: gee-web/day7-panic-recover/main.go
function main (line 41) | func main() {
Condensed preview — 396 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (795K chars).
[
{
"path": ".gitignore",
"chars": 38,
"preview": ".DS_Store\n.idea\n.vscode\ntmp\n*.db\n*.sum"
},
{
"path": "LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2020 Dai Jie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "README.md",
"chars": 8058,
"preview": "# 7 days golang programs from scratch\n\n[)\t\n\tgo get -u github.com/shurcooL/goexec\nendif\n\tg"
},
{
"path": "demo-wasm/callback/index.html",
"chars": 442,
"preview": "<html>\n\n<head>\n <script src=\"static/wasm_exec.js\"></script>\n <script>\n const go = new Go();\n WebAsse"
},
{
"path": "demo-wasm/callback/main.go",
"chars": 507,
"preview": "// main.go\npackage main\n\nimport (\n\t\"syscall/js\"\n\t\"time\"\n)\n\nfunc fib(i int) int {\n\tif i == 0 || i == 1 {\n\t\treturn 1\n\t}\n\tr"
},
{
"path": "demo-wasm/hello-world/Makefile",
"chars": 365,
"preview": "all: static/main.wasm static/wasm_exec.js\nifeq (, $(shell which goexec))\t\n\tgo get -u github.com/shurcooL/goexec\nendif\n\tg"
},
{
"path": "demo-wasm/hello-world/index.html",
"chars": 276,
"preview": "<html>\n\n<head>\n <script src=\"static/wasm_exec.js\"></script>\n <script>\n const go = new Go();\n WebAsse"
},
{
"path": "demo-wasm/hello-world/main.go",
"chars": 126,
"preview": "// main.go\npackage main\n\nimport \"syscall/js\"\n\nfunc main() {\n\talert := js.Global().Get(\"alert\")\n\talert.Invoke(\"Hello Worl"
},
{
"path": "demo-wasm/manipulate-dom/Makefile",
"chars": 365,
"preview": "all: static/main.wasm static/wasm_exec.js\nifeq (, $(shell which goexec))\t\n\tgo get -u github.com/shurcooL/goexec\nendif\n\tg"
},
{
"path": "demo-wasm/manipulate-dom/index.html",
"chars": 387,
"preview": "<html>\n\n<head>\n <script src=\"static/wasm_exec.js\"></script>\n <script>\n const go = new Go();\n WebAsse"
},
{
"path": "demo-wasm/manipulate-dom/main.go",
"chars": 646,
"preview": "package main\n\nimport (\n\t\"strconv\"\n\t\"syscall/js\"\n)\n\nfunc fib(i int) int {\n\tif i == 0 || i == 1 {\n\t\treturn 1\n\t}\n\treturn fi"
},
{
"path": "demo-wasm/register-functions/Makefile",
"chars": 365,
"preview": "all: static/main.wasm static/wasm_exec.js\nifeq (, $(shell which goexec))\t\n\tgo get -u github.com/shurcooL/goexec\nendif\n\tg"
},
{
"path": "demo-wasm/register-functions/index.html",
"chars": 434,
"preview": "<html>\n\n<head>\n <script src=\"static/wasm_exec.js\"></script>\n <script>\n const go = new Go();\n WebAsse"
},
{
"path": "demo-wasm/register-functions/main.go",
"chars": 336,
"preview": "// main.go\npackage main\n\nimport \"syscall/js\"\n\nfunc fib(i int) int {\n\tif i == 0 || i == 1 {\n\t\treturn 1\n\t}\n\treturn fib(i-1"
},
{
"path": "gee-bolt/day1-pages/go.mod",
"chars": 24,
"preview": "module geebolt\n\ngo 1.13\n"
},
{
"path": "gee-bolt/day1-pages/meta.go",
"chars": 600,
"preview": "package geebolt\n\nimport (\n\t\"errors\"\n\t\"hash/fnv\"\n\t\"unsafe\"\n)\n\n// Represent a marker value to indicate that a file is a ge"
},
{
"path": "gee-bolt/day1-pages/page.go",
"chars": 1834,
"preview": "package geebolt\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"unsafe\"\n)\n\nconst pageHeaderSize = unsafe.Sizeof(page{})\nconst branchPageEl"
},
{
"path": "gee-bolt/day2-mmap/db.go",
"chars": 198,
"preview": "package geebolt\n\nimport \"os\"\n\ntype DB struct {\n\tdata []byte\n\tfile *os.File\n}\n\nconst maxMapSize = 1 << 31\n\nfunc (db *DB) "
},
{
"path": "gee-bolt/day2-mmap/go.mod",
"chars": 24,
"preview": "module geebolt\n\ngo 1.13\n"
},
{
"path": "gee-bolt/day3-tree/go.mod",
"chars": 24,
"preview": "module geebolt\n\ngo 1.13\n"
},
{
"path": "gee-bolt/day3-tree/meta.go",
"chars": 600,
"preview": "package geebolt\n\nimport (\n\t\"errors\"\n\t\"hash/fnv\"\n\t\"unsafe\"\n)\n\n// Represent a marker value to indicate that a file is a ge"
},
{
"path": "gee-bolt/day3-tree/node.go",
"chars": 910,
"preview": "package geebolt\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n)\n\ntype kv struct {\n\tkey []byte\n\tvalue []byte\n}\n\ntype node struct {\n\tisLeaf"
},
{
"path": "gee-bolt/day3-tree/page.go",
"chars": 1834,
"preview": "package geebolt\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"unsafe\"\n)\n\nconst pageHeaderSize = unsafe.Sizeof(page{})\nconst branchPageEl"
},
{
"path": "gee-cache/day1-lru/geecache/go.mod",
"chars": 25,
"preview": "module geecache\n\ngo 1.13\n"
},
{
"path": "gee-cache/day1-lru/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day1-lru/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day2-single-node/geecache/byteview.go",
"chars": 521,
"preview": "package geecache\n\n// A ByteView holds an immutable view of bytes.\ntype ByteView struct {\n\tb []byte\n}\n\n// Len returns the"
},
{
"path": "gee-cache/day2-single-node/geecache/cache.go",
"chars": 510,
"preview": "package geecache\n\nimport (\n\t\"geecache/lru\"\n\t\"sync\"\n)\n\ntype cache struct {\n\tmu sync.Mutex\n\tlru *lru.Cache\n"
},
{
"path": "gee-cache/day2-single-node/geecache/geecache.go",
"chars": 1778,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n)\n\n// A Group is a cache namespace and associated data loaded spread ov"
},
{
"path": "gee-cache/day2-single-node/geecache/geecache_test.go",
"chars": 1543,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar db = map[string]string{\n\t\"Tom\": \"630\",\n\t\"Jack\": \""
},
{
"path": "gee-cache/day2-single-node/geecache/go.mod",
"chars": 25,
"preview": "module geecache\n\ngo 1.13\n"
},
{
"path": "gee-cache/day2-single-node/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day2-single-node/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day3-http-server/geecache/byteview.go",
"chars": 521,
"preview": "package geecache\n\n// A ByteView holds an immutable view of bytes.\ntype ByteView struct {\n\tb []byte\n}\n\n// Len returns the"
},
{
"path": "gee-cache/day3-http-server/geecache/cache.go",
"chars": 510,
"preview": "package geecache\n\nimport (\n\t\"geecache/lru\"\n\t\"sync\"\n)\n\ntype cache struct {\n\tmu sync.Mutex\n\tlru *lru.Cache\n"
},
{
"path": "gee-cache/day3-http-server/geecache/geecache.go",
"chars": 1778,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n)\n\n// A Group is a cache namespace and associated data loaded spread ov"
},
{
"path": "gee-cache/day3-http-server/geecache/geecache_test.go",
"chars": 1543,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar db = map[string]string{\n\t\"Tom\": \"630\",\n\t\"Jack\": \""
},
{
"path": "gee-cache/day3-http-server/geecache/go.mod",
"chars": 25,
"preview": "module geecache\n\ngo 1.13\n"
},
{
"path": "gee-cache/day3-http-server/geecache/http.go",
"chars": 1434,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n)\n\nconst defaultBasePath = \"/_geecache/\"\n\n// HTTPPool im"
},
{
"path": "gee-cache/day3-http-server/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day3-http-server/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day3-http-server/go.mod",
"chars": 81,
"preview": "module example\n\ngo 1.13\n\nrequire geecache v0.0.0\n\nreplace geecache => ./geecache\n"
},
{
"path": "gee-cache/day3-http-server/main.go",
"chars": 689,
"preview": "package main\n\n/*\n$ curl http://localhost:9999/_geecache/scores/Tom\n630\n\n$ curl http://localhost:9999/_geecache/scores/kk"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/byteview.go",
"chars": 521,
"preview": "package geecache\n\n// A ByteView holds an immutable view of bytes.\ntype ByteView struct {\n\tb []byte\n}\n\n// Len returns the"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/cache.go",
"chars": 510,
"preview": "package geecache\n\nimport (\n\t\"geecache/lru\"\n\t\"sync\"\n)\n\ntype cache struct {\n\tmu sync.Mutex\n\tlru *lru.Cache\n"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash.go",
"chars": 1112,
"preview": "package consistenthash\n\nimport (\n\t\"hash/crc32\"\n\t\"sort\"\n\t\"strconv\"\n)\n\n// Hash maps bytes to uint32\ntype Hash func(data []"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/consistenthash/consistenthash_test.go",
"chars": 751,
"preview": "package consistenthash\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestHashing(t *testing.T) {\n\thash := New(3, func(key []by"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/geecache.go",
"chars": 1778,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n)\n\n// A Group is a cache namespace and associated data loaded spread ov"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/geecache_test.go",
"chars": 1543,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar db = map[string]string{\n\t\"Tom\": \"630\",\n\t\"Jack\": \""
},
{
"path": "gee-cache/day4-consistent-hash/geecache/go.mod",
"chars": 25,
"preview": "module geecache\n\ngo 1.13\n"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/http.go",
"chars": 1434,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n)\n\nconst defaultBasePath = \"/_geecache/\"\n\n// HTTPPool im"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day4-consistent-hash/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day4-consistent-hash/go.mod",
"chars": 81,
"preview": "module example\n\ngo 1.13\n\nrequire geecache v0.0.0\n\nreplace geecache => ./geecache\n"
},
{
"path": "gee-cache/day4-consistent-hash/main.go",
"chars": 689,
"preview": "package main\n\n/*\n$ curl http://localhost:9999/_geecache/scores/Tom\n630\n\n$ curl http://localhost:9999/_geecache/scores/kk"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/byteview.go",
"chars": 521,
"preview": "package geecache\n\n// A ByteView holds an immutable view of bytes.\ntype ByteView struct {\n\tb []byte\n}\n\n// Len returns the"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/cache.go",
"chars": 510,
"preview": "package geecache\n\nimport (\n\t\"geecache/lru\"\n\t\"sync\"\n)\n\ntype cache struct {\n\tmu sync.Mutex\n\tlru *lru.Cache\n"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash.go",
"chars": 1112,
"preview": "package consistenthash\n\nimport (\n\t\"hash/crc32\"\n\t\"sort\"\n\t\"strconv\"\n)\n\n// Hash maps bytes to uint32\ntype Hash func(data []"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/consistenthash/consistenthash_test.go",
"chars": 751,
"preview": "package consistenthash\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestHashing(t *testing.T) {\n\thash := New(3, func(key []by"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/geecache.go",
"chars": 2422,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n)\n\n// A Group is a cache namespace and associated data loaded spread ov"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/geecache_test.go",
"chars": 1543,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar db = map[string]string{\n\t\"Tom\": \"630\",\n\t\"Jack\": \""
},
{
"path": "gee-cache/day5-multi-nodes/geecache/go.mod",
"chars": 25,
"preview": "module geecache\n\ngo 1.13\n"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/http.go",
"chars": 2948,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"geecache/consistenthash\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"syn"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day5-multi-nodes/geecache/peers.go",
"chars": 346,
"preview": "package geecache\n\n// PeerPicker is the interface that must be implemented to locate\n// the peer that owns a specific key"
},
{
"path": "gee-cache/day5-multi-nodes/go.mod",
"chars": 81,
"preview": "module example\n\ngo 1.13\n\nrequire geecache v0.0.0\n\nreplace geecache => ./geecache\n"
},
{
"path": "gee-cache/day5-multi-nodes/main.go",
"chars": 1865,
"preview": "package main\n\n/*\n$ curl \"http://localhost:9999/api?key=Tom\"\n630\n\n$ curl \"http://localhost:9999/api?key=kkk\"\nkkk not exis"
},
{
"path": "gee-cache/day5-multi-nodes/run.sh",
"chars": 299,
"preview": "#!/bin/bash\ntrap \"rm server;kill 0\" EXIT\n\ngo build -o server\n./server -port=8001 &\n./server -port=8002 &\n./server -port="
},
{
"path": "gee-cache/day6-single-flight/geecache/byteview.go",
"chars": 521,
"preview": "package geecache\n\n// A ByteView holds an immutable view of bytes.\ntype ByteView struct {\n\tb []byte\n}\n\n// Len returns the"
},
{
"path": "gee-cache/day6-single-flight/geecache/cache.go",
"chars": 510,
"preview": "package geecache\n\nimport (\n\t\"geecache/lru\"\n\t\"sync\"\n)\n\ntype cache struct {\n\tmu sync.Mutex\n\tlru *lru.Cache\n"
},
{
"path": "gee-cache/day6-single-flight/geecache/consistenthash/consistenthash.go",
"chars": 1112,
"preview": "package consistenthash\n\nimport (\n\t\"hash/crc32\"\n\t\"sort\"\n\t\"strconv\"\n)\n\n// Hash maps bytes to uint32\ntype Hash func(data []"
},
{
"path": "gee-cache/day6-single-flight/geecache/consistenthash/consistenthash_test.go",
"chars": 751,
"preview": "package consistenthash\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestHashing(t *testing.T) {\n\thash := New(3, func(key []by"
},
{
"path": "gee-cache/day6-single-flight/geecache/geecache.go",
"chars": 2840,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"geecache/singleflight\"\n\t\"log\"\n\t\"sync\"\n)\n\n// A Group is a cache namespace and associa"
},
{
"path": "gee-cache/day6-single-flight/geecache/geecache_test.go",
"chars": 1543,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar db = map[string]string{\n\t\"Tom\": \"630\",\n\t\"Jack\": \""
},
{
"path": "gee-cache/day6-single-flight/geecache/go.mod",
"chars": 25,
"preview": "module geecache\n\ngo 1.13\n"
},
{
"path": "gee-cache/day6-single-flight/geecache/http.go",
"chars": 2948,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"geecache/consistenthash\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"syn"
},
{
"path": "gee-cache/day6-single-flight/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day6-single-flight/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day6-single-flight/geecache/peers.go",
"chars": 346,
"preview": "package geecache\n\n// PeerPicker is the interface that must be implemented to locate\n// the peer that owns a specific key"
},
{
"path": "gee-cache/day6-single-flight/geecache/singleflight/singleflight.go",
"chars": 1037,
"preview": "package singleflight\n\nimport \"sync\"\n\n// call is an in-flight or completed Do call\ntype call struct {\n\twg sync.WaitGroup"
},
{
"path": "gee-cache/day6-single-flight/geecache/singleflight/singleflight_test.go",
"chars": 244,
"preview": "package singleflight\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDo(t *testing.T) {\n\tvar g Group\n\tv, err := g.Do(\"key\", func() (int"
},
{
"path": "gee-cache/day6-single-flight/go.mod",
"chars": 81,
"preview": "module example\n\ngo 1.13\n\nrequire geecache v0.0.0\n\nreplace geecache => ./geecache\n"
},
{
"path": "gee-cache/day6-single-flight/main.go",
"chars": 1865,
"preview": "package main\n\n/*\n$ curl \"http://localhost:9999/api?key=Tom\"\n630\n\n$ curl \"http://localhost:9999/api?key=kkk\"\nkkk not exis"
},
{
"path": "gee-cache/day6-single-flight/run.sh",
"chars": 471,
"preview": "#!/bin/bash\ntrap \"rm server;kill 0\" EXIT\n\ngo build -o server\n./server -port=8001 &\n./server -port=8002 &\n./server -port="
},
{
"path": "gee-cache/day7-proto-buf/geecache/byteview.go",
"chars": 521,
"preview": "package geecache\n\n// A ByteView holds an immutable view of bytes.\ntype ByteView struct {\n\tb []byte\n}\n\n// Len returns the"
},
{
"path": "gee-cache/day7-proto-buf/geecache/cache.go",
"chars": 510,
"preview": "package geecache\n\nimport (\n\t\"geecache/lru\"\n\t\"sync\"\n)\n\ntype cache struct {\n\tmu sync.Mutex\n\tlru *lru.Cache\n"
},
{
"path": "gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash.go",
"chars": 1112,
"preview": "package consistenthash\n\nimport (\n\t\"hash/crc32\"\n\t\"sort\"\n\t\"strconv\"\n)\n\n// Hash maps bytes to uint32\ntype Hash func(data []"
},
{
"path": "gee-cache/day7-proto-buf/geecache/consistenthash/consistenthash_test.go",
"chars": 751,
"preview": "package consistenthash\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestHashing(t *testing.T) {\n\thash := New(3, func(key []by"
},
{
"path": "gee-cache/day7-proto-buf/geecache/geecache.go",
"chars": 2938,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\tpb \"geecache/geecachepb\"\n\t\"geecache/singleflight\"\n\t\"log\"\n\t\"sync\"\n)\n\n// A Group is a c"
},
{
"path": "gee-cache/day7-proto-buf/geecache/geecache_test.go",
"chars": 1543,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar db = map[string]string{\n\t\"Tom\": \"630\",\n\t\"Jack\": \""
},
{
"path": "gee-cache/day7-proto-buf/geecache/geecachepb/geecachepb.pb.go",
"chars": 4336,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: geecachepb.proto\n\npackage geecachepb\n\nimport (\n\tfmt \"fmt\"\n\tp"
},
{
"path": "gee-cache/day7-proto-buf/geecache/geecachepb/geecachepb.proto",
"chars": 203,
"preview": "syntax = \"proto3\";\n\npackage geecachepb;\n\nmessage Request {\n string group = 1;\n string key = 2;\n}\n\nmessage Response {\n "
},
{
"path": "gee-cache/day7-proto-buf/geecache/go.mod",
"chars": 68,
"preview": "module geecache\n\ngo 1.13\n\nrequire github.com/golang/protobuf v1.3.3\n"
},
{
"path": "gee-cache/day7-proto-buf/geecache/http.go",
"chars": 3321,
"preview": "package geecache\n\nimport (\n\t\"fmt\"\n\t\"geecache/consistenthash\"\n\tpb \"geecache/geecachepb\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t"
},
{
"path": "gee-cache/day7-proto-buf/geecache/lru/lru.go",
"chars": 1726,
"preview": "package lru\n\nimport \"container/list\"\n\n// Cache is a LRU cache. It is not safe for concurrent access.\ntype Cache struct {"
},
{
"path": "gee-cache/day7-proto-buf/geecache/lru/lru_test.go",
"chars": 1398,
"preview": "package lru\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype String string\n\nfunc (d String) Len() int {\n\treturn len(d)\n}\n\nfunc Te"
},
{
"path": "gee-cache/day7-proto-buf/geecache/peers.go",
"chars": 377,
"preview": "package geecache\n\nimport pb \"geecache/geecachepb\"\n\n// PeerPicker is the interface that must be implemented to locate\n// "
},
{
"path": "gee-cache/day7-proto-buf/geecache/singleflight/singleflight.go",
"chars": 1037,
"preview": "package singleflight\n\nimport \"sync\"\n\n// call is an in-flight or completed Do call\ntype call struct {\n\twg sync.WaitGroup"
},
{
"path": "gee-cache/day7-proto-buf/geecache/singleflight/singleflight_test.go",
"chars": 244,
"preview": "package singleflight\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDo(t *testing.T) {\n\tvar g Group\n\tv, err := g.Do(\"key\", func() (int"
},
{
"path": "gee-cache/day7-proto-buf/go.mod",
"chars": 81,
"preview": "module example\n\ngo 1.13\n\nrequire geecache v0.0.0\n\nreplace geecache => ./geecache\n"
},
{
"path": "gee-cache/day7-proto-buf/main.go",
"chars": 1865,
"preview": "package main\n\n/*\n$ curl \"http://localhost:9999/api?key=Tom\"\n630\n\n$ curl \"http://localhost:9999/api?key=kkk\"\nkkk not exis"
},
{
"path": "gee-cache/day7-proto-buf/run.sh",
"chars": 299,
"preview": "#!/bin/bash\ntrap \"rm server;kill 0\" EXIT\n\ngo build -o server\n./server -port=8001 &\n./server -port=8002 &\n./server -port="
},
{
"path": "gee-cache/doc/geecache-day1.md",
"chars": 6385,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第一天 LRU 缓存淘汰策略\ndate: 2020-02-11 22:00:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教"
},
{
"path": "gee-cache/doc/geecache-day2.md",
"chars": 10228,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第二天 单机并发缓存\ndate: 2020-02-12 22:00:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 "
},
{
"path": "gee-cache/doc/geecache-day3.md",
"chars": 6023,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第三天 HTTP 服务端\ndate: 2020-02-12 23:00:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程("
},
{
"path": "gee-cache/doc/geecache-day4.md",
"chars": 6188,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第四天 一致性哈希(hash)\ndate: 2020-02-16 20:00:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache "
},
{
"path": "gee-cache/doc/geecache-day5.md",
"chars": 9530,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第五天 分布式节点\ndate: 2020-02-16 21:30:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 d"
},
{
"path": "gee-cache/doc/geecache-day6.md",
"chars": 5212,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第六天 防止缓存击穿\ndate: 2020-02-16 23:00:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 "
},
{
"path": "gee-cache/doc/geecache-day7.md",
"chars": 4718,
"preview": "---\ntitle: 动手写分布式缓存 - GeeCache第七天 使用 Protobuf 通信\ndate: 2020-02-17 00:30:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCac"
},
{
"path": "gee-cache/doc/geecache.md",
"chars": 3456,
"preview": "---\ntitle: 7天用Go从零实现分布式缓存GeeCache\ndate: 2020-02-08 01:00:00\ndescription: 7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days im"
},
{
"path": "gee-orm/day1-database-sql/cmd_test/main.go",
"chars": 522,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"geeorm\"\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nfunc main() {\n\tengine, _ := geeorm.NewEngine"
},
{
"path": "gee-orm/day1-database-sql/geeorm.go",
"chars": 966,
"preview": "package geeorm\n\nimport (\n\t\"database/sql\"\n\n\t\"geeorm/log\"\n\t\"geeorm/session\"\n)\n\n// Engine is the main struct of geeorm, man"
},
{
"path": "gee-orm/day1-database-sql/geeorm_test.go",
"chars": 321,
"preview": "package geeorm\n\nimport (\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"testing\"\n)\n\nfunc OpenDB(t *testing.T) *Engine {\n\tt.Helper()\n"
},
{
"path": "gee-orm/day1-database-sql/go.mod",
"chars": 80,
"preview": "module geeorm\n\ngo 1.13\n\nrequire github.com/mattn/go-sqlite3 v2.0.3+incompatible\n"
},
{
"path": "gee-orm/day1-database-sql/log/log.go",
"chars": 790,
"preview": "package log\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\nvar (\n\terrorLog = log.New(os.Stdout, \"\\033[31m[error]\\033[0m "
},
{
"path": "gee-orm/day1-database-sql/log/log_test.go",
"chars": 339,
"preview": "package log\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestSetLevel(t *testing.T) {\n\tSetLevel(ErrorLevel)\n\tif infoLog.Writer() ="
},
{
"path": "gee-orm/day1-database-sql/session/raw.go",
"chars": 1430,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/log\"\n\t\"strings\"\n)\n\n// Session keep a pointer to sql.DB and provides a"
},
{
"path": "gee-orm/day1-database-sql/session/raw_test.go",
"chars": 992,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nvar TestDB *sql.DB\n\nfunc"
},
{
"path": "gee-orm/day2-reflect-schema/dialect/dialect.go",
"chars": 574,
"preview": "package dialect\n\nimport \"reflect\"\n\nvar dialectsMap = map[string]Dialect{}\n\n// Dialect is an interface contains methods t"
},
{
"path": "gee-orm/day2-reflect-schema/dialect/sqlite3.go",
"chars": 1130,
"preview": "package dialect\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype sqlite3 struct{}\n\nvar _ Dialect = (*sqlite3)(nil)\n\nfunc init"
},
{
"path": "gee-orm/day2-reflect-schema/dialect/sqlite3_test.go",
"chars": 412,
"preview": "package dialect\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDataTypeOf(t *testing.T) {\n\tdial := &sqlite3{}\n\tcases := []s"
},
{
"path": "gee-orm/day2-reflect-schema/geeorm.go",
"chars": 1193,
"preview": "package geeorm\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/session\"\n)\n\n// Engine is the main stru"
},
{
"path": "gee-orm/day2-reflect-schema/geeorm_test.go",
"chars": 321,
"preview": "package geeorm\n\nimport (\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"testing\"\n)\n\nfunc OpenDB(t *testing.T) *Engine {\n\tt.Helper()\n"
},
{
"path": "gee-orm/day2-reflect-schema/go.mod",
"chars": 80,
"preview": "module geeorm\n\ngo 1.13\n\nrequire github.com/mattn/go-sqlite3 v2.0.3+incompatible\n"
},
{
"path": "gee-orm/day2-reflect-schema/log/log.go",
"chars": 790,
"preview": "package log\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\nvar (\n\terrorLog = log.New(os.Stdout, \"\\033[31m[error]\\033[0m "
},
{
"path": "gee-orm/day2-reflect-schema/log/log_test.go",
"chars": 339,
"preview": "package log\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestSetLevel(t *testing.T) {\n\tSetLevel(ErrorLevel)\n\tif infoLog.Writer() ="
},
{
"path": "gee-orm/day2-reflect-schema/schema/schema.go",
"chars": 1724,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"go/ast\"\n\t\"reflect\"\n)\n\n// Field represents a column of database\ntype Field s"
},
{
"path": "gee-orm/day2-reflect-schema/schema/schema_test.go",
"chars": 1024,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge in"
},
{
"path": "gee-orm/day2-reflect-schema/session/raw.go",
"chars": 1575,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/schema\"\n\t\"strings\"\n)\n\n// Session keep"
},
{
"path": "gee-orm/day2-reflect-schema/session/raw_test.go",
"chars": 1076,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"geeorm/dialect\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nvar ("
},
{
"path": "gee-orm/day2-reflect-schema/session/table.go",
"chars": 1396,
"preview": "package session\n\nimport (\n\t\"fmt\"\n\t\"geeorm/log\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"geeorm/schema\"\n)\n\n// Model assigns refTable\nfunc"
},
{
"path": "gee-orm/day2-reflect-schema/session/table_test.go",
"chars": 512,
"preview": "package session\n\nimport (\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge int\n}\nfunc TestSess"
},
{
"path": "gee-orm/day3-save-query/clause/clause.go",
"chars": 915,
"preview": "package clause\n\nimport (\n\t\"strings\"\n)\n\n// Clause contains SQL conditions\ntype Clause struct {\n\tsql map[Type]string\n\t"
},
{
"path": "gee-orm/day3-save-query/clause/clause_test.go",
"chars": 905,
"preview": "package clause\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestClause_Set(t *testing.T) {\n\tvar clause Clause\n\tclause.Set(INS"
},
{
"path": "gee-orm/day3-save-query/clause/generator.go",
"chars": 1905,
"preview": "package clause\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype generator func(values ...interface{}) (string, []interface{})\n\nvar ge"
},
{
"path": "gee-orm/day3-save-query/dialect/dialect.go",
"chars": 574,
"preview": "package dialect\n\nimport \"reflect\"\n\nvar dialectsMap = map[string]Dialect{}\n\n// Dialect is an interface contains methods t"
},
{
"path": "gee-orm/day3-save-query/dialect/sqlite3.go",
"chars": 1130,
"preview": "package dialect\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype sqlite3 struct{}\n\nvar _ Dialect = (*sqlite3)(nil)\n\nfunc init"
},
{
"path": "gee-orm/day3-save-query/dialect/sqlite3_test.go",
"chars": 412,
"preview": "package dialect\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDataTypeOf(t *testing.T) {\n\tdial := &sqlite3{}\n\tcases := []s"
},
{
"path": "gee-orm/day3-save-query/geeorm.go",
"chars": 1193,
"preview": "package geeorm\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/session\"\n)\n\n// Engine is the main stru"
},
{
"path": "gee-orm/day3-save-query/geeorm_test.go",
"chars": 321,
"preview": "package geeorm\n\nimport (\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"testing\"\n)\n\nfunc OpenDB(t *testing.T) *Engine {\n\tt.Helper()\n"
},
{
"path": "gee-orm/day3-save-query/go.mod",
"chars": 80,
"preview": "module geeorm\n\ngo 1.13\n\nrequire github.com/mattn/go-sqlite3 v2.0.3+incompatible\n"
},
{
"path": "gee-orm/day3-save-query/log/log.go",
"chars": 790,
"preview": "package log\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\nvar (\n\terrorLog = log.New(os.Stdout, \"\\033[31m[error]\\033[0m "
},
{
"path": "gee-orm/day3-save-query/log/log_test.go",
"chars": 339,
"preview": "package log\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestSetLevel(t *testing.T) {\n\tSetLevel(ErrorLevel)\n\tif infoLog.Writer() ="
},
{
"path": "gee-orm/day3-save-query/schema/schema.go",
"chars": 1723,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"go/ast\"\n\t\"reflect\"\n)\n\n// Field represents a column of database\ntype Field s"
},
{
"path": "gee-orm/day3-save-query/schema/schema_test.go",
"chars": 1024,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge in"
},
{
"path": "gee-orm/day3-save-query/session/raw.go",
"chars": 1643,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/clause\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/schema\"\n\t\"strings\"\n)"
},
{
"path": "gee-orm/day3-save-query/session/raw_test.go",
"chars": 1075,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"geeorm/dialect\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nvar ("
},
{
"path": "gee-orm/day3-save-query/session/record.go",
"chars": 1438,
"preview": "package session\n\nimport (\n\t\"geeorm/clause\"\n\t\"reflect\"\n)\n\n// Insert one or more records in database\nfunc (s *Session) Ins"
},
{
"path": "gee-orm/day3-save-query/session/record_test.go",
"chars": 751,
"preview": "package session\n\nimport \"testing\"\n\nvar (\n\tuser1 = &User{\"Tom\", 18}\n\tuser2 = &User{\"Sam\", 25}\n\tuser3 = &User{\"Jack\", 25}\n"
},
{
"path": "gee-orm/day3-save-query/session/table.go",
"chars": 1396,
"preview": "package session\n\nimport (\n\t\"fmt\"\n\t\"geeorm/log\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"geeorm/schema\"\n)\n\n// Model assigns refTable\nfunc"
},
{
"path": "gee-orm/day3-save-query/session/table_test.go",
"chars": 513,
"preview": "package session\n\nimport (\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge int\n}\n\nfunc TestSes"
},
{
"path": "gee-orm/day4-chain-operation/clause/clause.go",
"chars": 938,
"preview": "package clause\n\nimport (\n\t\"strings\"\n)\n\n// Clause contains SQL conditions\ntype Clause struct {\n\tsql map[Type]string\n\t"
},
{
"path": "gee-orm/day4-chain-operation/clause/clause_test.go",
"chars": 1780,
"preview": "package clause\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestClause_Set(t *testing.T) {\n\tvar clause Clause\n\tclause.Set(INS"
},
{
"path": "gee-orm/day4-chain-operation/clause/generator.go",
"chars": 2576,
"preview": "package clause\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype generator func(values ...interface{}) (string, []interface{})\n\nvar ge"
},
{
"path": "gee-orm/day4-chain-operation/dialect/dialect.go",
"chars": 574,
"preview": "package dialect\n\nimport \"reflect\"\n\nvar dialectsMap = map[string]Dialect{}\n\n// Dialect is an interface contains methods t"
},
{
"path": "gee-orm/day4-chain-operation/dialect/sqlite3.go",
"chars": 1130,
"preview": "package dialect\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype sqlite3 struct{}\n\nvar _ Dialect = (*sqlite3)(nil)\n\nfunc init"
},
{
"path": "gee-orm/day4-chain-operation/dialect/sqlite3_test.go",
"chars": 412,
"preview": "package dialect\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDataTypeOf(t *testing.T) {\n\tdial := &sqlite3{}\n\tcases := []s"
},
{
"path": "gee-orm/day4-chain-operation/geeorm.go",
"chars": 1193,
"preview": "package geeorm\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/session\"\n)\n\n// Engine is the main stru"
},
{
"path": "gee-orm/day4-chain-operation/geeorm_test.go",
"chars": 321,
"preview": "package geeorm\n\nimport (\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"testing\"\n)\n\nfunc OpenDB(t *testing.T) *Engine {\n\tt.Helper()\n"
},
{
"path": "gee-orm/day4-chain-operation/go.mod",
"chars": 80,
"preview": "module geeorm\n\ngo 1.13\n\nrequire github.com/mattn/go-sqlite3 v2.0.3+incompatible\n"
},
{
"path": "gee-orm/day4-chain-operation/log/log.go",
"chars": 790,
"preview": "package log\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\nvar (\n\terrorLog = log.New(os.Stdout, \"\\033[31m[error]\\033[0m "
},
{
"path": "gee-orm/day4-chain-operation/log/log_test.go",
"chars": 339,
"preview": "package log\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestSetLevel(t *testing.T) {\n\tSetLevel(ErrorLevel)\n\tif infoLog.Writer() ="
},
{
"path": "gee-orm/day4-chain-operation/schema/schema.go",
"chars": 1723,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"go/ast\"\n\t\"reflect\"\n)\n\n// Field represents a column of database\ntype Field s"
},
{
"path": "gee-orm/day4-chain-operation/schema/schema_test.go",
"chars": 1024,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge in"
},
{
"path": "gee-orm/day4-chain-operation/session/raw.go",
"chars": 1643,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/clause\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/schema\"\n\t\"strings\"\n)"
},
{
"path": "gee-orm/day4-chain-operation/session/raw_test.go",
"chars": 1076,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"geeorm/dialect\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nvar ("
},
{
"path": "gee-orm/day4-chain-operation/session/record.go",
"chars": 3502,
"preview": "package session\n\nimport (\n\t\"errors\"\n\t\"geeorm/clause\"\n\t\"reflect\"\n)\n\n// Insert one or more records in database\nfunc (s *Se"
},
{
"path": "gee-orm/day4-chain-operation/session/record_test.go",
"chars": 2091,
"preview": "package session\n\nimport \"testing\"\n\nvar (\n\tuser1 = &User{\"Tom\", 18}\n\tuser2 = &User{\"Sam\", 25}\n\tuser3 = &User{\"Jack\", 25}\n"
},
{
"path": "gee-orm/day4-chain-operation/session/table.go",
"chars": 1396,
"preview": "package session\n\nimport (\n\t\"fmt\"\n\t\"geeorm/log\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"geeorm/schema\"\n)\n\n// Model assigns refTable\nfunc"
},
{
"path": "gee-orm/day4-chain-operation/session/table_test.go",
"chars": 513,
"preview": "package session\n\nimport (\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge int\n}\n\nfunc TestSes"
},
{
"path": "gee-orm/day5-hooks/clause/clause.go",
"chars": 938,
"preview": "package clause\n\nimport (\n\t\"strings\"\n)\n\n// Clause contains SQL conditions\ntype Clause struct {\n\tsql map[Type]string\n\t"
},
{
"path": "gee-orm/day5-hooks/clause/clause_test.go",
"chars": 1780,
"preview": "package clause\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestClause_Set(t *testing.T) {\n\tvar clause Clause\n\tclause.Set(INS"
},
{
"path": "gee-orm/day5-hooks/clause/generator.go",
"chars": 2576,
"preview": "package clause\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype generator func(values ...interface{}) (string, []interface{})\n\nvar ge"
},
{
"path": "gee-orm/day5-hooks/dialect/dialect.go",
"chars": 574,
"preview": "package dialect\n\nimport \"reflect\"\n\nvar dialectsMap = map[string]Dialect{}\n\n// Dialect is an interface contains methods t"
},
{
"path": "gee-orm/day5-hooks/dialect/sqlite3.go",
"chars": 1130,
"preview": "package dialect\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype sqlite3 struct{}\n\nvar _ Dialect = (*sqlite3)(nil)\n\nfunc init"
},
{
"path": "gee-orm/day5-hooks/dialect/sqlite3_test.go",
"chars": 412,
"preview": "package dialect\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDataTypeOf(t *testing.T) {\n\tdial := &sqlite3{}\n\tcases := []s"
},
{
"path": "gee-orm/day5-hooks/geeorm.go",
"chars": 1193,
"preview": "package geeorm\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/session\"\n)\n\n// Engine is the main stru"
},
{
"path": "gee-orm/day5-hooks/geeorm_test.go",
"chars": 321,
"preview": "package geeorm\n\nimport (\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"testing\"\n)\n\nfunc OpenDB(t *testing.T) *Engine {\n\tt.Helper()\n"
},
{
"path": "gee-orm/day5-hooks/go.mod",
"chars": 80,
"preview": "module geeorm\n\ngo 1.13\n\nrequire github.com/mattn/go-sqlite3 v2.0.3+incompatible\n"
},
{
"path": "gee-orm/day5-hooks/log/log.go",
"chars": 790,
"preview": "package log\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\nvar (\n\terrorLog = log.New(os.Stdout, \"\\033[31m[error]\\033[0m "
},
{
"path": "gee-orm/day5-hooks/log/log_test.go",
"chars": 339,
"preview": "package log\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestSetLevel(t *testing.T) {\n\tSetLevel(ErrorLevel)\n\tif infoLog.Writer() ="
},
{
"path": "gee-orm/day5-hooks/schema/schema.go",
"chars": 1724,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"go/ast\"\n\t\"reflect\"\n)\n\n// Field represents a column of database\ntype Field s"
},
{
"path": "gee-orm/day5-hooks/schema/schema_test.go",
"chars": 1024,
"preview": "package schema\n\nimport (\n\t\"geeorm/dialect\"\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge in"
},
{
"path": "gee-orm/day5-hooks/session/hooks.go",
"chars": 762,
"preview": "package session\n\nimport (\n\t\"geeorm/log\"\n\t\"reflect\"\n)\n\n// Hooks constants\nconst (\n\tBeforeQuery = \"BeforeQuery\"\n\tAfterQue"
},
{
"path": "gee-orm/day5-hooks/session/hooks_test.go",
"chars": 733,
"preview": "package session\n\nimport (\n\t\"geeorm/log\"\n\t\"testing\"\n)\n\ntype Account struct {\n\tID int `geeorm:\"PRIMARY KEY\"`\n\tPasswo"
},
{
"path": "gee-orm/day5-hooks/session/raw.go",
"chars": 1643,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"geeorm/clause\"\n\t\"geeorm/dialect\"\n\t\"geeorm/log\"\n\t\"geeorm/schema\"\n\t\"strings\"\n)"
},
{
"path": "gee-orm/day5-hooks/session/raw_test.go",
"chars": 1076,
"preview": "package session\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"geeorm/dialect\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nvar ("
},
{
"path": "gee-orm/day5-hooks/session/record.go",
"chars": 3783,
"preview": "package session\n\nimport (\n\t\"errors\"\n\t\"geeorm/clause\"\n\t\"reflect\"\n)\n\n// Insert one or more records in database\nfunc (s *Se"
},
{
"path": "gee-orm/day5-hooks/session/record_test.go",
"chars": 2091,
"preview": "package session\n\nimport \"testing\"\n\nvar (\n\tuser1 = &User{\"Tom\", 18}\n\tuser2 = &User{\"Sam\", 25}\n\tuser3 = &User{\"Jack\", 25}\n"
},
{
"path": "gee-orm/day5-hooks/session/table.go",
"chars": 1396,
"preview": "package session\n\nimport (\n\t\"fmt\"\n\t\"geeorm/log\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"geeorm/schema\"\n)\n\n// Model assigns refTable\nfunc"
},
{
"path": "gee-orm/day5-hooks/session/table_test.go",
"chars": 513,
"preview": "package session\n\nimport (\n\t\"testing\"\n)\n\ntype User struct {\n\tName string `geeorm:\"PRIMARY KEY\"`\n\tAge int\n}\n\nfunc TestSes"
},
{
"path": "gee-orm/day6-transaction/clause/clause.go",
"chars": 938,
"preview": "package clause\n\nimport (\n\t\"strings\"\n)\n\n// Clause contains SQL conditions\ntype Clause struct {\n\tsql map[Type]string\n\t"
},
{
"path": "gee-orm/day6-transaction/clause/clause_test.go",
"chars": 1780,
"preview": "package clause\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestClause_Set(t *testing.T) {\n\tvar clause Clause\n\tclause.Set(INS"
},
{
"path": "gee-orm/day6-transaction/clause/generator.go",
"chars": 2576,
"preview": "package clause\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype generator func(values ...interface{}) (string, []interface{})\n\nvar ge"
},
{
"path": "gee-orm/day6-transaction/dialect/dialect.go",
"chars": 574,
"preview": "package dialect\n\nimport \"reflect\"\n\nvar dialectsMap = map[string]Dialect{}\n\n// Dialect is an interface contains methods t"
},
{
"path": "gee-orm/day6-transaction/dialect/sqlite3.go",
"chars": 1130,
"preview": "package dialect\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype sqlite3 struct{}\n\nvar _ Dialect = (*sqlite3)(nil)\n\nfunc init"
},
{
"path": "gee-orm/day6-transaction/dialect/sqlite3_test.go",
"chars": 412,
"preview": "package dialect\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDataTypeOf(t *testing.T) {\n\tdial := &sqlite3{}\n\tcases := []s"
}
]
// ... and 196 more files (download for full content)
About this extraction
This page contains the full source code of the geektutu/7days-golang GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 396 files (692.2 KB), approximately 236.0k tokens, and a symbol index with 1839 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.