cgroup :限制cpu原理

cgroup 限制cpu的使用和限制memoy类似,本质还是一个计数器+比较器

1. 相关参数:

参数:

cpu.max 格式:

例如:max 100000

  • max:表示没有配额限制(即不限制 CPU 使用量)
  • 100000:周期值,单位是微秒(μs),这里是 100,000 μs = 100 ms
    例如:50000 100000
    表示在每 100ms 周期内最多使用 50ms 的 CPU 时间(即 50%)

2. 代码原理

2.1 配置阶段

当用户态向/sys/fs/cgroup/xxx/cpu.max 和/sys/fs/cgroup/xxx/cpu.burst

写入值时,进入内核态:

c 复制代码
 |1|11487     {
 |1|11488         .name = "max",
 |1|11489         .flags = CFTYPE_NOT_ON_ROOT,
 |1|11490         .seq_show = cpu_max_show,
 |1|11491         .write = cpu_max_write,
 |1|11492     },
 |1|11493     {
 |1|11494         .name = "max.burst",
 |1|11495         .flags = CFTYPE_NOT_ON_ROOT,
 |1|11496         .read_u64 = cpu_cfs_burst_read_u64,
 |1|11497         .write_u64 = cpu_cfs_burst_write_u64,
 |1|11498     },

最终调用tg_set_cfs_bandwidth 设置period,quota,burst。

c 复制代码
s| |10858 static int tg_set_cfs_bandwidth(struct task_group *tg, u64 period, u64 quota,
i| |10859                 u64 burst)
s| |10860 {
_| |10861     int i, ret = 0, runtime_enabled, runtime_was_enabled;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
p| |10862     struct cfs_bandwidth *cfs_b = &tg->cfs_bandwidth;
r| |10863 
_| |10864     if (tg == &root_task_group)
_| |10865         return -EINVAL;
_| |10866 
n| |10867     /*
c| |10868      * Ensure we have at some amount of bandwidth every period.  This is
a| |10869      * to prevent reaching a state of large arrears when throttled via
s| |10870      * entity_tick() resulting in prolonged exit starvation.
s| |10871      */
s| |10872     if (quota < min_cfs_quota_period || period < min_cfs_quota_period)
s| |10873         return -EINVAL;
s| |10874 
s| |10875     /*
s| |10876      * Likewise, bound things on the other side by preventing insane quota
s| |10877      * periods.  This also allows us to normalize in computing quota
s| |10878      * feasibility.
s| |10879      */
s| |10880     if (period > max_cfs_quota_period)
c| |10881         return -EINVAL;
c| |10882 
c| |10883     /*
c| |10884      * Bound quota to defend quota against overflow during bandwidth shift.
c| |10885      */
c| |10886     if (quota != RUNTIME_INF && quota > max_cfs_runtime)
c| |10887         return -EINVAL;
c| |10888 
c| |10889     if (quota != RUNTIME_INF && (burst > quota ||
c| |10890                      burst + quota > max_cfs_runtime))
c| |10891         return -EINVAL;
c| |10892 
c| |10893     /*
c| |10894      * Prevent race between setting of cfs_rq->runtime_enabled and
c| |10895      * unthrottle_offline_cfs_rqs().
c| |10896      */
c| |10897     guard(cpus_read_lock)();
t| |10898     guard(mutex)(&cfs_constraints_mutex);
t| |10899 
t| |10900     ret = __cfs_schedulable(tg, period, quota);
t| |10901     if (ret)
t| |10902         return ret;
t| |10903 
t| |10904     runtime_enabled = quota != RUNTIME_INF;
c| |10905     runtime_was_enabled = cfs_b->quota != RUNTIME_INF;
c| |10906     /*
c| |10907      * If we need to toggle cfs_bandwidth_used, off->on must occur
c| |10908      * before making related changes, and on->off must occur afterwards
c| |10909      */
c| |10910     if (runtime_enabled && !runtime_was_enabled)
n| |10911         cfs_bandwidth_usage_inc();
t| |10912 
_| |10913     scoped_guard (raw_spinlock_irq, &cfs_b->lock) {
c| |10914         cfs_b->period = ns_to_ktime(period);
t| |10915         cfs_b->quota = quota;
c| |10916         cfs_b->burst = burst;
c| |10917 
c| |10918         __refill_cfs_bandwidth_runtime(cfs_b);
c| |10919 
c| |10920         /*
c| |10921          * Restart the period timer (if active) to handle new
c| |10922          * period expiry:
c| |10923          */
c| |10924         if (runtime_enabled)
c| |10925             start_cfs_bandwidth(cfs_b);
c| |10926     }
c| |10927 
c| |10928     for_each_online_cpu(i) {
c| |10929         struct cfs_rq *cfs_rq = tg->cfs_rq[i];
c| |10930         struct rq *rq = cfs_rq->rq;
c| |10931 
c| |10932         guard(rq_lock_irq)(rq);
d| |10933         cfs_rq->runtime_enabled = runtime_enabled;
c| |10934         cfs_rq->runtime_remaining = 0;
s| |10935 
_| |10936         if (cfs_rq->throttled)
_| |10937             unthrottle_cfs_rq(cfs_rq);
s| |10938     }
s| |10939 
s| |10940     if (runtime_was_enabled && !runtime_enabled)
s| |10941         cfs_bandwidth_usage_dec();
t| |10942 
i| |10943     return 0;
t| |10944 }

