Spring Batch Remote Chunk模式下,遠(yuǎn)程執(zhí)行JOB時(shí),傳輸?shù)膶?duì)象是ChunkRequest/ChunkResponse,無(wú)法轉(zhuǎn)成JSON格式傳輸。
注意此處使用的是SPRING JACKSON,而不是JACKSON。一般是在SPRING INTEGRATIONA框架下轉(zhuǎn)的。
需要自定義Transformer:
JsonToChunkRequestTransformer.java
package com.frandorado.springbatchawsintegrationslave.transformer;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.stream.IntStream;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.integration.chunk.ChunkRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.aws.support.AwsHeaders;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class JsonToChunkRequestTransformer extends JsonToObjectTransformer {
private static final String MESSAGE_GROUP_ID_HEADER = "message-group-id";
@Autowired
AmazonSQSAsync amazonSQSAsync;
@Override
protected Object doTransform(Message<?> message) throws Exception {
// ACK
ack(message);
return this.getMessageBuilderFactory().withPayload(buildChunkRequest(message)).setHeader(MESSAGE_GROUP_ID_HEADER, "unique").build();
}
private ChunkRequest buildChunkRequest(Message<?> message) throws IOException {
Map map = new ObjectMapper().readValue(message.getPayload().toString(), Map.class);
Map stepContributionMap = (Map) map.get("stepContribution");
Map exitStatusMap = (Map) stepContributionMap.get("exitStatus");
StepContribution stepContribution = new StepContribution(new StepExecution("null", null));
ExitStatus exitStatus = new ExitStatus((String) exitStatusMap.get("exitCode"), (String) exitStatusMap.get("exitDescription"));
IntStream.range(0, (Integer) stepContributionMap.get("readCount")).forEach(e -> stepContribution.incrementReadCount());
stepContribution.incrementWriteCount((Integer) stepContributionMap.get("writeCount"));
stepContribution.incrementFilterCount((Integer) stepContributionMap.get("filterCount"));
stepContribution.incrementReadSkipCount((Integer) stepContributionMap.get("readSkipCount"));
IntStream.range(0, (Integer) stepContributionMap.get("writeSkipCount")).forEach(e -> stepContribution.incrementWriteSkipCount());
IntStream.range(0, (Integer) stepContributionMap.get("processSkipCount"))
.forEach(e -> stepContribution.incrementProcessSkipCount());
stepContribution.setExitStatus(exitStatus);
return new ChunkRequest((Integer) map.get("sequence"), (Collection) map.get("items"), (Integer) map.get("jobId"), stepContribution);
}
private void ack(Message<?> message) {
String receiptHandle = message.getHeaders().get(AwsHeaders.RECEIPT_HANDLE, String.class);
String queue = message.getHeaders().get(AwsHeaders.QUEUE, String.class);
String queueUrl = amazonSQSAsync.getQueueUrl(queue).getQueueUrl();
amazonSQSAsync.deleteMessage(queueUrl, receiptHandle);
}
}
JsonToChunkResponseTransformer.java
package com.frandorado.springbatchawsintegrationmaster.transformer;
import java.io.IOException;
import java.util.Map;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.integration.chunk.ChunkResponse;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class JsonToChunkResponseTransformer extends JsonToObjectTransformer {
@Override
protected Object doTransform(Message<?> message) throws Exception {
return buildChunkResponse(message);
}
private ChunkResponse buildChunkResponse(Message<?> message) throws IOException {
Map map = new ObjectMapper().readValue(message.getPayload().toString(), Map.class);
Integer jobId = (Integer) map.get("jobId");
Integer sequence = (Integer) map.get("sequence");
String messageContent = (String) map.get("message");
Boolean status = (Boolean) map.get("successful");
StepContribution stepContribution = new StepContribution(new StepExecution("-", null));
return new ChunkResponse(status, sequence, Long.valueOf(jobId), stepContribution, messageContent);
}
}
還有一種方式,就是如果第三類不支持轉(zhuǎn)JSON,即代碼里沒有JACKSON的注解,可以采用MIXIN的方式:
StepExecutionJacksonMixIn.java
package org.springframework.cloud.dataflow.rest.client.support;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.batch.core.StepExecution;
/**
* Jackson MixIn for {@link StepExecution} de-serialization.
*
* @author Gunnar Hillert
* @since 1.0
*/
@JsonIgnoreProperties({ "jobExecution", "jobParameters", "jobExecutionId", "skipCount", "summary" })
public abstract class StepExecutionJacksonMixIn {
@JsonCreator
StepExecutionJacksonMixIn(@JsonProperty("stepName") String stepName) {
}
}
在配置文件中注冊(cè)才能使用:
JacksonConfiguration.java
import java.util.Locale;
import java.util.TimeZone;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.ISO8601DateFormatWithMilliSeconds;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.ExecutionContextJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.ExitStatusJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobExecutionJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobInstanceJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobParameterJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobParametersJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.StepExecutionJacksonMixIn;
@Configuration
public class JacksonConfiguration {
@Bean
public Jackson2JsonObjectMapper jackson2JsonObjectMapper(ObjectMapper objectMapper) {
return new Jackson2JsonObjectMapper(objectMapper);
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer dataflowObjectMapperBuilderCustomizer() {
return (builder) -> {
builder.dateFormat(new ISO8601DateFormatWithMilliSeconds(TimeZone.getDefault(), Locale.getDefault(), true));
// apply SCDF Batch Mixins to
// ignore the JobExecution in StepExecution to prevent infinite loop.
// https://github.com/spring-projects/spring-hateoas/issues/333
builder.mixIn(StepExecution.class, StepExecutionJacksonMixIn.class);
builder.mixIn(ExecutionContext.class, ExecutionContextJacksonMixIn.class);
builder.mixIn(JobExecution.class, JobExecutionJacksonMixIn.class);
builder.mixIn(JobParameters.class, JobParametersJacksonMixIn.class);
builder.mixIn(JobParameter.class, JobParameterJacksonMixIn.class);
builder.mixIn(JobInstance.class, JobInstanceJacksonMixIn.class);
// builder.mixIn(StepExecutionHistory.class, StepExecutionHistoryJacksonMixIn.class);
builder.mixIn(ExecutionContext.class, ExecutionContextJacksonMixIn.class);
builder.mixIn(ExitStatus.class, ExitStatusJacksonMixIn.class);
// objectMapper.setDateFormat(new ISO8601DateFormatWithMilliSeconds());
builder.modules(new JavaTimeModule(), new Jdk8Module());
};
}
}
@Bean
public IntegrationFlow flow4Contribution(
ConnectionFactory connectionFactory,
JobProperties jobProperties,
Jackson2JsonObjectMapper jackson2JsonObjectMapper
) {
return IntegrationFlows
.from(request4ContributionMaster())
.enrichHeaders(headerEnricherConfigurer())
.transform(Transformers.toJson(jackson2JsonObjectMapper))
.handle(jmsOutboundGateway4Contribution(connectionFactory, jobProperties))
.transform(Transformers.fromJson(StepExecution.class, jackson2JsonObjectMapper))
.channel(replies4ContributionMaster(null))
.get();
}
https://github.com/spring-cloud/spring-cloud-dataflow/tree/master/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/support
https://frandorado.github.io/spring/2019/07/29/spring-batch-aws-series-introduction.html
https://github.com/frandorado/spring-projects/tree/master/spring-batch-aws-integration/spring-batch-aws-integration-master/src/main/java/com/frandorado/springbatchawsintegrationmaster/transformer
https://github.com/frandorado/spring-projects/tree/master/spring-batch-aws-integration/spring-batch-aws-integration-slave/src/main/java/com/frandorado/springbatchawsintegrationslave/transformer
https://github.com/zakyalvan/spring-integration-java-dsl-learnpackage com.jwebs.learn.errorhandling;
import java.util.Random;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.core.Pollers;
import org.springframework.integration.dsl.jms.Jms;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessagingException;
/**
* Show how to handle error in spring integration flow.
* Please note, errorChannel in spring integration only applicable to
* error thrown in asynch component.
*
* @author zakyalvan
*/@SpringBootApplication
@IntegrationComponentScan
public class ErrorHandlingApplication {
public static void main(String[] args)
throws Exception {
ConfigurableApplicationContext applicationContext =
new SpringApplicationBuilder(ErrorHandlingApplication.
class)
.web(
false)
.run(args);
Runtime.getRuntime().addShutdownHook(
new Thread(() -> applicationContext.close()));
System.out.println("Pres enter key to exit

");
System.in.read();
System.exit(0);
}
@Autowired
private ConnectionFactory connectionFactory;
@Bean
public MessageSource<Integer> randomIntegerMessageSource() {
return () -> MessageBuilder.withPayload(
new Random().nextInt()).build();
}
@Bean
public IntegrationFlow withErrorFlow() {
return IntegrationFlows.from(randomIntegerMessageSource(), spec -> spec.poller(Pollers.fixedDelay(1000)))
.handle(Jms.outboundGateway(connectionFactory)
.requestDestination("processor.input")
.replyContainer(spec -> spec.sessionTransacted(
true)))
.get();
}
@Autowired
@Qualifier("errorChannel")
private PublishSubscribeChannel errorChannel;
@Bean
public IntegrationFlow errorHandlingFlow() {
return IntegrationFlows.from(errorChannel)
.handle(message -> System.out.println("@@@@@@@@@@@@@@@@@@@@@" + ((MessagingException) message.getPayload()).getFailedMessage().getPayload()))
.get();
}
}
基礎(chǔ)設(shè)施:條條道路通云端
對(duì)于云廠商來(lái)說(shuō),2019 年是碩果累累的一年。不僅初創(chuàng)公司在使用云計(jì)算,那些很注重安全的“保守派”公司(如政府機(jī)構(gòu)、醫(yī)療保健機(jī)構(gòu)、銀行、保險(xiǎn)公司,甚至是美國(guó)五角大樓)也在遷移到云端。這種趨勢(shì)在 2020 年將會(huì)繼續(xù),大大小小的公司都將(或者至少有計(jì)劃)遷移到云端。Gartner 公司最近發(fā)布了一個(gè)數(shù)字:

如果你是一個(gè)還在考慮要不要遷移到云端的決策者,不妨重新審視一下你的策略。如果你是一個(gè)獨(dú)立開發(fā)者,并且還沒使用過云基礎(chǔ)設(shè)施,那么完全可以在 2020 年嘗試一下。很多大型的云廠商(如亞馬遜、微軟、谷歌)都提供了免費(fèi)的體驗(yàn)機(jī)會(huì)。谷歌在這方面做得特別大方,它提供了價(jià)值 300 美元的一年免費(fèi)服務(wù)。

策劃注:阿里、騰訊、華為等國(guó)內(nèi)云廠商同樣有免費(fèi)云服務(wù)試用產(chǎn)品。
云平臺(tái):亞馬遜領(lǐng)頭,其他跟上
作為第一大云廠商,亞馬遜在 2019 年可謂風(fēng)生水起。憑借其豐富的產(chǎn)品組合,亞馬遜將把它的優(yōu)勢(shì)延續(xù)到 2020 年。Canalys 發(fā)布的 2019 年第三季度報(bào)告指出,大型云廠商(AWS、Azure、GCP)占據(jù) 56% 的市場(chǎng)份額,其中 AWS 獨(dú)享 32.6%。

其他云廠商也在努力縮短與 AWS 之間的差距。微軟把主要目標(biāo)轉(zhuǎn)向了大型企業(yè)。最近,微軟打敗了亞馬遜,從美國(guó)五角大樓拿到了一個(gè) 100 億美元的大單子。這個(gè)單子將提升 Azure 的聲譽(yù),同時(shí)削弱 AWS 的士氣。

谷歌一直在推動(dòng) CNCF,實(shí)現(xiàn)云計(jì)算運(yùn)維的標(biāo)準(zhǔn)化。谷歌的長(zhǎng)期目標(biāo)是讓云遷移變得更容易,方便企業(yè)從 AWS 遷移到 GCP。IBM 之前斥資 360 億美元收購(gòu)了 RedHat,也想要在云計(jì)算市場(chǎng)占有一席之地。

在亞太地區(qū),阿里云市場(chǎng)規(guī)模超過了 AWS、Azure 的總和,全球排名第三。中國(guó)國(guó)內(nèi)騰訊云等企業(yè)的增長(zhǎng)勢(shì)頭也十分迅猛。
2020 年將出現(xiàn)更多的并購(gòu)。當(dāng)然,很多初創(chuàng)公司將會(huì)帶來(lái)新的想法和創(chuàng)新,例如多云服務(wù)。因?yàn)楦?jìng)爭(zhēng)激烈,這些公司只能從降價(jià)和推出更多的創(chuàng)新產(chǎn)品來(lái)獲取利潤(rùn)。
容器化:Kubernetes 將會(huì)更酷
在容器編排領(lǐng)域,雖然一度出現(xiàn)了“三足鼎立”(Kubernetes、Docker Swarm 和 Mesos),但 Kubernetes 最終脫穎而出,成為絕對(duì)的贏家。云是一個(gè)分布式系統(tǒng),而 Kubernetes 是它的 OS(分布式的 Linux)。2019 年北美 KubeCon+CloudNativeCon 大會(huì)的參會(huì)者達(dá)到了 12000 名,比 2018 年增長(zhǎng)了 50%。以下是過去 4 年參會(huì)人數(shù)的增長(zhǎng)情況。

