Contents

golang-go-zero-微服务实践-微服务实战系列

go-zero官网

go-zero详细文档

本系列为作者阅读公众号微服务实践的文章做的笔记

另外go-zero作者kevwan大佬的learnku博客也是同步更新的

原文地址

工程结构

每个服务目录下我们又会分为多个服务,主要会有如下几类服务:

  • api - 对外的BFF服务,接受来自客户端的请求,暴露HTTP接口
  • rpc - 对内的微服务,仅接受来自内部其他微服务或者BFF的请求,暴露gRPC接口
  • rmq - 负责进行流式任务处理,上游一般依赖消息队列,比如kafka等
  • admin - 也是对内的服务,区别于rpc,更多的是面向运营侧的且数据权限较高,通过隔离可带来更好的代码级别的安全,直接提供HTTP接口

日志定义

1
2
3
4
5
//关闭Stat日志
logx.DisableStat()

//设置日志等级
logx.SetLevel(logx.ErrorLevel)

服务依赖

yaml配置中给依赖rpc或注册中心设置NonBlock实现不等待依赖启动

1
2
3
4
5
6
ReplyRPC:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: reply.rpc
  NonBlock: true

并行调用

mapreduce官方文档

图片上传

在yaml配置文件里面配置第三方OSS的三个属性

1
2
3
OSSEndpoint: https://oss-cn-hangzhou.aliyuncs.com
AccessKeyID: xxxxxxxxxxxxxxxxxxxxxxxx
AccessKeySecret: xxxxxxxxxxxxxxxxxxxxxxxx

在config.go里面添加接收参数

1
2
3
4
5
6
type Config struct {
	rest.RestConf
	OSSEndpoint string
	AccessKeyID string
	AccessKeySecret string
}

在svcCtx里面添加oss客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type ServiceContext struct {
    Config config.Config
    OssClient *oss.Client
}

func NewServiceContext(c config.Config) *ServiceContext {
    oc, err := oss.New(c.OSSEndpoint, c.AccessKeyID, c.AccessKeySecret)
    if err != nil {
        panic(err)
    }
    return &ServiceContext{
        Config: c,
        OssClient: oc,
    }
}

之后就可以在logic里面使用oss客户端上传图片了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func (l *UploadImageLogic) UploadImage() (resp *types.UploadImageResponse, err error) {
    file, header, err := l.r.FormFile(imageFileName)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    bucket, err := l.svcCtx.OssClient.Bucket(bucketName)
    if err != nil {
        return nil, err
    }
    if err = bucket.PutObject(header.Filename, file); err != nil {
        return nil, err
    }
    return &types.UploadImageResponse{Success: true}, nil
}

上传图片前需要先自行创建bucket

缓存代码怎么写

基本使用

goctl生成model时添加-c参数

yaml配置文件

1
2
3
CacheRedis:
  - Host: 127.0.0.1:6379
    Type: node

svc添加字段

1
CategoryModel: model.NewCategoryModel(conn, c.CacheRedis)

缓存雪崩

两种类型

  1. 大量的数据同时过期:避免大量的数据设置相同的过期时间,在过期时间上加一个较小的随机数
  2. Redis出现了宕机:让数据库支持熔断,压力较大时丢弃部分请求

缓存击穿

热点数据过期失效:每次查询缓存的时候使用Expire给缓存续期,不存在时singleflight查询数据库

缓存穿透

访问的数据既不在缓存中,也不在数据库中:缓存一个空值,避免每次都透传到数据库

缓存一致性保证

先删缓存再更新数据库

删除和更新之间被其他线程读取会留存旧值:延时双删,更新完数据库的值后,sleep一小段时间,再进行一次缓存删除操作

先更新数据库再删除缓存

推荐方式,删除缓存的动作很快

重试机制

使用消息队列来保证正确删除缓存

并发读写

由于读取不在缓存中的数据的过程是读取数据库然后插入缓存,所以可能会出现抹去修改的情况:将读取缓存的set cache操作换成add cache,只有在缓存不存在的时候才成功写入

优化高并发

本地缓存

go-zero的collection中提供了Cache来实现本地缓存的功能,Cache提供了Get和Set方法

1
localCache, err := collection.NewCache(localCacheExpire)

自动识别热点数据

滑动窗口统计请求次数,超过阈值升级为本地缓存

处理每秒上万次的下单请求

处理热点数据

优化:在内存中缓存热点数据

限制:限制单用户的请求次数,超过限制直接返回错误

隔离:服务隔离,即秒杀功能独立为一个服务,使用单独的Redis集群和单独的Mysql

流量削峰

消息队列

保证消息只被消费一次

kafka是能够保证"At Least Once"的机制的,消息不会丢失,但有可能会导致重复消费

保证在消息的生产和消费的过程是幂等的,不要出现相对加减,直接赋值

极致优化秒杀性能

批量数据聚合

批量数据聚合:减少网络IO和磁盘IO成本,将多条数据(如100条)聚合后进行处理。之前是1条对应一次网络IO和磁盘IO,现在就是100条对应1次

降低消息的消费延迟

  • 增加消费者的数量
  • 在一个消费者中增加消息处理的并行度

保证不会超卖

分布式锁

 |