查看原文
其他

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗

赵勇萍 京东技术
2024-08-24


Tech

导读

本文将探讨如何通过结合使用SpringBoot 2.7和JDK 17来优化和加速Java应用的性能。通过分析SpringBoot的新特性和改进,以及JDK 17的性能提升点,讨论它们如何协同工作,为Java开发者带来更高效的编程体验。本文还将涉及迁移现有应用至这一新环境的可能挑战和注意事项,为那些追求最前沿技术的勇敢尝试者提供实践建议。




01 前言

在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

众所周知,SpringBoot3.0迎来了全面支持JDK17的局面,且最低支持版本就是JDK17,这就意味着,Spring社区将完全抛弃JDK8,全面转战JDK17。作为JAVA开源生态里的扛把子,Spring可以说是整个JAVA生态的风向标,可以说,当Spring转战JDK17,会很快带领JAVA生态全面的跟进JDK17。而本篇文章重点讲述Spring版本和JDK17升级中的实践整理。



02  
为什么是Spring Boot 2.7
  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
Spring Boot 3.0是全面放弃JDK8,而Spring社区当然不会把事情做的那么决绝,在推出3.0之前,Spring就开始着手布局JDK17升级。而2.7版本,就是Spring社区为了升级JDK17而推出的过渡版本,具体包括一以下几个方面的升级和改进:
1.支持了JDK 17的新特性例如switch表达式、文本块、局部变量类型推断等。这使得在Spring应用程序中使用JDK 17的特性变得更加容易和方便。
2.利用了JDK 17的性能优化:JDK 17引入了许多性能优化,例如新的垃圾收集器、线程调度等。Spring 2.7利用了这些性能优化,可以提高Spring应用程序的性能和响应速度。
3.默认配置与JDK 17兼容:Spring Boot 2.7的默认配置与JDK 17兼容,这意味着您不需要进行额外的配置就可以在JDK 17上运行Spring Boot应用程序。这点很重要,Spring Boot 2.7依赖于Servlet 4.0,而Servlet 4.0本身并不直接支持JDK 17, Spring Boot 2.7为了支持JDK 17进行了一些兼容性调整和优化,以使其能够在JDK 17上运行。
4.改进的安全性:JDK 17通过增强加密算法、禁用旧版TLS和SSL协议等增强了安全性。Spring Boot 2.7利用了这些安全改进,提高了应用程序的安全性。
5.持续的性能优化和提升。相比于老系统的2.1到2.3版本,2.7版本对内存管理和bean管理都有很大程度的优化和提升,内存使用更加合理。虽然官网没有给出所谓的性能提升对比,但性能的优化和系统的稳定性是一定加强的。
总之,使用Spring Boot 2.7可以更好地利用JDK 17的特性,提高应用程序的性能和响应速度,同时还可以获得更好的兼容性和安全性。所以通过Spring Boot 2.7过渡升级JDK17,是一种更为温和方式,且遇到的兼容性问题最小。Spring官方给出了这样的说明:
If you’re currently running with an earlier version of Spring Boot, we strongly recommend that you upgrade to Spring Boot 2.7 before migrating to Spring Boot 3.0.


03   为什么是JDK17  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。

关于JDK17的新特性和优势,对于它支持的新语法和编程特性,此处不再赘述。

落地JDK17的动力主要源于两个方面:一是更为安全的语言特性,二是更加优异的垃圾回收器和性能提升。

3.1  安全的语言特性


    
安全性首先体现在JDK17对于包扫描和反射的权限控制,可能大家对当年的FastJson漏洞记忆犹新,它的病根在于对反射的滥用。而对于这种反射的滥用,在JDK17里有了更严格的控制。
JDK 17对反射进行了优化,主要表现在对反射调用进行了权限控制。具体来说,它通过setAccessible()方法启动或禁止访问安全检查开关。当参数值为true时,反射的对象在使用时取消安全检查,提高反射的效率;当参数值为false时,反射的对象执行安全检查。这样的优化使得在处理反射调用时,可以更加灵活地控制访问权限。
1、除此之外,JDK 17增强了包扫描的权限控制。在之前的版本中,Java的包扫描是基于类的,而在JDK 17中,它扩展到了对整个包的权限控制。这使得开发者可以更加精细地控制对特定包的访问权限。
2、针对于语法本身,引入了密封的类和接口,具体使用细节大家可以网上查看。通过密封类和接口,进一步增加了面向对象开发的封闭性,提升代码质量的安全可靠。

