Go 语言进阶 - 工程进阶 | 青训营笔记

190 阅读3分钟

Go 语言进阶 - 工程进阶 | 青训营笔记


这是我参与「第五届青训营 」笔记创作活动的第2天

概览

并发控制


  • go的一个特性就是高并发,这是由其所支持的协程(goroutine)所体现的。

  • go中实现并发控制有两个方式:

    • 通过通信实现共享内存(goroutine + channel)

      • 其中channel分为无缓冲make(chan TypeName)和有缓冲make(chan TypeName, bufSize)
    • 通过共享内存实现通信(传统lock)

    image-20230115234934819.png

    • 实现通信进行并发控制

      src := make(chan int)
      dest := make(chan int, 3)
      ​
      go func() {
          defer close(src)
          for i := 0; i < 10; i++ {
              src <- i
          }
      }()
      ​
      go func() {
          defer close(dest)
          for i := range src {
              dest <- i * i
          }
      }()
      ​
      for i := range dest {
          fmt.Println(i)
      }
      
    • 通过共享内存实现通信

      var(
          x int64
          lock sync.Mutex
      )
      ​
      func AddWithLock() {
          for i := 0; i < 2000; i++ {
              lock.Lock()
              x++
              lock.Unlock()
          }
      }
      
  • 使用WaitGroup完成协程的同步阻塞,防止主进程先于子协程结束

    var wg sync.WaitGroup
    wg.Add(2)
    ​
    for i := 0; i < 2; i++ {
        go func(j int) {
            defer wg.Done()
            fmt.Println(j)
        }()
    }
    wg.Wait()
    

依赖管理


  • 历史:GOPATH->Go Vendor->Go Moudle
  • GOPATH:所有的依赖代码放在src下,存在不同项目的依赖冲突
  • Go Vendor:在各自的项目文件下存放依赖包副本,避免各项目的依赖冲突
  • Go Module:解决Go Vendor的间接依赖冲突问题,实现了依赖的版本控制。 选择最低兼容性版本解决冲突
  • GOPROXY:解决依赖分发。使用逗号分隔各服务站点

单元测试


  • 单元测试

    • 命名:测试函数_test.go

      func TestFuncName(t *testing.T) {
          testing statement
      }
      
    • go test -v开启测试

    image-20230116170028763.png

    • go test -v --cover开启测试并且输出测试覆盖率

    image-20230116170114608.png

    • 在test文件中推荐使用assert断言进行测试比较
  • Mock测试

    • 测试需要保证幂等和稳定,常规的以文件为输入的测试难以保证这一点,为此可以使用Mock打桩测试,该测试的原理是使用了go中unsafe包,对动态运行的函数地址进行修改,替换输入测试函数为Patch函数。注意使用defer Unpatch()解除打桩测试
      image-20230116171346282.png
  • 基准测试

    • 基准测试常常是用于对性能消耗的测试

      func BenchmarkSelect(b *testing.B) {
          InitServerIndex()
          b.ResetTimer()
          for i := 0; i < b.N; i++ {
              Select()
          }
      }
      ​
      func BenchmarkSelectParallel(b *testing.B) {
          InitServerIndex()
          b.ResetTimer()
          b.RunParallel(func (pb *testing.PB) {
              for pb.Next() {
                  Select()
              }
          })
      }
      

项目实战


  • 需求分析:监听http响应,返回对应请求所代表的帖子中的内容和回复 编码分析:

    • 实体:用户、帖子、回复
    • 服务:查看帖子(根据帖子id加载帖子内容和回复内容)、回复帖子(添加记录到数据库,绑定回复的帖子ID)
  • 项目代码:回帖交互服务器

  • 项目结构分析:

    • repository层:存放了数据库连接和初始化的函数、各Entity和对应的DAO(类似pojo和dao的结合)

      image-20230116173418990.png

      • post.go中包含了post的结构体定义和postdao的定义和相应dao方法的实现 其中值得注意的是:实例化DAO时使用了sync.Once,是单例模式的编程思想的体现

        var postDao *PostDao
        var postOnce sync.Once
        ​
        func NewPostDaoInstance() *PostDao {
            postOnce.Do(
                func() {
                    postDao = &PostDao{}
                })
            return postDao
        }
        
      • 其他topic.go和user.go的实现类似

    • service层:上述查看帖子和回复帖子的服务实现

      image-20230116174306070.png

      • publish_post.go中的核心就是调用了post.go中的DAO方法,实现了新回复的持久化存储

        func (f *PublishPostFlow) publish() error {
            post := &repository.Post{
                ParentId:   f.topicId,
                UserId:     f.userId,
                Content:    f.content,
                CreateTime: time.Now(),
            }
            if err := repository.NewPostDaoInstance().CreatePost(post); err != nil {
                return err
            }
            f.postId = post.Id
            return nil
        }
        
      • query_page_info.go中首先实现了userMap map[int64]*repository.User用户的内存索引。

        在页面内容用获取时使用了协程实现了性能优化

        go func() {
            defer wg.Done()
            topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
            if err != nil {
                topicErr = err
                return
            }
            f.topic = topic
        }()
        //获取post列表
        go func() {
            defer wg.Done()
            posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId)
            if err != nil {
                postErr = err
                return
            }
            f.posts = posts
        }()
        
    • handler层:类似mvc中controller层和view层的结合,用于调用service中方法和向前端返回json化数据

      image-20230116174817880.png

      • handler层的内容就是简单的service调用和状态码及json内容的处理传递

        topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
        if err != nil {
            return &PageData{
                Code: -1,
                Msg:  err.Error(),
            }
        }
        //获取service层结果
        pageInfo, err := service.QueryPageInfo(topicId)
        if err != nil {
            return &PageData{
                Code: -1,
                Msg:  err.Error(),
            }
        }
        return &PageData{
            Code: 0,
            Msg:  "success",
            Data: pageInfo,
        }
        

引用参考


Moonlight-Zhao/go-project-example (github.com)

Go 语言工程实践之测试 - 掘金 (juejin.cn)

‍‬⁣⁣⁢⁡⁢‌‌‌⁡‌⁣⁢⁣⁡‬⁡⁤⁢‍‌⁢‌⁤⁣⁤⁤⁣⁡‌‌‌⁣⁡‌‍⁢‍Go 语言入门 - 工程实践 .pptx - 飞书云文档 (feishu.cn)