首页 文章 精选 留言 我的

精选列表

搜索[裸金属云服务器],共10000篇文章
优秀的个人博客,低调大师

GaussDB云数据库SQL应用系列-定时任务管理

前言 GaussDB数据库定时任务主要可以用于实现定期的备份、统计信息采集、数据汇总、数据清理与优化等,它是指在指定的时间间隔内自动执行一次或多次SQL语句的程序。 一、GaussDB数据库定时任务介绍 GaussDB数据库兼容Oracle定时任务功能主要通过DBE_TASK高级功能包提供的二次封装接口实现(另可参见GaussDB提供的基础接口PKG_SERVICE,此接口也可实现定时任务创建),主要包括创建定时任务、任务到期自动执行、任务删除、修改任务属性(任务id、任务的关闭开启、任务的触发时间、触发时间间隔、任务内容)等。 创建(提交)一个定时任务: CALL DBE_TASK.ID_SUBMIT (job_id,task_name,start_time,interval_time) job_id:作业号,指定job_id创建任务,其中job_id可用范围为1~32767。 task_name:要执行的SQL语句。支持一个或多个‘DDL’(不支持DB相关操作),‘DML’,‘匿名块’,‘调用存储过程的语句’或4种混合的场景。 start_time:任务的开始时间,可以使用sysdate表示立即执行。 interval_time:任务的执行间隔,例如'interval ''1 day'''表示每天执行一次。 需要注意的是,该功能需要在具有相应权限的数据库会话中运行,并且需要替换相应的任务名称和其他参数。此外,该功能只是提交一个定时任务,并不会立即执行任务。任务的执行时间由start_time和interval_time参数控制。 二、创建GaussDB定时任务(场景+示例) 根据GaussDB数据库定时任务应用场景,创建测试表及定时任务。 1、设置应用场景 数据备份和恢复:例如,根据业务需要,按天备份数据库中的一张日销售信息表(累全量)。(Tips:以下是模拟一个简单的业务场景,实际生产环境时,对应的字段、更优的业务逻辑处理方式,请以真实场景为准。) 1)创建测试表 创建一张日销售信息表(sell_info)和一张销售信息全量表(sell_info_full),其中包含字段有:销售编号、销售日期、商品编号、商品名称、商品数量、商品售价。全量表sell_info_full比表sell_info多一个任务执行日期字段(etl_date)。 --日销售信息表(sell_info) CREATE TABLE sell_info ( sell_id int PRIMARY KEY ,sell_date date not null ,goods_id char(20) not null ,goods_name char(20) not null ,goods_number int not null ,sell_goods_amount int not null ); --插入一条测试数据 INSERT INTO sell_info(sell_id,sell_date,goods_id,goods_name,goods_number,sell_goods_amount) VALUES (1001,date'2023-06-02','g_001','g_001',10,10); --销售信息全量表(sell_info_full), CREATE TABLE sell_info_full ( sell_id int PRIMARY KEY ,sell_date date not null ,goods_id char(20) not null ,goods_name char(20) not null ,goods_number int not null ,sell_goods_amount int not null ,etl_date date not null ); 2)创建存储过程,用于定时任务调度 --创建存储过程 sell_info_full_pro CREATE PROCEDURE sell_info_full_pro() IS BEGIN --累加存放每日销售信息数据 INSERT INTO sell_info_full( sell_id ,sell_date ,goods_id ,goods_name ,goods_number ,sell_goods_amount ,etl_date) SELECT sell_id ,sell_date ,goods_id ,goods_name ,goods_number ,sell_goods_amount ,sell_date as etl_date FROM sell_info; --清空日销售信息表 TRUNCATE TABLE sell_info; END; / 3)创建定时任务 任务目标:通过创建定时任务, 每天在销售系统关闭后(22:00)进行累全操作,假设job启动日期是2023-06-02,则提交一个定时任务如下: call dbe_task.id_submit(1,'call sell_info_full_pro();','2023-06-02 22:00:00','interval ''1 day''') 4)查看已创建的任务 select job,dbname,start_date,last_date,this_date,next_date,broken,status,interval,failures,what from my_jobs; 其中字段及其参数说明见下文第3节第1点。 5)查看执行结果 根据业务逻辑,查看相应的结果(此处略)。测试时可将定时“2023-06-02 22:00:00”改成立刻执行“sysdate”,然后查看执行结果。 2、更多适用场景 1)数据清理和优化:例如,定期归档(清理)数据库中的无用数据、过期数据和重复数据,并对数据库进行性能优化,以提高数据库的运行效率和稳定性。 2)数据统计和分析:例如,根据业务需求,定期从数据库中提取数据并进行统计和分析,以便为企业决策提供支持。 3)定时任务通知:例如,通过定时任务向用户发送邮件、短信或其他通知信息,提醒用户关注某些重要的业务活动或事件。 4)定时更新状态:例如,商品定时开售,活动到期自动修改为已结束状态,活动结束商品自动下架,超过三十分钟未支付自动取消订单,到期自动解冻账号。 …… 三、管理GaussDB定时任务 1、查看已创建的任务 通过MY_JOBS视图查看当前用户所属的定时任务详情 --查看当前用户所属的定时任务详情 select job,dbname,start_date,last_date,this_date,next_date,broken,status,interval,failures,what from my_jobs; MY_JOBS视图为当前用户所属定时任务的详细信息,其字段含义参见下表: 名称 类型 描述 job bigint 作业ID。 log_user name 创建者的UserName。 priv_user name 作业执行者的UserName。 dbname name 作业创建数据库名称。 schema_user name 定时任务的默认模式。 start_date timestamp without time zone 作业的开始时间。 start_suc text 作业成功执行的开始时间。 last_date timestamp without time zone 上次运行开始时间。 last_suc text 上次成功运行的开始时间。 last_sec text 上次成功运行的开始时间,提供兼容性支持。 this_date timestamp without time zone 正在运行任务的开始时间。 this_suc text 正在运行任务成功的开始时间。 this_sec text 正在运行任务成功的开始时间,提供兼容性支持。 next_date timestamp without time zone 任务下次执行时间。 next_suc text 任务下次成功执行时间。 next_sec text 任务下次成功执行时间,提供兼容性支持。 total_time numeric 任务最近一次的执行时长。 broken text 如果任务状态为破,则为'y',否则为'n'。 status "char" 执行状态,取值范围:('r', 's', 'f', 'd'),默认为'r',取值含义:r=running、s=successfully finished、f= job failed、d=aborted interval text 用来计算下次运行时间的时间表达式,如果为null则表示定时任务只执行一次。 failures smallint 失败计数,作业连续执行失败16次,不再继续执行。 what text 可执行的作业。 2、停用/启用任务 语法格式: call dbe_task.finish(id, broken, next_time); 1)停用任务, 是将对应任务挂起,不做任何操作。 call dbe_task.finish(1,true); 2)启用任务,则是将任务正常启动, 按任务设置执行相应的SQL语句或程序 call dbe_task.finish(1,false,'2023-06-02 22:00:00'); 3、修改任务 1)DBE_TASK.UPDATE——修改定时任务的属性,包括任务内容、下次执行时间、执行间隔。 2)另,可分别且单独修改其参数内容: DBE_TASK.CONTENT ——修改可执行的SQL语句块 DBE_TASK.NEXT_TIME——修改下次执行时间属性 DBE_TASK.INTERVAL——修改执行时间间隔 4、删除任务 DBE_TASK.CANCEL——删除指定的定时任务。 小结:以上所有管理过程,均以任务作业号为唯一键进行操作。 四、总结 GaussDB数据库提供了丰富的定时任务功能。需要注意的是,定时任务的设置和使用需要谨慎,避免误删重要数据或影响业务正常运行。同时,也需要保证定时任务的可靠性和稳定性,避免出现任务漏执行或执行重复的情况。 总之,在使用GaussDB数据库时,创建定时任务可以在很多场景中应用,同时可以帮助我们提高系统的运行效率和方便性等。 ——结束

