Spring Boot+Docker微服务分布式服务架构设计和部署案例

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中实现分布式集群部署,体现出了微服务强大的可伸缩性。

工程结构图:

Spring Boot+Docker微服务分布式服务架构设计和部署案例_第1张图片

演示界面:

     Spring Boot+Docker微服务分布式服务架构设计和部署案例_第2张图片      

输入快递单号,结果输出快递单的信息

部分代码演示:

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软件开发方面已经具有超过十年以上的经验,并且通过对GrailsRatpack web框架这样的开源项目进行贡献的方式分享他的经验。Dan也是Gr8conf和SpringOne 2GX会议上的演讲者,他在会议上展现了他在JVM的企业级应用架构上的专业知识。

http://www.infoq.com/cn/articles/boot-microservices

你可能感兴趣的:(spring,docker,Boot,微服务)