Hello World - 默认分类
type: channel
link: https://www.ronie.ren/index.php/category/default/
description: 只是一个默认分类
items: LM + 大数据六个月学习计划（ai规划）
items: Temporal风控（十八）控制流与数据面分离设计方法
items: Temporal风控（十六）步骤条件跳过机制设计方法
items: ElasticSearch
items: redis 限流
items: Nginx 配置文件注释
items: docker compose kafka
items: docker-compose-redis
items: LeetCode（2023-10-30）
items: LeetCode（2023-09-21）

LM + 大数据六个月学习计划（ai规划）
type: item
date: 2026-05-15T07:30:00+00:00
description: 0. 计划前提学习目标本计划目标不是“泛泛了解大模型”，而是在 6 个月内形成可以落地的工程能力：理解机器学习、神经网络、Transformer、大模型推理的核心机制。能用 PyTorch 实现基础训练流程、Attention、Transformer Block 和 mini GPT。能做 Prompt、Tool Calling、RAG、Agent、评估等 LLM 应用工程。掌握 Spark、Kafka、Flink、ClickHouse 在 LLM 数据管道中的典型用法。能完成一个“大数据 + LLM 知识库与日志评估平台”综合项目。能做基础 LoRA 微调、评估、性能优化和项目文档整理。时间投入建议最低时间投入：工作日：每天 3 小时。非工作日：每天 8 小时（可弹性延至 10 小时）。每周合计：31 小时（工作日 15h + 周末 16h）。6 个月约 24 周，总投入约 744 小时。如果某周工作压力较大，最低不能低于 20 小时；低于 20 小时的周，优先在下周周末弹性时间内补回核心项目实践（周末额外的 2h/天是天然的追进度缓冲区，不要在正常周挤占它）。学习原则每周必须有代码产出。每两周必须有一个可运行 demo。每个月必须有一个可展示成果。数学不脱离代码学习，每个数学概念都要用 NumPy 或 PyTorch 实现一次。大模型应用不只调 API，要同时记录数据流、失败案例、日志和评估指标。大数据不只学组件概念，要和 LLM 文档处理、日志分析、评估闭环结合。每周固定节奏工作日：理论学习 + 代码实现 + 复盘练习，三件事每天都要有。周六：理论补强 + 项目编码为主，提交代码，写 README 片段。周日：复盘本周知识点，继续实现项目，补测试整理结果，写周总结和下周计划。每周输出模板每周结束时写一份简短总结，建议包含：本周主题： 本周完成： 核心概念： 代码产出： 踩坑记录： 仍不理解的问题： 下周计划：1. 总体路线第 1 月：数学 + 机器学习 + PyTorch 第 2 月：Transformer / LLM 原理 第 3 月：LLM 应用工程 第 4 月：大数据工程 第 5 月：大数据 + LLM 综合项目 第 6 月：微调 + 性能优化 + 作品整理最终产出：project-01-numpy-ml：NumPy 逻辑回归与数学基础实现。project-02-pytorch-template：通用 PyTorch 训练模板。project-03-mini-gpt：mini GPT 训练与生成。project-04-rag-agent：RAG + Tool Calling + Agent demo。project-05-bigdata-llm-pipeline：Spark / Kafka / ClickHouse 日志与文档管道。project-06-llm-bigdata-platform：综合项目。project-07-lora-eval-optimization：LoRA 微调、评估和优化报告。2. 第 1 月：数学 + 机器学习 + PyTorch目标：补齐大模型所需最小数学基础。能用 NumPy 理解模型训练的底层计算。能用 PyTorch 写出可复用训练模板。理解 loss、梯度、optimizer、训练循环和评估指标。第 1 周：线性代数 + 逻辑回归本周目标理解向量、矩阵、点积、矩阵乘法。理解 sigmoid、二分类、逻辑回归。用 NumPy 实现一个二分类模型。必学概念标量、向量、矩阵、张量。向量点积。矩阵乘法。shape 推导。sigmoid 函数。binary cross entropy。梯度下降的直观含义。每日安排周一：- 学习向量、矩阵、点积、矩阵乘法。- 用 NumPy 写矩阵乘法、点积、向量范数。- 整理 shape 笔记，写 10 个 shape 推导例子。周二：- 学习 sigmoid、二分类概率输出。- 写 sigmoid、预测函数 y_hat = sigmoid(X @ w + b)。- 构造 toy dataset，跑一次前向预测。周三：- 学习 binary cross entropy。- NumPy 实现 BCE loss。- 手算一个样本的 loss，和代码结果对比。周四：学习梯度下降。推导逻辑回归参数梯度。实现 dw、db 更新。周五：复盘本周数学。把逻辑回归训练流程串起来。写周总结和问题清单。周六：补线性代数练习，重点看 shape。完成 NumPy 逻辑回归完整训练脚本。画训练 loss 曲线。周日：复盘逻辑回归。添加 train / validation split 和 accuracy。整理 README。总结“矩阵乘法在模型里的作用”。产出NumPy 逻辑…

