1.基于springboot2.0.5,springcloud2.X(Finchley.SR1),本文会先搭建一个简单的springcloud示例,包括feign、hystrix、zuul、springcloud-config、springcloud-stream这些的整合。
2.项目模块说明
eureka-server 注册中心
config-server 配置中心
user-service 用户服务
order-service 订单服务
message-service 消息服务
gateway 路由服务
consumer 前端服务
2.开始
2.1 注册中心
依赖如下:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.boot
spring-boot-starter-web
application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false #是否将自身注册到eureka
fetchRegistry: false #是否抓取注册信息
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类加上注解@EnableEurekaServer
2.2 服务提供者
order-service,message-service,user-service,三者依赖都一样:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
order-service的配置文件:
spring:
application:
name: order-service
server:
port: 8763
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
user-service的配置文件:
spring:
application:
name: user-service
server:
port: 8762
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
message-service的配置文件:
spring:
application:
name: message-service
server:
port: 8764
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
然后我们分别启动这4个项目,浏览器输入http://localhost:8761/ 可以看到服务启动成功。
2.3 整合feign和hystrix
2.3.1 新建模块consumer,前端模块,consumer不需要注册到eureka,在这里我们通过consumer去调用服务集群。
2.3.2 consumer引入feign依赖
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
2.3.3 给order-service新建一个简单的controller
@RestController
public class OrderController {
static Logger log = LogManager.getLogger(OrderController.class);
@RequestMapping(value = "/get")
public OrderEntity get(LocalDateTime dateTime){
OrderEntity orderEntity = new OrderEntity();
orderEntity.setId(8L);
orderEntity.setCustomerName("test测试");
orderEntity.setDate(dateTime);
return orderEntity;
}
@RequestMapping(value = "/getById")
public OrderEntity get(Long id){
OrderEntity orderEntity = new OrderEntity();
orderEntity.setId(id);
orderEntity.setCustomerName("test测试");
orderEntity.setDate(LocalDateTime.now());
return orderEntity;
}
@RequestMapping(value = "/normal")
public String normal(){
return "1";
}
@RequestMapping(value = "/abnormal")
public String abNormal() throws InterruptedException {
log.info("abnormal-start:{}",LocalDateTime.now());
Thread.sleep(8000);
log.info("abnormal-end:{}",LocalDateTime.now());
return "2";
}
@RequestMapping(value = "/abnormalWithCallBack")
public String abnormalWithCallBack() throws InterruptedException {
log.info("abnormalWithCallBack-start:{}",LocalDateTime.now());
Thread.sleep(6000);
log.info("abnormalWithCallBack-end:{}",LocalDateTime.now());
return "3";
}
}
2.3.4 consumer模块中新建feign客户端
@FeignClient(name = "order",url = "http://localhost:8861/order",fallbackFactory = OrderClientFallBack.class)
public interface OrderClient {
@RequestMapping(method = RequestMethod.GET,value = "/get")
OrderEntity getOne(LocalDateTime dateTime);
@RequestMapping(method = RequestMethod.GET,value = "/getById")
OrderEntity getOne(Long id);
@RequestMapping(method = RequestMethod.GET,value = "/normal")
String normal();
@RequestMapping(method = RequestMethod.GET,value = "/abnormal")
String abnormal();
@RequestMapping(method = RequestMethod.GET,value = "/abnormalWithCallBack")
String abnormalWithCallBack();
}
fallback处理:
@Component
public class OrderClientFallBack implements FallbackFactory {
static Logger log = LogManager.getLogger(OrderClientFallBack.class);
@Override
public OrderClient create(Throwable throwable) {
return new OrderClient() {
@Override
public OrderEntity getOne(LocalDateTime dateTime) {
return null;
}
@Override
public OrderEntity getOne(Long id) {
return null;
}
@Override
public String normal() {
return null;
}
@Override
public String abnormal() {
return null;
}
@Override
public String abnormalWithCallBack() {
log.info("abnormalWithCallBack - fail");
return "abnormalWithCallBack - fail";
}
};
}
}
consumer配置文件如下:增加超时时间的设置(feign默认1秒钟超时),并打开feign-hystrix(默认是关),一般可以将hystrix的超时时间设置的比feign的超时时间长一些,否则feign的重试(如果配置了)将会失效。
spring:
application:
name: consumer
server:
port: 8862
feign:
client:
config:
default:
connectTimeout: 7000
readTimeout: 7000
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 8000
2.3.5 consumer增加controller
@RestController
@RequestMapping(value = "/consumer/test")
public class ConsumerController {
static Logger log = LogManager.getLogger(ConsumerController.class);
@Autowired
OrderClient orderClient;
@RequestMapping(value = "/order/get" )
public OrderEntity get(){
return orderClient.getOne(LocalDateTime.now());
}
@RequestMapping(value = "/order/getById" )
public OrderEntity getById(){
return orderClient.getOne(8L);
}
@RequestMapping(value = "/order/normal" )
public String normal(){
return orderClient.normal();
}
@RequestMapping(value = "/order/abnormal" )
public String abnormal(){
return orderClient.abnormal();
}
@RequestMapping(value = "/order/abnormalWithCallBack" )
public String abnormalWithCallBack(){
try {
String returnStr = orderClient.abnormalWithCallBack();
log.info("s:{}",returnStr);
return returnStr;
}catch (Exception e){
//FeignException
log.error("xxx",e);
return "catch Exception";
}
}
}
2.3.6 启动类上加上@EnableFeignClients、@EnableHystrixDashboard、@EnableCircuitBreaker。
2.4 整合zuul
新建gateway项目,依赖如下:
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
配置文件如下:
spring:
application:
name: gateway
server:
port: 8861
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
order:
path: /order/**
serviceId: order-service
# url: http://localhost:8763
user:
path: /user/**
serviceId: user-service
host:
connect-timeout-millis: 10000
socket-timeout-millis: 10000
order-service:
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
#超时配置说明:
#如果路由方式是serviceId的方式,配置为:zuul:routes:order:serviceId,那么ribbon的超时配置生效(order-service:ribbon:ReadTimeout)
#如果如果是url的方式,配置为:zuul:routes:order:url,则zuul.host开头的生效。
启动类加上注解@EnableZuulProxy开启zuul
到这里有几个超时时间要注意以下,一个是consumer的feign客户端超时时间,一个是consumer启用的hystrix的超时时间,还有一个是zuul的超时时间。只要其中一个超时了,就会触发fallback。
2.5 整合spring cloud cofig
先在git上准备一下环境,如下:
其中test内容为:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.101:3306/test?characterEncoding=utf8&useSSL=false
username: root
password: root
version: test
test-string: abcdefghijk
新建config-server项目:
依赖如下:
org.springframework.cloud
spring-cloud-config-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
配置文件如下:
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/pjypjy/learning-config #uri
search-paths: /** #配置文件目录
label: master
username: solider #github账号
password: 123456 #github密码
server:
port: 8862
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
启动类加上注解@EnableConfigServer
为order-service增加配置文件bootstrap.yml:
spring:
cloud:
config:
discovery:
enabled: true
service-id: config-server
profile: test
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
并且为order-service增加依赖:
mysql
mysql-connector-java
8.0.12
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.cloud
spring-cloud-starter-config
org.springframework.boot
spring-boot-starter-actuator
order-service增加请求处理:
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
Environment environment;
@RequestMapping(value = "/config/query")
public List
order-service配置文件修改:这里需暴露refresh
management:
endpoints:
web:
exposure:
include: ["refresh"]
然后先启动config-server,再重新启动order-service,测试一下,dataSource成功创建,可以查询出数据,然后访问/config/testString,得到字符串:abcdefghijk,然后我们将github上的test-string项改为:abc,访问/config/refresh,发送post请求让order-service重新拉取配置,然后再访问/config/testString可以得到:abc。
2.6 整合spring cloud stream
为order-service、user-service、message-service增加依赖:
org.springframework.cloud
spring-cloud-starter-stream-rabbit
配置文件也统一增加rabbitmq的配置信息:
order-service的部分配置如下:
spring:
application:
name: order-service
rabbitmq:
host: 192.168.1.102
port: 5672
username: root
password: root
virtual-host: test
cloud:
stream:
bindings:
myTest:
group: groupA
message-service的部分配置如下:
spring:
application:
name: message-service
rabbitmq:
host: 192.168.1.102
port: 5672
username: root
password: root
virtual-host: test
user-service部分配置如下:
spring:
application:
name: user-service
rabbitmq:
host: 192.168.1.102
port: 5672
username: root
password: root
virtual-host: test
首先是接收端:
为这order-service、message-service项目增加接收端:
public interface ReceiveService {
@Input("myTest")
SubscribableChannel receive();
}
启动类增加代码:
@StreamListener("myTest")
public void myReceive1(byte[] msg){
System.out.println("===receive:"+new String(msg));
}
启动类增加注解
@EnableBinding(value = {ReceiveService.class})
然后我们把user-service改造成发送端
为user-service增加发送端:
public interface ReceiveService {
@Input("myTest")
SubscribableChannel receive();
}
为user-service增加注解:
@EnableBinding(value = {SendService.class})
为user-service增加controller:
@RestController
public class UserController {
@Autowired
SendService sendService;
/**
* 1.如果在配置文件中未配置消费者组,系统会自动生成一个临时队列,连接断开,队列消失,队列名:myTest.anonymous.fsd15hu64....随机生成
* 2.如果配置了消费者组,则自动生成队列为: myTest.消费者组名称 (非临时队列)
* 3.默认发送消息,会向所有消费者组推送消息,如果一个有多个消费者 在 同一个消费者组里,消息会轮询发给这个组里的消费者
* @return
*/
@RequestMapping(value = "/mySend")
public boolean mySend(){
Message msg = MessageBuilder.withPayload("from: user-service to: order-service".getBytes()).build();
boolean send = sendService.send().send(msg);
return send;
}
}
访问/mySend可以看到message-service和order-service都能接收到消息,要注意,这里没有为message-service配置消费者组,springcloud stream会自动创建临时队列,用于order-service接收消息。order-service设置了消费者组则自动生成队列为: myTest.消费者组名称 (非临时队列)。消息会推送给所有消费者组,如果有多个项目都用了同一个消费者组(如groupA),消息将会轮询发送给这些项目。