在 2020 年,Kubernetes 不僅不會(huì)后退,只會(huì)變得越來(lái)越強(qiáng),你完全可以把賭注壓在 Kubernetes 身上。另外值得一提的是,Migrantis 最近收購(gòu)了 Docker Enterprise,不過收購(gòu)數(shù)額不詳。

幾年前,人們張口閉口說(shuō)的都是 Docker,而現(xiàn)在換成了 Kubernetes。Docker 在它的全盛時(shí)期未能盈利,反而在優(yōu)勢(shì)漸退幾年之后才嘗試變現(xiàn)。這再次說(shuō)明,在現(xiàn)代技術(shù)世界,時(shí)機(jī)就是一切。
軟件架構(gòu):微服務(wù)將成為主流
谷歌趨勢(shì)表明,微服務(wù)架構(gòu)范式在 2019 年持續(xù)增長(zhǎng)了一整年。

隨著軟件行業(yè)整體逐步遷移到云端,微服務(wù)也將成為占主導(dǎo)地位的架構(gòu)范式。微服務(wù)架構(gòu)崛起的一個(gè)主要原因是它與云原生完美契合,可以實(shí)現(xiàn)快速的軟件開發(fā)。我在之前的一篇博文中解釋了微服務(wù)架構(gòu)的基本原則及其優(yōu)勢(shì)和劣勢(shì)。
https://towardsdatascience.com/microservice-architecture-a-brief-overview-and-why-you-should-use-it-in-your-next-project-a17b6e19adfd
我假設(shè)現(xiàn)在也存在一種回歸到單體架構(gòu)的趨勢(shì),因?yàn)樵诤芏嗲闆r下,微服務(wù)架構(gòu)有點(diǎn)過頭了,而且做好微服務(wù)架構(gòu)設(shè)計(jì)其實(shí)很難。微服務(wù)架構(gòu)有哪些好的實(shí)踐?在之前的另一篇博文中,我也給出了一些大概,希望對(duì)讀者有用。
https://towardsdatascience.com/effective-microservices-10-best-practices-c6e4ba0c6ee2
編程語(yǔ)言(整體):Python 將吞噬世界
機(jī)器學(xué)習(xí)、數(shù)據(jù)分析、數(shù)據(jù)處理、Web 開發(fā)、企業(yè)軟件開發(fā),甚至是拼接黑洞照片,Python 的影子無(wú)處不在。
在著名的編程語(yǔ)言排行榜網(wǎng)站 TIOBE 上,Python 位居最流行編程語(yǔ)言第三位,僅次于 Java 和 C 語(yǔ)言。