Temporal风控（十八）控制流与数据面分离设计方法
type: item
date: 2026-04-23T13:36:00+00:00
description: 一、为什么很多风控系统越做越重很多风控系统一开始看起来很简单： 主流程负责调步骤，步骤里算出结果，最后返回决策。问题在于，随着业务扩张，主流程往往会逐渐承担越来越多内容：进度管理中间态存储错误摘要大结果拼装复用搜索回放状态最后系统会进入一个很危险的状态： 控制流和数据面不再分离。这意味着主流程既要负责“怎么跑”，又要负责“数据放哪、怎么读、如何复用”。 一旦走到这一步，系统几乎必然变重。二、什么是控制流，什么是数据面1. 控制流控制流负责回答：现在执行到哪一步下一步该执行什么哪些步骤可以并发失败如何传播超时和重试如何处理2. 数据面数据面负责回答：数据结果存在哪里特征如何共享模型结果如何复用中间态如何命名和清理这两者天然相关，但不应混杂。三、为什么控制流不适合承载大量中间态如果把大量中间态直接挂在控制流对象上，会迅速带来几个问题：状态体积膨胀序列化成本变高重放语义更脆弱进度查询被大量无关数据污染控制流最适合保存的是：阶段状态运行索引错误摘要关键进度元信息而不是海量计算结果。四、为什么数据面需要独立上下文系统中间态一旦从控制流中分离出来，就必须有自己的正式承载方式。 这就是上下文系统的意义。上下文系统应负责：命名空间路径组织读写语义搜索复用生命周期清理只有这样，控制流与数据面分离才不只是“把数据丢到别处”，而是真正的架构分层。五、分离之后，系统会获得什么1. 控制流更轻主流程可以专注于阶段推进和生命周期管理。2. 数据面更清晰所有中间态都有统一命名空间和清理语义。3. 回放更容易回放系统可以复用同一控制骨架，只替换数据面来源与执行适配。4. 控制面更容易建设控制面查询控制流状态，上下文治理面查询数据面状态，两者边界清晰。六、分离设计中的关键连接点是什么控制流与数据面虽然分层，但仍然必须通过若干稳定契约连接。这些契约通常包括：步骤身份命名空间身份节点路径约定结果写回协议如果没有这些连接点，分离就会变成断裂； 如果连接点过多，分离又会重新失效。七、常见反模式1. 把所有结果都挂在流程状态里这会让控制流越来越重。2. 数据面没有统一命名和清理语义这会让分离流于形式。3. 控制面同时读取两套不一致状态这会让系统观测变得混乱。4. 回放系统另起一套状态模型这会让分离设计失去复用价值。八、如何判断控制流与数据面是否真正分离1. 主流程是否仍持有大量业务结果如果是，说明分离不彻底。2. 中间态是否都能通过统一上下文系统访问如果不能，说明数据面没有真正建立。3. 控制面能否明确区分运行态与中间态治理如果不能，说明两者边界仍不清晰。九、总结：这层分离为什么这么重要控制流与数据面分离的价值，不只是“结构更好看”。 它真正决定了风控引擎是否具备：轻量可维护的流程骨架可治理的中间态系统可复用的回放模式可解释的控制面从工程角度看，这是复杂风控平台能否长期稳定演进的底层分水岭。 一旦控制流和数据面重新糊在一起，系统很快就会重新走回重流程、重状态、重耦合的老路。

