Spring Cloud入门操作手册(Greenwich)

雨点打透心脏的1/2处 2023-06-19 02:20 106阅读 0赞

这是旧版本,新版本请看下面链接

https://blog.csdn.net/weixin_38305440/article/details/102775484

spring cloud 介绍

spring cloud 是一系列框架的集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。spring cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 spring boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

spring cloud 对于中小型互联网公司来说是一种福音,因为这类公司往往没有实力或者没有足够的资金投入去开发自己的分布式系统基础设施,使用 spring cloud 一站式解决方案能在从容应对业务发展的同时大大减少开发成本。同时,随着近几年微服务架构和 docker 容器概念的火爆,也会让 spring cloud 在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在目前五花八门的分布式解决方案中提供了标准化的、一站式的技术方案,意义可能会堪比当年 servlet 规范的诞生,有效推进服务端软件系统技术水平的进步。

spring cloud 技术组成

  • eureka
    微服务治理,服务注册和发现
  • ribbon
    负载均衡、请求重试
  • hystrix
    断路器,服务降级、熔断
  • feign
    ribbon + hystrix 集成,并提供生命式客户端
  • hystrix dashboard 和 turbine
    hystrix 微服务监控
  • zuul
    API 网关,提供微服务的统一入口,并提供统一的权限验证
  • config
    配置中心
  • bus
    消息总线, 配置刷新
  • sleuth+zipkin
    链路跟踪

Spring Cloud 对比 Dubbo

  • Dubbo

    • Dubbo只是一个远程调用(RPC)框架
    • 默认基于长连接,支持多种序列化格式
  • Spring Cloud

    • 框架集
    • 提供了一整套微服务解决方案(全家桶)

一、service - 服务

三个服务

  • 商品服务 item service,端口 8001
  • 用户服务 user service,端口 8101
  • 订单服务 order service,端口 8201

二、commons 通用项目

新建 maven 项目

新建commons通用项目

pom.xml

  1. <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">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>com.tedu</groupId>
  4. <artifactId>sp01-commons</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <name>sp01-commons</name>
  7. <dependencies>
  8. <dependency>
  9. <groupId>com.fasterxml.jackson.module</groupId>
  10. <artifactId>jackson-module-parameter-names</artifactId>
  11. <version>2.9.8</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>com.fasterxml.jackson.datatype</groupId>
  15. <artifactId>jackson-datatype-jdk8</artifactId>
  16. <version>2.9.8</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>com.fasterxml.jackson.datatype</groupId>
  20. <artifactId>jackson-datatype-jsr310</artifactId>
  21. <version>2.9.8</version>
  22. </dependency>
  23. <dependency>
  24. <groupId>com.fasterxml.jackson.datatype</groupId>
  25. <artifactId>jackson-datatype-guava</artifactId>
  26. <version>2.9.8</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.projectlombok</groupId>
  30. <artifactId>lombok</artifactId>
  31. <version>1.18.6</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>javax.servlet</groupId>
  35. <artifactId>javax.servlet-api</artifactId>
  36. <version>3.1.0</version>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.slf4j</groupId>
  40. <artifactId>slf4j-api</artifactId>
  41. <version>1.7.26</version>
  42. </dependency>
  43. <dependency>
  44. <groupId>org.apache.commons</groupId>
  45. <artifactId>commons-lang3</artifactId>
  46. <version>3.9</version>
  47. </dependency>
  48. </dependencies>
  49. <build>
  50. <plugins>
  51. <plugin>
  52. <groupId>org.apache.maven.plugins</groupId>
  53. <artifactId>maven-compiler-plugin</artifactId>
  54. <version>3.8.0</version>
  55. <configuration>
  56. <source>1.8</source>
  57. <target>1.8</target>
  58. </configuration>
  59. </plugin>
  60. </plugins>
  61. </build>
  62. </project>

java 源文件

java源文件

pojo

Item

  1. package com.tedu.sp01.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class Item {
  9. private Integer id;
  10. private String name;
  11. private Integer number;
  12. }

User

  1. package com.tedu.sp01.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class User {
  9. private Integer id;
  10. private String username;
  11. private String password;
  12. }

Order

  1. package com.tedu.sp01.pojo;
  2. import java.util.List;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. @Data
  7. @NoArgsConstructor
  8. @AllArgsConstructor
  9. public class Order {
  10. private String id;
  11. private User user;
  12. private List<Item> items;
  13. }

service

ItemService

  1. package com.tedu.sp01.service;
  2. import java.util.List;
  3. import com.tedu.sp01.pojo.Item;
  4. public interface ItemService {
  5. List<Item> getItems(String orderId);
  6. void decreaseNumbers(List<Item> list);
  7. }

UserService

  1. package com.tedu.sp01.service;
  2. import com.tedu.sp01.pojo.User;
  3. public interface UserService {
  4. User getUser(Integer id);
  5. void addScore(Integer id, Integer score);
  6. }

OrderService

  1. package com.tedu.sp01.service;
  2. import com.tedu.sp01.pojo.Order;
  3. public interface OrderService {
  4. Order getOrder(String orderId);
  5. void addOrder(Order order);
  6. }

util

CookieUtil

  1. package com.tedu.web.util;
  2. import javax.servlet.http.Cookie;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. public class CookieUtil {
  6. /** * @param response * @param name * @param value * @param maxAge */
  7. public static void setCookie(HttpServletResponse response,
  8. String name, String value, String domain, String path, int maxAge) {
  9. Cookie cookie = new Cookie(name, value);
  10. if(domain != null) {
  11. cookie.setDomain(domain);
  12. }
  13. cookie.setPath(path);
  14. cookie.setMaxAge(maxAge);
  15. response.addCookie(cookie);
  16. }
  17. public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
  18. setCookie(response, name, value, null, "/", maxAge);
  19. }
  20. public static void setCookie(HttpServletResponse response, String name, String value) {
  21. setCookie(response, name, value, null, "/", 3600);
  22. }
  23. public static void setCookie(HttpServletResponse response, String name) {
  24. setCookie(response, name, "", null, "/", 3600);
  25. }
  26. /** * @param request * @param name * @return */
  27. public static String getCookie(HttpServletRequest request, String name) {
  28. String value = null;
  29. Cookie[] cookies = request.getCookies();
  30. if (null != cookies) {
  31. for (Cookie cookie : cookies) {
  32. if (cookie.getName().equals(name)) {
  33. value = cookie.getValue();
  34. }
  35. }
  36. }
  37. return value;
  38. }
  39. /** * @param response * @param name * @return */
  40. public static void removeCookie(HttpServletResponse response, String name, String domain, String path) {
  41. setCookie(response, name, "", domain, path, 0);
  42. }
  43. }