更有意思的是,在 2019 年,Python 的流行度翻了一番(從 5% 到 10%)。
Python 的崛起將在 2020 年延續(xù),并縮短與 Java 和 C 語(yǔ)言之間的差距。另一門無(wú)所不在的編程語(yǔ)言 JavaScript 正面臨下行的風(fēng)險(xiǎn)。為什么 Python 的勢(shì)頭會(huì)如此強(qiáng)勁?因?yàn)樗娜胧珠T檻低,有一個(gè)優(yōu)秀的社區(qū)在支持,并受到數(shù)據(jù)科學(xué)家和新生代開發(fā)者的喜愛。
編程語(yǔ)言(企業(yè)方面):Java 將占主導(dǎo)
之前的 TIOBE 網(wǎng)站截圖顯示,Java 仍然是一門占主導(dǎo)地位的編程語(yǔ)言,并將在 2020 年繼續(xù)保持這種地位。JVM 是 Java 的基石,其他編程語(yǔ)言(如 Kotlin、Scala、Clojure、Groovy)也將 JVM 作為運(yùn)行時(shí)。最近,Oracle 修改了 JVM 的許可協(xié)議。

新的許可協(xié)議意味著使用 Java、Kotlin、Scala 或其他 JVM 編程語(yǔ)言的公司需要向 Oracle 支付大額費(fèi)用。所幸的是,OpenJDK 讓 JVM 繼續(xù)免費(fèi)。另外,還有其他一些公司為 JVM 提供企業(yè)支持。

