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