<Spring MicroService In Action> 读书笔记
Chapter 4: install Lombok to use @Getter @Setter @ToString, build 出错时再build一次就好了.
Docker-mave-plugin 在mac系统运行出错, 升级Mac版本到12.2以上就好了
Chapter 5:
- build configserver docker image first, modify bootstrap.yml line 6, delete "git" ,use local file system to store config file, maven Build skip Test
- 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文件,没有配置凭据,会出错,参考 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 {
public List 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(CollectionuserList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数; NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection 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里都能兼容
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.(XStream.java:593) ~[xstream-1.4.11.1.jar:1.4.11.1] at com.thoughtworks.xstream.XStream. (XStream.java:515) ~[xstream-1.4.11.1.jar:1.4.11.1] at com.thoughtworks.xstream.XStream. (XStream.java:484) ~[xstream-1.4.11.1.jar:1.4.11.1] at com.thoughtworks.xstream.XStream. (XStream.java:430) ~[xstream-1.4.11.1.jar:1.4.11.1] at com.thoughtworks.xstream.XStream. (XStream.java:397) ~[xstream-1.4.11.1.jar:1.4.11.1] at com.netflix.discovery.converters.XmlXStream. (XmlXStream.java:51) ~[eureka-client-1.9.13.jar:1.9.13] at com.netflix.discovery.converters.XmlXStream. (XmlXStream.java:42) ~[eureka-client-1.9.13.jar:1.9.13]
网上查一下说某个jar包和springboot的版本不兼容的原因, 然后在POM.xml里把springBoot改成最新的2.6.3, 又出现了另一个错误
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
这个则是springBoot和springCloud使用的版本不一致导致的,在POM里改成
If you have set spring.cloud.config.server.bootstrap=true, you need to use a composite configuration. 新版本如果要用bootrap.yml, 需要另外加一个依赖
org.springframework.cloud spring-cloud-starter-bootstrap
升级了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){ ResponseEntityrestExchange = 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" } ] }