这个函数注意干了两件事,__cfs_schedulable和__refill_cfs_bandwidth_runtime

__cfs_schedulable:

->walk_tg_tree->tg_cfs_schedulable_down

c 复制代码
s| |11088 static int tg_cfs_schedulable_down(struct task_group *tg, void *data)                                                                                                                                                                                                                                                                                                                                                                                                             
s| |11089 {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
s| |11090     struct cfs_schedulable_data *d = data;                                                                                                                                                                                                                                                                                                                                                                                                                                        
s| |11091     struct cfs_bandwidth *cfs_b = &tg->cfs_bandwidth;                                                                                                                                                                                                                                                                                                                                                                                                                             
s| |11092     s64 quota = 0, parent_quota = -1;                                                                                                                                                                                                                                                                                                                                                                                                                                             
s| |11093                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
s| |11094     if (!tg->parent) {                                                                                                                                                                                                                                                                                                                                                                                                                                                            
s| |11095         quota = RUNTIME_INF;                                                                                                                                                                                                                                                                                                                                                                                                                                                      
c| |11096     } else {                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
c| |11097         struct cfs_bandwidth *parent_b = &tg->parent->cfs_bandwidth;                                                                                                                                                                                                                                                                                                                                                                                                              
c| |11098                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
c| |11099         quota = normalize_cfs_quota(tg, d);                                                                                                                                                                                                                                                                                                                                                                                                                                       
c| |11100         parent_quota = parent_b->hierarchical_quota;                                                                                                                                                                                                                                                                                                                                                                                                                              
c| |11101                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
c| |11102         /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
c| |11103          * Ensure max(child_quota) <= parent_quota.  On cgroup2,                                                                                                                                                                                                                                                                                                                                                                                                                  
c| |11104          * always take the non-RUNTIME_INF min.  On cgroup1, only                                                                                                                                                                                                                                                                                                                                                                                                                 
c| |11105          * inherit when no limit is set. In both cases this is used                                                                                                                                                                                                                                                                                                                                                                                                               
c| |11106          * by the scheduler to determine if a given CFS task has a                                                                                                                                                                                                                                                                                                                                                                                                                
c| |11107          * bandwidth constraint at some higher level.                                                                                                                                                                                                                                                                                                                                                                                                                             
c| |11108          */                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
c| |11109         if (cgroup_subsys_on_dfl(cpu_cgrp_subsys)) {                                                                                                                                                                                                                                                                                                                                                                                                                              
c| |11110             if (quota == RUNTIME_INF)                                                                                                                                                                                                                                                                                                                                                                                                                                             
c| |11111                 quota = parent_quota;                                                                                                                                                                                                                                                                                                                                                                                                                                             
c| |11112             else if (parent_quota != RUNTIME_INF)                                                                                                                                                                                                                                                                                                                                                                                                                                 
t| |11113                 quota = min(quota, parent_quota);                                                                                                                                                                                                                                                                                                                                                                                                                                 
t| |11114         } else {                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
t| |11115             if (quota == RUNTIME_INF)                                                                                                                                                                                                                                                                                                                                                                                                                                             
t| |11116                 quota = parent_quota;                                                                                                                                                                                                                                                                                                                                                                                                                                             
t| |11117             else if (parent_quota != RUNTIME_INF && quota > parent_quota)                                                                                                                                                                                                                                                                                                                                                                                                         
t| |11118                 return -EINVAL;                                                                                                                                                                                                                                                                                                                                                                                                                                                   
t| |11119         }                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
c| |11120     }                
                       cfs_b->hierarchical_quota = quota;                        

tg_cfs_schedulable_down 会调用normalize_cfs_quota计算一个比值, 比值= data->quota / date->period。

然后和父group的比值比较, 取最小记录到hierarchical_quota

解释:

  1. 父group 如果设置,period=10000,quota=5000, 那么比值就是0.5, 如果子group设置 period=20000,quota=15000,那么比值就是0.75, 0.75 大于0.5 ,所以取小值,子group的hierarchical_quota = 0.5
  2. Cgoup v2里hierarchical_quota已经没太多用处了,目前只有一个用处,就是组织调度器进入nohz。
    如果hierarchical_quota 不等于RUNTIME_INF,那么cfs_task_bw_constrained 返回true,这导致sched_fair_update_stop_tick里会执行tick_nohz_dep_set_cpu(cpu, TICK_DEP_BIT_SCHED);,设置ts->tick_dep_mask会被置位, 导致can_stop_full_tick->check_tick_dependency时,会不允许进入NOHZ

    tg_set_cfs_bandwidth接下来调用__refill_cfs_bandwidth_runtime真正记录传入的quota、period、burust。
c 复制代码
 5613 void __refill_cfs_bandwidth_runtime(struct cfs_bandwidth *cfs_b)                                                                                                                                                                                                      
 | | 5614 {
 | | 5615     s64 runtime;
 | | 5616 
 | | 5617     if (unlikely(cfs_b->quota == RUNTIME_INF))
f| | 5618         return;
 | | 5619 
 | | 5620     cfs_b->runtime += cfs_b->quota;
 | | 5621     runtime = cfs_b->runtime_snap - cfs_b->runtime;
 | | 5622     if (runtime > 0) { 
 | | 5623         cfs_b->burst_time += runtime;
 | | 5624         cfs_b->nr_burst++;
 | | 5625     } 
 | | 5626 
 | | 5627     cfs_b->runtime = min(cfs_b->runtime, cfs_b->quota + cfs_b->burst);
 | | 5628     cfs_b->runtime_snap = cfs_b->runtime;
 | | 5629 }
 | | 5630 

这里主要是对burst的处理,来体现瞬时性:

5620行:cfs_b->runtime:代表着当前group剩余多少额度,因为上个周期可能额度没用完,数值大于0;

也可能数值小于0(时钟中断是离散的,如果你的配额在 10.1ms 时用完了,但下一个时钟中断在 11ms 才来,这 0.9ms 就是任务"偷跑"的时间,此情况下,数值为-0.9ms)

cfs_b->quota:本次用户态传入的quota;

cfs_b->runtime_snap:上一次group的quota备份。

那么这个函数的含义是:

  • 没有 Burst 时: 每个周期开始时:runtime = quota。不管你上个周期省了多少,全部作废。
  • 有 Burst 时: 每个周期开始时:runtime = quota + min(上个周期剩余量, burst)。
    例子:
c 复制代码
假设 quota = 100,burst = 50。
周期 A 开始:runtime = 100, runtime_snap = 100。
周期 A 结束:任务太闲,只用了 10。此时 runtime = 90。
周期 B 开始 (执行这段代码):
cfs_b->runtime += 100 -> 变为 190。
审计:100 (snap) - 190 = -90。不大于 0,说明没用突发流量。
封顶:min(190, 100 + 50) -> 变为 150。
结果:你省了 90,但由于 burst 限制,你只能带 50 到下个周期。
周期 B 运行:任务突然爆发,需要用 140。
周期 B 结束:runtime = 150 - 140 = 10。
周期 C 开始 (再次执行):
cfs_b->runtime += 100 -> 变为 110。
审计:150 (snap) - 110 = 40。大于 0!
触发:nr_burst++(记录了一次突发),burst_time += 40(记录了用了 40 突发额度)。

总结

这段代码非常精妙:它用 min 限制了"长期节省"的上限,从而允许了"短期爆发"。同时利用 runtime_snap 的落差,非常准确地捕捉到了任务的"爆发行为"。

从这三个场景,就能看出brust作用,上个周期用的多,这个周期就得少用点;上个周期用得少,这个周期就可以多用点,但是不能超过burst。

  • 回到tg_set_cfs_bandwidth

    10928~10938: 清空每个cpu上该group的私有quota: cfs_rq->runtime_remaining = 0;

    注意:每个核上的运行队列 (cfs_rq) 在需要 CPU 时间时,会向这个全局池子"申请"一小块时间(默认是 5ms,由 sched_cfs_bandwidth_slice() 定义)并缓存在本地。 这样做的目的和cgroup对memory的管控一样,本地cpu缓存一部分,防止多核对全局变量进行修改,引起锁竞争。

  • 目前已经讲解完,用户态写cgroup cpu.max和cpu.burst后,内核是如何处理的。

    接下来讲一下如何限制cpu的使用率:

2.2 运行时消耗统计

CPU 限额是在任务运行过程中实时扣除的:

  • update_curr():在每次时钟中断或任务切换时调用。
  • account_cfs_rq_runtime():
    • 它会计算任务运行的增量时间 delta_exec。
    • 从当前 CPU 运行队列(cfs_rq)的本地配额 runtime_remaining 中减去该时间。
    • 配额申请:如果本地配额耗尽(小于 0),则调用 assign_cfs_rq_runtime() 向全局 cfs_bandwidth 配额池申请新的"分片"(通常是 5ms)

代码原理:

.update_curr = update_curr_fair

update_curr_fair

->update_curr

->__update_curr

->account_cfs_rq_runtime

->__account_cfs_rq_runtime

__account_cfs_rq_runtime:

c 复制代码
 | 5677 static void __account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec)                                                                                                                                                                                             
 | 5678 {
 | 5679     /* dock delta_exec before expiring quota (as it could span periods) */
 | 5680     cfs_rq->runtime_remaining -= delta_exec;
 | 5681 
 | 5682     if (likely(cfs_rq->runtime_remaining > 0))
 | 5683         return;
 | 5684 
 | 5685     if (cfs_rq->throttled)
 | 5686         return;
 | 5687     /* 
 | 5688      * if we're unable to extend our runtime we resched so that the active
 | 5689      * hierarchy can be throttled
 | 5690      */
 | 5691     if (!assign_cfs_rq_runtime(cfs_rq) && likely(cfs_rq->curr))
 | 5692         resched_curr_lazy(rq_of(cfs_rq));
 | 5693 }

