Redis高并发锁(三)分布式锁

news2024/11/16 16:45:44

在很多情况下,你的数据库不支持事务,分布式部署也使得你无法去使用JVM锁,那么这种时候,你可以考虑用分布式锁

文章目录

  • 分布式锁
    • 1. 实现方式
    • 2. 特征
    • 3. 操作
    • 4. 代码改造
    • 5. 测试
  • 优化
    • 1. 递归改成循环
    • 2. 防止死锁
    • 3. 防误删
    • 4. LUA脚本 保证原子性
      • 1) EVAL基本使用
      • 2)代码实现
      • 3) 测试
    • 5 分布式可重入锁: lua脚本 + hash
      • redis的hash数据结构
        • 1) hset
        • 2) hexists
        • 3) hincrby
      • 加锁优化为可重入
        • 1)写成LUA脚本
        • 2) 测试
      • 解锁优化为可重入
        • 1) 编写LUA脚本
        • 2)测试
      • 代码改造
        • 1) 代码较复杂,将lock类单独拿出来定义使用
        • 2) 工厂模式 DistributedLockClent
        • 3)DistributedRedisLock
        • 4) StockService
      • 测试
      • uuid优化
        • 1) DistributedLockClient
        • 2) DistributedRedisLock
    • 6. 自动续期
      • 1) Timer定时器
      • 2) LUA脚本
      • 3) DistributedRedisLock
      • 4) 测试
  • 总结
    • 加锁
    • 解锁
    • 重试 循环/迭代

分布式锁

1. 实现方式

  • 基于redis实现
  • 基于zookeeper/etcd实现
  • 基于mysql实现

2. 特征

(1) 独占排他使用 setnx

(2) 防死锁发生:
redis客户端获取到锁后,立马宕机(设置失效时间)
不可重入:可重入

(3) 原子性:

  • 获取锁和设置过期 set key value ex 3 nx
  • 判断和释放锁之间:使用 lua脚本

(4) 防误删:解铃还须系铃人,先判断再删除

(5) 可重入性:hash(key field value) + LUA脚本

(6) 自动续期:实际执行时间大于设置的失效时间
定时任务(时间驱动Timer定时器)+ lua脚本

3. 操作

  • 加锁
  • 解锁
  • 重试:递归、循环

简单来说,其实就类似于操作系统中的临界区-临界资源
一群线程去争抢该资源

  1. 经过一道阀口,只有一个线程可以抢到钥匙
  2. 持有钥匙的线程去执行,其他线程循环等待
  3. 该线程执行完成,归还钥匙,下一个抢到钥匙的线程执行
    循环如此

那么代码上具体是如何操作呢

4. 代码改造


@Service
public class StockService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public void deduct() {

        // 用排他锁
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "111");
        if(Boolean.FALSE.equals(flag)){
            // 如果没抢到,等50ms后,接着抢
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            deduct();
            return;
        }
        // 如果抢到了,直接执行

        try{
            // 1。 查询库存
            String stockStr = redisTemplate.opsForValue().get("stock");
            if (!StringUtil.isNullOrEmpty(stockStr)) {
                int stock = Integer.parseInt(stockStr);
                // 2。 判断条件是否满足
                if (stock > 0) {
                    // 3 更新redis
                    redisTemplate.opsForValue().set("stock", String.valueOf(stock - 1));
                }
            }
        }finally {
            redisTemplate.delete("lock");
        }
    }

其实就是用一个新的redis key作为这把钥匙,

  1. 某个线程成功setIfAbsent,那它就拿到了这把钥匙,其他线程只能等待
  2. 该线程执行完成后,delete该key
  3. 另一个成功setIfAbsent的线程拿到钥匙,开始执行

5. 测试

在这里插入图片描述
这里我直接使用了本地redis,之前连接的都是远程redis和mysql,发行并发量很低,后续都直接用本地了
可以看出并发量很大,达到了455

在这里插入图片描述
redis中库存也成功清0

优化

1. 递归改成循环


@Service
public class StockService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public void deduct() {

