SpringCloud+Eureka+Feign+Ribbon的簡化搭建流程,加入熔斷,網關和Redis緩存 ...
作者:故事我忘了¢
個人微信公眾號:程式猿的月光寶盒
前提:本篇是基於
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數據
以上....