• <Spring MicroService In Action> 读书笔记


    新建SpringBoot项目在Start.spring.IO 选择版本,packageName,Dependency,生成脚手架,在POM.xml增加一个plugin, Properties增加一个项, 当然项目中要增加Dockerfile

    <!-- This plugin is used to create a docker image and publish the image to docker hub-->
                <plugin>
                    <groupId>com.spotify</groupId>
                    <artifactId>dockerfile-maven-plugin</artifactId>
                    <version>1.4.13</version>
                    <configuration>
                        <repository>${docker.image.prefix}/${project.artifactId}</repository>
                        <tag>${project.version}</tag>
                        <buildArgs>
                            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                        </buildArgs>
                    </configuration>
                    <executions>
                        <execution>
                            <id>default</id>
                            <phase>install</phase>
                            <goals>
                                <goal>build</goal>
                                <goal>push</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
    <properties>		
        <docker.image.prefix>ostock</docker.image.prefix>
    </properties>
    

    书中的DockerFile 其实可以简化

    #stage 1
    #Start with a base image containing Java runtime
    FROM openjdk:11-slim as build
    
    # The application's jar file
    ARG JAR_FILE
    
    # Add the application's jar to the container
    COPY ${JAR_FILE} app.jar
    
    
    #Add volume pointing to /tmp
    VOLUME /tmp
    
    #execute the application
    ENTRYPOINT ["java","-jar","/app.jar"]

      

    Chapter 4: install Lombok to use @Getter @Setter @ToString, build 出错时再build一次就好了.

           Docker-mave-plugin 在mac系统运行出错, 升级Mac版本到12.2以上就好了

    Chapter 5:

        dockerhub里面有一个springcloud/configserver的镜像,大小只有200m, 我自己打包出来的快500m了. 但是它那个运行起来, 我进去docker的CLI发现没有JAVA命令的.

    docker ps 查看入口点是 /cnb/lifecycle/laun

      configSever的作用是把众多的微服务的config,集中在一个地方管理  [URL]/[微服务名字]/[default/dev/prod]

    1.  build configserver docker image first, modify bootstrap.yml  line 6, delete "git" ,use local file system to store config file,  maven Build skip Test
      (其实可以把bootstrap.yml改名成application.yml, 不然ConfigurationServerApplicationTests 运行测试的时候就会找不到Git地址,所以原书上的代码是把Test 用@disalbed)
    2.  build licensing-service docker image
    3.  goto docker folder. use docker-compose up command,       I want find  a docker image size less then openjdk:11-slim(429M), but not found

      configserver 的性能监测,健康检查在 localhost:8071/actuator   native文件存储在 search-locations: classpath:/config 

      licensing-service 的dev环境的配置文件在localhost:8071/licensing-service/dev  对应的/src/main/resource/config目录下对应的[微服务的名字]-[Dev/prod].properties文件, 但是放在这个位置, 每次修改都要重新打包,太麻烦了. 可以改成 /usr/src/app/config/, 然后在docker-compose.yml里的增加volume映射

    spring:
      application:
        name: config-server
      profiles:
        active:
        - native
      cloud:
        config:
          server:
            native:
              search-locations: /usr/src/app/config/   #classpath:/config
     configserver:
        image: ostock/configserver:0.0.1-SNAPSHOT
        ports:
           - "8071:8071"
        environment:
          ENCRYPT_KEY: "fje83Ki8403Iod87dne7Yjsl3THueh48jfuO9j4U2hf64Lo"
        volumes:
            - ../configserver/src/main/resources/config:/usr/src/app/config/
        networks:
          backend:
            aliases:
              - "configserver"

     如果要改用Git存储, (国内可以改用Gitee,速度快)示例上的yml文件,没有配置凭据,会出错,参考git配置SSH  spring cloud config server使用ssh方式连接

    https://XXXXXXX/config.git: Authentication is required but no CredentialsProvider has been registered 

    在 spring cloud config server 中使用 ssh 连接 git 仓库_HermitSun  但我测试时发现只有输入账号密码在yml里才能连接私有仓库,用SSH的方法不行,还没找到原因 

    dockerfile-maven-plugin 打包时,需要在线,不然会报错(就算本地已经有openjdk:11的image)

    configserver 变成单点了??? 假如configserver不能启动,其他微服务也不能启动了. 有data字段,假如连不上configserver时用???

    Spring Cloud Config 实现配置中心,看这一篇就够了 

    在licensing-service 看到这段代码,一开始我很震惊, 接口只声明了方法,没有实现都可以查询到....

    @Repository
    public interface LicenseRepository extends CrudRepository<License,String>  {
        public List<License> findByOrganizationId(String organizationId);
        public License findByOrganizationIdAndLicenseId(String organizationId,String licenseId);
        
    }

    后来才了解到这个是JPA的约定 继承Repository的接口在使用的时候,通过@Autowired会自动创建接口的实现类,不需要怎么去实现这个接口,这也是jpa最方便的地方

    1.findBy  findAllBy的区别
    它们之间没有区别,它们将执行完全相同的查询,当从方法名称派生查询时,Spring Data会忽略All部分。唯一重要的一点是By关键字,其后面的任何内容都被视为字段名称
    
    如 findXXXXXXXXXXXXXByName 实际上==》 findByName
    
    2、JPA中支持的关键词
    And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
    Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
    Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
    LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
    GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
    IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull();
    IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
    NotNull --- 与 IsNotNull 等价;
    Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user);但是有一点需要注意的是,%需要我们自己来写
    NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
    OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
    Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user);
    In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
    NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,
    方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

     ============================

    Chapter 6:  客户端LoadBalancer, 是比单纯使用服务发现更坚固的方法. 当服务的客户端要使用服务时,先从本地缓存去找微服务(采用Round Robin 轮询调度算法. 就是以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器),找不到再去服务发现里找. 定时从服务发现里更新本地缓存(比如10分钟), 当访问某一个服务时出现Fail,则马上更新本地缓存. (这种客户端的负载均衡是强侵入,必须客户端用JAVA,Spring)

     居然代码跑不起来. 四个项目configServer, EurekaServer,licensing-service,organization-service.   其他三个项目都依赖configServer, 3个的bootrap.yml 都指定cloud config的地址  http://configserver:8071, 这个地址只能在docker里跑,但是我们还没有docker image文件, 首先在Eclipse里能跑起来才能打包成Image, 当eclipse运行configServer它默认是在http://localhost:8071, 我们又不想把bootrap.yml里面的内容改来改去,

    有一个小办法. 可以在windows的host文件里把configServer 定义成127.0.0.1, 这样在宿主机和container里都能兼容

    Mac 则是打开Finder,之后按下Shift+Command+G组合键,最后输入/etc/hosts回车即可找到hosts文件。

    licensing-service,organization-service都依赖pgsql 和EurekaServer, pgsql 可以单独docker run起来, 但EurekaServer启动却出错了.

    022-02-06 00:11:19.223  INFO 13184 --- [           main] c.s.j.s.i.a.WebApplicationImpl           : Initiating Jersey application, version 'Jersey: 1.19.1 03/11/2016 02:08 PM'
    2022-02-06 00:11:19.305  INFO 13184 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
    2022-02-06 00:11:19.306  INFO 13184 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
    2022-02-06 00:11:19.484 ERROR 13184 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Exception starting filter [servletContainer]
    
    java.lang.ExceptionInInitializerError: null
        at com.thoughtworks.xstream.XStream.setupConverters(XStream.java:990) ~[xstream-1.4.11.1.jar:1.4.11.1]
        at com.thoughtworks.xstream.XStream.<init>(XStream.java:593) ~[xstream-1.4.11.1.jar:1.4.11.1]
        at com.thoughtworks.xstream.XStream.<init>(XStream.java:515) ~[xstream-1.4.11.1.jar:1.4.11.1]
        at com.thoughtworks.xstream.XStream.<init>(XStream.java:484) ~[xstream-1.4.11.1.jar:1.4.11.1]
        at com.thoughtworks.xstream.XStream.<init>(XStream.java:430) ~[xstream-1.4.11.1.jar:1.4.11.1]
        at com.thoughtworks.xstream.XStream.<init>(XStream.java:397) ~[xstream-1.4.11.1.jar:1.4.11.1]
        at com.netflix.discovery.converters.XmlXStream.<init>(XmlXStream.java:51) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.converters.XmlXStream.<clinit>(XmlXStream.java:42) ~[eureka-client-1.9.13.jar:1.9.13]

     网上查一下说某个jar包和springboot的版本不兼容的原因, xstream 改成新版, 又会有另一个错误 

            <dependency>
                <groupId>com.thoughtworks.xstream</groupId>
                <artifactId>xstream</artifactId>
                <version>1.4.19</version>
            </dependency>

    Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}  但最后能在8080端口启动

    最后决定在POM.xml里把springBoot改成最新的2.6.3, 又出现了另一个错误

    Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata

    这个则是springBoot和springCloud使用的版本不一致导致的,在POM里改成<spring-cloud.version>2021.0.0</spring-cloud.version>


    出现这个错误, No spring.config.import property has been defined, 要加一个依赖

     If you have set spring.cloud.config.server.bootstrap=true, you need to use a composite configuration. 新版本如果要用bootrap.yml, 需要另外加一个依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>

     升级了2.6.3后 licensing-service又有另一个错误, 把LicenseServiceApplication里面的localeResolver 的Bean注释掉

    The bean 'localeResolver', defined in com.optimagrowth.license.LicenseServiceApplication, could not be registered. 
    A bean with that name has already been defined in class path resource

    Every service registered with Eureka will have two components associated with it: the application ID and the instance ID.

    Eureka注册的是bootrap.yml里面的spring.application.name作为程序ID, 实例ID则是一个随机数字  

    在每个微服务的对应的configserver里的config文件里指定Eureka的地址端口

    eureka.instance.preferIpAddress = true
    eureka.client.registerWithEureka = true
    eureka.client.fetchRegistry = true
    eureka.client.serviceUrl.defaultZone = http://eurekaserver:8070/eureka/

    licensing-service,organization-serivce, 如何启动多个实例在不同的端口呢? 在本机Docker-compose.yml里可以这样写,K8s就不用这样

      organizationservice:
        image: ostock/organization-service:0.0.1-SNAPSHOT
        environment:
          PROFILE: "dev"
          CONFIGSERVER_URI: "http://configserver:8071"
          CONFIGSERVER_PORT:   "8071"
          DATABASESERVER_PORT: "5432"
          ENCRYPT_KEY:       "IMSYMMETRIC"
        depends_on:
          database:
            condition: service_healthy
          configserver:
            condition: service_started
        ports:
          - "8081:8081"
        networks:
          - backend
      organizationservice1:
        image: ostock/organization-service:0.0.1-SNAPSHOT
        environment:
          PROFILE: "dev"
          CONFIGSERVER_URI: "http://configserver:8071"
          CONFIGSERVER_PORT:   "8071"
          DATABASESERVER_PORT: "5432"
          ENCRYPT_KEY:       "IMSYMMETRIC"
        depends_on:
          database:
            condition: service_healthy
          configserver:
            condition: service_started
        ports:
          - "8082:8081"
        networks:
          - backend

     

     但又引申出另一个问题, 我访问时我怎么知道是访问哪一个instance, 性能监控如何

     三种客户端的区别:  DiscoveryClient 有注册服务的功能,但它的写法没有使用客户端的负载均衡,而且需要程序员自己选择用哪一个实例.而且要写很多代码, 差评. 不要用它.

    FeignClient基于REST的服务调用上提供更高级别的抽象, FeignClient简化了请求的编写,只要写一个接口的方法,不需要写实现. 且通过动态负载进行选择要使用哪个服务进行消费,而这一切都由Spring动态配置实现,我们不用关心这些,只管使用方法即可。RestTemplate还需要写上服务器IP这些信息. 

    Using a Load Balancer–backed RestTemplate to call a service

    (其实也不用,只要在LicenseServiceApplication里用@LoadBalanced注解RestTemplate ,RestTemplate 就能用http://organization-service 这样的写法,自动分配Round Robin 轮询调度)

    public class LicenseServiceApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(LicenseServiceApplication.class, args);
    	}
    	
    	@LoadBalanced
    	@Bean
    	public RestTemplate getRestTemplate(){
    		return new RestTemplate();
    	}
    

      

    public class OrganizationRestTemplateClient {
        @Autowired
        RestTemplate restTemplate;
    
        public Organization getOrganization(String organizationId){
            ResponseEntity<Organization> restExchange =
                    restTemplate.exchange(
                            "http://organization-service/v1/organization/{organizationId}",
                            HttpMethod.GET,
                            null, Organization.class, organizationId);
    
            return restExchange.getBody();
        }
    }
    @FeignClient("organization-service")
    public interface OrganizationFeignClient {
        @RequestMapping(
                method= RequestMethod.GET,
                value="/v1/organization/{organizationId}",
                consumes="application/json")
        Organization getOrganization(@PathVariable("organizationId") String organizationId);
    }

     如何Eureka 服务Down了, 访问licensing-service时,它会从Eureka来找Organization service就会出现这个错误

    {
    "metadata": {
    "status": "NOT_ACCEPTABLE"
    },
    "errors": [
    {
    "message": "No instances available for organization-service",
    "code": null,
    "detail": "No instances available for organization-service"
    }
    ]
    }

    Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。

    Eureka Server的高可用实际上就是将自己做为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
    Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。可以采用两两注册的方式实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现。

    Chapter 7:

    把Licensing-service 升级到2.6.3 spring-boot, 然后把 <resilience4j.version>1.7.0</resilience4j.version> 改成1.7.0, 默认的设置,每次都会fallback, 先把注解去掉,一个个再加上看看是哪一个问题

    //    @CircuitBreaker(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
    //    @RateLimiter(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
    //    @Retry(name = "retryLicenseService", fallbackMethod = "buildFallbackLicenseList")
    //    @Bulkhead(name = "bulkheadLicenseService", type= Type.THREADPOOL, fallbackMethod = "buildFallbackLicenseList")
        public List<License> getLicensesByOrganization(String organizationId) throws TimeoutException {
            logger.debug("getLicensesByOrganization Correlation id: {}",
                    UserContextHolder.getContext().getCorrelationId());
            randomlyRunLong();
            return licenseRepository.findByOrganizationId(organizationId);
        }
  • 相关阅读:
    iOS 9 ContactsFramework
    performSelector延时调用导致的内存泄露
    ARC 下内存泄露的那些点
    CoreText.framework --- 基本用法
    edgesForExtendedLayout
    CocoaPods使用详细说明
    IOS开发笔记(11)IOS开发之NSLog使用技巧
    网页中调用JS与JS注入
    Block就像delegate的简化版
    转:UINavigationBar--修改导航栏返回按钮的文字
  • 原文地址:https://www.cnblogs.com/zitjubiz/p/spring_Microservice_In_Action_Note.html
Copyright © 2020-2023  润新知