        while(!redisTemplate.opsForValue().setIfAbsent("lock", "111")){
            // 如果没抢到,等50ms后,接着抢
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        // 如果抢到了,直接执行

        try{
            // 1。 查询库存
            String stockStr = redisTemplate.opsForValue().get("stock");
            if (!StringUtil.isNullOrEmpty(stockStr)) {
                int stock = Integer.parseInt(stockStr);
                // 2。 判断条件是否满足
                if (stock > 0) {
                    // 3 更新redis
                    redisTemplate.opsForValue().set("stock", String.valueOf(stock - 1));
                }
            }
        }finally {
            redisTemplate.delete("lock");
        }
    }

2. 防止死锁

若抢到钥匙的线程执行过程中,redis客户端程序突然宕机,那即使重启,该钥匙也永远无法自动归还了,所以设置个过期时间

redisTemplate.opsForValue()
.setIfAbsent("lock", "111",3,TimeUnit.SECONDS)

3. 防误删

利用一个唯一标识,比如线程号/uuid,先判断是否是自己的再删除

String uuid = UUID.randomUUID().toString();
redisTemplate.opsForValue()
.setIfAbsent("lock", uuid,3,TimeUnit.SECONDS)

然后再删除的时候进行判断

// 先判断是否自己的锁,再解锁(防误删)
            if(StrUtil.equals(redisTemplate.opsForValue().get("lock"),uuid)){
                redisTemplate.delete("lock");
            }

但该删除操作并不能保证判断再删除是原子性的,在高并发情况下,
A判断成功后,属于A的key刚好失效,
然后B拿到了锁,最后A 删掉的是B的锁。

4. LUA脚本 保证原子性

redis单线程,执行指令遵循one-by-one。
而LUA脚本一次性发送多个指令给redis,保证原子性。

1) EVAL基本使用

EVAL script numkeys key [key …] arg [arg …]
script: lua脚本字符串
numkeys: key列表的元素数量
key列表:以空格分割 KEYS(index从1开始)
arg列表:以空格分割 ARGV(index从1开始)

  • 输出的不是print,而是return返回值

在这里插入图片描述

  • 全局变量 a = 5 (redis不允许定义全局变量)

  • 局部变量 local a = 5
    在这里插入图片描述

  • 分支控制
    if 条件 then 代码块 elseif 条件 then 代码块 end
    在这里插入图片描述

  • 传递参数 KEYS[] ARGV[]
    在这里插入图片描述

  • LUA来调用redis的常见命令 get
    在这里插入图片描述
    与redis的执行命令顺序一致,如果是set
    在这里插入图片描述
    非常简单,弄懂了后就可以开始使用lua脚本

// 判断是否是自己的锁,如果是自己的锁,则执行删除操作
if redis.call(‘get’, ‘lock’) == uuid
then return redis.call(‘del’,‘lock’)
else return 0
end
key: lock
argv: uuid

整理成一句lua脚本语句

EVAL " if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end" 1 ‘lock’ ‘1234’

简单测试一下
在这里插入图片描述
修改下,看不是自己的lock能不能删除
在这里插入图片描述
不是自己的lock,则不删除,ok

2)代码实现

String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                    "then return redis.call('del',KEYS[1]) " +
                    "else return 0 " +
                    "end\"";
// 先判断是否自己的锁,再解锁(防误删)
redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);

注意execute参数,

 new DefaultRedisScript<>(script, Boolean.class) 脚本和运行返回类型
 Arrays.asList("lock") keys列表,所有keys放在列表中
 uuid argv参数,后续参数直接,叠加

3) 测试

在这里插入图片描述
并发可达475
在这里插入图片描述
redis也正常清零了,成功

5 分布式可重入锁: lua脚本 + hash

可参考ReentrantLock可重入锁解析锁实现分布式可重入锁

这个时候就考虑用redis的hash结构,因为你不仅只有key-value,还有个state属性,即加锁次数

redis的hash数据结构

1) hset

hset key field value

在这里插入图片描述
在这里插入图片描述
hash可以简单看成一个大Map中套着个小Map <key , <key1,value> >

2) hexists

作用:测试给定key下的field是否存在。
格式:hexists key field

在这里插入图片描述

3) hincrby

作用:增减数值
格式:hincrby key field increment