JsonUtil

  1. package com.tedu.web.util;
  2. import java.io.File;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import java.io.Writer;
  8. import java.math.BigDecimal;
  9. import java.math.BigInteger;
  10. import java.net.URL;
  11. import java.nio.charset.StandardCharsets;
  12. import java.text.SimpleDateFormat;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. import org.apache.commons.lang3.StringUtils;
  16. import com.fasterxml.jackson.annotation.JsonInclude;
  17. import com.fasterxml.jackson.core.JsonGenerator;
  18. import com.fasterxml.jackson.core.JsonParser;
  19. import com.fasterxml.jackson.core.JsonProcessingException;
  20. import com.fasterxml.jackson.core.type.TypeReference;
  21. import com.fasterxml.jackson.databind.DeserializationFeature;
  22. import com.fasterxml.jackson.databind.JsonNode;
  23. import com.fasterxml.jackson.databind.ObjectMapper;
  24. import com.fasterxml.jackson.databind.SerializationFeature;
  25. import com.fasterxml.jackson.databind.node.ObjectNode;
  26. import com.fasterxml.jackson.datatype.guava.GuavaModule;
  27. import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
  28. import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
  29. import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
  30. import lombok.extern.slf4j.Slf4j;
  31. @Slf4j
  32. public class JsonUtil {
  33. private static ObjectMapper mapper;
  34. private static JsonInclude.Include DEFAULT_PROPERTY_INCLUSION = JsonInclude.Include.NON_DEFAULT;
  35. private static boolean IS_ENABLE_INDENT_OUTPUT = false;
  36. private static String CSV_DEFAULT_COLUMN_SEPARATOR = ",";
  37. static {
  38. try {
  39. initMapper();
  40. configPropertyInclusion();
  41. configIndentOutput();
  42. configCommon();
  43. } catch (Exception e) {
  44. log.error("jackson config error", e);
  45. }
  46. }
  47. private static void initMapper() {
  48. mapper = new ObjectMapper();
  49. }
  50. private static void configCommon() {
  51. config(mapper);
  52. }
  53. private static void configPropertyInclusion() {
  54. mapper.setSerializationInclusion(DEFAULT_PROPERTY_INCLUSION);
  55. }
  56. private static void configIndentOutput() {
  57. mapper.configure(SerializationFeature.INDENT_OUTPUT, IS_ENABLE_INDENT_OUTPUT);
  58. }
  59. private static void config(ObjectMapper objectMapper) {
  60. objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
  61. objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
  62. objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
  63. objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
  64. objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
  65. objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  66. objectMapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
  67. objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
  68. objectMapper.enable(JsonParser.Feature.ALLOW_COMMENTS);
  69. objectMapper.disable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
  70. objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
  71. objectMapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
  72. objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  73. objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
  74. objectMapper.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
  75. objectMapper.registerModule(new ParameterNamesModule());
  76. objectMapper.registerModule(new Jdk8Module());
  77. objectMapper.registerModule(new JavaTimeModule());
  78. objectMapper.registerModule(new GuavaModule());
  79. }
  80. public static void setSerializationInclusion(JsonInclude.Include inclusion) {
  81. DEFAULT_PROPERTY_INCLUSION = inclusion;
  82. configPropertyInclusion();
  83. }
  84. public static void setIndentOutput(boolean isEnable) {
  85. IS_ENABLE_INDENT_OUTPUT = isEnable;
  86. configIndentOutput();
  87. }
  88. public static <V> V from(URL url, Class<V> c) {
  89. try {
  90. return mapper.readValue(url, c);
  91. } catch (IOException e) {
  92. log.error("jackson from error, url: {}, type: {}", url.getPath(), c, e);
  93. return null;
  94. }
  95. }
  96. public static <V> V from(InputStream inputStream, Class<V> c) {
  97. try {
  98. return mapper.readValue(inputStream, c);
  99. } catch (IOException e) {
  100. log.error("jackson from error, type: {}", c, e);
  101. return null;
  102. }
  103. }
  104. public static <V> V from(File file, Class<V> c) {
  105. try {
  106. return mapper.readValue(file, c);
  107. } catch (IOException e) {
  108. log.error("jackson from error, file path: {}, type: {}", file.getPath(), c, e);
  109. return null;
  110. }
  111. }
  112. public static <V> V from(Object jsonObj, Class<V> c) {
  113. try {
  114. return mapper.readValue(jsonObj.toString(), c);
  115. } catch (IOException e) {
  116. log.error("jackson from error, json: {}, type: {}", jsonObj.toString(), c, e);
  117. return null;
  118. }
  119. }
  120. public static <V> V from(String json, Class<V> c) {
  121. try {
  122. return mapper.readValue(json, c);
  123. } catch (IOException e) {
  124. log.error("jackson from error, json: {}, type: {}", json, c, e);
  125. return null;
  126. }
  127. }
  128. public static <V> V from(URL url, TypeReference<V> type) {
  129. try {
  130. return mapper.readValue(url, type);
  131. } catch (IOException e) {
  132. log.error("jackson from error, url: {}, type: {}", url.getPath(), type, e);
  133. return null;
  134. }
  135. }
  136. public static <V> V from(InputStream inputStream, TypeReference<V> type) {
  137. try {
  138. return mapper.readValue(inputStream, type);
  139. } catch (IOException e) {
  140. log.error("jackson from error, type: {}", type, e);
  141. return null;
  142. }
  143. }
  144. public static <V> V from(File file, TypeReference<V> type) {
  145. try {
  146. return mapper.readValue(file, type);
  147. } catch (IOException e) {
  148. log.error("jackson from error, file path: {}, type: {}", file.getPath(), type, e);
  149. return null;
  150. }
  151. }
  152. public static <V> V from(Object jsonObj, TypeReference<V> type) {
  153. try {
  154. return mapper.readValue(jsonObj.toString(), type);
  155. } catch (IOException e) {
  156. log.error("jackson from error, json: {}, type: {}", jsonObj.toString(), type, e);
  157. return null;
  158. }
  159. }
  160. public static <V> V from(String json, TypeReference<V> type) {
  161. try {
  162. return mapper.readValue(json, type);
  163. } catch (IOException e) {
  164. log.error("jackson from error, json: {}, type: {}", json, type, e);
  165. return null;
  166. }
  167. }
  168. public static <V> String to(List<V> list) {
  169. try {
  170. return mapper.writeValueAsString(list);
  171. } catch (JsonProcessingException e) {
  172. log.error("jackson to error, obj: {}", list, e);
  173. return null;
  174. }
  175. }
  176. public static <V> String to(V v) {
  177. try {
  178. return mapper.writeValueAsString(v);
  179. } catch (JsonProcessingException e) {
  180. log.error("jackson to error, obj: {}", v, e);
  181. return null;
  182. }
  183. }
  184. public static <V> void toFile(String path, List<V> list) {
  185. try (Writer writer = new FileWriter(new File(path), true)) {
  186. mapper.writer().writeValues(writer).writeAll(list);
  187. writer.flush();
  188. } catch (Exception e) {
  189. log.error("jackson to file error, path: {}, list: {}", path, list, e);
  190. }
  191. }
  192. public static <V> void toFile(String path, V v) {
  193. try (Writer writer = new FileWriter(new File(path), true)) {
  194. mapper.writer().writeValues(writer).write(v);
  195. writer.flush();
  196. } catch (Exception e) {
  197. log.error("jackson to file error, path: {}, obj: {}", path, v, e);
  198. }
  199. }
  200. public static String getString(String json, String key) {
  201. if (StringUtils.isEmpty(json)) {
  202. return null;
  203. }
  204. try {
  205. JsonNode node = mapper.readTree(json);
  206. if (null != node) {
  207. return node.get(key).toString();
  208. } else {
  209. return null;
  210. }
  211. } catch (IOException e) {
  212. log.error("jackson get string error, json: {}, key: {}", json, key, e);
  213. return null;
  214. }
  215. }
  216. public static Integer getInt(String json, String key) {
  217. if (StringUtils.isEmpty(json)) {
  218. return null;
  219. }
  220. try {
  221. JsonNode node = mapper.readTree(json);
  222. if (null != node) {
  223. return node.get(key).intValue();
  224. } else {
  225. return null;
  226. }
  227. } catch (IOException e) {
  228. log.error("jackson get int error, json: {}, key: {}", json, key, e);
  229. return null;
  230. }
  231. }
  232. public static Long getLong(String json, String key) {
  233. if (StringUtils.isEmpty(json)) {
  234. return null;
  235. }
  236. try {
  237. JsonNode node = mapper.readTree(json);
  238. if (null != node) {
  239. return node.get(key).longValue();
  240. } else {
  241. return null;
  242. }
  243. } catch (IOException e) {
  244. log.error("jackson get long error, json: {}, key: {}", json, key, e);
  245. return null;
  246. }
  247. }
  248. public static Double getDouble(String json, String key) {
  249. if (StringUtils.isEmpty(json)) {
  250. return null;
  251. }
  252. try {
  253. JsonNode node = mapper.readTree(json);
  254. if (null != node) {
  255. return node.get(key).doubleValue();
  256. } else {
  257. return null;
  258. }
  259. } catch (IOException e) {
  260. log.error("jackson get double error, json: {}, key: {}", json, key, e);
  261. return null;
  262. }
  263. }
  264. public static BigInteger getBigInteger(String json, String key) {
  265. if (StringUtils.isEmpty(json)) {
  266. return new BigInteger(String.valueOf(0.00));
  267. }
  268. try {
  269. JsonNode node = mapper.readTree(json);
  270. if (null != node) {
  271. return node.get(key).bigIntegerValue();
  272. } else {
  273. return null;
  274. }
  275. } catch (IOException e) {
  276. log.error("jackson get biginteger error, json: {}, key: {}", json, key, e);
  277. return null;
  278. }
  279. }
  280. public static BigDecimal getBigDecimal(String json, String key) {
  281. if (StringUtils.isEmpty(json)) {
  282. return null;
  283. }
  284. try {
  285. JsonNode node = mapper.readTree(json);
  286. if (null != node) {
  287. return node.get(key).decimalValue();
  288. } else {
  289. return null;
  290. }
  291. } catch (IOException e) {
  292. log.error("jackson get bigdecimal error, json: {}, key: {}", json, key, e);
  293. return null;
  294. }
  295. }
  296. public static boolean getBoolean(String json, String key) {
  297. if (StringUtils.isEmpty(json)) {
  298. return false;
  299. }
  300. try {
  301. JsonNode node = mapper.readTree(json);
  302. if (null != node) {
  303. return node.get(key).booleanValue();
  304. } else {
  305. return false;
  306. }
  307. } catch (IOException e) {
  308. log.error("jackson get boolean error, json: {}, key: {}", json, key, e);
  309. return false;
  310. }
  311. }
  312. public static byte[] getByte(String json, String key) {
  313. if (StringUtils.isEmpty(json)) {
  314. return null;
  315. }
  316. try {
  317. JsonNode node = mapper.readTree(json);
  318. if (null != node) {
  319. return node.get(key).binaryValue();
  320. } else {
  321. return null;
  322. }
  323. } catch (IOException e) {
  324. log.error("jackson get byte error, json: {}, key: {}", json, key, e);
  325. return null;
  326. }
  327. }
  328. public static <T> ArrayList<T> getList(String json, String key) {
  329. if (StringUtils.isEmpty(json)) {
  330. return null;
  331. }
  332. String string = getString(json, key);
  333. return from(string, new TypeReference<ArrayList<T>>() { });
  334. }
  335. public static <T> String add(String json, String key, T value) {
  336. try {
  337. JsonNode node = mapper.readTree(json);
  338. add(node, key, value);
  339. return node.toString();
  340. } catch (IOException e) {
  341. log.error("jackson add error, json: {}, key: {}, value: {}", json, key, value, e);
  342. return json;
  343. }
  344. }
  345. private static <T> void add(JsonNode jsonNode, String key, T value) {
  346. if (value instanceof String) {
  347. ((ObjectNode) jsonNode).put(key, (String) value);
  348. } else if (value instanceof Short) {
  349. ((ObjectNode) jsonNode).put(key, (Short) value);
  350. } else if (value instanceof Integer) {
  351. ((ObjectNode) jsonNode).put(key, (Integer) value);
  352. } else if (value instanceof Long) {
  353. ((ObjectNode) jsonNode).put(key, (Long) value);
  354. } else if (value instanceof Float) {
  355. ((ObjectNode) jsonNode).put(key, (Float) value);
  356. } else if (value instanceof Double) {
  357. ((ObjectNode) jsonNode).put(key, (Double) value);
  358. } else if (value instanceof BigDecimal) {
  359. ((ObjectNode) jsonNode).put(key, (BigDecimal) value);
  360. } else if (value instanceof BigInteger) {
  361. ((ObjectNode) jsonNode).put(key, (BigInteger) value);
  362. } else if (value instanceof Boolean) {
  363. ((ObjectNode) jsonNode).put(key, (Boolean) value);
  364. } else if (value instanceof byte[]) {
  365. ((ObjectNode) jsonNode).put(key, (byte[]) value);
  366. } else {
  367. ((ObjectNode) jsonNode).put(key, to(value));
  368. }
  369. }
  370. public static String remove(String json, String key) {
  371. try {
  372. JsonNode node = mapper.readTree(json);
  373. ((ObjectNode) node).remove(key);
  374. return node.toString();
  375. } catch (IOException e) {
  376. log.error("jackson remove error, json: {}, key: {}", json, key, e);
  377. return json;
  378. }
  379. }
  380. public static <T> String update(String json, String key, T value) {
  381. try {
  382. JsonNode node = mapper.readTree(json);
  383. ((ObjectNode) node).remove(key);
  384. add(node, key, value);
  385. return node.toString();
  386. } catch (IOException e) {
  387. log.error("jackson update error, json: {}, key: {}, value: {}", json, key, value, e);
  388. return json;
  389. }
  390. }
  391. public static String format(String json) {
  392. try {
  393. JsonNode node = mapper.readTree(json);
  394. return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
  395. } catch (IOException e) {
  396. log.error("jackson format json error, json: {}", json, e);
  397. return json;
  398. }
  399. }
  400. public static boolean isJson(String json) {
  401. try {
  402. mapper.readTree(json);
  403. return true;
  404. } catch (Exception e) {
  405. log.error("jackson check json error, json: {}", json, e);
  406. return false;
  407. }
  408. }
  409. private static InputStream getResourceStream(String name) {
  410. return JsonUtil.class.getClassLoader().getResourceAsStream(name);
  411. }
  412. private static InputStreamReader getResourceReader(InputStream inputStream) {
  413. if (null == inputStream) {
  414. return null;
  415. }
  416. return new InputStreamReader(inputStream, StandardCharsets.UTF_8);
  417. }
  418. }

JsonResult

  1. package com.tedu.web.util;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. @Getter
  5. @Setter
  6. public class JsonResult<T> {
  7. /** 成功 */
  8. public static final int SUCCESS = 200;
  9. /** 没有登录 */
  10. public static final int NOT_LOGIN = 400;
  11. /** 发生异常 */
  12. public static final int EXCEPTION = 401;
  13. /** 系统错误 */
  14. public static final int SYS_ERROR = 402;
  15. /** 参数错误 */
  16. public static final int PARAMS_ERROR = 403;
  17. /** 不支持或已经废弃 */
  18. public static final int NOT_SUPPORTED = 410;
  19. /** AuthCode错误 */
  20. public static final int INVALID_AUTHCODE = 444;
  21. /** 太频繁的调用 */
  22. public static final int TOO_FREQUENT = 445;
  23. /** 未知的错误 */
  24. public static final int UNKNOWN_ERROR = 499;
  25. private int code;
  26. private String msg;
  27. private T data;
  28. public static JsonResult build() {
  29. return new JsonResult();
  30. }
  31. public static JsonResult build(int code) {
  32. return new JsonResult().code(code);
  33. }
  34. public static JsonResult build(int code, String msg) {
  35. return new JsonResult<String>().code(code).msg(msg);
  36. }
  37. public static <T> JsonResult<T> build(int code, T data) {
  38. return new JsonResult<T>().code(code).data(data);
  39. }
  40. public static <T> JsonResult<T> build(int code, String msg, T data) {
  41. return new JsonResult<T>().code(code).msg(msg).data(data);
  42. }
  43. public JsonResult<T> code(int code) {
  44. this.code = code;
  45. return this;
  46. }
  47. public JsonResult<T> msg(String msg) {
  48. this.msg = msg;
  49. return this;
  50. }
  51. public JsonResult<T> data(T data) {
  52. this.data = data;
  53. return this;
  54. }
  55. public static JsonResult ok() {
  56. return build(SUCCESS);
  57. }
  58. public static JsonResult ok(String msg) {
  59. return build(SUCCESS, msg);
  60. }
  61. public static <T> JsonResult<T> ok(T data) {
  62. return build(SUCCESS, data);
  63. }
  64. public static JsonResult err() {
  65. return build(EXCEPTION);
  66. }
  67. public static JsonResult err(String msg) {
  68. return build(EXCEPTION, msg);
  69. }
  70. @Override
  71. public String toString() {
  72. return JsonUtil.to(this);
  73. }
  74. }

三、item service 商品服务

  1. 新建项目
  2. 配置依赖 pom.xml
  3. 配置 application.yml
  4. 配置主程序
  5. 编写代码

新建 spring boot 起步项目

新建itemservice项目

选择依赖项

  • 只选择 web

选择依赖

pom.xml

  • 要填加 sp01-commons 项目依赖

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    sp02-itemservice
    0.0.1-SNAPSHOT
    sp02-itemservice
    Demo project for Spring Boot


    1.8




    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-test
    test


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT






    org.springframework.boot
    spring-boot-maven-plugin




application.yml

yml文件

  1. spring:
  2. application:
  3. name: item-service
  4. server:
  5. port: 8001

主程序

  • 默认代码,不需要修改

    package com.tedu.sp02;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class Sp02ItemserviceApplication {

    1. public static void main(String[] args) {
    2. SpringApplication.run(Sp02ItemserviceApplication.class, args);
    3. }

    }

java 源文件

java源文件

ItemServiceImpl

  1. package com.tedu.sp02.item.service;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.springframework.stereotype.Service;
  5. import com.tedu.sp01.pojo.Item;
  6. import com.tedu.sp01.service.ItemService;
  7. import lombok.extern.slf4j.Slf4j;
  8. @Slf4j
  9. @Service
  10. public class ItemServiceImpl implements ItemService {
  11. @Override
  12. public List<Item> getItems(String orderId) {
  13. ArrayList<Item> list = new ArrayList<Item>();
  14. list.add(new Item(1, "商品 1",1));
  15. list.add(new Item(2, "商品 2",2));
  16. list.add(new Item(3, "商品 3",3));
  17. list.add(new Item(4, "商品 4",4));
  18. list.add(new Item(5, "商品 5",5));
  19. return list;
  20. }
  21. @Override
  22. public void decreaseNumbers(List<Item> list) {
  23. if (log.isInfoEnabled()) {
  24. for(Item item : list) {
  25. log.info("减少库存 - "+item);
  26. }
  27. }
  28. }
  29. }

ItemController

  • 如果存在 jackson-dataformat-xml 依赖,会根据请求协议头,可能返回 xml 或 json;可以强制返回 json:
    produces=MediaType.APPLICATION_JSON_UTF8_VALUE

    package com.tedu.sp02.item.controller;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;

    import com.tedu.sp01.pojo.Item;
    import com.tedu.sp01.service.ItemService;
    import com.tedu.web.util.JsonResult;

    import lombok.extern.slf4j.Slf4j;

    @Slf4j
    @RestController
    public class ItemController {

    1. @Autowired
    2. private ItemService itemService;
    3. @Value("${server.port}")
    4. private int port;
    5. @GetMapping("/{orderId}")
    6. public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
    7. log.info("server.port="+port+", orderId="+orderId);
    8. List<Item> items = itemService.getItems(orderId);
    9. return JsonResult.ok(items).msg("port="+port);
    10. }
    11. @PostMapping("/decreaseNumber")
    12. public JsonResult decreaseNumber(@RequestBody List<Item> items) {
    13. itemService.decreaseNumbers(items);
    14. return JsonResult.ok();
    15. }

    }

