1.GOMAXPROCS 与容器的码分相处之道
2.Golang微服务框架Kratos实现分布式计划任务队列Asynq
3.scheduler startåè½åè°ç¨schedulejobå
4.go的actor框架protoactor的底层是怎么实现actor的?
5.Golang 里一个有趣的小细节
GOMAXPROCS 与容器的相处之道
了解Golang中的GOMAXPROCS环境变量及其与容器虚拟化技术如Docker和Kubernetes的相互作用,是码分深入探讨并发处理和资源管理的关键。GOMAXPROCS用于调整Runtime Scheduler中处理器(P)的码分数量,直接影响Golang Runtime的码分并发性能。默认值为CPU核心数,码分而容器技术通过cgroup等隔离资源,码分如何调出源码文件限制CPU使用。码分本文通过实验探索容器技术对GOMAXPROCS的码分影响及其对并发表现的可能影响。
在并发处理中,码分Goroutines是码分Golang的基石,go-scheduler通过处理器(P)、码分机器(M)和goroutine(G)三个抽象来实现并发。码分P类似于CPU核心,码分控制并发M的码分数量,M与P绑定执行。码分M数量动态增长,P数量保持默认CPU核心数,由用户通过GOMAXPROCS调整。pycharm导入源码后
本文采用实验方法,针对Docker和Kubernetes进行验证,探索它们对CPU资源的隔离限制是否影响GOMAXPROCS设定,进而影响并发表现。实验环境包括XPS-笔记本的四核CPU,使用自定义Docker镜像进行测试。
在Kubernetes和Docker环境中,我们观察到,尽管对CPU资源进行了限制,GOMAXPROCS设定仍不受影响。实验结果表明,Kubernetes和Docker的CPU限制策略并不改变Runtime对CPU数量的判定。
性能测试使用上游社区提供的CPU密集型Benchmark concprime,对不同限制手段和GOMAXPROCS取值进行性能分析。结果揭示,尽管限制手段影响了CPU使用,但GOMAXPROCS设定对性能的eos dice 源码分析直接影响不大。
分析指出,Kubernetes和Docker的CFS Bandwidth Control策略限制了CPU使用,但并未影响Runtime对CPU数量的判定。Go程序在Kubernetes中始终认为可以使用所有CPU资源,导致P数量与CPU核心数相同。手动设置GOMAXPROCS后,性能显著提升。
目前,Golang官方尚无有效解决方案避免这一问题,而Uber提出的Workaround(uber-go/automaxprocs)提供了一种修改GOMAXPROCS的实现,根据cgroup或runtime选择合适的取值。这一方法值得尝试。
综上所述,容器技术如Docker和Kubernetes对CPU资源的限制对GOMAXPROCS设定影响不大,但手动调整GOMAXPROCS可以优化并发性能。了解这些相互作用有助于更高效地利用资源和提升并发处理能力。
Golang微服务框架Kratos实现分布式计划任务队列Asynq
任务队列(Task Queue)是KrPano功能化源码一种在跨线程或跨计算机环境中分配任务的机制,其核心是生产者-消费者模型,其中生产者将任务发送至队列,而消费者负责处理这些任务。任务队列的输入是任务(Task),即工作单元,由专门的工作进程持续监视队列以查找新任务。 在Golang语言中,有如Asynq和Machinery等类似于Celery的分布式任务队列。然而,尽管Celery是一个知名的Python分布式任务队列,其他语言环境中的任务队列,如Asynq,也遵循类似的原理和架构。 Asynq是一个使用Go语言实现的分布式任务队列和异步处理库,其设计用于与Redis集成,提供轻量级、易于使用的集运小程序源码API,并支持高扩展性和自定义性。此库由Ken Hibino开发,目前在Google工作。 Asynq由几个关键组件构成,通过使用Asynq,开发人员可以轻松实现异步任务处理,并获得高效率、高可扩展性和高自定义性的解决方案。此库提供命令行工具(CLI)和基于Web的界面(Web UI)以进行监控和管理。 Asynq的核心特点包括: 可视化监控:通过CLI和Web UI进行任务和队列的实时监控。 Web UI:可使用Docker轻松部署。 在微服务框架Kratos中,分布式任务队列可以通过transport.Server的形式集成。目前,Go语言中有两个分布式任务队列可用,且它们已被支持。为了在Kratos中实现此功能,需要安装Redis服务器并通过Docker方式部署。接下来,需要在项目中添加Asynq的依赖库,并创建Server实例。注册任务回调以订阅特定任务类型,最后通过NewTask或NewPeriodicTask创建新任务。 创建任务时,可以使用NewTask或NewPeriodicTask方法,分别对应Asynq.Client和Asynq.Scheduler。普通任务和延迟任务(Delay Task)各有其特点,普通任务可能立即执行(无需排队),而延迟任务则允许在特定时间执行。周期性任务(Periodic Task)通过Crontab实现定时执行,但调度器必须持续运行以确保任务调度。 示例代码可在单元测试文件中找到,以帮助理解和实现Asynq在Kratos框架中的集成。scheduler startåè½åè°ç¨schedulejobå
ã第ä¸æ¥ï¼å¼å
ããè¦ä½¿ç¨Quartzï¼å¿ é¡»è¦å¼å ¥ä»¥ä¸è¿å 个å ï¼
ãã1ãlog4j-1.2.
ãã2ãquartz-2.1.7
ãã3ãslf4j-api-1.6.1.jar
ãã4ãslf4j-log4j-1.6.1.jar
ããè¿äºå é½å¨ä¸è½½çQuartzå éé¢å å«çï¼å æ¤æ²¡æå¿ è¦ä¸ºå¯»æ¾è¿å 个å è头ç¼ã
ãã第äºæ¥ï¼å建è¦è¢«å®æ§è¡çä»»å¡ç±»
ããè¿ä¸æ¥ä¹å¾ç®åï¼åªéè¦å建ä¸ä¸ªå®ç°äºorg.quartz.Jobæ¥å£çç±»ï¼å¹¶å®ç°è¿ä¸ªæ¥å£çå¯ä¸ä¸ä¸ªæ¹æ³execute(JobExecutionContext arg0) throws JobExecutionExceptionå³å¯ãå¦ï¼
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class myJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(sdf.format(new Date()));
}
}
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class myJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(sdf.format(new Date()));
}
}
ããè¿ä¸ªä¾åå¾ç®åï¼å°±ä¸ç¨è§£è¯´äºã
ãã第ä¸æ¥ï¼å建任å¡è°åº¦ï¼å¹¶æ§è¡
ããè¿ä¸æ¥åºè¯¥ç®æ¯æé¾çä¸æ¥çï¼ä½å ¶å®æ¯é常ç®åçï¼ç´æ¥ä¸ä»£ç
ãã
import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public void go() throws Exception {
// é¦å ï¼å¿ éè¦åå¾ä¸ä¸ªSchedulerçå¼ç¨
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
//jobså¯ä»¥å¨scheduledçsched.start()æ¹æ³å被è°ç¨
//job 1å°æ¯éç§æ§è¡ä¸æ¬¡
JobDetail job = newJob(myJob.class).withIdentity("job1", "group1").build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/ * * * * ?")).build();
Date ft = sched.scheduleJob(job, trigger);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(job.getKey() + " 已被å®ææ§è¡äº: " + sdf.format(ft) + "ï¼å¹¶ä¸ä»¥å¦ä¸éå¤è§åéå¤æ§è¡: " + trigger.getCronExpression());
// job 2å°æ¯2åéæ§è¡ä¸æ¬¡ï¼å¨è¯¥åéç第ç§)
job = newJob(myJob.class).withIdentity("job2", "group1").build();
trigger = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule(" 0/2 * * * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " 已被å®ææ§è¡äº: " + sdf.format(ft) + "ï¼å¹¶ä¸ä»¥å¦ä¸éå¤è§åéå¤æ§è¡: "+ trigger.getCronExpression());
// å¼å§æ§è¡ï¼start()æ¹æ³è¢«è°ç¨åï¼è®¡æ¶å¨å°±å¼å§å·¥ä½ï¼è®¡æ¶è°åº¦ä¸å 许æ¾å ¥N个Job
sched.start();
try {
//主线ç¨çå¾ ä¸åé
Thread.sleep(L * L);
} catch (Exception e) { }
//å ³éå®æ¶è°åº¦ï¼å®æ¶å¨ä¸åå·¥ä½
sched.shutdown(true);
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.go();
}
}
go的actor框架protoactor的底层是怎么实现actor的?
探讨Go的actor框架Protoactor底层实现细节,核心在于利用类型为interface的channel进行switch case数据转换,进而调用相应方法。此过程中,依旧依托Go的scheduler来驱动任务执行。Actor模型本质上是CSP的变体,通过这种方式,Protoactor实现高效灵活的并发处理。通过interface的channel,系统能够实现多线程间的无缝数据交互,确保在复杂并发场景下,任务执行的高效与协调。Go的scheduler在此背景下,起到了关键作用,它负责调度任务,确保每一个actor能够有序、高效地执行其负责的任务。通过这种设计,Protoactor能够提供强大而简洁的并发编程模型,适用于多种场景下的并行计算需求。
Golang 里一个有趣的小细节
本文尝试解释在Golang中一个引起卡死的问题。
首先,Golang的byte被alias到uint8上,所以循环条件总是成立,i++导致i溢出,使得循环无法退出。
其次,Goroutine调度是复杂的,基于GPM模型,一个P上挂多个G。当一个G执行结束,P选择下一个G执行。如果一个G执行太久,scheduler会调度后面的G执行。但循环G无法主动让出执行权,即使执行时间长,scheduler已经打上preempt标记。
回到问题,main函数中启动的Goroutine是一个死循环,没有阻塞条件,无法主动让出执行权,即使scheduler已标记。一旦G拿到执行权,其后的G无法再获得P的执行权。为了让G获取执行权,main函数主动执行runtime.Gosched()让出执行权。
P的数量由GOMAXPROCS设置,通常为CPU数量。
问题出现在GC阶段。Golang的GC基于标记-清除,标记阶段需要STW,停止所有正在运行的Goroutine。死循环Goroutine无法停止,main Goroutine阻塞在GC STW这里,等待所有Goroutine停止执行。main Goroutine等待永远不会停止的G,程序因此卡死。
同样,设置GOMAXPROCS时也需STW,使得代码卡死。
这几行代码隐藏着复杂逻辑,揭示了Golang中Goroutine、调度和GC的巧妙之处。