在这里插入图片描述

把age+1 变成了12

加锁优化为可重入

    1. 判断锁是否存在(exists),若不存在则直接获取锁 hset key field value
    1. 若锁存在则判断是否是自己的锁(hexists),若是则重入:hincrby key field increment
    1. 否则重试,递归/循环

1)写成LUA脚本

 if redis.call('exists','lock')==0 
 then redis.call('hset','lock',uuid,1) 
	  redis.call('expire','lock',30)
 	  return 1
 elseif redis.call('hexists','lock',uuid)==0
 then redis.call('hincrby','lock',uuid,1) 
 	  redis.call('expire','lock',30)
 	  return 1
 else return 0
 end

这里可以再优化下
在这里插入图片描述

hincrby若没有key field会自动生成,此处可替换hset

 if redis.call('exists','lock')==0  or redis.call('hexists','lock',uuid)==0
 then redis.call('hincrby','lock',uuid,1) 
 	  redis.call('expire','lock',30)
 	  return 1
 else return 0
 end

再将KEYS,ARGV加入后拼凑成一句语句

if redis.call('exists',KEYS[1])==0  or redis.call('hexists',KEYS[1],ARGV[1])==1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end

2) 测试

  1. 先简单加锁 参数 1 “lock” “1234567” 30
    在这里插入图片描述

在这里插入图片描述
加锁成功

  1. 重入加锁 参数 1 “lock” “1234567” 30
    在这里插入图片描述

在这里插入图片描述

  • value变成了2, 而且失效时间成功更新了
  1. 先参数 1 “lock” “123” 30 再执行参数 1 “lock” “1234567” 30

在这里插入图片描述
在这里插入图片描述
后一条加锁失败,直至行了123那条。测试成功。

解锁优化为可重入

1) 编写LUA脚本

  • 判断自己的锁是否存在(hexists),不存在则返回nil
  • 若自己的锁存在,则减1(hincrby -1),判断减1后的值是否为0,为0则释放锁(del)并返回1
  • 不为0,返回0

LUA脚本

 if redis.call('hexists','lock',uuid) == 0
 then return nil
 elseif redis.call('hincrby', 'lock',uuid,-1)==0 
 then return redis.call('del','lock')
 else return 0
 end

把参数和key填充进去后,优化成一句语句

 if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then return nil elseif redis.call('hincrby', KEYS[1],ARGV[1],-1)==0 then return redis.call('del',KEYS[1]) else return 0 end

2)测试

  • 先参数 1 “lock” “1234567”
    在这里插入图片描述
    当前没有该锁,返回nil

  • 用之前脚本加锁
    在这里插入图片描述
    加锁一次,解锁一次,返回1,解锁第二次,返回nil

  • 多次加锁,多次解锁
    在这里插入图片描述
    两次加锁成功,返回1,解锁第一次返回0,第二次返回1。

代码改造

1) 代码较复杂,将lock类单独拿出来定义使用

DistributedRedisLock 参照ReentrantLock实现Lock接口

public class DistributedRedisLock implements Lock {
    @Override
    public void lock() {
        
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

2) 工厂模式 DistributedLockClent

由于后续可能还有其他分布式锁类,直接用工厂模式实现。
将StringRedisTemplate传递,因为只有DistributedLockClent添加了@Component供其他类注入

@Component
public class DistributedLockClient {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public DistributedRedisLock getRedisLock(String key){
        return new DistributedRedisLock(redisTemplate,key);
    }

}

3)DistributedRedisLock