因?yàn)轶w積和速度方面的問題,基于 JVM 的編程語(yǔ)言并不適合用在今天的無(wú)服務(wù)器環(huán)境中。Oracle 正在推動(dòng) GraalVM 計(jì)劃,旨在讓 Java 變得更加敏捷和快速,讓它更適合用在無(wú)服務(wù)器環(huán)境中。因?yàn)槌?Java,沒有其他編程語(yǔ)言可以提供企業(yè)級(jí)的穩(wěn)定性和可靠性,所以 Java 將在 2020 年繼續(xù)占主導(dǎo)地位。
企業(yè)版 Java:Spring 繼續(xù)發(fā)力
曾幾何時(shí),在企業(yè)開發(fā)領(lǐng)域,Spring 和 JavaEE 之間存在著白熱化的競(jìng)爭(zhēng)。但因?yàn)?Oracle 在 JavaEE 方面沒有作為,在競(jìng)爭(zhēng)中慘敗,這導(dǎo)致了“MicroProfile”計(jì)劃的形成,并最終促成了 JakartaEE。
雖然所有的政策和活動(dòng)都是圍繞 JavaEE 展開,但 Spring 事實(shí)上已經(jīng)贏得了這場(chǎng)企業(yè) JVM 之爭(zhēng)。2020 年,Spring 將成為 JVM 生態(tài)系統(tǒng)的頭牌。
有兩個(gè)正在進(jìn)展中的項(xiàng)目,它們旨在減小 Java 的體積,讓它更適合用在無(wú)服務(wù)器環(huán)境中。
其中一個(gè)是 Micronaut(https://micronaut.io/)。

另一個(gè)是 Quarkus(https://quarkus.io/)。

這兩個(gè)項(xiàng)目都使用了 GraalVM,它們?cè)?2020 年將會(huì)得到 Java 社區(qū)更多的關(guān)注。
編程語(yǔ)言:后起之秀的突破
2000 年代,編程語(yǔ)言的發(fā)展出現(xiàn)了停滯。大多數(shù)人認(rèn)為沒有必要再去開發(fā)新的編程語(yǔ)言,Java、C 語(yǔ)言、C++、JavaScript 和 Python 已經(jīng)可以滿足所有的需求。但是,谷歌的 Go 語(yǔ)言為新編程語(yǔ)言大門打開了一扇大門。在過去十年出現(xiàn)了很多有趣的編程語(yǔ)言,比如 Rust、Swift、Kotlin、TypeScript。導(dǎo)致這種情況的一個(gè)主要原因是已有的編程語(yǔ)言無(wú)法充分利用硬件優(yōu)勢(shì)(例如多核、更快的網(wǎng)絡(luò)、云)。另一個(gè)原因是現(xiàn)代編程語(yǔ)言更加關(guān)注開發(fā)者經(jīng)濟(jì),即實(shí)現(xiàn)更快速更容易的開發(fā)。在 Stackoverflow 提供的一份開發(fā)者報(bào)告中,排名靠前的現(xiàn)代編程語(yǔ)言如下所示(Rust 連續(xù) 4 年名列第一)。

在之前的一篇博文中,我深入探討了現(xiàn)代編程語(yǔ)言,對(duì)比 Rust 和 Go 語(yǔ)言,并說(shuō)明了為什么現(xiàn)在是采用這些語(yǔ)言的好時(shí)機(jī)。
https://towardsdatascience.com/back-to-the-metal-top-3-programming-language-to-develop-big-data-frameworks-in-2019-69a44a36a842
最近,微軟宣布他們?cè)谔剿魇褂?Rust 來(lái)開發(fā)更安全的軟件。