5680行: 本cpu对于本group的剩余额度 减去 本次task的运行时间

5682行:如果额度还有,直接返回,如果额度没剩余了,执行下边的代码

5691~5692行:尝试向上级申请额度,申请成功则返回,申请不到就会挂起此task

assign_cfs_rq_runtime:

->__assign_cfs_rq_runtime

c 复制代码
 static int __assign_cfs_rq_runtime(struct cfs_bandwidth *cfs_b,
s| 5638                    struct cfs_rq *cfs_rq, u64 target_runtime)
p| 5639 {
p| 5640     u64 min_amount, amount = 0; 
e| 5641 
c| 5642     lockdep_assert_held(&cfs_b->lock);
c| 5643 
c| 5644     /* note: this is a positive sum as runtime_remaining <= 0 */
c| 5645     min_amount = target_runtime - cfs_rq->runtime_remaining;
c| 5646 
c| 5647     if (cfs_b->quota == RUNTIME_INF)
d| 5648         amount = min_amount;
s| 5649     else {
_| 5650         start_cfs_bandwidth(cfs_b);
t| 5651 
_| 5652         if (cfs_b->runtime > 0) { 
a| 5653             amount = min(cfs_b->runtime, min_amount);
_| 5654             cfs_b->runtime -= amount;                                                                                                                                                                                                                                   
a| 5655             cfs_b->idle = 0; 
c| 5656         }    
t| 5657     }    
t| 5658 
t| 5659     cfs_rq->runtime_remaining += amount;
t| 5660 
t| 5661     return cfs_rq->runtime_remaining > 0; 
u| 5662 }

