- 微服务之道:度量驱动开发
- 范亚敏 傅健
- 4312字
- 2023-05-17 17:31:59
1.5 土豆微服务案例快速上手
Todo List是使用得最多的一种时间管理方法,即把要做的事情写下来,标注上优先级、计划开始时间、估计完成时间、地点,如表1-3所示。
表1-3 Todo List
①优先级可分为四级:A—重要并紧急,B—重要不紧急,C—紧急不重要,D—不紧急不重要
“拖延症”患者最大的问题在于Todo List越来越长,想做的事情越来越多,可是没有几件大事是能够坚持完成的,小事也丢三落四,延误了很多。很多事情计划了,可并没有及时完成,有的压根儿没能开始,就已经超过了截止时间,久而久之,这些事的进展仍然只是停留在计划阶段。
Todo List的关键在于提醒我们专注和坚持。在此我们就来构建一个“土豆”(Todo的中文谐音)服务来维护我们的待办事项,提醒我们坚持完成任务。接下来就从如何构建和部署两个方面介绍,让大家能快速上手微服务开发。
1.5.1 土豆微服务构建计划
按照以上需求,可以大体设计一下土豆微服务的架构,绘制用例图,如图1-3所示。
图1-3 “土豆”微服务用例图
对于待办事项,有6个基本用例:
1)Create Potato(创建待办事项):其扩展用例包括创建提醒器Create Reminder(提醒用户及时开始和完成任务)。
2)Retrieve Potato(获取待办事项):根据事项的唯一标识符来获取其详细信息。
3)Update Potato(更新待办事项):其扩展用例有修改提醒器Update Reminder(不用提醒开始,只要提醒按时完成任务)。
4)Start Potato(启动待办事项)。其扩展用例也是修改提醒器Update Reminder(不用提醒开始,只要提醒按时完成任务)。
5)Stop Potato(停止待办事项):停止正在进行的事项,或者是完成事项,或者是中止事项。
6)Delete Potato(删除待办事项)。其扩展用例有删除提醒器Remove Reminder。
在初始版本中,可以先创建如下3个微服务。
❑ Potato-Web:提供一个前端服务,主要功能是渲染前端页面,并为前端页面的Ajax调用提供API。
❑ Potato-Service:核心服务,主要功能是对待办事项(Potato)进行增删改查,以及开始和结束待办事项。
❑ Potato-Reminder:提醒功能,主要用来在待办事项(Potato)预定开始时间及截止时间之前发送提醒邮件、即时消息或播放音乐。
3个微服之间的关系如图1-4所示。
图1-4 “土豆”系列微服务之间的关系
微服务可以用任何一种语言实现,笔者比较喜欢用Java和Python。这里用Java实现,后端基于Spring Boot框架,前端使用Vue框架。
下面我们一一介绍这3个微服务。先从核心的Potato-Service说起。
1.5.2 微服务构建一:土豆管理微服务
土豆管理微服务Potato-Service的内部结构采用标准的3层架构,如图1-5所示。
图1-5 土豆管理微服务内部结构
❑ Controller模块:用户界面层,也就是API交互层。
❑ Service模块:逻辑应用层。
❑ Repository模块:数据访问层。
1.具体构建与结构
我们采用Spring Boot来实现这个微服务。Spring Boot有一个用来快速生成应用骨架的页面https://start.spring.io,选中所需的依赖库,会生成一个压缩文件,解压缩后就生成一个简单的Spring Boot项目。也可以用命令行工具来生成。例如,在Macbook上可以用brew来安装springboot命令行工具。
brew tap pivotal/tap brew install springboot
在其他Linux系统上可通过sdkman来安装,在Windows上建议通过vagrant安装一个ubuntu虚拟机。
构建工具可以选择Gradle或传统的Maven,也可以使用https://start.spring.io,从中选取所需要的子模块,生成一个项目骨架。假设项目命名为potato(“土豆”),保存后即为potato.zip。这里用Spring Cli命令工具来生成项目骨架。
spring init --java-version=1.8--dependencies=web, actuator, cloud-eureka, devtools -packaging=jar --groupId=com.github.walterfan.potato --artifactId=server unzip server.zip -d potato-server
使用上面生成的“骨架”文件完成业务功能的编写。最终生成的代码结构如图1-6所示。
图1-6 土豆管理微服务代码结构
2.主要 代码类
构建标准的Java微服务,首先要创建一个POM文件,这里不列举其内容,请参考前言中提到的网站所附实例中的源码。其核心要点是设置spring-boot-starter-parent,并添加相关依赖。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <! -- lookup parent from repository --> </parent> <dependency> <! -- 省略大部分内容 --> <dependencies> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
主要的数据传输对象如图1-7所示。
图1-7 土豆管理微服务中数据传输对象“土豆”的定义
主要的启动类PotatoApplication用于启动这个微服务,代码如下:
package com.github.walterfan.potato.server; //省略import语句 @EnableEurekaClient @SpringBootApplication public class PotatoApplication { public static void main(String[] args) { SpringApplication.run(PotatoApplication.class, args); } }
控制类PotatoController用于指定API的URL映射,并调用下层的服务:
package com.github.walterfan.potato.server; //省略import语句 @RestController @RequestMapping("/potato/api/v1") @Slf4j public class PotatoController { @Value("${spring.application.name}") private String serviceName; @Value("${server.port}") private Integer serverPort; @Autowired private PotatoService potatoService; @RequestMapping(value = "/potatoes", method = RequestMethod.POST) @ApiCallMetricAnnotation(name = "CreatePotato") public PotatoDTO create(@RequestBody PotatoDTO potatoRequest) { log.info("create {}", potatoRequest); return potatoService.create(potatoRequest); } @LogDetail @RequestMapping(value = "/potatoes/{id}/start", method = RequestMethod.POST) @ApiCallMetricAnnotation(name = "StartPotato") public void start(@PathVariable UUID id) { potatoService.startPotato(id); } @LogDetail @RequestMapping(value = "/potatoes/{id}/stop", method = RequestMethod.POST) @ApiCallMetricAnnotation(name = "StopPotato") public void stop(@PathVariable UUID id) { potatoService.stopPotato(id); } @LogDetail @RequestMapping(value = "/potatoes/{id}", method = RequestMethod.DELETE) @ApiCallMetricAnnotation(name = "DeletePotato") public void delete(@PathVariable UUID id) { potatoService.delete(id); } //省略其他管理操作,如删除、查询等
服务类PotatoService用来处理核心的业务逻辑,调用下层的数据存取方法来访问数据库:
package com.github.walterfan.potato.server; //省略import语句 @Service @Slf4j public class PotatoServiceImpl implements PotatoService { //省略属性代码 @Override public PotatoDTO create(PotatoDTO potatoRequest) { PotatoEntity potato = potatoDto2Entity(potatoRequest, null); PotatoEntity savedPotato = potatoRepository.save(potato); scheduleRemindEmails(potatoRequest); return this.potatoEntity2Dto(savedPotato); } private void scheduleRemindEmails(PotatoDTO potatoRequest) { String emailContent = potatoRequest.getDescription(); //schedule remind RemindEmailRequest remindEmailRequest = RemindEmailRequest.builder() .email(this.remindEmail) .subject("To start:" + potatoRequest.getName()) .body(emailContent) .dateTime(potatoRequest.getScheduleTime()) .build(); ResponseEntity<RemindEmailResponse> respEntity1 = potatoSchedulerClient. scheduleRemindEmail(remindEmailRequest); log.info("respEntity1:{}", respEntity1.getStatusCode()); RemindEmailRequest remindEmailRequest2 = RemindEmailRequest.builder() .email(this.remindEmail) .subject("To finish:" + potatoRequest.getName()) .body(emailContent) .dateTime(potatoRequest.getDeadline()) .build(); ResponseEntity<RemindEmailResponse> respEntity2 = potatoSchedulerClient. scheduleRemindEmail(remindEmailRequest2); log.info("respEntity2:{}", respEntity2.getStatusCode()); } //省略其他维护“土豆”的操作
数据仓库类PotatoRespository用来读写数据库:
package com.github.walterfan.potato.server.repository; //省略import语句 @Repository public interface PotatoRepository extends PagingAndSortingRepository<PotatoEntity, UUID>, JpaSpecificationExecutor<PotatoEntity> { Page<PotatoEntity> findByUserId(UUID userId, Pageable pageable); Page<PotatoEntity> findByUserId(UUID userId, Specification<PotatoEntity>spec, Pageable pageable); PotatoEntity findByUserIdAndName(UUID userId, String name); }
3.配置文件
配置文件application.properties中指定了profiles为dev,如下所示:
spring.profiles.active=dev # ============================================================== # app # ============================================================== potato.remind.email=fanyamin@hotmail.com potato.guest.userId=53a3093e-6436-4663-9125-ac93d2af91f9
配置文件application-dev.properties用来设置所需的若干属性:
# =============================== # = General # =============================== app.id=potato debug=false server.port=9003 spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet. error.ErrorMvcAutoConfiguration spring.application.name=potato-service spring.application.version=1.0 spring.application.component=potato-app spring.application.env=production spring.messages.encoding=UTF-8 server.tomcat.uri-encoding=UTF-8 logging.level.root=INFO logging.level.org.hibernate=DEBUG logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG # =============================== # = DATA SOURCE # =============================== spring.datasource.url=jdbc:sqlite:/var/lib/sqlite/potato.db spring.datasource.username=walter spring.datasource.password=pass1234 spring.datasource.testWhileIdle=true spring.datasource.validationQuery=SELECT 1 spring.datasource.driver-class-name=org.sqlite.JDBC spring.datasource.sql-script-encoding=UTF-8 # =============================== # = JPA / HIBERNATE # =============================== spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy spring.jpa.database-platform=org.hibernate.dialect.SQLiteDialect # ============================================================== # = Initialize the database using data.sql script # ============================================================== spring.datasource.initialization-mode=always # =============================== # = Thymeleaf configurations # =============================== #spring.thymeleaf.mode=LEGACYHTML5 spring.thymeleaf.cache=false # ============================================================== # InfluxDB # ============================================================== spring.influxdb.url=${INFLUXDB_URL} spring.influxdb.username=admin spring.influxdb.password=admin spring.influxdb.database=potato # ============================================================== # = Actuator # ============================================================== spring.jmx.default-domain=potato management_endpoints_jmx.exposure.include=* management.endpoint.shutdown.enabled=true management.endpoints.web.exposure.include=* management.endpont.shutdown.enabled=true management.endpont.health.show-details=when_authorized info.app.name=Potato Task Application info.app.description=This is Potato Application based on spring boot info.app.version=1.0.0 # ============================================================== # spring cloud # ============================================================== spring.cloud.bus.enabled:false spring.cloud.bootstrap.enabled:false spring.cloud.discovery.enabled:false spring.cloud.consul.enabled:false spring.cloud.consul.config.enabled:false spring.cloud.config.discovery.enabled:false eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.serviceUrl.defaultZone: http://registry:8761/eureka #http://zipkin:9411 spring.zipkin.url=${ZIPKIN_URL} spring.sleuth.sampler.percentage=1.0
4.数据库表结构
由上面的配置可知,我们使用了sqlite这个迷你的文件型数据库。可以安装sqlite3这个命令行工具查看由spring-data-jpa自动生成的表结构,其ORM对象关系映射如图1-8和图1-9所示。
图1-8 土豆管理微服务数据实体(Entity)
图1-9 土豆管理微服务数据表设计
5.测试
我们在pom文件中加入了springfox-swagger-ui的支持,并且加入了相关的配置,这样可以生成一份API文档,并可以用它做一些简单的测试。示例如下:
package com.github.walterfan.potato.server.config; //省略import @Configuration @EnableAutoConfiguration @EnableSwagger2 @EnableJpaRepositories(basePackages = {"com.github.walterfan.potato.server"}) @ComponentScan(basePackages = {"com.github.walterfan.potato.server"}) @PropertySource("classpath:application.properties") public class WebConfig { private boolean enableSwagger = true; @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER 2) .forCodeGeneration(Boolean.TRUE) .select() .apis(RequestHandlerSelectors.basePackage("com.github.walterfan. potato.server")) //.paths(regex("/potato/api/v1/*")) .paths(PathSelectors.any()) .paths(Predicates.and(PathSelectors.regex("/potato/api.*"))) .build() .enable(enableSwagger) .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfo( "REST API", "REST description of API.", "API TOS", "Terms of service", new Contact("Walter Fan", "http://www.fanyamin.com", "walter. fan@gmail.com"), "License of API", "API license URL", Collections.emptyList()); } }
这样就得到了一个开箱即用的API文档和测试工具。运行如下命令:
cd potato-server java -jar target/task-0.0.1-SNAPSHOT.jar
打开http://localhost:9005/v2/api-docs,可以看到详细的API文档说明,如图1-10所示。
图1-10 API文档说明
打开http://localhost:9005/swagger-ui.html,可以看到各个API端点,如图1-11所示。
图1-11 土豆管理微服务API
直接单击一个API端点,可以在Example Value输入框中直接输入json格式的请求内容,提交一个请求,如图1-12所示。
图1-12 土豆管理微服务API测试之请求
得到的响应如图1-13所示。
图1-13 土豆管理微服务API测试之响应
1.5.3 微服务构建二:土豆提醒微服务
多个微服务需要共享并交换信息,它们之间通过网络通信协议进行通信,彼此之间存在依赖与被依赖的关系。我们在前面设计的PotatoService也需要和其他服务一起工作。
1.功能说明
当创建一个待办事项,指定了预定开始时间和结束时间后,就会调用potato-service的API来创建一个potato对象,potato-service又会调用potato-reminder的API创建两个RemindTask来发送提醒。所以提醒微服务的主要功能就是提醒,和其他微服务的互动时序图如图1-14所示。
图1-14 土豆微服务之间的互动时序图
2.主要代码
此处创建的还是典型的Spring Boot项目,在pom.xml中加入相关依赖项,最主要的是spring-boot-starter-quartz。这里使用著名的作业安排开源库Quartz,数据库选用MySQL。代码结构类似于土豆管理微服务,所以不做过多解释。下面展示核心实现代码。
数据传输对象为RemindEmailRequest。
package com.github.walterfan.potato.common.dto; //省略若干import语句 @Data @NoArgsConstructor @AllArgsConstructor @Builder public class RemindEmailRequest extends AbstractDTO { @Email @NotEmpty private String email; @NotEmpty private String subject; @NotEmpty private String body; @NotNull private Instant dateTime; @NotNull private ZoneId timeZone; }
Controller层实现如下:
package com.github.walterfan.potato.scheduler; //省略import @RestController @RequestMapping("/scheduler/api/v1") @Slf4j public class ScheduleController { @Value("${spring.application.name}") private String serviceName; @Value("${server.port}") private Integer serverPort; @Autowired private ScheduleService scheduleService; @PostMapping("/reminders") public ResponseEntity<RemindEmailResponse> scheduleEmail(@Valid @RequestBody RemindEmailRequest scheduleEmailRequest) { log.info("Receive {}", scheduleEmailRequest); return ResponseEntity.of(Optional.ofNullable(scheduleService.scheduleEmail (scheduleEmailRequest))); } //省略其他类型提醒
Service层实现如下:
@Service @Slf4j public class ScheduleServiceImpl implements ScheduleService { public static final String EMAIL_JOB_GROUP = "emailReminder"; @Autowired private Scheduler scheduler; @Override public RemindEmailResponse scheduleEmail(RemindEmailRequest scheduleEmailRequest) { log.info("schedule {}", scheduleEmailRequest); try { ZonedDateTime dateTime = getZonedDateTime(scheduleEmailRequest. getDateTime(), scheduleEmailRequest.getTimeZone()); JobDetail jobDetail = buildJobDetail(scheduleEmailRequest); Trigger trigger = buildJobTrigger(jobDetail.getKey(), dateTime); Date scheduledDate = scheduler.scheduleJob(jobDetail, trigger); RemindEmailResponse scheduleEmailResponse = new RemindEmailResponse(true, jobDetail.getKey().getName(), jobDetail.getKey().getGroup(),"Email Scheduled Successfully at " + scheduledDate); log.info("Send {}", scheduleEmailResponse); return scheduleEmailResponse; } catch (SchedulerException ex) { log.error("Error scheduling email", ex); throw new ResponseStatusException( HttpStatus.INTERNAL_SERVER_ERROR, "dateTime must be after current time"); } } //省略其他类型提醒 private JobDetail buildJobDetail(RemindEmailRequest scheduleEmailRequest) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("email", scheduleEmailRequest.getEmail()); jobDataMap.put("subject", scheduleEmailRequest.getSubject()); jobDataMap.put("body", scheduleEmailRequest.getBody()); return JobBuilder.newJob(EmailJob.class) .withIdentity(UUID.randomUUID().toString(), EMAIL_JOB_GROUP) .withDescription("Send Email Job") .usingJobData(jobDataMap) .storeDurably() .build(); } private Trigger buildJobTrigger(JobKey jobKey, ZonedDateTime startAt) { return TriggerBuilder.newTrigger() .withIdentity(jobKey.getName(), jobKey.getGroup()) .withDescription("Send Email Trigger") .startAt(Date.from(startAt.toInstant())) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfire-HandlingInstructionFireNow()) .build(); } }
最后,启动土豆提醒微服务:
package com.github.walterfan.potato.scheduler; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class SchedulerApplication { public static void main(String[] args) { SpringApplication.run(SchedulerApplication.class, args); } }
3.配置文件
配置文件中的相关属性可参考src/main/resources/application-dev.properties。
spring.application.name=potato-scheduler server.port=9002 # ============================================================== # Eureka client # ============================================================== eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.serviceUrl.defaultZone: http://localhost:8761/eureka/ # ============================================================== # data source # ============================================================== ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) spring.datasource.url=jdbc:mysql://mysqldb/scheduler? useUnicode=true&character-Encoding=utf8 spring.datasource.username=${MYSQL_USER} spring.datasource.password=${MYSQL_PWD} # ============================================================== ## QuartzProperties # ============================================================== spring.quartz.job-store-type=jdbc spring.quartz.properties.org.quartz.threadPool.threadCount=5 # ============================================================== ## MailProperties # ============================================================== spring.mail.host=${EMAIL_SMTP_SERVER} spring.mail.port=587 spring.mail.username=${EMAIL_USER} spring.mail.password=${EMAIL_PWD} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true ## https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0- Migration-Guide#spring-boot-actuator # ============================================================== # = Actuator # ============================================================== spring.jmx.default-domain=potato management_endpoints_jmx.exposure.include=* management.endpoint.shutdown.enabled=true management.endpoints.web.exposure.include=* management.endpont.shutdown.enabled=true management.endpont.health.show-details=when_authorized management.endpoints.web.base-path=/ management.endpoints.enabled-by-default=true management.endpoint.info.enabled=true management.endpoint.info.sensitive=false # actuator info info.app.name=Potato Schedule Application info.app.description=This is Potato Scheduler Application based on spring boot info.app.version=1.0.0
4.数据库表结构
这里选用Quartz的MySQL实现数据库表结构,直接用SQL脚本来创建数据库表,脚本参见源码。其中共有11张数据库表,如图1-15所示。
图1-15 土豆提醒微服务数据库设计
5.测试
打开http://localhost:9002/swagger-ui.html,可以看到我们提供的API,如图1-16所示。
图1-16 土豆提醒微服务API
可以通过swagger的测试界面来发起一个请求,在规定时间到达时,用户就会收到提醒的邮件,如图1-17所示。
图1-17 土豆提醒微服务“提醒”效果
1.5.4 微服务构建三:土豆网页微服务
提供一个前端页面由VUE框架简单实现,后端页面由Thymeleaf提供模板,再由Spring Boot框架提供Rest API。
1.主页界面
主页界面上有Potato待办事项的增删改查,以及启动、停止等功能。其中显示所有待办事项的网页如图1-18所示。
图1-18 土豆网页微服务之待办事项
创建Potato的界面如图1-19所示。
图1-19 土豆网页微服务之Potato创建界面
2.代码结构
这里创建的还是标准的Spring Boot Web应用程序,增加了thymeleaf模板文件(potato. html)和JavaScript代码(potato.js),代码结构如图1-20所示。
图1-20 土豆网页微服务之代码结构
3.主要代码
主要的Java代码就是两个Controller。
1)PotatoWebController将URL指向potatoes.html。
@Controller public class PotatoWebController { @RequestMapping(path = {"/potatoes"}) public String potatoes(Model model) { return "potatoes"; } @RequestMapping(path = {"/admin"}) public String admin(Model model) { return "welcome"; } }
2)PotatoApiController提供待办事项的增删改查功能,以及启动和停止的API供前端调用。
@RestController @RequestMapping("/api/v1") @Slf4j public class PotatoApiController { @Value("${spring.application.name}") private String serviceName; @Value("${server.port}") private Integer serverPort; @Value("${potato.guest.userId}") private String guestUserId; @Autowired private PotatoClient potatoClient; @RequestMapping(value = "/potatoes", method = RequestMethod.POST) @ApiCallMetricAnnotation(name = "CreatePotato") public PotatoDTO create(@RequestBody PotatoDTO potatoRequest) { log.info("create {}", potatoRequest); return potatoClient.createPotato(potatoRequest); } @LogDetail @RequestMapping(value = "/potatoes/{id}", method = RequestMethod.GET) @ApiCallMetricAnnotation(name = "RetrievePotato") public PotatoDTO retrieve(@PathVariable UUID id) { return potatoClient.retrievePotato(id); } //省略其他维护操作
HTML模板文件不在此详述,请参见源代码。potato.js文件提供模板内容的填充和与后台REST API的交互,使用了VUE框架。这里给出部分源码,仅供演示。
<! -- potato list --> function pad(number, length) { var str = "" + number while (str.length < length) { str = '0' + str } return str } Date.prototype.plusHours = function (hours) { var mm = this.getMonth() + 1; // getMonth() is zero-based var dd = this.getDate(); var hh = this.getHours() + hours; var mi = this.getMinutes(); var ss = this.getSeconds(); var offset = this.getTimezoneOffset(); offset = (offset < 0 ? '+' :'-') + pad(parseInt(Math.abs(offset / 60)), 2)+ ":" + pad(Math.abs(offset % 60), 2); return [this.getFullYear(), "-", (mm > 9 ? '' :'0') + mm, "-", (dd > 9 ? '' :'0') + dd, "T", (hh > 9 ? '' :'0') + hh, ":", (mi > 9 ? '' :'0') + mi, ":", (ss > 9 ? '' :'0') + ss, offset ].join(''); }; var Potato = Vue.extend({ template: '#potato', data:function () { return { 'potato': {} }; }, mounted() { axios .get('/api/v1/potatoes/' + this.$route.params.potato_id) .then(response => { this.potato = response.data; }) . catch(e => { this.errors.push(e); }) ; } }); var AddPotato = Vue.extend({ template: '#add-potato', data:function () { var rightNow = new Date(); var later1 = rightNow.plusHours(1); var later2 = rightNow.plusHours(2); return { potato: { name: '', description: '', tags: '', priority: 1, duration: 1, timeUnit: "HOURS", scheduleTime: later1, deadline: later2 }, errors: [] } }, methods: { createPotato:function () { console.log("--- createPotato:" + this.potato.name + ", " + this. potato.scheduleTime + ", " + this.potato.deadline); axios.post('/api/v1/potatoes', this.potato) .then(response => {} ) . catch(e => { this.errors.push(e) }) router.push('/'); } } }); //省略其他土豆维护操作 var router = new VueRouter({ routes: [ {path:'/', component:PotatoList}, {path:'/potatoes/:potato_id', component:Potato, title:'Toast Potato'}, {path:'/add-potato', component:AddPotato}, ] }); var app = new Vue({ router: router }).$mount('#app')
1.5.5 部署土豆微服务
通过前面3节,我们完成了3个微服务的构建。接下来用Docker把每个微服务构建为一个docker image,其中potato-web的Docker文件如下,其他两个类似,请参见源码。
FROM java:8 MAINTAINER Walter Fan VOLUME /tmp RUN mkdir -p /opt ADD ./target/web-0.0.1-SNAPSHOT.jar /opt/potato-web.jar EXPOSE 9005 ENTRYPOINT ["java", "-jar", "/opt/potato-web.jar"]
用docker compose将这3个微服务部署在一台计算机上启动。docker-compose.yml的内容如下:
version: '2' services: mysqldb: image: mysql container_name: local-mysql environment: - MYSQL_DATABASE=test - MYSQL_ROOT_PASSWORD=pass1234 - MYSQL_USER=walter - MYSQL_PASSWORD=pass1234 ports: -3306:3306 volumes: - ./data/db/mysql:/var/lib/mysql scheduler: image: walterfan/potato-scheduler container_name: potato-scheduler ports: -9002:9002 environment: - MYSQL_URL=jdbc:mysql://mysqldb/scheduler? useUnicode=true&charac-terEncoding=utf8 - MYSQL_USER=${MYSQL_USER} - MYSQL_PWD=${MYSQL_PWD} - EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER} - EMAIL_USER=${EMAIL_USER} - EMAIL_PWD=${EMAIL_PWD} - INFLUXDB_URL=http://graflux:8086 - ZIPKIN_URL=http://zipkin:9411 volumes: - ./data/db/sqlite:/var/lib/sqlite - ./data/logs:/opt/logs depends_on: - "mysqldb" - "graflux" links: - "mysqldb" - "graflux" - "zipkin" potato: image: walterfan/potato-app container_name: potato-app ports: -9003:9003 environment: - INFLUXDB_URL=http://graflux:8086 - ZIPKIN_URL=http://zipkin:9411 - potato_scheduler_url=http://scheduler:9002/scheduler/api/v1 volumes: - ./data/db/sqlite:/var/lib/sqlite - ./data/logs:/opt/logs depends_on: - "mysqldb" links: - "graflux" - "mysqldb" - "scheduler" - "zipkin" web: image: walterfan/potato-web container_name: potato-web ports: -9005:9005 environment: - INFLUXDB_URL=http://graflux:8086 - ZIPKIN_URL=http://zipkin:9411 - potato_server_url=http://potato:9003/potato/api/v1 volumes: - ./data/logs:/opt/logs depends_on: - "potato" links: - "graflux" - "potato" - "zipkin"
这样,通过如下命令就可以启动并运行这3个微服务和依赖的数据库了。
docker-compose up -d
当添加了一个待办事项时,在它计划的开始和截止时间之前我们可以收到提醒邮件。在之后的运行过程中,或许会遇到一些问题,例如收不到提醒邮件,我们不知道是什么原因,同时这3个微服务的运行状况、资源使用情况、用量、性能我们都不清楚。在后续章节中,将通过度量驱动的方法来逐一解决这些问题。