SpringCloud eureka feign
1. 分布式-SpringCloud-服务实现 Netflix Eureka 2. 介绍 Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目 spring-cloud-netflix中,实现SpringCloud的服务发现功能。Eureka包含两个组件: Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也包含一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会 向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有 接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90 秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
3. 启动eureka服务器 3.1. Maven依赖 父项目pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <properties > <spring-cloud.version > Hoxton.SR3</spring-cloud.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring-cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement >
子项目pom.xml
1 2 3 4 5 6 7 8 <artifactId > edueureka</artifactId > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency > </dependencies >
3.2. 配置eureka-server 1 2 3 4 5 6 7 8 9 10 server.port =8003 spring.application.name =online-eureka eureka.client.register-with-eureka =false eureka.client.fetch-registry =false eureka.client.service-url.defaultZone =http://127.0.0.1:${server.port}/eureka/
3.3. 启动@EnableEurekaServer 1 2 3 4 5 6 7 8 @SpringBootApplication @EnableEurekaServer public class EurekaApplcation { public static void main (String[] args) { SpringApplication.run(EurekaApplcation.class,args); } }
3.4. 访问http://localhost:8003/ 4. eureka客户端 - eduservice,videoservice 4.1. eureka-client Maven依赖 1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
4.2. 配置eureka-client 1 2 3 4 5 6 7 8 9 10 11 server.port =8001 spring.application.name =online-edu eureka.instance.prefer-ip-address =true eureka.client.service-url.defaultZone =http://127.0.0.1:8003/eureka/
4.3. 启动客户端 1 2 3 4 5 6 7 8 @SpringBootApplication @EnableEurekaClient public class EduServiceApplication { public static void main (String[] args) { SpringApplication.run(EduServiceApplication.class,args); } }
4.4. 启动eduservice,videoservice 服务器端出现注册信息
Registered instance ONLINE-EDU/192.168.0.101:online-edu:8001 with status UP (replication=true)
Registered instance ONLINE-VIDEO/192.168.0.101:online-video:8002 with status UP (replication=true)
5. 删除课程并删除阿里云中对应的video 5.1. eduservice调用videoservice 5.2. 添加Maven依赖 1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
5.3. 主配置类中配置@EnableFeignClients 1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class EduServiceApplication { public static void main (String[] args) { SpringApplication.run(EduServiceApplication.class,args); } }
5.4. 添加接口@FeignClient videoservice中的videocontroller的访问路径就是/videoservice/video/delete/{videoId}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @CrossOrigin @RequestMapping("/videoservice/video") public class VideoController { @Autowired VideoService videoService; @DeleteMapping("/delete/{videoId}") public R deleteById (@PathVariable String videoId) { videoService.deleteVideoById(videoId); return R.ok().message("删除视频成功" ); } }
@FeignClient注解用于指定从哪个服务中调用功能 ,名称与被调用的服务名保持一致。
@RequestMapping注解用于对被调用的微服务进行地址映射。
@PathVariable注解一定要指定参数名称 ,否则出错
@Component注解防止,在其他位置注入CodClient时idea报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.online.edu.eduservice.client;import com.online.edu.common.R;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient("online-video") @Component public interface VodClient { @DeleteMapping("/videoservice/video/delete/{videoId}") public R deleteAliyunVideo (@PathVariable("videoId") String videoId) ; }
5.5. 添加到删除课程方法中 - 单个+多个 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @AutoWired VodClient vodClient; public boolean deleteById (String id) { String videoId = baseMapper.selectById(id).getVideoSourceId(); if (!StringUtils.isEmpty(videoId)) { vodClient.deleteAliyunVideo(videoId); } int flag = baseMapper.deleteById(id); return flag>0 ; } @Override public void deleteWrapper (QueryWrapper<EduVideo> queryWrapper) { queryWrapper.select("video_source_id" ); List<EduVideo> videos = baseMapper.selectList(queryWrapper); for (EduVideo video:videos){ vodClient.deleteAliyunVideo(video.getVideoSourceId()); } baseMapper.delete(queryWrapper); }
5.6. 使用swagger-ui测试 -error feign.RetryableException: Read timed out executing DELETE
大概是响应时间限制,响应默认为3s
1 2 3 feign.httpclient.connection-timeout =2000 feign.httpclient.connection-timer-repeat =3000
修改时间
1 2 3 feign.httpclient.connection-timeout =20000 feign.httpclient.connection-timer-repeat =30000
5.7. 成功返回操作 1 2 3 4 5 6 7 8 9 INFO com.netflix.discovery.DiscoveryClient - DiscoveryClient_ONLINE-EDU/192.168.0.101:online-edu:8001: registering service... INFO s.d.s.web.plugins.DocumentationPluginsBootstrapper - Context refreshed INFO s.d.s.web.plugins.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s) INFO c.n.loadbalancer.DynamicServerListLoadBalancer - DynamicServerListLoadBalancer for client online-video initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=online-video,current list of Servers=[192.168.0.101:8002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:192.168.0.101:8002; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList :org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@71b75228 INFO com.netflix.config.ChainedDynamicProperty - Flipping property: online-video.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
6. 统计日注册人数 6.1. 用户注册表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 drop table if exists ucenter_member;create table ucenter_member ( id char (19 ) not null comment '会员id' , openid varchar (128 ) default null comment '微信openid' , mobile varchar (20 ) default null comment '手机号' , password varchar (255 ) default null comment '密码' , nickname varchar (50 ) default null comment '昵称' , sex tinyint(2 ) unsigned default null comment '性别 1 女,2 男' , age tinyint(3 ) unsigned default null comment '年龄' , avatar varchar (255 ) default null comment '用户头像' , sign varchar (100 ) default null comment '用户签名' , is_disabled tinyint(1 ) not null default '0' comment '是否禁用 1(true)已禁用, 0(false)未禁用' , is_deleted tinyint(1 ) not null default '0' comment '逻辑删除 1(true)已删除, 0(false)未删除' , gmt_create datetime not null comment '创建时间' , gmt_modified datetime not null comment '更新时间' , primary key (id) ) comment'会员表' ;
6.2. 统计某天注册的人数 1 select count (* ) from ucenter_member u where Date (u.gmt_create)= '2019-01-19'
6.3. UcenterMemberController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.online.edu.memberservice.controller;import com.online.edu.common.R;import com.online.edu.memberservice.service.UcenterMemberService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/memberservice/ucenter-member") @CrossOrigin public class UcenterMemberController { @Autowired UcenterMemberService ucenterMemberService; @GetMapping("/getDailyRegisterNumber/{day}") public R getDailyRegisterNumber (@PathVariable String day) { int count = ucenterMemberService.getDailyRegisterMember(day); return R.ok().data("registerNum" ,count); } }
6.4. UcenterMemberMapper.xml 1 2 3 4 5 6 7 8 9 <?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.online.edu.memberservice.mapper.UcenterMemberMapper" > <select id ="getRegisterNum" resultType ="java.lang.Integer" > select count(*) from ucenter_member u where Date(u.gmt_create) = #{day} </select > </mapper >
6.5. UcenterMemberMapper 1 2 3 4 5 public interface UcenterMemberMapper extends BaseMapper <UcenterMember> { Integer getRegisterNum (String day) ; }
6.6. 配置扫描xml 1 2 3 4 5 mybatis-plus.mapper-locations =classpath:com/online/edu/memberservice/mapper/xml/*.xml mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
6.7. UcenterMemberServiceImpl 1 2 3 4 5 6 7 8 @Service public class UcenterMemberServiceImpl extends ServiceImpl <UcenterMemberMapper, UcenterMember> implements UcenterMemberService { @Override public Integer getDailyRegisterMember (String day) { return baseMapper.getRegisterNum(day); } }
6.8. 测试swagger-ui
6.9. 控制台 1 2 3 4 5 ==> Preparing: select count(*) from ucenter_member u where Date(u.gmt_create) = ? ==> Parameters: 2019-01-02(String) <== Columns: count(*) <== Row: 2 <== Total: 1
6.10. 将注册人数加入到统计表中 7. 统计结果放入日统计表 7.1. 日统计表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 drop table if exists statistics_daily;create table statistics_daily ( id char (19 ) not null comment '主键' , date_calculate varchar (20 ) not null comment '统计日期' , register_num int (11 ) not null default '0' comment '注册人数' , login_num int (11 ) not null default '0' comment '登录人数' , video_view_num int (11 ) not null default '0' comment '每日播放视频数' , course_num int (11 ) not null default '0' comment '每日新增课程数' , gmt_create datetime not null comment '创建时间' , gmt_modified datetime not null comment '更新时间' , primary key (id), key statistics_day (date_calculate) )comment= '网站统计日数据' ;
7.2. 调用memberservice项目路径,得到日注册量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.online.edu.statisticsservice.client;import com.online.edu.common.R;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient("online-member") @Component public interface MemberClient { @GetMapping("/memberservice/ucenter-member/getDailyRegisterNumber/{day}") public R getDailyRegisterNumber (@PathVariable("day") String day) ; }
7.3. StatisticsDailyServiceImpl调用MemberClient 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.online.edu.statisticsservice.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.online.edu.common.R;import com.online.edu.statisticsservice.client.MemberClient;import com.online.edu.statisticsservice.entity.StatisticsDaily;import com.online.edu.statisticsservice.mapper.StatisticsDailyMapper;import com.online.edu.statisticsservice.service.StatisticsDailyService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class StatisticsDailyServiceImpl extends ServiceImpl <StatisticsDailyMapper, StatisticsDaily> implements StatisticsDailyService { @Autowired MemberClient memberClient; @Override public boolean getDailyRegister (String day) { QueryWrapper<StatisticsDaily> queryWrapper = new QueryWrapper <>(); queryWrapper.eq("date_calculate" ,day); baseMapper.delete(queryWrapper); Integer count = (Integer) memberClient.getDailyRegisterNumber(day).getData().get("registerNum" ); StatisticsDaily daily = new StatisticsDaily (); daily.setDateCalculate(day); daily.setRegisterNum(count); daily.setCourseNum((int )(Math.random()*200 )); daily.setLoginNum((int )(Math.random()*200 )); daily.setVideoViewNum((int )(Math.random()*200 )); int flag= baseMapper.insert(daily); return flag>0 ; } }
7.4. StatisticsDailyController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.online.edu.statisticsservice.controller;import com.online.edu.common.R;import com.online.edu.statisticsservice.service.StatisticsDailyService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/statisticsservice/statistics-daily") @CrossOrigin public class StatisticsDailyController { @Autowired StatisticsDailyService dailyService; @GetMapping("/dailyRegister/{day}") public R getDailyRegister (@PathVariable String day) { boolean flag=dailyService.getDailyRegister(day); if (flag) { return R.ok().message("生成成功" ); }else { return R.error().message("生成失败" ); } } }
7.5. 生成语句 1 2 3 ==> Preparing: INSERT INTO statistics_daily ( id, date_calculate, register_num, login_num, video_view_num, course_num, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? ) ==> Parameters: 1236923304708902913(String), 2019-01-19(String), 5(Integer), 137(Integer), 56(Integer), 155(Integer), 2019-04-19 15:54:31.425(Timestamp), 2019-04-19 15:54:31.425(Timestamp) < == Updates: 1
8. 前端通过日期选择统计当天注册数 8.1. router/index.js - 生成路由链接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { path : '/statistic' , component : Layout , redirect : '/statistic/list' , name : '统计分析' , meta : { title :'统计分析' , icon : 'chart' }, children :[ { path : 'generte' , name : '生成数据' , component : () => import ('@/views/edu/statistic/generte' ), meta : {title :'生成数据' , icon : 'guide' } }, { path : 'list' , name : '图表显示' , component : () => import ('@/views/edu/statistic/list' ), meta : { title : '图表显示' , icon : 'table' } } ] }
8.2. api/edu/statistis.js - 请求方法 1 2 3 4 5 6 7 8 9 10 11 12 import request from '@/utils/request' const base = '/statisticsservice/statistics-daily' export default { getDailyRegisterNum (day ){ return request ({ url :`${base} /dailyRegister/` +day, method :'get' }) } }
8.3. views/edu/statistic/generte.vue - 页面展示 el-date-picker 日期组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <template> <div class="app-container"> <el-form :inline="true" class="demo-form-inline"> <el-form-item> <el-date-picker v-model="date" type="date" placeholder="请选择日期" value-format="yyyy-MM-dd"/> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-s-open" @click="getDailyRegisterNum">生成</el-button> </el-form-item> </el-form> </div> </template> <script> import statistic from '@/api/edu/statistic' export default { data(){ return{ date:'' } }, created(){ }, methods:{ getDailyRegisterNum(){ statistic.getDailyRegisterNum(this.date).then(response=>{ this.$message({ type:'success', message:response.message }) this.$router.push({ path:'/statistic/list' }) }) } } } </script>