Ali-Sentinel-集群流控

归档

  • GitHub: Ali-Sentinel-集群流控

测试

  • 参考:热点流控-测试

  • 新建 ClusterDemoApplication2

public class ClusterDemoApplication2 {public static void main(String[] args) {System.setProperty("csp.sentinel.dashboard.server", "127.0.0.1:8080");System.setProperty("project.name", "My-Cluster-8866");System.setProperty("server.port", "10012");SpringApplication.run(ClusterDemoApplication.class, args);}
}
  • 启动 2 应用后

    • 访问:http://localhost:10010/hello/test1
    • 访问:http://localhost:10012/hello/test1
  • 在控制台设置

    • 点击 集群流控:http://127.0.0.1:8080/#/dashboard/cluster/server/My-Cluster-8866
    • 点击 + 新增 Token Server
      • 机器类型 使用默认:应用内机器
      • 选择机器 选第一个即可
      • Server 端口 使用默认:18730
      • 最大允许 QPS 设置为:2
      • 请从中选取 client:
        • 选第一个,再点击
    • 点击 保存
      // POST http://127.0.0.1:8080//cluster/assign/single_server/My-Cluster-8866
      {"clusterMap": {"machineId": "10.32.51.130@8720","ip": "10.32.51.130","port": 18730,"clientSet": ["10.32.51.130@8721"],"belongToApp": true,"maxAllowedQps": 2},"remainingList": []
      }
      
    • 点击 簇点链路:http://127.0.0.1:8080/#/dashboard/identity/My-Cluster-8866
      • sayHello 列,点击 + 流控
        • 是否集群 打勾
        • 集群阈值模式总体阈值
        • 集群阈值 设置为:2
        • 其他使用默认值
      • 点击 新增
        // POST http://127.0.0.1:8080//v1/flow/rule
        {"enable": false,"strategy": 0,"grade": 1,"controlBehavior": 0,"resource": "sayHello","limitApp": "default","clusterMode": true,"clusterConfig": {"thresholdType": "1"},"app": "My-Cluster-8866","ip": "10.32.51.130","port": "8720","count": 2
        }
        
  • 再测试

    • 连续刷 3 次:http://localhost:10010/hello/test1
    • 出现:Oops, [test1] blocked by Sentinel

原理

改模式

  • Path: /cluster/assign/single_server/{app}
控制台
  • com.alibaba.csp.sentinel.dashboard.controller.cluster.ClusterAssignController
@RestController
@RequestMapping("/cluster/assign")
public class ClusterAssignController {// 全路径为: /cluster/assign/single_server/{app}@PostMapping("/single_server/{app}")public Result<ClusterAppAssignResultVO> apiAssignSingleClusterServersOfApp(@PathVariable String app,@RequestBody ClusterAppSingleServerAssignRequest assignRequest) {... // 参数校验try {return Result.ofSuccess(clusterAssignService.applyAssignToApp(  // ref: sign_m_100app, Collections.singletonList(assignRequest.getClusterMap()),assignRequest.getRemainingList()));} ... // catch}
}
  • com.alibaba.csp.sentinel.dashboard.service.ClusterAssignServiceImpl