亞馬遜最近也宣布要贊助 Rust。

谷歌宣布將 Kotlin 作為 Android 官方開發(fā)語(yǔ)言,所以,在 JVM 領(lǐng)域,Kotlin 成了 Java 的主要競(jìng)爭(zhēng)對(duì)手。

Angular 使用 TypeScript 代替 JavaScript,將其作為主要的編程語(yǔ)言,其他 JavaScript 框架(如 React 和 Vue)也開始為 TypeScript 提供更多的支持。
這種趨勢(shì)將在 2020 年延續(xù)下去,很多巨頭公司將會(huì)深入了解新一代編程語(yǔ)言(如 Rust、Swift、TypeScript、Kotlin),它們會(huì)站出來(lái)公開表示支持。
Web:JavaScript 繼續(xù)占主導(dǎo)地位
曾幾何時(shí),JavaScript 并不被認(rèn)為是一門強(qiáng)大的編程語(yǔ)言。在當(dāng)時(shí),前端內(nèi)容主要通過后端框架在服務(wù)器端進(jìn)行渲染。2014 年,AngularJS 的出現(xiàn)改變了這種局面。從那個(gè)時(shí)候開始,更多的 JavaScript 框架開始涌現(xiàn)(Angular 2+、React、Vue、Meteor),JavaScript 已然成為主流的 Web 開發(fā)語(yǔ)言。隨著 JavaScript 框架不斷創(chuàng)新以及微服務(wù)架構(gòu)的崛起,JavaScript 框架在 2020 年將繼續(xù)主導(dǎo)前端開發(fā)。
JavaScript 框架:React 閃耀
雖然 React 是在 AngularJS 之后出現(xiàn)的,但在過去十年對(duì) Web 開發(fā)產(chǎn)生了巨大的影響,這也讓 Facebook 在與 Google+ 的競(jìng)爭(zhēng)中打了一場(chǎng)勝戰(zhàn)。React 為前端開發(fā)帶來(lái)了一些新的想法,比如事件溯源、虛擬 DOM、單向數(shù)據(jù)綁定、基于組件的開發(fā),等等。它對(duì)開發(fā)者社區(qū)產(chǎn)生了重大影響,以至于谷歌放棄了 AngularJS,并借鑒 React 的想法推出了徹底重寫的 Angular 2+。React 是目前為止最為流行的 JavaScript 框架,下圖顯示了相關(guān)的 NPM 下載統(tǒng)計(jì)信息。

為了獲得更好的并發(fā)和用戶體驗(yàn),F(xiàn)acebook 宣布完全重寫 React 的核心算法,推出了 React-Fiber 項(xiàng)目。