Temporal风控（十六）步骤条件跳过机制设计方法
type: item
date: 2026-04-23T13:32:00+00:00
description: 一、为什么条件跳步不能只是一个 if 语句当流程变复杂之后，总会出现某些步骤不是每次都要执行的情况。 最自然的处理方式是在业务代码里写一个条件判断：if 某个条件: return # 跳过本步骤这种写法在小系统里完全够用，但在平台化风控引擎中会带来三个问题。第一，跳步语义被淹没在业务逻辑里，执行层和控制面都无法识别"这是一次合法跳过"还是"节点没被调度"。 第二，平台无法统一度量跳步行为，无法知道某个步骤的跳过频率。 第三，回放系统不能区分"这次应该跳过"和"这次因为错误被跳过"。因此，步骤条件跳过需要成为流程层的正式语义，而不是各节点各自处理的内部细节。二、跳步语义属于步骤定义还是执行层很多团队会把跳步逻辑放在以下位置之一：业务节点内部工作流的主循环里外部配置开关这三种方式都不是最优解。放在业务节点内部，平台无感知；放在工作流主循环，业务逻辑污染控制流；依赖外部配置开关，缺乏上下文感知能力。更成熟的设计是：跳步逻辑作为步骤定义的一个字段携带，执行层在调度步骤前统一评估，评估结果明确记录为"跳过"。这样做的好处是：步骤是否跳过由步骤自身声明，执行层只负责询问并执行结果，两者分工清晰。三、skip 函数的本质：一个可注入的运行时谓词步骤上的 skip 字段，本质上是一个谓词函数：入参是当前执行上下文（包含已有的中间态和 risk_input）出参是 bool（是否跳过）既支持同步，也支持异步这个设计有几个关键好处。1. 策略与执行解耦skip 函数只负责判断，不执行任何步骤逻辑。 这样跳步条件可以随业务演进，而不影响步骤本身的执行路径。2. 感知当前上下文skip 函数能访问 Context，这意味着它可以读取前置步骤的结果来决定是否跳过。 这是纯外部配置开关无法做到的事情。3. 同步/异步统一接口执行层通过 inspect.iscoroutinefunction 判断 skip 函数是否为协程，统一调用。 对 skip 函数的编写方不需要感知调度器的运行模式。四、skip 评估的时机：为什么必须在 step_setup 之前执行层在调度一个步骤时，通常会经历以下几个阶段：检查是否跳过执行 step_setup（写入步骤级初始化状态）启动子工作流（data / feature / model / rule）skip 评估必须在 step_setup 之前。 原因很简单：如果先执行 setup 再判断要跳过，就会产生一个已初始化但从未被执行的步骤残态。 这不仅浪费资源，还会污染上下文，让后续步骤搜索到不完整的中间态。因此，评估顺序为：check_skip → skip 则直接记录并返回，不继续初始化；不 skip 则进入正常执行路径。五、skip 语义在 Activity 层还是 Workflow 层评估有两个可选位置：在 Workflow 内部直接判断封装成 Activity 调用两种方式各有优劣。在 Workflow 内部判断：延迟极低，但 Temporal 的工作流代码要求确定性，不能任意访问外部状态。封装成 Activity 调用：可以自由访问 Redis、数据库等外部状态，适合绝大多数业务场景下的跳步判断。因此，绝大多数场景应选择 Activity 调用。 skip 函数通常依赖 Context 去读取 Redis 中已有的中间态，这恰好是 Activity 最擅长的事情。六、skip 的对立面：什么情况下不能允许跳过跳步机制一旦存在，就必须明确：哪些步骤不允许跳过。通常以下类型的步骤不适合携带 skip 语义：全局初始化步骤（global_setup）写入最终结果的步骤（build_result）清理步骤（clean_all）审计写入步骤（dwh_action）这类步骤的本质是"生命周期保障"，不能因为业务条件判断而省略。 如果允许跳过，流程的完整性会被破坏。七、跳步记录与指标的设计跳步发生后，有两类信息需要被记录：1. 指标执行层应在判定跳过时明确记录一个"skipped"状态的 step 指标，而不是简单丢弃这次调用。 这样观测平台才能区分：step 从未被调度step 被调度但判定跳过step 被调度但执行失败这三种状态语义完全不同，混淆会严重干扰排障效率。2. 日志跳步时应输出明确的日志，携带 global_namespace、workflow_name、step_id，说明"判定跳过"。 这样在回放或复盘时，工程师能清楚地知道流程路径是否走了跳步分支。八、skip 函数与依赖上下文的关系skip 函数在访问 Context 时，通常需要知道自己能读取哪些命名空间的数据。 这意味着它的执行依赖于"前置步骤已写入了足够的中间态"。这引出一个重要约束：如果 skip 函数依赖某个步骤的输出来做判断，那么该步骤必须在…