优秀的个人博客,低调大师

20个Golang片段让我不再健忘 | 京东云技术团队

前言 本文使用代码片段的形式来解释在go语言开发中经常遇到的小功能点,由于本人主要使用java开发,因此会与其作比较,希望对大家有所帮助。 1. hello world 新手村的第一课,毋庸置疑。 package main import "fmt" func main() { fmt.Printf("hello world") } 2. 隐形初始化 package main import "fmt" func main() { load() } func load() { fmt.Printf("初始化..手动%s 不错\n", "1") } func init() { fmt.Printf("隐形初始化。。\n") } 在 go 中定义init函数,程序在运行时会自动执行。类似使junit的@before注解。 3. 多模块的访问 java中package包的概念,go是通过文件夹 +package关键字来定义的。 一般而言,我们会通过go init来创建项目,生成的go.mod文件位于根目录。 常见的实践是,创建文件夹并且保持 package 名称与文件夹保持一致。这样import的永远是文件夹,遵循以上规则则意味着文件夹的名称即为模块名。 同一个package可以创建多个.go文件,虽然分布在不同的文件中。但是他们中的方法名称不能相同。需要注意,这里与java中不同类中方法可以重名不同。 此外,也没有诸如private、protected、public等包访问权限关键字。只要定义的函数首字母为大写。则可以被外部成功调用。 来看一下示例: go-tour └── ch3 ├── model │ └── test │ │ ├── testNest.go │ └── helper.go │ └── helper2.go │ └── main.go └── go.mod 此处,ch3、model、test均为文件夹,也可以说是package。helper.go位于model下,它的代码如下: package model import "fmt" var AppName = "bot" var appVersion = "1.0.0" func Say() { fmt.Printf("%s", "hello") } func init() { fmt.Printf("%s,%s", AppName, appVersion) } 再来看看main.go package main import ( "ch3/model" "ch3/model/test" ) func main() { model.Say() } 显然它的调用是通过packageName.MethodName()来使用的。需要注意的是,一个go.mod下只能有一个main包。 4. 引用外部库 和java的maven类似,go几经波折也提供了官方仓库。如下,通过go get github.com/satori/go.uuid命令即可安装uuid库,未指定版本,因此下载的为最新版本。 使用时是这样的: package main import ( "fmt" uuid "github.com/satori/go.uuid" ) func main() { uuid := uuid.NewV4() fmt.Printf("%s", uuid) } 5. 数组字典和循环 直接看代码就是了。 package main import "fmt" var item []int var m = map[int]int{ 100: 1000, } var m2 = make(map[int]int) func main() { for i := 0; i < 10; i++ { item = append(item, i) m[i] = i m2[i] = i } for i := range item { fmt.Printf("item vlaue=%d\n", i) } for key, value := range m { fmt.Printf("m:key=%d,value=%d\n", key, value) } for _, value := range m2 { fmt.Printf("m2:value=%d\n", value) } } := 的形式只能在方法内 全局的只能用 var x=.. map输出没有顺序 6. 结构体和JSON go中通过struct来定义结构体,你可以把它简单理解为对象。一般长这样。 type App struct { AppName string AppVersion string `json:"app_version"` appAuthor string "pleuvoir" DefaultD string "default" } 我们经常在java程序中使用fastjson来输出JSON字符串。go中自带了这样的类库。 package main import ( app2 "app/app" //可以定义别名 "encoding/json" "fmt" ) func main() { a := app2.App{} fmt.Printf("%s\n", a) app := app2.App{AppName: "bot", AppVersion: "1.0.1"} json, _ := json.Marshal(app) //转换为字符串 fmt.Printf("json is %s\n", json) } 结构体中JSON序列化不会转变大小写,可以指定它输出的key名称通过 json:xxx 的描述标签。 结构体中的默认值赋值了也不展示 7. 异常处理 作为一个有经验的程序员:),go的异常处理涉及的很简单,也往往为人所诟病。比如满屏幕的err使用。 package main import ( "fmt" "os" ) func _readFile() (int, error) { file, err := os.ReadFile("test.txt") if err != nil { fmt.Printf("error is = %s\n", err) return 0, err } fmt.Printf("file = %s \n", file) return len(file), err } func readFile() (int, error) { fileLength, err := _readFile() if err != nil { fmt.Printf("异常,存在错误 %s\n", err) } return fileLength, err } func main() { fileLength, _ := readFile() fmt.Printf("%d\n", fileLength) } 和java不同,它支持多返回值,为我们的使用带来了很多便利。如果不需要处理这个异常,可以使用_忽略。 8. 异步 千呼万唤始出来,令人兴奋的异步。 package main import ( "bufio" "fmt" "os" ) func worker() { for i := 0; i < 10; i++ { fmt.Printf("i=%d\n", i) } } func main() { go worker() go worker() //阻塞 获取控制台的输出 reader := bufio.NewReader(os.Stdin) read, err := reader.ReadBytes('\n') //注意是单引号 回车后结束控制台输出 if err != nil { fmt.Printf("err is =%s\n", err) return } fmt.Printf("read is %s \n", read) } 如此的优雅,如此的简单。只需要一个关键字go便可以启动一个协程。我们在java中经常使用的是线程池,而在go中也存在协程池。据我观察,部分协程池benchmark的性能确实比官方语言关键字高很多。 9. 异步等待 这里就类似java中使用countdownLatch等关键字空值并发编程中程序的等待问题。 package main import ( "fmt" "sync" "time" ) func upload(waitGroup *sync.WaitGroup) { for i := 0; i < 5; i++ { fmt.Printf("正在上传 i=%d \n", i) } time.Sleep(5 * time.Second) waitGroup.Done() } func saveToDb() { fmt.Printf("保存到数据库中\n") time.Sleep(3 * time.Second) } func main() { begin := time.Now() fmt.Printf("程序开始 %s \n", begin.Format(time.RFC850)) waitGroup := sync.WaitGroup{} waitGroup.Add(1) go upload(&waitGroup) go saveToDb() waitGroup.Wait() fmt.Printf("程序结束 耗时 %d ms ", time.Now().UnixMilli()-begin.UnixMilli()) } sync包类似于J.U.C包,里面可以找到很多并发编程的工具类。sync.WaitGroup便可以简简单单认为是countdownLatch吧。也不能多次调用变为负数,否则会报错。 注意,这里需要传入指针,因为它不是一个引用类型。一定要通过指针传值,不然进程会进入死锁状态。 10. 管道 package main import ( "fmt" "sync" ) var ch = make(chan int) var sum = 0 //是线程安全的 func consumer(wg *sync.WaitGroup) { for { select { case num, ok := <-ch: if !ok { wg.Done() return } sum = sum + num } } } func producer() { for i := 0; i < 10_0000; i++ { ch <- i } close(ch) //如果不关闭则会死锁 } func main() { wg := sync.WaitGroup{} wg.Add(1) go producer() go consumer(&wg) wg.Wait() fmt.Printf("sum = %d \n", sum) } 这里演示的是什么呢?管道类似一个队列,进行线程间数据的传递。当关闭时消费端也退出,如果没关闭管道,运行时会报死锁。可以看出全局变量在线程间是安全的。 可以衍生出一种固定写法: //固定写法 func consumer(wg *sync.WaitGroup) { for { select { case num, ok := <-ch: if !ok { wg.Done() return } sum = sum + num } } } 11. 接口 package main import "fmt" type Person interface { Say() SetName(name string) } type ZhangSan struct { Value string } func (z *ZhangSan) Say() { fmt.Printf("name=%s", z.Value) } func (z *ZhangSan) SetName(name string) { z.Value = name + ":hehe" } func main() { zhangSan := ZhangSan{} zhangSan.SetName("pleuvoir") zhangSan.Say() } 如上的程序演示了接口的使用。 go的接口没有强依赖 通过结构体 + 方法的形式实现,注意方法传入的可以是引用也可以是值 12. 锁 package main import ( "fmt" "sync" ) type Number struct { Value int mutex sync.Mutex //加锁 } func (receiver *Number) Add() { receiver.mutex.Lock() defer receiver.mutex.Unlock() //退出时会执行 receiver.Value = receiver.Value + 1 //fmt.Printf("add\n") } func (receiver *Number) Get() int { receiver.mutex.Lock() defer receiver.mutex.Unlock() return receiver.Value } func main() { number := Number{Value: 0} wg := sync.WaitGroup{} n := 100_0000 wg.Add(n) for i := 0; i < n; i++ { go func(wg *sync.WaitGroup) { number.Add() wg.Done() }(&wg) } wg.Wait() fmt.Printf("count=%d", number.Get()) } 这里是什么?显然就像是显示锁的ReentrantLock的使用,相信大家都能看懂。这里出现了新关键字defer,我暂且是理解为finally。不知道你怎么看? 13. 读写配置文件 这也是一个很常规的功能,看看怎么实现。 package main import ( "encoding/json" "fmt" "os" ) type Preferences struct { Name string `json:"name"` Version float64 `json:"version"` } const configPath = "config.json" func main() { preferences := Preferences{Name: "app", Version: 100.01} marshal, err := json.Marshal(preferences) err = os.WriteFile(configPath, marshal, 777) if err != nil { fmt.Printf("写入配置文件错误,%s\n", err) return } //读取配置文件 file, err := os.ReadFile(configPath) if err != nil { fmt.Printf("读取文件错误,%s\n", err) return } fmt.Printf("%s\n", file) //{"name":"app","version":100.01} //构建一个对象用来序列化 readConfig := Preferences{} //反序列化 err = json.Unmarshal(file, &readConfig) if err != nil { fmt.Printf("配置文件转换为JSON错误,%s\n", err) } fmt.Printf("%v", readConfig) //{app 100.01} 这里挺没意思的,写入JSON字符串,然后读取回来在加载到内存中。不过,简单的示例也够说明问题了。 14. 宕机处理 这是类似于一种最上层异常捕获的机制,在程序的入口处捕获所有的异常。 package main import ( "fmt" "time" ) func worker() { //defer func() { //不能写在主函数,最外层catch没啥用 // if err := recover(); err != nil { // fmt.Printf("%s", err) // } //}() defer recovery() panic("严重错误") } func recovery() { if err := recover(); err != nil { fmt.Printf("死机了。%s\n", err) } } func main() { for true { worker() time.Sleep(1 * time.Second) } } 注释写的很清楚,聪明的你一看就懂。 15. 单元测试 与java不同,go建议单元测试文件尽可能的离源代码文件近一些。比如这样: go-tour └── main.go └── main_test.go 并且它的命名也是这样简单粗暴: package main import ( "testing" ) func TestInit(t *testing.T) { t.Log("heh") helper := PersonHelper{} helper.init("pleuvoir") t.Log(helper.Name) } 以大写的Test开头,文件名称以_test结尾,很清爽的感觉。 16. 启动传参 这也是一个很常用的知识点。这里有两种方式: 直接传 使用 flag package main import ( "encoding/json" "flag" "fmt" "os" ) func main() { //第一种方式 args := os.Args for i, arg := range args { println(i, arg) } //第二种方式 config := struct { Debug bool Port int }{} flag.BoolVar(&config.Debug, "debug", true, "是否开启debug模式") flag.IntVar(&config.Port, "port", 80, "端口") flag.Parse() json, _ := json.Marshal(config) fmt.Printf("json is %s\n", json) } 我建议使用第二种,更便捷自带类型转换,还可以给默认值,非常好。 17. 优雅退出 package main import ( "fmt" "os" "os/signal" "syscall" ) func quit() { println("执行一些清理工作。。") } //正常的退出 //终端 CTRL+C退出 //异常退出 func main() { defer quit() println("进来了") //读取信号,没有一直会阻塞住 exitChan := make(chan os.Signal) //监听信号 signals := make(chan os.Signal) signal.Notify(signals, syscall.SIGINT, syscall.SIGQUIT) go func() { //有可能一次接收到多个 for s := range signals { switch s { case syscall.SIGINT, syscall.SIGQUIT: println("\n监听到操作系统信号。。") quit() //如果监听到这个信号没处理,那么程序就不会退出了 if i, ok := s.(syscall.Signal); ok { value := int(i) fmt.Printf("是信号类型,准备退出 %d", value) } else { println("不知道是啥,0退出") os.Exit(0) } // os.Exit(value) exitChan <- s } } }() println("\n程序在这里被阻塞了。") <-exitChan //panic("heh") println("\n阻塞被终止了。") } 这其实是在监听操作系统的信号,java中也有类似的回调的接口(我忘了名字)。 18. 反射 作为一门高级语言,反射肯定是有的。还是使用reflect包。 package main import ( "fmt" "reflect" ) type Person struct { Name string `json:"name"` } func (p *Person) SetName(name string) { p.Name = name } func (p *Person) GetName() (string, string) { return p.Name, "1.0.1" } func worker1() { p := Person{} p.SetName("pleuvoir") name, _ := p.GetName() fmt.Printf(name) } // 获取方法 func worker2() { p := Person{} rv := reflect.ValueOf(&p) value := []reflect.Value{reflect.ValueOf("peluvoir")} rv.MethodByName("SetName").Call(value) values := rv.MethodByName("GetName").Call(nil) for i, v := range values { fmt.Printf("\ni=%d,value=%s\n", i, v) } } func worker3() { s := Person{} rt := reflect.TypeOf(s) if field, ok := rt.FieldByName("Name"); ok { tag := field.Tag.Get("json") fmt.Printf("tag is %s \n", tag) } } func main() { //正常获取 worker1() //获取方法 worker2() //获取标签 worker3() } 没什么好说的,写代码全靠猜。 19. atomic 类似java中的atomic原子变量。 package main import ( "fmt" "sync" "sync/atomic" ) func main() { workers := 1000 wg := sync.WaitGroup{} wg.Add(workers) for i := 0; i < workers; i++ { go worker2(&wg) } wg.Wait() fmt.Printf("count = %d", count) } var count int64 = 0 func worker1(wg *sync.WaitGroup) { count++ wg.Done() } func worker2(wg *sync.WaitGroup) { atomic.AddInt64(&count, 1) //特别简单 wg.Done() } 真的是特别简单。 20. 线程安全的Map 类似于ConcurrentHashMap,与普通的api有所不同。 var sessions = sync.Map{} sessions.Store(uuid, uuid) load, ok := sessions.Load(value.Token) if ok { // 做你想做的事情 } 21. return func 这里就是函数式变成的例子了。函数是一等公民可以作为参数随意传递。java什么时候能支持呢? package main import "fmt" func main() { engine := Engine{} engine.Function = regular() function := engine.Function for i := 0; i < 3; i++ { s := function("pleuvoir") fmt.Printf("s is %s\n", s) } } type Engine struct { Function func(name string) string } func regular() (ret func(name string) string) { fmt.Printf("初始化一些东西。\n") return func(name string) string { fmt.Printf("我是worker。name is %s\n", name) return "我是匿名函数的返回值" } } 比如这里,如果要初始化日志什么。最后需要让框架在哪里打印日志,就需要将这个初始化的日志实例传递过去。总而言之,言而总之。会需要让代码各种传递。 这种方式在于第一次调用的时候会执行上面的代码片段,后面只是保存了这个函数的句柄,然后可以一直调用这个匿名函数。 22. context package main import ( "context" "fmt" "time" ) func main() { worker1() } func worker1() { //总共2秒超时 value := context.WithValue(context.Background(), "token", "pleuvoir") timeout, cancelFunc := context.WithTimeout(value, 5*time.Second) defer cancelFunc() //模拟任务 fmt.Println("开始任务") deep := 10 go handler(timeout, deep) fmt.Println("开始阻塞", time.Now()) //等待主线程超时,阻塞操作 select { case <-timeout.Done(): fmt.Println("阻塞结束", timeout.Err(), time.Now()) } } // 模拟任务处理,循环下载图片等 func handler(timeout context.Context, deep int) { if deep > 0 { fmt.Printf("[begin]token is %s %s deep=%d\n", timeout.Value("token"), time.Now(), deep) time.Sleep(1 * time.Second) go handler(timeout, deep-1) } //下面的哪个先返回 先执行哪个 //如果整体超时 或者 当前方法超过2秒 就结束 select { //等待超时会返回 case <-timeout.Done(): fmt.Println("超时了。", timeout.Err()) //等待这么久 然后会返回 这个函数可不是比较时间,这里其实是在模拟处理任务,固定执行一秒 和休息一秒效果一样 //但是休息一秒的话就不会实时返回了,所以这里实际应用可以是一个带超时的回调? case <-time.After(time.Second): fmt.Printf("[ end ]执行完成耗时一秒 %s %d\n", time.Now(), deep) } } 作用:在不同的协程中传递上下文。 传值 类似于threadLocal 可以使用超时机制,无论往下传递了多少协程,只要最上层时间到了 后面的都不执行 俄罗斯套娃一次一层包装 23. 字符串处理 这是最高频率的操作了,使用任何语言都无法错过。 package main import ( "fmt" "strings" ) func main() { str := " pleuvoir " trimSpace := strings.TrimSpace(str) fmt.Printf("去除空格 %s\n", trimSpace) subString := trimSpace[4:len(trimSpace)] fmt.Printf("subString after is %s\n", subString) prefix := strings.HasPrefix(subString, "vo") fmt.Printf("是否有前缀 vo : %v\n", prefix) suffix := strings.HasSuffix(subString, "ir") fmt.Printf("是否有后缀 ir : %v\n", suffix) builder := strings.Builder{} builder.WriteString("hello") builder.WriteString(" ") builder.WriteString("world") fmt.Printf("stringBuilder append is %s\n", builder.String()) eles := []string{"1", "2"} join := strings.Join(eles, "@") fmt.Printf("join after is %s\n", join) //拼接格式化字符串,并且能返回 sprintf := fmt.Sprintf("%s@%s", "1", "20") fmt.Printf("Sprintf after is %s\n", sprintf) //打印一个对象 比较清晰的方式 person := struct { Name string Age int }{"pleuvoir", 18} fmt.Printf("%v", person) // 输出 {Name:pleuvoir Age:18} } 主要是使用fmt包。 24. 任务投递 如果说使用go最激动人心的是什么?是大量的协程。如果在下载任务中,我们可以启动很多协程进行分片下载。如下,即展示使用多路复用高速下载。 package main import ( "fmt" "sync" "time" ) func main() { chunks := 10 //文件分成n份 workers := 5 //个线程处理 wg := sync.WaitGroup{} wg.Add(chunks) jobs := make(chan int, chunks) //带缓冲的管道 等于任务数 for i := 0; i < workers; i++ { go handler1(i, jobs, &wg) } //将任务全部投递给worker scheduler(jobs, chunks) wg.Wait() fmt.Println("download finished .") } // 分成 chunks 份任务 里分发 // 将 n 份下载任务都到管道中去,这里管道数量等于 任务数量n 管道不会阻塞 func scheduler(jobs chan int, chunks int) { for i := 0; i < chunks; i++ { //time.Sleep(time.Duration(rand.Intn(3)) * time.Second) jobs <- i } } // 写法2 // 注意这里的是直接接受管道,这也是一种固定写法,下面的 range jobs 可以认为是阻塞去抢这个任务,多个线程都在抢任务 func handler2(workerId int, jobs <-chan int, wg *sync.WaitGroup) { for job := range jobs { // fmt.Printf("workerId[%d] job[%d] start download .\n", workerId, job) time.Sleep(1 * time.Second) fmt.Printf("workerId[%d] job[%d] download ok.\n", workerId, job) wg.Done() //这里不要break,这样执行完当前的线程就能继续抢了 } } // 写法1,select case 多路复用 func handler1(workerId int, jobs chan int, wg *sync.WaitGroup) { for { select { case job, _ := <-jobs: // fmt.Printf("workerId[%d] job[%d] start download .\n", workerId, job) time.Sleep(3 * time.Second) fmt.Printf("workerId[%d] job[%d] download ok.\n", workerId, job) wg.Done() //这里不要break,这样执行完当前的线程就能继续抢了 } } } 后语 以上都是一个新手Gopher的经验总结,文中难免有错误,恳请指正。 作者:京东零售 付伟 来源:京东与开发者社区

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册