2020 年,React 仍然是你開發(fā)新項(xiàng)目的首選 Web 框架。其他框架(如 Angular/Angular 2+ 或 Vue)呢?Angular 仍然是一個(gè)不錯(cuò)的 Web 開發(fā)框架,特別適合企業(yè)開發(fā)。我敢肯定谷歌在未來(lái)幾年會(huì)在 Angular 上加大投入。Vue 是另一個(gè)非常流行的 Web 框架,由中國(guó)的巨頭公司阿里巴巴提供支持。如果你已經(jīng)在使用 Angular 或 Vue,就沒必要再遷移到 React 了。
App 開發(fā):原生應(yīng)用
在移動(dòng) App 開發(fā)方面,有關(guān)混合應(yīng)用開發(fā)的炒作有所消停。混合開發(fā)提供了更快的開發(fā)速度,因?yàn)橹恍枰粋€(gè)開發(fā)團(tuán)隊(duì),而不是多個(gè)。但原生應(yīng)用提供了更好的用戶體驗(yàn)和性能。另外,混合應(yīng)用需要經(jīng)過調(diào)整才能使用一些高級(jí)特性。對(duì)于企業(yè)來(lái)說(shuō),原生應(yīng)用仍然是首選的解決方案,這種趨勢(shì)將在 2020 年延續(xù)。Airbnb 在一篇博文中非常詳細(xì)地說(shuō)明了為什么他們要放棄混合應(yīng)用開發(fā)平臺(tái) React Native。
https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a
盡管 Facebook 嘗試改進(jìn) React Native,谷歌也非常努力地推動(dòng)混合 App 開發(fā)平臺(tái) Flutter,但它們?nèi)匀恢贿m合用于原型、POC、MVP 或輕量級(jí)應(yīng)用的開發(fā)。所以,原生應(yīng)用在 2020 年仍將繼續(xù)占主導(dǎo)地位。
在原生應(yīng)用開發(fā)方面,谷歌和蘋果分別將 Kotlin 和 Swift 作為各自平臺(tái)主要的編程語(yǔ)言。谷歌最近再次重申了對(duì) Kotlin 的支持,這對(duì)于 Kotlin 用戶來(lái)說(shuō)無(wú)疑是個(gè)好消息。

混合應(yīng)用開發(fā):React Native
在很多情況下,混合應(yīng)用是個(gè)不錯(cuò)的選擇。在這方面也有很多選擇:Xamarin、Inoic、React Native 和 Flutter。Facebook 基于成熟的 React 框架推出了 React Native。就像 React 在 Web 框架領(lǐng)域占據(jù)主導(dǎo)地位一樣,React Native 在混合應(yīng)用領(lǐng)域也占據(jù)著主導(dǎo)地位,如下圖所示。

React Native 和 React 有共同的基因,都提供了高度的代碼重用性以及“一次開發(fā),到處運(yùn)行”的能力。React Native 的另一個(gè)優(yōu)勢(shì)是 Facebook 本身也用它來(lái)開發(fā)移動(dòng)應(yīng)用。谷歌在這個(gè)領(lǐng)域起步較晚,但在去年,谷歌的混合應(yīng)用開發(fā)框架 Flutter 獲得了不少關(guān)注。Flutter 提供了更好的性能,但需要使用另一門不是那么流行的編程語(yǔ)言 Dart。React Native 在 2020 年將繼續(xù)占主導(dǎo)地位。
API:REST 將占主導(dǎo)地位
REST 是 API 領(lǐng)域事實(shí)上的標(biāo)準(zhǔn),被廣泛用在基于 API 的服務(wù)間通信上。當(dāng)然,除了 REST,我們還有其他選擇,比如來(lái)自谷歌的 gRPC 和來(lái)自 Facebook 的 GraphQL。
它們提供了不同的能力。谷歌開發(fā)的 gRPC 作為遠(yuǎn)程過程調(diào)用(如 SOAP)的化身,使用 Protobuf 代替 JSON 作為消息格式。Facebook 開發(fā)的 GraphQL 作為一個(gè)集成層,避免頻繁的 REST 調(diào)用。gRPC 和 GraphQL 都在各自的領(lǐng)域取得了成功。2020 年,REST 仍然是占主導(dǎo)地位的 API 技術(shù),而 GraphQL 和 gRPC 將作為補(bǔ)充技術(shù)。
人工智能:Tensorflow 2.0 將占主導(dǎo)地位
谷歌和 Facebook 也是深度學(xué)習(xí) / 神經(jīng)網(wǎng)絡(luò)領(lǐng)域的主要玩家。谷歌基于深度學(xué)習(xí)框架 Theano 推出了 TensorFlow,它很快就成為深度學(xué)習(xí) / 神經(jīng)網(wǎng)絡(luò)的主要開發(fā)庫(kù)。谷歌還推出了特別設(shè)計(jì)的 GPU(TPU)來(lái)加速 TensorFlow 的計(jì)算。
Facebook 在深度學(xué)習(xí)領(lǐng)域也不甘落后,他們擁有世界上最大的圖像和視頻數(shù)據(jù)集合。Facebook 基于另一個(gè)深度學(xué)習(xí)庫(kù) Torch 推出了深度學(xué)習(xí)庫(kù) PyTorch。TensorFlow 和 PyTorch 之間有一些區(qū)別,前者使用的是靜態(tài)圖進(jìn)行計(jì)算,而 PyTorch 使用的是動(dòng)態(tài)圖。使用動(dòng)態(tài)圖的好處是可以在運(yùn)行時(shí)糾正自己。另外,PyTorch 對(duì) Python 支持更好,而 Python 是數(shù)據(jù)科學(xué)領(lǐng)域的一門主要編程語(yǔ)言。
隨著 PyTorch 變得越來(lái)越流行,谷歌也趕緊在 2019 年 10 月推出了 TensorFlow 2.0,也使用了動(dòng)態(tài)圖,對(duì) Python 的支持也更好。