@Service
public class ClusterAssignServiceImpl implements ClusterAssignService {// sign_m_100@Overridepublic ClusterAppAssignResultVO applyAssignToApp(String app, List<ClusterAppAssignMap> clusterMap,Set<String> remainingSet) {... // 参数校验// 更改集群模式--为服务端 & 推送配置clusterMap.stream().filter(Objects::nonNull).filter(ClusterAppAssignMap::getBelongToApp).map(e -> {String ip = e.getIp();          // 传过来的值为: 10.32.51.130 (相当于连接到控制台的 Sentinel 使用者的机器 IP)int commandPort = parsePort(e); // 从 "machineId": "10.32.51.130@8720" 里取,取的值为: 8720CompletableFuture<Void> f = modifyMode(ip, commandPort, ClusterStateManager.CLUSTER_SERVER) // 改集群模式, ref: sign_m_101.thenCompose(v -> applyServerConfigChange(app, ip, commandPort, e));                    // 推送配置,   ref: sign_m_102 return Tuple2.of(e.getMachineId(), f);}).forEach(t -> handleFutureSync(t, failedServerSet));    // 失败或超时 (10s) 则记录到集合里// 更改集群模式--为客户端 & 推送配置clusterMap.parallelStream().filter(Objects::nonNull).forEach(e -> applyAllClientConfigChange(app, e, failedClientSet)); // 改集群模式为客户端并推送配置, ref: sign_m_110// 解绑剩余 (未分配的) 机器applyAllRemainingMachineSet(app, remainingSet, failedClientSet);return new ClusterAppAssignResultVO() ... // 组装失败的 Client/Server 集合并返回}// sign_m_101 改集群模式private CompletableFuture<Void> modifyMode(String ip, int port, int mode) {return sentinelApiClient.modifyClusterMode(ip, port, mode); // 命令为: setClusterMode, 处理者 ref: sign_c_110}// sign_m_102 推送配置给服务端private CompletableFuture<Void> applyServerConfigChange(String app, String ip, int commandPort,ClusterAppAssignMap assignMap) {ServerTransportConfig transportConfig = new ServerTransportConfig().setPort(assignMap.getPort())   // 传过来的值为: 18730.setIdleSeconds(600);/*** 推送服务端配置 (服务端口) * 命令为: cluster/server/modifyTransportConfig*/return sentinelApiClient.modifyClusterServerTransportConfig(app, ip, commandPort, transportConfig).thenCompose(v -> applyServerFlowConfigChange(app, ip, commandPort, assignMap))     // 推送集群整体流控 QPS, ref: sign_m_103.thenCompose(v -> applyServerNamespaceSetConfig(app, ip, commandPort, assignMap));  // 传过来的 namespaceSet 为空,不会处理,略...}// sign_m_103 推送集群整体流控 QPSprivate CompletableFuture<Void> applyServerFlowConfigChange(String app, String ip, int commandPort,ClusterAppAssignMap assignMap) {Double maxAllowedQps = assignMap.getMaxAllowedQps();... // maxAllowedQps 校验。注: QPS 大于 20w 则不推送/*** 推送集群流控配置* 命令为: cluster/server/modifyFlowConfig*/return sentinelApiClient.modifyClusterServerFlowConfig(app, ip, commandPort,new ServerFlowConfig().setMaxAllowedQps(maxAllowedQps));}// sign_m_110 改集群模式为客户端并推送配置private void applyAllClientConfigChange(String app, ClusterAppAssignMap assignMap,Set<String> failedSet) {Set<String> clientSet = assignMap.getClientSet();... // 空校验final String serverIp = assignMap.getIp();  // 集群服务端 IP:  10.32.51.130final int serverPort = assignMap.getPort(); // 集群服务端端口: 18730clientSet.stream().map(MachineUtils::parseCommandIpAndPort).filter(Optional::isPresent).map(Optional::get).map(ipPort -> {    // 将客户端机器 ID 用 @ 符截取成 [ip, port] 元组CompletableFuture<Void> f = sentinelApiClient/*** 改集群模式为客户端,命令为: setClusterMode* 处理者 ref: sign_c_110*/.modifyClusterMode(ipPort.r1, ipPort.r2, ClusterStateManager.CLUSTER_CLIENT)/*** 推送客户端配置 (服务端的 IP 和端口) * 命令为: cluster/client/modifyConfig*/.thenCompose(v -> sentinelApiClient.modifyClusterClientConfig(app, ipPort.r1, ipPort.r2,new ClusterClientConfig().setRequestTimeout(20).setServerHost(serverIp).setServerPort(serverPort)));return Tuple2.of(ipPort.r1 + '@' + ipPort.r2, f);}).forEach(t -> handleFutureSync(t, failedSet));  // 失败或超时 (10s) 则记录到集合里}}
应用机器
  • com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler
// sign_c_110 改集群模式命令处理者
@CommandMapping(name = "setClusterMode", desc = "set cluster mode...")
public class ModifyClusterModeCommandHandler implements CommandHandler<String> {@Overridepublic CommandResponse<String> handle(CommandRequest request) {try {int mode = Integer.valueOf(request.getParam("mode"));... // 对模式对应的 SPI 的校验ClusterStateManager.applyState(mode);   // 改模式, ref: sign_m_200return CommandResponse.ofSuccess("success");} ... // catch}
}
  • com.alibaba.csp.sentinel.cluster.ClusterStateManager
    • DynamicSentinelProperty 参考:入口控制-设置规则 sign_c_100
public final class ClusterStateManager {private static volatile SentinelProperty<Integer> stateProperty = new DynamicSentinelProperty<Integer>();private static final PropertyListener<Integer> PROPERTY_LISTENER = new ClusterStatePropertyListener();static {InitExecutor.doInit();stateProperty.addListener(PROPERTY_LISTENER);}// sign_m_200 改模式public static void applyState(Integer state) {stateProperty.updateValue(state);   // 最终触发内部处理, ref: sign_m_210}private static class ClusterStatePropertyListener implements PropertyListener<Integer> {... // configLoad 实现@Overridepublic synchronized void configUpdate(Integer value) {applyStateInternal(value);}}// sign_m_210 状态更改处理private static boolean applyStateInternal(Integer state) {... // state 校验try {switch (state) {case CLUSTER_CLIENT:return setToClient();   // 设置为客户端模式, ref: sign_m_211case CLUSTER_SERVER:return setToServer();   // 设置为服务端模式, ref: sign_m_215case CLUSTER_NOT_STARTED:setStop();return true;... // default 处理}} ... // catch}// sign_m_211 设置为客户端模式public static boolean setToClient() {if (mode == CLUSTER_CLIENT) {return true;}mode = CLUSTER_CLIENT;sleepIfNeeded();        // 转换完之后,至少要等 5s 才能换模式lastModified = TimeUtil.currentTimeMillis();return startClient();   // ref: sign_m_212}// sign_m_212 启动客户端并关闭服务端private static boolean startClient() {try {EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer();if (server != null) {server.stop();}ClusterTokenClient tokenClient = TokenClientProvider.getClient();if (tokenClient != null) {  // 默认实现为 DefaultClusterTokenClient, ref: sign_c_230tokenClient.start();    // ref: sign_m_230RecordLog.info("[ClusterStateManager] Changing cluster mode to client");return true;} ... // else} ... // catch}// sign_m_215 设置为服务端模式public static boolean setToServer() {... // 同样要等 5s 才能换模式return startServer();   // ref: sign_m_216}// sign_m_216 启动服务端并关闭客户端private static boolean startServer() {try {ClusterTokenClient tokenClient = TokenClientProvider.getClient();if (tokenClient != null) {tokenClient.stop();}EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer();if (server != null) {       // 默认实现为 DefaultEmbeddedTokenServer, ref: sign_c_220server.start();         // ref: sign_m_218RecordLog.info("[ClusterStateManager] Changing cluster mode to server");return true;} ... // else} ... // catch}
}
启用服务端
  • com.alibaba.csp.sentinel.cluster.server.DefaultEmbeddedTokenServer
/** sign_c_220 默认内嵌 Token 服务器 */
public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer {// 相当于对此进行一层封装private final ClusterTokenServer server = new SentinelDefaultTokenServer(true); // ref: sign_cm_221// sign_m_218@Overridepublic void start() throws Exception {server.start(); // ref: sign_m_220}
}
  • com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer
public class SentinelDefaultTokenServer implements ClusterTokenServer {// sign_cm_221public SentinelDefaultTokenServer(boolean embedded) {this.embedded = embedded;ClusterServerConfigManager.addTransportConfigChangeObserver(new ServerTransportConfigObserver() {@Overridepublic void onTransportConfigChange(ServerTransportConfig config) {changeServerConfig(config); // ref: sign_m_221}});initNewServer();    // 只是创建,并未启动...}// sign_m_220@Overridepublic void start() throws Exception {if (shouldStart.compareAndSet(false, true)) {startServerIfScheduled();   // ref: sign_m_222}}// sign_m_221private synchronized void changeServerConfig(ServerTransportConfig config) {...int newPort = config.getPort();... // 新端口与旧端口相同,则返回。(相当于界面用非默认端口 `18730`,则会启动 2 次 Netty 服务)try {if (server != null) {stopServer();}this.server = new NettyTransportServer(newPort);this.port = newPort;startServerIfScheduled();   } ... // catch}// sign_m_222private void startServerIfScheduled() throws Exception {if (shouldStart.get()) {if (server != null) {server.start();         // 启动 Netty 服务, ref: sign_m_223ClusterStateManager.markToServer();if (embedded) {handleEmbeddedStart();}}}}
}
Netty-服务
  • com.alibaba.csp.sentinel.cluster.server.NettyTransportServer
public class NettyTransportServer implements ClusterTokenServer {private final int port;public NettyTransportServer(int port) {this.port = port;}// sign_m_223 启动 Netty 服务@Overridepublic void start() {if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) {return;}ServerBootstrap b = new ServerBootstrap();this.bossGroup = new NioEventLoopGroup(1);this.workerGroup = new NioEventLoopGroup(DEFAULT_EVENT_LOOP_THREADS);b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));p.addLast(new NettyRequestDecoder());p.addLast(new LengthFieldPrepender(2));p.addLast(new NettyResponseEncoder());p.addLast(new TokenServerHandler(connectionPool));}})... // childOptionb.bind(port).addListener(new GenericFutureListener<ChannelFuture>() {@Overridepublic void operationComplete(ChannelFuture future) {if (future.cause() != null) {...try {Thread.sleep(failCount * RETRY_SLEEP_MS);   // 等待 n * 2sstart();    // 有异常,则尝试 3 次启动, ref: sign_m_223} ... // catch} else {// 没有异常,则改状态为启动完成currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED);}}});}
}
启用客户端
  • com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient
/** sign_c_230 默认客户端 */
public class DefaultClusterTokenClient implements ClusterTokenClient {// SPI 加载时会调用public DefaultClusterTokenClient() {ClusterClientConfigManager.addServerChangeObserver(new ServerChangeObserver() {@Overridepublic void onRemoteServerChange(ClusterClientAssignConfig assignConfig) {changeServer(assignConfig); // ref: sign_m_231}});initNewConnection();    // host 为空,不会创建连接...}// sign_m_230@Overridepublic void start() throws Exception {if (shouldStart.compareAndSet(false, true)) {startClientIfScheduled();   // ref: sign_m_232}}// sign_m_231private void changeServer(ClusterClientAssignConfig config) {... // 服务端的 IP 和端口没改,则返回不处理try {if (transportClient != null) {transportClient.stop();}this.transportClient = new NettyTransportClient(config.getServerHost(), config.getServerPort());... // 记录配置startClientIfScheduled();   // ref: sign_m_232} ... // catch}// sign_m_232private void startClientIfScheduled() throws Exception {if (shouldStart.get()) {if (transportClient != null) {transportClient.start();    // 启动 Netty 客户端连接, ref: sign_m_233} ... // else}}
}
Netty-客户端连接
  • com.alibaba.csp.sentinel.cluster.client.NettyTransportClient
public class NettyTransportClient implements ClusterTransportClient {private final String host;private final int port;public NettyTransportClient(String host, int port) {... // 校验参数this.host = host;this.port = port;}// sign_m_233 启动 Netty 客户端连接@Overridepublic void start() throws Exception {shouldRetry.set(true);startInternal();    // ref: sign_m_234}// sign_m_234private void startInternal() {connect(initClientBootstrap()); // sign_m_235 || sign_m_236}// sign_m_235private Bootstrap initClientBootstrap() {Bootstrap b = new Bootstrap();eventLoopGroup = new NioEventLoopGroup();b.group(eventLoopGroup).channel(NioSocketChannel.class)... // option.handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {clientHandler = new TokenClientHandler(currentState, disconnectCallback);ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));pipeline.addLast(new NettyResponseDecoder());pipeline.addLast(new LengthFieldPrepender(2));pipeline.addLast(new NettyRequestEncoder());pipeline.addLast(clientHandler);}});return b;}// sign_m_236private void connect(Bootstrap b) {if (currentState.compareAndSet(CLIENT_STATUS_OFF, CLIENT_STATUS_PENDING)) {b.connect(host, port).addListener(new GenericFutureListener<ChannelFuture>() {@Overridepublic void operationComplete(ChannelFuture future) {if (future.cause() != null) {   // 有异常,连接失败... // logfailConnectedTime.incrementAndGet();channel = null;} else {                        // 无异常,连接成功failConnectedTime.set(0);channel = future.channel();... // log}}});}}
}

更新规则

