• 读书笔记-SpringCloud微服务实战


    • Spring Cloud Eureka:基于NetFlix Eureka做了二次封装
      在服务不多的时候可以直接将服务间调用关系写在配置文件中,当服务调用关系量大的时候就需要用到服务治理
    • 搭建服务的注册中心
    引入eureka依赖
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.3.7.RELEASE</version>
    		<relativePath/>
    	</parent>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-eureka-server</artifactId>
    		</dependency>
    
    		<!--<dependency>-->
    			<!--<groupId>org.springframework.boot</groupId>-->
    			<!--<artifactId>spring-boot-starter-actuator</artifactId>-->
    		<!--</dependency>-->
    	</dependencies>
    
    	<dependencyManagement>
    		<dependencies>
    			<dependency>
    				<groupId>org.springframework.cloud</groupId>
    				<artifactId>spring-cloud-dependencies</artifactId>
    				<version>Brixton.SR5</version>
    				<type>pom</type>
    				<scope>import</scope>
    			</dependency>
    		</dependencies>
    	</dependencyManagement>
    
    • 添加@EnableEurekaServer注解
    增加配置
    spring.application.name=eureka-server
    server.port=1111
    
    eureka.instance.hostname=localhost
    
    # 关闭保护机制
    #eureka.server.enable-self-preservation=false
    #不向注册中心注册自己
    eureka.client.register-with-eureka=false
    #检索服务开关
    eureka.client.fetch-registry=false
    eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
    
    
    • 注册服务提供者
      加上@EnableDiscoveryClient注解
      配置上服务治理中心的地址
    spring.application.name=hello-service
    eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
    

    输出的主机名和服务名:host:DESKTOP-ROB4DSR, service_id:hello-service

    • 高可用注册中心
      制作两份配置文件,分别启动
    spring.application.name=eureka-server
    server.port=1111
    eureka.instance.hostname=peer1
    eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
    
    spring.application.name=eureka-server
    server.port=1112
    eureka.instance.hostname=peer2
    eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
    
    • eureka详解
      服务提供者在启动时会发送rest请求注册到Eureka server ,同时带上自身服务和一些元数据信息,eureka server接收到请求后,将信息存到一个双层的map中,第一层key是服务名,第二层key是具体服务实例名
    • 服务同步: 注册中心之间会互相同步服务提供者的信息
    • 服务续约: 注册完服务后,服务提供者会维护一个心跳连接,防止Eureka Server 剔除服务
    • 服务消费者:发送一个rest请求到服务注册中心,获取服务清单,eureka server会生成一份只读的服务清单,并且30s更新一次
    • 服务调用: Ribbon默认采用轮询方式调用,实现客户端负载均衡
    • 服务下线:服务下线后会发送一个rest请求给eureka server
    服务注册中心
    • 失效剔除:每60s将清单中没有续约的服务下线
    • 自我保护: 心跳15分钟内失败服务比例大于85%,eurekaserver会将当前实例注册信息保护起来,让这些实例不会过期,但是在保护期间,如果实例出现问题,客户端会调用不存在的实例,所以客户端要有容错机制,如请求重试,断路器等
    • EnableDiscoveryClient源码分析
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(EnableDiscoveryClientImportSelector.class)
    public @interface EnableDiscoveryClient {
    
    }
    


    public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
            Map<String, List<String>> orderedUrls = new LinkedHashMap();
            String region = getRegion(clientConfig);
            String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
            if (availZones == null || availZones.length == 0) {
                availZones = new String[]{"default"};
            }
    
            logger.debug("The availability zone for the given region {} are {}", region, Arrays.toString(availZones));
            int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);
            String zone = availZones[myZoneOffset];
            List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
            if (serviceUrls != null) {
                orderedUrls.put(zone, serviceUrls);
            }
    
            int currentOffset = myZoneOffset == availZones.length - 1 ? 0 : myZoneOffset + 1;
    
            while(currentOffset != myZoneOffset) {
                zone = availZones[currentOffset];
                serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
                if (serviceUrls != null) {
                    orderedUrls.put(zone, serviceUrls);
                }
    
                if (currentOffset == availZones.length - 1) {
                    currentOffset = 0;
                } else {
                    ++currentOffset;
                }
            }
    
            if (orderedUrls.size() < 1) {
                throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
            } else {
                return orderedUrls;
            }
        }
    
    
    

    该函数依次加载了Region,Zone
    通过getZone函数,从配置中取了一个Zone返回,所以一个微服务只能对应一个region,如果没有配置,默认为default

    • 加载完后开始加载serviceUrls
    public List<String> getEurekaServerServiceUrls(String myZone) {
            String serviceUrls = (String)this.serviceUrl.get(myZone);
            if (serviceUrls == null || serviceUrls.isEmpty()) {
                serviceUrls = (String)this.serviceUrl.get("defaultZone");
            }
    
            if (!StringUtils.isEmpty(serviceUrls)) {
                String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
                List<String> eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length);
                String[] var5 = serviceUrlsSplit;
                int var6 = serviceUrlsSplit.length;
    
                for(int var7 = 0; var7 < var6; ++var7) {
                    String eurekaServiceUrl = var5[var7];
                    if (!this.endsWithSlash(eurekaServiceUrl)) {
                        eurekaServiceUrl = eurekaServiceUrl + "/";
                    }
    
                    eurekaServiceUrls.add(eurekaServiceUrl);
                }
    
                return eurekaServiceUrls;
            } else {
                return new ArrayList();
            }
        }
    

    • 服务注册
    private void initScheduledTasks() {
            int renewalIntervalInSecs;
            int expBackOffBound;
            if (this.clientConfig.shouldFetchRegistry()) {
                renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
                expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
                this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
            }
    
            if (this.clientConfig.shouldRegisterWithEureka()) {
                renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
                expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
                logger.info("Starting heartbeat executor: renew interval is: " + renewalIntervalInSecs);
                this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
                this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);
                this.statusChangeListener = new StatusChangeListener() {
                    public String getId() {
                        return "statusChangeListener";
                    }
    
                    public void notify(StatusChangeEvent statusChangeEvent) {
                        if (InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
                            DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
                        } else {
                            DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
                        }
    
                        DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
                    }
                };
                if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
                    this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
                }
    
                this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
            } else {
                logger.info("Not registering with Eureka server per configuration");
            }
    
        }
    

    其中的InstanceInfoReplicator会创建定时任务
    在定时任务的run方法中有register


    注册时带上InstanceInfo就是注册时客户端给服务端的元数据

    Spring Cloud Ribbon
    • 使用Ribbon的方法
    	@Bean
    	@LoadBalanced
    	RestTemplate restTemplate() {
    		return new RestTemplate();
    	}
    声明后就可以直接使用`restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class)`了
    

    • 源码分析
    public interface LoadBalancerClient {
    
    	ServiceInstance choose(String serviceId);
    
    	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    
    	URI reconstructURI(ServiceInstance instance, URI original);
    }
    
    


    每次调用有@LoadBalanced注解的请求,会触发拦截器

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
    			final ClientHttpRequestExecution execution) throws IOException {
    		final URI originalUri = request.getURI();
    		String serviceName = originalUri.getHost();
    		return this.loadBalancer.execute(serviceName,
    				new LoadBalancerRequest<ClientHttpResponse>() {
    
    					@Override
    					public ClientHttpResponse apply(final ServiceInstance instance)
    							throws Exception {
    						HttpRequest serviceRequest = new ServiceRequestWrapper(request,
    								instance);
    						return execution.execute(serviceRequest, body);
    					}
    
    				});
    	}
    

    getHost拿到服务名,调用execute调用服务

  • 相关阅读:
    逻辑回归
    异常
    内部类
    接口
    多态
    final关键字(最终的)
    不能被继承的父类成员
    对象初始化的过程
    方法重写
    abstract关键字
  • 原文地址:https://www.cnblogs.com/Baronboy/p/14109305.html
Copyright © 2020-2023  润新知