2020 年,TensorFlow 2.0 和 PyTorch 將齊頭并進(jìn)。考慮到 TensorFlow 擁有更大的社區(qū),我估計(jì) TensorFlow 2.0 將成為占主導(dǎo)地位的深度學(xué)習(xí)庫(kù)。
數(shù)據(jù)庫(kù):SQL是王者,分布式SQL是王后
在炒作 NoSQL 的日子里,人們嘲笑 SQL,還指出了 SQL 的種種不足。有很多文章說(shuō) NoSQL 有多么的好,并將要取代 SQL。但等到炒作的潮水褪去,人們很快就意識(shí)到,我們的世界不能沒有 SQL。以下是最流行的數(shù)據(jù)庫(kù)的排名。

可以看到,SQL 數(shù)據(jù)庫(kù)占據(jù)了前四名。SQL 之所以占主導(dǎo)地位,是因?yàn)樗峁┝?ACID 事務(wù)保證,而 ACID 是業(yè)務(wù)系統(tǒng)最潛在的需求。NoSQL 數(shù)據(jù)庫(kù)提供了橫向伸縮能力,但代價(jià)是不提供 ACID 保證。
互聯(lián)網(wǎng)公司一直在尋找“大師級(jí)數(shù)據(jù)庫(kù)”,也就是既能提供 ACID 保證又能像 NoSQL 那樣可橫向伸縮的數(shù)據(jù)庫(kù)。目前有兩個(gè)解決方案可以部分滿足對(duì)“大師級(jí)數(shù)據(jù)庫(kù)”的要求,一個(gè)是亞馬遜的 Aurora,一個(gè)是谷歌的 Spanner。Aurora 提供了幾乎所有的 SQL 功能,但不支持橫向?qū)懮炜s,而 Spanner 提供了橫向?qū)懮炜s能力,但對(duì) SQL 支持得不好。
2020 年,但愿這兩個(gè)數(shù)據(jù)庫(kù)能夠越走越近,或者有人會(huì)帶來(lái)一個(gè)“分布式 SQL”數(shù)據(jù)庫(kù)。如果真有人做到了,那一定要給他頒發(fā)圖靈獎(jiǎng)。
數(shù)據(jù)湖:MinIO 將要崛起
現(xiàn)代數(shù)據(jù)平臺(tái)非常的復(fù)雜。企業(yè)一般都會(huì)有支持 ACID 事務(wù)的 OLTP 數(shù)據(jù)庫(kù)(SQL),也會(huì)有用于數(shù)據(jù)分析的 OLAP 數(shù)據(jù)庫(kù)(NoSQL)。除此之外,它們還有其他各種數(shù)據(jù)存儲(chǔ)系統(tǒng),比如用于搜索的 Solr、ElasticSearch,用于計(jì)算的 Spark。企業(yè)基于數(shù)據(jù)庫(kù)構(gòu)建自己的數(shù)據(jù)平臺(tái),將 OLTP 數(shù)據(jù)庫(kù)的數(shù)據(jù)拷貝到數(shù)據(jù)湖中。各種類型的數(shù)據(jù)應(yīng)用程序(比如 OLAP、搜索)將數(shù)據(jù)湖作為它們的事實(shí)來(lái)源。
HDFS 原本是事實(shí)上的數(shù)據(jù)湖,直到亞馬遜推出了對(duì)象存儲(chǔ) S3。S3 可伸縮,價(jià)格便宜,很快就成為很多公司事實(shí)上的數(shù)據(jù)湖。使用 S3 唯一的問題是數(shù)據(jù)平臺(tái)被緊緊地綁定在亞馬遜的 AWS 云平臺(tái)上。雖然微軟 Azure 推出了 Blob Storage,谷歌也有類似的對(duì)象存儲(chǔ),但都不是 S3 的對(duì)手。
對(duì)于很多公司來(lái)說(shuō),MinIO 或許是它們的救星。MinIO 是一個(gè)開源的對(duì)象存儲(chǔ),與 S3 兼容,提供了企業(yè)級(jí)的支持,并專門為云原生環(huán)境而構(gòu)建,提供了與云無(wú)關(guān)的數(shù)據(jù)湖。

