SpringCloud+Eureka+Feign+Ribbon的简化搭建流程,加入熔断,网关和Redis缓存[2]
个人微信公众号:程序猿的月光宝盒
前提:本篇是基于
SpringCloud+Eureka+Feign+Ribbon的简化搭建流程和CRUD练习[1]
的修改与拓展
1.修改consumer
的CenterFeign.java
,把返回值全部设置为String
/** * 是consumer调用provider(需要指定provider的名字) * 请求的清单列表:规定调用地址、参数、返回值 * 在正常走通的时候不走CenterFeignFallBack,当provider down的时候走熔断器,相当于是类的try-catch */ @FeignClient(name = "provider",fallback = CenterFeignFallBack.class) public interface CenterFeign { @GetMapping("/optionData.do") public String optionData(); @PostMapping("/showEmpData.do") //Feign:不支持对象传参,所以要用@RequestBody public String showEmpData(@RequestBody Emp emp); @PostMapping("/add.do") public String add(@RequestBody Emp emp); @PostMapping("/edit.do") public String edit(@RequestBody Emp emp); @GetMapping("/del.do") public String del(@RequestParam("empno") Integer empno); }
2.在CenterFeign.java
的同包下创建实现了CenterFeign
接口的CenterFeignFallBack.java
作为熔断器
@Component public class CenterFeignFallBack implements CenterFeign { @Override public String optionData() { return "provider-optionData-error"; } @Override public String showEmpData(Emp emp) { return "provider-showEmpData-error"; } @Override public String add(Emp emp) { return "provider-add-error"; } @Override public String edit(Emp emp) { return "provider-edit-error"; } @Override public String del(Integer empno) { return "provider-del-error"; } }
3.修改consumer
的CenterController.java
,返回值全改为String
@RestController public class CenterController{ @Resource private CenterFeign centerFeign; @GetMapping("/optionData-consumer.do") public String optionData() { return centerFeign.optionData(); } @PostMapping("/showEmpData-consumer.do") public String showEmpData(@RequestBody Emp emp) { return centerFeign.showEmpData(emp); } @PostMapping("/add-consumer.do") public String add(@RequestBody Emp emp) { return centerFeign.add(emp); } @PostMapping("/edit-consumer.do") public String edit(@RequestBody Emp emp) { return centerFeign.edit(emp); } @GetMapping("/del-consumer.do") public String del(@RequestParam("empno") Integer empno) { return centerFeign.del(empno); } }
4.确认consumer
中的配置文件application.properties
中有没有开启熔断feign.hystrix.enabled=true
,没有的话加上
#端口号 server.port=8764 #应用名 spring.application.name=consumer #eureka客户端服务url默认区域 eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ #开启熔断器 feign.hystrix.enabled=true #下线名称.ribbon.NF加载平衡规则类名,这里先注释 provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
5.开启服务测试
访问http://localhost:8761/
发现所有服务已经注册
正常走provider没有问题
走consumer也没有问题,只是返回的格式是字符串类型,不是json类型,但是没关系,格式还是json的格式,前台该怎么转还能怎么转,不影响
那怎么走容错?
现在手动停止一个provider
因为这里我开了负载均衡策略(在consumer中的配置文件中provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
),所以有一定几率触发熔断器,
这就相当于类之间的try-catch,没有熔断器的话这里百分百是报错误代码.那这里我先把负载均衡关掉,在测试有没有走容错,(猜一下,会走吗?)
这里我测了这么多次,都没有走熔断,所以显然不走,为何?
因为没有手动设置负载均衡策略的话,默认走的是轮询.机制,啥是Ribbon轮询机制?
简单的说就是有abc三台服务器,正常的情况下走a->b->c,在有一台down了的时候,那台就自动跳过,比如b down了,那么就是a->c
以上,个人理解,若有误请指正,よろしくお願いします~
现在,既然一台服务器工作是没有问题 那我两台provider全部停止呢?
那答案是肯定的,绝壁走容错~~
6.测试完没有问题,现在添加网关功能模块
新建模块(Spring initializr)zuul-client
7.编辑配置文件application.properties
#网关端口 server.port=8765 #应用名 spring.application.name=zuul #网关路径提供者,后面的**表示:xxx.do zuul.routes.provider=/pro/**/ #网关路径消费者,后面的**表示:xxx.do zuul.routes.consumer=/con/**/
8.在启动类上添加注解
//开启网关代理 @EnableZuulProxy //开启eureka客户端 @EnableEurekaClient @SpringBootApplication public class ZuulClientApplication { public static void main(String[] args) { SpringApplication.run(ZuulClientApplication.class, args); } }
9.开启服务测试
测试provider
测试consumer
10.网关作用
统一了所有客户端的ip地址和端口号,我们只要给不同层级的应用起别名就ok了(就是这里的)
11.加入Redis缓存
11.1在provider-one
模块的pom文件中加入redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
11.2修改provider-one
模块的DeptServiceImpl
文件,加入@Autowired private RedisTemplate redisTemplate;
@Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Autowired private RedisTemplate redisTemplate; @Override public Map<String, Object> optionData() { Map<String, Object> map=new HashMap<>(); List<Map<String, Object>> list = deptMapper.selAllDeptData(); // 定义Redis存储集合的对象 ListOperations<String,Map<String,Object>> redisList = redisTemplate.opsForList(); //拼接Redis中存储数据对应的key String key = "depts"; //判断Redis中是否有key,没有就说明是第一次访问,将数据放入Redis if(!redisTemplate.hasKey(key)){ //直接将数据库查询出来的值放入Redis System.out.println("provider-one-optionData的值已经放入Redis"); redisList.leftPushAll(key,list); } map.put("data",list); return map; } }
11.3对应的 修改DeptService
,返回值变成map
public interface DeptService { Map<String,Object> optionData(); }
11.4修改CenterController
,把返回值类型改为Map
public Map<String, Object> optionData() { return deptService.optionData(); }
11.5同样的在provider-two
的pom.xml中加入redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
11.6修改provider-two
模块的DeptServiceImpl
文件,加入@Autowired private RedisTemplate redisTemplate;
@Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Autowired private RedisTemplate redisTemplate; @Override public Map<String, Object> optionData() { Map<String, Object> map=new HashMap<>(); List<Map<String, Object>> list = deptMapper.selAllDeptData(); // 定义Redis存储集合的对象 ListOperations<String,Map<String,Object>> redisList = redisTemplate.opsForList(); //拼接Redis中存储数据对应的key String key = "depts"; //判断Redis中是否有key,没有就说明是第一次访问,将数据放入Redis if(!redisTemplate.hasKey(key)){ //直接将数据库查询出来的值放入Redis System.out.println("provider-two-optionData的值已经放入Redis"); redisList.leftPushAll(key,list); } map.put("data",list); return map; } }
11.7对应的 修改DeptService
,返回值变成map
public interface DeptService { Map<String,Object> optionData(); }
11.8修改CenterController
,把返回值类型改为Map
public Map<String, Object> optionData() { return deptService.optionData(); }
11.9更新provider-one
模块的配置文件application.properties
,加入Redis配置
#服务端口号 server.port=8762 #应用名 spring.application.name=provider #eureka客户端服务url默认区域 eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ #数据源驱动类名 spring.datasource.driver-class-name=com.mysql.jdbc.Driver #数据源url spring.datasource.url=jdbc:mysql:///kh75 #数据源用户名 spring.datasource.username=root #数据源密码 spring.datasource.password=admin #后期会写mapper.xml,这里先注释 #mybatis.mapper-locations=classpath:mapper/*.xml #给实体类起别名,同样这里先注释 #mybatis.type-aliases-package=cn.kgc.vo #配置Redis spring.redis.port=6379 #redis所在的主机地址 spring.redis.host=xxx.xxx.xxx spring.redis.database=0
11.10更新provider-two
模块的配置文件application.properties
,加入Redis配置
#服务端口号 server.port=8763 #应用名 spring.application.name=provider #eureka客户端服务url默认区域 eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ #数据源驱动类名 spring.datasource.driver-class-name=com.mysql.jdbc.Driver #数据源url spring.datasource.url=jdbc:mysql:///kh75 #数据源用户名 spring.datasource.username=root #数据源密码 spring.datasource.password=admin #后期会写mapper.xml,这里先注释 #mybatis.mapper-locations=classpath:mapper/*.xml #给实体类起别名,同样这里先注释 #mybatis.type-aliases-package=cn.kgc.vo #配置Redis spring.redis.port=6379 #redis所在的主机地址 spring.redis.host=xxx.xxx.xx spring.redis.database=0
11.11更新consumer
的CenterFeign,所有返回值都是map
@FeignClient(name = "provider",fallback = CenterFeignFallBack.class) public interface CenterFeign { @GetMapping("/optionData.do") Map<String,Object> optionData(); @PostMapping("/showEmpData.do") //Feign:不支持对象传参,所以要用@RequestBody Map<String,Object> showEmpData(@RequestBody Emp emp); @PostMapping("/add.do") Map<String,Object> add(@RequestBody Emp emp); @PostMapping("/edit.do") Map<String,Object> edit(@RequestBody Emp emp); @GetMapping("/del.do") Map<String,Object> del(@RequestParam("empno") Integer empno); }
11.12更新CenterFeignFallBack
@Component public class CenterFeignFallBack implements CenterFeign { private Map<String, Object> map = new HashMap<> (); @Autowired private RedisTemplate redisTemplate; @Override public Map<String,Object> optionData() { ListOperations<String,Map<String,Object>> redisList = redisTemplate.opsForList(); map.put("msg","provider-optionData-error"); map.put("feign-data",redisList.range("depts",0,-1)); return map; } @Override public Map<String,Object> showEmpData(Emp emp) { ListOperations<String,Map<String,Object>> redisList = redisTemplate.opsForList(); map.put("msg","provider-showEmpData-error"); //这里对应的serviceImp里面还没修改成Redis,先放着 map.put("feign-data",redisList.range("emps",0,-1)); return map; } @Override public Map<String,Object> add(Emp emp) { map.put("msg","provider-add-error"); return map; } @Override public Map<String,Object> edit(Emp emp) { map.put("msg","provider-edit-error"); return map; } @Override public Map<String,Object> del(Integer empno) { map.put("msg","provider-del-error"); return map; } }
11.13再修改consumer中的CenterController,返回值也全改成Map
@RestController public class CenterController{ @Resource private CenterFeign centerFeign; @GetMapping("/optionData-consumer.do") public Map<String,Object> optionData() { return centerFeign.optionData(); } @PostMapping("/showEmpData-consumer.do") public Map<String,Object> showEmpData(@RequestBody Emp emp) { return centerFeign.showEmpData(emp); } @PostMapping("/add-consumer.do") public Map<String,Object> add(@RequestBody Emp emp) { return centerFeign.add(emp); } @PostMapping("/edit-consumer.do") public Map<String,Object> edit(@RequestBody Emp emp) { return centerFeign.edit(emp); } @GetMapping("/del-consumer.do") public Map<String,Object> del(@RequestParam("empno") Integer empno) { return centerFeign.del(empno); } }
11.14检查consumer的pom文件里是否有加入Redis依赖,没有加上...
11.15 打开所有模块进行测试
把提供者断掉,理论上走熔断和redis数据
以上....