0➡1的SpringCloud速通

什么是分布式微服务

拆分大系统为各个微服务,同一服务多节点部署。springcloud实现各远程服务的调用。

Nacos:服务发现

nacos服务端+微服务(注册)发现端

服务注册

1.依赖引入

    spring-cloud-starter-alibaba-nacos-discovery

2.配置nacos服务端地址

    application.properties

    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

3.服务注册
@EnableDiscoveryClient //开启服务发现功能!!!
@SpringBootApplication
public class ProductMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductMainApplication.class, args);
    }
}

此时可在nacos可视化界面看见被注册的服务

4.服务发现
使用Spring Cloud Commons提供的DiscoveryClient
@Autowired
DiscoveryClient discoveryClient;
@Test
void discoveryClientTest(){
    //遍历服务
    for (String service : discoveryClient.getServices()) {
        System.out.println("service: " + service);
        //遍历各服务地址
        List<ServiceInstance> instances = discoveryClient.getInstances(service);
        for (ServiceInstance instance : instances) {
            System.out.println("ip:"+instance.getHost()+" port: " +instance.getPort());
        }
    }
}
使用nacos提供的NacosServiceDiscovery
@Autowired
NacosServiceDiscovery nacosServiceDiscovery;
@Test
void nacosServiceDiscoveryTest() throws NacosException {
    for (String service : nacosServiceDiscovery.getServices()) {
        System.out.println("service: " + service);
        List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(service);
        for (ServiceInstance instance : instances) {
            System.out.println("ip:"+instance.getHost()+" port: "+instance.getPort());
        }
    }
}
5.服务调用

使用远程调用类RestTemplate详解

创建Bean

@Configuration
public class UserConfiguration {
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

调用接口

@Autowired
RestTemplate restTemplate;
@Test
void testRestTemplate() {
    String forObject = restTemplate.getForObject("http://localhost:8080/test", String.class);
    System.out.println(forObject);
}

负载均衡

加载依赖 <font style="color:rgb(38,44,49);">spring-cloud-starter-loadbalancer</font>

使用LoadBalancerClient类

将服务发现寻找服务地址部分替换为

//获取服务所有机器IP+port
ServiceInstance choose = loadBalancerClient.choose("service-product");
//获取请求地址
String url = "http://"+choose.getHost() +":" +choose.getPort() +"/product/"+productId;
使用@LoadBalanced注释

添加注释 <font style="color:rgb(38,44,49);">@</font><font style="color:rgb(35,41,48);">LoadBalanced</font>

服务调用中service-product 将会被动态替换

String url = "http://service-product/product/"+productId;
Product product = restTemplate.getForObject(url, Product.class);

配置中心

引入依赖 <font style="color:rgb(38,44,49);">spring-cloud-starter-alibaba-nacos-config</font>

<font style="color:rgb(38,38,38);">application.properties</font> 配置nacos服务端地址后添加

spring.config.import=nacos:service-order.properties
使用@RefreshScope注解

在Controller类加上注解 <font style="color:rgb(38,44,49);">@</font><font style="color:rgb(35,41,48);">RefreshScope</font>

使用ConfigurationProperties注解(自动刷新)

在实体类中加上 <font style="color:rgb(38,44,49);">@</font><font style="color:rgb(35,41,48);">ConfigurationProperties</font><font style="color:rgb(38,44,49);">(</font><font style="color:rgb(35,41,48);">prefix </font><font style="color:rgb(225,0,35);">= </font><font style="color:rgb(0,47,109);">"order"</font><font style="color:rgb(38,44,49);">) </font>

配置批量绑定在nacos下,可以无需@RefreshScope就能实现自动刷新

NacosConfigManager

实时监听配置变化

@Bean
ApplicationRunner applicationRunner(NacosConfigManager manager){
    //函数式接口
    return args -> {
        ConfigService configService = manager.getConfigService();
        //选定要监听的服务和组别
        configService.addListener("service-order.properties", "DEFAULT_GROUP", new Listener() {
            @Override
            public Executor getExecutor() {
                //监听器在线程池中运行
                return Executors.newFixedThreadPool(4);
            }
            //接收到的监听信息
            @Override
            public void receiveConfigInfo(String configInfo) {
                System.out.println("configInfo = " + configInfo);
            }
        });
    };
}

配置分组

namespace 命名空间

实现多环境隔离,如:开发、测试、预发、生产等

dataId 数据集id

配置文件的名字。完整写法: <font style="color:rgb(38,38,38);">名字.后缀</font> 如: <font style="color:rgb(38,38,38);">common.properties</font>

groupId 分组id

一般用微服务自己的名字

OpenFeign:服务调用

无需手动编码,自动处理向服务发送请求的过程,简化HTTP调用(就是不再需要使用Nacos提供的类手动编写服务调用过程)

引入依赖

<font style="color:rgb(38,44,49);">spring-cloud-starter-openfeign</font>

在主函数入口加上注释 <font style="color:rgb(38,44,49);">@</font><font style="color:rgb(35,41,48);">EnableFeignClients</font>

调用在nacos注册的服务

创建OpenFeign客户端

Order服务中 com.example.order.feign.ProductFeignClient 接口

//指定Feign客户端
@FeignClient("stores")
public interface StoreClient {
    //MVC注解标注在FeignClient中,代表发送请求的地址
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    //Feign支持将调用返回的原始json解析为指定类
    List<Store> getStores();

    @GetMapping("/stores")
    Page<Store> getStores(Pageable pageable);

    @PostMapping(value = "/stores/{storeId}", consumes = "application/json",params = "mode=upsert")
    Store update(@PathVariable("storeId") Long storeId, Store store);