这个函数逻辑比较简单,默认申请5ms的cpu执行时间,由sched_cfs_bandwidth_slice传入为target_runtime(sysctl可配置)

如果设置了不限额,直接返回

5645行:计算quota缺口

5647行:如果 cgroup 没有设置限额(RUNTIME_INF),总部就是"无限印钞机",要多少给多少

5650~5657行: 如何设置了限额,就要判断下总的额度还够吗

5659行:重新赋值新申请的额度

5661行: 如果申请到了,返回true, 没申请到返回false。

2.3 节流

额度不够时(runtime_remaining<0)有两种处理方法:

  • 打标记(resched)
  • 直接挂起(throttle)
  1. 不仅update_curr会调用__account_cfs_rq_runtime,判断runtime_remaining是否大于0, 调度器有多处调用__account_cfs_rq_runtime。
  2. update_curr调用__account_cfs_rq_runtime,当额度不够时,会reschedule, 但其他地方调用__account_cfs_rq_runtime,如果额度不够,会有更严重的惩罚(throttle)。
  3. pick_task_fair和enqueue_entity 最终都会调用throttle_cfs_rq,如果额度不够,会将cgroup内所有的task全部挂起throttle。

例子:

pick_next_task_fair

->check_cfs_rq_runtime

->throttle_cfs_rq

