springboot2.0从apollo读取配置

本文详细介绍了如何在Windows环境下搭建Apollo配置中心,并通过示例展示了如何在SpringBoot 2.0项目中集成Apollo,实现动态配置管理。文章涵盖了环境准备、Apollo安装与配置、异常排查及SpringBoot集成步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:https://github.com/nobodyiam/apollo-build-scripts

本篇文章包括两点:Windows环境下apollo单机搭建、springboot2.0从apollo读取配置

一、windows环境下aollo单机搭建

1、环境:JDK1.8,mysql 5.7.23,git-2.1.9(执行apollo的启动脚本)

2、下载apollo安装包

3、解压之后,将 apollo-build-scripts-master\sql下的两个sql文本导入mysql中

source   apolloconfigdb.sql的目录;

source   apolloportald.sql的目录;

执行成功之后可以看到,红框这两个database

4、修改demo.sh的内容

修改mysql的url,username,password

之后在git base中执行demo.sh

启动:./demo.sh  start  

停止:./demo.sh stop

5、成功启动apollo服务端和客户端之后,打开浏览器输入http://localhost:8070/

用户名:apollo 

密码:admin

接下来就可以创建自己的项目了

6、apollo成功启动之后,在本地仓库就可以看到下面三个jar,springboot2.0读取apollo使用的就是apollo-client

7、搭建过程可能出现很多问题,查看红框中两个文件夹下的日志

8、切记mysql服务要启动,8080、8070、8090这三个端口不要被占用,也不要随意改动这三个端口

