diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/pom.xml new file mode 100644 index 0000000000..43ab972de3 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/pom.xml @@ -0,0 +1,90 @@ + + + + + com.alibaba.cloud + spring-cloud-alibaba-examples + ${revision} + ../../pom.xml + + 4.0.0 + + nacos-druid-example + Spring Cloud Starter Alibaba Nacos Config Proxy Druid Example + Example demonstrating how to use nacos config to proxy druid + jar + + + 17 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + mysql + mysql-connector-java + 8.0.28 + runtime + + + + com.alibaba + druid-spring-boot-3-starter + 1.2.27 + + + com.alibaba + druid + 1.2.27 + + + + com.alibaba.nacos + nacos-client-mse-extension + 1.0.6 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/readme-zh.md b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/readme-zh.md new file mode 100644 index 0000000000..e5da51a11c --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/readme-zh.md @@ -0,0 +1,66 @@ +# Nacos Proxy Druid动态数据源 + +## 项目说明 + +本项目介绍如何使用 Spring Cloud Alibaba Nacos Config 代理Druid数据源,实现应用的数据源配置运行时无损轮转 。 + +[Nacos](https://github.com/alibaba/Nacos) 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 + +## 购买 MSE Nacos企业版 + +商业化MSE Nacos数据源管理功能,集合KMS实现了对应用访问数据库所需的参数加密托管,包括账号密码,数据库连接池参数等,同时应用侧结合druid实现应用运行时数据源的无损轮转。 + +### + +参照 https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/spring-application-rds-data-source-account-password-run-time-rotation 完成数据源创建。 + +## 应用侧接入示例 + +### Spring Cloud Alibaba Nacos Config + +nacos-druid-example已经完成了基础的访问数据库的接口实例创建以及在application.properties的参数配置。 + +通过一下脚本创建工程代码访问的数据库表 + +``` +初始化表 +CREATE TABLE `demo_entity` ( +`id` bigint NOT NULL AUTO_INCREMENT, +`name` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, +`content` longtext, +PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=199492 DEFAULT CHARSET=utf8mb3 + +初始化数据 +INSERT INTO `demo_entity` (`id`,`name`,`content`) VALUES (123,'testname',null); + +``` + +在MSE Nacos企业版中创建好数据源之后,可以参照应用侧接入中的指引,对application.properties中的配置进行替换即可。 + +``` +# import datasource config to init druid datasource. +spring.config.import[0]=optional:nacos:{替换为数据源配置的DataId}?group=nacos-datasource + +# nacos server address, replace it with your own +spring.cloud.nacos.config.server-addr={替换为nacos企业版接入地址,保证本地可正常链接9848端口} +# nacos namespace, replace it with your own +spring.cloud.nacos.config.namespace={替换为nacos企业版数据源所在命名空间ID} + +# nacos kms regionId , replace it with your own +spring.cloud.nacos.config.kms_region_id={替换为nacos企业版实例关联KMS的RegionId} + +# nacos proxy druid switch and data id +spring.nacos.config.proxy.druid.enabled=true +spring.nacos.config.proxy.druid.data-id={替换为数据源配置的DataId} + +``` + +除以上配置外,需要通过环境变量或者-D JVM参数设置访问Nacos及KMS的凭证 + +``` +-Dspring.cloud.nacos.config.access-key=LTA**************** +-Dspring.cloud.nacos.config.secret-key=ot*************** +``` + +按要求设置参数后,运行NacosSpringBootDemoApplication即可。 diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/DemoController.java b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/DemoController.java new file mode 100644 index 0000000000..0dee2a214b --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/DemoController.java @@ -0,0 +1,136 @@ +package com.alibaba.demo.nacosdruidexample; + +import java.util.Date; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Executors; + +import com.alibaba.demo.nacosdruidexample.dao.JdbcEntityRepository; +import com.alibaba.demo.nacosdruidexample.dao.JpaDemoEntityRepository; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + + +@RestController("demo") +public class DemoController { + + JpaDemoEntityRepository jpaDemoEntityRepository; + + JdbcEntityRepository jdbcEntityRepository; + + @Value(value = "${testKey:123}") + private String testKey; + + @GetMapping("/getTestKey") + public String getTestKey(HttpServletResponse httpServletResponse) { + return testKey + new Date(); + } + + DemoController(JpaDemoEntityRepository jpaDemoEntityRepository, JdbcEntityRepository jdbcEntityRepository) { + this.jpaDemoEntityRepository = jpaDemoEntityRepository; + this.jdbcEntityRepository = jdbcEntityRepository; + + for (int i = 0; i < 10; i++) { + scheduleTask(); + } + } + + private void scheduleTask() { + Executors.newScheduledThreadPool(2).scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + selectTask(); + } + }, 200, 200, MILLISECONDS); + } + + private void selectTask() { + int count = 200; + int successCount = 0; + int failCount = 0; + long start = System.currentTimeMillis(); + while (count > 0) { + try { + String nameById = jdbcEntityRepository.findNameById(123l); + if (nameById != null) { + successCount++; + } + } + catch (Throwable throwable) { + failCount++; + } + finally { + count--; + } + } + + System.out.println(new Date() + ",SuccessCount:" + successCount + ",failCount:" + failCount + ", cost:" + (System.currentTimeMillis() - start) + " ms"); + + } + + private void searchTask() { + int count = 1000; + + while (count > 0) { + long start = System.currentTimeMillis(); + + try { + + List nameById = jdbcEntityRepository.getByContent(generateRandomString(1)); + if (nameById != null) { + System.out.println("Success, cost:" + (System.currentTimeMillis() - start) + " ms"); + + } + } + catch (Throwable throwable) { + System.out.println("Fail, cost:" + (System.currentTimeMillis() - start) + " ms"); + + } + finally { + count--; + } + } + } + + private void insertTask() { + int count = 1000; + int successCount = 0; + int failCount = 0; + long start = System.currentTimeMillis(); + while (count > 0) { + try { + jdbcEntityRepository.insertNameById(generateRandomString(5), generateRandomString(100_100)); + + } + catch (Throwable throwable) { + failCount++; + } + finally { + count--; + } + } + + System.out.println(new Date() + ",SuccessCount:" + successCount + ",failCount:" + failCount + ", cost:" + (System.currentTimeMillis() - start) + " ms"); + + } + + private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + public static String generateRandomString(int length) { + Random random = new Random(); + StringBuilder sb = new StringBuilder(length); + + // 从字符集中随机选择字符并追加到 StringBuilder 中 + for (int i = 0; i < length; i++) { + int index = random.nextInt(CHARACTERS.length()); + sb.append(CHARACTERS.charAt(index)); + } + + return sb.toString(); + } +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/NacosSpringBootDemoApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/NacosSpringBootDemoApplication.java new file mode 100644 index 0000000000..ea5642a854 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/NacosSpringBootDemoApplication.java @@ -0,0 +1,16 @@ +package com.alibaba.demo.nacosdruidexample; + + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@ComponentScan(basePackages = {"com.alibaba.cloud", "com.alibaba.demo"}) +@SpringBootApplication +public class NacosSpringBootDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(NacosSpringBootDemoApplication.class, args); + } + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/DemoEntity.java b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/DemoEntity.java new file mode 100644 index 0000000000..116d87810f --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/DemoEntity.java @@ -0,0 +1,42 @@ +package com.alibaba.demo.nacosdruidexample.dao; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class DemoEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + private String description; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JdbcEntityRepository.java b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JdbcEntityRepository.java new file mode 100644 index 0000000000..9c34409164 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JdbcEntityRepository.java @@ -0,0 +1,12 @@ +package com.alibaba.demo.nacosdruidexample.dao; + +import java.util.List; + +public interface JdbcEntityRepository { + + String findNameById(Long id); + + void insertNameById(String name, String content); + + List getByContent(String content); +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JdbcEntityRepositoryImpl.java b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JdbcEntityRepositoryImpl.java new file mode 100644 index 0000000000..b4672966e0 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JdbcEntityRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.alibaba.demo.nacosdruidexample.dao; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Component +public class JdbcEntityRepositoryImpl implements JdbcEntityRepository { + + @Autowired + private JdbcTemplate jdbcTemplate; + + public String findNameById(Long id) { + try { + return jdbcTemplate.queryForObject("SELECT name FROM demo_entity WHERE id = ?", String.class, id); + } + catch (EmptyResultDataAccessException e) { + return null; + } + catch (Throwable e) { + throw e; + } + } + + @Override + public void insertNameById(String name, String content) { + jdbcTemplate.update("insert into demo_entity(name,content) values(?,?) ", name, content); + } + + @Override + public List getByContent(String content) { + return jdbcTemplate.queryForList("select content from demo_entity WHERE content like ? limit 400", new Object[] {"%" + content + "%"}, String.class); + } + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JpaDemoEntityRepository.java b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JpaDemoEntityRepository.java new file mode 100644 index 0000000000..be5c2ee3b7 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/java/com/alibaba/demo/nacosdruidexample/dao/JpaDemoEntityRepository.java @@ -0,0 +1,7 @@ +package com.alibaba.demo.nacosdruidexample.dao; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaDemoEntityRepository extends JpaRepository { + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/resources/application.properties new file mode 100644 index 0000000000..692af18c66 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-druid-example/src/main/resources/application.properties @@ -0,0 +1,17 @@ +spring.application.name=nacos-druid-example + +# import datasource config to init druid datasource. +spring.config.import[0]=optional:nacos:cipher-kms-aes-256-proxy-spring-demods001.properties?group=nacos-datasource + +# nacos server address, replace it with your own +spring.cloud.nacos.config.server-addr=mse-****-p.nacos-ans.mse.aliyuncs.com + +# nacos namespace, replace it with your own +spring.cloud.nacos.config.namespace=public + +# nacos kms regionId , replace it with your own +spring.cloud.nacos.config.kms_region_id=cn-hangzhou + +# nacos proxy druid switch and data id +spring.nacos.config.proxy.druid.enabled=true +spring.nacos.config.proxy.druid.data-id=cipher-kms-aes-256-proxy-spring-demods001.properties diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml index 3e0d97aa16..3cf7efe702 100644 --- a/spring-cloud-alibaba-examples/pom.xml +++ b/spring-cloud-alibaba-examples/pom.xml @@ -25,6 +25,7 @@ sentinel-example/sentinel-spring-cloud-gateway-example nacos-example/nacos-discovery-example nacos-example/nacos-config-example + nacos-example/nacos-druid-example nacos-example/nacos-gateway-example seata-example/business-service seata-example/order-service