SpringCloud系列 (三)基本案例

898 阅读6分钟

基本案例

第 1 节 案例说明

本部分我们按照普通⽅式模拟⼀个微服务之间的调⽤(后续我们将⼀步步使⽤ Spring Cloud的组件对案例进⾏改造)。

拉勾App⾥有这样⼀个功能:“⾯试直通⻋”,当求职⽤户开启了⾯试直通⻋之后,会根据企业客户的招聘岗位需求进⾏双向匹配。其中有⼀个操作是:为企业⽤户开启⼀个定时任务,根据企业录⼊的⽤⼈条件,每⽇匹配⼀定数量的应聘者“投递”到企业的资源池中去,那么系统在将匹配到的应聘者投递到资源池的时候需要先检查:此时应聘者默认简历的状态(公开/隐藏),如果此时默认简历的状态已经被应聘者设置为“隐藏”,那么不再执⾏“投递”操作。 “⾃动投递功能”在“⾃动投递微服务”中,“简历状态查询功能”在“简历微服务”中,那么就涉及到“⾃动投递微服务”调⽤“简历微服务”查询简历。在这种场景下,“⾃动投递微服务”就是⼀个服务消费者,“简历微服务”就是⼀个服务提供者。

第 2 节 案例数据库环境准备

简历基本信息表 r_resume

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for r_resume
-- ----------------------------
DROP TABLE IF EXISTS `r_resume`;
CREATE TABLE `r_resume` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sex` varchar(10) DEFAULT NULL COMMENT '性别',
  `birthday` varchar(30) DEFAULT NULL COMMENT '出生日期',
  `work_year` varchar(100) DEFAULT NULL COMMENT '工作年限',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `status` varchar(80) DEFAULT NULL COMMENT '目前状态',
  `resumeName` varchar(500) DEFAULT NULL COMMENT '简历名称',
  `name` varchar(40) DEFAULT NULL,
  `createTime` datetime DEFAULT NULL COMMENT '创建日期',
  `headPic` varchar(100) DEFAULT NULL COMMENT '头像',
  `isDel` int(2) DEFAULT NULL COMMENT '是否删除 默认值0-未删除 1-已删除',
  `updateTime` datetime DEFAULT NULL COMMENT '简历更新时间',
  `userId` int(11) DEFAULT NULL COMMENT '用户ID',
  `isDefault` int(2) DEFAULT NULL COMMENT '是否为默认简历 0-默认 1-非默认',
  `highestEducation` varchar(20) DEFAULT '' COMMENT '最高学历',
  `deliverNearByConfirm` int(2) DEFAULT '0' COMMENT '投递附件简历确认 0-需要确认 1-不需要确认',
  `refuseCount` int(11) NOT NULL DEFAULT '0' COMMENT '简历被拒绝次数',
  `markCanInterviewCount` int(11) NOT NULL DEFAULT '0' COMMENT '被标记为可面试次数',
  `haveNoticeInterCount` int(11) NOT NULL DEFAULT '0' COMMENT '已通知面试次数',
  `oneWord` varchar(100) DEFAULT '' COMMENT '一句话介绍自己',
  `liveCity` varchar(100) DEFAULT '' COMMENT '居住城市',
  `resumeScore` int(3) DEFAULT NULL COMMENT '简历得分',
  `userIdentity` int(1) DEFAULT '0' COMMENT '用户身份1-学生 2-工人',
  `isOpenResume` int(1) DEFAULT '3' COMMENT '人才搜索-开放简历 0-关闭,1-打开,2-简历未达到投放标准被动关闭 3-从未设置过开放简历',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2195388 DEFAULT CHARSET=utf8;
 
SET FOREIGN_KEY_CHECKS = 1;

第 3 节 案例⼯程环境准备

我们基于SpringBoot来构造⼯程环境,我们的⼯程模块关系如下所示:

  1. 建立一个父工程 lagou-parent
  2. 建立一个公共模块 lagou-service-common 项目提供数据模型,公共类,组件
  3. 建立一个简历微服务模块 lagou-service-resume 项目完成CRUD,提供API接口
  4. 建立一个自动投递微服务模块 lagou-service-autodeliver 通过接口调用简历微服务模块

整个项目的结构如下图所示:

3.1 构建lagou-parent父工程

<?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">
 	....

    <!--⽗⼯程打包⽅式为pom-->
    <packaging>pom</packaging>
    <!--spring boot ⽗启动器依赖-->

    <!--spring boot 父启动器依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <dependencies>
        <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--⽇志依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <!--测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--lombok⼯具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- Actuator可以帮助你监控和管理Spring Boot应⽤-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!--打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2 公共服务

  1. 新建maven项目名为:lagou-service-common

  2. 导入依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
  1. 新建简历的pojo
@Data
@Entity
@Table(name="r_resume")
public class Resume {
    @Id
    private Long id; // 主键
    private String sex; // 性别
    private String birthday; // ⽣⽇
    ...
}

3.3 简历微服务

此项目针对简历进行查询操作,并且提供查询接口,查询某个简历的开放状态

新建模块 lagou-service-resume,分别创建常规的CRUD操作,建立Dao,Service,Controller三层,外加一个启动程序

ResumeDao

public interface ResumeDao extends JpaRepository<Resume,Long> {
}

ResumeService

public interface ResumeService {
    Resume findDefaultResumeByUserId(Long userId);
}

ResumeServiceImpl

@Service
public class ResumeServiceImpl implements ResumeService {

    @Autowired
    private ResumeDao resumeDao;

    @Override
    public Resume findDefaultResumeByUserId(Long userId) {
        Resume resume=new Resume();
        resume.setUserId(userId);
        resume.setIsDefault(1);
        Example<? extends Resume> example=Example.of(resume);
        return resumeDao.findOne(example).get();
    }
}

ResumeController

@RestController
@RequestMapping("/resume")
public class ResumeController {

    @Autowired
    private ResumeService resumeService;

    //http://localhost:8080/resume/openstate/1545133
    @GetMapping("/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable Long userId){

        return resumeService.findDefaultResumeByUserId(userId).getIsOpenResume();
    }
}

LagouResumeApplication

@SpringBootApplication
@EntityScan("com.lagou.edu.pojo")
public class LagouResumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(LagouResumeApplication.class, args);
    }
}

application.yml文件

server:
  port: 8080
spring:
  application:
    name: lagou-service-resume

  datasource:
   driver-class-name: com.mysql.jdbc.Driver
   #url: jdbc:mysql://localhost:3306/lagou?useUnicode=true&characterEncoding=utf8
   url: jdbc:mysql://localhost:3306/lagou?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
   username: root
   password: root

  jpa:
   database: MySQL
   show-sql: true
   hibernate:
    naming:
     physical-strategy:
      org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #避免将驼峰命名转换为下划线命名

3.4 投递服务

投递服务就是简单的调用简历微服务的接口,完成查询

AutodeliverApplicaton

@SpringBootApplication
public class AutodeliverApplicaton {
    public static void main(String[] args) {
        SpringApplication.run(AutodeliverApplicaton.class,args);
    }
    //使用Resttemple进行远程调用,先对注入该对象
    @Bean
    public RestTemplate getRestTemplete(){
        return new RestTemplate();
    }
}

AutoDeliverController

@RestController
@RequestMapping("/autodeliver")
public class AutoDeliverController {
    @Autowired
    private RestTemplate restTemplate;

    //http://localhost:8090/autodeliver/checkState/1545133
    @GetMapping("/checkState/{userId}")
    public Integer findResumeOpenState(@PathVariable Long userId){
        //调用远程服务--> 简历微服务

        //直接拿到结果
        Integer forObject = restTemplate.getForObject("http://localhost:8080/resume/openstate/" + userId, Integer.class); 
        return forObject;
    }
}

application.yml

server:
  port: 8090

第 4 节 案例代码问题分析

我们在⾃动投递微服务中使⽤RestTemplate调⽤简历微服务的简历状态接⼝时(Restful API 接⼝)。在微服务分布式集群环境下会存在什么问题呢?怎么解决?

存在的问题:

  • 1)在服务消费者中,我们把url地址硬编码到代码中,不⽅便后期维护。
  • 2)服务提供者只有⼀个服务,即便服务提供者形成集群,服务消费者还需要⾃⼰实现负载均衡。
  • 3)在服务消费者中,不清楚服务提供者的状态。
  • 4)服务消费者调⽤服务提供者时候,如果出现故障能否及时发现不向⽤户抛出异常⻚⾯?
  • 5)RestTemplate这种请求调⽤⽅式是否还有优化空间?能不能类似于Dubbo那样玩?
  • 6)这么多的微服务统⼀认证如何实现?
  • 7)配置⽂件每次都修改好多个很麻烦!?
  • 8)....

上述分析出的问题,其实就是微服务架构中必然⾯临的⼀些问题:

  • 1)服务管理:⾃动注册与发现、状态监管
  • 2)服务负载均衡
  • 3)熔断
  • 4)远程过程调⽤
  • 5)⽹关拦截、路由转发
  • 6)统⼀认证
  • 7)集中式配置管理,配置信息实时⾃动更新

总结 相信看到这里,大家都有所共鸣。这不就是我们平常的CRUD么?然后维护一个别人写好的接口地址,根据业务来进行调用,别人蹦了,自己也就蹦了。稍微好一点的,还能做点日志记录,微信推送。企业内部业务出了问题也没太大关系,挂了就挂了。是时候彻底改变这种无力回天的现象了

基本演示代码 github.com/langkemaoxi…