-
CSE JAVA SDK使用vertx的HTTP实现,支持客户端TLS和服务端TLS。 使用openssl 加上配置项: ssl.engine: openssl即可。 文档参考: https://docs.servicecomb.io/java-chassis/zh_CN/security/tls.html
-
某业务出现一个奇怪的消息,一个接口的调用始终超时, 错误消息为:The timeout period of 60000ms has been exceeded while executing POST /rest/csbmessagecenterservice/v1/config/email/test-email-config for host 172.16.1.186, server is rest://172.16.1.186:8443?sslEnabled=true详细日志中有如下内容,表示配置了重试,重试也是失败。用户使用postman直接请求,调用不会超时,而使用测试工具系统请求,则会超时。 一时不知道为什么。[2019-01-22 06:16:25:638] [ERROR] - [transport-vert.x-eventloop-thread-18] [org.apache.servicecomb.transport.rest.client.http.RestClientInvocation.lambda$invoke$0(RestClientInvocation.java:104)] - Failed to send request to /172.16.1.186:8443.io.vertx.core.http.impl.HttpClientRequestBase$1: The timeout period of 60000ms has been exceeded while executing POST /rest/csbmessagecenterservice/v1/config/email/test-email-config for host 172.16.1.186[2019-01-22 06:16:25:638] [ERROR] - [transport-vert.x-eventloop-thread-18] [org.apache.servicecomb.loadbalance.LoadbalanceHandler$4.lambda$null$0(LoadbalanceHandler.java:369)] - service CONSUMER rest CSBMessageCenterService.ApiMessageConfigResource.testEmailConfig, call error, msg is cause:InvocationException,message:InvocationException: code=490;msg=CommonExceptionData [message=Cse Internal Bad Request];cause:,message:The timeout period of 60000ms has been exceeded while executing POST /rest/csbmessagecenterservice/v1/config/email/test-email-config for host 172.16.1.186, server is rest://172.16.1.186:8443?sslEnabled=true[2019-01-22 06:16:25:639] [ERROR] - [transport-vert.x-eventloop-thread-18] [org.apache.servicecomb.loadbalance.LoadbalanceHandler$3.onExceptionWithServer(LoadbalanceHandler.java:294)] - Invoke server failed. Operation CONSUMER rest CSBMessageCenterService.ApiMessageConfigResource.testEmailConfig; server rest://172.16.1.186:8443?sslEnabled=true; 0-0 msg cause:InvocationException,message:InvocationException: code=490;msg=CommonExceptionData [message=Cse Internal Bad Request];cause:,message:The timeout period of 60000ms has been exceeded while executing POST /rest/csbmessagecenterservice/v1/config/email/test-email-config for host 172.16.1.186[2019-01-22 06:16:25:639] [ERROR] - [transport-vert.x-eventloop-thread-18] [org.apache.servicecomb.loadbalance.LoadbalanceHandler$3.onExecutionFailed(LoadbalanceHandler.java:322)] - Invoke all server failed. Operation CONSUMER rest CSBMessageCenterService.ApiMessageConfigResource.testEmailConfig, e=cause:InvocationException,message:InvocationException: code=490;msg=CommonExceptionData [message=Cse Internal Bad Request];cause:,message:The timeout period of 60000ms has been exceeded while executing POST /rest/csbmessagecenterservice/v1/config/email/test-email-config for host 172.16.1.186超时问题一般通过日志看不出根本原因。 建议业务先做了如下排查:查看服务端的access log,看是否有接受到请求;弄清楚问题出现的环节。 查看下打印超时日志的的服务的access log, 将调用目标服务的其他接口的访问情况观察看,看是一个接口超时还是所有接口都超时。通过日志看,很多接口都有超时,但也并不是每次都超时,只是有些请求超时。 一时找不到原因。 无赖只好自己把所有日志都拉出来,重新排查了一遍,最后发现业务开发者在排查第1步的都是搞漏了。 业务接口在某种输入情况下,处理时间超过8分钟,而access log是在业务处理完毕后打印的,包日志看漏了。 contract:172.16.3.8 - - - - [22/Jan/2019:03:26:16 +0000] "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" 400 72 8172.16.3.8 - - - - [22/Jan/2019:03:33:07 +0000] "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" 500 62 481105172.16.3.8 - - - - [22/Jan/2019:03:34:48 +0000] "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" 500 62 481116172.16.3.8 - - - - [22/Jan/2019:03:40:17 +0000] "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" 400 72 8172.16.3.8 - - - - [22/Jan/2019:03:41:44 +0000] "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" 400 72 7172.16.3.8 - - - - [22/Jan/2019:03:42:42 +0000] "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" 400 72 8edge:172.16.3.1 - 2019-01-22 03:25:06,271 "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" - 490 38 60003 809168370546 -- 业务22/Jan/2019:03:33:07返回,超过8分钟172.16.3.1 - 2019-01-22 03:26:16,297 "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" - 400 61 13 5c468d580e692bee172.16.3.1 - 2019-01-22 03:26:47,616 "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" - 490 38 60002 180593684930 -- 业务22/Jan/2019:03:34:48返回,超过8分钟172.16.3.1 - 2019-01-22 03:40:17,559 "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" - 400 61 12 5c4690a18978dcfe172.16.3.1 - 2019-01-22 03:41:44,823 "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" - 400 61 11 180593684930172.16.3.1 - 2019-01-22 03:42:42,757 "PUT /rest/csb/csbcontractservice/v1/business-change HTTP/1.1" - 400 61 12 180593684930为了让超时问题更好的得到定位,建议开发者每个服务都打开access log,便于分析。 对于性能问题,可能还需要打开metrics日志,分析瓶颈点。
-
系统性问题:1. 整体服务架构和规模摸底a. 对服务规模(微服务数量,每个微服务的实例数,每个微服务的schema个数等)需要进行一定的评估,如果微服务总接口数非常多,并且所有服务的请求都经过Edge Service转发,建议Edge Service等需要调用大量其他服务的消费者设置较大的的JAVA metaspace空间, -XX:MaxMetaspaceSize=512m。 一个微服务接口数量太多还有个问题就是服务第一次被访问的时候,加载会比较慢。 对于对一次访问有明显要求的业务,可以考虑预加载提升访问速度。 2. 业务线程池配置a. CSE提供的两个配置项servicecomb.rest.server.thread-count和servicecomb.rest.client.thread-count并不是业务线程池配置。这两个参数不需要配置的特别大,一般等于CPU核数即可,建议配置为8~16之间。b. 对于tomcat场景,以及Edge Service的vert.x场景,都有一个业务处理线程池(Edge Service的场景默认在reactive模式,是没有业务线程池的)。CSE设置的默认线程池的线程个数为2 * CPU个数。如果部分服务处理比较慢(比如评价时延>50ms),那么建议要设置一个较大的业务线程池,以提升吞吐量。如果所有接口都处理的很快,则不需要设置非常大的业务线程池,过多线程反而会因为线程调度,增加处理时延。如果一个微服务,有少量的几个接口处理非常耗时,需要考虑将这些接口放到独立的线程池执行(线程池隔离),防止访问慢的接口,影响访问快的接口。 3. 运维参数和建议a. 设置合理的超时时间。超时时间设置不能够小于3秒,即使业务处理时间都非常小(比如小于1ms)b. 打开metrics,用于分析性能瓶颈,并将日志输出到独立的日志文件c. 打开access logd. 对接APM的调用链,增强问题快速定界能力 4. 开发效率提升开发效率提升需要把一些准备工作放到平时,每个微服务尽可能梳理出本微服务对外的依赖关系,通过在本地安装依赖服务(或者提供模拟桩),实现在开发环境调试和验证微服务基本功能。这样能够大大提高开发效率,同时提升软件质量。 5. SSL性能风险SSL在高并发、频繁断连重连的情况下,可能出现内存高、性能慢的情况。SSL协议慢是已知问题,并没有解决方案,对于对于时延要求很高,频繁超时、短连接,并发特别高等场景,不适合使用HTTPS。 目前有些产品的具体做法:1. 内部通讯使用HTTP;接入层使用HTTPS;2. 使用和验证HTTP2协议,降低连接数和连接重建。 这个可以纳入后续规划,以应对可能的用户增长问题。 开发常见错误:1. 业务中常驻线程或者定时任务的保护。 常驻线程或者定时任务的Task跑出异常, 会导致这些任务不再被调度执行。如果存在这种场景,一定要通过catch Throwable保证线程的可靠性。 可以通过通用的ThreadFactory创建线程实现,详细参考这个问题修改代码。2. 使用vert.x异步HttpClient一定需要注意设置如下几个异常处理器,保证异常的时候,异步回调能够正常执行。这类逻辑再CBC的Edge网关等场景也有使用,建议也排查下。a. request.exceptionHandlerb. response.exceptionHandler --- 这个特别容易遗漏c. response.bodyHandler3. 程序故障保护和运维方面的一些问题,保护错误,能够让程序在一些故障场景下能够有更好的适应性,但是实际上也会隐含的一层意思“业务故障可能被推迟发现”。 如果没有良好的运维机制,那么故障还是随着问题积累,慢慢的被发现。虽然从软件角度来讲,这个可以提高SLA的水平,是正向的,但是我们还是需要有一定的机制,能够先于用户发现问题。4. Tomcat超时问题:maxKeepAliveRequests修改为-1, keepAliveTimeout建议是客户端的2倍5. 使用环境变量指定SC地址问题(以及其他需要使用yaml中的List类型情况):需要测试下配置两个IP地址的情况下,如果一个地址失败,是否访问第二个地址。案例参考: https://bbs.huaweicloud.com/forum/thread-13404-1-1.html
-
CSE JAVA SDK Maven 仓库地址早期做过一次变更,最新迁移到华为云了。 <repository> <id>HuaweiCloudSDK</id> <url>https://mirrors.huaweicloud.com/repository/maven/huaweicloudsdk/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository>详细配置参考: https://support.huaweicloud.com/devg-cse/cse_03_summary.html
-
背景信息CSE Java SDK的开源部分ServiceComb项目已于2017年12月全票通过进入Apache孵化器,根据Apache软件基金会要求,项目groupId统一由io.servicecomb调整为org.apache.servicecomb。CSE Java SDK作为ServiceComb的商业版本,除提供对接华为公有云的能力、安全、分布式数据一致性等商业能力外,其大部分组件来自于开源的ServiceComb。因此提供本指导用于支持客户顺利升级至包名变更后的CSE Java SDK版本(2.3.3+)。更改说明CSE Java SDK版本(2.3.3+)中涉及包名变化情况如下:序号包名称groupId(修改前)groupId(修改后)变化类型1CSE Java-SDK开源包65个包名变更,详见CSE Java SDK开源包列表io.servicecomborg.apache.servicecomb修改2CSE Java-SDK开源包foundation-config-cc新增config-cc,groupId为org.apache.servicecomb新增3CSE Java-SDK商业包foundation-config-cc移除foundation-config-cc删除4CSE Java-SDK商业包共计13个,详见CSE Java SDK商业包列表com.huawei.paas.cse无变化操作步骤通过配置maven setting文件以获取SDK依赖。profiles中增加如下配置。<profile> <id>nexusProfile</id> <repositories> <repository> <id>cse1</id> <url>http://maven.huaweicse.com/nexus/content/groups/public/</url> </repository> </repositories> </profile>新增activeProfiles配置。<activeProfiles> <activeProfile>nexusProfile</activeProfile> </activeProfiles>引入dependencyManagement,建议开发者在pom.xml中进行如下配置以便更好的管理三方件。 说明:版本使用2.3.3以上版本。<dependencyManagement> <dependencies> <dependency> <groupId>com.huawei.paas.cse</groupId> <artifactId>cse-dependency</artifactId> <version>2.3.58</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>修改pom.xml中依赖包的groupid。此处仅以引入对spring-boot-starter-provider、cse-solution-service-engine、foundation-auth的依赖为例。修改前<dependency> <groupId>io.servicecomb</groupId> <artifactId>spring-boot-starter-provider</artifactId> </dependency> <dependency> <groupId>com.huawei.paas.cse</groupId> <artifactId>cse-solution-service-engine</artifactId> </dependency> <dependency> <groupId>com.huawei.paas.cse</groupId> <artifactId>foundation-auth</artifactId> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>修改后<dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>spring-boot-starter-provider</artifactId> </dependency> <dependency> <groupId>com.huawei.paas.cse</groupId> <artifactId>cse-solution-service-engine</artifactId> </dependency> <dependency> <groupId>com.huawei.paas.cse</groupId> <artifactId>foundation-auth</artifactId> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>CSE Java SDK开源包列表序号artifactIdgroupid(修改前)groupid(修改后)1common-javassistio.servicecomborg.apache.servicecomb2common-protobufio.servicecomborg.apache.servicecomb3common-restio.servicecomborg.apache.servicecomb4commonio.servicecomborg.apache.servicecomb5config-apolloio.servicecomborg.apache.servicecomb6dynamic-configio.servicecomborg.apache.servicecomb7edge-coreio.servicecomborg.apache.servicecomb8edgeio.servicecomborg.apache.servicecomb9foundation-commonio.servicecomborg.apache.servicecomb10config-ccio.servicecomborg.apache.servicecomb11foundation-configio.servicecomborg.apache.servicecomb12foundation-metricsio.servicecomborg.apache.servicecomb13foundation-sslio.servicecomborg.apache.servicecomb14foundation-test-scaffoldingio.servicecomborg.apache.servicecomb15foundation-vertxio.servicecomborg.apache.servicecomb16foundationsio.servicecomborg.apache.servicecomb17handler-bizkeeperio.servicecomborg.apache.servicecomb18handler-flowcontrol-qpsio.servicecomborg.apache.servicecomb19handler-loadbalanceio.servicecomborg.apache.servicecomb20handler-publickey-authio.servicecomborg.apache.servicecomb21handler-tracing-zipkinio.servicecomborg.apache.servicecomb22handlersio.servicecomborg.apache.servicecomb23java-chassis-coreio.servicecomborg.apache.servicecomb24java-chassis-dependenciesio.servicecomborg.apache.servicecomb25java-chassis-parentio.servicecomborg.apache.servicecomb26java-chassisio.servicecomborg.apache.servicecomb27metrics-commonio.servicecomborg.apache.servicecomb28metrics-coreio.servicecomborg.apache.servicecomb29metrics-extensionio.servicecomborg.apache.servicecomb30metrics-integrationio.servicecomborg.apache.servicecomb31metrics-prometheusio.servicecomborg.apache.servicecomb32metricsio.servicecomborg.apache.servicecomb33provider-jaxrsio.servicecomborg.apache.servicecomb34provider-pojoio.servicecomborg.apache.servicecomb35provider-rest-commonio.servicecomborg.apache.servicecomb36provider-springmvcio.servicecomborg.apache.servicecomb37providersio.servicecomborg.apache.servicecomb38service-registryio.servicecomborg.apache.servicecomb39spring-boot-starter-configurationio.servicecomborg.apache.servicecomb40spring-boot-starter-discoveryio.servicecomborg.apache.servicecomb41spring-boot-starter-parentio.servicecomborg.apache.servicecomb42spring-boot-starter-providerio.servicecomborg.apache.servicecomb43spring-boot-starter-registryio.servicecomborg.apache.servicecomb44spring-boot-starter-servicecombio.servicecomborg.apache.servicecomb45spring-boot-starter-transportio.servicecomborg.apache.servicecomb46spring-cloud-zuul-zipkinio.servicecomborg.apache.servicecomb47spring-cloud-zuulio.servicecomborg.apache.servicecomb48swagger-generator-coreio.servicecomborg.apache.servicecomb49swagger-generator-jaxrsio.servicecomborg.apache.servicecomb50swagger-generator-springmvcio.servicecomborg.apache.servicecomb51swagger-generatorio.servicecomborg.apache.servicecomb52swagger-invocation-coreio.servicecomborg.apache.servicecomb53swagger-invocation-jaxrsio.servicecomborg.apache.servicecomb54swagger-invocation-springmvcio.servicecomborg.apache.servicecomb55swagger-invocationio.servicecomborg.apache.servicecomb56swaggerio.servicecomborg.apache.servicecomb57tracing-commonio.servicecomborg.apache.servicecomb58tracing-zipkinio.servicecomborg.apache.servicecomb59tracingio.servicecomborg.apache.servicecomb60transport-highwayio.servicecomborg.apache.servicecomb61transport-rest-clientio.servicecomborg.apache.servicecomb62transport-rest-servletio.servicecomborg.apache.servicecomb63transport-rest-vertxio.servicecomborg.apache.servicecomb64transport-restio.servicecomborg.apache.servicecomb65transportsio.servicecomborg.apache.servicecombCSE Java SDK商业包列表增删说明序号artifactIdgroupid(不涉及修改)1foundation-config-cc已迁移至ServiceCombCSE Java-SDK商业包列表序号artifactIdgroupid(不涉及修改)1cse-adapter-springmvccom.huawei.paas.cse2cse-dependencycom.huawei.paas.cse3cse-handler-2pccom.huawei.paas.cse4cse-handler-cloud-extensioncom.huawei.paas.cse5cse-handler-performance-statscom.huawei.paas.cse6cse-handler-tcccom.huawei.paas.cse7cse-handler-tracing-apmcom.huawei.paas.cse8cse-handler-tracingcom.huawei.paas.cse9cse-narayanacom.huawei.paas.cse10cse-solution-service-enginecom.huawei.paas.cse11cse-solutionscom.huawei.paas.cse12foundation-authcom.huawei.paas.cse13paas-csecom.huawei.paas.cse
-
当有些业务处理比较耗时的时候,日志里面经常打印如下异常:2019-01-09 10:42:22,890 [ntloop-thread-0] ERROR Unhandled exception [io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:345)]java.lang.IllegalStateException: Response is closedat io.vertx.core.http.impl.HttpServerResponseImpl.checkValid(HttpServerResponseImpl.java:548)at io.vertx.core.http.impl.HttpServerResponseImpl.end0(HttpServerResponseImpl.java:401)at io.vertx.core.http.impl.HttpServerResponseImpl.end(HttpServerResponseImpl.java:319)at org.apache.servicecomb.foundation.vertx.http.VertxServerResponseToHttpServletResponse.internalFlushBuffer(VertxServerResponseToHttpServletResponse.java:122)at org.apache.servicecomb.foundation.vertx.http.VertxServerResponseToHttpServletResponse.lambda$flushBuffer$0(VertxServerResponseToHttpServletResponse.java:112)at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:339)at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)at java.lang.Thread.run(Thread.java:745)这个异常表示的是在给请求返回结果的时候,写结果发现连接已经关闭。这种情况通常发生在业务处理比较长的情况。这个日志在服务端打印(服务端是相对的)。和这个问题相关的配置由如下几个:客户端:cse.rest.client.connection.idleTimeoutInSeconds(缺省值30s)服务端:cse.rest.server.connection.idleTimeoutInSeconds(缺省值30s)如果业务处理时间超过这两个值的最小值,那么服务端就会报告这个异常。
-
[技术干货] Mismatched content. Msg=Cannot deserialize instance of `java.lang.String` out of START_OBJECT token在使用CSE开发REST接口,并使用postman或者客户端调用,经常出现:Mismatched content. Msg=Cannot deserialize instance of `java.lang.String` out of START_OBJECT token的错误。 这个错误的含义是将HTTP body转换为 REST接口定义的参数发生的,抛出异常的是jackson API。 2018-12-22 09:49:01.440 ERROR 3048 --- [pool-1-thread-1] o.a.s.common.rest.codec.RestCodec : Parameter is not valid for operation cse-reporting-service.ReportingEndpoint.addAlarm. Message is cause:MismatchedInputException,message:Cannot deserialize instance of `java.lang.String` out of START_OBJECT token at [Source: (org.apache.servicecomb.foundation.vertx.stream.BufferInputStream); line: 1, column: 1].2018-12-22 09:49:01.447 ERROR 3048 --- [pool-1-thread-1] o.a.s.c.rest.AbstractRestInvocation : unknown rest exception.org.apache.servicecomb.swagger.invocation.exception.InvocationException: InvocationException: code=400;msg=CommonExceptionData [message=Parameter is not valid.]at org.apache.servicecomb.common.rest.codec.RestCodec.restToArgs(RestCodec.java:72) ~[common-rest-1.1.0.B030.jar:1.1.0.B030]at org.apache.servicecomb.common.rest.filter.inner.ServerRestArgsFilter.afterReceiveRequest(ServerRestArgsFilter.java:60) ~[common-rest-1.1.0.B030.jar:1.1.0.B030]at org.apache.servicecomb.common.rest.AbstractRestInvocation.prepareInvoke(AbstractRestInvocation.java:226) [common-rest-1.1.0.B030.jar:1.1.0.B030]at org.apache.servicecomb.common.rest.AbstractRestInvocation.invoke(AbstractRestInvocation.java:206) [common-rest-1.1.0.B030.jar:1.1.0.B030]at org.apache.servicecomb.common.rest.AbstractRestInvocation.runOnExecutor(AbstractRestInvocation.java:196) [common-rest-1.1.0.B030.jar:1.1.0.B030]at org.apache.servicecomb.common.rest.AbstractRestInvocation.lambda$scheduleInvocation$0(AbstractRestInvocation.java:159) [common-rest-1.1.0.B030.jar:1.1.0.B030]at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_111]at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_111]at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_111]下面解读下这个错误:"Cannot deserialize instance of `java.lang.String`" 表面目标类型是一个String类型; "out of START_OBJECT token"表面HTTP body的内容,是一个对象,比如{"name":"bobo"}String的json表示是需要用引号的,所以就报了上述错误。正确的传递参数应该是:"This is a String Json content."或者:"{\"abcd\":\"abcd\"}"
-
在java内部已有内置的观察者模式,如类 java.util.Observable和类java.util.Observer,即是被观察者和观察者。在 java.util.Observable 中,存储观察者对象的容器是Vector,此容器支持动态扩展和同步性,用法与ArrayList类似。Observable内部方法如下所示: 一对一直播源码,Java观察者模式案例简析观察者模式的内在原理:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。即:通过将一组对象实例存储在一个数组容器中,在某一时刻遍历数组并调用数组内的对象方法完成更新。被观察者Observable内部的核心方法:1 public void notifyObservers(Object arg) {2 /*3 * a temporary array buffer, used as a snapshot of the state of4 * current Observers.5 */6 Object[] arrLocal;7 8 synchronized (this) {9 if (!changed)10 return;11 arrLocal = obs.toArray();12 clearChanged();13 }14 15 for (int i = arrLocal.length-1; i>=0; i--)16 ((Observer)arrLocal).update(this, arg);17 }加同步锁synchronized可避免线程并发问题,设置changed状态可监听更新。for循环遍历执行观察者Observer内部的方法update()。 被观察者继承Observable类,动态发布消息通知观察者更新。如:复制代码1 /**2 * 被观察者类3 *4 * @author jinzifu5 * @create 2018/2/22 15:366 */7 public class ObservableClass extends Observable {8 private String message;9 10 public String getMessage() {11 return message;12 }13 14 public void setMessage(String message) {15 this.message = message;16 //被观察者数据发生变化时,通过以下两行代码通知所有的观察者17 this.setChanged();18 this.notifyObservers(message);19 }20 }复制代码观察者实现了Observer接口,支持扩展。如: 复制代码1 /**2 * 观察者类3 *4 * @author jinzifu5 * @create 2018/2/22 15:146 */7 public class ObserverClass implements Observer {8 private String name;9 10 public ObserverClass(String name) {11 this.name = name;12 }13 14 @Override15 public void update(Observable o, Object arg) {16 System.out.println(name + ":" + arg);17 }18 }复制代码外部测试如 复制代码1 /**2 * @author jinzifu3 */4 public class Main {5 6 public static void main(String[] args) {7 8 ObservableClass observableClass = new ObservableClass();9 ObserverClass observerClass1 = new ObserverClass("observerClass1");10 ObserverClass observerClass2 = new ObserverClass("observerClass2");11 ObserverClass observerClass3 = new ObserverClass("observerClass3");12 ObserverClass observerClass4 = new ObserverClass("observerClass4");13 14 observableClass.addObserver(observerClass1);15 observableClass.addObserver(observerClass2);16 observableClass.addObserver(observerClass3);17 observableClass.addObserver(observerClass4);18 19 observableClass.setMessage("收到通知,完成更新。");20 }21 }
-
以下博客将持续更新模拟器及使用方法:https://bbs.huaweicloud.com/blogs/acc3919d038911e9bd5a7ca23e93a891
-
一、Java demo开发工具是用eclipse,为了避免出现一些意外情况,打开源码工程应使用eclipse import导入开发。二、修改demo南向接入地址:修改第一处:修改第二处:三、修改接入设备信息,AgentLiteBind.java四、根据profile修改设备上报数据五、如果不需要注册子设备,可以注释掉相关的代码六、运行查看结果七、至此,整个demo已经可以跑起来了,如果设备无法上线,可以删掉如下文件试试。
-
最近刚好在infoq上看到下一代分布式的设计理念,感触颇深,二十年来,整个分布式系统架构的演进,从 C/S 到 B/S,再到分布式系统。今天,微服务,网格,云计算大行其道,虽然二十年前我刚刚上初中!以当前常见的服务架构举例:微服务常见微服务架构,其前端流量入口采用负载。应用层常见是采取一级网络 (通过配置推送的软负载) 或者二级网络 (通过应用网关负载隔离) 模式。阿里是使用前者,百度、新浪使用后者,主要取决于微服务的展现形式 (RPC or Rest-API),差异是是否需要一个专职配置中心。为保证请求无状态地实现迁移,所以使用共享数据节点 (存储各种形式的临时或中间数据) 的形式实现。数据节点往往采用分片的主备模式。而伴随微服务的增长,当前我们的系统作为2.0基于RPC的微服务架构,也衍生出很多问题,例如服务间调用关系由当初的设计自底向上,逐渐变成网状调用依赖。部署上资源限制,效率瓶颈,缺乏总体架构。大量的远程调用。爆炸式增长的碎片化应用。问题定位变得复。另外,远程调用中最大的问题就是性能问题。如何解决以上的问题,同时也是下一代架构目标:流式架构/反应式编程(Reactive Architecture/Programming)作为一种范式在整个业界正在逐步受到认可和落地,是对过往系统的业务需求理解梳理之后对系统技术设计/架构模式的提升总结。 作为JAVA程序员的你应该感到高兴,因为,Java作为一个成熟平台,对于趋势一向有些稳健的接纳和跟进能力,有着令人惊叹的生命活力: 1. Java 7提供了ForkJoinPool,支持了Java 8提供的Stream(Reactive Stream是RP的一个核心组件)。 2. 另外Java 8还提供了Lamda(有效地表达和使用RP需要FP的语言构件和理念)。 3. 有了前面的这些稳健但不失时机的准备,在Java 9中提供了面向RP的Flow API,为Java圈子提供了官方的RP API,标志着RP由集市式的自由探索阶段 向 教堂式的统一使用的转变。 响应式编程就是异步数据流编程 说了这么就,什么是响应式编程(Reactive Programming)?传统的顺序编程采用每条指令依次执行的方式,上一条指令没有执行结束,当前的线程就得等着,任你如何提升机器性能还是代码性能,如果本质不变,始终改变不了响应需要等待的现实。若要响应迅速,就得把顺序执行指令的方式换一换——同步换成异步,方法执行换做消息发送,于是,就有了上边的响应式的定义:响应式编程就是异步数据流编程,这其实是一种编程范式,是编程理念的一种思想转型。因为采用响应式编程,我们就不再将软件要处理的业务视为对象,又或者函数,而是直接透析到本质:数据流(Data Stream)。万事万物皆为流从函数式编程的角度来讲,一连串组合函数的调用,其实就是数据在流动。函数可以抽象地视为一种数据类型到另一种数据类型的转换。将各种形式的转换(map、flatMap、filter)穿起来,同时保证数据的不变性(Immutable),则数据就能非常可靠地在函数链条中流动,或者被分析,或者被转换,或者被过滤。当我们将编程的范式切换为“流(Stream)”时,我们欣喜地发现,这种方式可以在很大程度上确保数据是不变的。这就为并行开发创造了可能。然而,普通的数据流编程范式并不能满足“响应式Reactive”的本初定义。我们需要响应迅速。如何才能做到?那就是要做到没有阻塞,这就是我们通常所说的异步工作方式。因而,响应式编程的设计原则是:保持数据的不变性没有共享阻塞是有害的恰好,这三条特征也是Actor模型拥有的。Actor,这个诞生在1970的产物,遥遥领先于那个时间,知道很久以后,Erlang这种基于Acotr的模型设计的面向并发编程的新语言横空出世,并在并发领域树立一座丰碑,古老的Actor才重见天日,再次成为分布式计算领域的焦点技术之一,目前,几乎所有的主流平台都支持Actor模型:JAVA平台下的Scala的Actor类库jetlan。Actor的理念很简单,Actor是一等公民,一切皆Actor,之间通过消息发送通信,所有都是异步的,不同Actor可以自己处理各自的消息。整个系统获得良好的大规模并发处理能力。在《Scala并发编程》一书中,Aleksandar Prokopec形象地描述了Actor系统:Actor系统模仿了人类的组织,如公司、政府和其他大型机构。在软件公司中,有许多需要以并发方式达成的目标。为了实现这些目标,数百或数千名员工一起努力工作,而且这些员工通常会被组织成一种层次结构。许多员工会为级别比他们低的员工分派工作。为了高效地工作和决策,员工们使用电子邮件进行通信。当员工早上上班时,就会检查他的电子邮箱并对重要的消息做出回应。如果某封电子邮件非常重要,那么这个员工就必须立刻回复这封邮件。当员工忙着回复一封电子邮件时,可能会收到另一封电子邮件,而且后续的电子邮件都会进入他的电子邮箱中。只有当员工处理完成当前的电子邮件后,他才能继续处理下一封电子邮件。软件公司就相当于是一个ActorSystem,每位员工则是一个一个Actor。电子邮件是Actor之间彼此发送的消息(Message),一旦发送了消息,就不必等待收件人的回复,可以继续自己的工作,也就是说这种消息发送的方式是异步非阻塞的。Actor持有的MailBox正好借用了这里所谓的电子邮箱概念。因而对于每个Actor而言:每个Actor都拥有独立的MailBox;接收到的消息皆为不可变对象,且完全独立;不管是tell消息还是ask消息,Actor执行消息的方式都是异步非阻塞的。不得不提的Akka AKKA框架是一个实现Actors模型的Scala或Java平台,实现多线程安全应用。 Actors是一个轻量级的对象,通过发送消息实现交互。每个Actors在同一时间处理最多一个消息,可以发送消息给其他Actors。在同一时间可以于一个Java虚拟机存在数以百万计的参与者,构架是一个分层的父层(管理) - 子层,其中父层监控子层的行为。还可以很容易地扩展Actor运行在集群中各个节点之间 - 无需修改一行代码。每个演员都可以有内部状态(字段/变量) ,但通信只能通过消息传递,不会有共享数据结构(计数器,队列) 。Akka框架支持两种语言Java和Scala,** is cheap,show me the codeAkka actors是轻量,能够平均在一个系统创建数千个。线程是重量的,场景切换相当慢。横向扩展Scale out,Actor能够在代码没有任何修改时在远程运行。失败恢复和错误处理Actor的Java代码就如上面简介中所说的,AKKA把并发操作的各种复杂的东西都统一的做了封装.我们主要关心的是业务逻辑的实现,只需要少量的关心Actor模型的串联即可构建出高可用,高性能,高扩展的应用.我希望在后续的应用的开发中朝着这个方向努力,今天的分享就到这里。最近我对内存相关的内容比较感兴趣,下个blog我会在跟大家分享下关于内存的内容。谢谢! 部分资料来自 infoq & 简书https://www.jianshu.com/p/3bdb8dbaa35c
-
1 前言前两天有同事发现,通过华为云 ServiceStage 的流水线部署基于模板创建的 CSEJavaSDK demo 服务时,会在容器启动过程中报错。初步排查是由于 JVM 占用的内存超出了 docker 内存配额的上限,导致容器被 kill 掉。于是我们需要排查一下问题出在哪里,为什么以前没有这类问题,而现在却发生了。2 基本定位要确定 docker 容器内存超限问题的直接原因并不难。直接进入docker容器,执行 top 命令,我们发现宿主机是一台8核16G的机器,而且 docker 并不会屏蔽这些信息,也就是 JVM 会认为自己工作于一台 16G 内存的机器上。而查看 demo 服务的 Dockerfile,发现运行服务时并没有对 JVM 的内存进行任何限制,于是 JVM 会根据默认的设置来工作 —— 最大堆内存为物理内存的1/4(这里的描述并不完全准确,因为 JVM 的默认堆内存大小限制比例其实是根据物理内存有所变化的,具体内容请自行搜索资料),而基于模板创建的 ServiceStage 流水线,在部署应用堆栈的时候会把 docker 容器的内存配额默认设置为 512M,于是容器就会在启动的时候内存超限了。至于以前没有碰到过这种问题的原因,只是因为以前没将这么高规格的 ECS 服务器用于流水线部署应用堆栈。在查询过相关资料后,我们找到了两种问题解决方案,一个是直接在 jar 包运行命令里加上 -Xmx 参数来指定最大堆内存,不过这种方式只能将 JVM 堆内存限制为一个固定的值;另一个方法是在执行 jar 包时加上 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 参数,让 JVM 能够感知到docker容器所设置的 cgroup 限制,相应地调整自身的堆内存大小,不过这个特性是 JDK 8u131 以上的版本才具备的。最终,我们提醒 ServiceStage 流水线的同学将 CSEJavaSDK demo 的创建模板做了改进,在 Dockerfile 中将打包的基础镜像版本由原先的 java:8u111-jre-alpine 升级为了 openjdk:8u181-jdk-alpine,并且在运行服务 jar 包的命令中加上了 -Xmx256m参数。问题至此已经解决了。3 进一步的探究虽然问题已经解决,但是在好奇心的驱使下,我还是打算自己找个 demo 实际去触发一下问题,另外看看从网上搜到的解决方法到底好不好用 : )3.1 准备工作3.1.1 创建云上工程首先需要在华为云 ServiceStage 创建一个云上工程。在 ServiceStage -> 应用开发 -> 微服务开发 -> 工程管理 -> 创建云上工程中,选择“基于模板创建”,语言选择 Java, 框架选择 CSE-Java (SpringMVC),部署系统选择“云容器引擎CCE”,给你的云上工程取一个名字,比如test-memo-consuming,最后选择存放代码的仓库,就可以完成云上工程的创建了。之后云上工程会根据你的选项自动地生成脚手架代码,上传到你指定的代码仓库中,并且为你创建一条流水线,完成代码编译、构建、打包、归档镜像包的操作,并且使用打好的 docker 镜像包在 CCE 集群中部署一个应用堆栈。创建云上工程和流水线不是本文的重点,我就不详细讲操作了 : )。同一个应用堆栈的实例可以部署多个,在这里为了实验方便就按照默认值1个来部署。由于云上工程已经改进了脚手架代码的模板,不会再出现内存超限的问题,所以我们现在能看到 demo 服务已经正常的跑起来,微服务实例已经注册到服务中心了。登录到 demo 服务所部署的容器,使用curl命令可以调用 demo 服务的 helloworld 接口,可以看到此时服务已经可以正常工作。3.1.2 增加实验代码为了能够触发微服务实例消耗更多的内存,我在项目代码中增加了如下接口,当调用/allocateMemory接口时,微服务实例会不停申请内存,直到 JVM 抛出 OOM 错误或者容器内存超限被 kill 掉。private HashMap<String, long[]> cacheMap = new HashMap<>(); @GetMapping(value = "/allocateMemory") public String allocateMemory() { LOGGER.info("allocateMemory() is called"); try { for (long i = 0; true; ++i) { cacheMap.put("key" + i, new long[1024 * 1024]); } } catch (Throwable t) { LOGGER.info("allocateMemory() gets error", t); } return "allocated"; }此时用来打镜像包的基础镜像是openjdk:8u181-jdk-alpine,jar 包启动命令中加上了-Xmx256m参数。执行流水线,应用堆栈部署成功后,调用/allocateMemory接口触发微服务实例消耗内存,直到 JVM 抛出 OOM 错误,可以在 ServiceStage -> 应用上线 -> 应用管理中选择相应的应用,点击进入概览页面,查看应用使用内存的情况。应用使用的内存从 800M+ 陡然下降的时间点就是我重新打包部署的时间,而之后由于调用/allocateMemory接口,内存占用量上升到了接近 400M,并且在这个水平稳定了下来,显示-Xmx256m参数发挥了预期的作用。3.2 复现问题现在将 demo 工程中的 Dockerfile 修改一下,将基础镜像改为 java:8u111-jre-alpine,并且删除启动命令中的-Xmx256m参数,将其提交为noLimit_oldBase分支,推送到代码仓库中。然后编辑流水线,将 source 阶段的任务所使用的代码分支改为noLimit_oldBase分支,保存并重新运行流水线,将新的代码打包部署到应用堆栈中。在微服务实例列表中查询到新的微服务实例的 endpoint IP 后,调用`/allocateMemory`接口,观察内存情况,内存从接近 400M 突然掉下去一下,然后又上升到约 450M 的时间点就是修改代码后的微服务实例部署成功的时间点,之后内存占用量突然下跌就是因为调用`/allocateMemory`接口导致容器内存超限被 kill 掉了。如果你事先使用docker logs -f命令查看容器日志的话,那么日志大概是这个样子的2018-11-23 15:40:04,920 INFO SCBEngine:152 - receive MicroserviceInstanceRegisterTask event, check instance Id... 2018-11-23 15:40:04,920 INFO SCBEngine:154 - instance registry succeeds for the first time, will send AFTER_REGISTRY event. 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:116 - keyStore [server.p12] file not exist, please check! 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:136 - trustStore [trust.jks] file not exist, please check! 2018-11-23 15:40:04,928 INFO DataFactory:62 - Monitor data sender started. Configured data providers is {com.huawei.paas.cse.tcc.upload.TransactionMonitorDataProvider,com.huawei.paas.monitor.HealthMonitorDataProvider,} 2018-11-23 15:40:04,929 INFO ServiceCenterTask:51 - read MicroserviceInstanceRegisterTask status is FINISHED 2018-11-23 15:40:04,939 INFO TestmemoconsumingApplication:57 - Started TestmemoconsumingApplication in 34.81 seconds (JVM running for 38.752) 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:258 - find instances[1] from service center success. service=default/CseMonitoring/latest, old revision=null, new revision=28475010.1 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:266 - service id=8b09a7085f4011e89f130255ac10470c, instance id=8b160d485f4011e89f130255ac10470c, endpoints=[rest://100.125.0.198:30109?sslEnabled=true] 2018-11-23 15:40:34,937 INFO ServiceCenterTaskMonitor:39 - sc task interval changed from -1 to 30 2018-11-23 15:47:03,823 INFO SPIServiceUtils:76 - Found SPI service javax.ws.rs.core.Response$StatusType, count=0. 2018-11-23 15:47:04,657 INFO TestmemoconsumingImpl:39 - allocateMemory() is called Killed可以看到allocateMemory方法被调用,然后 JVM 还没来得及抛出 OOM 错误,整个容器就被 kill 掉了。这里也给大家提了一个醒:不要以为自己的服务容器能启动起来就万事大吉了,如果没有特定的限制,JVM 会在运行时继续申请堆内存,也有可能造成内存用量超过 docker 容器的配额!3.3 让 JVM 感知cgroup限制前文提到还有另外一种方法解决 JVM 内存超限的问题,这种方法可以让 JVM 自动感知 docker 容器的 cgroup 限制,从而动态的调整堆内存大小,感觉挺不错的。我们也来试一下这种方法,看看效果如何 ; )回到demo项目代码的master分支,将 Dockerfile 中启动命令参数的-Xmx256m替换为-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap,提交为useCGroupMemoryLimitForHeap分支,推送到代码仓库里。再次运行流水线进行构建部署。等 demo 服务部署成功后,再次调用`/allocateMemory`接口,容器的内存占用情况如上图所示(最右边的那一部分连续曲线),内存上升到一定程度后,JVM 抛出了 OOM 错误,没有继续申请堆内存。看来这种方式也是有效果的。不过,仔细观察容器的内存占用情况,可以发现容器所使用的内存仅为不到 300M,而我们对于这个容器的内存配额限制为 512M,也就是还有 200M+ 是闲置的,并不会被 JVM 利用。这个利用率,比起上文中直接设置-Xmx256m的内存利用率要低 : ( 。推测是因为 JVM 并不会感知到自己是部署在一个 docker 容器里的,所以它把当前的环境当成一个物理内存只有 512M 的物理机,按照比例来限制自己的最大堆内存,另一部分就被闲置了。如此看来,如果想要充分利用自己的服务器资源,还是得多花一点功夫,手动调整好-Xmx参数。4 参考资料Java and Docker, the limitationsJava inside docker: What you must know to not FAIL本文转载自Docker容器内部署Java微服务的内存限制问题
-
failed to decode response body, exception is [Cannot deserialize instance of `java.lang.String` out of START_OBJECT tokenat [Source: (org.apache.servicecomb.foundation.vertx.stream.BufferInputStream); line: 1, column: 139] (through reference chain出错原因:在edge上引用了一个业务包,业务包定义的model跟契约不一致,导致出错。排查方法,可以在DefaultHttpClientFilter打个断点,然后查看下这个目标类的classloader是不是APPClassLoader,如果是,那么就是该问题
-
本SDK是对华为OBS原生SDK(com.huawei.storage:esdk-obs-java)包装,在其基础上,提供断点上传、断点下载、回调、进度速率监控等特性。因此,本SDK不是原生SDK的替代,而是对原生SDK的补充。开发者应首先参考原生SDK的用户手册,在熟悉原生SDK的基础上再使用本SDK。下载链接:release一、功能1. 断点上传将整个上传任务转化为分段上传,每上传完一分段,在断点文件中记录完成分段上传结果,等所有分段上传成功后,合并所有分段。如果上传过程被中断,再次上传时,会读取断点文件中记录的分段结果,对于已经上传成功的分段不用再次上传。2. 断点下载将整个下载任务转化为分段(范围)下载,每个分段被成功的写入本地文件中后,在断点文件中记录完成分段结果,所有分段写入成功,即完成下载。如果下载过程被中断,再次下载时,会读取断点文件中记录的分段结果,对于已经成功的分段不用再次下载。3. 过程信息返回(进度,速率)在上传、下载操作中,对待上传的流/下载的流使用 ReadBytesMonitorInputStream 包装,其监控读取流的字节总数,并生成某一时刻的快照信息(包括总字节数、当前已读取字节数、当前时间、上一快照已读取字节数、上一快照时间等信息), 可以通过快照信息计算出进度、速率、是否完成等。二、日志配置参考原生SDK日志配置方式,如果要打印本SDK日志,配置logger name为"com.obs.extension"即可。注:当前SDK打印的日志级别为debug。<Logger name="com.obs.extension" level="debug" additivity="false"> <AppenderRef ref="NorthInterfaceLogAppender" /> </Logger>三、初始化该SDK提供了统一的对象操作门面,IObjectOperateClient。 要初始化 IObjectOperateClient ,需要传入原生SDK的obsClient实例,obsClient实例的初始化参考原生SDK手册。示例:IObjectOperateClient objectOperateClient = ObjectOperateClientImpl.newInstance(obsClient);四、代码示例1. 文件上传分段上传文件,支持断点续传和速率监控,IParallelUploadFuture 可以获取整个文件的速率信息,也可以获取每个分段上传的速率信息。//分段下载下载,开启断点续传和速率监控,关闭完整性校验 public void downloadFileWithProcessing() throws InterruptedException { String downloadFilePath = ""; String objectKey = ""; DownloadFileRequest request = new DownloadFileRequest(bucketName, objectKey); request.setDownloadFile(downloadFilePath); request.setEnableCheckpoint(true); request.setPartSize(5242880L); IParallelDownloadFuture downloadFuture = objectOperateClient.downloadFileAsyncWithProgress(request, false, printCallback); printProcessingInfo(downloadFuture.aggregationProcessing()); objectOperateClient.shutdown(); }2. 文件下载分段下载桶中对象到本地,支持断点续传、速率监控特性。//分段下载下载,开启断点续传和速率监控,关闭完整性校验 public void downloadFileWithProcessing() throws InterruptedException { String downloadFilePath = ""; String objectKey = ""; DownloadFileRequest request = new DownloadFileRequest(bucketName, objectKey); request.setDownloadFile(downloadFilePath); request.setEnableCheckpoint(true); request.setPartSize(5242880L); IParallelDownloadFuture downloadFuture = objectOperateClient.downloadFileAsyncWithProgress(request, false, printCallback); printProcessingInfo(downloadFuture.aggregationProcessing()); objectOperateClient.shutdown(); }3. printProcessingInfo() 方法public void printProcessingInfo(IProcessing processingInfo) throws InterruptedException { while (!processingInfo.isComplete()){ System.out.println("是否支持速率信息:" + processingInfo.isSupportProgress()); if(processingInfo.isSupportProgress()) { System.out.println("总字节数: " + processingInfo.getTotalBytes()); System.out.println("进度: " + processingInfo.getProgress()); } System.out.println("已传输: " + processingInfo.getTransferedBytes()); System.out.println("速率: " +processingInfo.perSecondRate() + " bytes/s"); System.out.println("-------------"); TimeUnit.MILLISECONDS.sleep(1000); } if (processingInfo.isComplete()) { System.out.println("是否支持速率信息:" + processingInfo.isSupportProgress()); if(processingInfo.isSupportProgress()) { System.out.println("总字节数: " + processingInfo.getTotalBytes()); System.out.println("进度: " + processingInfo.getProgress()); } System.out.println("已传输: " + processingInfo.getTransferedBytes()); System.out.println("速率: " +processingInfo.perSecondRate() + " bytes/s"); System.out.println("-------------"); } }
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签