【业务功能107】微服务-springcloud-springboot-Sentinel容器安装-熔断降级限流

Sentinel

  • 在微服务架构下,会涉及到 微服务A需要远程调用其他的微服务B,C,D等的接口,比如商品服务接口,需要调用库存服务数据,秒杀服务数据等,这里就会衍生一个长链路的调用过程,那么一旦下游需要被调用的数据接口出现异常,那么就会导致整个上游的接口都会报错无法使用,所以就需要去做相应的方案措施,避免上游服务异常。

image.png

image.png

image.png

一、熔断、降级、限流

1.熔断

  服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。

停止是说,当前服务一旦对下游服务进行熔断,当请求到达时,当前服务不再对下游服务进行调用,而是使用设定好的策略(如构建默认值)直接返回。

暂时是说,熔断后,并不会一直不再调用下游服务,而是以一定的策略(如每分钟调用 10 次,若均返回成功,则增大调用量)试探调用下游服务,当下游服务恢复可用时,自动停止熔断。

image.png

2.降级

  降级是指当自身服务压力增大时,采取一些手段,增强自身服务的处理能力,以保障服务的持续可用。比如,下线非核心服务以保证核心服务的稳定、降低实时性、降低数据一致性。

  为了预防某些功能出现负荷过载或者响应慢的情况,在其内部暂时舍弃一些非核心接口和数据的请求(如评论、积分),而直接返回一个提前准备好的 fallback(退路) 错误处理信息。释放CPU和内存资源,以保证整个系统的稳定性和可用性。

3.限流

  限流是指上游服务对本服务请求 QPS 超过阙值时,通过一定的策略(如延迟处理、拒绝处理)对上游服务的请求量进行限制,以保证本服务不被压垮,从而持续提供稳定服务。常见的限流算法有滑动窗口、令牌桶、漏桶等。

二、Sentinel

1.其他组件对比

image.png

2.Sentinel介绍

  随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 的历史:

  • 2012 年,Sentinel 诞生,主要功能为入口流量控制。
  • 2013-2017 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量归整场景以及生产实践。
  • 2018 年,Sentinel 开源,并持续演进。
  • 2019 年,Sentinel 朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对 Service Mesh 场景也推出了 Envoy 集群流量控制支持,以解决 Service Mesh 架构下多语言限流的问题。
  • 2020 年,推出 Sentinel Go 版本,继续朝着云原生方向演进。

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。

3. 基本概念及作用

基本概念:

资源:是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

主要作用:

  1. 流量控制
  2. 熔断降级
  3. 系统负载保护

我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:

  1. 定义资源
  2. 定义规则
  3. 检验规则是否生效

先把可能需要保护的资源定义好,之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。

4.Sentinel应用

官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

4.1 Sentinel控制台搭建

下载对应的jar包:https://github.com/alibaba/Sentinel/releases 下载适合自己的版本

通过命令行启动:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

启动后需要输入账号密码:sentinel sentinel

image.png

登录成功后看到的页面

image.png

4.2 服务监控

  如果我们需要把自己的服务被Sentinel监控,那么我们需要添加对应的依赖

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>

然后需要添加对应的属性信息

image.png

然后我们访问请求后,在控制台就可以看到对应的监控信息了

image.png

然后我们可以指定简单的限流规则

image.png

表示1秒中内只能有一个QPS。超过就限流

image.png

测试我们发送请求快一点就会出现限流的处理

4.3 实时监控

  实时监控这块需要引入Acuator

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>

endpoint端点:

image.png

4.4 限流信息自定义

@Component
public class SentinelUrlBlockHandler implements BlockExceptionHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {R r = R.error(500,"访问太快,稍后再试!");httpServletResponse.setContentType("application/json;charset=UTF-8");httpServletResponse.getWriter().write(JSON.toJSONString(r));}
}

image.png

5.sentinel容器安装

  直接在windows中安装Sentinel启动管理还是不太方便,所以我们还是把Sentinel安装在Docker容器中

拉取对应的镜像文件