-加锁

	@Override
    public void lock() {
        tryLock();
    }
 	@Override
    public boolean tryLock() {
        try {
            return tryLock(-1,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
	
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

层层调用后修改tryLock即可。

/**
     * 加锁方法
     * @param time the maximum time to wait for the lock
     * @param unit the time unit of the {@code time} argument
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // 如果不是默认调用的,那么自动计算出秒数
        if(time != -1){
            expire = unit.toSeconds(time);
        }

        // 如果没有锁加锁,如果是我的锁,重入 state+1
        String script = "if redis.call('exists',KEYS[1])==0  or redis.call('hexists',KEYS[1],ARGV[1])==1 " +
                "then" +
                "   redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                "   redis.call('expire',KEYS[1],ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";

        // 如果return 0 即抢锁失败,则循环抢
        while (Boolean.FALSE.equals(redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(key), uuid, String.valueOf(expire)))){
            Thread.sleep(50);
        }
        return true;
    }
  • 解锁
 /**
     * 解锁方法
     */
    @Override
    public void unlock() {
        // 如果不是我的锁,return nil 是我的锁减1,建减后state为0,则全部解锁完成return 1 ,不为0, 则return 0
        String script = " if redis.call('hexists',KEYS[1],ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1],ARGV[1],-1)==0 " +
                "then " +
                "   return redis.call('del',KEYS[1]) " +
                "else return 0 " +
                "end";
        Long flag = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class),Arrays.asList(key),uuid);
        if(flag == null){
            throw new IllegalMonitorStateException("this lock does not belong to you");
        }
    }

由于返回值有nil , 1, 0 所有返回类型为 Long.class,分别对应null,1,0

4) StockService

@Service
public class StockService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLockClient distributedLockClient;

    public void deduct() {
        DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock");
        redisLock.lock();

        try{
            // 1。 查询库存
            String stockStr = redisTemplate.opsForValue().get("stock");
            if (!StringUtil.isNullOrEmpty(stockStr)) {
                int stock = Integer.parseInt(stockStr);
                // 2。 判断条件是否满足
                if (stock > 0) {
                    // 3 更新redis
                    redisTemplate.opsForValue().set("stock", String.valueOf(stock - 1));
                }
            }
        }finally {
           redisLock.unlock();
        }
    }
}

测试

在这里插入图片描述
并发达到了440
在这里插入图片描述
库存也正常清空

uuid优化

目前是每个线程都生成一个uuid,在可重入可能会出现问题,

  public void test1(){
        DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock");
        redisLock.lock();
        test2();
        redisLock.unlock();
        
    }
    public void test2(){
        DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock");
        redisLock.lock();
        redisLock.unlock();
    }

优化为线程id+uuid,且一个工厂类生成一个uuid的方式

1) DistributedLockClient

@Component
public class DistributedLockClient {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public DistributedLockClient() {
        this.uuid = UUID.randomUUID().toString();
    }

    private String uuid;

    public DistributedRedisLock getRedisLock(String key){
        return new DistributedRedisLock(redisTemplate,key,uuid);
    }

}

2) DistributedRedisLock

public String getId(){
        return uuid + ":" + Thread.currentThread().getId();
    }

把之前的uuid使用地方,替换成getId()即可。

测试后成功。

6. 自动续期

定时任务(时间驱动 Timer定时器) + LUA脚本

1) Timer定时器

public static void main(String[] args) {
        System.out.println("定时任务开始时间" + System.currentTimeMillis());

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时任务执行时间" + System.currentTimeMillis());
            }
        },2000,5000);
    }

在这里插入图片描述
由于它可以手动取消,所以选择了它来使用

2) LUA脚本

  • 判断自己的锁是否存在(hexists),若存在则重置过期时间
if redis.call('hexists','lock',uuid) == 1 
then return redis.call('expire','lock',30)
else return 0
end

整理后

if redis.call('hexists',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end

3) DistributedRedisLock

 private void renewExpire(){
        // 如果锁存在则续期,不存在则不续期
        String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire',KEYS[1],ARGV[2]) " +
                "else " +
                "   return 0 end";

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                // 如果锁还在,则自动续期
                if(Boolean.TRUE.equals(redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(key), uuid, String.valueOf(expire)))){
                    renewExpire();
                }

            }
        },expire*1000/3);
    }

同时,修改初始化的uuid,防止定时器内部的线程id不同。

public DistributedRedisLock(StringRedisTemplate redisTemplate, String key, String uuid) {
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.uuid = uuid + ":" + Thread.currentThread().getId();
    }

4) 测试

在这里插入图片描述
成功,但并发较低100,很耗线程。

总结