  • 参考:WebUI-更新规则-应用端 sign_c_200
    • 服务端并没有将规则同步到客户端
  • 更新规则的入口
    • ModifyClusterFlowRulesCommandHandler ( cluster/server/modifyFlowRules )
      • ClusterFlowRuleManager #loadRules
      • ClusterFlowRuleManager #FLOW_RULES
  • 集群规则现在还没有同步,需要自己改造控制台
    • ref: https://github.com/alibaba/Sentinel/issues/1670
    • 可用 V2,也可用配置源
      • 然后在 ModifyRulesCommandHandler 用 SPI 做下扩展
    • 最主要的是要保持 ruleId 一致

流控原理

  • 参考:

    • 链路控制-ClusterBuilderSlot
    • 链路控制-FlowSlot sign_m_721
  • com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker

public class FlowRuleChecker {// sign_m_411 集群流控 (调用者, ref: sign_m_721)private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,boolean prioritized) {try {// 客户端模式下,SPI 提供的实现者为: DefaultClusterTokenClient, ref: sign_c_421TokenService clusterService = pickClusterService();... // clusterService 为空则回退到本地单机校验long flowId = rule.getClusterConfig().getFlowId();// 根据规则 id 请求 token 服务器,以获取流控结果. ref: sign_m_421 TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);return applyTokenResult(result, rule, context, node, acquireCount, prioritized); // ref: sign_m_412} ... // catch// 回退到本地单机校验return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);}// sign_m_412 根据流控结果进行处理private static boolean applyTokenResult(TokenResult result, FlowRule rule, Context context,DefaultNode node,int acquireCount, boolean prioritized) {switch (result.getStatus()) {case TokenResultStatus.OK:return true;    // 放行case TokenResultStatus.SHOULD_WAIT:try {Thread.sleep(result.getWaitInMs());} catch (InterruptedException e) {e.printStackTrace();}return true;    // 等待指定时间后放行case TokenResultStatus.NO_RULE_EXISTS:case TokenResultStatus.BAD_REQUEST:case TokenResultStatus.FAIL:case TokenResultStatus.TOO_MANY_REQUEST:// 回退到本地单机校验return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);case TokenResultStatus.BLOCKED:default:return false;}}}
  • com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient
// sign_c_421
public class DefaultClusterTokenClient implements ClusterTokenClient {// sign_m_421 请求 token 服务器,获取流控结果@Overridepublic TokenResult requestToken(Long flowId, int acquireCount, boolean prioritized) {...FlowRequestData data = new FlowRequestData().setCount(acquireCount).setFlowId(flowId).setPriority(prioritized);// 此请求的处理者为 FlowRequestProcessor, ref: sign_c_510 | sign_m_510ClusterRequest<FlowRequestData> request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data);try {TokenResult result = sendTokenRequest(request); // ref: sign_m_422...return result;} ... // catch}// sign_m_422private TokenResult sendTokenRequest(ClusterRequest request) throws Exception {...// 通过 Netty 客户端 (NettyTransportClient) 发送ClusterResponse response = transportClient.sendRequest(request);TokenResult result = new TokenResult(response.getStatus());if (response.getData() != null) {FlowTokenResponseData responseData = (FlowTokenResponseData) response.getData();result.setRemaining(responseData.getRemainingCount()).setWaitInMs(responseData.getWaitInMs());}return result;}
}
服务端流控
  • com.alibaba.csp.sentinel.cluster.server.processor.FlowRequestProcessor
// sign_c_510 流控请求处理器
@RequestType(ClusterConstants.MSG_TYPE_FLOW)
public class FlowRequestProcessor implements RequestProcessor<FlowRequestData, FlowTokenResponseData> {// sign_m_510@Overridepublic ClusterResponse<FlowTokenResponseData> processRequest(ClusterRequest<FlowRequestData> request) {// 服务端模式下,SPI 提供的实现者为 DefaultTokenService, ref: sign_c_521TokenService tokenService = TokenServiceProvider.getService();long flowId = request.getData().getFlowId();int count = request.getData().getCount();boolean prioritized = request.getData().isPriority();TokenResult result = tokenService.requestToken(flowId, count, prioritized); // ref: sign_m_521return toResponse(result, request); // ref: sign_m_511}// sign_m_511private ClusterResponse<FlowTokenResponseData> toResponse(TokenResult result, ClusterRequest request) {return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(),new FlowTokenResponseData().setRemainingCount(result.getRemaining()).setWaitInMs(result.getWaitInMs()));}}
  • com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService
// sign_c_521
@Spi(isDefault = true)
public class DefaultTokenService implements TokenService {// sign_m_521@Overridepublic TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {...FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);if (rule == null) { // 测试的时候,为 null (因为没同步)return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);}// 集群流控校验 (逻辑较简单。集群指标在 ClusterFlowRuleManager #applyClusterFlowRule 里初始化)return ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized);}}
总结
  • 集群流控时,每次请求都会转发到服务端(由服务端去控制)

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

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

相关文章

华为手机卡顿(仅针对于部分人来说,我也不清楚是否真的有用)

关机&#xff01; 之前一段时间手机变得特别卡顿&#xff0c;然后网上搜了一堆教程一点用没有&#xff0c;结果因为昨天下午在考试所以把手机关机了一个多小时&#xff0c;再打开之后手机就变得很流畅&#xff0c;原因不详&#xff0c;但效果显著&#xff0c;如有需要可尝试一…

docker使用PostgreSQL容器

要执行 db/migrations/ 目录下的多个 SQL 文件以迁移数据库&#xff0c;可以按照以下步骤进行&#xff1a; 1 确认 PostgreSQL 容器正在运行&#xff1a; 确认你已经启动了 PostgreSQL 容器并且它在运行&#xff1a; docker ps -a2 确认容器 beaconchain 是 Up 状态。 将 SQ…

docker 挂载运行镜像

文章目录 前言docker 挂载运行镜像1. 作用2. 命令3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&#xff0c;那欢…

软考 软件设计师 场景分析题 速成篇

文章目录 试题一&#xff1a;数据流图&#x1f496; 基本图形元素1. 外部实体2. 数据存储3. 加工4. 数据流 &#x1f4da; 例题&#xff08;1&#xff09;实体名称&#xff08;2&#xff09;数据存储名称&#xff08;3&#xff09;数据流① 父子图平衡② 加工有输入有输出④ 数…

在Windows10中重命名文件和文件夹的6种方法,有你熟悉和不熟悉的

序言 你可以通过多种方式在Windows 10上重命名文件。如果每次你想更改文件名时仍右键单击并选择“重命名”,那么我们有一些技巧可以加快更改速度。 使用文件资源管理器重命名文件和文件夹 Windows 10的文件资源管理器是一个功能强大的工具。你知道吗,有四种不同的方法可以…

电商API接口接入电商平台抓取热门商品推荐上货api接入示例

电商API接口接入电商平台抓取热门商品推荐上货的示例代码如下&#xff1a; # coding:utf-8 """ Compatible for python2.x and python3.x requirement: pip install requests """ from __future__ import print_function import requests # 请求…

CSS【常用CSS样式、盒子模型、定位、浮动 、扩展样式】--学习JavaEE的day46

day46 CSS 练习 页面实现&#xff1a; 分析&#xff1a; 未优化&#xff1a; 优化&#xff1a; 参考代码&#xff1a;&#xff08;包含样式优化–选择器CSS属性&#xff09; 先写上table方便实现&#xff0c;之后再去除即可 name没有服务器&#xff0c;可暂时不写 <!…

智能驾驶核心伪代码

智能驾驶是一个复杂且广泛的领域&#xff0c;涉及多个子系统和组件&#xff0c;包括但不限于感知、定位、决策规划、控制等。由于智能驾驶系统的复杂性&#xff0c;不可能简单地提供一个通用的代码片段。然而&#xff0c;我可以提供一个简单的示例&#xff0c;展示如何使用一些…

mysql - explain执行计划

explain执行计划 explain是mysql中一关键字&#xff0c;用于查看执行计划, 模拟执行器执行sql查询语句, 从而分析sql语句或表结构的性能瓶颈或优化方向。 explain用途 可分析得到以下信息&#xff1a; 表读取顺序数据读取操作的操作类型可使用的索引实际使用的索引表间引用…

微软开源多模态大模型Phi-3-vision,微调实战来了

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型& AIGC 技术趋势、大模型& AIGC 落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了…

零部件销售|基于SSM+vue的轻型卡车零部件销售平台系统的设计与实现(源码+数据库+文档)

轻型卡车零部件销售平台 目录 基于SSM&#xff0b;vue的轻型卡车零部件销售平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1 系统功能模块 2 管理员功能模块 3 用户后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题…

stream( ).collect ( Collectors.groupingBy ( ) ) 的用法

文章目录 第一种解释1、基本用法2、指定值收集器3、多级分组4、常见应用场景和用处 第二种解释1、基本语法2、示例3、更复杂的用法 第一种解释 Collectors.groupingBy 是 Java 8 引入的 Stream API 中的一个收集器&#xff08;Collector&#xff09;&#xff0c;它用于将流&am…

Golang:flosch/pongo2是一个类似Django模板语法的模板引擎

模板引擎可以用来做一些字符串渲染拼接工作&#xff0c;避免在代码中出现大量的字符串拼接 文档 https://www.schlachter.tech/solutions/pongo2-template-engine/https://github.com/flosch/pongo2 安装 go get -u github.com/flosch/pongo2/v6使用示例 1、字符串模板 p…

【笔记】Qt 按钮控件介绍(QPushButton,QCheckBox,QToolButton)

文章目录 QAbstractButton 抽象类(父类)QAbstractButton 类中的属性QAbstractButton 类中的函数QAbstractButton 类中的信号QAbstractButton 类中的槽 QPushButton 类(标准按钮)QPushButton 类中的属性QPushButton 类中的函数、槽 QCheckBox 类(复选按钮)QCheckBox 类的属性QCh…

Html中,想利用JS引入Jquery文件;$.getScript()无效

在使用$.getScript()时&#xff0c;会爆出错误&#xff1a;ReferenceError: $ is not defined &#xff0c;这是因为没有在JS文件前引入Jquery。 那么可以这样使用&#xff1a;(这个方式只适合放在页面代码最后面使用) (function () {var script window.document.createEleme…

Kettle简介

一、Kettle简介 Kettle是一个开源的ETL&#xff08;Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程&#xff09;项目。 项目名很有意思&#xff0c;水壶。按项目负责人Matt的说法&#xff1a;把各种数据放到一个壶里&#xff0c;然后呢&#xff0c;以…

PHP反射API与接口的动态分析

PHP的反射&#xff08;Reflection&#xff09;API 提供了一种在运行时获取类和对象信息的能力&#xff0c;包括类的方法、属性、接口等。这对于动态分析、构建IDE的自动完成功能、或者进行复杂的元编程非常有用。以下是如何使用PHP反射API进行动态分析的示例代码。 1. 反射类&…

MySQL触发器怎么使用?

触发器&#xff08;Trigger&#xff09;是数据库中的一种重要机制&#xff0c;用于在特定的数据库事件&#xff08;如插入、更新或删除&#xff09;发生时&#xff0c;自动执行预定义的SQL语句。在MySQL中&#xff0c;触发器能够帮助我们实现复杂的业务逻辑、数据验证和自动化任…

ComfyUI 本地部署指南:概念、部署过程、生成图片、共享 WebUI 模型

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本文主要讲解 ComfyUI 的本地部署指南&#xff0c;内容主要包括&#xff1a;ComfyUI 的概念、ComfyUI 本地部署过程、使…

MySQL---增删改查

MySQL是一个流行的关系型数据库管理系统,它使用结构化查询语言(SQL)来管理数据库中的数据。以下是MySQL中增删改查(CRUD)操作的基本命令: 创建(Create): 创建新表:CREATE TABLE table_name (column1 datatype,column2 datatype,...PRIMARY KEY (column) );插入数据:…