docker pull bladex/sentinel-dashboard:1.7.2

启动容器

docker run --name sentinel  -d -p 8858:8858 -d  bladex/sentinel-dashboard:1.7.2

开机自启动

docker update --restart=always sentinel

访问:

image.png

对应的客户端的使用:需要调整的配置信息

image.png

image.png

6.Sentinel监控所有服务

在公共微服务中引入以上说明的两个依赖,使得每个需要进行限流降级操作的微服务进入sentinel监控中。

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>

  在每一个服务都添加Sentinel的配置 以及 Acuator实时监控的端点配置,这里都是重复的配置信息,也可以配置在nacos配置中心

spring:cloud:sentinel:transport:port: 8719client-ip: 192.168.56.1     dashboard: 192.168.56.100:8858
management:endpoints:web:exposure:include: '*'

image.png

7.熔断降级

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

场景一 远程服务调用异常 熔断降级实例: 刷新商品页面详情会调用到多个接口包括远程服务接口
商品服务product中–>>会调用本身服务多个接口方法–>>同时也会通过fegin调用到其他的微服务 比如秒杀服务,假如此时秒杀服务接口异常(异常有很多种,比如我们模拟的时候,可以直接把秒杀服务关了…或者接口代码去抛异常…),那么商品服务的请求链路会因为秒杀服务异常而导致整个接口请求异常,页面报错,那么我们通过代码的方式 设置熔断机制,使得即使fegin远程调用的接口异常返回一个回调相应数据,来确保业务逻辑不报错

1.添加对应的配置

feign:sentinel:enabled: true

image.png

2.将涉及fegin远程调用的秒杀服务查询接口添加托底的方法,回调类

@Slf4j
@Component
public class SeckillFeignServiceFallback implements SeckillFeignService {@Overridepublic R getSeckillSessionBySkuId(Long skuId) {log.error("熔断降级....SeckillFeignService:{}",skuId);return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());}
}

image.png

3.在商品服务的调用远程服务接口中增加注解的属性值指定前面定义的回调类:fallback = SeckillFeignServiceFallback.class

@FeignClient(value = "mall-seckill",fallback = SeckillFeignServiceFallback.class)
public interface SeckillFeignService {@GetMapping("/seckill/seckillSessionBySkuId")R getSeckillSessionBySkuId(@RequestParam("skuId") Long skuId);
}

image.png


4.设置好回调类信息,需要进一步的优化业务逻辑的响应,避免出现空指针异常,需要先判断返回值的响应代码,如果是正常的r.getCode() == 0再做进一步的调用

CompletableFuture<Void> seckillFuture = CompletableFuture.runAsync(() -> {// 查询商品的秒杀活动R r = seckillFeignService.getSeckillSessionBySkuId(skuId);if(r.getCode() == 0){SeckillVO seckillVO = JSON.parseObject(r.get("data").toString(),SeckillVO.class);vo.setSeckillVO(seckillVO);}}, threadPoolExecutor);

场景二 远程服务调用慢 延时 熔断降级实例:刷新商品页面详情会调用到多个接口包括远程服务接口
与场景一相同,只不过此时我们模拟远程调用接口时长久一些,调用线程睡眠方法,比如设置500毫秒,然后
可以通过我们安装好的sentinel启动访问客户端,访问商品服务接口中调用到的 远程服务接口,点击编辑降级规则,熔断策略设置:慢调用比例, 最大RT200毫秒,比例阈值0.1 ,熔断时长10s,最小请求数5

熔断策略
Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

手动配置:客户端的Sentinel版本需要和服务器的版本要保持一致。

    @ResponseBody@GetMapping("/seckillSessionBySkuId")public R getSeckillSessionBySkuId(@RequestParam("skuId") Long skuId){System.out.println("seckillSessionBySkuId -----------------------");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}SeckillSkuRedisDto dto = seckillService.getSeckillSessionBySkuId(skuId);return  R.ok().put("data",JSON.toJSONString(dto));}

image.png

8.自定义资源

详情看官网
https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E5%AE%9A%E4%B9%89%E8%B5%84%E6%BA%90

定义资源

  • 方式一:主流框架的默认适配
    为了减少开发的复杂程度,我们对大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。您只需要引入对应的依赖即可方便地整合 Sentinel。可以参见: 主流框架的适配。 比如前面结合Feign

  • 方式二:抛出异常的方式定义资源
    SphU 包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:


// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {// 被保护的业务逻辑// do something here...
} catch (BlockException ex) {// 资源访问阻止,被限流或被降级// 在此处进行相应的处理操作
}
  • 方式四:注解方式定义资源
    Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理。示例:
