Spring Boot应用关闭分析

优质博文:IT-BLOG-CN

一、使用spring容器的close方法关闭。

可通过在代码中获取SpringContext并调用close方法去关闭容器。

使用SpringApplication的exit方法。

public static int exit(ApplicationContext context, ExitCodeGenerator... exitCodeGenerators) { Assert.notNull(context, "Context must not be null"); int exitCode = 0; try { try { //获取ExitCodeGenerator的Bean并用ExitCodeGenerators管理 ExitCodeGenerators generators = new ExitCodeGenerators(); Collection<ExitCodeGenerator> beans = context .getBeansOfType(ExitCodeGenerator.class).values(); generators.addAll(exitCodeGenerators); generators.addAll(beans); exitCode = generators.getExitCode(); if (exitCode != 0) { // 发布ExitCodeEvent事件 context.publishEvent(new ExitCodeEvent(context, exitCode)); } } finally { close(context); } } catch (Exception ex) { ex.printStackTrace(); exitCode = (exitCode != 0) ? exitCode : 1; } return exitCode; }

上述代码,总的来说就是,获取ExitCodeGenerator的Bean并用ExitCodeGenerators管理, 注意getExitCode()的实现是取ExitCodeGenerator集合中最大的exitCode作为最终exitCode,

最后,关闭容器。

二、exitCode

SpringApplication#exit方法返回的exitCode还需要自行调用System#exit方法去指定。 该System#exit(int code)的参数,能被父进程获取并使用。一般按照惯例0为程序正常退出,非0位不正常退出。 我写了的运行demo:

@Slf4j @SpringBootApplication public class ApplicationMainShutDownBySpringApplication { public static void main(String[] args) { ConfigurableApplicationContext ctx = new SpringApplicationBuilder(ApplicationMainShutDownBySpringApplication.class).build().run(args); int exitCode = SpringApplication.exit(ctx); log.info("exitCode is {}!", exitCode); System.exit(exitCode); } @Bean public ExitCodeGenerator exitCodeGenerator() { return () -> 10; } } 执行window bat: java -jar spring-boot-mvc-shutdown-demo.jar & echo %ERRORLEVEL% 省略其他日志最终输出: 10

可以看最终输出是:10。

二、使用Actuator的shutdown http接口或JMX

可参考Actuator包的ShutdownEndpoint,实质上是调用spring容器的close方法关闭的。

http方式关闭:

JMX方式关闭:

三、kill进程

一般的kill(kill -15)会触发应用在refreshContext时(并且SpringApplication实例的registerShutdownHook为true时)加上的注册到JVM的shutdownhook。

public void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() { @Override public void run() { synchronized (startupShutdownMonitor) { doClose(); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }

四、pidFile生成

spring boot提供ApplicationPidFileWriter类,将运行时pid写入指定文件中。
应用场景:可供kill使用。kill $(cat application.pid)

添加步骤:

【1】增加监听器

org.springframework.context.ApplicationListener=

org.springframework.boot.context.ApplicationPidFileWriter

【2】配置项

spring: pid: fail-on-write-error: true #可通过此文件里的pid去关闭应用 file: ./application.pid

五、spring容器close代码分析

这里对容器关闭进行一些分析,以注释的形式写在下面。

/** * Close this application context, destroying all beans in its bean factory. * <p>Delegates to {@code doClose()} for the actual closing procedure. * Also removes a JVM shutdown hook, if registered, as it's not needed anymore. * @see #doClose() * @see #registerShutdownHook() */ @Override public void close() { synchronized (this.startupShutdownMonitor) { //委托给钩子方法doClose去做 doClose(); // If we registered a JVM shutdown hook, we don't need it anymore now: // We've already explicitly closed the context. if (this.shutdownHook != null) { try { //去掉shutdown hook Runtime.getRuntime().removeShutdownHook(this.shutdownHook); } catch (IllegalStateException ex) { // ignore - VM is already shutting down } } } } protected void doClose() { // Check whether an actual close attempt is necessary... if (this.active.get() && this.closed.compareAndSet(false, true)) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this); } LiveBeansView.unregisterApplicationContext(this); try { // Publish shutdown event. //发布事件,有需要,可写ApplicationListener对ContextClosedEvent事件进行监听,在容器关闭时执行自定义的操作 publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. //触发lifecycle bean的关闭周期 if (this.lifecycleProcessor != null) { try { this.lifecycleProcessor.onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); } } // Destroy all cached singletons in the context's BeanFactory. // 期间会触发@PreDestroy、DisposableBean接口方法、@Bean的destroyMethod等等,具体执行顺序请参照BeanFactory接口的JavaDoc,里面定义了初始化和销毁期的方法执行顺序。 destroyBeans(); // Close the state of this context itself. //目前没做什么操作 closeBeanFactory(); // Let subclasses do some final clean-up if they wish... // 模板方法,比如,AnnotationConfigServletApplicationContext会触发tomcat服务器的关闭和释放 onClose(); // 重置listeners为初始状态,因为在容器启动过程中会对ApplicationListener做了一些更改 if (this.earlyApplicationListeners != null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // 将容器的激活状态设为false this.active.set(false); }

整体流程
列出关闭流程:

五、容器关闭拓展点

上面的示例,容器关闭时的日志如下,

INFO 241764 --- [ main] .w.s.s.ApplicationMainShutDownByActuator : Actuator shutdown result: {"message":"Shutting down, bye..."} INFO 241764 --- [ Thread-25] s.s.ApplicationContextCloseEventListener : event: org.springframework.context.event.ContextClosedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2f48b3d2, started on Sat Jun 27 01:22:57 CST 2020], source: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2f48b3d2, started on Sat Jun 27 01:22:57 CST 2020 INFO 241764 --- [ Thread-25] n.t.d.s.w.s.s.spring.LoggingLifeCycle : In Life cycle bean stop(). INFO 241764 --- [ Thread-25] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' INFO 241764 --- [ Thread-25] n.t.d.s.w.s.shutdown.bean.SimpleBean : @PreDestroy! INFO 241764 --- [ Thread-25] n.t.d.s.w.s.shutdown.bean.SimpleBean : DisposableBean is destroying! INFO 241764 --- [ Thread-25] n.t.d.s.w.s.shutdown.bean.SimpleBean : On my way to destroy!

可看到一般可供使用的容器关闭时拓展点不多,分别有这两个:
1、监听ContextClosedEvent事件,对应例子是demo中的ApplicationContextCloseEventListener类。
2、LifeCycle/SmartLifeCycle的stop()方法,对应例子是demo中的LoggingLifeCycle类。

他们的触发时机在上面的close代码的分析中有注释。

六、拓展探究:kill与kill -9的区别

为什么说这个呢,作为开发一般我们知道kill的时候,Spring Boot程序可以执行应用退出相关的代码,而kill -9则不能。任意使用kill -9,有时会造成一些问题, 比如,一些执行中的数据不能及时保存等等。

上面已经说到了,kill可以触发java程序的shutdownhook,从而触发spring容器的优雅关闭。
这里,我不仅想讨论shutdownhook怎么注册,怎样触发,我把问题放大了一点:kill和kill -9在操作系统和JVM层面来看分别有什么不同,各自做了什么?

先看看命令的意思,以Ubuntu为例,执行kill -l,如下:

kill命令不带参数,默认是-15(SIGTERM),而kill -9是SIGKILL。

SIGTERM
The SIGTERM signal is sent to a process to request its termination. Unlike the SIGKILL signal, it can be caught and interpreted or ignored by the process. This allows the process to perform nice termination releasing resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM.
SIGKILL
The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal. The following exceptions apply: Zombie processes cannot be killed since they are already dead and waiting for their parent processes to reap them. Processes that are in the blocked state will not die until they wake up again. The init process is special: It does not get signals that it does not want to handle, and thus it can ignore SIGKILL.[10] An exception from this rule is while init is ptraced on Linux.[11][12] An uninterruptibly sleeping process may not terminate (and free its resources) even when sent SIGKILL. This is one of the few cases in which a UNIX system may have to be rebooted to solve a temporary software problem. SIGKILL is used as a last resort when terminating processes in most system shutdown procedures if it does not voluntarily exit in response to SIGTERM. To speed the computer shutdown procedure, Mac OS X 10.6, aka Snow Leopard, will send SIGKILL to applications that have marked themselves “clean” resulting in faster shutdown times with, presumably, no ill effects.[13] The command killall -9 has a similar, while dangerous effect, when executed e.g. in Linux; it doesn’t let programs save unsaved data. It has other options, and with none, uses the safer SIGTERM signal.

七、那么一个进程(包括Java进程)是怎么处理信号的呢?

第一篇文章从内核源码的跟踪,陈述了kill -9的操作系统执行机制。

信号是异步的,信号的接收不是由用户进程来完成的,而是由内核代理。 当一个进程P2向另一个进程P1发送信号后,内核接受到信号,并将其放在P1的信号队列当中。当P1再次陷入内核态时,会检查信号队列,并根据相应的信号调取相应的信号处理函数。

特别说下,一个点:
在如下信号处理代码中可知,
如果是强制信号(比如SIGKILL(kill -9)),不走挂载pending队列的流程,直接快速路径优先处理。 然后,在内核层面就给处理掉,不会发送到进程。

static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns) { struct sigpending *pending; struct sigqueue *q; int override_rlimit; int ret = 0, result; assert_spin_locked(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; // (1)判断是否可以忽略信号 if (!prepare_signal(sig, t, from_ancestor_ns || (info == SEND_SIG_FORCED))) goto ret; // (2)选择信号pending队列 // 线程组共享队列(t->signal->shared_pending) or 进程私有队列(t->pending) pending = group ? &t->signal->shared_pending : &t->pending; /* * Short-circuit ignored signals and support queuing * exactly one non-rt signal, so that we can get more * detailed information about the cause of the signal. */ result = TRACE_SIGNAL_ALREADY_PENDING; // (3)如果信号是常规信号(regular signal),且已经在pending队列中,则忽略重复信号; // 另外一方面也说明了,如果是实时信号,尽管信号重复,但还是要加入pending队列; // 实时信号的多个信号都需要能被接收到。 if (legacy_queue(pending, sig)) goto ret; result = TRACE_SIGNAL_DELIVERED; /* * fast-pathed signals for kernel-internal things like SIGSTOP * or SIGKILL. */ // (4)如果是强制信号(SEND_SIG_FORCED),不走挂载pending队列的流程,直接快速路径优先处理。 if (info == SEND_SIG_FORCED) goto out_set; /* * Real-time signals must be queued if sent by sigqueue, or * some other real-time mechanism. It is implementation * defined whether kill() does so. We attempt to do so, on * the principle of least surprise, but since kill is not * allowed to fail with EAGAIN when low on memory we just * make sure at least one signal gets delivered and don't * pass on the info struct. */ // (5)符合条件的特殊信号可以突破siganl pending队列的大小限制(rlimit) // 否则在队列满的情况下,丢弃信号 // signal pending队列大小rlimit的值可以通过命令"ulimit -i"查看 if (sig < SIGRTMIN) override_rlimit = (is_si_special(info) || info->si_code >= 0); else override_rlimit = 0; // (6)没有ignore的信号,加入到pending队列中。 q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, override_rlimit); if (q) { list_add_tail(&q->list, &pending->list); switch ((unsigned long) info) { case (unsigned long) SEND_SIG_NOINFO: q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_USER; q->info.si_pid = task_tgid_nr_ns(current, task_active_pid_ns(t)); q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); break; case (unsigned long) SEND_SIG_PRIV: q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_KERNEL; q->info.si_pid = 0; q->info.si_uid = 0; break; default: copy_siginfo(&q->info, info); if (from_ancestor_ns) q->info.si_pid = 0; break; } userns_fixup_signal_uid(&q->info, t); } else if (!is_si_special(info)) { if (sig >= SIGRTMIN && info->si_code != SI_USER) { /* * Queue overflow, abort. We may abort if the * signal was rt and sent by user using something * other than kill(). */ result = TRACE_SIGNAL_OVERFLOW_FAIL; ret = -EAGAIN; goto ret; } else { /* * This is a silent loss of information. We still * send the signal, but the *info bits are lost. */ result = TRACE_SIGNAL_LOSE_INFO; } } out_set: signalfd_notify(t, sig); // (7)更新pending->signal信号集合中对应的bit sigaddset(&pending->signal, sig); // (8)选择合适的进程来响应信号,如果需要并唤醒对应的进程 complete_signal(sig, t, group); ret: trace_signal_generate(sig, info, t, group, result); return ret; } | → static bool prepare_signal(int sig, struct task_struct *p, bool force) { struct signal_struct *signal = p->signal; struct task_struct *t; sigset_t flush; if (signal->flags & (SIGNAL_GROUP_EXIT | SIGNAL_GROUP_COREDUMP)) { // (1.1)如果进程正在处于SIGNAL_GROUP_COREDUMP,则当前信号被忽略 if (signal->flags & SIGNAL_GROUP_COREDUMP) { pr_debug("[%d:%s] is in the middle of doing coredump so skip sig %d ", p->pid, p->comm, sig); return 0; } /* * The process is in the middle of dying, nothing to do. */ } else if (sig_kernel_stop(sig)) { // (1.2)如果当前是stop信号,则移除线程组所有线程pending队列中的SIGCONT信号 /* * This is a stop signal. Remove SIGCONT from all queues. */ siginitset(&flush, sigmask(SIGCONT)); flush_sigqueue_mask(&flush, &signal->shared_pending); for_each_thread(p, t) flush_sigqueue_mask(&flush, &t->pending); } else if (sig == SIGCONT) { unsigned int why; // (1.3)如果当前是SIGCONT信号,则移除线程组所有线程pending队列中的stop信号,并唤醒stop进程 /* * Remove all stop signals from all queues, wake all threads. */ siginitset(&flush, SIG_KERNEL_STOP_MASK); flush_sigqueue_mask(&flush, &signal->shared_pending); for_each_thread(p, t) { flush_sigqueue_mask(&flush, &t->pending); task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING); if (likely(!(t->ptrace & PT_SEIZED))) wake_up_state(t, __TASK_STOPPED); else ptrace_trap_notify(t); } /* * Notify the parent with CLD_CONTINUED if we were stopped. * * If we were in the middle of a group stop, we pretend it * was already finished, and then continued. Since SIGCHLD * doesn't queue we report only CLD_STOPPED, as if the next * CLD_CONTINUED was dropped. */ why = 0; if (signal->flags & SIGNAL_STOP_STOPPED) why |= SIGNAL_CLD_CONTINUED; else if (signal->group_stop_count) why |= SIGNAL_CLD_STOPPED; if (why) { /* * The first thread which returns from do_signal_stop() * will take ->siglock, notice SIGNAL_CLD_MASK, and * notify its parent. See get_signal_to_deliver(). */ signal->flags = why | SIGNAL_STOP_CONTINUED; signal->group_stop_count = 0; signal->group_exit_code = 0; } } // (1.4)进一步判断信号是否会被忽略 return !sig_ignored(p, sig, force); } || → static int sig_ignored(struct task_struct *t, int sig, bool force) { /* * Blocked signals are never ignored, since the * signal handler may change by the time it is * unblocked. */ // (1.4.1)如果信号被blocked,不会被忽略 if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig)) return 0; // (1.4.2)进一步判断信号的忽略条件 if (!sig_task_ignored(t, sig, force)) return 0; /* * Tracers may want to know about even ignored signals. */ // (1.4.3)信号符合忽略条件,且没有被trace,则信号被忽略 return !t->ptrace; } ||| → static int sig_task_ignored(struct task_struct *t, int sig, bool force) { void __user *handler; // (1.4.2.1)提取信号的操作函数 handler = sig_handler(t, sig); // (1.4.2.2)如果符合条件,信号被忽略 if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) && handler == SIG_DFL && !force) return 1; // (1.4.2.3) return sig_handler_ignored(handler, sig); } |||| → static int sig_handler_ignored(void __user *handler, int sig) { /* Is it explicitly or implicitly ignored? */ // (1.4.2.3.1)如果信号操作函数是忽略SIG_IGN,或者操作函数是默认SIG_DFL但是默认动作是忽略 // 默认动作是忽略的信号包括: // #define SIG_KERNEL_IGNORE_MASK ( // rt_sigmask(SIGCONT) | rt_sigmask(SIGCHLD) | // rt_sigmask(SIGWINCH) | rt_sigmask(SIGURG) ) // 忽略这一类信号 return handler == SIG_IGN || (handler == SIG_DFL && sig_kernel_ignore(sig)); } | → static void complete_signal(int sig, struct task_struct *p, int group) { struct signal_struct *signal = p->signal; struct task_struct *t; /* * Now find a thread we can wake up to take the signal off the queue. * * If the main thread wants the signal, it gets first crack. * Probably the least surprising to the average bear. */ // (8.1)判断当前线程是否符合响应信号的条件 if (wants_signal(sig, p)) t = p; else if (!group || thread_group_empty(p)) // (8.2)如果信号是发给单线程的,直接返回 /* * There is just one thread and it does not need to be woken. * It will dequeue unblocked signals before it runs again. */ return; else { /* * Otherwise try to find a suitable thread. */ // (8.3)在当前线程组中挑出一个符合响应信号条件的线程 // 从signal->curr_target线程开始查找 t = signal->curr_target; while (!wants_signal(sig, t)) { t = next_thread(t); if (t == signal->curr_target) /* * No thread needs to be woken. * Any eligible threads will see * the signal in the queue soon. */ return; } signal->curr_target = t; } /* * Found a killable thread. If the signal will be fatal, * then start taking the whole group down immediately. */ if (sig_fatal(p, sig) && !(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) && !sigismember(&t->real_blocked, sig) && (sig == SIGKILL || !t->ptrace)) { /* * This signal will be fatal to the whole group. */ if (!sig_kernel_coredump(sig)) { /* * Start a group exit and wake everybody up. * This way we don't have other threads * running and doing things after a slower * thread has the fatal signal pending. */ signal->flags = SIGNAL_GROUP_EXIT; signal->group_exit_code = sig; signal->group_stop_count = 0; t = p; do { task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK); sigaddset(&t->pending.signal, SIGKILL); signal_wake_up(t, 1); } while_each_thread(p, t); return; } } /* * The signal is already in the shared-pending queue. * Tell the chosen thread to wake up and dequeue it. */ // (8.4)唤醒挑选出的响应信号的线程 signal_wake_up(t, sig == SIGKILL); return; } || → static inline void ptrace_signal_wake_up(struct task_struct *t, bool resume) { signal_wake_up_state(t, resume ? __TASK_TRACED : 0); } ||| → void signal_wake_up_state(struct task_struct *t, unsigned int state) { // (8.4.1)设置thread_info->flags中的TIF_SIGPENDING标志 // ret_to_user()时会根据此标志来调用do_notify_resume() set_tsk_thread_flag(t, TIF_SIGPENDING); /* * TASK_WAKEKILL also means wake it up in the stopped/traced/killable * case. We don't check t->state here because there is a race with it * executing another processor and just now entering stopped state. * By using wake_up_state, we ensure the process will wake up and * handle its death signal. */ // (8.4.2)唤醒阻塞状态为TASK_INTERRUPTIBLE的信号响应线程 if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) kick_process(t); }

八、JDK处理信号的相关代码分析

由上一节可知,kill -9就不会到进程这里。kill -15(默认的kill)是JVM可是接收到的。这里说下JDK的支持。

JDK在linux里用到的信号及用途有这些: Signals Used in Oracle Solaris and Linux | Java Platform, Standard Edition Troubleshooting Guide 其中,SIGTERM, SIGINT, SIGHUP支持了ShutdownHook。

JDK的挂载信号处理函数在这里。openjdk8|jsig.c

九、JDK挂载Shutdownhook的代码分析

我们可以通过sun.misc.Signal类挂载信号处理器。

Signal.handle(new Signal(“INT”), System.out::println);
其实,JDK就是通过挂载信号处理器去执行shutdownhook的。

留意,Terminator类针对SIGINT、SIGTERM挂载了两个信号处理器。

class Terminator { private static SignalHandler handler = null; /* Invocations of setup and teardown are already synchronized * on the shutdown lock, so no further synchronization is needed here */ static void setup() { if (handler != null) return; SignalHandler sh = new SignalHandler() { public void handle(Signal sig) { Shutdown.exit(sig.getNumber() + 0200); } }; handler = sh; // When -Xrs is specified the user is responsible for // ensuring that shutdown hooks are run by calling // System.exit() try { Signal.handle(new Signal("INT"), sh); } catch (IllegalArgumentException e) { } try { Signal.handle(new Signal("TERM"), sh); } catch (IllegalArgumentException e) { } } static void teardown() { /* The current sun.misc.Signal class does not support * the cancellation of handlers */ } }

在存在对应信号而触发执行时,实际执行Shutdown#exit()方法。 进一步跟踪,在sequence()的runHooks()会执行Runtime#addShutdownHook(Thread hook)添加进来的Shutdownhook。

btw,我们可以看到Shutdown有两个方法,一个是Shutdown#exit(int status), Shutdown#shutdown()。 当最后一条非Daemon线程已经执行完了,JVM会通过JNI调用Shutdown#shutdown()方法。

所以,触发shutdownhook执行有两个契机,
1、一是JAVA程序收到SIGINT、SIGTERM信号量。
2、或者当非Daemon线程都已经执行完了。

十、总结

本文挖掘了Spring Boot的关闭方式,并列举了关闭方式,从原理、源码的角度阐述了Spring Boot的关闭代码及扩展点。同时,额外说明了一些系统特性 和原理,比如,程序退出码和信号机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1142177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HY-MT1.5-7B部署教程:GPU算力配置最佳实践

HY-MT1.5-7B部署教程&#xff1a;GPU算力配置最佳实践 1. 引言 随着多语言交流需求的快速增长&#xff0c;高质量、低延迟的翻译模型成为智能应用的核心组件。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其在多语言互译、混合语言处理和术语控制方面的卓越表现&a…

HY-MT1.5-7B带注释翻译场景优化详细教程

HY-MT1.5-7B带注释翻译场景优化详细教程 1. 引言 随着全球化进程的加速&#xff0c;高质量、多语言互译能力成为自然语言处理领域的重要需求。腾讯近期开源了混元翻译大模型系列的最新版本——HY-MT1.5&#xff0c;包含两个核心模型&#xff1a;HY-MT1.5-1.8B 和 HY-MT1.5-7B…

项目应用中LCD1602并行接口无响应的排查步骤

LCD1602只亮不显示&#xff1f;一文讲透并行接口无响应的系统性排查你有没有遇到过这种情况&#xff1a;LCD1602背光亮得明明白白&#xff0c;但屏幕却一片空白&#xff0c;既没有字符、也没有光标&#xff0c;甚至连初始化时该出现的一排黑块都看不到&#xff1f;这可不是“对…

混元翻译1.5模型实战:法律文件精准翻译指南

混元翻译1.5模型实战&#xff1a;法律文件精准翻译指南 随着全球化进程的加速&#xff0c;跨语言法律协作日益频繁&#xff0c;对高精度、可定制化翻译系统的需求愈发迫切。传统通用翻译模型在处理法律文本时常常面临术语不准、语义模糊、格式错乱等问题&#xff0c;难以满足专…

腾讯混元翻译1.5:如何实现高质量格式化输出

腾讯混元翻译1.5&#xff1a;如何实现高质量格式化输出 随着全球化进程加速&#xff0c;跨语言沟通需求激增&#xff0c;传统翻译模型在保持语义准确的同时&#xff0c;往往难以兼顾格式一致性、术语统一性和上下文连贯性。腾讯推出的混元翻译模型 1.5&#xff08;HY-MT1.5&am…

HY-MT1.5多GPU推理:Tensor并行实战

HY-MT1.5多GPU推理&#xff1a;Tensor并行实战 1. 引言 随着全球化进程的加速&#xff0c;高质量、低延迟的机器翻译需求日益增长。腾讯近期开源了混元翻译大模型1.5版本&#xff08;HY-MT1.5&#xff09;&#xff0c;包含两个核心模型&#xff1a;HY-MT1.5-1.8B 和 HY-MT1.5…

HY-MT1.5-1.8B vs Google Translate对比:33语种互译速度评测

HY-MT1.5-1.8B vs Google Translate对比&#xff1a;33语种互译速度评测 近年来&#xff0c;随着全球化进程加速和多语言内容爆发式增长&#xff0c;高质量、低延迟的机器翻译需求日益迫切。传统云服务依赖高带宽与中心化算力&#xff0c;难以满足边缘侧实时翻译场景的需求。在…

2026年AI翻译新趋势:Hunyuan-HY-MT1.5开源模型+按需计费GPU

2026年AI翻译新趋势&#xff1a;Hunyuan-HY-MT1.5开源模型按需计费GPU 随着多语言交流需求的爆发式增长&#xff0c;AI翻译技术正从“通用可用”向“精准可控、高效部署”演进。2026年&#xff0c;腾讯混元团队推出的 Hunyuan-HY-MT1.5 系列翻译大模型&#xff0c;标志着开源翻…

HY-MT1.5-1.8B性能测试:边缘设备上的翻译质量

HY-MT1.5-1.8B性能测试&#xff1a;边缘设备上的翻译质量 近年来&#xff0c;随着多语言交流需求的不断增长&#xff0c;高质量、低延迟的机器翻译模型成为智能硬件和本地化服务的核心支撑。腾讯开源的混元翻译模型&#xff08;HY-MT&#xff09;系列在这一背景下持续演进&…

为什么选HY-MT1.5做本地化?多语言软件翻译实战案例

为什么选HY-MT1.5做本地化&#xff1f;多语言软件翻译实战案例 在当前全球化背景下&#xff0c;多语言支持已成为软件产品出海和本地化部署的关键能力。然而&#xff0c;依赖云端商业翻译API不仅存在数据隐私风险&#xff0c;还可能因网络延迟影响用户体验。为此&#xff0c;腾…

HY-MT1.5-7B混合精度训练技术揭秘

HY-MT1.5-7B混合精度训练技术揭秘 近年来&#xff0c;随着多语言交流需求的激增&#xff0c;高质量机器翻译模型成为AI领域的重要研究方向。腾讯推出的混元翻译大模型HY-MT1.5系列&#xff0c;凭借其在多语言支持、翻译质量与部署灵活性上的卓越表现&#xff0c;迅速引起业界关…

32B参数Granite 4.0:企业级AI助手新选择

32B参数Granite 4.0&#xff1a;企业级AI助手新选择 【免费下载链接】granite-4.0-h-small-bnb-4bit 项目地址: https://ai.gitcode.com/hf_mirrors/unsloth/granite-4.0-h-small-bnb-4bit 导语 IBM推出32B参数的Granite 4.0-H-Small大语言模型&#xff0c;以其卓越的…

WS2812B驱动程序硬件抽象层设计:模块化开发指南

WS2812B驱动还能写得更优雅&#xff1f;聊聊如何用硬件抽象层实现“一次编码&#xff0c;到处运行”你有没有遇到过这样的场景&#xff1a;项目刚在STM32上跑通WS2812B灯带&#xff0c;客户突然说要换成ESP32&#xff1b;或者团队里两个人分别维护不同平台的驱动代码&#xff0…

HY-MT1.5双模型部署教程:1.8B与7B适用场景对比指南

HY-MT1.5双模型部署教程&#xff1a;1.8B与7B适用场景对比指南 随着多语言交流需求的不断增长&#xff0c;高质量、低延迟的翻译模型成为智能应用的核心组件。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其在翻译质量、部署灵活性和功能丰富性上的突出表现&#…

手把手教程:STM32驱动LCD显示屏I2C接口设计

用两个IO点亮屏幕&#xff1a;STM32 IC LCD驱动实战全解析你有没有遇到过这样的窘境&#xff1f;项目快完成了&#xff0c;结果发现MCU的GPIO几乎被占光——定时器、串口、ADC、按键……最后只剩两根“边角料”引脚&#xff0c;可你还想给设备加个显示屏。别急。今天我们就来解…

HY-MT1.5-7B高精度翻译部署:术语库注入实战优化教程

HY-MT1.5-7B高精度翻译部署&#xff1a;术语库注入实战优化教程 1. 引言 随着全球化业务的不断扩展&#xff0c;高质量、可定制化的机器翻译需求日益增长。传统通用翻译模型虽然在日常语句上表现良好&#xff0c;但在专业领域&#xff08;如法律、医疗、金融&#xff09;中常因…

CCS20快速理解:基础工具链使用解析

CCS20实战入门&#xff1a;从编译到调试的全链路解析你有没有遇到过这样的场景&#xff1f;代码写完&#xff0c;点击“Build”&#xff0c;结果报错一堆链接问题&#xff1b;好不容易烧录进板子&#xff0c;运行却莫名其妙复位。查寄存器&#xff1f;看波形&#xff1f;一头雾…

HY-MT1.5-7B格式化引擎:自定义输出规则设计

HY-MT1.5-7B格式化引擎&#xff1a;自定义输出规则设计 1. 引言&#xff1a;混元翻译模型的技术演进与核心价值 随着全球化进程的加速&#xff0c;高质量、多语言互译能力已成为自然语言处理&#xff08;NLP&#xff09;领域的重要基础设施。腾讯推出的混元翻译大模型HY-MT1.…

HY-MT1.5实时语音翻译系统:端到端解决方案

HY-MT1.5实时语音翻译系统&#xff1a;端到端解决方案 随着全球化进程加速&#xff0c;跨语言沟通需求激增&#xff0c;传统翻译系统在延迟、准确性和部署成本上的瓶颈日益凸显。腾讯推出的混元翻译大模型HY-MT1.5系列&#xff0c;正是为应对这一挑战而生。该系列包含两个核心…

HY-MT1.5格式化翻译教程:结构化文本处理技巧

HY-MT1.5格式化翻译教程&#xff1a;结构化文本处理技巧 随着多语言内容在互联网、企业服务和智能硬件中的广泛应用&#xff0c;高质量、可定制的机器翻译模型成为关键基础设施。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其对多语言互译、术语控制与格式保留能…