当前位置: 首页 > news >正文

go最佳实践:如何舒适地编码

什么是 "最佳 "做法?

有很多做法:你可以自己想出来,在互联网上找到,或者从其他语言中拿来,但由于其主观性,并不总是容易说哪一个比另一个好。”最佳”的含义因人而异,也取决于其背景,例如网络应用的最佳实践可能与中间件的最佳实践不一样。
为了写这篇文章,我带着一个问题看了go的实践,那就是 “它在多大程度上让我对写Go感到舒服?”,当我说"语言的最佳实践是什么?"时,那是在我刚接触这门语言,还没有完全适应写这门语言的时候。
当然,还有更多的做法,我在这里不做介绍,但如果你在写go时知道这些做法,就会非常有用,但这三个做法对我在go中的信心影响最大。
这就是我选择"最佳"做法的原因。现在是该上手的时候了。

实践1:package布局

当我开始学习go时,最令人惊讶的事情之一是,go没有像Laravel对PHP,Express对Node那样的网络框架。这意味着在编写网络应用时,如何组织你的代码和包,完全取决于你。虽然在如何组织代码方面拥有自由是一件好事,但如果没有指导原则,很容易迷失方向。
另外,这也是最难达成一致的话题之一;"最佳 "的含义很容易改变,这取决于程序处理的业务逻辑或代码库的大小/成熟度。即使是同一个代码库,当前的软件包组织在6个月后也可能不是最好的。
虽然没有单一的做法可以统治一切,但为了补救这种情况,我将介绍一些准则,希望它们能使决策过程更容易。

准则1:从平面布局开始

除非你知道代码库会很大,并且需要某种预先的包布局,否则最好从平面布局开始,简单地将所有的go文件放在根文件夹中。
这是一个来自github.com/patrickmn/g…软件包的文件结构。

❯ tree
.
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── cache.go
├── cache_test.go
├── sharded.go
└── sharded_test.go

它只有一个领域的关注:对数据缓存,对于像这样的包,甚至不需要包的布局。扁平结构在这种情况下最适合。
但随着代码库的增长,根文件夹会变得很忙,你会开始觉得扁平结构不再是最好的了。是时候把一些文件移到它们自己的包里了。

准则2:创建子包

据我所知,主要有三种模式:直接在根部,在pkg文件夹下,以及在internal文件夹下。

在根部

在根目录下创建一个带有软件包名称的文件夹,并将所有相关文件移到该文件夹下。这样做的好处是:

  • 没有深层次/嵌套的目录
  • 导入路径不杂乱

缺点是根文件夹会变得有点乱,特别是当有其他文件夹如scripts、bin和docs时。

在pkg包下

创建一个名为pkg的目录,把子包放在它下面。好的方面是:

  • 这个名字清楚地表明这个目录包含了子包
  • 你可以保持顶层的清洁

而不好的方面是你需要在导入路径中有pkg,这并不意味着什么,因为很明显你在导入包。
然而,这种模式有一个更大的问题,也是前一种模式的问题:有可能从版本库外部访问子包。
这对私人仓库来说是可以接受的,因为如果发生这种情况,在审查过程中会被注意到,但重要的是要注意什么是公开的,特别是在开放源码的背景下,向后兼容性很重要。一旦你把它公开,你就不能轻易改变它。
有第三个选择来处理这种情况。

在internal包下

如果/internal在导入路径中,go处理包的方式有点不同。如果软件包被放在/internal文件夹下,只有共享/internal之前的路径的软件包才能访问里面的软件包。
例如,如果软件包路径是/a/b/c/internal/d/e/f,只有/a/b/c目录下的软件包可以访问/internal目录下的软件包。这意味着如果你把internal放在根目录下,只有该仓库内的包可以使用子包,而其他仓库不能访问。如果你想拥有子包,同时保持它们的API在内部,这很有用。

准则3:将main移至cmd目录下

把主包放在cmd/<命令名称>目录下也是一种常见的做法。
假设我们有一个用go编写的管理个人笔记的API服务器,用这种模式看起来会是这样。

$ tree
.
├── cmd
│    └── personal-note-api
│        └── main.go
...
├── Makefile
├── go.mod
└── go.sum

要考虑使用这种模式的情况是:

  • 你可能想在一个资源库中拥有多个二进制文件。你可以在cmd下创建任意多的文件夹,只要你想。
  • 有时需要将主包移到其他地方,以避免循环依赖。

