在前后端分离架构中,服务层被拆分成了很多的微服务,微服务的信息如何管理?Spring Cloud中提供服务注册中心来管理微服务信息。
注册中心作用:
1、微服务数量众多,要进行远程调用就需要知道服务端端ip地址和端口,注册中心帮助我们管理这些服务端ip和端口
2、微服务会实时上报自己的状态,注册中心统一管理这些微服务的状态,将存在问题的服务踢出服务列表,客户端获取到可用的服务进行调用。
Eureka注册中心:
Spring Cloud Eureka是对Netflix公司的Eureka的二次封装,他实现了服务治理的功能,Spring Cloud Eureka提供服务端和客户端,服务端即是Eureka服务注册中心,客户端完成微服务向Eureka服务端注册和发现。服务端和客户端均采用java语言编写。
1、Euraka server,负责管理各微服务节点的信息和状态
2、在微服务上部署Eureka Client程序,远程访问Eureka server将自己注册在Eureka server
3、微服务需要调用另一个微服务时从Eureka Server中获取服务调用地址,进行远程调用
注册中心实现:
一、创建工程:xc-govern-center
添加依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
二、编写启动类
@SpringBootApplication @EnableEurekaServer //表示这是一个Eureka服务 public class GovernCenterApplication { public static void main(String[] args) { SpringApplication.run(GovernCenterApplication.class); } }
三、编写配置文件
server: port: 50101 spring: application: name:xc-govern-center eureka: client: registerWithEureka: false #服务注册,是否将自己注册到Eureka服务中 fetchRegistry: false # 服务发现,是否从Eureka中获取注册信息 serviceUrl: defaultZone: http://loaclhost:50101/eureka server: enable-self-preservation: false #是否开启自我保护模式 eciction-interval-timer-in-ms : 60000 #服务注册表清理间隔
注意:Eureka Server高可用环境需要部署两个Eureka server,她们互相向对方注册。如果在本机启动两个Eureka需要注意两个Eureka server的端口要设置不一样。
服务注册功能实现
将cms向Eureka Server注册
一、添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
二、在application.yml中配置
eureka: client: registerWithEureka: true #注册服务开关 fetchRegistry: true #服务发现开关 serverUrl: defaultZone: http://loaclhost:50101/eureka instance: prefer-ip-address: true #将自己的ip地址注册到Eureka服务中 ip-address: 127.0.0.1 instance-id: ${spring-application-name}:${server-port} #指定实例id
三、在启动类上添加注解:
@EnableDiscoveryClient
Feign远程调用:
在前后端分离架构中,服务层被拆分成了很多的微服务,服务和服务之间难免发生交互,就需要进行微服务的远程调用。
1、服务将自己注册到注册中心
2、服务从注册中心获取所需要的服务地址
3、服务远程调用所需要的服务
Ribbon
Ribbon是Netflix公司开源的一个负载均衡的项目,它是一个基于HTTP、TCP的客户端负载均衡器
负载均衡是微服务框架中必须使用的技术,通过负载均衡来实现系统的高可用,集群扩容等功能,负载均衡可通过硬件设备及软件来实现。
用户请求先到达负载均衡器,负载均衡器根据负载均衡算法将请求转发到微服务。负载均衡算法有:轮询,随机,加权轮询,加权随机,地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力。
Ribbon从Eureka Server获取服务列表,Ribbon根据负载均衡算法直接请求到具体微服务,中间省去了负载均衡服务。
Spring Cloud引入Ribbon配合restTemplate实现客户端负载均衡。java中远程调用的技术有很多,如:webservice,socket,rmi,HttpClient,OkHttp等,互联网项目使用基于http的客户端比较多,本项目使用OkHttp。
一、添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </dependency>
二、配置文件
ribbon: MaxAutoRetries: 2 #最大重试次数 MaxAutoRetriesNextServer: 3 #切换实例大重试次数 OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是Post,Put等操作没有实现幂等的情况下是很危险的,所有设置为false ConnectTimeout: 5000 #请求连接的超时时间 ReadTimeout: 6000 #请求处理的超时时间
Feign
Feign是NetFlix公司开源的轻量级rest客户端,使用Feign可以非常方便的实现Http客户端,Spring Cloud引入Feign并且集成Ribbon实现客户端负载均衡调用。
一、添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
二、在接口上添加@FeignClient
三、启动类添加@EnableFeifnClient
工作原理:
1、启动类添加@EnableFeignClient注解,Spring会扫描标记@FeignClient注解的接口,并生成此接口的代理对象
2、@FeignClient注解指定了cms的服务名称,feign会从注册中心获取cms服务列表,并通过负载均衡算法进行服务调用
3、在接口方法中使用注解@GetMapping,指定url,feign将根据url进行远程调用
注意:
1、feignClient接口有参数在参数上必须加@PathVariable和@RequestParam
2、feignClient返回值为复杂对象时其类型必须有无参构造函数
课程预览实现
课程预览是未来保证课程发布后的正确性,通过课程预览可以直观的通过课程详情页面看到课程的信息是否正确,通过课程预览看到的页面内容和课程发布后的页面内容是一致的。
因为本页面的访问量会非常大,为了保证速度我们需要提前让页面生成html静态页面存储在nginx服务器用户直接访问nginx即可,对于一些动态信息可以访问服务端获取json数据在页面渲染
优点:使用nginx作为web服务器,并且直接访问html页面,性能出色
缺点:需要维护大量的静态页面,增加了维护的难度
课程数据模型查询功能
一、定义模型
@Data @ToString @NoArgsConstructor public class CourseView implements Serializable { CourseBase courseBase ; // 基础信息 CourseMarket courseMarket; //课程营销 CoursePic coursePic; //课程图片 TeachplanNode teachplanNode; //教学计划 }
二、定义接口
@ApiOperation("课程视图模型数据查询") public CourseView courseview(String id);
三、service
/** * 课程视图数据模型查询 * @param id * @return */ public CourseView getCourseView(String id){ CourseView courseView = new CourseView(); Optional<CourseBase> optional = courseBaseRepository.findById(id); if (optional.isPresent()){ CourseBase courseBase = optional.get(); courseView.setCourseBase(courseBase); } Optional<CourseMarket> optional1 = courseMarketRepository.findById(id); if (optional1.isPresent()){ CourseMarket courseMarket = optional1.get(); courseView.setCourseMarket(courseMarket); } Optional<CoursePic> optional2 = coursePicRepository.findById(id); if (optional2.isPresent()){ CoursePic coursePic = optional2.get(); courseView.setCoursePic(coursePic); } TeachplanNode teachplanNode = teachplanMapper.selectList(id); courseView.setTeachplanNode(teachplanNode); return courseView; }
四、controller
@Override @GetMapping("/courseview/{id}") public CourseView courseview(@PathVariable("id") String id) { return courseService.getCourseView(id); }
课程预览功能开发
业务流程:
1、用户进入课程管理页面,点击课程预览,请求到课程管理服务
2、课程管理服务远程调用cms添加页面接口向cms添加课程详情页面
3、课程管理服务得到cms返回课程详情页面id,并拼接生成课程预览url
4、课程管理服务将课程预览url给前端返回
5、用户在前端页面请求课程预览Url,打开新窗口显示课程详情内容
一、定义接口
@ApiOperation("保存页面") public CmsPageResult save(CmsPage cmsPage);
二、service
/** * 添加页面,如果已存在则更新页面 * @param cmsPage * @return */ public CmsPageResult save(CmsPage cmsPage){ CmsPage cmsPage1 = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(),cmsPage.getSiteId(),cmsPage.getPageWebPath()); if (cmsPage1 != null){ return this.update(cmsPage1.getPageId(),cmsPage); }else { return this.add(cmsPage); } }
三、controller
@Override @PostMapping("/save") public CmsPageResult save(CmsPage cmsPage) { return pageService.save(cmsPage); }
四、定义模型
@Data @ToString @NoArgsConstructor public class CoursePublishResult extends ResponseResult { String previewUrl; public CoursePublishResult(ResultCode resultCode,String previewUrl){ super(resultCode); this.previewUrl = previewUrl; } }
五、定义接口
@ApiOperation("预览课程") public CoursePublishResult preview(String id);
六、创建FeignClient
package com.xuecheng.manage_course.client; import com.xuecheng.framework.client.XcServiceList; import com.xuecheng.framework.domain.cms.CmsPage; import com.xuecheng.framework.domain.cms.response.CmsPageResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS) public interface CmsPageClient { //保存页面 @PostMapping("/cms/page/save") public CmsPageResult save(@RequestBody CmsPage cmsPage); }
七、service
/** * 课程预览 * @param courseid * @return */ public CoursePublishResult preview(String courseid){ CourseBase one = this.findCourseBaseById(courseid); CmsPage cmsPage = new CmsPage(); //TODO 页面内容设置 CmsPageResult cmsPageResult = cmsPageClient.save(cmsPage); if (cmsPageResult.isSuccess()){ return new CoursePublishResult(CommonCode.FAIL,null); } String pageId = cmsPageResult.getCmsPage().getPageId(); return new CoursePublishResult(CommonCode.SUCCESS,pageId); }
八、controller
@Override @PostMapping("/preview/{id}") public CoursePublishResult preview(@PathVariable("id") String id) { return courseService.preview(id); }
课程发布功能实现
业务流程:
1、用户进入教学管理中心,进入某个课程的管理界面
2、点击课程发布,前端请求到课程管理服务
3、课程管理服务远程调用CMS生成课程发布页面,CMS将课程详情页面发布到服务器
4、课程管理服务修改课程发布状态为已发布,并向前端返回发布成功
5、用户在教学管理中心点击课程详情页,查看课程详情页内容
CMS发布功能实现:
1、接收课程管理服务发布页面信息
2、将页面信息添加到数据库(MongoDB)
3、对页面信息进行静态化
4、将页面信息发布到服务器
一、定义模型
@Data @NoArgsConstructor public class CmsPostPageResult extends ResponseResult { String pageUrl; public CmsPostPageResult (ResultCode resultCode,String pageUrl){ super(resultCode); this.pageUrl = pageUrl; } }
二、定义接口
@ApiOperation("一键发布页面") public CmsPostPageResult postPageQuick(CmsPage cmsPage);
三、dao
@Repository public interface CmsSiteRepository extends MongoRepository<CmsSite,String> { }
四、service
/** * 一键发布页面 * @param cmsPage * @return */ public CmsPostPageResult postPageQuick(CmsPage cmsPage){ CmsPageResult save = this.save(cmsPage); if (!save.isSuccess()){ return new CmsPostPageResult(CommonCode.FAIL,null); } CmsPage cmsPage1 = save.getCmsPage(); String pageId = cmsPage1.getPageId(); ResponseResult responseResult = this.postpage(pageId); if (!responseResult.isSuccess()){ return new CmsPostPageResult(CommonCode.FAIL,null); } String siteId = cmsPage1.getSiteId(); CmsSite cmsSite = this.findCmsSiteById(siteId); String siteDomain = cmsSite.getSiteDomain(); String siteWebPath = cmsSite.getSiteWebPath(); String pageWebPath = cmsPage1.getPageWebPath(); String pageName = cmsPage1.getPageName(); String pageUrl = siteDomain+siteWebPath+pageWebPath+pageName; return new CmsPostPageResult(CommonCode.SUCCESS,pageUrl); } /** * 根据id查询站点信息 * @param siteId * @return */ public CmsSite findCmsSiteById(String siteId){ Optional<CmsSite> optional = cmsSiteRepository.findById(siteId); if (optional.isPresent()){ return optional.get(); } return null; }
五、controller
@Override @PostMapping("/postPageQuick") public CmsPostPageResult postPageQuick(CmsPage cmsPage) { return pageService.postPageQuick(cmsPage); }