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中实现分布式集群部署,体现出了微服务强大的可伸缩性。
工程结构图:
输入快递单号,结果输出快递单的信息
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; } }
@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查找,需要单号需要再封装一个单号查询方法即可。
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