go-zero框架
gozero(又稱go-zero)是一款由知名開發者kevwan設計的Golang微服務框架,專注于高性能、低延遲和易用性。其核心目標是簡化分布式系統的開發,提供開箱即用的工具鏈,涵蓋API網關、RPC服務、緩存管理、數據庫操作等模塊。
項目目錄結構
zero_remake/
├── common/
│ ├── init_gorm.go
│ └── init_redis.go
├── shorturl_api/
│ ├── etc/
│ │ └── shorturl.yaml
│ ├── internal/
│ │ ├── config/
│ │ │ └── config.go
│ │ ├── handler/
│ │ │ ├── redirecthandler.go
│ │ │ └── ...
│ │ ├── logic/
│ │ │ ├── redirectlogic.go
│ │ │ └── ...
│ │ ├── svc/
│ │ │ └── servicecontext.go
│ │ └── types/
│ │ └── ...
│ └── main.go
├── shorturl_rpc/
│ ├── etc/
│ │ └── shorturl.yaml
│ ├── internal/
│ │ ├── config/
│ │ │ └── config.go
│ │ ├── logic/
│ │ │ └── ...
│ │ ├── svc/
│ │ │ └── servicecontext.go
│ │ └── types/
│ │ └── ...
│ └── main.go
└── go.mod
common/
:公共初始化、工具等代碼shorturl_api/
:API 網關服務shorturl_rpc/
:RPC 服務- 各自有?
etc/
?配置、internal/
?業務代碼、main.go
?啟動入口
配置文件(yaml)
Name: shorturl.rpc
ListenOn: 0.0.0.0:8888
Etcd:Hosts:- 127.0.0.1:2379Key: shorturl.rpcMysql:DbUser: rootDbPort: "3306"DbPass: wwy040609DbName: shorturlDbHost: localhostBizRedis:RedisHost: 127.0.0.1RedisPort: "6379"RedisPass: ""RedisDB: 0
Etcd用于定位RPC服務端口
Mysql用于永久存儲
Redis用于緩存熱點數據
接口文檔
雪花算法
雪花算法的實現原理:
雪花算法是一種隨時間變化的分布式全局唯一ID算法,其生成的ID可以看做是一個64位的正整數,除了最高位,將剩余的63位分別分為41位的時間戳,10位的機器ID以及12位的自增序列號。
我們不采用MySQL的主鍵自增ID和redsi的incr的自增ID,而是使用本地雪花算法的形式直接生成ID,這樣性能更高。
Redis
將數據存儲在內存上,查詢速度極快,可以存放熱點數據,減少數據庫查詢次數
redis的github地址https://github.com/redis/redis
redis-go的github地址https://github.com/redis/go-redis
Redis的幾種數據結構
- 字符串:可以存儲文本、序列化的對象等。例如,可以將用戶的登錄令牌存儲為字符串,方便快速驗證用戶身份。
- 哈希:適合存儲對象的多個屬性。例如,可以存儲用戶信息,以用戶 ID 為鍵,用戶的各種屬性(如姓名、年齡、地址等)作為哈希中的字段和值,方便快速查詢和更新用戶的單個屬性。
- 列表:可用于實現消息隊列、任務隊列等應用場景。例如,將待處理的任務依次加入列表,多個消費者可以從列表中取出任務進行處理。
- 集合:存儲不重復元素,可用于實現標簽系統、好友列表、共同關注等功能。例如,將用戶的關注用戶列表存儲為集合,方便進行集合運算,如求交集、并集等,以找出共同關注的用戶。
- 有序集合:存儲元素及其分數,可用于排行榜系統,根據分數對元素進行排序。例如,存儲游戲玩家的分數,按分數對玩家進行排名。
本次項目中的Redis
- 本次Redis中的鍵值對都是以字符串形式進行存儲的
- 本次Redis第一次可以自己設定時間,當Redis中的數據因為expiration設置到期而消失時,如果繼續從數據庫中可以獲取時間,那么自動將其添加到緩存中從新設置為24h
if err := l.svcCtx.Redis.Rdb.Set(l.svcCtx.Redis.Ctx, shortCode, in.Url, expireDuration).Err(); err != nil {return &shortUrl.GenerateShortUrlResponse{Code: errmsg.ERROR_FAILED_SAVE_TO_REDIS,Shortcode: "",}, errors.New("Fail to save to redis")}
布隆過濾器
Bloom Filter:short-url/zero_remake/shorturl_rpc/internal/logic/repository/BoomFilter.go
作用
- 布隆過濾過濾器通過hash存儲可以快速判斷數據是否已經存在
- 如果出現緩存和數據庫中都沒有的數據,那么可以通過布隆過濾器快速判斷數據不存在,減少數據庫查詢次數(緩存穿透)
hash 算法
布隆過濾器的hash算法
布隆過濾器的優缺點
優點
- 時間復雜度低,增加和查詢元素的時間復雜為O(N),(N為哈希函數的個數,通常情況比較小)
- 保密性強,布隆過濾器不存儲元素本身
- 存儲空間小,如果允許存在一定的誤判,布隆過濾器是非常節省空間的(相比其他數據結構如Set、Map集合)
缺點
- 有點一定的誤判率,但是可以通過調整參數來降低
- 無法獲取元素本身
- 很難刪除元素
short-url\server\server.go 服務層
GenerateShortURL(發送長鏈接并生成短鏈接)
工作流程
- 檢查URL是否為空:如果URL為空,返回錯誤。
- 檢查布隆過濾器和數據庫:先用布隆過濾器快速檢查URL是否已存在,若可能存在則進一步查詢數據庫確認。
- 生成短鏈:使用MD5哈希和Base64編碼生成短鏈。
- 解析過期時間:如果提供了過期時間,則解析并驗證其格式。
- 存儲短鏈信息:將短鏈信息存儲到MySQL,并將短鏈和原始URL存儲到Redis中,同時設置過期時間。
- 更新布隆過濾器:將原始URL添加到布隆過濾器。
HandlerURL(獲取短鏈接并跳轉)
工作流程
- 清除超時數據:調用 DeleteWithTime() 函數清除Redis中過期的數據。
- 查找原始URL : * 首先從Redis中查找短鏈接對應的原始URL。 * 如果Redis中不存在,則去數據庫中查找。
- 處理結果: 1. 如果在Redis或數據庫中找到原始URL,返回成功并重定向到原始URL。 2. 如果未找到或出現錯誤,返回相應的錯誤碼。
?
特殊的
對于存在超過一個月的數據,會自動在數據庫中刪除
func (l *HandleShortLogic) DeleteWithTime() error {err := l.svcCtx.DB.Where("created_at < ?", time.Now().Add(-time.Hour*24*30)).Delete(&models.Shorturl{}).Errorif err != nil {return err}return nil
}
server中的InDb函數
這個函數其實可以不用寫的,因為每次重啟程序布隆過濾器會重置,所以會重復查詢添加,如果后端一直開著就不會出現這種問題
func InDb(url string) bool {var shortURL model.Shorturlif err := model.Db.Where("url =?", url).First(&shortURL).Error; err == nil {// 已經生成過,直接返回短鏈return true}return false
}
if Bloom.MightContain(url) {// 可能已經生成過,進行精確檢查var shortURL model.Shorturlif err := model.Db.Where("url =?", url).First(&shortURL).Error; err == nil {// 已經生成過,直接返回短鏈log.Println("已經生成過,直接返回短鏈")return errmsg.SUCCESS, shortURL.Shorturl}}
你會發現布隆過濾器的判斷和這個函數如出一轍
例子:?https://www.google.com/search?q=%E7%89%9B%E5%AE%A2%E8%B0%83%E6%95%B4%E7%AE%80%E5%8E%86%E5%90%84%E4%B8%AA%E6%A8%A1%E5%9D%97%E7%9A%84%E9%A1%BA%E5%BA%8F