本篇模拟订单服务调用商品服务,同时商品服务采用集群部署。html
注册中心服务端口号7001,订单服务端口号9001,商品集群端口号:800一、800二、8003。java
各服务的配置文件这里我这边不在显示了,和上篇博客配置同样。博客地址:SpringCloud(3)---Eureka服务注册与发现web
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jincou</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>product</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <!--定义当前springcloud版本--> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--代表是Eureka Client客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
@Data @NoArgsConstructor @AllArgsConstructor public class Product implements Serializable { private int id; //商品名称 private String name; //价格,分为单位 private int price; //库存 private int store; }
public interface ProductService { //查找全部商品 List<Product> listProduct(); //根据商品ID查找商品 Product findById(int id); }
@Service public class ProductServiceImpl implements ProductService { private static final Map<Integer, Product> daoMap = new HashMap<>(); //模拟数据库商品数据 static { Product p1 = new Product(1, "苹果X", 9999, 10); Product p2 = new Product(2, "冰箱", 5342, 19); Product p3 = new Product(3, "洗衣机", 523, 90); Product p4 = new Product(4, "电话", 64345, 150); daoMap.put(p1.getId(), p1); daoMap.put(p2.getId(), p2); daoMap.put(p3.getId(), p3); daoMap.put(p4.getId(), p4); } @Override public List<Product> listProduct() { Collection<Product> collection = daoMap.values(); List<Product> list = new ArrayList<>(collection); return list; } @Override public Product findById(int id) { return daoMap.get(id); } }
@RestController @RequestMapping("/api/v1/product") public class ProductController { //集群状况下,用于订单服务查看到底调用的是哪一个商品微服务节点 @Value("${server.port}") private String port; @Autowired private ProductService productService; //获取全部商品列表 @RequestMapping("list") public Object list(){ return productService.listProduct(); } //根据id查找商品详情 @RequestMapping("find") public Object findById(int id){ Product product = productService.findById(id); Product result = new Product(); BeanUtils.copyProperties(product,result); result.setName( result.getName() + " data from port="+port ); return result; } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jincou</groupId> <artifactId>order</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>order</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
@Data @AllArgsConstructor @NoArgsConstructor public class ProductOrder implements Serializable { //订单ID private int id; // 商品名称 private String productName; //订单号 private String tradeNo; // 价格,分 private int price; //订单建立时间 private Date createTime; //用户id private int userId; //用户名 private String userName; }
/** * 订单业务类 */ public interface ProductOrderService { //下单接口 ProductOrder save(int userId, int productId); }
@Service public class ProductOrderServiceImpl implements ProductOrderService { @Autowired private RestTemplate restTemplate; @Override public ProductOrder save(int userId, int productId) { //product-service是微服务名称(这里指向的商品微服务名称),api/v1/product/find?id=? 就是商品微服务对外的接口 Map<String, Object> productMap = restTemplate.getForObject("http://product-service/api/v1/product/find?id=" + productId, Map.class); ProductOrder productOrder = new ProductOrder(); productOrder.setCreateTime(new Date()); productOrder.setUserId(userId); productOrder.setTradeNo(UUID.randomUUID().toString()); //获取商品名称和商品价格 productOrder.setProductName(productMap.get("name").toString()); productOrder.setPrice(Integer.parseInt(productMap.get("price").toString())); //由于在商品微服务配置了集群,因此这里打印看下调用了是哪一个集群节点,输出端口号。 System.out.println(productMap.get("name").toString()); return productOrder; } }
@RestController @RequestMapping("api/v1/order") public class OrderController { @Autowired private ProductOrderService productOrderService; @RequestMapping("save") public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id") int productId){ return productOrderService.save(userId, productId); } }
@SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } //当添加@LoadBalanced注解,就表明启动Ribbon,进行负载均衡 @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
多调几回接口,看后台打印算法
发现订单服务去掉商品服务的时候,不是固定节点,并且集群的每一个节点都有可能。因此经过Ribbon实现了负载均衡。spring
在springcloud中,引入Ribbon来做为客户端时,负载均衡使用的是被@LoadBalanced
修饰的RestTemplate
对象。数据库
RestTemplate 是Spring本身封装的http请求的客户端,也就是说它只能发送一个正常的Http请求,这跟咱们要求的负载均衡是有出入的,还有就是这个请求的连接上的域名apache
是咱们微服的一个服务名,而不是一个真正的域名,那它是怎么实现负载均衡功能的呢?api
咱们来看看RestTemplate的父类InterceptingHttpAccessor。app
从源码咱们能够知道InterceptingHttpAccessor中有一个拦截器列表List<ClientHttpRequestInterceptor>,若是这个列表为空,则走正常请求流程,若是不为空则走负载均衡
拦截器,因此只要给RestTemplate添加拦截器,而这个拦截器中的逻辑就是Ribbon的负载均衡的逻辑。经过为RestTemplate配置添加拦截器。
具体的拦截器的生成在LoadBalancerAutoConfiguration这个配置类中,全部的RestTemplate的请求都会转到Ribbon的负载均衡器上
(固然这个时候若是你用RestTemplate发起一个正常的Http请求时走不通,由于它找不到对应的服务。)这样就实现了Ribbon的请求的触发。
上面提到过,发起http后请求后,请求会到达到达拦截器中,在拦截其中实现负载均衡,先看看代码:
咱们能够看到在intercept()方法中实现拦截的具体逻辑,首先会根据传进来的请求连接,获取微服的名字serviceName,而后调用LoadBalancerClient的
execute(String serviceId, LoadBalancerRequest<T> request)方法,这个方法直接返回了请求结果,因此正真的路由逻辑在LoadBalancerClient的实现类中,
而这个实现类就是RibbonLoadBalancerClient,看看execute()的源码:
首先是得到均衡器ILoadBalancer这个类上面讲到过这是Netflix Ribbon中的均衡器,这是一个抽象类,具体的实现类是ZoneAwareLoadBalancer上面也讲到过,
每个微服名对应一个均衡器,均衡器中维护者微服名下全部的服务清单。getLoadBalancer()方法经过serviceId得到对应的均衡器,getServer()方法经过对应的均衡器
在对应的路由的算法下计算获得须要路由到Server,Server中有该服务的具体域名等相关信息。获得了具体的Server后执行正常的Http请求,整个请求的负载均衡逻辑就完成了。
在微服中Ribbon和 Hystrix一般是一块儿使用的,其实直接使用Ribbon和Hystrix实现服务间的调用并非很方便,一般在Spring Cloud中咱们使用Feign完成服务间的调用,
而Feign是对Ribbon和Hystrix作了进一步的封装方便你们使用,对Ribbon的学习能帮你更好的完成Spring Cloud中服务间的调用。
我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即使有过天真愚钝,也不值得谴责。毕竟,日后的日子,还很长。不断鼓励本身,
天一亮,又是崭新的起点,又是未知的征程(上校6)