加锁

  1. setnx: 独占排他 死锁、不可重入、原子性
  2. set k v ex 30 nx: 独占排他 死锁 不可重入
  3. hash + lua脚本: 可重入锁
  • 判断锁是否占用,若没有占用则直接获取锁,并设置过期时间
  • 若锁被占用,则判断是否当前线程占用,如果是则重入并重置过期时间
  • 否则获取锁失败,在代码中重试
  1. Timer定时器+lua脚本:实现锁的自动续期
  • 判断是否是自己的锁,若是则续期。

解锁

  1. del: 导致误删
  2. 先判断再删除,同时保持原子性: lua脚本
  3. hash+lua脚本:可重入
  • 判断当前线程的锁是否存在,不存在则返回nil,抛出异常
  • 存在则直接减1,判断减1后的值是否为0,为0则释放锁(del),并返回1
  • 不为0,则返回0

重试 循环/迭代

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

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

相关文章

Arduino code for RS-365PW 16120

Pictures These pictures are from Baidu Search. Picture 1: Installment Picture 2: Appearance Picture 3: Encoder of Motor Picture 4: Pins location and number Physical Specification Brand: Mabuchi Motor (万宝至电机)Type: RS-365PW 16120 Body length&#xff1…

学生抢课接口(高并发入门)

