j360-microservice
spring-boot+docker微服务设计之j360-microservice:(欢迎star、fork)
https://github.com/xuminwlt/j360-microservice
前言
自从spring4发布spring boot开始,springboot非常自然的成为了spring的顶级项目,打开spring的网站,springboot也位列第二,在他爹地spring io platform后面,发展到现在springboot已经在很多场景下发挥了巨大的生产力,尤其是通过内置容器的方式,简单一个java -jar xxx.jar便可以发布该工程,随着springboot被越来越多的人使用并且推荐,包括我,springboot为微服务架构方式不再只停留在概念阶段,springboot为创建微服务架构提供了稳定的基础,作为大家耳熟能详的SOA架构,各说纷纭,微服务在我理解范围内,特别是在得到springboot的支持下,微服务更加能体现出分布式服务的精髓,微服务的颗粒度更小,作为单一的功能而言,springboot更加能快速简单的提供服务支撑。
Docker自从14年热吧热吧以来,在今年docker已经占据了所有容器板块的头条位置,docker也很努力,发展迭代马不停蹄。
从分别关注springboot和docker一开始我便一直在思考如何让两个人在一起,满足我的私欲,必须在一起,不为别的,springboot是1,docker是0.
介绍
微服务
微服务的特点是将每个单一服务能够独立提供服务,这个概念和SOA非常相似,就是所有的服务都必须提供服务的方式,在基于springboot的微服务架构中,使用restful的api是官方推荐的接口,也就是基于HTTP协议的通信方式。
微服务使用springmvc可以很好的提供rest服务,而作为客户端可以基于Spring中的RestTemplate进行创建。springboot的就是为服务方和客户端提供良好的支撑。
容器部署
使用docker可以很方便地为springboot提供容器服务,在任何安装jdk的docker环境中,springboot都可以发布。
案例
通过案例来介绍springboot和docker是一件很有意思的事情,本案例j360-microservice通过两个服务来描述快递下单和查询订单的功能,订单的底层服务交给j360-order服务,订单的查询和下单交给j360-express,通过微服务设计的高度可扩展性,j360-order同样还可以处理来源于其他的客户端发来的请求,同样j360-express可以给其他的快递公司发送请求,通过在docker中实现分布式集群部署,体现出了微服务强大的可伸缩性。
工程结构图:
演示界面:
输入快递单号,结果输出快递单的信息
部分代码演示:
j360-order:微服务服务提供方
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>j360-microservice</artifactId>
<groupId>me.j360.boot.microservice</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>j360-order</artifactId>
<properties>
<java.version>1.7</java.version>
</properties>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<!--jpa-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--使用SpringLoaded工具,war包可以使用,但是必须使用内置的容器,去掉private的scope-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<!--不是每个人都喜欢继承 spring-boot-starter-parent POM。你可能需要使用公司标准parent,或你可能倾向于显式声明所有
Maven配置。如果你不使用 spring-boot-starter-parent ,通过使用一个 scope=import 的依赖,你仍能获取到依赖管理的好处-->
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
<!--为了在Maven命令行下使用Spring Loaded,你只需将它作为一个依赖添加到Spring Boot插件声明中即可-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.5.BUILD-SNAPSHOT</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
<!-- Additional lines to be added here... -->
<!-- (you don't need this if you are using a .RELEASE version) -->
<repositories>
<repository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<url>http://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/snapshot</url>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<url>http://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
package me.j360.boot.microservice.order.web;
import me.j360.boot.microservice.order.entity.Express;
import me.j360.boot.microservice.order.repository.ExpressRepository;
import me.j360.boot.microservice.order.validator.ExpressValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
/**
* Created with j360-microservice -> me.j360.boot.microservice.order.web.
* User: min_xu
* Date: 2015/9/27
* Time: 21:07
* 说明:通过restController实现的restAPI
*/
@RestController
@RequestMapping("/expresses")
public class ExpressController {
private final ExpressRepository repository;
private final ExpressValidator validator;
@Autowired
public ExpressController(ExpressRepository repository,ExpressValidator validator) {
this.repository = repository;
this.validator = validator;
}
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(validator);
}
@RequestMapping(method = RequestMethod.GET)
public Iterable findAll() {
return repository.findAll();
}
@RequestMapping(method = RequestMethod.POST)
public Express create(@RequestBody Express express) {
return repository.save(express);
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Express find(@PathVariable long id) {
Express detail = repository.findOne(id);
if (detail == null) {
throw new ExpressNotFoundException();
} else {
return detail;
}
}
@ResponseStatus(HttpStatus.NOT_FOUND)
static class ExpressNotFoundException extends RuntimeException {
}
}
@Entity
public class Express {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private BigDecimal cost;
private String no;
@OneToMany(mappedBy = "express",fetch = FetchType.LAZY)
@OrderBy("id desc")
private List<Track> tracks;
public List<Track> getTracks() {
return tracks;
}
public void setTracks(List<Track> tracks) {
this.tracks = tracks;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
j360-deliver:用户界面,调用微服务
@Override
public Map getMap(Long id) {
ResponseEntity<Express> entity = restTemplate.getForEntity("http://localhost:8080/expresses/1",Express.class);
Express express = entity.getBody();
Map<String,Object> map = new HashMap<>();
map.put("cost",express.getCost());
return map;
}
@Aspect
@Service
public class HystrixAdvice {
private static final String GROUP = "express";
private static final int TIMEOUT = 60000;
private static final Logger logger = LoggerFactory
.getLogger(HystrixAdvice.class);
/*
* 定义object需要转化为Object对象,此处省略
@Around("execution(* me.j360.boot.microservice.service.ExpressService.findOne(..))")
public Object hystrixCommand(final ProceedingJoinPoint pjp){
logger.info("log Around method: "
+ pjp.getTarget().getClass().getName() + "."
+ pjp.getSignature().getName());
List<Callable<AsyncResponse>> callables = new ArrayList<Callable<AsyncResponse>>();
callables.add(new BackendServiceCallable("express", getExpressMap(pjp)));
Map<String, Map<String, Object>> map = HystrixService.doBackendAsyncServiceCall(callables);
try {
String json = new ObjectMapper().writeValueAsString(map.get("express"));
return new ObjectMapper().readValue(json,Express.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}*/
@Around("execution(* me.j360.boot.microservice.service.ExpressService.getMap(..))")
public Map<String, Object> hystrixCommandMap(final ProceedingJoinPoint pjp){
logger.info("log Around method: "
+ pjp.getTarget().getClass().getName() + "."
+ pjp.getSignature().getName());
List<Callable<AsyncResponse>> callables = new ArrayList<Callable<AsyncResponse>>();
callables.add(new BackendServiceCallable("express", getExpressMap(pjp)));
Map<String, Map<String, Object>> map = HystrixService.doBackendAsyncServiceCall(callables);
return map.get("express");
}
@Cacheable
private HystrixCommand<Map<String, Object>> getExpressMap(final ProceedingJoinPoint pjp) {
return new HystrixCommand<Map<String, Object>>(
HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(GROUP))
.andCommandKey(HystrixCommandKey.Factory.asKey("getExpressMap"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationThreadTimeoutInMilliseconds(TIMEOUT)
)
) {
@Override
protected Map<String, Object> run() throws Exception {
try {
return (Map<String, Object>) pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return new HashMap<String, Object>();
}
@Override
protected Map getFallback() {
return new HashMap<String, Object>();
}
};
}
}
@Controller
public class ExpressController {
@Autowired
private ExpressService expressService;
@RequestMapping(value = "/",method = RequestMethod.GET)
public String index() {
return "index";
}
@RequestMapping(value = "/express",method = RequestMethod.GET)
public String express(Model model) {
Map<String,Object> map = expressService.getMap(1l);
model.addAttribute("express",map);
return "index";
}
}
此处演示用的是写死的id查找,需要单号需要再封装一个单号查询方法即可。
部署Docker
1、通过maven package分别生成对应的jar文件
2、如果使用docker maven的话,在docker中安装git+maven直接生成jar也可以,这里涉及到持续集成的过程,略过
参考:
Daniel Woods是一位技术狂热者,尤其是在企业级的Java、Groovy,和Grails开发方面。他在JVM软件开发方面已经具有超过十年以上的经验,并且通过对Grails和Ratpack web框架这样的开源项目进行贡献的方式分享他的经验。Dan也是Gr8conf和SpringOne 2GX会议上的演讲者,他在会议上展现了他在JVM的企业级应用架构上的专业知识。
http://www.infoq.com/cn/articles/boot-microservices