微服务技术是分布式架构(把服务做拆分)的一种
而springcloud仅仅是解决了拆分时的微服务治理的问题,其他更复杂的问题并没有给出解决方案
一个完整的微服务技术要包含的不仅仅是springcloud
order表user_id虽然和user表进行关联,但是因为是跨database的,因此无法进行关联查询
项目中所有的微服务:
以下是两个分别的application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
将两个都启动:
在浏览器中(下载Chrome插件Json Viewer)
那么问题就转变成了:如何在java代码中(像浏览器一样)发起http请求,这样就能实现远程调用了
Spring提供了一个工具——RestTemplate,用来发http请求
通过Bean的方式把RestTemplate注册为其中的一个对象,然后将来可以在任何地方注入这个对象来使用
写在哪里呢?Bean的注入只能放在配置类中,而启动类(OrderApplication)(带有@SpringBootApplication)本身也是配置类,所以完全可以在这里写Bean的注入
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
/**
* 创建RestTemplate并注入Spring容器
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
修改OrderService
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 0.注入RestTemplate
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.利用RestTemplate发送http请求,查询用户
// 2.1.url路径
String url = "http://localhost:8081/user/" + order.getUserId();
// 2.2.发送http请求,实现远程调用(getForObject默认返回json,加上User.class参数g告诉它返回值的类型,自动转化成User对象)
User user = restTemplate.getForObject(url, User.class);
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
}
刚才将user-service服务的ip和端口硬编码在代码中的,有一定问题
所以不能用硬编码方式
每一个服务启动时都会把自己的信息注册给eureka
搭建EurekaServer需要创建一个独立的微服务
starter是springboot中的自动装配,也就是说在这个依赖中已经帮我们把springeureka的所有的配置都做好了,也就是说我们可以零配置直接拿来用了
当然,自动装配是需要有开关的,第二步这个注解就是eurekaserver自动装配的开关
第三步需要编写一些配置信息,端口信息、服务名称、eureka的地址信息
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
注意到我们引入的时候并没有指定版本信息,以前学习springboot时也遇到过这种情况,原因是在我们的父工程中,已经把依赖的版本都管理好了,所以可以进入cloud-demo的父工程的pom文件看,可以看到spring-boot-starter-parent的版本为2.3.9.RELEASE,以及spring-cloud版本和其他,并且有spring-cloud-dependencies依赖库,里面有大量的springcloud的组件及其版本信息,全都定义好了,所以在我们引入springcloud组件时,我们无需指定任何版本信息
2、编写main函数并添加注解
新建cn.itcast.eureka.EurekaApplication类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3、编写一个配置文件
resources下新建application.yml
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
注意由于eureka自己也是一个微服务,会将自己也注册到eureka上(这是为了将来eureka集群去用的),所以它也需要一个名字,而且还需要再配一个eureka的地址eureka.client.service-url
也就是说“这里是为了做服务注册才配置这些信息”
然后点击EurekaApplication左边的三角形启动,就可以在下面的服务中看到多出来了EurekaApplication
点击这个蓝色的端口号,会自动跳转到浏览器
status中的up表示正常状态,down表示挂掉了,up后面的是ip
可以看到,除了依赖不一样(刚才时eureka的服务端,现在是eureka的客户端),这个配置和刚才在eureka中做的配置文件长得非常像。为什么?因为eureka启动时会把自己注册到eureka,所以它也需要配服务名称和地址。所以服务名称和地址的配置实际上是服务注册的配置,只要配了它,就可以做注册
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
然后配置yml文件
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice # user服务的服务名称
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
然后重启UserApplication
接下来把Order服务也注册到Eureka,同上,然后重启
这里每个服务都只有一个实例,我们可以每个服务启动多个实例
(在这里配置端口,是为了覆盖yml文件的端口的)
然后把它也启动,
我们希望orderservice可以基于服务名称拉取到userservice的两个实例信息,然后实现负载均衡
用服务名称代替ip、端口:在eureka中可以看到userservice这个服务名代指的就是这两个端口
要在多个实例中负载均衡,要给之前用到的RestTemplate这个Bean加一个注解@LoadBalanced
都弄完之后,我们把两个UserApplication的都清空,因为我们想知道OrderApplication究竟使用的是哪一个UserApplication,因为在OrderService中url中只是userservice,没有加任何ip地址
直接在浏览器中访问userservice/user/1是无法访问的
IRule接口决定了负载均衡的策略
ZoneAvoidanceRule是默认
注意第一种代码方式是全局的,也就是说我一旦配了上面这种方案,在orderservice中不管你是调用哪一个微服务,都是randomrule;而第二种配置文件方式会先指定服务名称,再指定负载均衡的规则,只针对某个服务而言。
重启OrderApplication,
达到了非常恐怖的311毫秒,然后再刷新一次,
时长就只有19毫秒了
创建LoadBalanceClient的过程中还要做服务的拉取,因此时间长
(这个配置是在orderservice的application.yml文件)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice # order服务的服务名称
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称
- userservice
- xxservice
这个ribbon与上面userservice下的ribbon不冲突
<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.0modelVersion>
<groupId>cn.itcast.demogroupId>
<artifactId>cloud-demoartifactId>
<version>1.0version>
<modules>
<module>user-servicemodule>
<module>order-servicemodule>
<module>eureka-servermodule>
modules>
<packaging>pompackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.9.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.SR10spring-cloud.version>
<mysql.version>5.1.47mysql.version>
<mybatis.version>2.1.1mybatis.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.version}version>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
project>
<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">
<parent>
<artifactId>cloud-demoartifactId>
<groupId>cn.itcast.demogroupId>
<version>1.0version>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>eureka-serverartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
<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">
<parent>
<artifactId>cloud-demoartifactId>
<groupId>cn.itcast.demogroupId>
<version>1.0version>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>user-serviceartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
<build>
<finalName>appfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice # user服务的服务名称
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
@MapperScan("cn.itcast.user.mapper")
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return userMapper.findById(id);
}
}
@Data
public class User {
private Long id;
private String username;
private String address;
}
@Mapper
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User findById(@Param("id") Long id);
}
<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">
<parent>
<artifactId>cloud-demoartifactId>
<groupId>cn.itcast.demogroupId>
<version>1.0version>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>order-serviceartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice # order服务的服务名称
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称
- userservice
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
/**
* 创建RestTemplate并注入Spring容器
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 0.注入RestTemplate
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.利用RestTemplate发送http请求,查询用户
// 2.1.url路径
String url = "http://userservice/user/" + order.getUserId();
// 2.2.发送http请求,实现远程调用(getForObject默认返回json,加上User.class参数自动转化成User对象)
User user = restTemplate.getForObject(url, User.class);
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
}
@Data
public class User {
private Long id;
private String username;
private String address;
}
@Data
public class Order {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId;
private User user;
}
@Mapper
public interface OrderMapper {
@Select("select * from tb_order where id = #{id}")
Order findById(Long id);
}