使用SpringCloud實戰微服務
一微服務架構概述
1.1 微服務特性以及優點
- 每個服務可以獨立執行在自己的程序裡
- 一系列獨立執行的微服務(goods,order,pay,user,search…)共同構建了整個系統
- 每個服務為獨立的業務開發,一個微服務只關注某個特定的功能,例如使用者管理,商品管理微服務
- 微服務之間通過一些輕量級的通訊機制進行通訊,例如通過Restful API進行呼叫
- 技術棧不受限:可以使用不同的開發語言和資料儲存技術
- 全自動的部署機制
- 按需伸縮:根據需求和應用場景,實現細粒度的水平擴充套件
1.2 微服務帶來的挑戰
- 運維要求較高
- 分散式的複雜性
- 介面調整成本較高
1.3 微服務設計原則
- 單一職責原則
- 服務自治原則
- 輕量級通訊機制
- 微服務粒度
1.4 微服務開發框架
- SpringCloud:眾多元件構造完善的分散式系統
- Dubbo/Dubbox:關注服務治理
- Dropwizard:關注單個微服務開發
二 SpringCloud概述與開發環境
2.1 SpringCloud概述
SpringCloud是基於SpringBoot之上的用來快速構建微服務系統的工具集,擁有功能完善的輕量級微服務元件,例如服務治理(Eureka),宣告式REST呼叫(Feign),客戶端負載均衡(Ribbon),服務容錯(Hystrix),服務閘道器(Zuul)以及服務配置(Spring Cloud Config),服務跟蹤(Sleuth)等等。
官網連結:http://projects.spring.io/spring-cloud/
目前主流的版本為SpringBoot1.4.5.RELEAE和SpringCloudCamden.SR7,
Maven pom配置如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version >
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
2.2 開發環境
MacOS10.12+JDK8u131+IntelliJ IDEA2017.1.4
Tomcat8.5+Maven3.3.9+Git2.12+Firefox54
Spring4.3.9.RELEASE+SpringBoot1.4.5.RELEASE+SpringCloud Camden.SR7
三 工程機器模組說明
3.1 工程說明
工程首先自定義了Maven父工程,其中定義如下的公共元件:
<?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">
<modelVersion>4.0.0</modelVersion>
<!-- 自定義工程的maven座標-->
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- 基於SpringBoot 1.4.5.RELEASE-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version>
<relativePath/>
</parent>
<!-- 定義引用類庫版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR7</spring-cloud.version>
<springcloud-parent.version>2.0.0-SNAPSHOT</springcloud-parent.version>
<druid.version>1.0.31</druid.version>
<jackson.version>2.8.8</jackson.version>
<commons-lang3.version>3.5</commons-lang3.version>
<ehcache.version>3.1.4</ehcache.version>
<hibernate.version>5.0.12.Final</hibernate.version>
<servlet-api.version>3.1.0</servlet-api.version>
<commons-collection4.version>4.1</commons-collection4.version>
<springframework.oxm.version>4.3.9.RELEASE</springframework.oxm.version>
</properties>
<!-- 引入SpringCloud微服務常用元件-->
<modules>
<module>springcloud-eureka-server</module>
<module>springcloud-eureka-server-ha</module>
<module>springcloud-provider-user-service</module>
<module>springcloud-consumer-h5-ribbon-hystrix</module>
<module>springcloud-consumer-h5-feign</module>
<module>springcloud-api-gateway</module>
<module>springcloud-consumer-h5</module>
<module>springcloud-config-server</module>
</modules>
<dependencies>
<!-- 服務發現元件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 應用監控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 應用測試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collection4.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${springframework.oxm.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3.2 模組說明
模組則藉助IntelliJ IDEA結合Spring Initializer自動生成工程結構,主要模組和說明如下:
四 使用SpringBoot實現服務提供者
所屬maven模組:springcloud-provider-user-service
基於SpringBoot的Web和JPA模組實現Restful API的常用方法
4.1 entity
主要包含User,Role,Department三個實體
Role.java
package com.ekeyfund.springcloud.entity;
import javax.persistence.*;
import java.io.Serializable;
/**
* Role Entity
*
* @author Liuguanglei [email protected]
* @create 2017-06-下午2:36
*/
@Entity
@Table(name = "springboot_role")
public class Role implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long id;
@Column(name = "role_name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new org.apache.commons.lang3.builder.ToStringBuilder(this)
.append("id", id)
.append("name", name)
.toString();
}
}
Department.java
package com.ekeyfund.springcloud.entity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
/**
* Department Entity
*
* @author Liuguanglei [email protected]
* @create 2017-06-下午2:31
*/
@Entity
@Table(name = "springboot_department")
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Department implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "department_id")
private Long id;
@Column(name = "department_name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.toString();
}
}
User.java
package com.ekeyfund.springcloud.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* User Entity
*
* @author Liuguanglei [email protected]
* @create 2017-06-下午2:32
*/
@Entity
@Table(name = "springboot_user")
public class User implements Serializable{
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name")
private String name;
@Column(name = "user_password")
private String password;
@Column(name = "user_create_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createDate;
@ManyToOne
@JoinColumn(name = "department_id")
@JsonBackReference
private Department department;
@ManyToMany(cascade = {},fetch = FetchType.EAGER)
@JoinTable(name = "springboot_user_role",joinColumns = {@JoinColumn(name="user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")}
)
private List<Role> roleList;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("password", password)
.append("createDate", createDate)
.append("department", department)
.append("roleList", roleList)
.toString();
}
}
4.2 資料訪問Repository
主要包含UserRepository和DepartmentRepository
DepartmentRepository.java
package com.ekeyfund.springcloud.repository;
import com.ekeyfund.springcloud.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* Created by tony on 2017/6/19.
*/
@Repository
public interface DepartmentRepository extends JpaRepository<Department,Long> {
}
UserRepoistory.java
package com.ekeyfund.springcloud.repository;
import com.ekeyfund.springcloud.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* User Repository
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:54
*/
@Repository
public interface UserRepository extends JpaRepository<User,Long>{
/**
* and
* @param id
* @param name
* @return
*/
User findByIdAndName(Long id, String name);
User findByNameAndPassword(String name, String password);
/**
* or
* @param id
* @param name
* @return
*/
User findByIdOrName(Long id, String name);
/**
* between
* @param start
* @param end
* @return
*/
List<User> findByCreateDateBetween(Date start, Date end);
/**
* lessThan
* @param start
* @return
*/
List<User> getByCreateDateLessThan(Date start);
/**
* Greater Than
* @param start
* @return
*/
List<User> findByCreateDateGreaterThan(Date start);
/**
* is null
* @return
*/
List<User> findByNameIsNull();
/**
* in
* @param nameList
* @return
*/
List<User> findByNameIn(Collection<String> nameList);
}
4.3 業務邏輯Service
主要包含UserService,DepartmentService及其實現
UserService.java
package com.ekeyfund.springcloud.service;
import com.ekeyfund.springcloud.entity.User;
import java.util.List;
/**
* Created by tony on 2017/6/19.
*/
public interface UserService {
/**
* 登入
* @param name
* @param password
* @return
*/
public User login(String name, String password);
/**
* 註冊
* @param user
* @return
*/
public User register(User user);
/**
* 登出
* @param user
* @return
*/
void writeOff(User user);
/**
* 當前使用者是否已經存在
* @param user
* @return
*/
boolean isExists(User user);
List<User> getAllUser();
User getUserById(Long id);
}
UserServiceImpl.java
package com.ekeyfund.springcloud.service.impl;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.repository.UserRepository;
import com.ekeyfund.springcloud.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
/**
* User Service Impl
*
* @author Liuguanglei [email protected]
* @create 2017-06-下午3:34
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User login(String name, String password) {
return userRepository.findByNameAndPassword(name,password);
}
@Override
public User register(User user) {
return userRepository.save(user);
}
@Override
public void writeOff(User user) {
userRepository.delete(user);
}
@Override
public boolean isExists(User user) {
return userRepository.findOne(user.getId())!=null?true:false;
}
@Override
public List<User> getAllUser() {
return userRepository.findAll();
}
@Override
public User getUserById(Long id) {
return userRepository.findOne(id);
}
}
DepartmentService.java
package com.ekeyfund.springcloud.service;
import com.ekeyfund.springcloud.entity.Department;
/**
* Created by tony on 2017/6/19.
*/
public interface DepartmentService {
Department saveDepartment(Department department);
Department getDepartmentById(Long id);
}
DepartmentServiceImpl.java
package com.ekeyfund.springcloud.service.impl;
import com.ekeyfund.springcloud.entity.Department;
import com.ekeyfund.springcloud.repository.DepartmentRepository;
import com.ekeyfund.springcloud.service.DepartmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
/**
* Department Impl
*
* @author Liuguanglei [email protected]
* @create 2017-06-下午3:12
*/
@Transactional
@Service
public class DepartmentImpl implements DepartmentService {
@Autowired
private DepartmentRepository departmentRepository;
@Override
public Department saveDepartment(Department department) {
return departmentRepository.save(department);
}
@Override
public Department getDepartmentById(Long id) {
return departmentRepository.findOne(id);
}
}
4.4 Controller層
主要包含提供User完整的Restful API 的UserController
UserController.java
package com.ekeyfund.springcloud.controller;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.service.UserService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
/**
* UserController
* Restful API
* @author Liuguanglei [email protected]
* @create 2017-06-下午11:24
*/
@RestController
public class UserController {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private UserService userService;
@GetMapping(value = "/list")
public List<User> list(){
ServiceInstance instance=discoveryClient.getLocalServiceInstance();
LOGGER.info("call user/list service host is "+instance.getHost()+"service_id is "+instance.getServiceId());
return userService.getAllUser();
}
@GetMapping(value = "/login")
public User login( @RequestParam String name,@RequestParam String password){
User user=userService.login(name,password);
return user;
}
@PostMapping("/register")
public String register(@ModelAttribute User user){
User result =userService.register(user);
return result!=null?"success":"fail";
}
@GetMapping("/get/{id}")
public User get(@PathVariable Long id){
return userService.getUserById(id);
}
@PutMapping("/update/{id}")
public String update(@PathVariable Long id,@ModelAttribute User user){
User updatedUser =userService.getUserById(id);
updatedUser.setName(user.getName());
updatedUser.setPassword(user.getPassword());
updatedUser.setCreateDate(new Date());
User result= userService.register(updatedUser);
return result!=null?"success":"fail";
}
@DeleteMapping("/delete/{id}")
public String delete(@PathVariable Long id){
User user =new User();
user.setId(id);
userService.writeOff(user);
return "success";
}
}
4.5 Configuration
主要包含資料來源Druid和JPA的配置
DruidConfiguation.java
package com.ekeyfund.springcloud.configuration;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* Druid Configuration
*
* @author Liuguanglei [email protected]
* @create 2017-06-下午5:48
*/
public class DruidConfiguration {
@Bean
public ServletRegistrationBean statViewServle(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//白名單:
servletRegistrationBean.addInitParameter("allow","192.168.1.218,127.0.0.1");
//IP黑名單 (存在共同時,deny優先於allow) : 如果滿足deny的即提示:Sorry, you are not permitted to view this page.
servletRegistrationBean.addInitParameter("deny","192.168.1.100");
//登入檢視資訊的賬號密碼.
servletRegistrationBean.addInitParameter("loginUsername","druid");
servletRegistrationBean.addInitParameter("loginPassword","12345678");
//是否能夠重置資料.
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean statFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//新增過濾規則.
filterRegistrationBean.addUrlPatterns("/*");
//新增不需要忽略的格式資訊.
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
JPAPersistenceConfiguration
package com.ekeyfund.springcloud.configuration;
import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;
/**
* JPA Persistence Configuration
*
* @author Liuguanglei [email protected]
* @create 2017-06-上午11:26
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true) //啟用JPA的事務管理
@EnableJpaRepositories(basePackages = "com.ekeyfund.springcloud.repository" )//啟用JPA資源庫並指定資源庫介面位置
@EntityScan(basePackages = "com.ekeyfund.springcloud.entity")//指定實體的位置
public class JPAPersistenceConfiguration {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JPAPersistenceConfiguration.class);
/*******************資料庫和連線池配置資訊,讀取application.properties檔案的屬性值****************************/
@Value("${spring.datasource.driver-class-name}")
private String driverClass;
@Value("${spring.datasource.username}")
private String userName;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private long maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private long minEvictableIdleTimeMillis;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Bean(name = "druidDataSource",initMethod = "init",destroyMethod = "close")
public DataSource dataSource(){
DruidDataSource druidDataSource =new DruidDataSource();
druidDataSource.setDriverClassName(driverClass);
druidDataSource.setUsername(userName);
druidDataSource.setPassword(password);
druidDataSource.setUrl(url);
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
druidDataSource.setMaxActive(maxActive);
druidDataSource.setMaxWait(maxWait);
druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
druidDataSource.setConnectionProperties(connectionProperties);
try {
druidDataSource.setFilters(filters);
} catch (SQLException e) {
LOGGER.error("build datasoure exception ",e.getMessage());
}
return druidDataSource;
}
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource druidDataSource){
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(druidDataSource);
localContainerEntityManagerFactoryBean.setPackagesToScan("com.ekeyfund.springcloud.entity");
localContainerEntityManagerFactoryBean.setJpaProperties(buildHibernateProperties());
localContainerEntityManagerFactoryBean.setJpaDialect(new HibernateJpaDialect());
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter(){
{
setDatabase(org.springframework.orm.jpa.vendor.Database.MYSQL);
setDatabasePlatform("org.hi