访问测试

根据orderid,查询商品
http://localhost:8001/35

减少商品库存
http://localhost:8001/decreaseNumber

使用postman,POST发送以下格式数据:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]

postman


四、user service 用户服务

  1. 新建项目
  2. 配置依赖 pom.xml
  3. 配置 application.yml
  4. 配置主程序
  5. 编写代码

新建 spring boot 起步项目

新建userservice项目

选择依赖项

  • 只选择 web

选择依赖

pom.xml

  • 要填加 sp01-commons 项目依赖

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    sp03-userservice
    0.0.1-SNAPSHOT
    sp03-userservice
    Demo project for Spring Boot


    1.8




    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-test
    test


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT






    org.springframework.boot
    spring-boot-maven-plugin




application.yml

  • 其中 sp.user-service.users 属性为自定义属性,提供用于测试的用户数据

    sp:
    user-service:

    1. users: "[{\"id\":7, \"username\":\"abc\",\"password\":\"123\"},{\"id\":8, \"username\":\"def\",\"password\":\"456\"},{\"id\":9, \"username\":\"ghi\",\"password\":\"789\"}]"

    spring:
    application:

    1. name: user-service

    server:
    port: 8101

主程序

  • 默认代码,不需要修改

    package com.tedu.sp03;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class Sp03UserserviceApplication {

    1. public static void main(String[] args) {
    2. SpringApplication.run(Sp03UserserviceApplication.class, args);
    3. }

    }

java源文件

java源文件

UserServiceImpl

  1. package com.tedu.sp03.user.service;
  2. import java.util.List;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.stereotype.Service;
  5. import com.fasterxml.jackson.core.type.TypeReference;
  6. import com.tedu.sp01.pojo.User;
  7. import com.tedu.sp01.service.UserService;
  8. import com.tedu.web.util.JsonUtil;
  9. import lombok.extern.slf4j.Slf4j;
  10. @Slf4j
  11. @Service
  12. public class UserServiceImpl implements UserService {
  13. @Value("${sp.user-service.users}")
  14. private String userJson;
  15. @Override
  16. public User getUser(Integer id) {
  17. log.info("users json string : "+userJson);
  18. List<User> list = JsonUtil.from(userJson, new TypeReference<List<User>>() { });
  19. for (User u : list) {
  20. if (u.getId().equals(id)) {
  21. return u;
  22. }
  23. }
  24. return new User(id, "name-"+id, "pwd-"+id);
  25. }
  26. @Override
  27. public void addScore(Integer id, Integer score) {
  28. //TODO 这里增加积分
  29. log.info("user "+id+" - 增加积分 "+score);
  30. }
  31. }

UserController

  • 如果存在 jackson-dataformat-xml 依赖,会根据请求协议头,可能返回 xml 或 json;可以强制返回 json:
    produces=MediaType.APPLICATION_JSON_UTF8_VALUE

    package com.tedu.sp03.user.controller;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;

    import com.tedu.sp01.pojo.User;
    import com.tedu.sp01.service.UserService;
    import com.tedu.web.util.JsonResult;

    import lombok.extern.slf4j.Slf4j;

    @Slf4j
    @RestController
    public class UserController {

    1. @Autowired
    2. private UserService userService;
    3. @GetMapping("/{userId}")
    4. public JsonResult<User> getUser(@PathVariable Integer userId) {
    5. log.info("get user, userId="+userId);
    6. User u = userService.getUser(userId);
    7. return JsonResult.ok(u);
    8. }
    9. @GetMapping("/{userId}/score")
    10. public JsonResult addScore(
    11. @PathVariable Integer userId, Integer score) {
    12. userService.addScore(userId, score);
    13. return JsonResult.ok();
    14. }

    }

访问测试

根据userid查询用户信息
http://localhost:8101/7

根据userid,为用户增加积分
http://localhost:8101/7/score?score=100


五、order service 订单服务

  1. 新建项目
  2. 配置依赖 pom.xml
  3. 配置 application.yml
  4. 配置主程序
  5. 编写代码

新建 spring boot 起步项目

新建orderservice项目

选择依赖项

  • 只选择 web

选择依赖

pom.xml

  • 要填加 sp01-commons 项目依赖

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    sp04-orderservice
    0.0.1-SNAPSHOT
    sp04-orderservice
    Demo project for Spring Boot


    1.8




    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-test
    test


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT






    org.springframework.boot
    spring-boot-maven-plugin




applicatoin.yml

  1. spring:
  2. application:
  3. name: order-service
  4. server:
  5. port: 8201

主程序

  • 默认代码,不需要修改

    package com.tedu.sp04;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class Sp04OrderserviceApplication {

    1. public static void main(String[] args) {
    2. SpringApplication.run(Sp04OrderserviceApplication.class, args);
    3. }

    }

java 源文件

java源文件

OrderServiceImpl

  1. package com.tedu.sp04.order.service;
  2. import org.springframework.stereotype.Service;
  3. import com.tedu.sp01.pojo.Order;
  4. import com.tedu.sp01.service.OrderService;
  5. import lombok.extern.slf4j.Slf4j;
  6. @Slf4j
  7. @Service
  8. public class OrderServiceImpl implements OrderService {
  9. @Override
  10. public Order getOrder(String orderId) {
  11. //TODO: 调用user-service获取用户信息
  12. //TODO: 调用item-service获取商品信息
  13. Order order = new Order();
  14. order.setId(orderId);
  15. return order;
  16. }
  17. @Override
  18. public void addOrder(Order order) {
  19. //TODO: 调用item-service减少商品库存
  20. //TODO: 调用user-service增加用户积分
  21. log.info("保存订单:"+order);
  22. }
  23. }

OrderController

  1. package com.tedu.sp04.order.controller;
  2. import java.util.Arrays;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import com.tedu.sp01.pojo.Item;
  8. import com.tedu.sp01.pojo.Order;
  9. import com.tedu.sp01.pojo.User;
  10. import com.tedu.sp01.service.OrderService;
  11. import com.tedu.web.util.JsonResult;
  12. import lombok.extern.slf4j.Slf4j;
  13. @Slf4j
  14. @RestController
  15. public class OrderController {
  16. @Autowired
  17. private OrderService orderService;
  18. @GetMapping("/{orderId}")
  19. public JsonResult<Order> getOrder(@PathVariable String orderId) {
  20. log.info("get order, id="+orderId);
  21. Order order = orderService.getOrder(orderId);
  22. return JsonResult.ok(order);
  23. }
  24. @GetMapping("/")
  25. public JsonResult addOrder() {
  26. //模拟post提交的数据
  27. Order order = new Order();
  28. order.setId("123abc");
  29. order.setUser(new User(7,null,null));
  30. order.setItems(Arrays.asList(new Item[] {
  31. new Item(1,"aaa",2),
  32. new Item(2,"bbb",1),
  33. new Item(3,"ccc",3),
  34. new Item(4,"ddd",1),
  35. new Item(5,"eee",5),
  36. }));
  37. orderService.addOrder(order);
  38. return JsonResult.ok();
  39. }
  40. }

访问测试

根据orderid,获取订单
http://localhost:8201/123abc

保存订单,观察控制台日志输出
http://localhost:8201/


六、service 访问测试汇总

  • item-service

根据orderid,查询商品
http://localhost:8001/35

减少商品库存
http://localhost:8001/decreaseNumber

使用postman,POST发送以下格式数据:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]

  • user-service

根据userid查询用户信息
http://localhost:8101/7

根据userid,为用户增加积分
http://localhost:8101/7/score?score=100

  • order-service

根据orderid,获取订单
http://localhost:8201/123abc

保存订单,观察控制台日志输出
http://localhost:8201/

postman


七、eureka 注册与发现

eureka注册中心

  1. 创建eureka项目
  2. 配置依赖 pom.xml
  3. 配置 application.yml
  4. 主程序启用 eureka 服务器
  5. 启动,访问测试

创建 eureka server 项目:sp05-eureka

新建eureka项目
选择依赖

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-starter-parent</artifactId>
  7. <version>2.1.4.RELEASE</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <groupId>com.tedu</groupId>
  11. <artifactId>sp05-eureka</artifactId>
  12. <version>0.0.1-SNAPSHOT</version>
  13. <name>sp05-eureka</name>
  14. <description>Demo project for Spring Boot</description>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.cloud</groupId>
  22. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-test</artifactId>
  27. <scope>test</scope>
  28. </dependency>
  29. </dependencies>
  30. <dependencyManagement>
  31. <dependencies>
  32. <dependency>
  33. <groupId>org.springframework.cloud</groupId>
  34. <artifactId>spring-cloud-dependencies</artifactId>
  35. <version>${spring-cloud.version}</version>
  36. <type>pom</type>
  37. <scope>import</scope>
  38. </dependency>
  39. </dependencies>
  40. </dependencyManagement>
  41. <build>
  42. <plugins>
  43. <plugin>
  44. <groupId>org.springframework.boot</groupId>
  45. <artifactId>spring-boot-maven-plugin</artifactId>
  46. </plugin>
  47. </plugins>
  48. </build>
  49. </project>

application.yml

  1. spring:
  2. application:
  3. name: eureka-server
  4. server:
  5. port: 2001
  6. eureka:
  7. server:
  8. enable-self-preservation: false
  9. instance:
  10. hostname: eureka1
  11. client:
  12. register-with-eureka: false
  13. fetch-registry: false
  • eureka 集群服务器之间,通过 hostname 来区分
  • eureka.server.enable-self-preservation

    eureka 的自我保护状态:心跳失败的比例,在15分钟内是否低于85%,如果出现了低于的情况,Eureka Server会将当前的实例注册信息保护起来,同时提示一个警告,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据。也就是不会注销任何微服务

  • eureka.client.register-with-eureka=false

    不向自身注册

  • eureka.client.fetch-registry=false

    不从自身拉取注册信息

  • eureka.instance.lease-expiration-duration-in-seconds

    最后一次心跳后,间隔多久认定微服务不可用,默认90

主程序

  • 添加 @EnableEurekaServer

    package com.tedu.sp05;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

    @EnableEurekaServer
    @SpringBootApplication
    public class Sp05EurekaApplication {

    1. public static void main(String[] args) {
    2. SpringApplication.run(Sp05EurekaApplication.class, args);
    3. }

    }

修改 hosts 文件,添加 eureka 域名映射

C:\Windows\System32\drivers\etc\hosts

添加内容:

  1. 127.0.0.1 eureka1
  2. 127.0.0.1 eureka2

启动,并访问测试

  • http://eureka1:2001

eureka


八、service provider 服务提供者

eureka注册中心

  • 修改 item-service、user-service、order-service,把微服务注册到 eureka 服务器
  1. pom.xml 添加eureka依赖
  2. application.yml 添加eureka注册配置
  3. 主程序启用eureka客户端
  4. 启动服务,在eureka中查看注册信息

pom.xml 添加 eureka 客户端依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  4. </dependency>

application.yml 添加 eureka注册配置

  1. eureka:
  2. client:
  3. service-url:
  4. defaultZone: http://eureka1:2001/eureka
  • eureka.instance.lease-renewal-interval-in-seconds

    心跳间隔时间,默认 30 秒

  • defaultZone,默认位置,可以修改为具体地理位置,比如:beiJing, shangHai, shenZhen 等,表示 eureka 服务器的部署位置
  • eureka.client.registry-fetch-interval-seconds

    拉取注册信息间隔时间,默认 30 秒

主程序启用服务注册发现客户端

修改 item-service、user-service 和 order-service,
主程序添加 @EnableDiscoveryClient 注解

启动,并访问 eureka 查看注册信息

启动服务

  • http://eureka1:2001

eureka



九、eureka 和 “服务提供者”的高可用

eureka和item高可用

eureka 高可用

application.yml

  1. spring:
  2. application:
  3. name: eureka-server
  4. #server:
  5. # port: 2001
  6. eureka:
  7. server:
  8. enable-self-preservation: false
  9. # instance:
  10. # hostname: eureka1
  11. # client:
  12. # register-with-eureka: false
  13. # fetch-registry: false
  14. ---
  15. spring:
  16. profiles: eureka1
  17. server:
  18. port: 2001
  19. # eureka1 向 eureka2 注册
  20. eureka:
  21. instance:
  22. hostname: eureka1
  23. client:
  24. service-url:
  25. defaultZone: http://eureka2:2002/eureka
  26. ---
  27. spring:
  28. profiles: eureka2
  29. server:
  30. port: 2002
  31. # eureka2 向 eureka1 注册
  32. eureka:
  33. instance:
  34. hostname: eureka2
  35. client:
  36. service-url:
  37. defaultZone: http://eureka1:2001/eureka

STS 配置启动参数 --spring.profiles.active

  • eureka1 启动参数:

    —spring.profiles.active=eureka1

配置启动项

