FeinClient服务实例缓存实时刷新

Spring Cloud OpenFeign默认会将从注册中心Consul获取的服务实例信息在Caffine缓存中缓存35秒,这会出现服务下线后,下线服务仍然被调用的问题,导致上游服务在缓存有效期内不可用。本文讲解如何通过Consul Watch机制来实时监听相关服务的更新,以实现服务的平滑上下线。

RoundRobinLoadBalancer使用CachingServiceInstanceListSupplierle加载服务实例信息

	public CachingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, CacheManager cacheManager) {
		super(delegate);
		this.serviceInstances = CacheFlux.lookup(key -> {
			// TODO: configurable cache name
            //首先从缓存中获取
			Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
			if (cache == null) {
				if (log.isErrorEnabled()) {
					log.error("Unable to find cache: " + SERVICE_INSTANCE_CACHE_NAME);
				}
				return Mono.empty();
			}
			List<ServiceInstance> list = cache.get(key, List.class);
			if (list == null || list.isEmpty()) {
				return Mono.empty();
			}
			return Flux.just(list).materialize().collectList();
		}, delegate.getServiceId())
        //未命中缓存时,使用DiscoveryClient从Consul查询
        .onCacheMissResume(delegate.get().take(1))
				.andWriteWith((key, signals) -> Flux.fromIterable(signals).dematerialize().doOnNext(instances -> {
					Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
					if (cache == null) {
						if (log.isErrorEnabled()) {
							log.error("Unable to find cache for writing: " + SERVICE_INSTANCE_CACHE_NAME);
						}
					}
					else {
                        //写入缓存
						cache.put(key, instances);
					}
				}).then());
	}

参考Spring Cloud Config的源码,首先定义Consul心跳监视器类 HeartbeatMonitor,就是Copy源码...

/**
 * Helper class for listeners to the {@link HeartbeatEvent}, providing a convenient way to
 * determine if there has been a change in state.
 *
 * @author Dave Syer
 */
public class HeartbeatMonitor {

	private AtomicReference<Object> latestHeartbeat = new AtomicReference<>();

	/**
	 * @param value The latest heartbeat.
	 * @return True if the state changed.
	 */
	public boolean update(Object value) {
		Object last = this.latestHeartbeat.get();
		if (value != null && !value.equals(last)) {
			return this.latestHeartbeat.compareAndSet(last, value);
		}
		return false;
	}

}
源码中ConsulCatalogWatch类会定时监听(默认1秒)Consul的服务状态变化,仿照Spring Cloud Config,监听Consul心跳事件,在Consul中注册的服务实例信息变更时,刷新Caffine缓存。
//参考 Spring Cloud Config - ConfigServerInstanceMonitor类
@Slf4j
@Component
public class ServiceInstanceMonitor implements ApplicationListener<HeartbeatEvent>,
        ApplicationContextAware, InitializingBean {

    private final HeartbeatMonitor monitor = new HeartbeatMonitor();

    private ApplicationContext applicationContext;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private Environment environment;

    private List<String> feignServiceIdList = new ArrayList<>();

    @Override
    public void onApplicationEvent(HeartbeatEvent event) {
        if (this.monitor.update(event.getValue())) {
            refresh();
        }
    }

    public void refresh() {
        ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = applicationContext
                .getBeanProvider(LoadBalancerCacheManager.class);
        if (cacheManagerProvider.getIfAvailable() != null) {
            //Feign Service Instances Cache刷新
            LoadBalancerCacheManager cacheManager = cacheManagerProvider.getIfAvailable();
            Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
            for (String serviceId : feignServiceIdList) {
                List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceId);
                if (CollectionUtils.isEmpty(serviceInstances)) {
                    log.warn("refresh | {} can not discovery.", serviceId);
                    continue;
                }
                cache.put(serviceId, serviceInstances);
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, Object> feignServiceMap =
                applicationContext.getBeansWithAnnotation(FeignClient.class);
        for (Object feignBean : feignServiceMap.values()) {
            FeignClient annotation =
                    AnnotationUtils.findAnnotation(feignBean.getClass(), FeignClient.class);
            String url = (String) AnnotationUtils.getValue(annotation, "url");
            url = environment.resolvePlaceholders(url);
            //@FeignClient的url属性值不为空时,说明是指定服务请求URL的,不需要load-balancer
            if (StringUtils.isBlank(url)) {
                String serviceId = (String) AnnotationUtils.getValue(annotation);
                serviceId = environment.resolvePlaceholders(serviceId);
                if(StringUtils.isBlank(serviceId)){
                    throw new IllegalArgumentException("feign service-name must be not empty");
                }
                feignServiceIdList.add(serviceId);
            }
        }
    }

}

加入上面代码后,在服务上下线时使相应的服务实例缓存刷新即可。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值