新建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>
1 2 3 | <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]
- 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) - build licensing-service docker image
- 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方式连接
1 | 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注释掉
1 | The bean 'localeResolver' , defined in com.optimagrowth.license.LicenseServiceApplication, could not be registered. <br>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 轮询调度)
1 2 3 4 5 6 7 8 9 10 11 | 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); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?