// 原本的业务方法.
@SentinelResource(blockHandler = "blockHandlerForGetUser")
public User getUserById(String id) {throw new RuntimeException("getUserById command failed");
}// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User blockHandlerForGetUser(String id, BlockException ex) {return new User("admin");
}

带入实际业务接口 秒杀商品信息接口实例:

 public List<SeckillSkuRedisDto> blockHandler(BlockException blockException){log.error("限流执行的 blockHandler 方法 ....{}",blockException.getMessage());return null;}/*** 查询出当前时间内的秒杀活动及对应的商品SKU信息* @return*/@SentinelResource(value = "currentSeckillSkusResources",blockHandler = "blockHandler")@Overridepublic List<SeckillSkuRedisDto> getCurrentSeckillSkus() {// 1.确定当前时间是属于哪个秒杀活动的long time = new Date().getTime();try (Entry entry = SphU.entry("getCurrentSeckillSkusResources")) {// 被保护的业务逻辑// 从Redis中查询所有的秒杀活动Set<String> keys = redisTemplate.keys(SeckillConstant.SESSION_CHACE_PREFIX + "*");for (String key : keys) {//seckill:sessions1656468000000_1656469800000String replace = key.replace(SeckillConstant.SESSION_CHACE_PREFIX, "");// 1656468000000_1656469800000String[] s = replace.split("_");Long start = Long.parseLong(s[0]); // 活动开始的时间Long end = Long.parseLong(s[1]); // 活动结束的时间if(time > start && time < end){// 说明的秒杀活动就是当前时间需要参与的活动// 取出来的是SKU的ID  2_9List<String> range = redisTemplate.opsForList().range(key, -100, 100);BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);List<String> list = ops.multiGet(range);if(list != null && list.size() > 0){List<SeckillSkuRedisDto> collect = list.stream().map(item -> {SeckillSkuRedisDto seckillSkuRedisDto = JSON.parseObject(item, SeckillSkuRedisDto.class);return seckillSkuRedisDto;}).collect(Collectors.toList());return collect;}}}} catch (BlockException ex) {// 资源访问阻止,被限流或被降级log.error("getCurrentSeckillSkusResources被限制访问了...");// 在此处进行相应的处理操作}return null;}

9.网关流控

  • 前面所做的限流都是基于具体的业务微服务中,在业务代码去限制,而我们微服务架构中也有网关服务Gateway,请求通过网关来转发到具体的微服务中,我们也可以在网关处做限流动作,可以参考官网的指南: 网关流量控制

这里演示一个简单的例子,在网关服务pom文件,导入一个依赖,然后与前面介绍的使用方式一致,直接请求我们的电商首页等接口信息,因为这些都是通过网关转发,然后在sentinel客户端页面刷新,就可以打开看的网关服务的接口信息,请求链路栏目中可以看的API的名称,是网关服务定义的路由id 值如:- id: msbmall_host_route1,点击 流控 按钮 进行相应的 阈值设置即可生效

网关服务依赖引入:

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId><version>2021.1</version></dependency>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/77015.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

800V威迈斯车载充电机

800V威迈斯车载充电机VMAX二合一车载充电系统高压平台&#xff0c;将传统的6.6KW双向OBC、2.5KW DC\DC进行系统级的高功率密度集成&#xff1b;采用磁集成技术方案&#xff0c;创新性地将产品的原边绕组&#xff0c;高压绕组及低压Busbar进行系统级的集成&#xff1b;OBC功能转…

力扣(LeetCode)算法_C++——稀疏矩阵的乘法

给定两个 稀疏矩阵 &#xff1a;大小为 m x k 的稀疏矩阵 mat1 和大小为 k x n 的稀疏矩阵 mat2 &#xff0c;返回 mat1 x mat2 的结果。你可以假设乘法总是可能的。 示例 1&#xff1a; 输入&#xff1a;mat1 [[1,0,0],[-1,0,3]], mat2 [[7,0,0],[0,0,0],[0,0,1]] 输出&am…

【STL】模拟实现map和set {带头结点的红黑树;红黑树的核心结构;红黑树的迭代器;红黑树的插入和查找;map和set的封装}

模拟实现map和set map和set是红黑树的两种不同封装形式&#xff0c;底层使用同一颗泛型结构的红黑树。set是红黑树的K模型&#xff1b;map是红黑树的KV模型。 下面的代码和讲解着重体现红黑树的底层实现和map\set上层封装的衔接。关于二叉搜索树性质&#xff0c;map和set的介…

UMA 2 - Unity Multipurpose Avatar☀️四.UMA人物部位的默认颜色和自定义(共享)颜色

文章目录 🟥 人物颜色介绍1️⃣ 使用默认颜色2️⃣ 使用自定义颜色🟧 UMA自定义颜色的作用🟨 自定义颜色还可作为共享颜色🟥 人物颜色介绍 UMA不同部位的颜色分为默认的内置颜色和我们新定义的颜色. 1️⃣ 使用默认颜色 比如不勾选UseSharedColor时,使用的眼睛的默认…

品牌策划经理工作内容|工作职责|品牌策划经理做什么?

一位美国作家曾说过“品牌是一系列期望、记忆、故事和关系&#xff0c;他们共同构成了消费者最终原则一个产品或者服务的原因。” 所以&#xff0c;品牌经理这个岗位主要是创造感知价值主张&#xff0c;激发消费者购买这个品牌后带来的感知价值&#xff0c;这种回报的本质相对…

Git 基本操作【本地仓库与远程仓库的推送、克隆和拉取】

文章目录 一、Git简介二、Git的下载安装三、Git常规命令四、新建本地仓库五、本地分支操作六、Git远程仓库七、远程仓库克隆、抓取和拉取八、总结九、学习交流 一、Git简介 Git是分布式版本控制系统&#xff08;Distributed Version Control System&#xff0c;简称 DVCS&…

使用rpm重新安装包

#查询 rpm -qa | grep cloudstack #卸载 rpm -e cloudstack-agent-4.18.0.0-1.x86_64 #安装 rpm -ivh cloudstack-agent-4.18.0.0-1.x86_64.rpm

【PowerQuery】PowerQuery学习路径

PowerQuery这么好,怎么去学习呢?相信很多初读本书的朋友迫切的希望了解整个PowerQuery全景知识和它提供的相应的功能。但是对于PowerQuery来说,一开始就会进行自定义函数的构建当然也是不可能的,这里有相应的学习路径来进行由浅入深的学习,帮助读者更好的理解PowerQuery的…

leetcode 649. Dota2 参议院

2023.9.11 先简化一下题意&#xff1a;本题的意思就是每次投票中&#xff0c;前面的议员可以干掉后面的议员(当然是干掉敌对方的)&#xff0c;然后他将参与下一次的投票&#xff0c;而被干掉的议员则不能参与投票了。 如&#xff1a;[R D D] -> [R D] ->[R D]->[D] 。…

【算法基础】时间复杂度和空间复杂度

目录 1 算法的评价 2 算法复杂度 2.1 时间复杂度&#xff08;Time Complexity&#xff09; 2.1.1 如何计算时间复杂度&#xff1a; 2.1.2 常见的时间复杂度类别与示例 2.2 空间复杂度 2.2.1 如何计算空间复杂度 2.2.2 常见的空间复杂度与示例 3 时间复杂度和空间复杂度…

【附安装包】2023最新版Python安装详细教程!一键安装,永久使用

一、python官网 Python官网主要有python的About (简介)、Downloads (下载)、Documentation(文档)、Community (团体)、Success Stories (成功案例)、News (新闻)、Events (事件动态)等栏目。 Python官网地址&#xff1a;https://www.python.org/ 【领取方式见文末】 二、在…

免费的低代码助力售后工单管理:快速搭建,高效定制

编者按&#xff1a;本文旨在阐述免费且高效的低代码平台在实现售后工单管理系统方面的优势、功能及其作用。这些优势和功能对于提高企业的服务质量和效率具有重要的意义。 关键词&#xff1a;低代码平台、售后工单系统、私有化部署 1.售后工单系统有什么作用&#xff1f; 售后工…

day55:C++ day5,运算符重载剩余部分、静态成员、继承

#include <iostream> #include <cstring> #define pi 3.14 using namespace std;class Shape { protected:double round;double area; public://无参构造Shape():round(40),area(100){cout<<"Shape::无参构造函数&#xff0c;默认周长为40&#xff0c;面…

Python的命令行参数

Python的命令行参数&#xff0c;提供了很多有用的功能&#xff0c;可以方便调试和运行&#xff0c;通过man python就能查看&#xff0c;以下是一些常用参数使用实例和场景: 1. -B参数 在import时候&#xff0c;不产生pyc或者pyo文件: 比如有程序main.py如下: from Hello im…

软件测试/测试开发丨使用ChatGPT自动进行需求分析

简介 在实际工作过程中&#xff0c;常常需要拿到产品的PRD文档或者原型图进行需求分析&#xff0c;为产品的功能设计和优化提供建议。 而使用ChatGPT可以很好地帮助分析和整理用户需求。 实践演练 接下来&#xff0c;需要使用ChatGPT 辅助我们完成需求分析的任务 注意&…

数据链路层相关知识

数据链路层的作用 两个设备(同一种数据链路节点)之间进行传递数据 认识以太网 "以太网"是一种技术标准;既包含了数据链路层的内容,也包含了一些物理层的内容.例如:规定了网络拓扑结构,访问控制方式,传输速率等;以太网中的网线必须使用双绞线;传输速率有10M,100M,1…

Linux设备驱动——自动创建设备节点udev机制的实现过程

创建设备文件的机制有以下下列几种&#xff1a; mknod命令&#xff1a;手动创建设备节点的命令devfs:可以用于创建设备节点&#xff0c;创建设备节点的逻辑在内核空间&#xff08;内核2.4版本之前使用&#xff09;udev:自动创建设备节点的机制&#xff0c;创建设备节点的逻辑在…

python机器人编程——用python实现一个写字机器人

目录 一、前言二、整体框架2.1 系统构成2.2 硬件介绍2.2.1主要组成部分2.2.2机械结构2.2.3驱动及控制主板PS电机驱动原理简介: 2.2.4其余部分 2.3 机器人python程序框架2.3.1通信服务模块2.3.2消息处理模块2.3.3轨迹解析模块2.3.4机械臂逆解模块2.3.5写字板模块 三、机械臂的建…

0门槛限制!快来领取你的专属元宇宙虚拟展厅!

数字化时代中&#xff0c;元宇宙虚拟展厅仿佛成为了一种新的潮流&#xff0c;虚拟展厅的出现为我们呈现出了一个超越现实的全新世界。元宇宙虚拟展厅以其多样性、互动性、沉浸式展示为特点&#xff0c;同产品进行交互&#xff0c;创造出逼真的虚拟环境&#xff0c;为广大用户打…

八股——const 关键字

1.const作用 作用&#xff1a;const用于保护指针指向数据不被修改 测试代码1 显示数组的函数不小心修改了指针指向的值&#xff0c;这时候没有加const关键字&#xff0c;编译器不会报错 #include <stdio.h> void showar(int ar[]);int main(void) {int ar[4]{2,3,4,5…