准则4:按其责任组织包装

我们已经研究了何时以及如何制作子包,但还有一个大问题:它们应该如何分组?我认为这是最棘手的部分,需要一些时间来适应,主要是因为它在很大程度上受应用程序的领域关注和功能影响。深入了解代码的作用是做出决定的必要条件。
对此,最常见的建议是按照责任来组织。
对于那些熟悉MVC框架的人来说,拥有"model"、“controller”、“service"等包可能感觉很自然。建议不要在go中使用它们。
相反,我们建议使用更多的责任/领域导向的包名,如"用户"或"事务”。

准则5:按依赖关系对子包进行分组

根据它们的依赖关系来命名包,例如"redis"、“kafka"或"pubsub”,在某些情况下提供了明确的抽象性。
想象一下,你有一个这样的接口:

package bestpracticetype User struct {}type UserService interface {User(context.Context, string) (*User, error)
}

而你在redis子包里有一个服务,它是这样实现的:

package redisimport ("github.com/thirdfort/go-bestpractice""github.com/thirdfort/go-redis"
)type UserService struct {...
}func (s *UserService) User(ctx context.Context, id string) (*bestpractice.User, error) {...err := redis.RetrieveHash(ctx, k)...
}

如果消费者(大概是主函数)只依赖于接口,它可以很容易地被替代的实现所取代,如postgres或inmemory。

附加提示1:给包起一个简短的名字

关于命名包的几个要点。

  • 短而有代表性的名称
  • 使用一个词
  • 使用缩略语,但不要让它变得神秘莫测

如果你想使用多个词(如billing_account)怎么办?我能想到的选项是:

  • 为每个词设置一个嵌套包:billing/account
  • 如果没有混淆,就简单地命名为帐户
  • 使用缩略语:billacc

补充提示2:避免重复

这是关于如何命名包内的内容(结构/界面/函数)。go的建议是,在消费包的时候尽量避免重复。例如,如果我们有一个包,内容是这样的:

package userfunc GetUser(ctx context.Context, id string) (*User, error) {...
}

这个包的消费者要这样调用这个函数:user.GetUser(ctx, u.ID)
在函数调用中出现了两次user这个词。即使我们把user这个词从函数中去掉:user.Get,仍然可以看出它返回了一个用户,因为从包的名称中可以看出。go更倾向于简单的名字。
我希望这些准则在决定包的布局时能有所帮助。
让我们来看看关于上下文的第二个实践。

实践2:熟悉context.Context

在95%的情况下,你唯一需要做的就是将调用者提供的上下文传递给需要上下文作为参数的子程序调用。

func (u *User) Store(ctx context.Context) error {...if err := u.Hash.Store(ctx, k, u); err != nil {return err}...
}

尽管如此,由于context在go程序中随处可见,因此了解何时需要它,以及如何使用它是非常重要的。

context的三种用途

首先,也是最重要的一点是,要意识到上下文可以有三种不同的用途:

  • 发送取消信号
  • 设置超时
  • 存储/检索请求的相关值

发送取消信号

context.Context提供了一种机制,可以发送一个信号,告诉收到context的进程停止。
例如,优雅关机
当一个服务器收到关闭信号时,它需要"优雅地"停止;如果它正在处理一个请求,它需要在关闭之前为其提供服务。context包提供了context.WithCancel API,它返回一个配置了cancel的新上下文和一个取消它的函数。如果你调用cancel函数,信号会被发送到接收该上下文的进程中。
在下面的例子中,它调用context.WithCancel后,在启动服务器时将其传递给服务器。当程序收到OS信号时,会调用cancel:

func main() {ctx, cancel := context.WithCancel(context.Background())defer cancel()go func() {sigchan := make(chan os.Signal, 1)signal.Notify(sigchan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)<-sigchancancel()}()svr := &ctxpkg.Server{}svr.Run(ctx) // ← long running processlog.Println("graceful stop")
}

让我们看看"伪"服务器的实现;它实际上什么也没做,但为了演示,它有足够的功能:

type Server struct{}func (s *Server) Run(ctx context.Context) {for {select {case <-ctx.Done():log.Println("cancel received, attempting graceful stop...")// clean up processreturndefault:handleRequest()}}
}func handleRequest() {time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
}

它首先进入一个无限的循环。在这个循环中,它检查上下文是否已经在ctx.Done()通道上使用select取消了。如果取消了,它就清理进程并返回。如果没有,它就处理一个请求。一旦请求被处理,它就回到循环中,再次检查上下文。
这里的重点是通过使用context.Context,你可以允许进程在他们准备好的时候返回。

设置超时

第二种用法是为操作设置超时。想象一下,你正在向第三方发送HTTP请求。如果由于某些原因,如网络中断,请求的时间超过预期,你可能想取消请求,以防止整个过程挂起。通过context.WithTimeout,你可以为这些情况设置超时。

func main() {ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel() // ← cancel should be called even if timeout didn't happenSendRequest(ctx) // ← subroutine that can get stuck
}

在SendRequest方法中,在不同的goroutine中发送请求后,它同时在ctx.Done()通道和响应通道中等待。当超时发生时,你会从ctx.Done()通道得到一个信号,这样你就可以从该函数中退出,而不用等待响应。

func SendRequest(ctx context.Context) {respCh := make(chan interface{}, 1)go sendRequest(respCh)select {case <-ctx.Done():log.Println("operation timed out!")case <-respCh:log.Println("response received")}
}func sendRequest(ch chan<- interface{}) {time.Sleep(60 * time.Second)ch <- struct{}{}
}

context包也有context.WithDeadline();不同的是,context.WithTimeout需要time.Duration,而context.WithDeadline()需要time.Time。

存储/检索请求的相关值

上下文的最后一种用法是在上下文中存储和检索与请求相关的值。例如,如果服务器收到一个请求,你可能希望在请求过程中产生的所有日志行都有请求信息,如路径和方法。在这种情况下,你可以创建一个日志记录器,设置请求相关的信息,并使用context.WithValue将其存储在上下文中。

var logCtxKey = &struct{}{}func handleRequest(w http.ResponseWriter, r *http.Request) {method, path := r.Method, r.URL.Pathlogger := log.With().Str("method", method).Str("path", path).Logger()ctxWithLogger := context.WithValue(r.Context(), logCtxKey, logger)...accessDatabase(ctxWithLogger)
}

在某个地方,你可以用同样的键把记录器从上下文中取出来。例如,如果你想在数据库访问层留下一个日志,你可以这样做:

func accessDatabase(ctx context.Context) {logger := ctx.Value(logCtxKey).(zerolog.Logger)logger.Debug().Msg("accessing database")
}

这产生了以下包含请求方法和路径的日志行。

{"level":"debug","method":"GET","path":"/v1/todo","time":"2022-11-15T15:44:53Z","message":"accessing database"}

就像我说的,你需要使用这些上下文API的情况并不常见,但了解它的作用真的很重要,这样你就知道在哪种情况下你真的需要注意它。
让我们进入最后一个实践。

实践3:了解 Table Driven Test(表格驱动方法)

表驱动测试是一种组织测试的技术,更多地关注输入数据/模拟/存根和预期输出,而不是断言,这有时可能是重复的。
我选择这种方法的原因不仅是因为这是一种常用的做法,而且这也使我在编写测试时更有乐趣。在编写测试时有一个良好的动机,对于有一个快乐的编码生活是非常重要的,不用说编写可靠的代码。
让我们来看看一个例子。
假设我们有一个餐厅的数据类型,它有一个方法,如果它在某一特定时间开放,则返回真。

type Restaurant struct {openAt  time.TimecloseAt time.Time
}func (r Restaurant) IsOpen(at time.Time) bool {return (at.Equal(r.openAt) || at.After(r.openAt)) &&(at.Equal(r.closeAt) || at.Before(r.closeAt))
}

让我们为这个方法写一些测试。

如果我们在餐厅开门的时候访问了它,我们期望它是开放的。

func TestRestaurantJustOpened(t *testing.T) {r := Restaurant{openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),}input := r.openAtgot := r.IsOpen(input)assert.True(t, got)
}

到目前为止还不错。让我为边界条件添加更多测试:

func TestRestaurantBeforeOpen(t *testing.T) {r := Restaurant{openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),}input := r.openAt.Add(-1 * time.Second)got := r.IsOpen(input)assert.False(t, got)
}func TestRestaurantBeforeClose(t *testing.T) {r := Restaurant{openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),}input := r.closeAtgot := r.IsOpen(input)assert.True(t, got)
}

你可能已经注意到,这些测试之间的差异非常小,我认为这是表驱动测试的一个典型用例。

表驱动测试的介绍

现在让我们看看,如果用表驱动的方式来写,会是什么样子:

func TestRestaurantTableDriven(t *testing.T) {r := Restaurant{openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),}// test casescases := map[string]struct {input time.Timewant  bool}{"before open": {input: r.openAt.Add(-1 * time.Second),want:  false,},"just opened": {input: r.openAt,want:  true,},"before close": {input: r.closeAt,want:  true,},"just closed": {input: r.closeAt.Add(1 * time.Second),want:  false,},}for name, c := range cases {t.Run(name, func(t *testing.T) {got := r.IsOpen(c.input)assert.Equal(t, c.want, got)})}
}

首先,我声明了测试目标。根据情况,它可以在每个测试案例里面。
接下来,我定义了测试用例。我在这里使用了map,所以我可以使用测试名称作为map键。测试用例结构包含每个情况下的输入和预期输出。
最后,我对测试用例进行了循环,并对每个测试用例运行了子测试。断言与之前的例子相同,但这里我从测试用例结构中获取输入和预期值。
以表格驱动方式编写的测试很紧凑,重复性较低,如果你想添加更多的测试,你只需要添加一个新的测试用例,无需更多的断言。

去尝试吧!

一方面,了解社区中共享的实践很重要。go社区足够大,很容易找到它们。你可以找到博客文章、讲座、YouTube视频等等。另外,说到go,很多实践都来自go的标准库。表驱动测试就是一个很好的例子。go是一种开源语言。阅读标准包代码是个好主意。
另一方面,仅仅知道它们并不能让你感到舒服。到目前为止,学习最佳实践的最好方法是在你现在工作的真实代码库中使用它们,看看它们有多合适,这实际上是我学习go实践的方式。所以,多写go,不要害怕犯错。

相关文章:

go最佳实践:如何舒适地编码

什么是 "最佳 "做法&#xff1f; 有很多做法&#xff1a;你可以自己想出来&#xff0c;在互联网上找到&#xff0c;或者从其他语言中拿来&#xff0c;但由于其主观性&#xff0c;并不总是容易说哪一个比另一个好。”最佳”的含义因人而异&#xff0c;也取决于其背景…...

C# 消息队列、多线程、回滚、并行编程、异步编程、反射

消息队列 消息队列是一种在应用程序之间传递消息的异步通信机制。它可以使应用程序解耦并提高系统的可伸缩性和可靠性。在 C# 中&#xff0c;你可以使用多个消息队列技术&#xff0c;其中一种广泛使用的技术是 RabbitMQ。 RabbitMQ 是一个开源的消息代理&#xff0c;实现了高…...

汇编代码生成和编译器的后端

1.前置程序&#xff1a;语义分析和中间代码生成 基于SLR(1)分析的语义分析及中间代码生成程序-CSDN博客https://blog.csdn.net/lijj0304/article/details/135097554?spm1001.2014.3001.5501 2.程序目标 在前面编译器前端实现的基础上&#xff0c;将所生成的中间代码翻译成某…...

MySQL 8.0中新增的功能(九)

FROM_UNIXTIME()、UNIX_TIMESTAMP()和CONVERT_TZ()的64位支持 根据MySQL 8.0.28版本的更新&#xff0c;FROM_UNIXTIME()、UNIX_TIMESTAMP() 和 CONVERT_TZ() 函数现在在支持64位的平台上处理64位值。这包括64位版本的Linux、MacOS和Windows。在兼容的平台上&#xff0c;UNIX_T…...

QT基础篇(8)QT5模型视图结构

1.概述 QT5的模型视图结构主要包括模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和委托&#xff08;Delegate&#xff09;三个部分。 模型&#xff08;Model&#xff09;&#xff1a;模型是数据的抽象表示&#xff0c;负责存储和管理数据。它可以是自…...

vue3-响应式基础之reactive

reactive() 还有另一种声明响应式状态的方式&#xff0c;即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同&#xff0c;reactive() 将使对象本身具有响应性&#xff1a; 「点击按钮1」 <script lang"ts" setup> import { reactive } from vuec…...

【ceph】如何将osd的内容挂载出来---ceph-objectstore-tool 实现

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…...

怎样实现安全便捷的网间数据安全交换?

数据安全交换是指在数据传输过程中采取一系列措施来保护数据的完整性、机密性和可用性。网间数据安全交换&#xff0c;则是需要进行跨网络、跨网段甚至跨组织地进行数据交互&#xff0c;对于数据的传输要求会更高。 大部分企业都是通过网闸、DMZ区、VLAN、双网云桌面等方式实现…...