启动参数

  • eureka2 启动参数:

    —spring.profiles.active=eureka2

复制启动项

启动参数

启动项

命令行运行时添加参数:

  1. java -jar xxx.jar --spring.profiles.active=eureka1

访问 eureka 服务器,查看注册信息

  • http://eureka1:2001/

eureka1

  • http://eureka2:2002/

eureka2

eureka客户端注册时,向两个服务器注册

修改以下微服务

  • sp02-itemservice
  • sp03-userservice
  • sp04-orderservice
  • sp06-ribbon

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

当一个 eureka 服务宕机时,仍可以连接另一个 eureka 服务

item-service 高可用

application.yml

  1. spring:
  2. application:
  3. name: item-service
  4. #server:
  5. # port: 8001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. ---
  11. spring:
  12. profiles: item1
  13. server:
  14. port: 8001
  15. ---
  16. spring:
  17. profiles: item2
  18. server:
  19. port: 8002

配置启动参数

  • item-service-8001

    —spring.profiles.active=item1

配置启动项

启动参数

复制启动项

  • item-service-8002

    —spring.profiles.active=item2

启动参数

启动项

启动测试

  • 访问 eureka 查看 item-service 注册信息

集群

  • 访问两个端口测试
    http://localhost:8001/35
    http://localhost:8002/35

十、ribbon 服务消费者

服务消费者

ribbon 提供了负载均衡和重试功能

  1. 新建 ribbon 项目
  2. pom.xml
  3. application.yml
  4. 主程序
  5. controller
  6. 启动,并访问测试

新建 sp06-ribbon 项目

新建ribbon项目
选择依赖

pom.xml

  • eureka-client 中已经包含 ribbon 依赖
  • 需要添加 sp01-commons 依赖

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    springcloud-012-ribbon
    0.0.1-SNAPSHOT
    springcloud-012-ribbon
    Demo project for Spring Cloud


    1.8
    Greenwich.SR1




    org.springframework.boot
    spring-boot-starter-web


    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client



    org.springframework.boot
    spring-boot-starter-test
    test


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT






    org.springframework.cloud
    spring-cloud-dependencies
    ${spring-cloud.version}
    pom
    import







    org.springframework.boot
    spring-boot-maven-plugin




application.yml

  1. spring:
  2. application:
  3. name: ribbon
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka

主程序

  • 创建 RestTemplate 实例

    RestTemplate 是用来调用其他微服务的工具类,封装了远程调用代码,提供了一组用于远程调用的模板方法,例如:getForObject()postForObject()

    package com.tedu.sp06;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;

    @EnableDiscoveryClient
    @SpringBootApplication
    public class Sp06RibbonApplication {

    1. //创建 RestTemplate 实例,并存入 spring 容器
    2. @Bean
    3. public RestTemplate getRestTemplate() {
    4. return new RestTemplate();
    5. }
    6. public static void main(String[] args) {
    7. SpringApplication.run(Sp06RibbonApplication.class, args);
    8. }

    }

RibbonController

  1. package com.tedu.sp06.consoller;
  2. import java.util.List;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import org.springframework.web.client.RestTemplate;
  10. import com.tedu.sp01.pojo.Item;
  11. import com.tedu.sp01.pojo.Order;
  12. import com.tedu.sp01.pojo.User;
  13. import com.tedu.web.util.JsonResult;
  14. @RestController
  15. public class RibbonController {
  16. @Autowired
  17. private RestTemplate rt;
  18. @GetMapping("/item-service/{orderId}")
  19. public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
  20. //向指定微服务地址发送 get 请求,并获得该服务的返回结果
  21. //{1} 占位符,用 orderId 填充
  22. return rt.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId);
  23. }
  24. @PostMapping("/item-service/decreaseNumber")
  25. public JsonResult decreaseNumber(@RequestBody List<Item> items) {
  26. //发送 post 请求
  27. return rt.postForObject("http://localhost:8001/decreaseNumber", items, JsonResult.class);
  28. }
  29. /
  30. @GetMapping("/user-service/{userId}")
  31. public JsonResult<User> getUser(@PathVariable Integer userId) {
  32. return rt.getForObject("http://localhost:8101/{1}", JsonResult.class, userId);
  33. }
  34. @GetMapping("/user-service/{userId}/score")
  35. public JsonResult addScore(
  36. @PathVariable Integer userId, Integer score) {
  37. return rt.getForObject("http://localhost:8101/{1}/score?score={2}", JsonResult.class, userId, score);
  38. }
  39. /
  40. @GetMapping("/order-service/{orderId}")
  41. public JsonResult<Order> getOrder(@PathVariable String orderId) {
  42. return rt.getForObject("http://localhost:8201/{1}", JsonResult.class, orderId);
  43. }
  44. @GetMapping("/order-service")
  45. public JsonResult addOrder() {
  46. return rt.getForObject("http://localhost:8201/", JsonResult.class);
  47. }
  48. }

启动服务,并访问测试

启动服务

http://eureka1:2001

http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST发送以下格式数据:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]

http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/


十一、ribbon 负载均衡

ribbon

  • 修改 sp06-ribbon 项目
  1. 添加 ribbon 起步依赖(可选)
  2. RestTemplate 设置 @LoadBalanced
  3. 访问路径设置为服务id

添加 ribbon 起步依赖(可选)

  • eureka 依赖中已经包含了 ribbon


    org.springframework.cloud
    spring-cloud-starter-netflix-ribbon

RestTemplate 设置 @LoadBalanced

@LoadBalanced 负载均衡注解,会对 RestTemplate 实例进行封装,创建动态代理对象,并切入(AOP)负载均衡代码,把请求分散分发到集群中的服务器

  1. package com.tedu.sp06;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.web.client.RestTemplate;
  8. @EnableDiscoveryClient
  9. @SpringBootApplication
  10. public class Sp06RibbonApplication {
  11. @LoadBalanced //负载均衡注解
  12. @Bean
  13. public RestTemplate getRestTemplate() {
  14. return new RestTemplate();
  15. }
  16. public static void main(String[] args) {
  17. SpringApplication.run(Sp06RibbonApplication.class, args);
  18. }
  19. }

访问路径设置为服务id

  1. package com.tedu.sp06.consoller;
  2. import java.util.List;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import org.springframework.web.client.RestTemplate;
  10. import com.tedu.sp01.pojo.Item;
  11. import com.tedu.sp01.pojo.Order;
  12. import com.tedu.sp01.pojo.User;
  13. import com.tedu.web.util.JsonResult;
  14. @RestController
  15. public class RibbonController {
  16. @Autowired
  17. private RestTemplate rt;
  18. @GetMapping("/item-service/{orderId}")
  19. public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
  20. //这里服务器路径用 service-id 代替,ribbon 会向服务的多台集群服务器分发请求
  21. return rt.getForObject("http://item-service/{1}", JsonResult.class, orderId);
  22. }
  23. @PostMapping("/item-service/decreaseNumber")
  24. public JsonResult decreaseNumber(@RequestBody List<Item> items) {
  25. return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
  26. }
  27. /
  28. @GetMapping("/user-service/{userId}")
  29. public JsonResult<User> getUser(@PathVariable Integer userId) {
  30. return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
  31. }
  32. @GetMapping("/user-service/{userId}/score")
  33. public JsonResult addScore(
  34. @PathVariable Integer userId, Integer score) {
  35. return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score);
  36. }
  37. /
  38. @GetMapping("/order-service/{orderId}")
  39. public JsonResult<Order> getOrder(@PathVariable String orderId) {
  40. return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
  41. }
  42. @GetMapping("/order-service")
  43. public JsonResult addOrder() {
  44. return rt.getForObject("http://order-service/", JsonResult.class);
  45. }
  46. }

访问测试

  • 访问测试,ribbon 会把请求分发到 8001 和 8002 两个服务端口上

    http://localhost:3001/item-service/34

访问测试

访问测试

ribbon 重试

pom.xml 添加 spring-retry 依赖

  1. <dependency>
  2. <groupId>org.springframework.retry</groupId>
  3. <artifactId>spring-retry</artifactId>
  4. </dependency>

application.yml 配置 ribbon 重试

  1. spring:
  2. application:
  3. name: ribbon
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. ribbon:
  11. MaxAutoRetriesNextServer: 2
  12. MaxAutoRetries: 1
  13. OkToRetryOnAllOperations: true
  • ConnectionTimeout
  • ReadTimeout
  • OkToRetryOnAllOperations=true
    对连接超时、读取超时都进行重试
  • MaxAutoRetriesNextServer
    更换实例的次数
  • MaxAutoRetries
    当前实例重试次数,尝试失败会更换下一个实例

主程序设置 RestTemplate 的请求工厂的超时属性

  1. package com.tedu.sp06;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.http.client.SimpleClientHttpRequestFactory;
  8. import org.springframework.web.client.RestTemplate;
  9. @EnableDiscoveryClient
  10. @SpringBootApplication
  11. public class Sp06RibbonApplication {
  12. @LoadBalanced
  13. @Bean
  14. public RestTemplate getRestTemplate() {
  15. SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
  16. f.setConnectTimeout(1000);
  17. f.setReadTimeout(1000);
  18. return new RestTemplate(f);
  19. //RestTemplate 中默认的 Factory 实例中,两个超时属性默认是 -1,
  20. //未启用超时,也不会触发重试
  21. //return new RestTemplate();
  22. }
  23. public static void main(String[] args) {
  24. SpringApplication.run(Sp06RibbonApplication.class, args);
  25. }
  26. }

item-service 的 ItemController 添加延迟代码,以便测试 ribbon 的重试机制

  1. package com.tedu.sp02.item.controller;
  2. import java.util.List;
  3. import java.util.Random;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.PathVariable;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestBody;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import com.tedu.sp01.pojo.Item;
  12. import com.tedu.sp01.service.ItemService;
  13. import com.tedu.web.util.JsonResult;
  14. import lombok.extern.slf4j.Slf4j;
  15. @Slf4j
  16. @RestController
  17. public class ItemController {
  18. @Autowired
  19. private ItemService itemService;
  20. @Value("${server.port}")
  21. private int port;
  22. @GetMapping("/{orderId}")
  23. public JsonResult<List<Item>> getItems(@PathVariable String orderId) throws Exception {
  24. log.info("server.port="+port+", orderId="+orderId);
  25. ///--设置随机延迟
  26. long t = new Random().nextInt(5000);
  27. if(Math.random()<0.6) {
  28. log.info("item-service-"+port+" - 暂停 "+t);
  29. Thread.sleep(t);
  30. }
  31. ///~~
  32. List<Item> items = itemService.getItems(orderId);
  33. return JsonResult.ok(items).msg("port="+port);
  34. }
  35. @PostMapping("/decreaseNumber")
  36. public JsonResult decreaseNumber(@RequestBody List<Item> items) {
  37. itemService.decreaseNumbers(items);
  38. return JsonResult.ok();
  39. }
  40. }

访问,测试 ribbon 重试机制

  • 通过 ribbon 访问 item-service,当超时,ribbon 会重试请求集群中其他服务器
    http://localhost:3001/item-service/35

重试测试
重试测试

  • ribbon的重试机制,在 feign 和 zuul 中进一步进行了封装,后续可以使用feign或zuul的重试机制

十二、ribbon + hystrix 断路器

  • https://github.com/Netflix/Hystrix/wiki
    hystrix

微服务宕机时,ribbon 无法转发请求

  • 关闭 item-service

关闭item

白板页

复制 sp06-ribbon 项目,命名为sp07-hystrix

  • 选择 sp06-ribbon 项目,ctrl-c,ctrl-v,复制为sp07-hystrix
  • 关闭 sp06-ribbon 项目,后续测试使用 sp07-hystrix 项目

复制项目

修改 pom.xml

修改pom

添加 hystrix 起步依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

修改 application.yml

修改yml

  1. spring:
  2. application:
  3. name: hystrix
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. ribbon:
  11. MaxAutoRetries: 1
  12. MaxAutoRetriesNextServer: 2
  13. OkToRetryOnAllOperations: true

主程序添加 @EnableCircuitBreaker 启用 hystrix 断路器

启动断路器,断路器提供两个核心功能:

  • 降级,超时、出错、不可到达时,对服务降级,返回错误信息或者是缓存数据
  • 熔断,当服务压力过大,错误比例过多时,熔断所有请求,所有请求直接降级
  • 可以使用 @SpringCloudApplication 注解代替三个注解

    package com.tedu.sp06;

    import org.springframework.boot.SpringApplication;
    import org.springframework.cloud.client.SpringCloudApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.client.SimpleClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;

    //@EnableCircuitBreaker
    //@EnableDiscoveryClient
    //@SpringBootApplication

    @SpringCloudApplication
    public class Sp06RibbonApplication {

    1. @LoadBalanced
    2. @Bean
    3. public RestTemplate getRestTemplate() {
    4. SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
    5. f.setConnectTimeout(1000);
    6. f.setReadTimeout(1000);
    7. return new RestTemplate(f);
    8. //RestTemplate 中默认的 Factory 实例中,两个超时属性默认是 -1,
    9. //未启用超时,也不会触发重试
    10. //return new RestTemplate();
    11. }
    12. public static void main(String[] args) {
    13. SpringApplication.run(Sp06RibbonApplication.class, args);
    14. }

    }