我出现这个异常是因为把(config_server_url=http://localhost:8080)的端口改成8801了,导致registered-replicas和unavailable-replicas  url的端口为8801了,启动成功了,但是配置中心页面一直提示:系统出错,请重试或联系系统负责人

registered-replicashttp://localhost:8801/eureka/
unavailable-replicashttp://localhost:8801/eureka/,
com.ctrip.framework.apollo.common.exception.ServiceException: No available admin server. Maybe because of meta server down or all admin server down. Meta server address: http://localhost:8080
	at com.ctrip.framework.apollo.portal.component.RetryableRestTemplate.getAdminServices(RetryableRestTemplate.java:172)
	at com.ctrip.framework.apollo.portal.component.RetryableRestTemplate.execute(RetryableRestTemplate.java:90)
	at com.ctrip.framework.apollo.portal.component.RetryableRestTemplate.get(RetryableRestTemplate.java:56)
	at com.ctrip.framework.apollo.portal.api.AdminServiceAPI$HealthAPI.health(AdminServiceAPI.java:43)
	at com.ctrip.framework.apollo.portal.component.PortalSettings$HealthCheckTask.isUp(PortalSettings.java:127)
	at com.ctrip.framework.apollo.portal.component.PortalSettings$HealthCheckTask.run(PortalSettings.java:103)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

9、github上单机搭建文档非常给力了,出现问题,慢慢采坑!!!

二、springboot2.0项目读取apollo配置中心的配置

1、pom.xml 引入apollo-client  jar包,注意这里jar包,单机搭建apollo成功之后就在自己本地库install了,要和自己的apollo版本一致

<dependency>
			<groupId>com.ctrip.framework.apollo</groupId>
			<artifactId>apollo-client</artifactId>
			<version>0.8.0</version>
		</dependency>

2、这里创建了一个项目,app.id = wangwangdev,应用名随便起(还是专业一点吧),部门可以自己修改,开始只有样例1,样例2,在apolloportaldb.serverconfig,修改字段值,其它的管理员都可以修改!

3、ApollaDemoApplication

package com.dl.cn;

import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
/**
 * 在微服务应用启动中使用apollo配置中心获取配置信息
 * */
@EnableApolloConfig
public class ApollaDemoApplication {

	static {
		System.setProperty("-Dapp.id","wangwangdev");
		System.setProperty("-Denv","dev");
		System.setProperty("-Ddev_meta","http://127.0.0.1:8080");
	}

	public static void main(String[] args) {
		SpringApplication.run(ApollaDemoApplication.class, args);
	}

}

4、ApolloDemoController

package com.dl.cn;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Properties;
import java.util.Set;

/**
 * Created by Tiger 2018/10/10.
 */
@RestController
@RequestMapping("/apollo")
public class ApolloDemoController {

    /**
     * 从apollo获取配置信息
     * */
    @ApolloConfig
    private Config config;

    @GetMapping("/read_demo")
    public Properties apolloReadDemo(){
        /**
         * 得到当前app.id中的配置
         * */
        Set<String> set = config.getPropertyNames();
        for(String key : set){
            PropertiesUtils.properties.setProperty(key,config.getProperty(key,null));
        }
        for(String key : PropertiesUtils.properties.stringPropertyNames()){
            System.out.println(key+">>>"+PropertiesUtils.properties.getProperty(key));
        }
        return PropertiesUtils.properties;
    }
}

5、application.properties,把server.port 改成非8080,默认是8080

server.port=8000

6、PropertiesUtils

package com.dl.cn;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;

import java.util.Properties;

/**
 * Created by Tiger on 2018/10/10.
 * 读取公共apollo配置
 */
public class PropertiesUtils {
    private static final String COMMON = "nova1.NovaCommon";
    public static Properties properties = new Properties();
    static {
        Config commonConfig = ConfigService.getConfig(COMMON);
        if(commonConfig != null){
            for(String key : commonConfig.getPropertyNames()){
                properties.setProperty(key,commonConfig.getProperty(key,null));
            }
        }
    }
}

7、测试结果

127.0.0.1:8000/apollo/read_demo

{
    "commonKey2": "commonValue2",
    "commonKey1": "commonValue1",
    "email": "Tianyu@163.com",
    "userName": "Tianyu",
    "commonKey3": "commonValue3"
}
2018-10-11 10:32:57.432  INFO 36468 --- [nio-8000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-10-11 10:32:57.455  INFO 36468 --- [nio-8000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 23 ms
commonKey2>>>commonValue2
commonKey1>>>commonValue1
email>>>Tianyu@163.com
userName>>>Tianyu
commonKey3>>>commonValue3

<think>我们正在处理一个关于Java轮询调用接口的集群模式设置时间间隔的问题。根据用户的问题,我们需要实现一个集群环境下的轮询机制,并且能够调整轮询的时间间隔。同时,用户提供了一些引用,我们可以参考其中的一些技术点。 用户引用中提到了: 引用[1]:RocketMQ的延迟消息,其中设置了延迟等级(message.setDelayTimeLevel(3)),这个例子展示了如何设置延迟时间,但我们的场景是轮询,不是延迟消息。 引用[2]:一个Order类,与问题无关。 引用[3]:xxljob集群模式下调度失败问题排查,提到了集群模式下的任务调度,与我们的轮询集群模式有相似之处,但xxljob是分布式任务调度框架,我们可以借鉴其集群思想。 引用[4]:一个HTTP轮询模型的Servlet示例(doGet方法),但只给出了方法签名。 用户需求:在Java中实现轮询调用接口的集群模式,并调整轮询的时间间隔。 分析: 1. 轮询调用接口:定期调用某个HTTP接口。 2. 集群模式:多个实例同时运行,需要保证同一时间只有一个实例执行轮询,避免重复调用。这通常需要分布式协调(如使用Redis分布式锁、ZooKeeper等)。 3. 设置时间间隔:控制轮询的频率。 因此,我们需要设计一个集群环境下的定时任务,保证任务在集群中只有一个节点执行,并且可以动态调整时间间隔。 方案: 我们可以使用Spring Boot作为基础框架,结合Scheduled定时任务和分布式锁(如Redisson)来实现集群环境下的轮询调用。同时,为了能够动态调整时间间隔,我们可以将时间间隔配置在数据库或配置中心(如Nacos、Apollo)中,定时任务每次执行时读取最新的间隔,然后动态调整下一次执行的时间。 但是,标准的@Scheduled注解不支持动态调整间隔(除非使用SchedulingConfigurer)。另一种方案是使用一个单线程的ScheduledExecutorService,并在每次执行后重新调度,这样可以根据从配置中心获取的间隔动态调整。 步骤: 1. 引入依赖(Spring Boot、Redisson、配置中心客户端等)。 2. 实现一个可动态调整间隔的轮询任务,使用分布式锁确保集群中只有一个实例执行。 3. 每次执行任务后,重新获取下一次执行的时间间隔,并重新调度任务。 考虑到集群模式,我们使用分布式锁来保证只有一个节点获得执行权。同时,为了避免任务执行时间超过间隔时间,我们使用单线程调度,每次执行完后再调度下一次。 具体实现: 步骤1:创建分布式锁工具类(使用Redisson) 步骤2:创建动态轮询任务 步骤3:配置任务调度器 由于用户要求轮询调用接口,我们还需要一个HTTP客户端,例如使用RestTemplate或WebClient。 以下是一个示例代码: 1. 添加依赖(pom.xml): ```xml <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Redisson for distributed lock --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.4</version> </dependency> <!-- 配置中心(这里以Spring Cloud Config为例,也可以使用其他) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies> ``` 2. 创建分布式锁服务类: ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class DistributedLockService { @Autowired private RedissonClient redissonClient; public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { RLock lock = redissonClient.getLock(lockKey); return lock.tryLock(waitTime, leaseTime, unit); } public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } } ``` 3. 创建动态轮询任务类: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.util.Date; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @Component public class DynamicPollingTask implements SchedulingConfigurer { @Autowired private DistributedLockService lockService; @Autowired private RestTemplate restTemplate; // 初始间隔,单位毫秒,可以从配置中心动态获取 private long interval = 5000; // 默认5秒 // 锁的key private static final String LOCK_KEY = "POLLING_TASK_LOCK"; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskScheduler()); taskRegistrar.addTriggerTask( // 任务内容 () -> { try { // 尝试获取分布式锁,等待时间为0,即如果锁被其他节点持有则跳过 if (lockService.tryLock(LOCK_KEY, 0, 10, TimeUnit.SECONDS)) { try { // 执行轮询调用 String result = restTemplate.getForObject("http://example.com/api", String.class); // 处理结果... System.out.println("Polling result: " + result); } finally { lockService.unlock(LOCK_KEY); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, // 触发器,动态调整间隔 triggerContext -> { // 这里可以动态获取间隔,比如从配置中心、数据库等 // 假设我们有一个方法获取最新的间隔 long newInterval = getIntervalFromConfig(); // 更新当前间隔 interval = newInterval; // 返回下一次执行的时间间隔 return new Date(triggerContext.lastActualExecutionTime().getTime() + interval); } ); } private Executor taskScheduler() { return Executors.newScheduledThreadPool(1); } // 模拟从配置中心获取间隔 private long getIntervalFromConfig() { // 这里可以是从配置中心、数据库等获取 // 返回毫秒 return interval; // 实际应用中,这里应该返回从配置中心获取的值 } // 提供外部更新间隔的方法,比如通过API调用更新 public void setInterval(long newInterval) { this.interval = newInterval; } } ``` 说明: - 我们使用SchedulingConfigurer接口来实现动态定时任务。 - 在触发器(trigger)中,每次执行完任务后,我们都会重新获取时间间隔(这里通过getIntervalFromConfig方法),然后设置下一次执行的时间为上次执行时间加上新的间隔。 - 使用分布式锁确保集群中只有一个节点执行任务。锁的等待时间为0,表示如果锁被其他节点持有则当前节点跳过执行。锁的持有时间设置为10秒,确保任务在10秒内执行完,防止死锁。 - 任务调度器使用单线程池(newScheduledThreadPool(1)),这样每次只有一个任务在运行,避免任务重叠。 4. 配置RestTemplate(可选): ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } } ``` 5. 启动类: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 动态调整间隔: 我们可以通过调用DynamicPollingTask的setInterval方法来动态调整间隔,例如提供一个API接口来更新间隔。 注意:由于我们每次执行任务后都会重新获取间隔,所以即使间隔改变,也会在下次任务生效。 另外,我们也可以将间隔存储在数据库中,每次getIntervalFromConfig方法都去查询数据库,或者使用配置中心(如Nacos)的监听机制来更新interval变量。 优化:为了避免每次执行都去查询数据库,我们可以使用配置中心的监听机制,当配置改变时,自动调用setInterval方法更新。 例如,使用Nacos配置中心: ```java import com.alibaba.nacos.api.config.annotation.NacosValue; import org.springframework.stereotype.Component; @Component public class IntervalConfig { @NacosValue(value = "${polling.interval:5000}", autoRefreshed = true) private long interval; public long getInterval() { return interval; } } ``` 然后在DynamicPollingTask中注入IntervalConfig,getIntervalFromConfig方法返回intervalConfig.getInterval()。 这样,当配置中心中的polling.interval值改变时,interval字段会自动更新,从而动态调整轮询间隔。 总结: 我们通过Spring动态定时任务、分布式锁和配置中心,实现了集群模式下轮询调用接口,并能动态调整轮询间隔。 注意:分布式锁的持有时间要设置合理,确保任务能在锁超时前完成,否则可能导致多个节点同时执行。如果任务执行时间较长,可以适当增加锁的超时时间,或者在任务执行过程中定时续期(watchdog机制,Redisson已经支持)。 相关问题: 1. 如何避免轮询任务在集群中重复执行? 2. 如何实现动态调整定时任务的执行间隔? 3. 在Spring Boot中如何整合Redisson实现分布式锁? 4. 如何通过配置中心(如Nacos)动态更新Spring Boot应用的配置?</think>以下是基于Java实现轮询调用接口的集群模式并设置时间间隔的完整方案,结合分布式协调、负载均衡和动态配置管理技术: ### 一、集群模式轮询架构设计 ```mermaid graph TD A[负载均衡器] --> B[实例1] A --> C[实例2] A --> D[实例3] B --> E[分布式锁服务] C --> E D --> E E --> F[配置中心] F -->|间隔配置| G[MySQL/Redis] H[外部接口] --> B H --> C H --> D ``` ### 二、核心实现代码 #### 1. 分布式锁管理(使用Redisson) ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class PollingScheduler { private final RedissonClient redisson; private final String lockKey = "polling-lock"; @Scheduled(fixedDelayString = "${polling.interval}") public void clusterPolling() { RLock lock = redisson.getLock(lockKey); try { // 尝试获取锁,等待100ms,锁持有30s if (lock.tryLock(100, 30000, TimeUnit.MILLISECONDS)) { executePollingTask(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } private void executePollingTask() { // 实际接口调用逻辑 restTemplate.getForObject("http://api-service/data", String.class); } } ``` #### 2. 动态时间间隔配置(使用Spring Cloud Config) ```yaml # application.yml polling: interval: 5000 # 默认5秒 ``` ```java @RefreshScope public class PollingConfig { @Value("${polling.interval:5000}") private long interval; // 动态更新方法 public void updateInterval(long newInterval) { this.interval = newInterval; } } ``` #### 3. 健康检查与故障转移 ```java @Scheduled(fixedRate = 10000) public void healthCheck() { if (!healthClient.checkServiceHealth()) { // 触发故障转移 failoverHandler.transferWorkload(); } } ``` ### 三、关键配置说明 | 组件 | 配置项 | 默认值 | 说明 | |------|--------|--------|------| | 分布式锁 | lock.tryWaitTime | 100ms | 获取锁最大等待时间 | | 分布式锁 | lock.leaseTime | 30s | 锁自动释放时间 | | 轮询任务 | polling.interval | 5s | 轮询间隔时间 | | 健康检查 | healthcheck.interval | 10s | 节点健康检查频率 | ### 四、时间间隔动态调整方案 #### 1. 通过配置中心实时更新 ```bash # 使用curl更新配置 curl -X POST http://config-server/actuator/refresh \ -d '{"polling.interval": "3000"}' \ -H "Content-Type: application/json" ``` #### 2. 基于负载的自适应调整算法 ```java public void adjustIntervalDynamically() { double systemLoad = getSystemLoadAverage(); long currentInterval = config.getInterval(); if (systemLoad > 0.7) { // 负载过高时增加间隔 config.updateInterval(currentInterval * 1.5); } else if (systemLoad < 0.3) { // 负载过低时减少间隔 config.updateInterval(Math.max(1000, currentInterval * 0.8)); } } ``` ### 五、集群部署注意事项 1. **锁服务选择**:推荐使用Redis Cluster或ZooKeeper 2. **心跳机制**:实现节点间心跳检测[^3] 3. **日志追踪**:使用traceId关联跨节点日志 4. **退避策略**:接口调用失败时采用指数退避重试 ```java RetryTemplate template = new RetryTemplate(); ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy(); backOff.setInitialInterval(1000); backOff.setMultiplier(2.0); template.setBackOffPolicy(backOff); ``` ### 六、性能优化建议 1. **连接池配置**: ```yaml httpclient: max-total: 100 default-max-per-route: 20 connection-timeout: 3000 socket-timeout: 5000 ``` 2. **结果缓存**:对非实时数据使用Guava Cache 3. **批处理**:合并多个请求减少调用次数[^1] 4. **异步轮询**:使用@Async实现非阻塞调用 ### 七、监控指标设计 | 指标名称 | 监控方式 | 告警阈值 | |----------|----------|----------| | 轮询成功率 | Prometheus+Grafana | <95% | | 平均响应时间 | Micrometer | >1s | | 集群负载均衡度 | 自定义指标 | 节点差异>30% | | 锁竞争次数 | Redis监控 | >10次/秒 | --- ### 相关问题 1. 如何实现分布式锁在Redis集群故障时的自动转移? 2. 轮询间隔动态调整的最优算法是什么? 3. 如何在Kubernetes环境中部署此轮询集群? 4. 轮询任务如何实现优雅停机? 5. 如何设计跨地域的轮询集群容灾方案?
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燕少༒江湖

给我一份鼓励!谢谢!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值