微信小程序定义并获取日志/实时log信息

步骤一&#xff1a;开通实时日志 可以在开发者工具->详情->性能质量->实时日志&#xff0c;点击前往&#xff0c;在浏览器打开we分析界面&#xff1a; 也可登录小程序管理后台&#xff0c;点击统计进入we分析&#xff1a; 在we分析界面找到性能质量&#xff0c;打开实…...

海外代理IP怎么用?常见使用问题及解决方案

海外代理IP是指提供全球范围内的代理服务器&#xff0c;代理服务器充当IP与目标网站之间的中介&#xff0c;可以起到安全匿名、提高网速、突破网络壁垒的作用。在使用代理IP的过程中&#xff0c;用户可能会遇到各种挑战&#xff0c;如连接问题、速度慢等。理解这些问题的原因并…...

DP:数位DP

数位DP的大致思想&#xff1a;枚举每一位能选取的合法值。 1. LC 2376 统计特殊整数 说是DP&#xff0c;但实际上状态转移方程挺难写的&#xff0c;毕竟是枚举集合论&#xff0c;这里就不贴状态转移方程了。总体的写法其实是搜索记忆化。之所以称之为DP&#xff0c;是因为&am…...

js逆向第21例:猿人学第20题新年挑战

文章目录 一、前言二、定位加密参数1、定位wasm加密2、反编译wasm3、定位sign加密三、代码实现四、参考文献一、前言 新春福利:抓取这5页的数字,计算加和并提交结果 二、定位加密参数 通过get请求地址可以看到需要搞定参数有page、sign、t如下图: 进入堆栈不难发现这样一…...

贪心+蓝桥杯

原题路径 题目思路 : 思路很简单&#xff0c;肯定是贪心做法&#xff0c;要使总代价最小&#xff0c;需用那些出现次数比avg多的数来替换那些没有出现或者是出现次数少于avg的数, 所以我们存当前数每次出现的代价是多少 ,枚举每一个 0 - 9 之间的数 &#xff0c;如果当前数出现…...

第二篇:新建node项目并运行

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 安装 Node.js&#xff1a;首先&#xff0c;确保你的…...

阳光保险选择OceanBase稳定运行超700天

阳光保险集团成立于 2005 年 7 月&#xff0c;旗下拥有财产保险、人寿保险、信用保证保险、资产管理等多家专业子公司&#xff0c;是全球市场化企业中成长最快的集团公司之一&#xff0c;目前位列中国保险行业前八。随着数字化升级趋势的不断加速&#xff0c;很多企业产生将软硬…...

最强大脑闪电心算草稿1

#include<bits/stdc.h> #include<windows.h> using namespace std; int main() {double speed,n,op,sum0;int ans;srand(time(NULL));cout<<"请输入加(1)/减(2)/加减混合(3):";cin>>op;cout<<"请输入题目数量:";cin>>…...

融优学堂-艺术史

导论4 1.【单选题】根据导论的讲解&#xff0c;下列表述正确的是&#xff08;&#xff09;。&#xff08;1&#xff09;艺术品是因人的活动而被创造出来的人工制品。&#xff08;2&#xff09;许多物品被制造出来时&#xff0c;最初的目的是满足某种实用的用途&#xff0c;而不…...

༺༽༾ཊ—设计-七个-07-原则-模式—ཏ༿༼༻

第七原则&#xff1a;迪米特职责 类与类之间的耦合度尽可能低 换言之&#xff0c;我们可以理解成———只与直接朋友说话&#xff0c;不跟陌生人说话 直接朋友&#xff1a; 通过方法传参传进来的朋友&#xff0c; 类自己的字段&#xff0c; 构造函数进来的也是直接朋友&…...

一篇文章带你搞懂---全排序

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 全排序&#xff08;Permutation&#xff09;是指将一组元素按照一定的顺序进行排列的过程。在计算机科学中&#xff0c;全排序是一…...

提升问题检索的能力

事实上&#xff0c;在信息极度丰富的时代&#xff0c;信息检索和筛选能力格外重要。一些搜索引擎的出现已极大地方便了我们日常的信息检索&#xff0c;此处需要注意的是我们不能仅仅局限于常见的搜索引擎&#xff0c;也需要关注和积累一些专业平台或是具有集成功能的引擎&#…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

数据挖掘是什么?数据挖掘技术有哪些?

目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...