前言
springcloud是微服务架构的集大成者,将一系列优秀的组件进行了整合。基于springboot构建,多用于大型互联网架构,笔者今天就带大家一步步搭建springcloud框架,本框架使用idea的多模块架构集成了注册中心eureka,网关zuul,缓存redis,熔断hystrix,负载均衡ribbon,源码已上传至github,有需要的童鞋可以下载
准备工作
本demo涉及到的工具:idea2016,mysql5.6,redis(windows)
环境:jdk1.8
需要对springboot,rpc原理有一定的了解
正文
项目结构截图
模块说明
演示流程
1.用户登陆,后台生成一个令牌存入redis,并将令牌下发
2.用户拿令牌查询用户信息,顺序为postman--zuul--sendCodePlatformUserService--mysql
3.用户保存订单信息,顺序为postman--zuul--sendCodePlatformQrService--sendCodePlatformUserService
工程的pom.xml,其中的commonApi不是一个服务,而是作为一个模块被打成jar包给其它服务使用,可以类比为我们平时项目中引入的jar包
<?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>
<groupId>com.lovnx</groupId>
<artifactId>trafficQrService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>eureka-sever</module>
<module>zuul</module>
<module>commonApi</module>
<module>sendCodePlatformUserService</module>
<module>sendCodePlatformQrService</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
</project>先看一下注册中心的 application.yml
eureka:
client:
register-with-eureka: false #服务注册中心也会将自己作为客户端来尝试注册自己,为true(默认)时自动生效
fetch-registry: false #检索服务选项,当设置为True(默认值)时,会进行服务检索,注册中心不负责检索服务
serviceUrl:
defaultZone: http://localhost:${server.port}/eureka/
server:
#测试环境关闭自我保护机制,正式环境需要打开,因为有可能心跳通讯阻塞,服务是正常的
enable-self-preservation: false
#清理无效节点,默认60*1000毫秒,即60秒,扫描失效服务的间隔时间
eviction-interval-timer-in-ms: 5000
server:
port: 7070
spring:
application:
name: eureka-server启动注册中心,登陆http://localhost:7070/, 我们发现还没有服务注册到Eureka中
看一下数据库的表结构
用户表
订单表
我们看一下用户服务项目的application.yml
eureka:
client:
serviceUrl:
#正式环境Eureka做主副备份,需要2个地址
defaultZone: http://localhost:7070/eureka/
instance:
#eureka客户端需要多长时间发送心跳给eureka服务器,表明他仍然活着,默认30秒
lease-renewal-interval-in-seconds: 5
#eureka服务器在接受到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认90S
#如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。如果设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉
lease-expiration-duration-in-seconds: 10
server:
port: 7074
spring:
application:
name: sendCodePlatformUserService
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
druid:
#jdbc配置
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true
username: root
password: 123456
#连接池配置
initial-size: 5
min-idle: 5
max-active: 10
max-wait: 50000
mybatis:
mapper-locations: classpath:mapping/*.xml
management:
security:
enabled: false需要告诉该服务注册中心的地址,指定该服务的name,即在注册中心的serviceId,数据库的相关信息,启动数据库(用到的表和数据可根据项目人造)和该服务,登陆注册中心
说明用户服务已经注册到Eureka中了
再看一下订单服务的application.yml
eureka:
client:
serviceUrl:
#正式环境Eureka做主副备份,故这里预留2个地址
defaultZone: http://localhost:7070/eureka/
instance:
#eureka客户端需要多长时间发送心跳给eureka服务器,表明他仍然活着,默认30秒
lease-renewal-interval-in-seconds: 5
#eureka服务器在接受到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认90S
#如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。如果设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉
lease-expiration-duration-in-seconds: 10
server:
port: 7075
spring:
application:
name: sendCodePlatformQrService
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
druid:
#jdbc配置
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true
username: root
password: 123456
#连接池配置
initial-size: 5
min-idle: 5
max-active: 10
max-wait: 50000
mybatis:
mapper-locations: classpath:mapping/*.xml
management:
security:
enabled: false
#设置hystrix超时时间为40S,默认1S服务无响应就直接返回了
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 50000
ribbon:
ReadTimeout: 50000
ConnectTimeout: 50000启动服务,并登陆注册中心
我们发现订单服务也注册进去了
再看一下网关zuul的application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7070/eureka/
#表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
registry-fetch-interval-seconds: 5
server:
port: 7072
spring:
application:
name: zuul
## Redis 配置
## Redis数据库索引(默认为0)
redis:
database: 0
## Redis服务器地址
host: 127.0.0.1
## Redis服务器连接端口
port: 6379
#Redis服务器连接密码(默认为空)
## 连接池最大连接数(使用负值表示没有限制)
pool:
max-active: 8
## 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
## 连接池中的最大空闲连接
max-idle: 8
## 连接池中的最小空闲连接
min-idle: 0
## 连接超时时间(毫秒)
timeout: 30000
zuul:
routes:
#用户类请求
api-a:
path: /api/user/**
serviceId: sendCodePlatformUserService
#发码类请求
api-b:
path: /api/qr/**
serviceId: sendCodePlatformQrService
#host:
#url配置生效
socket-timeout-millis: 50000
connect-timeout-millis: 50000
#设置hystrix超时时间为50S,默认1S服务无响应就直接返回了
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 20000
#ribbon:
# 请求处理的超时时间
ReadTimeout: 50000
#请求连接的超时时间
ConnectTimeout: 50000启动zuul并登陆注册中心
我们发现zuul也注册进去了
redis主要用来缓存用户的token信息,避免将用户的token存入数据库,提高并发能力,网关配置那里通过api-a,api-b进行路由分发,serviceId是服务中配置的name
启动本地的redis
调用postman
因为token无效,所以查询失败,需要先登陆拿到token
调用登陆
登陆成功,拿到token
调用用户信息查询
查询成功
接下来我们新增一笔订单,请求会通过网关先到订单服务,订单服务调用用户服务的查询接口以核实用户的有效性,然后新增一笔订单
订单服务相关代码,通过feign调用用户服务
public class QrController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private UserFeignClient userFeignClient;
@Autowired
private QrTicketInfoFacade qrTicketInfoFacade;
@RequestMapping(value = "/saveQrOrder" ,method = RequestMethod.POST)
public BasePojoObj saveQrOrder(@RequestBody ReqApiParam clientParam) {
BasePojoObj result = new BasePojoObj();
try {
//Thread.sleep(40000);
//通过feign调用用户相关服务
BasePojoObj obj=userFeignClient.getUserInfo(clientParam);
if(obj!=null) {
qrTicketInfoFacade.saveQrTicketInfos();
result.setStatus(BasePojo.CODE_SUCCESS);
result.setMsg("新增订单成功");
}
} catch (Exception e) {
logger.error("异常:{}",e);
result.setStatus(BasePojo.CODE_FAIL);
result.setMsg(e.getMessage());
}
return result;
}
}@FeignClient("sendCodePlatformUserService")
public interface UserFeignClient {
@RequestMapping(value = "/getUserInfo" ,method = RequestMethod.POST)
BasePojoObj getUserInfo(@RequestBody ReqApiParam clientParam);
}请求新增一笔订单
我们发现订单服务调用用户服务查询接口成功了,并且新增了一笔订单
总结
我们看到springcloud非常灵活,能够把业务进行拆分,比较适合大型项目,除了这些组件,springcloud还有配置中心Config, Bus消息总线,Sleuth分布式服务追踪等等,每种组件必然有其存在的意义,一般是为了解决某一类问题,所以springcloud的博大精深值得大家研究一番。