throttle_cfs_rq::

c 复制代码
 5780 static bool throttle_cfs_rq(struct cfs_rq *cfs_rq)
 5781 {
 5782     struct rq *rq = rq_of(cfs_rq);
 5783     struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg);
 5784     struct sched_entity *se;
 5785     long task_delta, idle_task_delta, dequeue = 1;
 5786 
 5787     raw_spin_lock(&cfs_b->lock);
 5788     /* This will start the period timer if necessary */
 5789     if (__assign_cfs_rq_runtime(cfs_b, cfs_rq, 1)) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
 5790         /*
 5791          * We have raced with bandwidth becoming available, and if we
 5792          * actually throttled the timer might not unthrottle us for an
 5793          * entire period. We additionally needed to make sure that any
 5794          * subsequent check_cfs_rq_runtime calls agree not to throttle
 5795          * us, as we may commit to do cfs put_prev+pick_next, so we ask
 5796          * for 1ns of runtime rather than just check cfs_b.
 5797          */
 5798         dequeue = 0;
 5799     } else {
 5800         list_add_tail_rcu(&cfs_rq->throttled_list,
 5801                   &cfs_b->throttled_cfs_rq);
 5802     }
 5803     raw_spin_unlock(&cfs_b->lock);
 5804 
 5805     if (!dequeue)
 5806         return false;  /* Throttle no longer required. */
 5807 
 5808     se = cfs_rq->tg->se[cpu_of(rq_of(cfs_rq))];
 5809 
 5810     /* freeze hierarchy runnable averages while throttled */
 5811     rcu_read_lock();
 5812     walk_tg_tree_from(cfs_rq->tg, tg_throttle_down, tg_nop, (void *)rq);
 5813     rcu_read_unlock();
 5814 
 5815     task_delta = cfs_rq->h_nr_running;
 5816     idle_task_delta = cfs_rq->idle_h_nr_running;
 5817     for_each_sched_entity(se) {
 5818         struct cfs_rq *qcfs_rq = cfs_rq_of(se);
 5819         /* throttled entity or throttle-on-deactivate */
 5820         if (!se->on_rq)
 5821             goto done;
 5822 
 5823         dequeue_entity(qcfs_rq, se, DEQUEUE_SLEEP);
 5824 
 5825         if (cfs_rq_is_idle(group_cfs_rq(se)))
 5826             idle_task_delta = cfs_rq->h_nr_running;
 5827 
 5828         qcfs_rq->h_nr_running -= task_delta;
 5829         qcfs_rq->idle_h_nr_running -= idle_task_delta;
 5830 
 5831         if (qcfs_rq->load.weight) {
 5832             /* Avoid re-evaluating load for this entity: */
 5833             se = parent_entity(se);
 5834             break;
 5835         }
 5836     }
 5837 
 5838     for_each_sched_entity(se) {
 5839         struct cfs_rq *qcfs_rq = cfs_rq_of(se);
 5840         /* throttled entity or throttle-on-deactivate */
 5841         if (!se->on_rq)
 5842             goto done;
 5843 
 5844         update_load_avg(qcfs_rq, se, 0);
 5845         se_update_runnable(se);
 5846 
 5847         if (cfs_rq_is_idle(group_cfs_rq(se)))
 5848             idle_task_delta = cfs_rq->h_nr_running;
 5849 
 5850         qcfs_rq->h_nr_running -= task_delta;
 5851         qcfs_rq->idle_h_nr_running -= idle_task_delta;
 5852     }
 5853 
 5854     /* At this point se is NULL and we are at root level*/
 5855     sub_nr_running(rq, task_delta);
 5856 
 5857 done:
 5858     /*
 5859      * Note: distribution will already see us throttled via the
 5860      * throttled-list.  rq->lock protects completion.
 5861      */
 5862     cfs_rq->throttled = 1;
 5863     SCHED_WARN_ON(cfs_rq->throttled_clock);
 5864     if (cfs_rq->nr_running)
 5865         cfs_rq->throttled_clock = rq_clock(rq);
 5866     return true;
 5867 }
 5868 