微軟在 Azure Marketplace 是這么描述 MinIO 的:“為 Azure Blog Storage 服務(wù)提供與亞馬遜 S3 API 兼容的數(shù)據(jù)訪問”。如果谷歌 GCP 和其他云廠商也提供 MinIO,那么我們將會(huì)向多云邁出一大步。
大數(shù)據(jù)批處理:Spark 將繼續(xù)閃耀
現(xiàn)如今,企業(yè)通常需要基于大規(guī)模數(shù)據(jù)執(zhí)行計(jì)算,所以需要分布式的批處理作業(yè)。Hadoop 的 Map-Reduce 是第一個(gè)分布式批處理平臺(tái),后來(lái) Spark 取代了 Hadoop 的地位,成為真正的批處理之王。Spark 是怎樣提供了比 Hadoop 更好的性能的?我之前寫了另一篇文章,對(duì)現(xiàn)代數(shù)據(jù)平臺(tái)進(jìn)行了深入分析。
https://towardsdatascience.com/programming-language-that-rules-the-data-intensive-big-data-fast-data-frameworks-6cd7d5f754b0
Spark 解決了 Hadoop Map-Reduce 的痛點(diǎn),它將所有東西放在內(nèi)存中,而不是在完成每一個(gè)昂貴的操作之后把數(shù)據(jù)保存在存儲(chǔ)系統(tǒng)中。盡管 Spark 重度使用 CPU 和 JVM 來(lái)執(zhí)行批處理作業(yè),但這并不妨礙它成為 2020 年批處理框架之王。我希望有人能夠使用 Rust 開發(fā)出一個(gè)更加高效的批處理框架,取代 Spark,并為企業(yè)省下大量的云資源費(fèi)用。
大數(shù)據(jù)流式處理:Flink 是未來(lái)
幾年前,實(shí)現(xiàn)實(shí)時(shí)的流式處理幾乎是不可能的事情。一些微批次處理框架(比如 Spark Streaming)可以提供“幾近”實(shí)時(shí)的流式處理能力。不過,F(xiàn)link 改變了這一狀況,它提供了實(shí)時(shí)的流式處理能力。
2019 年之前,F(xiàn)link 未能得到足夠的關(guān)注,因?yàn)樗鼰o(wú)法撼動(dòng) Spark。直到 2019 年 1 月份,中國(guó)巨頭公司阿里巴巴收購(gòu)了 Data Artisan(Flink 背后的公司)。

在 2020 年,企業(yè)如果想要進(jìn)行實(shí)時(shí)流式處理,F(xiàn)link 應(yīng)該是不二之選。不過,跟 Spark 一樣,F(xiàn)link 同樣重度依賴 CPU 和 JVM,并且需要使用大量的云資源。
字節(jié)碼:WebAssembly將被廣泛采用
我從 JavaScript 作者 Brandon Eich 的一次訪談中知道了 WebAssembly 這個(gè)東西。現(xiàn)代 JavaScript(ES5 之后的版本)是一門優(yōu)秀的編程語(yǔ)言,但與其他編程語(yǔ)言一樣,都有自己的局限性。最大的局限性是 JavaScript 引擎在執(zhí)行 JavaScript 時(shí)需要讀取、解析和處理“抽象語(yǔ)法樹”。另一個(gè)問題是 JavaScript 的單線程模型無(wú)法充分利用現(xiàn)代硬件(如多核 CPU 或 GPU)。正因?yàn)檫@些原因,很多計(jì)算密集型的應(yīng)用程序(如游戲、3D 圖像)無(wú)法運(yùn)行在瀏覽器中。
一些公司(由 Mozilla 帶領(lǐng))開發(fā)了 WebAssembly,一種底層字節(jié)碼格式,讓任何一門編程語(yǔ)言都可以在瀏覽器中運(yùn)行。目前發(fā)布的 WebAssembly 版本可以支持 C++、Rust 等。

WebAssembly 讓計(jì)算密集型應(yīng)用程序(比如游戲和 AutoCAD)可以在瀏覽器中運(yùn)行。不過,WebAssembly 的目標(biāo)不僅限于此,它還要讓應(yīng)用程序可以在瀏覽器之外運(yùn)行。WebAssembly 可以被用在以下這些“瀏覽器外”的場(chǎng)景中。
- 移動(dòng)設(shè)備上的混合原生應(yīng)用。
- 沒有冷啟動(dòng)問題的無(wú)服務(wù)器計(jì)算。
- 在服務(wù)器端執(zhí)行不受信任的代碼。
我預(yù)測(cè),2020 年將是 WebAssembly 取得突破的一年,很多巨頭公司(包括云廠商)和社區(qū)將會(huì)擁抱 WebAssembly。
代碼:低代碼 / 無(wú)代碼將更進(jìn)一步
快速的數(shù)字化和工業(yè) 4.0 革命意味著軟件開發(fā)者的供需缺口巨大。由于缺乏開發(fā)人員,很多企業(yè)無(wú)法實(shí)現(xiàn)它們的想法。為了降低進(jìn)入軟件開發(fā)的門檻,可以嘗試無(wú)代碼(No Code)或低代碼(Low Code)軟件開發(fā),也就是所謂的 LCNC(Low-Code No-Code)。它已經(jīng)在 2019 年取得了一些成功。

LCNC 的目標(biāo)是讓沒有編程經(jīng)驗(yàn)的人也能開發(fā)軟件,只要他們想要實(shí)現(xiàn)自己的想法。
雖然我對(duì)在正式環(huán)境中使用 LCNC 框架仍然心存疑慮,但它為其他公司奠定了良好的基礎(chǔ),像亞馬遜和谷歌這樣的公司可以基于這個(gè)基礎(chǔ)構(gòu)建出有用的產(chǎn)品,就像 AWS Lambda 的蓬勃發(fā)展是以谷歌 App Engine 為基礎(chǔ)。
2020 年,LCNC 將會(huì)獲得更多關(guān)注。