++==(純手打,代碼可能有錯!)==++nginx
RestTemplate restTemplate = new RestTemplate(); String responseStr = restTemplate.getForObject(「http://localhost:8080/msg」,String.class);
@Autowired private LoadBalancerClient loadBalancerClient;
RestTemplate restTemplate = new RestTemplate(); ServiceInstance serviceInstance = loadBalancerClient.choose(「PRODUCT」); String url = String.format(「http://%s:%s」, serviceInstance.getHost, serviceInstance.getPort()); String responseStr = restTemplate.getForObject(url, String.class);
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
String responseStr = restTemplate.getForObject(「http://PRODUCT/msg」, String.class);
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
@EnableFeignClients
// product對應的是服務名 @FeignClient(name="product") public interface ProductClient{ @GetMapping("/msg") String productMsg(); }
而後,在須要調用"/msg"接口的controller中這樣使用:git
@Autowired private ProductClient productClient;
或添加其餘服務的pom直接訪問redis
當要修改上線項目的一些配置、文案的時候,爲了避免用重複發行版本,只作一些小修改,就能夠用統一配置中心。算法
<!-- 引入cloud依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<!-- 引入cloud配置中心服務端依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <!-- 引入Eureka客戶端依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
spring: application: name: config-server cloud: config: server: git: # 配置文件只搜索url目錄下的searchPaths uri: git@gitee.com:szliugx/spring_cloud_config.git # 指定搜索路徑,若是有多個路徑則使用,分隔 searchPaths: infomation/ # 對於使用git,svn作爲後端配置,從遠程庫獲取配置文件,須要存儲到本地文件 basedir: /tmp/spring-cloud-repo # 配置中心經過git從遠程git庫,有時本地的拷貝被污染,這時配置中心沒法從遠程庫更新本地配置,設置force-pull=true,則強制從遠程庫中更新本地庫 force-pull: true #username: username #password: password #服務註冊中心端口號 server: port: 6130 #服務註冊中心實例的主機名、端口 #是否向服務註冊中心註冊本身 #是否檢索服務 #服務註冊中心的配置內容,指定服務註冊中心的位置 eureka: port: instance: hostname: localhost client: register-with-eureka: true fetch-registry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${eureka.port}/eureka/
@SpringBoorApplication @EnableConfigServer @EnableEurekaClient public class ConfigServerApplication { public static void main(String[] args){SpringApplication.run(ConfigServerApplication.class,args);} }
最後,爲每一個服務添加一個屬於本身的配置文件在碼雲上
注意遠程配置的命名,如 user-dev.properties,服務名 -環境.properties/服務名 -環境.yml
/{name}-{profiles}.yml
/{label}/{name}-{profiles}.yml
Name: 服務名
Profiles:環境
Label:分支(branch)spring
配置.YML文件,開放全部的接口;
使用註解:@RefreshScope數據庫
例如,用戶註冊後,須要發短信和加積分。用戶信息註冊寫入數據庫後,通知異步消息,通知短信服務和積分服務作事情。json
Spring-boot-starter-amqp
;rabbbitmq: host: localhost port: 5671 username: guest password: guest
/** * 水果商供應商服務 接收消息 * @param message */ @RabbitListener(bindings = @QueueBinding( exchange = @Exchange("myOrder"), key = "fruit", value = @Queue("fruitOrder") )) public void processFruit(String message){ log.info("fruit MqReceiver: {}",message); }
public class MqReceiver{ // 1.@RabbitListener(queues = "myQueue") // 2.自動建立隊列 @RabbitListener(queuesToDeclare = @Queue("myQueue")) public void process(String message){ log.info("MqReceiver: {}",message); } }
發送端:參數有(exchange、routingKey、message)後端
@Component public class MqSenderTest extends OrderApplicationTests { @Autowired private AmqpTemplate amqpTemplate; @Test public void send(){ amqpTemplate.converAndSend("myQueue", "now"); } @Test public void sendOrder(){ amqpTemplate.convertAndSend("myOrder", "computer"); } }
官方定義 Spring Cloud Stream 是一個構建消息驅動微服務的框架
應用程序經過 inputs 或者 outputs 來與 Spring Cloud Stream 中binder 交互,經過咱們配置來 binding ,而 Spring Cloud Stream 的 binder 負責與消息中間件交互。因此,咱們只須要搞清楚如何與 Spring Cloud Stream 交互就能夠方便使用消息驅動的方式。跨域
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
rabbbitmq: host: localhost port: 5671 username: guest password: guest
public interface StreamClient{ String INPUT = "myMessage"; @Input(StreamClient.INPUT) SubscribableChannel input(); @Output(StreamClient.INPUT) MessageChannel output(); }
消息接收端:
@Component @EnableBinding(StreamClient.class) @Slf4j public class StreamReceiver{ @StreamListener(StreamClient.INPUT) public void process(Object message){ log.info("StreamReceiver: {}",message); } }
消息發送端:
@RestController public class SendMessageController{ @Autowired private StreamClient streamClient; @GetMapping("/sendMessage") public void process(){ String message = "now" + new Date(); streamClient.output().send(MessageBuilder.withPayload(message).build()); } }
消息分組:
把一個服務放到一個組裏面,無論這個服務有多少個實例,只會由一個實例來處理一個消息。
spring: application: name: order cloud: config: discovery: enabled: true service-id: CONFIG profile: test stream: bindings: # 隊列名稱 myMessage: # 組名稱 group: order
爲了在MQ界面裏面看到消息中java對象的具體內容,加一個配置:
spring: application: name: order cloud: config: discovery: enabled: true service-id: CONFIG profile: test stream: bindings: # 隊列名稱 myMessage: # 組名稱 group: order # 設置消息內容類型(可查看java對象屬性) content-type: application/json
效果以下:
處理完消息以後,若是須要回應一下:
public interface StreamClient { String INPUT = "myMessage"; String INPUT2 = "myMessage2"; @Input(StreamClient.INPUT) SubscribableChannel input(); @Output(StreamClient.INPUT) MessageChannel output(); @Input(StreamClient.INPUT2) SubscribableChannel input2(); @Output(StreamClient.INPUT2) MessageChannel output2(); }
@StreamListener(value = StreamClient.INPUT) @SendTo(StreamClient.INPUT) public String process(OrderDTO message){ log.info("StreamReceiver: {}",message); return "received"; } @StreamListener(value = StreamClient.INPU2T) public String process2(OrderDTO message){ log.info("StreamReceiver2: {}",message); return "received"; }
異步扣庫存分析
訂單生成的時候(此時訂單狀態爲等待中),向MQ發送信息,通知商品服務扣除庫存,商品服務不論成功仍是失敗,都要返回結果給訂單服務。訂單服務訂閱了商品服務,根據返回的信息,以爲這個訂單是否生成。
***
建議使用Nginx和zuul混搭的方式,使用nginx對外暴露一個URL,nginx把請求轉發到多個zuul服務上,nginx繼續作負載均衡,這樣能夠作到nginx和zuul的取長補短。
常見的網關方案:
Nginx+Lua
Spring Cloud Zuul
路由+過濾器 = Zuul
Zuul核心是一系列的過濾器
四種標準過濾器類型:
要實現路由轉發功能要加註解:@EnableZuulProxy
訪問路徑控制:(自定義、限制訪問)
zuul: routes: # /myProduct/product/list -> /product/product/list # product: # path: /myProduct/** # serviceId: product # 簡潔寫法 product: /myProduct/** # 排除某些路由 ignored-patterns: - /**/product/listForProduct
Cookie轉發(默認是不能傳cookie的):
默認限制了cookie、set-cookie、authorization的轉發:
該字段設置爲空便可放開:
zuul: routes: # /myProduct/product/list -> /product/product/list product: path: /myProduct/** serviceId: product # 設置爲空便可 sensitiveHeaders: # 簡潔寫法 product: /myProduct/** # 排除某些路由 ignored-patterns: - /**/product/listForProduct
Zuul過濾器:
自定義preFilter:
@Component public class TokenFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); //這裏從url參數裏獲取, 也能夠從cookie, header裏獲取 String token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; } }
自定義postFilter:
@Component public class AddResponseHeaderFilter extends ZuulFilter{ @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletResponse response = requestContext.getResponse(); response.setHeader("X-Foo", UUID.randomUUID().toString()); return null; } }
令牌桶限流(訪問攔截):
@Component public class RateLimitFilter extends ZuulFilter{ //Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令牌桶算法實現流量限制,使用十分方便,並且十分高效。 private static final RateLimiter RATE_LIMITER = RateLimiter.create(100); @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return SERVLET_DETECTION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { //若是沒有令牌,拋出異常 if(!RATE_LIMITER.tryAcquire()){ throw new RateLimitException(); } return null; } }
微信商城中買家端和賣家端登陸以後的區別:
買家端登陸以後往cookie裏寫了一個openid=」openid」;
賣家端登陸以後,在cookie中存了一個token="redisKey",這個redisKey通常是UUID,redis中存了一個redisKey="openid"
權限攔截:
例子:買家完成訂單
public class AuthSellerFilter extends ZuulFilter{ @Autowired private StringRedisTemplate stringRedisTemplate; @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return PRE_DETECTION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); if("/order/order/finish".equals(request.getRequest())){ return true } return false; } @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); // /order/finish 只能賣家訪問(cookie裏面有token,而且對應redis中的值) Cookie cookie = CookieUtil.get(request,"token"); if(cookie == null || StringUtils.isEmpty(cookie.getValue()) || StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE, cookie.getValue())))){ requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; } }
網關配置全部服務均可以傳遞cookie:
zuul: # 所有服務忽略敏感頭 sensitive-headers: routes:
Zuul跨域:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.setAllowedOrgins(Arrays.asList("*")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowedMethods(Arrays.asList("*")); config.setMaxAge(300l); source.registerCorsConfiguration("/**",config); return new CorsFilter(source); } }
鏈路監控
Spring Cloud Sleuth
Spring Cloud Sleuth是Spring Cloud提供的分佈式系統服務鏈追蹤組件。
一個請求可能會通過多個服務纔會獲得結果,若是在這個過程當中出現了異常,就很難去定位問題。因此,必需要實現一個分佈式鏈路跟蹤的功能,直觀的展現出完整的調用過程。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
sleuth: sampler: # 1表示100%,全部日誌都發送到外部程序展現,會消耗帶寬等資源,只能在開發中使用 percentage: 1
Zipkin
官網有安裝方法:https://zipkin.io/pages/quickstart
重要概念:
TraceId: 全局跟蹤ID,是跟蹤的入口點。
SpanId: 下一層請求ID。一個traceId包含1個以上的spanId。
ParentId: 上一層請求跟蹤ID,用來將先後的請求串聯起來
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-zipkin</artifactId> </dependency>
由於zipkin和sleuth須要一塊兒使用,因此都須要導入依賴:
<!--<dependency>--> <!--<groupId>org.springframework.cloud</groupId>--> <!--<artifactId>spring-cloud-starter-sleuth</artifactId>--> <!--</dependency>--> <!--<dependency>--> <!--<groupId>org.springframework.cloud</groupId>--> <!--<artifactId>spring-cloud-sleuth-zipkin</artifactId>--> <!--</dependency>--> <!-- 包含sleuth和zipkin --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
zipkin: base-url: http://localhost:9411/ sleuth: sampler: percentage: 1