3.2  垃圾收集器


    

JDK17引入了ZGC作为垃圾收集器,此处引用一下京东科技的曲振富做的关于不同垃圾收集器在不同JDK版本下的压测结果:

图 1.

压测服务背景:

DOS平台上选择了不同配置的机器(2C4G、4C8G、8C16G),并分别使用JDK8、JDK11和JDK17进行部署和压测。整个压测过程限时60分钟,用180个虚拟用户并发请求一个接口,每次接口请求都创建512Kb的数据。最终产出不同GC回收器的各项指标数据,来分析GC的性能提升效果。

3.3  OpenJDK17下载地址


    

本文为读者提供了一个OpenJDK17的下载地址: 

https://adoptium.net/zh-cn/temurin/releases/?version=17&os=linux&arch=x64


  
04   

   行云部署上的实践方案  

  
 

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目

4.1  Spring Boot 2.7


    
4.1.1. pom.xml版本依赖
在实践的版本选择中,本文选择了2.7大版本下的最新小版本,即Spring Boot 2.7.17版本。pom.xml依赖如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.jd.magnus</groupId> <artifactId>magnus-multi-ddd</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.17</version> </parent>
<modules> <module>magnus-multi-ddd-adapter</module> <module>magnus-multi-ddd-application</module> <module>magnus-multi-ddd-domain</module> <module>magnus-multi-ddd-infrastructure</module> <module>magnus-multi-ddd-client</module> <module>magnus-multi-ddd-worker</module> </modules>
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jsf-lite.version>1.0.0-HOTFIX-T2</jsf-lite.version> <ump.version>20221231.1</ump.version> </properties></project>

4.1.2. 动态配置

Spring Boot 2.7对动态配置进行了更新。具体来说,Spring Boot 2.7更改了自动配置注册文件的路径和格式,从META-INF/spring.factories变更为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。

同时,Spring Boot 2.7还引入了新的注解@SpringBootApplication,该注解包含了@EnableAutoConfiguration和@ComponentScan等注解,使得配置更加简洁和方便。此外,Spring Boot 2.7还更新了一些自动配置的类和方法,以支持新版本的Spring Framework和Java。

1.废弃的方法和类删除。列一下主要删除的方法和类:

SpringBootServletInitializer:在Spring Boot 2.7中,该类已经被移除,建议使用SpringBootServletWebServerApplicationContext来代替。ServletWebServerFactoryCustomizer:这个接口已经从Spring Boot 2.7中移除,可以使用WebServerFactoryCustomizer来代替。BasicErrorController:这个类已经从Spring Boot 2.7中移除,可以使用ErrorController接口来代替。ContentNegotiationStrategy:这个接口已经从Spring Boot 2.7中移除,可以使用RequestMappingHandlerMapping的setContentTypeResolver(ContentTypeResolver)方法来代替。HttpMessageConverters:这个接口已经从Spring Boot 2.7中移除,可以使用HttpMessageConvertingComparator来代替。HttpMessageConvertingComparator:这个类已经从Spring Boot 2.7中移除,可以使用ComparatorChain来代替。ServletWebServerFactoryCustomizerBeanPostProcessor:这个类已经从Spring Boot 2.7中移除。SpringBootApplicationContextLoader:这个类已经从Spring Boot 2.7中移除,可以使用SpringApplicationWebApplicationContext来代替。SpringBootServletInitializerAutoConfiguration:这个类已经从Spring Boot 2.7中移除,可以使用SpringBootServletWebServerApplicationContextAutoConfiguration来代替。
此外,还有一些被移除的配置属性,例如spring.http.converters.preferred-json-mapper、spring.jackson.serialization-features.default-pretty-print-xml、spring.jackson.serialization-features.sort-property-names-by-default等。其他信息读者可以查看Spring的版本更新说明:

github上的release版本说明:

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes

4.1.3. 单元测试升级

在Spring Boot 2.7版本,已经不再依赖JUnit4, 而是将Test换成了 JUnit Jupiter, 这也导致之前单元测试使用的方法和注解会产生变化。
常用的一些方法和注解变化如下:
变更项
JUnit4
JUnit Jupiter
@Test注解

包路径:org.junit.Test

包路径:org.junit.jupiter.api.Test
断言
类:org.junit.Assert
类:rg.junit.jupiter.api.Assertions,提供了更简洁的断言方法
@RunWith
需要使用@RunWith注解来指定测试运行器
@RunWith移除,不再需要,单侧只需要@SpringBootTest
参数化接口测试

@RunWith配合

@Parameters实现参数化

@ParameterizedTest和@ValueSource注解配合使用


4.1.4. hibernate-validator包依赖问题

Springboot从2.3以后,spring-boot-starter-web中不再引入hibernate-validator,需要手动引入。此处可以直接引用spring-boot-starter-validation的包,里面会间接引用hibernate-validator的包,且版本号可以被spring boot parent统一管理。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId></dependency>
4.1.5. 诊断升级兼容性方法
如果是老项目版本升级,Spring Boot 提供了一种在启动时分析应用程序环境并打印诊断信息的方法,而且还可以在运行时临时迁移属性。要启用该功能,请将以下依赖项添加到项目中:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-properties-migrator</artifactId> <scope>runtime</scope></dependency>

4.2  行云部署配置


    
4.2.1. 镜像配置

首先需要新的基础镜像包,包括OpenJDK17的。之前行云的镜像市场上是没有相关的镜像的,后来联系科技运维同事帮忙制作了新的镜像,新的镜像包是基于Tomcat应用类型的,大家可以在jdos的中国站的景象市场搜索到。镜像名如下,

base_tomcat/java-jd-centos7-jdk17-tomcat8.5.42-ngx197:latest
4.2.2. 编译配置
编译配置中,需要选择的JDK版本为17,同时Maven的版本也可以尽量选高一些。因为按照惯例,maven的版本会对JDK的版本兼容性有所不同,一般越是高版本的Maven对JDK17兼容性更好。虽然官方没有明确说明Maven版本支持情况,但选择高版本的Maven是比较稳妥的选择,所以在JDOS上选择maven-3.9.0版本比较好。

图 2.

4.2.3. JVM参数配置

然后就是配置JVM启动参数,需要开启ZGC。具体启动参数以4C8G的资源为例,配置参数如下:

-Xms5324m -Xmx5324m -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m -XX:MaxDirectMemorySize=983m -Djava.library.path=/usr/local/lib -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/Logs -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300 -XX:+UseZGC

此处需要注意,ZGC不要配置并行GC线程的数量,并发标记线程数等信息,配置了反而会出现启动报错情况。

4.3  兼容性问题说明


    
关于兼容性问题本文以如何兼容京东的UMP, DUCC等为例。这些中间件的兼容性问题产生主要由于JDK17中对于反射和扫描的安全性检查导致的,一个简单的解决办法是将没开放的module强制对外开放。所以需要一些额外配置。
先整理结论,额外配置集合如下,该集合可以配置在VM 启动参数之中:
--add-opens java.base/sun.security.action=ALL-UNNAMED--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/java.math=ALL-UNNAMED--add-opens java.base/java.util=ALL-UNNAMED--add-opens java.base/sun.util.calendar=ALL-UNNAMED--add-opens java.base/java.util.concurrent=ALL-UNNAMED--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED--add-opens java.base/java.security=ALL-UNNAMED--add-opens java.base/jdk.internal.loader=ALL-UNNAMED--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED--add-opens java.base/java.net=ALL-UNNAMED--add-opens java.base/sun.nio.ch=ALL-UNNAMED--add-opens java.management/java.lang.management=ALL-UNNAMED--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED--add-opens java.management/sun.management=ALL-UNNAMED--add-opens java.base/sun.security.action=ALL-UNNAMED--add-opens java.base/sun.net.util=ALL-UNNAMED--add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED

1. SGM依赖需要加入

--add-opens java.management/java.lang.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED

2. R2M需要加入

--add-opens java.base/java.time=ALL-UNNAMED

3. DUCC依赖需要加入

--add-opens java.base/java.util.concurrent=ALL-UNNAMED--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED--add-opens java.base/java.security=ALL-UNNAMED--add-opens java.base/jdk.internal.loader=ALL-UNNAMED--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED 

4. AKS依赖需要加入

--add-exports java.base/sun.security.action=ALL-UNNAMED--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/java.math=ALL-UNNAMED--add-opens java.base/java.util=ALL-UNNAMED--add-opens java.base/sun.util.calendar=ALL-UNNAMED

5. Pfinder依赖需要加入

--add-opens java.base/sun.net.util=ALL-UNNAMED

6. jsf依赖需要加入

--add-opens java.base/java.time=ALL-UNNAMED #当使用LacalDate做出入参时需要添加--add-opens java.base/java.io=ALL-UNNAMED

7. Swagger兼容性配置

分两步:
(1)properties配置文件中增加一下配置:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
(2)代码在配置类中新增BeanPostProcessor重写:
/** * 增加如下配置可解决Spring Boot 2.7.15 与Swagger 3.0.0 不兼容问题**/@Beanpublic BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { return new BeanPostProcessor() {
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); } return bean; }
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) { List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList()); mappings.clear(); mappings.addAll(copy); }
@SuppressWarnings("unchecked") private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { try { Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); field.setAccessible(true); return (List<RequestMappingInfoHandlerMapping>) field.get(bean); } catch (IllegalArgumentException | IllegalAccessException e) { throw new IllegalStateException(e); } } }; }
8. JDK维度兼容性问题(只挑作者本人遇到的问题重点说)
•JDK11就删除了javaFX库,所以该库下的所有方法在JDK17中不可用。如果读者是从JDK8直接升级到JDK17, 需要注意,javaFX下的javafx.util包方法有可能会被大家不小心用到。
以下列举一下javafx.util下的一些常用工具类(项目中尽量不要再用):
类名
方法说明
javafx.util.Pair
getKey():获取 Pair 对象的键。getValue():获取 Pair 对象的值。setKey(K key):设置 Pair 对象的键。setValue(V value):设置 Pair 对象的值。
avafx.util.Duration
toSeconds():将持续时间转换为秒。toMillis():将持续时间转换为毫秒。toNanos():将持续时间转换为纳秒。add(Duration other):将另一个持续时间添加到当前持续时间。subtract(Duration other):从当前持续时间中减去另一个持续时间。
javafx.util.converter
fromString(String value):将字符串值转换为目标类型。toString(T value):将目标类型的值转换为字符串。
javafx.util.StringConverter
fromString(String value):将字符串值转换为目标类型。toString(T value):将目标类型的值转换为字符串。


•其次, Java EE(Java Enterprise Edition)规范在 Java 9 之后被重新命名为 Jakarta EE。这是由于 Java EE 规范的开源版本迁移到了 Eclipse Foundation,并改名为 Jakarta EE。

因此有一些包名路径变更,为了兼容JSF,需要手动引入一些JAR包。但由于部署环境采用的是外置的Tomcat8,所以还是包含java EE的相关包。不需要额外加入,但本地debug时,需要加入。