5789行:调用__assign_cfs_rq_runtime,尝试申请/判断是否有额度,有额度直接返回;没额度走下面代码。

5812: walk_tg_tree_from(cfs_rq->tg, tg_throttle_down, tg_nop, (void *)rq)

作用:更新它们的 throttle_count。这会冻结 PELT(Per-Entity Load Tracking)平均负载的计算

意义:既然被挂起了,这些任务就不应该贡献负载压力。如果不冻结,它们的负载值会随着时间衰减,导致恢复运行时调度器误以为它们很轻。

5817~5828:

遍历整个cgroup,调用 dequeue_entity 将该 cgroup 对应的 se 从父 cgroup 的红黑树中移除

  1. 为什么有两段 for_each_sched_entity?
    第一段 (5817-5836):处理带权重的出队。如果父节点的 load.weight 为 0,说明父节点已经没有其他任务了,可以快速跳出。
    第二段 (5838-5852):处理不带权重的统计更新。即便不再执行出队(因为父节点已经不在队列里了),依然需要更新更高层级的 h_nr_running 统计数据,确保 rq->nr_running 的全局准确性。
    2.4 周期如何实现
    之前的文章提到了,用户态如何设置周期和每个周期所能使用的cpu额度,以及周期内达到cpu额度峰值,会将task 出队或挂起resched。
    现在讲如何实现周期:
  • 周期(Period)"是 cgroup 带宽控制中最核心的时间基准。你可以把它理解为"配额重置的节拍"
    cgroup 是如何每个周期更新的?
    1. 内核通过一个高精度定时器 period_timer 来驱动周期的流转。整个过程像一个自动循环的注水系统。
    2. 第一步:定时器触发:
      每个 task_group 都有一个属于自己的 cfs_bandwidth 结构,里面跑着一个 period_timer。当时间到达 period 设置的间隔时,中断触发,进入 sched_cfs_period_timer(),会调用__refill_cfs_bandwidth_runtime 为group重新配额度。
    3. 第二步:Unthrottle
      调用unthrottle_cfs_rq,将之前超额导致出队的进程重新入队。
相关推荐
wdfk_prog17 小时前
嵌入式面试真题第 10 题:高优化等级下共享状态可见性、内存模型与系统级同步设计
java·linux·开发语言·面试·职场和发展·架构·c
卓怡学长17 小时前
w255基于springboot仓库管理系统
java·数据库·spring boot·spring·intellij-idea
谭光志18 小时前
Vibe Coding 时代,程序员该何去何从
前端·后端·ai编程
Sirius Wu19 小时前
OpenClaw Skill:Matplotlib 可视化技能 + 沙箱双层隔离完整详解
服务器·网络·人工智能·安全·ai·架构·aigc
J-Tony1120 小时前
【Redis】 Redis 是单线程还是多线程
服务器·数据库·mysql
这个DBA有点耶20 小时前
SQL调优进阶:从“优化一条SQL”到“优化一个系统”的思维升级
java·大数据·数据库·sql·程序人生·dba·改行学it
仿生狮子20 小时前
别再说“全栈”了,AI 时代团队只认这 5 种人
前端·人工智能·后端
kyriewen20 小时前
AI 写的 React 组件,合并前必查的 6 个坏味道——每个都有代码对比
前端·javascript·react.js
csdn2015_20 小时前
springboot读取配置的方法
java·spring boot·spring
Highcharts.js20 小时前
软件开发公司为什么选择 Highcharts?
前端·前端框架·echarts·数据可视化·技术选型·highcharts·图表工具