RibbonController 中添加降级方法

  • 为每个方法添加降级方法,例如 getItems() 添加降级方法 getItemsFB()
  • 添加 @HystrixCommand 注解,指定降级方法名

    package com.tedu.sp06.consoller;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;

    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.tedu.sp01.pojo.Item;
    import com.tedu.sp01.pojo.Order;
    import com.tedu.sp01.pojo.User;
    import com.tedu.web.util.JsonResult;

    @RestController
    public class RibbonController {

    1. @Autowired
    2. private RestTemplate rt;
    3. @GetMapping("/item-service/{orderId}")
    4. @HystrixCommand(fallbackMethod = "getItemsFB") //指定降级方法的方法名
    5. public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
    6. return rt.getForObject("http://item-service/{1}", JsonResult.class, orderId);
    7. }
    8. @PostMapping("/item-service/decreaseNumber")
    9. @HystrixCommand(fallbackMethod = "decreaseNumberFB")
    10. public JsonResult decreaseNumber(@RequestBody List<Item> items) {
    11. return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
    12. }
    13. /
    14. @GetMapping("/user-service/{userId}")
    15. @HystrixCommand(fallbackMethod = "getUserFB")
    16. public JsonResult<User> getUser(@PathVariable Integer userId) {
    17. return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
    18. }
    19. @GetMapping("/user-service/{userId}/score")
    20. @HystrixCommand(fallbackMethod = "addScoreFB")
    21. public JsonResult addScore(@PathVariable Integer userId, Integer score) {
    22. return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score);
    23. }
    24. /
    25. @GetMapping("/order-service/{orderId}")
    26. @HystrixCommand(fallbackMethod = "getOrderFB")
    27. public JsonResult<Order> getOrder(@PathVariable String orderId) {
    28. return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
    29. }
    30. @GetMapping("/order-service")
    31. @HystrixCommand(fallbackMethod = "addOrderFB")
    32. public JsonResult addOrder() {
    33. return rt.getForObject("http://order-service/", JsonResult.class);
    34. }
    35. /
    36. //降级方法的参数和返回值,需要和原始方法一致,方法名任意
    37. public JsonResult<List<Item>> getItemsFB(String orderId) {
    38. return JsonResult.err("获取订单商品列表失败");
    39. }
    40. public JsonResult decreaseNumberFB(List<Item> items) {
    41. return JsonResult.err("更新商品库存失败");
    42. }
    43. public JsonResult<User> getUserFB(Integer userId) {
    44. return JsonResult.err("获取用户信息失败");
    45. }
    46. public JsonResult addScoreFB(Integer userId, Integer score) {
    47. return JsonResult.err("增加用户积分失败");
    48. }
    49. public JsonResult<Order> getOrderFB(String orderId) {
    50. return JsonResult.err("获取订单失败");
    51. }
    52. public JsonResult addOrderFB() {
    53. return JsonResult.err("添加订单失败");
    54. }

    }

hystrix 短路超时设置

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

为了测试 hystrix 短路功能,我们把 hystrix 等待超时设置得非常小(500毫秒)

此设置一般应大于 ribbon 的重试超时时长,例如 10 秒

  1. spring:
  2. application:
  3. name: hystrix
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. ribbon:
  11. MaxAutoRetriesNextServer: 2
  12. MaxAutoRetries: 1
  13. OkToRetryOnAllOperations: true
  14. hystrix:
  15. command:
  16. default:
  17. execution:
  18. isolation:
  19. thread:
  20. timeoutInMilliseconds: 500

启动项目进行测试

启动测试

  • 通过 hystrix 服务,访问可能超时失败的 item-service
    http://localhost:3001/item-service/35
  • 通过 hystrix 服务,访问未启动的 user-service
    http://localhost:3001/user-service/7
  • 可以看到,如果 item-service 请求超时,hystrix 会立即执行降级方法
  • 访问 user-service,由于该服务未启动,hystrix也会立即执行降级方法

降级


十三、hystrix dashboard 断路器仪表盘

dashboard

hystrix 对请求的熔断和断路处理,可以产生监控信息,hystrix dashboard可以实时的进行监控

sp07-hystrix 项目添加 actuator,并暴露 hystrix 监控端点

actuator 是 spring boot 提供的服务监控工具,提供了各种监控信息的监控端点

management.endpoints.web.exposure.include 配置选项,
可以指定端点名,来暴露监控端点

如果要暴露所有端点,可以用 “*”

pom.xml 添加 actuator 依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

调整 application.yml 配置,并暴露 hystrix 监控端点

  1. spring:
  2. application:
  3. name: hystrix
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. ribbon:
  11. MaxAutoRetriesNextServer: 1
  12. MaxAutoRetries: 1
  13. OkToRetryOnAllOperations: true
  14. hystrix:
  15. command:
  16. default:
  17. execution:
  18. isolation:
  19. thread:
  20. timeoutInMilliseconds: 2000
  21. management:
  22. endpoints:
  23. web:
  24. exposure:
  25. include: hystrix.stream

访问 actuator 路径,查看监控端点

  • http://localhost:3001/actuator

actuator

新建 sp08-hystrix-dashboard 项目

dashboard项目

添加依赖

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-starter-parent</artifactId>
  7. <version>2.1.4.RELEASE</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <groupId>com.tedu</groupId>
  11. <artifactId>sp08-hystrix-dashboard</artifactId>
  12. <version>0.0.1-SNAPSHOT</version>
  13. <name>sp08-hystrix-dashboard</name>
  14. <description>Demo project for Spring Boot</description>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.cloud</groupId>
  22. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-test</artifactId>
  31. <scope>test</scope>
  32. </dependency>
  33. </dependencies>
  34. <dependencyManagement>
  35. <dependencies>
  36. <dependency>
  37. <groupId>org.springframework.cloud</groupId>
  38. <artifactId>spring-cloud-dependencies</artifactId>
  39. <version>${spring-cloud.version}</version>
  40. <type>pom</type>
  41. <scope>import</scope>
  42. </dependency>
  43. </dependencies>
  44. </dependencyManagement>
  45. <build>
  46. <plugins>
  47. <plugin>
  48. <groupId>org.springframework.boot</groupId>
  49. <artifactId>spring-boot-maven-plugin</artifactId>
  50. </plugin>
  51. </plugins>
  52. </build>
  53. </project>

application.yml

  1. spring:
  2. application:
  3. name: hystrix-dashboard
  4. server:
  5. port: 4001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

主程序添加 @EnableHystrixDashboard@EnableDiscoveryClient

  1. package com.tedu.sp08;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
  6. @EnableDiscoveryClient
  7. @EnableHystrixDashboard
  8. @SpringBootApplication
  9. public class Sp08HystrixDashboardApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(Sp08HystrixDashboardApplication.class, args);
  12. }
  13. }

启动,并访问测试

启动访问测试

访问 hystrix dashboard

  • http://localhost:4001/hystrix

dashboard

填入 hystrix 的监控端点,开启监控

  • http://localhost:3001/actuator/hystrix.stream

开启监控

  • 通过 hystrix 访问服务多次,观察监控信息

http://localhost:3001/item-service/35

http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/

监控界面

监控界面


hystrix 熔断

整个链路达到一定的阈值,默认情况下,10秒内产生超过20次请求,则符合第一个条件。
满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为50%。
Hystrix的逻辑,先判断是否满足第一个条件,再判断第二个条件,如果两个条件都满足,则会开启断路器

断路器打开 5 秒后,会处于半开状态,会尝试转发请求,如果仍然失败,保持打开状态,如果成功,则关闭断路器

使用 apache 的并发访问测试工具 ab

http://httpd.apache.org/docs/current/platform/windows.html#down
下载Apache

  • 用 ab 工具,以并发50次,来发送20000个请求

    ab -n 20000 -c 50 http://localhost:3001/item-service/35

  • 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法

熔断

hystrix 配置

https://github.com/Netflix/Hystrix/wiki/Configuration

  • hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
    请求超时时间,超时后触发失败降级
  • hystrix.command.default.circuitBreaker.requestVolumeThreshold
    10秒内请求数量,默认20,如果没有达到该数量,即使请求全部失败,也不会触发断路器打开
  • hystrix.command.default.circuitBreaker.errorThresholdPercentage
    失败请求百分比,达到该比例则触发断路器打开
  • hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds
    断路器打开多长时间后,再次允许尝试访问(半开),仍失败则继续保持打开状态,如成功访问则关闭断路器,默认 5000

十四、feign 整合ribbon+hystrix

微服务应用中,ribbon 和 hystrix 总是同时出现,feign 整合了两者,并提供了声明式消费者客户端

  • 用 feign 代替 hystrix+ribbon

feign

新建 sp09-feign 项目

新建feign项目

添加依赖

pom.xml

  • 需要添加 sp01-commons 依赖

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    sp09-feign
    0.0.1-SNAPSHOT
    sp09-feign
    Demo project for Spring Boot


    1.8
    Greenwich.SR1




    org.springframework.boot
    spring-boot-starter-actuator


    org.springframework.boot
    spring-boot-starter-web


    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client


    org.springframework.cloud
    spring-cloud-starter-openfeign



    org.springframework.boot
    spring-boot-starter-test
    test


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT






    org.springframework.cloud
    spring-cloud-dependencies
    ${spring-cloud.version}
    pom
    import







    org.springframework.boot
    spring-boot-maven-plugin




application.yml

  1. spring:
  2. application:
  3. name: feign
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

主程序添加 @EnableDiscoveryClient@EnableFeignClients

  1. package com.tedu.sp09;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. @EnableFeignClients
  7. @EnableDiscoveryClient
  8. @SpringBootApplication
  9. public class Sp09FeignApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(Sp09FeignApplication.class, args);
  12. }
  13. }

java 源文件

java源文件

feign 声明式客户端

feign 利用了我们熟悉的 spring mvc 注解来对接口方法进行设置,降低了我们的学习成本。

通过这些设置,feign可以拼接后台服务的访问路径和提交的参数

例如:

  1. @GetMapping("/{userId}/score")
  2. JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);

当这样调用该方法:

  1. service.addScore(7, 100);

那么 feign 会向服务器发送请求:

  1. http://用户微服务/7/score?score=100
  • 注意:如果 score 参数名与变量名不同,需要添加参数名设置:

    @GetMapping(“/{userId}/score”)
    JsonResult addScore(@PathVariable Integer userId, @RequestParam(“score”) Integer s);

ItemFeignService

  1. package com.tedu.sp09.service;
  2. import java.util.List;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import com.tedu.sp01.pojo.Item;
  9. import com.tedu.web.util.JsonResult;
  10. @FeignClient("item-service")
  11. public interface ItemFeignService {
  12. @GetMapping("/{orderId}")
  13. JsonResult<List<Item>> getItems(@PathVariable String orderId);
  14. @PostMapping("/decreaseNumber")
  15. JsonResult decreaseNumber(@RequestBody List<Item> items);
  16. }

UserFeignService

  • 注意,如果请求参数名与方法参数名不同,@RequestParam不能省略,并且要指定请求参数名:

    @RequestParam("score") Integer s

    package com.tedu.sp09.service;

    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestParam;

    import com.tedu.sp01.pojo.User;
    import com.tedu.web.util.JsonResult;

    @FeignClient(“user-service”)
    public interface UserFeignService {

    1. @GetMapping("/{userId}")
    2. JsonResult<User> getUser(@PathVariable Integer userId);
    3. // 拼接路径 /{userId}/score?score=新增积分
    4. // 如果请求参数和方法参数同名,@RequestParam可省略
    5. @GetMapping("/{userId}/score")
    6. JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);

    }

OrderFeignService

  1. package com.tedu.sp09.service;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. import com.tedu.sp01.pojo.Order;
  6. import com.tedu.web.util.JsonResult;
  7. @FeignClient("order-service")
  8. public interface OrderFeignService {
  9. @GetMapping("/{orderId}")
  10. JsonResult<Order> getOrder(@PathVariable String orderId);
  11. @GetMapping("/")
  12. JsonResult addOrder();
  13. }

FeignController

  1. package com.tedu.sp09.controller;
  2. import java.util.List;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import com.tedu.sp01.pojo.Item;
  10. import com.tedu.sp01.pojo.Order;
  11. import com.tedu.sp01.pojo.User;
  12. import com.tedu.sp09.service.ItemFeignService;
  13. import com.tedu.sp09.service.OrderFeignService;
  14. import com.tedu.sp09.service.UserFeignService;
  15. import com.tedu.web.util.JsonResult;
  16. @RestController
  17. public class FeignController {
  18. @Autowired
  19. private ItemFeignService itemService;
  20. @Autowired
  21. private UserFeignService userService;
  22. @Autowired
  23. private OrderFeignService orderService;
  24. @GetMapping("/item-service/{orderId}")
  25. public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
  26. return itemService.getItems(orderId);
  27. }
  28. @PostMapping("/item-service/decreaseNumber")
  29. public JsonResult decreaseNumber(@RequestBody List<Item> items) {
  30. return itemService.decreaseNumber(items);
  31. }
  32. /
  33. @GetMapping("/user-service/{userId}")
  34. public JsonResult<User> getUser(@PathVariable Integer userId) {
  35. return userService.getUser(userId);
  36. }
  37. @GetMapping("/user-service/{userId}/score")
  38. public JsonResult addScore(@PathVariable Integer userId, Integer score) {
  39. return userService.addScore(userId, score);
  40. }
  41. /
  42. @GetMapping("/order-service/{orderId}")
  43. public JsonResult<Order> getOrder(@PathVariable String orderId) {
  44. return orderService.getOrder(orderId);
  45. }
  46. @GetMapping("/order-service")
  47. public JsonResult addOrder() {
  48. return orderService.addOrder();
  49. }
  50. }

