服务提供者和服务消费者,名词非常的高大上,但是其实很简单,我们看什么是服务提供者,什么是服务消费者,
服务的被调用方是服务的提供者,服务的调用方是消费者,以我们之前的电影系统为例,一个用户发起了购票的
请求,发现满足要求了,去查当前用户的信息,然后在这种场景下,电影微服务就是一个服务消费者,用户微服务
就是一个服务提供者,用户微服务提供的一个接口被他消费了,是这个意思
编写一个服务提供者,然后再编写一个简单的消费者,还是以电影销售系统为例,我们来模拟这种情况,
发起一个远程的调用,首先我们写一个服务提供者,我们先写服务提供者,microservice-simple-provider-user,
国内阿里云的镜像,这样下载会比较快,他的parent是spring-boot-starter-parent,首先我们写一些建表语句,
drop table user;
create table user(
id bigint generated by default as identity,
username varchar(40),
name varchar(20),
age int(3),
balance decimal(10,2),
primary key(id)
);
balance是余额
插入一些数据
insert into user(id,username, name, age, balance) values(1,'user1', '张三', 20, 100.00);
insert into user(id,username, name, age, balance) values(2,'user2', '李四', 20, 100.00);
insert into user(id,username, name, age, balance) values(3,'user3', '王五', 20, 100.00);
insert into user(id,username, name, age, balance) values(4,'user4', '马六', 20, 100.00);
@GetMapping它是一个组合注解,它是在Spring4.3以后支持的,同样还有PostMapping,PutMapping,DeleteMapping,
GetMapping他其实相当于RequestMapping,RequestMethod的Get的混合注解,
/**
* Annotation for mapping HTTP {@code GET} requests onto specific handler
* methods.
*
* <p>Specifically, {@code @GetMapping} is a <em>composed annotation</em> that
* acts as a shortcut for {@code @RequestMapping(method = RequestMethod.GET)}.
*
*
* @author Sam Brannen
* @since 4.3
* @see PostMapping
* @see PutMapping
* @see DeleteMapping
* @see PatchMapping
* @see RequestMapping
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
@RestController其实也是一个混合注解,相当于加了ResonseBody和Controller,
/**
* A convenience annotation that is itself annotated with
* {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
* <p>
* Types that carry this annotation are treated as controllers where
* {@link RequestMapping @RequestMapping} methods assume
* {@link ResponseBody @ResponseBody} semantics by default.
*
* <p><b>NOTE:</b> {@code @RestController} is processed if an appropriate
* {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
* {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter}
* pair which are the default in the MVC Java config and the MVC namespace.
* In particular {@code @RestController} is not supported with the
* {@code DefaultAnnotationHandlerMapping}-{@code AnnotationMethodHandlerAdapter}
* pair both of which are also deprecated.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
<?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.learn</groupId>
<artifactId>microservice-simple-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
<dependencies>
<dependency>
<!-- 引入web模块 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot进行单元测试的模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 引入druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<!-- 这个插件,可以将应用打包成一个可执行的jar包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.learn.cloud.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.learn.cloud.entity.User;
import com.learn.cloud.mapper.UserMapper;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/simple/{id}")
public User findById(@PathVariable Long id) {
return this.userMapper.getUserById(id);
}
}
package com.learn.cloud.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.learn.cloud.entity.User;
//@Mapper或者@MapperScan将接口扫描装配到容器中
@Mapper
public interface UserMapper {
public User getUserById(Long id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.cloud.mapper.UserMapper">
<select id="getUserById" resultType="com.learn.cloud.entity.User">
select * from user where id = #{id}
</select>
</mapper>
package com.learn.cloud.entity;
import java.math.BigDecimal;
public class User {
private Long id;
private String username;
private String name;
private Short age;
private BigDecimal balance;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Short getAge() {
return this.age;
}
public void setAge(Short age) {
this.age = age;
}
public BigDecimal getBalance() {
return this.balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
package com.learn.cloud;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan(value="com.learn.cloud.mapper")
@SpringBootApplication
public class MicroserviceSimpleProviderUserApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceSimpleProviderUserApplication.class, args);
}
}
#debug=true
server.port=7900
#server.context-path=/boot02
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.level.com.learn=trace
#logging.file=D:/springboot.log
logging.file=springboot.log
#logging.path=/spring/log
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd} ==== [%thread] %-5level ==== %logger{50} ==== %msg%n
#spring.resources.static-locations=classpath:/hello,classpath:/learn
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://59.110.158.145:3306/SpringCloud?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
http://localhost:7900/simple/1
{"id":1,"username":"user1","name":"张三","age":20,"balance":100.00}
我们发现是可以正常访问的,这样一个非常简单的用户微服务我们已经写完了
DROP DATABASE IF EXISTS SpringCloud;
create database SpringCloud;
use SpringCloud;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL,
`username` varchar(40) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`balance` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into user(id,username,name,age,balance) values(1,'user1','张三',20,100);
insert into user(id,username,name,age,balance) values(2,'user2','李四',20,100);
现在我们写一下服务消费者,也就是这边的movie项目,我们让他比较简单的去调用这个user,这个微服务,
nohup java -jar microservice-simple-provider-user-0.0.1-SNAPSHOT.jar &
sudo apt-get install iptables
sudo aztech aztech
sudo iptables -I INPUT -p tcp --dport 7900 -j ACCEPT
sudo iptables-save
10.40.8.152:7900/simple/1
微服务服务之间是有自治的,所以我们启动movie也是OK的,即使User没有启动好,启动user也是OK的,
restTemplate有问题,因为我们没有new,我们在启动类上加一点小小的配置
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean会把实例化的RestTemplate以方法名去命名,他的名称是restTemplate,启动7901,也就是movie这个微服务
localhost:7901/movie/1
<?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.learn</groupId>
<artifactId>microservice-simple-consumer-movie</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>microservice-simple-consumer-movie</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
#debug=true
server.port=7901
user.userServicePath: http://10.40.8.152:7900/simple/
package com.learn.cloud.entity;
import java.math.BigDecimal;
public class User {
private Long id;
private String username;
private String name;
private Short age;
private BigDecimal balance;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Short getAge() {
return this.age;
}
public void setAge(Short age) {
this.age = age;
}
public BigDecimal getBalance() {
return this.balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
package com.learn.cloud.controller;
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.RestController;
import org.springframework.web.client.RestTemplate;
import com.learn.cloud.entity.User;
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@Value("${user.userServicePath}")
private String userServicePath;
@GetMapping("/movie/{id}")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject(this.userServicePath + id, User.class);
}
}
package com.learn.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class MicroserviceSimpleConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MicroserviceSimpleConsumerMovieApplication.class, args);
}
}
是可以正常请求到user服务的,是可以用到user服务的接口的,同时写了一个服务消费者,我们总结一下需要注意点,
首先是服务提供者,第一个是@GetMapping,它是一个组合注解,是Spring4.3给我们提供的一个注解,yml是有严格的
缩进的,可以是两个空格缩进,也可以是四个空格缩进,然后回到movie这个项目,@Bean注解,我们用方法名称,作为
实例化后的Bean的名称
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
相当于RestTemplate restTemplate = new RestTemplate();
目前这个架构其实是存在很多问题的,我现在是在消费的这一侧,把这个硬编码了,在传统的一些项目里面,大概没有太多
的问题了,因为这些环境都是比较固定的,IP端口都是比较固定的,但是在现在这种新式的环境下,就不一样了,比如说docker,
在一些云环境里面,IP和端口往往是动态的,当我把provider-user部署好了之后,他的IP和端口往往是动态的,那这个时候我们
怎么去进行一个配置呢,加上我provider重新的进行上限,他的IP进行了变更,那这边怎么样去玩呢,所以硬编码是行不通的,
但是即使这样也不行
@Value("${user.userServicePath}")
private String userServicePath;
user.userServicePath: http://10.40.8.152:7900/simple/
当然变得好维护了一些,当他的IP和端口发生了变化的时候,我这边也要重新的配置,重新的启动,当他又为其它的服务提供服务,
那别的服务是不是也得改配置,这种维护量是很大的,这是第一个问题,第二个问题,movie微服务调用user微服务,当然它是提供者,
现在这种情况是直接调,但是他有一定的问题,当我有多个提供的节点的时候,我怎么做到负载,中间加Nginx,由Nginx完成转发,
但是我们要知道,在一个复杂的项目里面,几百个微服务,甚至上万个,这都是有可能的,像亚马逊光首页,700个微服务,那是不是只要
有服务调用的时候,就加个nginx,那这个也是非常的难管理,那怎么去解决目前的这种问题呢