目录 使用Mysql 常规测试 张三测试 流程总结 redis优化 修改代码 测试 使用分布式锁 总结 使用Mysql 常规测试 原始代码: Override Transactional public ResponseResult selectCourse(SelectParmas selectParmas) {if (Objects.isNull(selectParmas)){return new …

【python游戏】新的一年快来变身兔兔战士打败獾守护兔兔吧~

前言 大家早好、午好、晚好吖 ❤ ~ 一只快乐兔&#xff0c; 来到快乐山&#xff0c;喝了快乐泉&#xff0c; 又到快乐殿&#xff0c;吃了快乐莲&#xff0c;遇到快乐仙&#xff0c; 听了快乐言&#xff1a;快乐很简单&#xff0c;快乐在身边&#xff0c;快乐无极限&#xff…

C++中STL的vector扩容机制

目录前言发生扩容扩容机制size()和capacity()reserve()和resize()前言 前阵子面试的时候&#xff0c;被问到往vector中插入一个数据可能会发生什么&#xff1f; 我答:可能会扩容; 为啥vector支持变长&#xff1f; 我答:它实在堆上动态申请内存&#xff0c;因此有自己的一套扩容…

Redis集群系列十 —— 集群伸缩之收缩

集群收缩原理 集群收缩就是让其中一些节点安全下线。 所谓的安全下线指的是让一个节点下线之前&#xff0c;把其负责的所有 slots 迁移到别的节点上&#xff0c;否则该节点下线后其负责的 slots 就没法继续提供服务了。 收缩流程如下&#xff1a; 需求 前面扩容完成后&…

字符串大小写转化,有序数组二分查找个人心得等若干内容

tips 1. 在电脑里面&#xff0c;任何一切字符&#xff0c;当一看到的时候&#xff0c;脑子里面就要把它转化成ACSII值。如while(0)&#xff0c;可以实现死循环。 2. 统计整形数组的长度不能用strlen()&#xff0c;别一天到晚用到底&#xff0c;strlen统计的是字符数组的长度 …

在wsl下开发T113的主线linux(1)-准备wsl开发环境

首先在win10或win11下安装wsl&#xff0c;选择wsl1或者wsl2都可以&#xff0c;wsl2的性能更高一些&#xff0c;wsl1的跨系统文件操作速度更快一些&#xff0c;我这里因为有一些工程在win文件系统下&#xff0c;所以选择了wsl1&#xff0c;发行版使用最新的Ubuntu 22.04.01 LTS。…

MySQL隐式转换

隐式转换概念 When an operator is used with operands of different types, type conversion occurs to make the operands compatible. Some conversions occur implicitly. 当运算符与不同类型的操作数一起使用时&#xff0c;将进行类型转换以使操作数兼容。某些转换是隐式发…

2022年年终总结---新方向,新期待

2022年行将结束&#xff0c;回首年初立下的flag&#xff1a; (1)完成OpenCoord版本升级&#xff0c;增加ITRF框架及历元转换、EGM2008查询功能&#xff1b; (2)完成多波束开源项目的数据读取和显示操作。 任务(1)已经完成了&#xff0c;任务&#xff08;2&#xff09;没有完成。…

力扣(LeetCode)2351. 第一个出现两次的字母(C++)

哈希集合 开哈希集合&#xff0c;遍历字符串&#xff0c;未出现的字母加入哈希集合&#xff0c;如果字母出现过&#xff0c;返回这个字母即可。 class Solution { public:char repeatedCharacter(string s) {unordered_set<char> S;for(auto &c:s)if(!S.count(c)) …

第二证券|72家公司接待机构过千,迈瑞医疗热度“断层领先”

2022年最终一个交易日&#xff0c;沪指以红盘收官&#xff0c;但年内A股商场震荡起伏&#xff0c;三大指数均收跌&#xff0c;其间&#xff0c;沪指全年下跌15%&#xff0c;创业板指跌近三成。 调研活动是出资者挖掘上市公司信息的重要来源&#xff0c;是洞悉商场主力资金意向的…

记录NCNN Yolov5部署华为鸿蒙系统踩过的坑

目录 踩坑一&#xff1a;Android Studio连接鸿蒙系统踩过的坑 踩坑二&#xff1a;配置Android studio环境 踩坑三&#xff1a;打开文件夹的位置 踩坑四&#xff1a;No toolchains found in the NDK toolchains folder for ABI with prefix: arm-linux-androideabi 总结 踩…

使用 Bitnami Helm 安装 Kafka

服务器端 K3S 上部署 Kafka Server Kafka 安装 &#x1f4da;️ Quote: charts/bitnami/kafka at master bitnami/charts (github.com) 输入如下命令添加 Helm 仓库&#xff1a; > helm repo add tkemarket https://market-tke.tencentcloudcr.com/chartrepo/opensource-…

LinuxShell注意事项

Linux 介绍 内存 虚拟内存 = 物理内存 + 交换空间 交换空间 = 交换空间 当用户访问某一个程序内存时,需要访问物理内存,而不是交换内存,如果物理内存没有,而交换内存有,则会将交换内存中的程序 加载进物理内存供用户使用,同样,当一个程序长期未访问,内核会将物理内…

【Mongoose笔记】Websocket 服务器

【Mongoose笔记】Websocket 服务器 简介 Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。 Mongoose 是一个 C/C 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。 项目地址&#xff1a; https://github.com/cesanta/mongoose学习 …

Chromium Embedded Framework(CEF)源码编译基于windows

1.打开维基百科CEF主页: Chromium Embedded Framework - Wikipedia 2.找到CEF仓库地址:chromiumembedded / cef — Bitbucket 打开CEF源码仓库: 点击 Wiki,然后在 Getting Started节,点击cef-project 向下拉浏览滚动条,可看到Setup章节

第三十二讲:神州交换机和路由器上分别实现QoS

QoS&#xff08;Quality of Service&#xff0c;服务品质保证&#xff09;是指一个网络能够利用各种各样的技术向选定的网络通信提供更好的服务的能力。QoS是服务品质保证&#xff0c;提供稳定、可预测的数据传送服务&#xff0c;来满足使用程序的要求&#xff0c;QoS不能产生新…

深入浅出闭包

目录 一、闭包概念 二、重载operator() 三、lambda表达式 3.1 lambda表达式介绍 3.2 lambda表达式语法 3.2.1 [capture-list]捕捉列表 3.2.2(parameters)&#xff1a;参数列表 3.2.3 mutable关键字 3.2.4 ->returntype&#xff1a;返回值类型 3.2.5 {statement}&a…

基于OpenSSL的安全Web Server实现

目录 一、实验目的 二、实验软硬件要求 三、实验预习 四、实验内容&#xff08;实验步骤、测试数据等&#xff09; 实验步骤 1编辑代码 2解决报错 3准备网页 五、实验体会&#xff08;遇到的问题及解决方法&#xff09; 六、服务器代码 七、测试网页代码 一、实验目…

(02)Cartographer源码无死角解析-(43) 2D栅格地图→

讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文末…