    @DeleteMapping("/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId);
}

调用

在Controller中注入

@Autowired
ProductFeignClient productFeignClient;

即可直接使用

Product product = productFeignClient.getProductById(productId);

调用第三方Api

创建OpenFeign客户端

@FeignClient(value = "weather-client", url = "http://aliv18.data.moji.com")
public interface WeatherFeignClient {
    @PostMapping("/whapi/json/alicityweather/condition")
    String getWeather(@RequestHeader("Authorization") String auth,
                        @RequestParam("token") String token,
                        @RequestParam("cityld")string cityId);
}

注入接口后可直接调用

开启日志

Spring Cloud OpenFeign 功能 :: Spring Cloud Openfeign - Spring 框架

application.yml 配置

logging: 
 level: 
 com.atguigu.order.feign: debug

在 .config.OrderConfig 配置记录日志级别

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

失败处理

超时控制

设置服务限时等待,单一服务不可用将不会导致其他服务陷入循环等待。在这种情况下不可用的服务返回错误信息/返回兜底数据。

  • <font style="color:rgb(25, 30, 30);">connectTimeout</font> 防止因服务器长时间处理而阻塞调用者。默认10s
  • <font style="color:rgb(25, 30, 30);">readTimeout</font> 从连接建立时开始应用,并在返回响应花费太长时间时触发。默认60s

通过修改application文件修改某一服务的超时控制时间

失败重试

在容器 .config.OrderConfig 中配置bean

@Bean
Retryer retryer(){
    return new Retryer.Default();
}

请求拦截

OpenFeign提供的定制需求修改器

@Component
public class XTokenRequestInterceptor implements RequestInterceptor {
    //请求拦截器
    @Override
    public void apply(RequestTemplate template){
        System.out.println("XTokenRequestInterceptor .......");
        template.header("X-Token",UUID.randomUUID().tostring());
    }
}

Sentinel:服务保护

quick-start | Sentinel

引入依赖 <font style="color:rgb(38,44,49);">spring-cloud-starter-alibaba-sentinel</font>

保护各种资源(自动获取项目中编写的web接口/手动注入SphU API或@SentinelResource)

根据自定规则(流量规则、熔断规则、来源规则等)放行流量

整合

启动控制面板jar包 <font style="color:rgb(38,38,38);">java -jar sentinel-dashboard.jar</font>

配置控制面板

spring:
 cloud:
  sentinel:
   transport:
    dashboard: localhost:8080

异常处理

basic-api-resource-rule | Sentinel

结合OpenFeign重写fallback

实现返回默认数据/返回缓存数据

开启熔断
feign:
 sentinel:
  enabled: true
编写fallback函数
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
    @Override
    public Product getProductById(Long id) {
        System.out.println("兜底回调....");
        Product product = new Product();
        product.setId(id);
        product.setPrice(new BigDecimal("0"));
        product.setProductName("兜底商品");
        product.setNum(0);
        return product;
    }
}
将接口的错误回调指向fallback
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class) 
public interface ProductFeignClient {
    ...
}
对web接口使用自定义BlockExceptionHandler处理异常

默认的BlockExceptionHandler只输出一行string而非json,通过自定义可修改

.exception.MyBlockExceptionHandler

@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                        String resourceName, BlockException e) throws Exception {
        response.setStatus(429); //too many requests
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        //提前自定义返回数据类
        R error = R.error(500, resourceName + " 被Sentinel限制了,原因:" +e.getClass());
        String json = objectMapper.writeValueAsString(error);
        writer.write(json);
        writer.flush();
        writer.close();
     }
}
对@SentinelResource资源使用blockHandler处理异常

.service.OrderServiceImpl

//指定异常处理器
@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
@Override
    public Order createOrder(Long productId, Long userId) {
    // Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);
    //使用Feign完成远程调用
    Product product = productFeignClient.getProductById(productId);
    Order order = new Order();
    order.setId(1L);
    // 总金额
    order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
    order.setUserId(userId);
    order.setNickName("zhangsan");
    order.setAddress("china");
    //远程查询商品列表
    order.setProductList(Arrays.asList(product));
    return order;
}

//兜底回调
public Order createOrderFallback(Long productId, Long userId, BlockException e){
    Order order = new Order();
    order.setId(0L);
    order.setTotalAmount(new BigDecimal("0"));
    order.setUserId(userId);
    order.setNickName("未知用户");
    order.setAddress("异常信息:"+e.getClass());
    return order;
}
编写SphU调用
try {
    //资源名称
    AsyncEntry entry = SphU.asyncEntry(resourceName);

    // 异步调用.
    doAsync(userId, result -> {
        try {
            // 在此处处理异步调用的结果.
        } finally {
            // 在回调结束后 exit.
            entry.exit();
        }
    });
} catch (BlockException ex) {
    //如果流量超过限制抛出错误,进行处理
}

规则限制

流量控制FlowRule

flow-control | Sentinel

限制多余请求,防止系统资源被耗尽。

根据流控模式判断某一服务的请求是否超过阈值,若超过则产生流控效果

阈值类型

QPS:统计每秒请求数 (一般选用)

并发线程数:统计并发线程数

流控模式

链路策略:仅对某一访问路径生效

关联策略:优先分配资源给重要功能

流控效果

快速失败:抛出 BlockException 异常,若有 BlockExceptionHandler 则安装自定义的步骤处理异常

冷启动:逐步启动,使程序逐步适应 QPS-单机阈值 Period-启动周期

匀速排队:在快速失败基础上不直接丢弃请求,而是放入排队队列中等候执行。当排队时长超出服务timeout时长再被丢弃。

熔断控制

This article was updated on 七月 28, 2025