尽管 Jakarta EE 是 Java EE 的继任者,但为了保持向后兼容性,许多 Java EE 规范和 API 在 Jakarta EE 中仍然存在,并且在 Jakarta EE 中的命名空间从 javax 变为 jakarta。且一些命名空间也改变了,比如javax包下的方法和属性都不能再试用,例如: javax.xml.bind.*更改为jakarta.xml.bind.*。以下有一个该问题引起的JSF报错修复:
关于JSF启动有报错信息:运行时找不到 javax.xml.bind.JAXBException 类。在 JDK 9 及更高版本中,javax.xml.bind 包被移除了,并且不再包含在标准的 Java SE 中。如果您的项目依赖于 JAXB API,您可以尝试以下解决方法之一:如果您使用的是 JDK 8 或更早版本,请确保您的项目使用的是兼容的 JDK 版本。如果您使用的是 JDK 9 或更高版本,并且需要使用 JAXB API,您可以添加以下依赖项来解决该问题:<dependency> <groupId>jakarta.xml.bind</groupId> <artifactId>jakarta.xml.bind-api</artifactId> <version>3.0.1</version> <!-- 根据您的需求选择合适的版本 --></dependency>
•此处正好多说一下Spring boot 3.0的一个小问题,@Resource在Spring boot 3.0上,已经不再依赖javax.annoation包,所以包路径也由javax.annotation.Resource改为了jakarta.annotation.Resource。当然此处在2.7版本依然兼容,可以不用修改。
以下贴一个改动对比的表格:
module
packagesreplacement groupldreplacement artifactld
java.activationjavax.activtioncom.sun.activationjakarta.activation
java.xml.ws.annotationjava.annotationjakartaa.annotationjakarta.annotation-api
java.xml.bindjavax.xml.bind.*jakarta.xml.bind.com.sun.xml.bindjakarta.xml.bind-apijab-impl


除此之外,JSF在使用ForkJoinPool会导致反序列化问题,包括底层使用ForkJoinPool的CompletableFuture.runAsync 有同样问题。原因是升级JDK后,jsf依赖了jaxb包在新JDK中是缺失的,会导致注册中心自动切换有问题,解决方案是手动引入如下包:

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.2.8</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.2.8</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.2.8</version></dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency>
垃圾回收器的话,从JDK14开始,已经删除了CMS,所以在JDK17下,只建议使用ZGC。

还有一个最大的变化是之前的--illegal-access参数不在可用,如果在java 17使用这个参数访问受限的api则会报出InaccessibleObjectException,大多数情况下只要升级了依赖项是不会碰到这个情况的,但如果出现问题,则可以使用--add-opens来对不可访问的api授权。以上的SGM,R2M,DUCC,AKS,Pfinder的兼容性问题都是因为这个特性变化引起的。


05   脚手架支持  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目

目前最的DDD脚手架已经支持Spring Boot 2.7.17 和JDK17 ,下载脚本如下:

mvn archetype:generate \ -DarchetypeGroupId=com.jd.magnus \ -DarchetypeArtifactId=magnus-multi-ddd-archetype \ -DarchetypeVersion=1.0.0-SNAPSHOT \ -DinteractiveMode=false \ -DarchetypeCatalog=remote \ -Dversion=1.0.0-SNAPSHOT \ -DgroupId=com.jdl.sps \ -DartifactId=bff-demo1

该脚手架以在京东内部申请为开源项目,开源项目地址如下:

http://xingyun.jd.com/shendeng/openSource/detail/793



06   总结  

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目


目前,将部门内的京旗API服务 发货平台BFF服务物流发货商家基础信息服务作为试点,已经在行云测试环境上用JDK17+Spring Boot2.7版本进行试运行,京东三方依赖包括JSF lite版本,ducc, easyJob,jmq, 云redis, ump, pfinder。经测试,兼容性没有太大问题,服务可用。后续还会进一步观察和测试。据作者了解,现在开源社区里,以apache为代表的大型开源项目都对JDK17有了不错的兼容, 未来可以逐步再从Spring Boot 2.7升级到Spring Boot 3.0。


推荐阅读浅析“代码可视化”京东科技设计稿转代码平台介绍百万并发场景中倒排索引与位图计算的实践架构探索之ClickHouse

求分享

求点赞

求在看

打造SAAS化服务的会员徽章体系,可以作为标准的产品化方案统一对外输出。结合现有平台的通用能力,实现会员行为全路径覆盖,并能结合企业自身业务特点,规划相应的会员精准营销活动,提升会员忠诚度和业务的持续增长。底层能力:维护用户基础数据、行为数据建模、用户画像分析、精准营销策略的制定

▪功能支撑:会员成长体系、等级计算策略、权益体系、营销底层能力支持

▪用户活跃:会员关怀、用户触达、活跃活动、业务线交叉获客、拉新促活

修改于
继续滑动看下一个
京东技术
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存