启动服务,并访问测试

启动服务

  • http://eureka1:2001
  • http://localhost:3001/item-service/35
  • http://localhost:3001/item-service/decreaseNumber
    使用postman,POST发送以下格式数据:
    [{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
  • http://localhost:3001/user-service/7
  • http://localhost:3001/user-service/7/score?score=100
  • http://localhost:3001/order-service/123abc
  • http://localhost:3001/order-service/

十五、feign + ribbon 负载均衡和重试

  • 无需额外配置,feign 默认已启用了 ribbon 负载均衡和重试机制。可以通过配置对参数进行调整

application.yml 配置 ribbon 超时和重试

  • ribbon.xxx 全局配置
  • item-service.ribbon.xxx 对特定服务实例的配置

    spring:
    application:

    1. name: feign

    server:
    port: 3001

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

    ribbon:
    ConnectTimeout: 1000
    ReadTimeout: 1000

    item-service:
    ribbon:

    1. ConnectTimeout: 500
    2. ReadTimeout: 1000
    3. MaxAutoRetriesNextServer: 2
    4. MaxAutoRetries: 1

启动服务,访问测试

http://localhost:3001/item-service/35


十六、feign + hystrix 降级

feign 启用 hystrix

feign 默认没有启用 hystrix,添加配置,启用 hystrix

  • feign.hystrix.enabled=true

application.yml 添加配置

  1. feign:
  2. hystrix:
  3. enabled: true

启用 hystrix 后,访问服务
http://localhost:3001/item-service/35

默认1秒会快速失败,没有降级方法时,会显示白板页

白板页

可以添加配置,暂时减小降级超时时间,以便后续对降级进行测试

  1. ......
  2. feign:
  3. hystrix:
  4. enabled: true
  5. hystrix:
  6. command:
  7. default:
  8. execution:
  9. isolation:
  10. thread:
  11. timeoutInMilliseconds: 500

feign + hystrix 降级

降级类

ItemFeignServiceFB

  1. package com.tedu.sp09.service;
  2. import java.util.List;
  3. import org.springframework.stereotype.Component;
  4. import com.tedu.sp01.pojo.Item;
  5. import com.tedu.web.util.JsonResult;
  6. @Component
  7. public class ItemFeignServiceFB implements ItemFeignService {
  8. @Override
  9. public JsonResult<List<Item>> getItems(String orderId) {
  10. return JsonResult.err("无法获取订单商品列表");
  11. }
  12. @Override
  13. public JsonResult decreaseNumber(List<Item> items) {
  14. return JsonResult.err("无法修改商品库存");
  15. }
  16. }

UserFeignServiceFB

  1. package com.tedu.sp09.service;
  2. import org.springframework.stereotype.Component;
  3. import com.tedu.sp01.pojo.User;
  4. import com.tedu.web.util.JsonResult;
  5. @Component
  6. public class UserFeignServiceFB implements UserFeignService {
  7. @Override
  8. public JsonResult<User> getUser(Integer userId) {
  9. return JsonResult.err("无法获取用户信息");
  10. }
  11. @Override
  12. public JsonResult addScore(Integer userId, Integer score) {
  13. return JsonResult.err("无法增加用户积分");
  14. }
  15. }

OrderFeignServiceFB

  1. package com.tedu.sp09.service;
  2. import org.springframework.stereotype.Component;
  3. import com.tedu.sp01.pojo.Order;
  4. import com.tedu.web.util.JsonResult;
  5. @Component
  6. public class OrderFeignServiceFB implements OrderFeignService {
  7. @Override
  8. public JsonResult<Order> getOrder(String orderId) {
  9. return JsonResult.err("无法获取商品订单");
  10. }
  11. @Override
  12. public JsonResult addOrder() {
  13. return JsonResult.err("无法保存订单");
  14. }
  15. }

feign service 接口中指定降级类

ItemFeignService

  1. ...
  2. @FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
  3. public interface ItemFeignService {
  4. ...

UserFeignService

  1. ...
  2. @FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
  3. public interface UserFeignService {
  4. ...

OrderFeignService

  1. ...
  2. @FeignClient(name="order-service",fallback = OrderFeignServiceFB.class)
  3. public interface OrderFeignService {
  4. ...

启动服务,访问测试

http://localhost:3001/item-service/35

访问测试


十七、feign + hystrix 监控和熔断测试

监控

修改sp09-feign项目 pom.xml 添加 hystrix 起步依赖

  • feign 没有包含完整的 hystrix 依赖


    org.springframework.cloud

    spring-cloud-starter-netflix-hystrix

主程序添加 @EnableCircuitBreaker

  1. package com.tedu.sp09;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
  5. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  6. import org.springframework.cloud.openfeign.EnableFeignClients;
  7. @EnableCircuitBreaker
  8. @EnableFeignClients
  9. @EnableDiscoveryClient
  10. @SpringBootApplication
  11. public class Sp09FeignApplication {
  12. public static void main(String[] args) {
  13. SpringApplication.run(Sp09FeignApplication.class, args);
  14. }
  15. }

sp09-feign 配置 actuator,暴露 hystrix.stream 监控端点

actuator 依赖

确认已经添加了 actuator 依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

application.yml 暴露 hystrix.stream 端点

  1. management:
  2. endpoints:
  3. web:
  4. exposure:
  5. include: hystrix.stream

启动服务,查看监控端点

监控端点

hystrix dashboard

启动 hystrix dashboard 服务,填入 feign 监控路径,开启监控
访问 http://localhost:4001/hystrix

  • 填入 feign 监控路径:
    http://localhost:3001/actuator/hystrix.stream
  • 访问微服务,以产生监控数据

http://localhost:3001/item-service/35

http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/

监控

熔断测试

  • 用 ab 工具,以并发50次,来发送20000个请求

    ab -n 20000 -c 50 http://localhost:3001/item-service/35

  • 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法

压力测试


十八、order service 调用商品库存服务和用户服务

order调用user和item

修改 sp04-orderservice 项目,添加 feign,调用 item service 和 user service

  1. pom.xml
  2. application.yml
  3. 主程序
  4. ItemFeignService
  5. UserFeignService
  6. ItemFeignServiceFB
  7. UserFeignServiceFB
  8. OrderServiceImpl

pom.xml

  • 添加以下依赖:
  • actuator
  • feign
  • hystrix

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    sp04-orderservice
    0.0.1-SNAPSHOT
    sp04-orderservice
    Demo project for Spring Boot


    1.8




    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-test
    test


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT


    org.springframework.cloud

    spring-cloud-starter-netflix-eureka-client



    org.springframework.boot
    spring-boot-starter-actuator


    org.springframework.cloud

    spring-cloud-starter-netflix-hystrix



    org.springframework.cloud
    spring-cloud-starter-openfeign






    org.springframework.boot
    spring-boot-maven-plugin







    org.springframework.cloud
    spring-cloud-dependencies
    Greenwich.SR1
    pom
    import



application.yml

  • ribbon 重试和 hystrix 超时,这里没有设置,采用了默认值

    spring:
    application:

    1. name: order-service

    server:

    port: 8201

  1. eureka:
  2. client:
  3. service-url:
  4. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  5. feign:
  6. hystrix:
  7. enabled: true
  8. management:
  9. endpoints:
  10. web:
  11. exposure:
  12. include: hystrix.stream
  13. ---
  14. spring:
  15. profiles: order1
  16. server:
  17. port: 8201
  18. ---
  19. spring:
  20. profiles: order2
  21. server:
  22. port: 8202

主程序

  1. package com.tedu.sp04;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.SpringCloudApplication;
  5. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  6. import org.springframework.cloud.openfeign.EnableFeignClients;
  7. //@EnableDiscoveryClient
  8. //@SpringBootApplication
  9. @EnableFeignClients
  10. @SpringCloudApplication
  11. public class Sp04OrderserviceApplication {
  12. public static void main(String[] args) {
  13. SpringApplication.run(Sp04OrderserviceApplication.class, args);
  14. }
  15. }

ItemFeignService

  1. package com.tedu.sp04.order.feignclient;
  2. import java.util.List;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import com.tedu.sp01.pojo.Item;
  9. import com.tedu.web.util.JsonResult;
  10. @FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
  11. public interface ItemFeignService {
  12. @GetMapping("/{orderId}")
  13. JsonResult<List<Item>> getItems(@PathVariable String orderId);
  14. @PostMapping("/decreaseNumber")
  15. JsonResult decreaseNumber(@RequestBody List<Item> items);
  16. }

UserFeignService

  1. package com.tedu.sp04.order.feignclient;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. import org.springframework.web.bind.annotation.RequestParam;
  6. import com.tedu.sp01.pojo.User;
  7. import com.tedu.web.util.JsonResult;
  8. @FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
  9. public interface UserFeignService {
  10. @GetMapping("/{userId}")
  11. JsonResult<User> getUser(@PathVariable Integer userId);
  12. @GetMapping("/{userId}/score")
  13. JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
  14. }

ItemFeignServiceFB

  • 获取商品列表的降级方法,模拟使用缓存数据

    package com.tedu.sp04.order.feignclient;

    import java.util.Arrays;
    import java.util.List;

    import org.springframework.stereotype.Component;

    import com.tedu.sp01.pojo.Item;
    import com.tedu.web.util.JsonResult;

    @Component
    public class ItemFeignServiceFB implements ItemFeignService {

    1. @Override
    2. public JsonResult<List<Item>> getItems(String orderId) {
    3. if(Math.random()<0.5) {
    4. return JsonResult.ok().data(
    5. Arrays.asList(new Item[] {
    6. new Item(1,"缓存aaa",2),
    7. new Item(2,"缓存bbb",1),
    8. new Item(3,"缓存ccc",3),
    9. new Item(4,"缓存ddd",1),
    10. new Item(5,"缓存eee",5)
    11. })
    12. );
    13. }
    14. return JsonResult.err("无法获取订单商品列表");
    15. }
    16. @Override
    17. public JsonResult decreaseNumber(List<Item> items) {
    18. return JsonResult.err("无法修改商品库存");
    19. }

    }

UserFeignServiceFB

  • 获取用户信息的降级方法,模拟使用缓存数据

    package com.tedu.sp04.order.feignclient;

    import org.springframework.stereotype.Component;

    import com.tedu.sp01.pojo.User;
    import com.tedu.web.util.JsonResult;

    @Component
    public class UserFeignServiceFB implements UserFeignService {

    1. @Override
    2. public JsonResult<User> getUser(Integer userId) {
    3. if(Math.random()<0.4) {
    4. return JsonResult.ok(new User(userId, "缓存name"+userId, "缓存pwd"+userId));
    5. }
    6. return JsonResult.err("无法获取用户信息");
    7. }
    8. @Override
    9. public JsonResult addScore(Integer userId, Integer score) {
    10. return JsonResult.err("无法增加用户积分");
    11. }

    }

OrderServiceImpl

  1. package com.tedu.sp04.order.service;
  2. import java.util.List;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Service;
  5. import com.tedu.sp01.pojo.Item;
  6. import com.tedu.sp01.pojo.Order;
  7. import com.tedu.sp01.pojo.User;
  8. import com.tedu.sp01.service.OrderService;
  9. import com.tedu.sp04.order.feignclient.ItemFeignService;
  10. import com.tedu.sp04.order.feignclient.UserFeignService;
  11. import com.tedu.web.util.JsonResult;
  12. import lombok.extern.slf4j.Slf4j;
  13. @Slf4j
  14. @Service
  15. public class OrderServiceImpl implements OrderService {
  16. @Autowired
  17. private ItemFeignService itemService;
  18. @Autowired
  19. private UserFeignService userService;
  20. @Override
  21. public Order getOrder(String orderId) {
  22. //调用user-service获取用户信息
  23. JsonResult<User> user = userService.getUser(7);
  24. //调用item-service获取商品信息
  25. JsonResult<List<Item>> items = itemService.getItems(orderId);
  26. Order order = new Order();
  27. order.setId(orderId);
  28. order.setUser(user.getData());
  29. order.setItems(items.getData());
  30. return order;
  31. }
  32. @Override
  33. public void addOrder(Order order) {
  34. //调用item-service减少商品库存
  35. itemService.decreaseNumber(order.getItems());
  36. //TODO: 调用user-service增加用户积分
  37. userService.addScore(7, 100);
  38. log.info("保存订单:"+order);
  39. }
  40. }

order-service 配置启动参数

  • --spring.profiles.active=order1
  • --spring.profiles.active=order2

启动项

启动服务,访问测试

启动

  • 根据orderid,获取订单
    http://localhost:8201/123abc
  • 保存订单
    http://localhost:8201/
  • 通过 feign 访问 order service
    http://localhost:3001/order-service/123abc
    http://localhost:3001/order-service/

hystrix dashboard 监控 order service 断路器

  • 访问 http://localhost:4001/hystrix ,填入 order service 的断路器监控路径,启动监控
  • http://localhost:8201/actuator/hystrix.stream
  • http://localhost:8202/actuator/hystrix.stream

监控


十九、hystrix + turbine 集群聚合监控

turbine

hystrix dashboard 一次只能监控一个服务实例,使用 turbine 可以汇集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展示和监控

新建 sp10-turbine 项目

turbine项目

选择依赖

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-starter-parent</artifactId>
  7. <version>2.1.4.RELEASE</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <groupId>com.tedu</groupId>
  11. <artifactId>sp10-turbine</artifactId>
  12. <version>0.0.1-SNAPSHOT</version>
  13. <name>sp10-turbine</name>
  14. <description>Demo project for Spring Boot</description>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.cloud</groupId>
  22. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-test</artifactId>
  31. <scope>test</scope>
  32. </dependency>
  33. </dependencies>
  34. <dependencyManagement>
  35. <dependencies>
  36. <dependency>
  37. <groupId>org.springframework.cloud</groupId>
  38. <artifactId>spring-cloud-dependencies</artifactId>
  39. <version>${spring-cloud.version}</version>
  40. <type>pom</type>
  41. <scope>import</scope>
  42. </dependency>
  43. </dependencies>
  44. </dependencyManagement>
  45. <build>
  46. <plugins>
  47. <plugin>
  48. <groupId>org.springframework.boot</groupId>
  49. <artifactId>spring-boot-maven-plugin</artifactId>
  50. </plugin>
  51. </plugins>
  52. </build>
  53. </project>

application.yml

  1. spring:
  2. application:
  3. name: turbin
  4. server:
  5. port: 5001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. turbine:
  11. app-config: order-service, feign
  12. cluster-name-expression: new String("default")

主程序

  1. package com.tedu.sp10;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.netflix.turbine.EnableTurbine;
  6. @EnableTurbine
  7. @EnableDiscoveryClient
  8. @SpringBootApplication
  9. public class Sp10TurbineApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(Sp10TurbineApplication.class, args);
  12. }
  13. }

访问测试

  • turbine 监控路径
    http://localhost:5001/turbine.stream
  • 在 hystrix dashboard 中填入turbine 监控路径,开启监控
    http://localhost:4001/hystrix
  • turbine聚合了feign服务和order-service服务集群的hystrix监控信息

监控

  • 8201服务器产生监控数据:

http://localhost:8201/abc123
http://localhost:8201/

  • 8202服务器产生监控数据:

http://localhost:8202/abc123
http://localhost:8202/

  • 3001服务器产生监控数据:

http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST发送以下格式数据:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]

http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/


二十、zuul API网关

zuul

zuul API 网关,为微服务应用提供统一的对外访问接口。
zuul 还提供过滤器,对所有微服务提供统一的请求校验。

新建 sp11-zuul 项目

zuul项目

添加依赖

pom.xml

  • 需要添加 sp01-commons 依赖

    <?xml version=”1.0” encoding=”UTF-8”?>


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE


    com.tedu
    sp11-zuul
    0.0.1-SNAPSHOT
    sp11-zuul
    Demo project for Spring Boot


    1.8
    Greenwich.SR1




    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client


    org.springframework.cloud
    spring-cloud-starter-netflix-zuul



    org.springframework.boot
    spring-boot-starter-test
    test


    org.springframework.retry
    spring-retry


    com.tedu
    sp01-commons
    0.0.1-SNAPSHOT






    org.springframework.cloud
    spring-cloud-dependencies
    ${spring-cloud.version}
    pom
    import







    org.springframework.boot
    spring-boot-maven-plugin




applicatoin.yml

  • zuul 路由配置可以省略,缺省以服务 id 作为访问路径

    spring:
    application:

    1. name: zuul

    server:
    port: 3001

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

    zuul:
    routes:

    1. item-service: /item-service/**
    2. user-service: /user-service/**
    3. order-service: /order-service/**

主程序

  1. package com.tedu.sp11;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
  6. @EnableZuulProxy
  7. @EnableDiscoveryClient
  8. @SpringBootApplication
  9. public class Sp11ZuulApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(Sp11ZuulApplication.class, args);
  12. }
  13. }

启动服务,访问测试

启动服务

  • http://eureka1:2001
  • http://localhost:3001/item-service/35
  • http://localhost:3001/item-service/decreaseNumber
    使用postman,POST发送以下格式数据:
    [{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
  • http://localhost:3001/user-service/7
  • http://localhost:3001/user-service/7/score?score=100
  • http://localhost:3001/order-service/123abc
  • http://localhost:3001/order-service/

zuul + ribbon 负载均衡

zuul 已经集成了 ribbon,默认已经实现了负载均衡

zuul + ribbon 重试

pom.xml 添加 spring-retry 依赖

  • 需要 spring-retry 依赖


    org.springframework.retry
    spring-retry

配置 zuul 开启重试,并配置 ribbon 重试参数

  • 需要开启重试,默认不开启

    spring:
    application:

    1. name: zuul

    server:
    port: 3001

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

    zuul:
    retryable: true

    routes:

    item-service: /item-service/**

    user-service: /user-service/**

    order-service: /order-service/**

    ribbon:
    ConnectTimeout: 1000
    ReadTimeout: 1000
    MaxAutoRetriesNextServer: 1
    MaxAutoRetries: 1

zuul + hystrix 降级

创建降级类

  • getRoute() 方法中指定应用此降级类的服务id,星号或null值可以通配所有服务

ItemServiceFallback

  1. package com.tedu.sp11;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
  6. import org.springframework.http.HttpHeaders;
  7. import org.springframework.http.HttpStatus;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.http.client.ClientHttpResponse;
  10. import org.springframework.stereotype.Component;
  11. import com.tedu.web.util.JsonResult;
  12. import lombok.extern.slf4j.Slf4j;
  13. @Slf4j
  14. @Component
  15. public class ItemServiceFallback implements FallbackProvider {
  16. @Override
  17. public String getRoute() {
  18. //当执行item-service失败,
  19. //应用当前这个降级类
  20. return "item-service";
  21. //星号和null都表示所有微服务失败都应用当前降级类
  22. //"*"; //null;
  23. }
  24. //该方法返回封装降级响应的对象
  25. //ClientHttpResponse中封装降级响应
  26. @Override
  27. public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
  28. return response();
  29. }
  30. private ClientHttpResponse response() {
  31. return new ClientHttpResponse() {
  32. //下面三个方法都是协议号
  33. @Override
  34. public HttpStatus getStatusCode() throws IOException {
  35. return HttpStatus.OK;
  36. }
  37. @Override
  38. public int getRawStatusCode() throws IOException {
  39. return HttpStatus.OK.value();
  40. }
  41. @Override
  42. public String getStatusText() throws IOException {
  43. return HttpStatus.OK.getReasonPhrase();
  44. }
  45. @Override
  46. public void close() {
  47. }
  48. @Override
  49. public InputStream getBody() throws IOException {
  50. log.info("fallback body");
  51. String s = JsonResult.err().msg("后台服务错误").toString();
  52. return new ByteArrayInputStream(s.getBytes("UTF-8"));
  53. }
  54. @Override
  55. public HttpHeaders getHeaders() {
  56. HttpHeaders headers = new HttpHeaders();
  57. headers.setContentType(MediaType.APPLICATION_JSON);
  58. return headers;
  59. }
  60. };
  61. }
  62. }

OrderServiceFallback

  1. package com.tedu.sp11;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
  6. import org.springframework.http.HttpHeaders;
  7. import org.springframework.http.HttpStatus;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.http.client.ClientHttpResponse;
  10. import org.springframework.stereotype.Component;
  11. import com.tedu.web.util.JsonResult;
  12. import lombok.extern.slf4j.Slf4j;
  13. @Slf4j
  14. @Component
  15. public class OrderServiceFallback implements FallbackProvider {
  16. @Override
  17. public String getRoute() {
  18. return "order-service"; //"*"; //null;
  19. }
  20. @Override
  21. public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
  22. return response();
  23. }
  24. private ClientHttpResponse response() {
  25. return new ClientHttpResponse() {
  26. @Override
  27. public HttpStatus getStatusCode() throws IOException {
  28. return HttpStatus.OK;
  29. }
  30. @Override
  31. public int getRawStatusCode() throws IOException {
  32. return HttpStatus.OK.value();
  33. }
  34. @Override
  35. public String getStatusText() throws IOException {
  36. return HttpStatus.OK.getReasonPhrase();
  37. }
  38. @Override
  39. public void close() {
  40. }
  41. @Override
  42. public InputStream getBody() throws IOException {
  43. log.info("fallback body");
  44. String s = JsonResult.err().msg("后台服务错误").toString();
  45. return new ByteArrayInputStream(s.getBytes("UTF-8"));
  46. }
  47. @Override
  48. public HttpHeaders getHeaders() {
  49. HttpHeaders headers = new HttpHeaders();
  50. headers.setContentType(MediaType.APPLICATION_JSON);
  51. return headers;
  52. }
  53. };
  54. }
  55. }

zuul + hystrix 熔断

降低 hystrix 超时时间,以便测试降级

  1. spring:
  2. application:
  3. name: zuul
  4. server:
  5. port: 3001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. zuul:
  11. retryable: true
  12. ribbon:
  13. ConnectTimeout: 1000
  14. ReadTimeout: 2000
  15. MaxAutoRetriesNextServer: 1
  16. MaxAutoRetries: 1
  17. hystrix:
  18. command:
  19. default:
  20. execution:
  21. isolation:
  22. thread:
  23. timeoutInMilliseconds: 500

降级

zuul + hystrix dashboard 监控

暴露 hystrix.stream 监控端点

  • zuul 已经包含 actuator 依赖

    management:
    endpoints:

    1. web:
    2. exposure:
    3. include: hystrix.stream
  • 查看暴露的监控端点
    http://localhost:3001/actuator
    http://localhost:3001/actuator/hystrix.stream

开启监控

启动 sp08-hystrix-dashboard,填入 zuul 的监控端点路径,开启监控

http://localhost:4001/hystrix

填入监控端点:
http://localhost:3001/actuator/hystrix.stream

监控

zuul + turbine 聚合监控

修改 turbine 项目,聚合 zuul 服务实例

  1. spring:
  2. application:
  3. name: turbin
  4. server:
  5. port: 5001
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  10. turbine:
  11. app-config: order-service, zuul
  12. cluster-name-expression: new String("default")
  • turbine 聚合监控端点
    http://localhost:5001/turbine.stream

监控

熔断测试

  1. ab -n 20000 -c 50 http://localhost:3001/order-service/123abc

熔断


二十一、zuul 请求过滤

zuul过滤器

定义过滤器,继承 ZuulFilter

在 sp11-zuul 项目中新建过滤器类

  1. package com.tedu.sp11.filter;
  2. import javax.servlet.http.HttpServletRequest;
  3. import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
  4. import org.springframework.stereotype.Component;
  5. import com.netflix.zuul.ZuulFilter;
  6. import com.netflix.zuul.context.RequestContext;
  7. import com.netflix.zuul.exception.ZuulException;
  8. import com.tedu.web.util.JsonResult;
  9. @Component
  10. public class AccessFilter extends ZuulFilter{
  11. @Override
  12. public boolean shouldFilter() {
  13. //对指定的serviceid过滤,如果要过滤所有服务,直接返回 true
  14. RequestContext ctx = RequestContext.getCurrentContext();
  15. String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY);
  16. if(serviceId.equals("item-service")) {
  17. return true;
  18. }
  19. return false;
  20. }
  21. @Override
  22. public Object run() throws ZuulException {
  23. RequestContext ctx = RequestContext.getCurrentContext();
  24. HttpServletRequest req = ctx.getRequest();
  25. String at = req.getParameter("token");
  26. if (at == null) {
  27. //此设置会阻止请求被路由到后台微服务
  28. ctx.setSendZuulResponse(false);
  29. ctx.setResponseStatusCode(200);
  30. ctx.setResponseBody(JsonResult.err().code(JsonResult.NOT_LOGIN).toString());
  31. }
  32. //zuul过滤器返回的数据设计为以后扩展使用,
  33. //目前该返回值没有被使用
  34. return null;
  35. }
  36. @Override
  37. public String filterType() {
  38. return FilterConstants.PRE_TYPE;
  39. }
  40. @Override
  41. public int filterOrder() {
  42. //该过滤器顺序要 > 5,才能得到 serviceid
  43. return FilterConstants.PRE_DECORATION_FILTER_ORDER+1;
  44. }
  45. }

访问测试

  • 没有token参数不允许访问
    http://localhost:3001/item-service/35
  • 有token参数可以访问
    http://localhost:3001/item-service/35?token=1234

二十二、zuul Cookie过滤

zuul 会过滤敏感 http 协议头,默认过滤以下协议头:

  • Cookie
  • Set-Cookie
  • Authorization

可以设置 zuul 不过滤这些协议头

  1. zuul:
  2. sensitive-headers:

二十三、config 配置中心

配置中心

yml 配置文件保存到 git 服务器,例如 github.com 或 gitee.com

微服务启动时,从服务器获取配置文件

github 上存放配置文件

新建 “Project”,命名为 config

新建空白项目
新建空白项目

将sp02,sp03,sp04,sp11四个项目的yml配置文件,复制到config项目,并改名

  • item-service-dev.yml
  • user-service-dev.yml
  • order-service-dev.yml
  • zuul-dev.yml

配置文件

将 config 项目上传到 github

  • 新建仓库

新建仓库

  • 仓库命名

仓库命名

  • 将项目分享到仓库

分享

  • 选择新建本地仓库
  • 仓库目录选择工作空间目录下一个新目录: git-repo

新建本地仓库

  • 提交项目

" class="reference-link">提交

提交


  • 填写sp-config仓库地址

仓库

  • 查看远程仓库文件

远程仓库

config 服务器

config 配置中心从 git 下载所有配置文件。

微服务启动时,从 config 配置中心获取配置信息。

新建 sp12-config 项目

config项目

添加依赖

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-starter-parent</artifactId>
  7. <version>2.1.4.RELEASE</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <groupId>com.tedu</groupId>
  11. <artifactId>sp12-config</artifactId>
  12. <version>0.0.1-SNAPSHOT</version>
  13. <name>sp12-config</name>
  14. <description>Demo project for Spring Boot</description>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.cloud</groupId>
  22. <artifactId>spring-cloud-config-server</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-test</artifactId>
  31. <scope>test</scope>
  32. </dependency>
  33. </dependencies>
  34. <dependencyManagement>
  35. <dependencies>
  36. <dependency>
  37. <groupId>org.springframework.cloud</groupId>
  38. <artifactId>spring-cloud-dependencies</artifactId>
  39. <version>${spring-cloud.version}</version>
  40. <type>pom</type>
  41. <scope>import</scope>
  42. </dependency>
  43. </dependencies>
  44. </dependencyManagement>
  45. <build>
  46. <plugins>
  47. <plugin>
  48. <groupId>org.springframework.boot</groupId>
  49. <artifactId>spring-boot-maven-plugin</artifactId>
  50. </plugin>
  51. </plugins>
  52. </build>
  53. </project>

application.yml

  1. spring:
  2. application:
  3. name: config-server
  4. cloud:
  5. config:
  6. server:
  7. git:
  8. uri: https://github.com/你的个人路径/sp-config
  9. searchPaths: config
  10. #username: your-username
  11. #password: your-password
  12. server:
  13. port: 6001
  14. eureka:
  15. client:
  16. service-url:
  17. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

主程序添加 @EnableConfigServer@EnableDiscoveryClient

  1. package com.tedu.sp12;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.config.server.EnableConfigServer;
  6. @EnableConfigServer
  7. @EnableDiscoveryClient
  8. @SpringBootApplication
  9. public class Sp12ConfigApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(Sp12ConfigApplication.class, args);
  12. }
  13. }

启动,访问测试

访问 item-service-dev.yml 可以使用以下形式:

http://localhost:6001/item-service-dev.yml
http://localhost:6001/item-service/dev

测试其他文件

http://localhost:6001/user-service/dev
http://localhost:6001/zuul/dev

config 客户端

修改以下项目,从配置中心获取配置信息

  • sp02-itemservice
  • sp03-userservice
  • sp04-orderservice
  • sp11-zuul

pom.xml 添加 config 客户端依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-config</artifactId>
  4. </dependency>

在四个项目中添加 bootstrap.yml

bootstrap.yml,引导配置文件,先于 application.yml 加载

  • item-service

    spring:
    cloud:

    1. config:
    2. discovery:
    3. enabled: true
    4. service-id: config-server
    5. name: item-service
    6. profile: dev

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  • user-service

    spring:
    cloud:

    1. config:
    2. discovery:
    3. enabled: true
    4. service-id: config-server
    5. name: user-service
    6. profile: dev

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  • order-service

    spring:
    cloud:

    1. config:
    2. discovery:
    3. enabled: true
    4. service-id: config-server
    5. name: order-service
    6. profile: dev

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  • zuul

    spring:
    cloud:

    1. config:
    2. discovery:
    3. enabled: true
    4. service-id: config-server
    5. name: zuul
    6. profile: dev

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

启动服务,观察从配置中心获取配置信息的日志

启动项目

日志

配置刷新

spring cloud 允许运行时动态刷新配置,可以重新加载本地配置文件 application.yml,或者从配置中心获取新的配置信息

user-service 为例演示配置刷新

pom.xml

user-service 的 pom.xml 中添加 actuator 依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

yml 配置文件中暴露 refresh 端点

  • 修改 github 仓库中的 user-service-dev.yml

    sp:
    user-service:

    1. users: "[{\"id\":7, \"username\":\"abc\",\"password\":\"123\"},{\"id\":8, \"username\":\"def\",\"password\":\"456\"},{\"id\":9, \"username\":\"ghi\",\"password\":\"789\"}]"

    spring:
    application:

    1. name: user-service

    server:
    port: 8101

    eureka:
    client:

    1. service-url:
    2. defaultZone: http://eureka1:2001/eureka, http://eureka1:2002/eureka

    management:
    endpoints:

    1. web:
    2. exposure:
    3. include: refresh

重启服务,查看暴露的刷新端点

  • 查看暴露的刷新端点
    http://localhost:8101/actuator

刷新端点

UserServiceImpl 添加 @RefreshScope 注解

  • 只允许对添加了 @RefreshScope@ConfigurationProperties 注解的 Bean 刷新配置,可以将更新的配置数据注入到 Bean 中

    package com.tedu.sp03.user.service;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.stereotype.Service;

    import com.fasterxml.jackson.core.type.TypeReference;
    import com.tedu.sp01.pojo.User;
    import com.tedu.sp01.service.UserService;
    import com.tedu.web.util.JsonUtil;

    import lombok.extern.slf4j.Slf4j;

    @RefreshScope
    @Slf4j
    @Service
    public class UserServiceImpl implements UserService {

    1. //动态刷新时,更新的配置数据,会重新注入
    2. @Value("${sp.user-service.users}")
    3. private String userJson;
    4. @Override
    5. public User getUser(Integer id) {
    6. log.info("users json string : "+userJson);
    7. List<User> list = JsonUtil.from(userJson, new TypeReference<List<User>>() { });
    8. for (User u : list) {
    9. if (u.getId().equals(id)) {
    10. return u;
    11. }
    12. }
    13. return new User(id, "name-"+id, "pwd-"+id);
    14. }
    15. @Override
    16. public void addScore(Integer id, Integer score) {
    17. //TODO 这里增加积分
    18. log.info("user "+id+" - 增加积分 "+score);
    19. }

    }

先启动 user-service,再修改 config项目的user-service-dev.yml文件并提交

  • 在末尾添加了一个新的用户数据

    sp:
    user-service:

    1. users: "[{\"id\":7, \"username\":\"abc\",\"password\":\"123\"},{\"id\":8, \"username\":\"def\",\"password\":\"456\"},{\"id\":9, \"username\":\"ghi\",\"password\":\"789\"},{\"id\":99, \"username\":\"aaa\",\"password\":\"111\"}]"

访问刷新端点刷新配置

  • 刷新端点路径:
    http://localhost:8101/actuator/refresh
  • 使用 postman 向刷新端点发送 post 请求

postman

访问 user-service,查看动态更新的新用户数据

  • http://localhost:8101/99

新数据


二十四、config bus + rabbitmq 消息总线配置刷新

bus

post 请求消息总线刷新端点,服务器会向 rabbitmq 发布刷新消息,接收到消息的微服务会向配置服务器请求刷新配置信息

rabbitmq 安装笔记

  • https://blog.csdn.net/weixin_38305440/article/details/102810522

需要动态更新配置的微服务,添加 spring cloud bus 依赖,并添加 rabbitmq 连接信息

修改以下微服务

  • item-service
  • user-service
  • order-service
  • zuul

pom.xml 添加 spring cloud bus 依赖

此处也可以使用 STS 编辑起步依赖,分别添加 bus、rabbitmq 依赖

  • 不能用 STS 插件来添加 bus 依赖,复制下面代码到 pom 文件


    org.springframework.cloud
    spring-cloud-starter-bus-amqp

配置文件中添加 rabbitmq 连接信息

  • 在config项目中修改,并提交到github
  • 连接信息请修改成你的连接信息

    spring:
    ……
    rabbitmq:

    1. host: 192.168.64.140
    2. port: 5672
    3. username: admin
    4. password: admin

config-server 添加 spring cloud bus 依赖、配置rabbitmq连接信息,并暴露 bus-refresh 监控端点

pom.xml

  • 不能用 STS 插件来添加 bus 依赖,复制下面代码到 pom 文件


    org.springframework.cloud
    spring-cloud-starter-bus-amqp

application.yml

  1. spring:
  2. application:
  3. name: config-server
  4. cloud:
  5. config:
  6. server:
  7. git:
  8. uri: https://github.com/你的名字路径/sp-config
  9. searchPaths: config
  10. #username: your-username
  11. #password: your-password
  12. rabbitmq:
  13. host: 192.168.64.140
  14. port: 5672
  15. username: admin
  16. password: admin
  17. server:
  18. port: 6001
  19. eureka:
  20. client:
  21. service-url:
  22. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  23. management:
  24. endpoints:
  25. web:
  26. exposure:
  27. include: bus-refresh
  • 查看刷新端点
    http://localhost:6001/actuator

刷新端点

启动服务,请求刷新端点发布刷新消息

启动服务

  • postman 向 bus-refresh 刷新端点发送 post 请求
    http://localhost:6001/actuator/bus-refresh

post刷新

  • 如果刷新指定的微服务,可按下面格式访问:
    http://localhost:6001/actuator/bus-refresh/user-service:8101

config 本地文系统

可以把配置文件保存在配置中心服务的 resources 目录下,直接访问本地文件

把配置文件保存到 sp12-config 项目的 resources/config 目录下

本地目录

修改 application.yml 激活 native profile,并指定配置文件目录

  • 必须配置 spring.profiles.active=native 来激活本地文件系统
  • 本地路径默认:[classpath:/, classpath:/config, file:./, file:./config]

    spring:
    application:

    1. name: config-server

    profiles:

    1. active: native

    cloud:

    1. config:
    2. server:
    3. native:
    4. search-locations: classpath:/config

    git:

    uri: https://github.com/你的用户路径/sp-config

    searchPaths: config

    username: your-username

    password: your-password

  1. rabbitmq:
  2. host: 192.168.64.140
  3. port: 5672
  4. username: admin
  5. password: admin
  6. server:
  7. port: 6001
  8. eureka:
  9. client:
  10. service-url:
  11. defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
  12. management:
  13. endpoints:
  14. web:
  15. exposure:
  16. include: bus-refresh

二十五、sleuth 链路跟踪

随着系统规模越来越大,微服务之间调用关系变得错综复杂,一条调用链路中可能调用多个微服务,任何一个微服务不可用都可能造整个调用过程失败

spring cloud sleuth 可以跟踪调用链路,分析链路中每个节点的执行情况

微服务中添加 spring cloud sleuth 依赖

修改以下微服务的 pom.xml,添加 sleuth 依赖

  • item-service
  • user-service
  • order-service
  • zuul


    org.springframework.cloud
    spring-cloud-starter-sleuth

在控制台查看链路跟踪日志

  • 通过 zuul 网关,访问 order-service
    http://localhost:3001/order-service/112233

四个微服务的控制台日志中,可以看到以下信息:
[服务id,请求id,span id,是否发送到zipkin]

  • 请求id:请求到达第一个微服务时生成一个请求id,该id在调用链路中会一直向后面的微服务传递
  • span id:链路中每一步微服务调用,都生成一个新的id

[zuul,6c24c0a7a8e7281a,6c24c0a7a8e7281a,false]

[order-service,6c24c0a7a8e7281a,993f53408ab7b6e3,false]

[item-service,6c24c0a7a8e7281a,ce0c820204dbaae1,false]

[user-service,6c24c0a7a8e7281a,fdd1e177f72d667b,false]


二十六、sleuth + zipkin 链路分析

zipkin 可以收集链路跟踪数据,提供可视化的链路分析

链路数据抽样比例

默认 10% 的链路数据会被发送到 zipkin 服务。可以配置修改抽样比例

  1. spring:
  2. sleuth:
  3. sampler:
  4. probability: 0.1

zipkin 服务

下载 zipkin 服务器

  • https://github.com/openzipkin/zipkin

下载zipkin

启动 zipkin 时,连接到 rabbitmq

java -jar zipkin-server-2.12.9-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.140:5672

启动

  • http://localhost:9411/zipkin

zipkin

微服务添加 zipkin 起步依赖

修改以下微服务

  • item-service
  • user-service
  • order-service
  • zuul


    org.springframework.cloud
    spring-cloud-starter-zipkin

如果没有配置过 spring cloud bus,需要再添加 spring-cloud-starter-stream-rabbit 依赖和 rabbitmq 连接信息

启动并访问服务,访问 zipkin 查看链路分析

  • http://localhost:3001/order-service/112233
    刷新访问多次,链路跟踪数据中,默认只有 10% 会被收集到zipkin
  • 访问 zipkin
    http://localhost:9411/zipkin

zipkin

zipkin

zipkin

发表评论

表情:
评论列表 (有 0 条评论,106人围观)

还没有评论,来说两句吧...

相关阅读