ElasticSearch
type: item
date: 2026-04-16T09:45:50+00:00
description: ElasticSearch 查询match all 查询匹配所有文档，_score 为 1GET policy_info/_search { "query": { "match_all": {} } }{ "took": 0, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3556, "relation": "eq" }, "max_score": 1, "hits": [ { "_index": "policy_info", "_id": "5", "_score": 1, "_source": { ... ...boost参数可改变_score的值GET policy_info/_search { "query": { "match_all": {"boost": 1.2 } } }{ ... "max_score": 1.2, "hits": [ { "_index": "policy_info", "_id": "5", "_score": 1.2, "_source": { ... } } ... }match none查询，反转match all查询结果，一个文档也不匹配GET policy_info/_search { "query": { "match_none": {} } }{ "took": 0, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 0, "relation": "eq" }, "max_score": null, "hits": [] } }Term 查询term查询返回和提供词严格匹配的文档，不要对text字段使用term查询，使用GET policy_info/_mapping查看索引结构；参数：GET xxx/_search { "query": { "term": { "field": { "value": 12, "boost": 2, "case_insensitive": true } } } }field[Required, string]：查询字段value[Required， string]：提供的精准匹配的值，包括空格，大小写敏感；boost[Optional, float]：用于减少或增加查询的相关分数；默认为1；case_insensitive[Optional, bool]：是否大小写敏感，默认true；GET policy_info/_search { "query": { "term": { "id": { "value": 12, "boost": 2 } } } }{ "took": 5, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 2, "hits": [ { "_index": "policy_info", "_id": "12", "_score": 2, "_source": { "id": 12, "qdu_id": "bc58c81c7b2143819487053cb50f9acf", ... "create_time": "2024-09-06 16:15:41", "row_update_time": "2024-09-06 16:15:41" } } ] } }terms 查询返回和提供词严格匹配的文档，可提供多个值，类似SQL or；参数：GET xxx/_search { "query": { "terms": { "field": ['xxx', 'xxx'], "boost": 2 } } }field[Required, List[string]]：查询字段；boost[Optional, float]：用于减少或增加查询的相关分数；默认为1；GET policy_info/_search { "query": { "terms": { "id": [12, 32], "boost": 2 } } }{ "took": 1, "timed_out": false, "_shards…

redis 限流
type: item
date: 2025-12-31T09:34:21+00:00
description: redis 限流redis 令牌桶限流@dataclass(frozen=True) class RateLimitResult: allowed: bool remaining_tokens: float retry_after_seconds: float now_ms: int class RedisTokenBucketRateLimiter: _TOKEN_BUCKET_LUA = r""" local key = KEYS[1] -- 令牌桶状态存储的 Redis Key（Hash） local capacity = tonumber(ARGV[1]) -- 桶容量：最多可累积的令牌数 local rate = tonumber(ARGV[2]) -- 补充速率：每秒补充多少令牌（tokens/s） local requested = tonumber(ARGV[3]) -- 本次请求要消耗的令牌数 local ttl_ms = tonumber(ARGV[4]) -- 令牌桶 Key 的过期时间（毫秒），用于回收冷 Key local t = redis.call('TIME') -- 读取 Redis 服务器时间：{秒, 微秒} local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000) -- 服务器时间换算为毫秒时间戳 local data = redis.call('HMGET', key, 'tokens', 'ts') -- 读取当前令牌数(tokens)与上次更新时间(ts) local tokens = tonumber(data[1]) -- 当前令牌数（可能为 nil） local ts = tonumber(data[2]) -- 上次更新时间戳（毫秒，可能为 nil） if tokens == nil then tokens = capacity end -- 初始化：若不存在，则认为桶是满的 if ts == nil then ts = now end -- 初始化：若不存在，则将上次更新时间设为当前 if now < ts then ts = now end -- 防御：若时钟回拨导致 now < ts，则强制对齐 local delta_ms = now - ts -- 距离上次更新过去了多少毫秒 local refill = (delta_ms / 1000.0) * rate -- 这段时间应补充的令牌数量（可为小数） tokens = math.min(capacity, tokens + refill) -- 将令牌补充后并截断到容量上限 local allowed = 0 -- 是否允许：0/1（Lua 没有布尔返回给 Python 的统一类型） local retry_after_ms = 0 -- 如果不允许，建议等待多少毫秒再试 if tokens >= requested then -- 若令牌充足 allowed = 1 -- 标记允许 tokens = tokens - requested -- 扣除本次消耗 else -- 否则令牌不足 allowed = 0 -- 标记拒绝 if rate > 0 then -- 若补充速率 > 0，可计算需要等待的时间 local missing = requested - tokens -- 还差多少令牌 retry_after_ms = math.ceil((missing / rate) * 1000) -- 差额按速率换算为等待毫秒数（向上取整） else -- 若 rate=0，则永远补不回令牌 retry_after_ms = -1 -- 用 -1 表示无法通过等待获得令牌 end -- 结束 rate>0 分支 end -- 结束 tokens>=requested 分支 redis.call('HMSET', key, 'tokens', tokens, 'ts', now) -- 写回最新 tokens 与 ts（原子更新） if ttl_ms ~= nil and ttl_ms > 0 then -- 若配置了过期时间且 >0 redis.call('PEXPIRE', key, ttl_ms) -- 给令牌桶 Key 设置过期（毫秒） end -- 结束 ttl 分支 return {allowed, tokens, retry_after_ms, now} -- 返回：是否允许、剩余令牌、建议等待(ms)、当前时间(ms) """ def __init__( self, redis: Optional[AsyncRedis…

Nginx 配置文件注释
type: item
date: 2025-03-27T07:00:49+00:00
description: user nobody; # Nginx worker进程运行的用户及用户组 worker_processes 4; # 在master/worker运行方式下，定义worker进程的个数; 可以为auto worker_rlimit_nofile 65535; # 设置 Nginx worker 进程可以打开的最大文件描述符数量。这个指令通常用于确保 Nginx 能够处理大量并发连接，因为每个连接通常会占用一个文件描述符。 worker_rlimit_core 1m; # 通过worker_rlimit_core配置可以限制core文件的大小 working_directory /usr/local/lighthouse/softwares/nginx/coredump; # 设置coredump文件所放置的目录 daemon on; # 是否以守护进程方式运行Nginx master_process on; # 是否以 master/worker 方式工作 worker_cpu_affinity 1000 0100 0010 0001; # 绑定worker进程到指定的CPU内核 error_log logs/error.log notice; # error_log/path/file level; timer_resolution 100ms; # 每次内核的事件调用（如epoll、select、poll、kqueue等）返回时，都会执行一次gettimeofday，实现用内核的时钟来更新Nginx中的缓存时钟。 events { # 处理网络连接设置的核心部分 debug_connection 10.224.66.14; # 仅仅来自以上IP地址的请求才会输出debug级别的日志 debug_connection 10.224.57.0/24; # 其他请求仍然沿用error_log中配置的日志级别 accept_mutex on; # 负载均衡锁 accept锁默认是打开的，如果关闭它，那么建立TCP连接的耗时会更短，但worker进程之间的负载会非常不均衡 accept_mutex_delay 500ms; # 使用accept锁后，同一时间只有一个worker进程能够取到accept锁。这个accept锁不是阻塞锁，如果取不到会立刻返回。 如果有一个worker进程试图取accept锁而没有取到，它至少要等accept_mutex_delay定义的时间间隔后才能再次试图取锁。 multi_accept off; # 当事件模型通知有新连接时，尽可能地对本次调度中客户端发起的所有TCP请求都建立连接。 use epoll; # 事件驱动模型 worker_connections 8192; # 每个worker进程可以同时处理的最大连接数。 } http { include mime.types; default_type application/octet-stream; log_format combinedio '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" $request_length $request_time $upstream_response_time'; access_log logs/access.log combinedio; sendfile on; # 硬盘 >> kernel buffer >> user buffer>> kernel socket buffer >>协议栈 | 硬盘 >> kernel buffer (快速拷贝到kernelsocket buffer) >>协议栈 gzip on; # 压缩响应内容 tcp_nopush on; # 数据量达到一定值后发送 tcp_nodelay on; # 对keepalive连接是否使用TCP_NODELAY选项。# 应用程序每产生一次操作就会发送一个包, delay 会等待数据，因此操作后它不会发送一个数据包，而是将这段时间内的数据打成一个大的包 keepalive_timeout 0; # 一个keepalive连接在闲置超过一定时间后,服务器关闭这个连接。 client_body_timeout 10; # second HTTP包体 client_header_timeout 10; # second 客户端与服务器建立连接后将开始接收HTTP头部，在这个过程中，如果在一个时间间隔（超时时间）内没有读取到客户端发来的字节，则认为超…

docker compose kafka
type: item
date: 2025-02-24T08:59:00+00:00
description: docker kafka 配置文件version: '3.8' name: "kafka-service" # https://docs.docker.com/compose/how-tos/project-name/ services: kafka-server0: image: bitnami/kafka:latest hostname: kafka0 container_name: kafka0 ports: - "9092:9094" environment: KAFKA_CFG_NODE_ID: 0 KAFKA_CFG_BROKER_ID: 0 KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://0.0.0.0:9094 KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka0:9092,EXTERNAL://192.168.60.134:9092 KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka1:9093,2@kafka2:9093,0@kafka0:9093 KAFKA_CFG_PROCESS_ROLES: broker,controller # KAFKA_CFG_LOG_DIRS: /var/lib/kafka/data KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: 3 KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3 KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: 2 KAFKA_CFG_MIN_INSYNC_REPLICAS: 1 KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: true KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_KRAFT_CLUSTER_ID: d6c6ea9d1bfe423ebdc74579b10c61f0 networks: kafka: ipv4_address: 172.16.0.2 volumes: - ~/kafka-data/0:/bitnami/kafka kafka-server1: image: bitnami/kafka:latest hostname: kafka1 container_name: kafka1 ports: - "9094:9094" environment: KAFKA_CFG_NODE_ID: 1 KAFKA_CFG_BROKER_ID: 1 KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://0.0.0.0:9094 KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092,EXTERNAL://192.168.60.134:9094 KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka1:9093,2@kafka2:9093,0@kafka0:9093 KAFKA_CFG_PROCESS_ROLES: broker,controller # KAFKA_CFG_LOG_DIRS: /var/lib/kafka/data KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: 3 KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3 KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: 2 KAFKA_CFG_MIN_INSYNC_REPLICAS: 1 KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: true KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_KRAFT_CLUSTE…

docker-compose-redis
type: item
date: 2025-02-24T08:57:56+00:00
description: RedisRedis-Server通过bitnami/redis:7.4.1实现 redis 主从集群version: '3.8' name: 'redis-service' services: redis-master: image: 'bitnami/redis:7.4.1' container_name: redis-master hostname: redis-master expose: - '6379' environment: - REDIS_REPLICATION_MODE=master - REDIS_PASSWORD=OeLFo2b3GVXKj0Ren1Dh - REDIS_MASTER_PORT_NUMBER=6379 networks: redis: ipv4_address: 192.168.0.2 redis-replica: image: 'bitnami/redis:7.4.1' deploy: replicas: 2 depends_on: - redis-master environment: - REDIS_REPLICATION_MODE=slave - REDIS_MASTER_HOST=192.168.0.2 - REDIS_MASTER_PORT_NUMBER=6379 - REDIS_MASTER_PASSWORD=OeLFo2b3GVXKj0Ren1Dh - REDIS_PASSWORD=OeLFo2b3GVXKj0Ren1Dh networks: redis: networks: redis: driver: bridge name: redis-net ipam: config: - subnet: 192.168.0.0/24 gateway: 192.168.0.1 Redis-Sentinel为保证redis集群高可用，添加redis sentinel监控master节点。version: '3.8' name: 'redis-sentinel-cluster' services: redis-sentinel: image: 'bitnami/redis-sentinel:6.2.16' deploy: replicas: 3 environment: REDIS_SENTINEL_PORT_NUMBER: '26379' REDIS_MASTER_HOST: '192.168.0.2' REDIS_MASTER_PORT_NUMBER: '6379' REDIS_SENTINEL_QUORUM: '2' # 在进行故障转移之前，必须同意主节点失效的 Sentinel 实例的最小数量。 REDIS_MASTER_PASSWORD: 'OeLFo2b3GVXKj0Ren1Dh' REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS: '5000' REDIS_SENTINEL_FAILOVER_TIMEOUT: '5000' REDIS_MASTER_SET: 'mymaster' networks: - redis networks: redis: name: 'redis-net' external: true测试后发现，redis sentinel 能在master节点失效后选举新master节点，但是在docker-compose中只暴露了master节点的6379端口。于是在redis-net网络中添加nginx反代。nginx反代（不可用）nginx-reverse-proxy: image: 'bitnami/nginx:1.27' volumes: - ./nginx.conf:/opt/bitnami/nginx/conf/stream_server_blocks/my_server_block.conf environment: NGINX_ENABLE_STREAM: yes ports: - '6379:6379' networks: redis: depends_on: - redis-replica添加反代后，发现slave节点不能向master节点转发命令（记错了，这个是redis分片集群的功能，分片节点计算键的插槽，该插槽不归该节点管就转发给该管这个插槽的节点）。放弃反代，直接将测试代码接入redis-net网络。问题发现容器内无法访问外部网络(.venv) ronie@ronie:~/PycharmProjects/evilbox$ docker attach 81 / # ping www.baidu.com PING www.baidu.com (180.101.50.188): 56 data bytes…

LeetCode（2023-10-30）
type: item
date: 2023-10-30T09:52:07+00:00
description: 1711. 大餐计数1711. 大餐计数from typing import List from collections import Counter import math class Solution: def countPairs(self, deliciousness: List[int]) -> int: min_deliciousness = min(deliciousness) if min_deliciousness <= 0: min_deliciousness = 0.5 max_deliciousness = max(deliciousness) count = Counter(deliciousness) ans = 0 wait_list = [2 ** i for i in range(int(math.log(min_deliciousness * 2, 2)), int(math.log(max_deliciousness * 2, 2)) + 1)] used = set() for k, v in count.items(): for i in range(len(wait_list)): if wait_list[i] - k in count: if wait_list[i] - k == k and v > 1: ans += (1 + v - 1) * (v - 1) // 2 ans = ans % (10 ** 9 + 7) elif wait_list[i] - k == k: pass else: if wait_list[i] - k not in used: ans += v * count[wait_list[i] - k] ans = ans % (10 ** 9 + 7) used.add(k) return ans % (10 ** 9 + 7)1702. 修改后的最大二进制字符串1702. 修改后的最大二进制字符串class Solution: def maximumBinaryString(self, binary: str) -> str: left = 0 binary = list(binary) while left < len(binary) and binary[left] == '1': left += 1 right = left + 1 while right < len(binary): if binary[right] == '1': right += 1 continue if right - left == 1: binary[left] = '1' else: binary[left] = '1' binary[right] = '1' binary[left + 1] = '0' left += 1 right += 1 return ''.join(binary)133. 克隆图133. 克隆图class Node: def __init__(self, val=0, neighbors=None): self.val = val self.neighbors = neighbors if neighbors is not None else [] from typing import Optional class Solution: def __init__(self): self.created = {} self.used = set() def cloneGraph(self, node: Optional['Node']) -> Optional['Node']: if node is None: return None val = node.val self.dfs(node) return self.created[val] def dfs(self, node: Optional['Node']): if node.val in self.used: return if node.val not in self.created: new_node = Node(node.val) self.created[node.val] = new_node else: new_node = self.created[node.val] self.used.add(node.val) for i in range(len(node.neighbors)): if node.neighbors[i].val in self.created: new_node.neighbors.append(self.created[node…

LeetCode（2023-09-21）
type: item
date: 2023-09-21T08:33:00+00:00
description: 2260. 必须拿起的最小连续卡牌数2260. 必须拿起的最小连续卡牌数class Solution: def minimumCardPickup(self, cards: List[int]) -> int: res = {} mark = {cards[k]: False for k in range(len(cards))} for i in range(len(cards)): if cards[i] in res: if res[cards[i]][1] - res[cards[i]][0] > i - res[cards[i]][2] or res[cards[i]][1] == res[cards[i]][0]: res[cards[i]] = [res[cards[i]][2], i, i] mark[cards[i]] = True res[cards[i]][2] = i else: res[cards[i]] = [i, i, i] ans = float('inf') for k, v in res.items(): if v[0] == v[1]: continue if mark[k] and ans > v[1] - v[0] + 1: ans = v[1] - v[0] + 1 return ans if ans != float('inf') else -11893. 检查是否区域内所有整数都被覆盖1893. 检查是否区域内所有整数都被覆盖class Solution: def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool: ranges.sort(key=lambda x: x[0]) father = [ranges[i][0] for i in range(len(ranges))] for i in range(1, len(ranges)): if ranges[i][0] < ranges[i - 1][1] and ranges[i][1] < ranges[i - 1][1]: ranges[i][1] = ranges[i - 1][1] for i in range(1, len(ranges)): if ranges[i][0] - ranges[i - 1][1] <= 1: father[i] = father[i - 1] left_father = None right_father = None for i in range(len(ranges)): if ranges[i][0] <= left <= ranges[i][1] and not left_father: left_father = father[i] if ranges[i][0] <= right <= ranges[i][1] and not right_father: right_father = father[i] if left_father and right_father: break if left_father is None and right_father is None: return False else: return left_father == right_father2269. 找到一个数字的 K 美丽值2269. 找到一个数字的 K 美丽值class Solution: def divisorSubstrings(self, num: int, k: int) -> int: num = str(num) ans = 0 for i in range(len(num) - k + 1): sub_num = int(num[i:i+k]) if sub_num == 0: continue if int(num) % sub_num == 0: ans += 1 return ans1779. 找到最近的有相同 X 或 Y 坐标的点1779. 找到最近的有相同 X 或 Y 坐标的点class Solution: def nearestValidPoint(self, x: int, y: int, points: List[List[int]]) -> int: ans = 0 distance = float('inf') for i in range(len(points)): if points[i][0] == x or points[i][1] == y: dis = abs(points[i][0] - x) + abs(points[i][1] -…