整体图
1. 搭建Consul Server Cluster
REF sites:
https://github.com/hashicorp/consul
https://www.consul.io/downloads.html
过程略(ACL)
[root@hadoopnode3 ~]# consul members -http-addr=10.0.0.8:8500
Node Address Status Type Build Protocol DC Segment
node104 10.0.0.104:8301 alive server 1.4.4 2 dc1
node19 10.0.0.19:8301 alive server 1.4.4 2 dc1
node9 10.0.0.9:8301 alive server 1.4.4 2 dc1
node8 10.0.0.8:8301 alive client 1.4.4 2 dc1
2. 搭建基础maven parent projects in STS/eclipse
4.0.0
com.sc
spring-boot-cloud-consul
1.0-SNAPSHOT
pom
${project.artifactId}
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
consul-gateway
consul-auth-service
consul-service-a
consul-service-b
consul-common
UTF-8
1.8
1.0.0
spring-boot-cloud-consul
Finchley.SR1
2.0.4
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring.cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
repackage
com.spotify
docker-maven-plugin
${docker.plugin.version}
package
build
${docker.image.prefix}/${project.artifactId}
${project.basedir}/
/
${project.build.directory}
${project.build.finalName}.jar
3. OAuth2认证项目 - consul-auth-service
│ Dockerfile
│ pom.xml
├───src
│ ├───main
│ │ ├───java
│ │ │ └───com
│ │ │ └───sc
│ │ │ └───auth
│ │ │ │ AuthApplication.java
│ │ │ │
│ │ │ ├───config
│ │ │ │ OAuthConfiguration.java
│ │ │ │ ResourceServerConfiguration.java
│ │ │ │ WebSecurityConfigurer.java
│ │ │ │
│ │ │ ├───controller
│ │ │ │ UserController.java
│ │ │ │
│ │ │ ├───entity
│ │ │ │ Authority.java
│ │ │ │ OAuthUser.java
│ │ │ │ User.java
│ │ │ │
│ │ │ ├───jpa
│ │ │ │ AuthorityJPA.java
│ │ │ │ UserJPA.java
│ │ │ │
│ │ │ └───service
│ │ │ AuthUserDetailsService.java
│ │ │
│ │ └───resources
│ │ application.yml
│ │ bootstrap.yml
│ │ oauth2.sql
主要是提供oauth2 server side 认证. oauth2 信息包括用户表创建都在mysql, 参考oauth2.sql
参考配置:
//data init script - oauth_client_details
/**
clients.jdbc(dataSource).passwordEncoder(passwordEncoder)
.withClient("client").secret("secret")
.authorizedGrantTypes("password", "refresh_token").scopes("read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.and()
.withClient("service-a").secret("password")
.authorizedGrantTypes("client_credentials", "refresh_token").scopes("server")
.and()
.withClient("service-b").secret("password")
.authorizedGrantTypes("client_credentials", "refresh_token").scopes("server")
.and()
.withClient("client_code") // client_id
.secret("secret_code") // client_secret
.authorizedGrantTypes("authorization_code") // 该client允许的授权类型
.scopes("app"); // 允许的授权范围 ;
*/
4. SpringCloud Gateway 项目 - consul-gateway
用户限流, 负载平衡, 功能转向等, 主要是配置
Spring Cloud Consul Config- support dynamic updates
server:
port: 8900
spring:
#限流redis server
redis:
host: 10.0.0.119
port: 6379
database: 0
cloud:
gateway:
discovery:
locator:
# 是否用service name 自动发现
enabled: false
lowerCaseServiceId: true
default-filters:
#全局配置
# 熔断降级配置
- name: Hystrix
args:
name : default
fallbackUri: 'forward:/defaultfallback'
#RequestRateLimiter can not be global until 2.1.0
#https://github.com/spring-cloud/spring-cloud-gateway/pull/720
routes:
- id: auth-service
uri: lb://consul-auth-service
predicates:
- Path=/uaa/**
filters:
- StripPrefix=1
- id: service-a
uri: lb://consul-service-a
predicates:
- Path=/svca/**
filters:
- StripPrefix=1
# redis限流 , filter名称必须是RequestRateLimiter
- name: RequestRateLimiter
args:
# 使用SpEL名称引用Bean,与上面新建的RateLimiterConfig类中的bean的name相同
key-resolver: '#{@urlPathKeyResolver}'
# 每秒最大访问次数
redis-rate-limiter.replenishRate: 4
# 令牌桶最大容量
redis-rate-limiter.burstCapacity: 4
- id: svcb
uri: lb://consul-service-b
predicates:
- Path=/svcb/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallback
fallbackUri: 'forward:/fallback'
- id: rewritepath_route
uri: https://blog.csdn.net
predicates:
- Path=/csdn/**
filters:
- RewritePath=/csdn/(?.*), /$\{segment}
- id: limit_route
uri: http://www.baidu.com
predicates:
- Path=/baidu/**
- After=2019-04-10T08:00:00.001-07:00[America/Denver]
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@remoteAddrKeyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
sleuth:
sampler:
percentage: 1
probability: 1.0 # 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
integration:
enabled: false
scheduled:
skip-pattern: "^org.*HystrixStreamTask$"
web:
client:
enabled: true #web开启sleuth功能
zipkin:
base-url: http://${ZIPKIN-SERVER:zipkin-server}:9411
#totally customized vars
#not solved, https://github.com/spring-cloud/spring-cloud-security/issues/61
authserver:
#hostname: auth-service
hostname: AUTH-SERVICE
port: 8001
contextPath:
#https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/
security:
oauth2:
resource:
user-info-uri: http://${authserver.hostname}:${authserver.port}${authserver.contextPath}/current
#prefer-token-info: true # Use the token info, can be set to false to use the user info.
#HystrixDashboard
management:
endpoints:
web:
exposure:
#include: hystrix.stream, info, health
include: "*"
cors:
allowed-origins: "*"
allowed-methods: "*"
#base-path: actuator //by default
turbine:
app-config: consul-service-a,consul-service-b
aggregator:
clusterConfig: default
clusterNameExpression: new String("default")
combine-host: true
instanceUrlSuffix:
default: actuator/hystrix.stream
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #默认:(方法上记得要加上@HystrixCommand,否则无效)
# strategy: SEMAPHORE
feign:
hystrix:
enabled: true
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
#https://github.com/spring-cloud/spring-cloud-security/issues/61
http:
client:
enabled: true
logging:
level:
root: INFO
org.springframework: INFO
5. 具体子项目实现demo - consul-service-a&consul-service-b
重点是测试Feigh client and Ribbon client
FeignClient需要配置Oauth2 client信息传递
package com.sc.svca.client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "consul-service-b", fallback = FeignServiceBClient.ServiceBClientFallback.class)
public interface FeignServiceBClient {
@GetMapping(value = "/test")
String test();
@RequestMapping(value = "/hi",method = RequestMethod.GET)
String sayHiFromClientOne(@RequestParam(value = "name") String name);
//String getSessionId(@RequestHeader("X-Auth-Token") String token);
@Component
class ServiceBClientFallback implements FeignServiceBClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBClientFallback.class);
@Override
public String test() {
LOGGER.info("now flow into callback method...");
return "SERVICE B FAILED! - FALLING BACK";
}
@Override
public String sayHiFromClientOne(String name) {
return "SERVICE sayHiFromClientOne FAILED! - FALLING BACK - with parameter - name:" + name;
//SERVICE sayHiFromClientOne FAILED! - FALLING BACK - with parameter - name:abc
}
}
}
Ribbon Client也需要公用consul-common模块的oauth2 client配置
package com.sc.svca.service;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.sc.cmn.config.RestTemplateClient;
/**
RestTemplate(Oauth2 supported)+Ribbon
**/
@Service
public class RibbonUserServiceClient extends RestTemplateClient {
//@Autowired
//RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "userIdNotFound")
public String getCurrUserId(HttpServletRequest request) {
String userId = getRestObject("consul-auth-service", "/getCurrUserId", getHeaders(request), null, String.class);
return userId;
}
public String userIdNotFound(HttpServletRequest request) {
return "can not find any userId logged in!";
}
}
consul-service-a sample
package com.sc.svca;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import com.sc.cmn.config.FeignHystrixConcurrencyStrategy;
@EnableTurbine //Notes: 开启turbine,@EnableTurbine注解包含了@EnableDiscoveryClient注解
//@EnableTurbineStream # 不可共存
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
@EnableCircuitBreaker //Note: 如果不加,Dashboard无法接收到来自Feign内部断路器的监控数据
@EnableHystrix //Note: 开启断路器
//@EnableHystrixDashboard //Hystrix仪表板, server UI part, now using standalone version
@EnableOAuth2Client
@ComponentScan({"com.sc.cmn","com.sc.svca"})
public class ServiceAApplication {
/**
* http://localhost:8764/turbine.stream
* 访问地址 http://localhost:8762/actuator/hystrix.stream
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@Bean
public FeignHystrixConcurrencyStrategy feignHystrixConcurrencyStrategy() {
return new FeignHystrixConcurrencyStrategy();
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
Controller测试部分sample
package com.sc.svca.controller;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.sc.cmn.util.ResponseModel;
import com.sc.svca.client.FeignServiceBClient;
import com.sc.svca.service.RibbonServiceBClient;
import com.sc.svca.service.RibbonUserServiceClient;
/**
* curl -X POST -vu client:secret http://gateway-server:8900/uaa/oauth/token -H "Accept: application/json" -d "password=password&username=admin&grant_type=password&scope=read%20write"
*
*
*/
//Spring Cloud Consul Config support dynamic change, add @RefreshScope
@RefreshScope
@RestController
public class ServiceAController {
private static final Logger log = LoggerFactory.getLogger(ServiceAController.class);
//from application.yml config
@Value("${msg:unknown}")
private String msg;
//from consul config
@Value("${my.consul.val}")
String myConsulVal;
//@Autowired
//EurekaDiscoveryClient discoveryClient;
//replace eureka with consul
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private FeignServiceBClient serviceBClient;
@Autowired
private RibbonUserServiceClient userService;
@GetMapping(value = "/")
public String printServiceA() {
log.info(discoveryClient.getInstances("consul-service-a").toString());
ServiceInstance serviceInstance = discoveryClient.getInstances("consul-service-a").get(0);
return serviceInstance.getServiceId() + " (" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + ")"
+ "===>name:" + msg + "
";
//consul-service-a (10.0.0.63:8901)===>name:test_string
}
@Value("${server.port}")
String port;
@RequestMapping("/hi2")
public String home(@RequestParam(value = "name", defaultValue = "defName") String name) {
return "hi " + name + " ,i am from port:" + port + ", get consul config my.consul.val=" + myConsulVal;
/**
* dynamic config change on Consul
*
[root@hadoopnode4 sts]# curl -i -H "Authorization: Bearer 8cb5bd5b-86c7-4f06-991c-ccc358060bfc" http://10.0.0.8:8900/svca/hi2
hi defName ,i am from port:8901, get consul config my.consul.val=myVal
[updated on Consul console...]
[root@hadoopnode4 sts]# curl -i -H "Authorization: Bearer 8cb5bd5b-86c7-4f06-991c-ccc358060bfc" http://10.0.0.8:8900/svca/hi2
hi defName ,i am from port:8901, get consul config my.consul.val=myVal_new
*
*/
}
/**
* way 1 - feign fallback
*
* the name parameter must be provided when calling
*
* [root@hadoopnode4 ~]# curl -i -H "Authorization: Bearer
* a8914a80-c6c0-46ae-9828-2bb7c8cb521a" http://10.0.0.63:8080/hi?name=abc
*
* @param name
* @return
*/
@GetMapping(value = "/hi")
public String sayHi(@RequestParam String name) {
log.info("calling into svca-hi with name======>" + name);
return serviceBClient.sayHiFromClientOne(name);
}
/**
*
* [root@hadoopnode4 ~]# curl -i -H "Authorization: Bearer
* 1c76f4f5-cf95-428c-ae96-c45715c8f7ae" http://10.0.0.63:8901/test
* {"status":200,"msg":"�功","data":{},"perm":null}
*
* @return
*/
@GetMapping(value = "/test")
public String test() {
return serviceBClient.test();
}
@Autowired
RibbonServiceBClient serviceBService;
/**
* way 2 - ribbon fallback
*
* [root@hadoopnode4 ~]# curl -X POST -i -H "Authorization: Bearer
* 1c76f4f5-cf95-428c-ae96-c45715c8f7ae" http://10.0.0.63:8901/hitest -d
* 'name=zhou&b=2&c=3'
*
* {"status":200,"msg":"�功","data":{"b":"2","c":"3","name":"zhou"}}[root@hadoopnode4
* ~]#
*
* @return
*/
@RequestMapping(value = "/hitest", method = RequestMethod.POST)
public ResponseModel hiTest(HttpServletRequest request) {
// request.getParameter("name") //hib?name=abc
return serviceBService.hiService(request);
}
@RequestMapping(value = "/hib")
public String hiB(HttpServletRequest request) {
return serviceBService.hi(request);
}
@RequestMapping(value = "/getCurrUserId")
public String getCurrUserId(HttpServletRequest request) {
return userService.getCurrUserId(request);
}
@GetMapping(path = "/current")
public Principal getCurrentAccount(Principal principal) {
return principal;
}
@Autowired
private LoadBalancerClient loadBalancer;
/**
* 获取所有服务
*/
@RequestMapping("/services")
public Object services() {
// 获取对应服务名称的所有实例信息
return discoveryClient.getInstances("consul-service-b");
//[{"serviceId":"consul-a","host":"10.0.0.63","port":8201,"secure":false,"metadata":{"secure":"false"},"uri":"http://10.0.0.63:8201","scheme":null}]
}
@Autowired
RestTemplate restTemplate;
/**
* Demo for invalid call, without token passed
* error":"invalid_token
*
* @return
*/
@RequestMapping("/callB")
public String call() {
ServiceInstance serviceInstance = loadBalancer.choose("consul-service-b");
log.info("serviceInstance.getUri:" + serviceInstance.getUri());
log.info("serviceInstance.getServiceId:" + serviceInstance.getServiceId());
String callServiceResult = restTemplate.getForObject(serviceInstance.getUri().toString() + "/hi",
String.class);
log.info("callServiceResult:" + callServiceResult);
return callServiceResult;
}
}
基础pom配置
4.0.0
com.sc
spring-boot-cloud-consul
1.0-SNAPSHOT
consul-service-a
1.0-SNAPSHOT
jar
${project.artifactId}
com.sc
consul-common
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.cloud
spring-cloud-starter-consul-config
org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-sleuth
org.springframework.cloud
spring-cloud-starter-zipkin
org.springframework.cloud
spring-cloud-starter-netflix-turbine
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-test
test
${project.artifactId}
org.springframework.boot
spring-boot-maven-plugin
连接注册consul部分yml配置
# document: https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/
security:
oauth2:
client:
clientId: service-a #clientId from OAUTH2 definition in db
clientSecret: ${SVCA_CLIENT_SECRET_OAUTH2:password}
accessTokenUri: http://${authserver.hostname}:${authserver.port}${authserver.contextPath}/oauth/token
grant-type: client_credentials,password
scope: server
spring:
profiles:
active: consul-dev #consul 配置key profile
application:
name: consul-service-a
cloud:
consul:
host: hadoopnode3
port: 8500
discovery:
serviceName: consul-service-a
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port} #实例ID
health-check-path: /actuator/health #健康检查
health-check-interval: 10s
prefer-ip-address: true
#ip-address: 10.0.0.63
#register-health-check: true
#health-check-critical-timeout: 2m
token: ${SPRING_CLOUD_CONSUL_TOKEN:245d0a09-7139-bbea-aadc-ff170a0562b1}
config:
enabled: true #设置config是否启用,默认为true
format: yaml #设置配置的值的格式,可以yaml和properties
prefix: config #目录,比如config
profile-separator: ':' #配置分隔符,默认为‘,’
data-key: data #为应用配置的key名字,值为整个应用配置的字符串
#defaultContext 设置默认的配置,被所有的应用读取,本例子没用的
6. Zipkin Server
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=mypass --MYSQL_HOST=10.0.0.8 --MYSQL_TCP_PORT=3306
https://zipkin.io/pages/architecture.html?from=singlemessage&isappinstalled=0
看到 每个call的时间及轨迹
7. Hystrix Dashboard
感受实时统计
https://github.com/kennedyoliveira/standalone-hystrix-dashboard
[root@hadoopnode4 sts]# java -jar standalone-hystrix-dashboard-1.5.6-all.jar
http://hadoopnode4:7979/hystrix-dashboard/
7. 结束语
https://github.com/dong099/spring-cloud-enterprise-consul