在先後端分離架構中,服務層被拆分紅了不少的微服務,微服務的信息如何管理?Spring Cloud中提供服務註冊中
心來管理微服務信息。
爲何 要用註冊中心?
一、微服務數量衆多,要進行遠程調用就須要知道服務端的ip地址和端口,註冊中心幫助咱們管理這些服務的ip和
端口。
二、微服務會實時上報本身的狀態,註冊中心統一管理這些微服務的狀態,將存在問題的服務踢出服務列表,客戶
端獲取到可用的服務進行調用。css
Spring Cloud Eureka 是對Netflix公司的Eureka的二次封裝,它實現了服務治理的功能,Spring Cloud Eureka提
供服務端與客戶端,服務端便是Eureka服務註冊中心,客戶端完成微服務向Eureka服務的註冊與發現。服務端和
客戶端均採用Java語言編寫。下圖顯示了Eureka Server與Eureka Client的關係:html
一、Eureka Server是服務端,負責管理各各微服務結點的信息和狀態。前端
2 、在微服務上部署Eureka Client程序,遠程訪問Eureka Server將本身註冊在Eureka Server。
三、微服務須要調用另外一個微服務時從Eureka Server中獲取服務調用地址,進行遠程調用。nginx
Eureka Server 高可用環境須要部署兩個Eureka server,它們互相向對方註冊。若是在本機啓動兩個Eureka須要
注意兩個Eureka Server的端口要設置不同,這裏咱們部署一個Eureka Server工程,將端口可配置,製做兩個
Eureka Server啓動腳本,啓動不一樣的端口,以下圖:git
包結構:com.xuecheng.govern.centergithub
在Eureka Server工程添加:web
<dependencies> <!‐‐ 導入Eureka服務的依賴 ‐‐> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring‐cloud‐starter‐netflix‐eureka‐server</artifactId> </dependency> </dependencies>
@EnableEurekaServer//標識這是一個Eureka服務,開啓服務 @SpringBootApplication public class GovernCenterApplication { public static void main(String[] args) { SpringApplication.run(GovernCenterApplication.class, args); } }
須要在啓動類上用@EnableEurekaServer標識此服務爲Eureka服務算法
server: port: ${PORT:50101} #服務端口 spring: application: name: xc‐govern‐center #指定服務名 eureka: client: registerWithEureka: true #服務註冊,是否將本身註冊到Eureka服務中 fetchRegistry: true #服務發現,是否從Eureka中獲取註冊信息 serviceUrl: #Eureka客戶端與Eureka服務端的交互地址,高可用狀態配置對方的地址,單機狀態配置本身(若是 不配置則默認本機8761端口) defaultZone: ${EUREKA_SERVER:http://eureka02:50102/eureka/} server: enable‐self‐preservation: false #是否開啓自我保護模式 eviction‐interval‐timer‐in‐ms: 60000 #服務註冊表清理間隔(單位毫秒,默認是60*1000) instance: hostname: ${EUREKA_DOMAIN:eureka01}
啓動1:spring
啓動2:mongodb
運行兩個啓動腳本,分別瀏覽:
http://localhost:50101/
http://localhost:50102/
<!‐‐ 導入Eureka客戶端的依賴 ‐‐> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring‐cloud‐starter‐netflix‐eureka‐client</artifactId> </dependency>
eureka: client: registerWithEureka: true #服務註冊開關 fetchRegistry: true #服務發現開關 serviceUrl: #Eureka客戶端與Eureka服務端進行交互的地址,多箇中間用逗號分隔 defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/} instance: prefer‐ip‐address: true #將本身的ip地址註冊到Eureka服務中 ip‐address: ${IP_ADDRESS:127.0.0.1} instance‐id: ${spring.application.name}:${server.port} #指定實例id
在啓動類上添加註解 @EnableDiscoveryClient ,表示它是一個Eureka的客戶端
在先後端分離架構中,服務層被拆分紅了不少的微服務,服務與服務之間不免發生交互,好比:課程發佈須要調用
CMS服務生成課程靜態化頁面,本節研究微服務遠程調用所使用的技術。
下圖是課程管理服務遠程調用CMS服務的流程圖:
工做流程以下:
1 、cms服務將本身註冊到註冊中心。
二、課程管理服務從註冊中心獲取cms服務的地址。
三、課程管理服務遠程調用cms服務。
Ribbon是Netflix公司開源的一個負載均衡的項目(https://github.com/Netflix/ribbon),它是一個基於 HTTP、
TCP的客戶端負載均衡器。
負載均衡是微服務架構中必須使用的技術,經過負載均衡來實現系統的高可用、集羣擴容等功能。負載均衡可經過
硬件設備及軟件來實現,硬件好比:F五、Array等,軟件好比:LVS、Nginx等。
以下圖是負載均衡的架構圖:
上圖是服務端負載均衡,客戶端負載均衡與服務端負載均衡的區別在於客戶端要維護一份服務列表,Ribbon從
Eureka Server獲取服務列表,Ribbon根據負載均衡算法直接請求到具體的微服務,中間省去了負載均衡服務。
以下圖是Ribbon負載均衡的流程圖:
一、在消費微服務中使用Ribbon實現負載均衡,Ribbon先從EurekaServer中獲取服務列表。
二、Ribbon根據負載均衡的算法去調用微服務。
一、在客戶端添加Ribbon依賴:
這裏在課程管理服務配置ribbon依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring‐cloud‐starter‐ribbon</artifactId> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </dependency>
二、配置Ribbon參數
這裏在課程管理服務的application.yml中配置ribbon參數
ribbon: MaxAutoRetries: 2 #最大重試次數,當Eureka中能夠找到服務,可是服務連不上時將會重試 MaxAutoRetriesNextServer: 3 #切換實例的重試次數 OkToRetryOnAllOperations: false #對全部操做請求都進行重試,若是是get則能夠,若是是post,put等操做 沒有實現冪等的狀況下是很危險的,因此設置爲false ConnectTimeout: 5000 #請求鏈接的超時時間 ReadTimeout: 6000 #請求處理的超時時間
三、負載均衡測試
1)啓動兩個cms服務,注意端口要不一致
2)定義RestTemplate,使用@LoadBalanced註解
在課程管理服務的啓動類中定義RestTemplate
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(new OkHttp3ClientHttpRequestFactory()); }
3 )測試代碼
在課程管理服務工程建立單元測試代碼,遠程調用cms的查詢頁面接口:
//負載均衡調用 @Test public void testRibbon() { //服務id String serviceId = "XC‐SERVICE‐MANAGE‐CMS"; for(int i=0;i<10;i++){ //經過服務id調用 ResponseEntity<CmsPage> forEntity = restTemplate.getForEntity("http://" + serviceId + "/cms/page/get/5a754adf6abb500ad05688d9", CmsPage.class); CmsPage cmsPage = forEntity.getBody(); System.out.println(cmsPage); } }
4)負載均衡測試
添加@LoadBalanced註解後,restTemplate會走LoadBalancerInterceptor攔截器,此攔截器中會經過
RibbonLoadBalancerClient查詢服務地址,能夠在此類打斷點觀察每次調用的服務地址和端口,兩個cms服務會輪
流被調用。
Feign是Netflix公司開源的輕量級rest客戶端,使用Feign能夠很是方便的實現Http 客戶端。Spring Cloud引入
Feign而且集成了Ribbon實現客戶端負載均衡調用。
1 、在客戶端添加依賴
在課程管理服務添加下邊的依賴:
<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接口
參考Swagger文檔定義FeignClient,注意接口的Url、請求參數類型、返回值類型與Swagger接口一致。
在課程管理服務中建立client包,定義查詢cms頁面的客戶端該用接口,
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS) public interface CmsPageClient { @GetMapping("/cms/page/get/{id}") public CmsPage findById(@PathVariable("id") String id); }
三、啓動類添加@EnableFeignClients註解
四、測試
@RunWith(SpringRunner.class) @SpringBootTest public class FeignTest { @Autowired CmsPageClient cmsPageClient; @Test public void testFeign() { //經過服務id調用cms的查詢頁面接口 CmsPage cmsPage = cmsPageClient.findById("5a754adf6abb500ad05688d9"); System.out.println(cmsPage); } }
Feign 工做原理以下:
一、 啓動類添加@EnableFeignClients註解,Spring會掃描標記了@FeignClient註解的接口,並生成此接口的代理
對象
二、 @FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)即指定了cms的服務名稱,Feign會從註冊中
心獲取cms服務列表,並經過負載均衡算法進行服務調用。
三、在接口方法 中使用註解@GetMapping("/cms/page/get/{id}"),指定調用的url,Feign將根據url進行遠程調
用。
咱們在編寫一個頁面時須要知道哪些信息是靜態信息,哪些信息爲動態信息,下圖是頁面的設計圖:
打開靜態頁面,觀察每部分的內容。
紅色表示動態信息,紅色之外表示靜態信息。
紅色動態信息:表示一個按鈕,根據用戶的登陸狀態、課程的購買狀態顯示按鈕的名稱及按鈕的事件。
包括如下信息內容:
一、課程信息
課程標題、價格、課程等級、授課模式、課程圖片、課程介紹、課程目錄。
二、課程統計信息
課程時長、評分、收藏人數
三、教育機構信息
公司名稱、公司簡介
四、教育機構統計信息
好評數、課程數、學生人數
五、教師信息
老師名稱、老師介紹
一、配置靜態資源虛擬主機
靜態資源虛擬主機負責處理課程詳情、公司信息、老師信息、統計信息等頁面的請求:
將課程資料中的「靜態頁面目錄」中的目錄拷貝到F:/develop/xuecheng/static下
在nginx中配置靜態虛擬主機以下:
學成網靜態資源 server { listen 91; server_name localhost; #公司信息 location /static/company/ { alias F:/develop/xuecheng/static/company/; } #老師信息 location /static/teacher/ { alias F:/develop/xuecheng/static/teacher/; } #統計信息 location /static/stat/ { alias F:/develop/xuecheng/static/stat/; } location /course/detail/ { alias F:/develop/xuecheng/static/course/detail/; } }
2 、經過www.xuecheng.com虛擬主機轉發到靜態資源
因爲課程頁面須要經過SSI加載頁頭和頁尾因此須要經過www.xuecheng.com虛擬主機轉發到靜態資源
在www.xuecheng.com虛擬主機加入以下配置:
location /static/company/ { proxy_pass http://static_server_pool; } location /static/teacher/ { proxy_pass http://static_server_pool; } location /static/stat/ { proxy_pass http://static_server_pool; } location /course/detail/ { proxy_pass http://static_server_pool; }
配置upstream實現請求轉發到資源服務虛擬主機:
#靜態資源服務 upstream static_server_pool{ server 127.0.0.1:91 weight=10; }
門戶中的一些圖片、樣式等靜態資源統一經過/static路徑對外提供服務,在www.xuecheng.com虛擬主機中配置如
下:
#靜態資源,包括系統所須要的圖片,js、css等靜態資源 location /static/img/ { alias F:/develop/xc_portal_static/img/; } location /static/css/ { alias F:/develop/xc_portal_static/css/; } location /static/js/ { alias F:/develop/xc_portal_static/js/; } location /static/plugins/ { alias F:/develop/xc_portal_static/plugins/; add_header Access‐Control‐Allow‐Origin http://ucenter.xuecheng.com; add_header Access‐Control‐Allow‐Credentials true; add_header Access‐Control‐Allow‐Methods GET; }
cors 跨域參數:
Access-Control-Allow-Origin:容許跨域訪問的外域地址
若是容許任何站點跨域訪問則設置爲*,一般這是不建議的。
Access-Control-Allow-Credentials: 容許客戶端攜帶證書訪問
Access-Control-Allow-Methods:容許客戶端跨域訪問的方法
請求:http://www.xuecheng.com/course/detail/course_main_template.html測試課程詳情頁面模板是否能夠正
常瀏覽。
一、響應結果類型
@Data @ToString @NoArgsConstructor public class CourseView implements Serializable { CourseBase courseBase;//基礎信息 CourseMarket courseMarket;//課程營銷 CoursePic coursePic;//課程圖片 TeachplanNode TeachplanNode;//教學計劃 }
二、請求類型
String :課程id
三、接口定義以下
@ApiOperation("課程視圖查詢") public CourseView courseview(String id);
須要對course_base、course_market、course_pic、teachplan等信息進行查詢,
新建課程營銷的dao,其它dao已經存在不用再建。
public interface CourseMarketRepository extends JpaRepository<CourseMarket,String> { }
//課程視圖查詢 public CourseView getCoruseView(String id) { CourseView courseView = new CourseView(); //查詢課程基本信息 Optional<CourseBase> optional = courseBaseRepository.findById(id); if(optional.isPresent()){ CourseBase courseBase = optional.get(); courseView.setCourseBase(courseBase); } //查詢課程營銷信息 Optional<CourseMarket> courseMarketOptional = courseMarketRepository.findById(id); if(courseMarketOptional.isPresent()){ CourseMarket courseMarket = courseMarketOptional.get(); courseView.setCourseMarket(courseMarket); } //查詢課程圖片信息 Optional<CoursePic> picOptional = coursePicRepository.findById(id); if(picOptional.isPresent()){ CoursePic coursePic = picOptional.get(); courseView.setCoursePic(picOptional.get()); } //查詢課程計劃信息 TeachplanNode teachplanNode = teachplanMapper.selectList(id); courseView.setTeachplanNode(teachplanNode); return courseView; }
@Override @GetMapping("/courseview/{id}") public CourseView courseview(@PathVariable("id") String id) { return courseService.getCoruseView(id); }
使用test-freemarker工程測試模板
編寫模板過程採用test-freemarker工程測試模板。
將course.ftl拷貝到test-freemarker工程的resources/templates下,並在test-freemarker工程的controller中添加
測試方法
//課程詳情頁面測試 @RequestMapping("/course") public String course(Map<String,Object> map){ ResponseEntity<Map> forEntity = restTemplate.getForEntity("http://localhost:31200/course/courseview/4028e581617f945f01617f9dabc4 0000", Map.class); Map body = forEntity.getBody(); map.put("model",body); return "course"; }
模板編寫並測試經過後要在數據庫保存:
一、模板信息保存在xc_cms數據庫(mongodb)的cms_template表
二、模板文件保存在mongodb的GridFS中。
第一步:將模板文件上傳到GridFS中
因爲本教學項目中模板管理模塊沒有開發,因此咱們使用Junit代碼向GridFS中保存:
// 文件存儲2 @Test public void testStore2() throws FileNotFoundException { File file = new File("C:\\Users\\admin\\Desktop\\course.ftl"); FileInputStream inputStream = new FileInputStream(file); //保存模版文件內容 GridFSFile gridFSFile = gridFsTemplate.store(inputStream, "課程詳情模板文件",""); String fileId = gridFSFile.getId().toString(); System.out.println(fileId); }
保存成功須要記錄模板文件的id,即上邊代碼中的fileId。
第二步:向cms_template表添加模板記錄(請不要重複添加)
使用Studio 3T鏈接mongodb,向cms_template添加記錄:
{ "_class" : "com.xuecheng.framework.domain.cms.CmsTemplate", "siteId" : "5a751fab6abb5044e0d19ea1", "templateName" : "課程詳情頁面正式模板", "templateFileId" : "這裏填寫上邊代碼返回的模板文件id" }
課程預覽功能將使用cms系統提供的頁面預覽功能,業務流程以下:
一、用戶進入課程管理頁面,點擊課程預覽,請求到課程管理服務
二、課程管理服務遠程調用cms添加頁面接口向cms添加課程詳情頁面
三、課程管理服務獲得cms返回課程詳情頁面id,並拼接生成課程預覽Url
四、課程管理服務將課程預覽Url給前端返回
五、用戶在前端頁面請求課程預覽Url,打開新窗口顯示課程詳情內容
CMS已經提供了頁面預覽功能,課程預覽功能要使用CMS頁面預覽接口實現,下邊經過cms頁面預覽接口測試課
程預覽的效果。
一、向cms_page表插入一條頁面記錄或者從cms_page找一個頁面進行測試。
注意:頁面配置必定要正確,需設置正確的模板id和dataUrl。
以下,是一條頁面的記錄。
{ "_id" : ObjectId("5b3469f794db44269cb2bff1"), "_class" : "com.xuecheng.framework.domain.cms.CmsPage", "siteId" : "5a751fab6abb5044e0d19ea1", "pageName" : "4028e581617f945f01617f9dabc40000.html", "pageAliase" : "課程詳情頁面測試01", "pageWebPath" : "/course/detail/", "pagePhysicalPath" : "/course/detail/", "pageType" : "1", "pageCreateTime" : ISODate("2018‐02‐25T01:37:25.974+0000"), "templateId" : "5b345a6b94db44269cb2bfec", "dataUrl" : "http://localhost:31200/course/courseview/4028e581617f945f01617f9dabc40000" }
二、課程詳細頁面 使用ssi注意
因爲 Nginx先請求cms的課程預覽功能獲得html頁面,再解析頁面中的ssi標籤,這裏必須保證cms頁面預覽返回的
頁面的Content-Type爲text/html;charset=utf-8
在cms頁面預覽的controller方法中添加:
response.setHeader("Content‐type","text/html;charset=utf‐8");
三、測試
請求:http://www.xuecheng.com/cms/preview/5b3469f794db44269cb2bff1傳入頁面 Id,測試效果以下:
cms服務對外提供添加頁面接口,實現:若是不存在頁面則添加,不然就更新頁面信息。
此接口由課程管理服務在課程預覽時調用。
@ApiOperation(" 保存頁面") public CmsPageResult save(CmsPage cmsPage);
// 添加頁面,若是已存在則更新頁面 public CmsPageResult save(CmsPage cmsPage){ //校驗頁面是否存在,根據頁面名稱、站點Id、頁面webpath查詢 CmsPage cmsPage1 = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath()); if(cmsPage1 !=null){ //更新 return this.update(cmsPage1.getPageId(),cmsPage); }else{ //添加 return this.add(cmsPage); } }
@Override @PostMapping("/save") public CmsPageResult save(@RequestBody CmsPage cmsPage) { return pageService.save(cmsPage); }
此Api是課程管理前端請求服務端進行課程預覽的Api
請求:課程Id
響應:課程預覽Url
一、定義響應類型
@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);
在課程管理工程建立CMS服務的Feign Client,經過此Client遠程請求cms添加頁面。
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS) public interface CmsPageClient{ //保存頁面 @PostMapping("/cms/page/save") public CmsPageResult save(@RequestBody CmsPage cmsPage); }
一、配置添加頁面參數信息
在application.yml中配置:
course‐publish: siteId: 5b30cba5f58b4411fc6cb1e5 templateId: 5b345a6b94db44269cb2bfec previewUrl: http://www.xuecheng.com/cms/preview/ pageWebPath: /course/detail/ pagePhysicalPath: /course/detail/ dataUrlPre: http://localhost:31200/course/courseview/
二、代碼以下:
@Value("${course‐publish.dataUrlPre}") private String publish_dataUrlPre; @Value("${course‐publish.pagePhysicalPath}") private String publish_page_physicalpath; @Value("${course‐publish.pageWebPath}") private String publish_page_webpath; @Value("${course‐publish.siteId}") private String publish_siteId; @Value("${course‐publish.templateId}") private String publish_templateId; @Value("${course‐publish.previewUrl}") private String previewUrl; //根據id查詢課程基本信息 public CourseBase findCourseBaseById(String courseId){ Optional<CourseBase> baseOptional = courseBaseRepository.findById(courseId); if(baseOptional.isPresent()){ CourseBase courseBase = baseOptional.get(); return courseBase; } ExceptionCast.cast(CourseCode.COURSE_GET_NOTEXISTS); return null; } //課程預覽 public CoursePublishResult preview(String courseId){ CourseBase one = this.findCourseBaseById(courseId); //發佈課程預覽頁面 CmsPage cmsPage = new CmsPage(); //站點 cmsPage.setSiteId(publish_siteId);//課程預覽站點 //模板 cmsPage.setTemplateId(publish_templateId); //頁面名稱 cmsPage.setPageName(courseId+".html"); //頁面別名 cmsPage.setPageAliase(one.getName()); //頁面訪問路徑 cmsPage.setPageWebPath(publish_page_webpath); //頁面存儲路徑 cmsPage.setPagePhysicalPath(publish_page_physicalpath); //數據url cmsPage.setDataUrl(publish_dataUrlPre+courseId); //遠程請求cms保存頁面信息 CmsPageResult cmsPageResult = cmsPageClient.save(cmsPage); if(!cmsPageResult.isSuccess()){ return new CoursePublishResult(CommonCode.FAIL,null); } //頁面id String pageId = cmsPageResult.getCmsPage().getPageId(); //頁面url String pageUrl = previewUrl+pageId; return new CoursePublishResult(CommonCode.SUCCESS,pageUrl); }
@Override @PostMapping("/preview/{id}") public CoursePublishResult preview(@PathVariable("id") String id) { return courseService.preview(id); }
頁面預覽swagger測試
頁面預覽前端測試