首页 > 其他分享 >用户空间锁-1-用户空间锁概述

用户空间锁-1-用户空间锁概述

时间:2024-04-24 15:00:31浏览次数:22  
标签:Java monitor lock 用户 futex mutex pthread 空间 概述

前言:

无论是内核锁还是用户空间锁,其基本原理都是一样的。这样,所有在内核锁上的优化其实都可以平移到用户空间。


一、上层锁概述

手机平台(特指安卓)平台上用户空间程序和锁相关的软件结构如下:


1. Java锁

安卓平台的Java层主要有二种锁的类型:JAVA内嵌锁和JUC锁。所谓Java内嵌锁就是 synchronized 关键字,在Java程序设计中,我们可以通过这个关键字完成互斥型的同步操作。Java内嵌锁使用非常的方便,对程序员非常友好,然而功能单一(例如不能完成读写锁的功能),不能完成复杂的同步逻辑,因此,Java世界中还有一个JUC的锁。JUC是Java Util Cocurrent的缩写,是一个Java package,功能非常的丰富。JUC中有一个lock的包,在 libcore/ojluni/src/main/java/java/util/concurrent/locks 路径下,提供了更加灵活的锁机制。

Java内嵌锁的互斥实现是在ART虚拟机中完成的。synchronized 关键字被解析成 monitor-enter 和 monitor-exit 两条Java字节码。ART中有monitor的模块来完成 monitorenter 和 monitorexit 两条Java字节码的解释执行。具体的互斥是通过虚拟机中的mutex模块完成。完成互斥功能有两个基本操作:原子操作(CAS(Compare and Switch))和排队操作。原子操作是有Atomic 模块提供,而排队功能需要通过futex系统调用,由内核提供支持。

虽然说Java内嵌锁的“锁”定义在上层(主要是指 Lockword),但是排队功能还是有内核参与的。而JUC锁则大部分功能(包括排队)都是在Java层实现,是当之无愧的“上层”锁。对于JUC中的锁,排队和 Lockword 的定义和操作都是在Java层实现,当然,“阻塞”和“唤醒”这样操作不可能在上层完成,因此对于JUC lock而言,这两个操作最终还是通过ART虚拟机中的线程控制模块来完成。这里仍然是需要futex的参与,但是futex仅仅提供阻塞功能,不再实现排队。此外,JUC lockword需要一些原子操作,这是由ART虚拟机的 sun misc unsafe 模块来完成的。


2. Native锁

和Java类似,C++也有内嵌锁(C++11的新增特性),例如 std::mutex。后续我们以互斥锁的持锁为例说明其基本的实现,其他的内嵌锁也是类似的。C++的 std::mutex 实现在 external/libcxx/src/mutex.cpp 文件中:

void mutex::lock()
{
    int ec = __libcpp_mutex_lock(&__m_);
    if (ec)
        __throw_system_error(ec, "mutex lock failed");
}

__libcpp_mutex_lock 函数本身又调用了 pthread_mutex_lock 函数来实现其互斥逻辑(参考 external/libcxx/include/__threading_support)。因此,本质上C++内嵌锁也就是pthread锁。

int __libcpp_mutex_lock(__libcpp_mutex_t *__m)
{
    return pthread_mutex_lock(__m);
}

pthread锁相信大家已经比较熟悉了,在安卓平台上,其功能由bionic实现(路径 android/bionic/libc/bionic/pthread_mutex.cpp),和内核的交互依然是我们的老朋友 futex 系统调用,在这里实现了等待队列功能。


二、Java内嵌锁

1. 概述

Java内嵌锁的软件结构图如下:


2. 上层使用场景

我们可以把 synchronized 关键字使用在一个类的实例方法上,如下:

public synchronized void method_need_sync() {
    //临界区代码
}

在这种使用场景中,method_need_sync 函数中的代码都是临界区,同一时间内只有一个线程可以进入该函数。当然,这种同步是针对该类的特定实例对象而言的。对象A和对象B的 method_need_sync 是无法保证互斥的。

synchronized 关键字也可以用在类的静态函数中,如下:

public static synchronized void static_method_need_sync() {
    //临界区代码
}

在这种使用场景中,锁实现在class对象中(在上面的同步实例方法中,锁实现在实例对象中),因此无论有多少实例,static_method_need_sync 函数都只有一个线程可以进入。除了一整个函数,我们还可以保护函数内部的代码块,如下:

public void codeblock_need_sync() {
    synchronized(this) {
        //临界区代码
    }
}

当然,无论什么方法,synchronized 关键字最终都是转换成 monitor-enter 和 monitor-exit 字节码,由ART虚拟机来进一步处理。


3. ThinLock和FatLock

Java编译器会解析 synchronized 关键字并在临界区代码的前面加上 monitor-enter 字节指令,而在临界区代码的后面加上 monitor-exit 字节指令。在具体执行的时候,无论是解释模式还是机器码执行模式,都是ART的monitor模块来处理(arm/runtime/monitor.cc),处理 monitor-enter 的函数定义如下:

ObjPtr<mirror::Object> Monitor::MonitorEnter(Thread* self, ObjPtr<mirror::Object> obj, bool trylock) {
    ...
}

对于上层锁而言,lockword 一定是定义在上层。在 monitor 这个场景,lockword 来自java对象头(第二个参数)。在虚拟机中一个java对象由对象头、实例数据和填充三个部分组成,而 lockword 就位于java的对象头中,共计32个bit,LockWord::value_ 定义如下:

高两位是类型码,这里 unlock、thinlock 和 fatlock 是和我们这个场景相关,其他的不必关注。作为一个天生有好奇心的程序员,你肯定有疑问:为何这里要搞的如此复杂?以至于需要thin lock和fat lock的迁移?我们知道,Java世界中,同步是和每一个对象捆绑在一起的,也就是说,不论你是不是使用 synchronized 关键字进行同步控制,控制同步的数据都会嵌入到每一个对象中。但是,实际上并不是每一个对象都使用java内嵌锁,即便是使用了,大部分的对象都是轻烈度的竞争,也只是简单的持锁,放锁。如果为每一个对象分配重型数据结构(monitor对象)来控制同步就耗费太多的内存了。因此,最开始(刚初始化)的时候对象都是处于 unlock 状态,如果有线程持锁,那么 lockword 会进入 Thin lock 状态,会记录 owner thread id 和 lock count。目前的monitor是可重入锁,因此 lock count 其实记录的是嵌套的深度。对于ART而言,Thin lock 状态下,每个对象控制同步的 lockword 就是 object 的 monitor_ 成员(见 class LockWord的注释,Object对象定义在 art/runtime/mirror/object.h 中,类型 uint32_t monitor_ 推测应该是有强制类型转换将unit32_t类型的变量强制转换成一个只有一个uint32_t类型成员变量的类LockWord后处理的,转换后对应 value_ 成员)。

为了延缓think lock膨胀成 fat lock,think lock 中还设计了乐观自旋的操作。其实内核的互斥锁mutex也有乐观自旋,由此可见,用户空间锁和内核锁还是有共通之处的。只是由于在上层,无法获取底层的信息(当前cpu的resched状态,owner的running状态),因此think lock的乐观自旋稍显盲目,具体如下:

(1) 100次的循环获取thinlock

(2) 如果步骤 (1) 未能成功,那么执行50次的循环获取锁。不过这里不是busy loop,而是调用了 sched_yield 让出CPU资源,给其他任务执行的机会。注: 实测即使用shell脚本跑满CPU,也能在72us内52次sched_yield调用。

如果竞争的确是非常激烈,乐观自旋后仍然无法持锁,那么阻塞当前线程已经是不可避免,那么我们只能是把 think lock 膨胀为 fat lock。这时候,我们需要分配一个 monitor 的对象,同时让 object 的 monitor_  成员变成 fat lock 形态,即其中的 monitor id 来表示该 monitor 对象。具体的代码可以参考 Monitor::InflateThinLocked() 函数。


Fat lock还原为 think lock 并非在 monitor-exit 的时候,由于膨胀过程中涉及了动态内存的分配,因此还原 thinlock 需要进行内存回收,这是在GC过程中完成。


4. 和内核交互

Thin lock和内核没有交互,只有膨胀为fat lock之后,内核才参与进来,我们先看看monitor类的主要的数据成员:

Monitor中最重要的数据成员就是 monitor_lock_,它是一个 mutex 对象,Monitor变成 fat lock之后的 lock、trylock、unlock 等操作实际上都是通过 monitor_lock_ 完成的。下面罗列一些简单的函数对应关系:

虽然膨胀为 fatlock,但是乐观自旋仍然不能少。fatlock 的乐观自旋逻辑和 thinlock 类似,刚开始是busy loop,然后调用 sched_yield() 让出cpu继续等待owner释放锁,最后通过 NanoSleep 来等锁,实在等不到了,最终还是会通过ART虚拟机中mutex进行阻塞操作。

在 Mutex::ExclusiveLock() 函数中,最终是由内核提供了阻塞服务,具体如下:

futex(state_and_contenders_.Address(), FUTEX_WAIT_PRIVATE, cur_state, nullptr, nullptr, 0)

由此,我们也可以得出结论:monitor fat lock 的 lock word 其实是由其mutex对象中的 state_and_contenders_ 提供,这个数据成员提供了两个信息:一个是 lock or unlock 的状态(bit 0), 另外一个是竞争者的数目(其它bits)。类似的,Mutex::ExclusiveUnlock() 函数也是通过futex系统调用完成唤醒操作,只不过操作码是 FUTEX_WAKE_PRIVATE。

此外,mutex模块中还有读写锁的实现,有兴趣可以自行阅读,此文不再赘述。


三、JUC锁

1. 概述

JUC锁的软件结构图如下:

注:此图可能不完全对,java中的wait-notify可能走的不是这个路径。

JUC是一个包含各种同步机制的工具箱(例如各种并发容器、线程池框架等),我们这里不能每一个详细描述,仅仅是对JUC的 reentrantLock 进行原理性的讲解。

JUC锁定义了三种接口:Lock、ReadWriteLock 和 Condition。互斥锁实现 Lock 接口,读写锁实现 ReadWriteLock 接口,condition 接口是对 wait-notify 同步机制的抽象,AbstractQueuedSynchronizer 中的 ConditionObject 类会实现 condition 接口。

Juc锁有三种:reentrantLock、ReentrantReadWriteLock 和 StampedLock。reentrantLock 是普通的互斥锁(类似monitor),可以重入(锁的名字已经将其出卖了),可以配置公平锁或者非公平锁。公平锁严格按照FIFO原则,可以保证等锁时间最长的线程优先持锁,从而让等锁时延参数比较平稳可控,但是往往吞吐量会稍微低一些。而非公平锁可以以任意顺序持锁,虽然非公平锁吞吐量方面的性能会好一些(减少了进程切换开销),不过会有饿死线程的现象。reentrantReadwriteLock 是读写锁,偏向reader。stampedLock 也是读写锁,偏向writer,这两种锁不是本文的重点。

AbstractQueuedSynchronizer 是所有锁的基类(后文简称AQS),定义了 lockword 并且管理了等待队列。AQS中的线程的阻塞和唤醒操作是通过 LockSupport 对象完成的,底层是通过ART中的 thread 类的 park 和 unpark 来完成的,sun.misc.unsafe 是作为java和native的桥梁。除了队列操作,原子操作也是通过 sun.misc.unsafe 这个java类来完成的。在ART虚拟机中,sun_misc_unsafe 提供了底层原子操作的支持。

JUC锁和java内嵌锁有一个明显的不同就是其排队是在上层完成的(具体是在AQS中)。这时估计有小伙伴跳出来挑战:你这里和monitor不都是通过futex到内核阻塞的吗?为何这里是上层排队呢?我们下一节具体讲解。


2. reentrantLock 简介

典型的 reentrantLock 使用方法如下:

import java.util.concurrent.locks.ReentrantLock;

class ReentrantLockTest {
    private final ReentrantLock rlock = new ReentrantLock();
    public method_need_sync() {
        rlock.lock();
        //临界区代码
        rlock.unlock();
    }
}

reentrantLock 类中有一个很重要的成员 private final Sync sync,对于公平锁,sync 是一个 FairSync 对象,如果是非公平锁,sync 是一个 NonfairSync 对象。FairSync 和 NonfairSync 类都是继承自 sync 类,而 sync 类是 AQS 的父类,这样 reentrantLock 通过 sync 建立和 AQS 的关系。

几乎 reentrantLock 的函数都是进一步调用 sync 的函数来完成的,例如lock函数:

//android/libcore/ojluni/src/main/java/java/util/concurrent/locks/ReentrantLock.java

public class ReentrantLock implements Lock, java.io.Serializable {
    public void lock() {
        sync.lock();
    }

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    static final class FairSync extends Sync {
        final void lock() {
            acquire(1);
        }
    }

    static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    }
}

对于公平锁,lock 函数会直接调用 AQS 的 acquire 函数。非公平锁也是类似,只不过先通过 compareAndSetState() 函数试图持锁,如果失败才调用 acquire 函数。释放锁也是类似的操作,直接调用 AQS 的 release 函数。reentrantLock 主要函数如下:

上面的函数其实都非常的简单,主要的控制逻辑还是在AQS中。


3. AQS简介

AQS的主要的数据:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
    static final class Node {
        private transient volatile Node head; //等待队列的头部
        private transient volatile Node tail; //等待队列的尾部
        private volatile int state; //锁的状态字,即JUC锁的 lock word
    }
}

AbstractOwnableSynchronizer 类(AQS的基类)中保存了owner task的信息:

ublic abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
    private transient Thread exclusiveOwnerThread;
}

任何睡眠锁都是有三元组:lock word、等待队列、和owner,上面的数据已经体现了锁的三元组信息,下面我一起看看如何对这些数据进行操作。


3.1 lock word的操作

reentrantLock 锁是派生自AQS类,由于AQS并不解释lock word,因此 reentrantLock 会重新定义一系列的函数来操作lock word,例如:

(1) tryAcquire(int):读取 lockword,判断是否为空锁,如果空锁,那么通过CAS操作设置为1,如果非空,看看owner task是否就是自己,如果是 lockword++,否则返回false。

(2) tryRelease(int):释放锁的时候首先看看owner task是否就是自己,如果不是自己,那么就抛出异常,自己上的锁只有自己能释放。如果 Lockword-- 后等于0,那么说明该锁的确是被释放了,将 owner task 设置为空同时返回0。如果 Lockword-- 后不等于0,那么说明还在锁的嵌套过程中,这时候锁并不释放,仅修改 lockword 即可。

具体的 lockword 操作包括 getState()、setState() 和 compareAndSetState()。getState() 和 setState() 分别是获取和写入 lock word,compareAndSetState()用来原子的修改 lock word(CAS操作)。


3.2 队列操作

对于 reentrantLock 这样的互斥锁,AQS提供了下面两个接口来完成持锁和释放锁的接口:

(1) acquire(int):调用 tryAcquire(派生的锁类实现该函数)来试图持锁,如果成功,不需要入队,直接返回即可。如果失败,那么为当前任务分配节点,挂入等待队列的尾部(addWaiter()函数),同时调用 LockSupport.park(this) 完成阻塞的动作。

(2) release(int):调用 tryRelease(派生的锁类实现该函数)来试图释放锁,如果返回false,说明不需要额外的操作(lockword 不等于0),锁仍然持有在当前线程手上。如果返回true,说明当前线程已经真正释放了锁,可以唤醒等待队列的线程了。调用 unparkSuccessor() 唤醒等待队列队首的节点。底层是调用 LockSupport.unpark() 完成具体的唤醒动作。出队的动作是在 acquire() 函数中完成的,一旦从阻塞状态中唤醒,持锁成功,那么该节点就会从等待队列中移除。


3.3 park & unpark

最后,我们简单聊一下虚拟机中的 Park 和 unpark,park 代码如下:

//art/runtime/thread.cc

Thread::Park(bool is_absolute, int64_t time)
    futex(tls32_.park_state_.Address(), FUTEX_WAIT_PRIVATE, /* sleep if val = */ kNoPermitWaiterWaiting, /* timeout */ nullptr, nullptr, 0);

显然,这里的 lockword 来自该线程的 tls 区域,即 per-thread 数据区。当本线程通过futex阻塞在内核之后,不会有其他的线程来持锁,毕竟 lockword 是 per-thread 的。因此JUC锁中,futex调用仅仅就是为了阻塞当前线程,而并没有把JUC锁的lockword(在java世界)传递给内核。TODO: 传递与不传递给内核有啥区别?

Java内嵌锁的 lockword 在虚拟机中,通过futex传递给内核,多个虚拟机中的线程可以阻塞在内核,因此会有排队的概念。


四、Native锁

1. 概述

Native锁的软件结构图如下:

C++内嵌锁有互斥锁(mutex)和条件变量(condition varible),其底层是 pthread mutex 和 pthread condition 的实现。此外,pthread中还实现了rwlock读写锁和自旋锁,值得一提的是虽然自旋锁的语义是永远自旋,但是在安卓平台上做了改良(估计是为了功耗),自旋一段时间之后还是会阻塞。本文主要描述pthread mutex,其他读者可以自行阅读代码学习。


2. Pthread mutex的lockword

在手机安卓平台上,pthread mutex 的代码位于 bionic/libc/bionic/pthread_mutex.cpp 中。本身 pthread mutex 是标准的API,因此接口层面不再详述。

pthread mutex 接口函数中使用 pthread_mutex_t 指针来指明互斥对象,但是在内部,我们将其转换为 pthread_mutex_internal,该数据结构根据不同平台、不同类型(PI或者NON-PI)有不同的数据成员。pthread_mutex_internal 在64位平台上的数据结构如下:

//android/bionic/libc/bionic/pthread_mutex.cpp

/* C++中struct也可以定义类,所有成员默认public的 */
struct pthread_mutex_internal_t {
    _Atomic(uint16_t) state;
    uint16_t __pad;
    union {
        atomic_int owner_tid;
        PIMutex pi_mutex;
    };
    char __reserved[28];
} __attribute__((aligned(4)));

共40B,对于non-PI lock,state如下所示:

owner_tid 只有 recursive 和 errorcheck lock 的时候有用,用来记录owner的thread id。在PI lock的情况下,state只有高2位有意义(bit0~bit13是填充位,应该是设置为0),设置为11,和non-PI lock的锁类型区别开来。

pthread_mutex_internal 在32位平台上的数据结构如下:

struct pthread_mutex_internal_t {
    _Atomic(uint16_t) state;
    union {
        _Atomic(uint16_t) owner_tid; //持锁线程的id
        uint16_t pi_mutex_id; //PI lock
    };
} __attribute__((aligned(4)));

共4B,各个成员和64bit平台是类似的,只是在表示pi mutex对象的时候一个用指针(64bit平台),一个用mutex id(32 bit平台)。无论是通过 pi_mutex_id(32bit)或者 pi_mutex 指针(64bit),控制pi lock的都是 PIMutex 这个数据结构:

struct PIMutex {
    uint8_t type; //mutex类型,0(normal), 1(recursive), 2(errorcheck) 在生命周期内恒定
    bool shared; //进程共享标志,在生命周期内恒定
    uint16_t counter; //PI mutex的嵌套深度
    atomic_int owner_tid; //由用户空间代码和内核代码读/写。 它包括三个字段:FUTEX_WAITERS、FUTEX_OWNER_DIED 和 FUTEX_TID_MASK。
};

type、shared 和 counter 的含义和non-PI lock的state成员表达的内容是一样的。

对 pthread_mutex_internal 数据结构整理如下:

32位平台,pthread_mutex_internal 长度是4B。64位平台,pthread_mutex_internal 长度是40B。对于futex系统调用,我们知道,无论如何配置(无论64bit平台还是32bit平台),futex word(即本文说的 lockword)始终都是32个bit。这个futex word也是用户空间程序(无论32bit app还是64bit app)和内核futex的接口,因此需要统一。在不支持PI的情况下,32位平台的futex word由 state+owner_tid 组成,64位平台的futex word由 state+2B 填充位组成。在支持PI的情况下,futex word依然是32bit,也就是 struct PIMutex 数据结构的 owner_tid 成员。


3. pthread mutex的持锁和释放锁

3.1 对于Non-PI普通类型的持锁,其调用链是:

(1) 入口函数是 pthread_mutex_lock

(2) 调用 NonPI::NormalMutexTryLock() 来尝试获取锁,如果成功获取锁,返回。

(3) 如果失败,调用 NonPI::MutexLockWithTimeout() 来挂入等待队列。对于普通类型的锁,具体是通过调用 NormalMutexLock() 函数来完成等锁操作的.

(4) 最终的等锁是通过futex系统调用完成的(传递给内核的 futex word 是 mutex->state,########### futex op code 是 FUTEX_WAIT_BITSET_PRIVATE(process private)或者 FUTEX_WAIT_BITSET(process share)),也就是说 pthread mutex 和 monitor 一样,都是在内核中的futex模块进行排队的。#########


3.2 对PI普通类型的持锁,其调用链是:

(1) 入口函数是 pthread_mutex_lock

(2) 调用 PIMutexTryLock() 来尝试获取锁,如果成功获取锁,返回

(3) 如果失败,调用 PIMutexTimedLock() 来挂入等待队列。

(4) 最终的等锁是通过futex系统调用完成的,当然调用参数和 NonPI 不一样,这时候 futex word 是 PIMutex 的 owner_tid,而 futex op code 是 FUTEX_LOCK_PI_PRIVATE(process private)或者 FUTEX_LOCK_PI(process share)。


Pthread mutex 释放锁是调用 pthread_mutex_unlock(),逻辑比较简单,具体细节留给读者自行分析吧。


五、总结

1. 本文简单的描述了在安卓手机平台上的各种用户空间锁机制,包括Java内嵌锁、JUC locks、C++内嵌锁和pthread locks。虽然各种锁有各自的控制逻辑,但是当需要阻塞(或者唤醒)当前进程的时候最终都是万法归一,通过futex系统调用进入内核。

2. Java内嵌锁字的转化过程:object::monitor_ --> LockWord::value_ --> mutex::state_and_contenders_ --> futex(*uaddr)

 

 

参考:

手机平台上的用户空间锁概述

 

标签:Java,monitor,lock,用户,futex,mutex,pthread,空间,概述
From: https://www.cnblogs.com/hellokitty2/p/18155290

相关文章

  • 变更文件所属用户及组
    基础知识: 在Linux中,创建一个文件时,该文件的拥有者都是创建该文件的用户。该文件用户可以修改该文件的拥有者及用户组,当然root用户可以修改任何文件的拥有者及用户组。在Linux中,对于文件的权限(rwx),分为三部分,一部分是该文件的拥有者所拥有的权限,一部分是该文件所在用户组的用户所......
  • git不同项目提交时,显示不同的用户名
    场景在使用git时,不同项目想使用不同的名称和邮箱解决方法每个项目独立设置不同的名称和邮箱项目clone下来后,使用如下命令gitconfig--localuser.name'yourname'gitconfig--localuser.name'youremail'或者直接修改.git/config文件,加入下列配置[user]name......
  • 1.C语言概述
    计算机语言发展史: 机器语言 汇编语言 高级语言(结构化+面向对象)C语言进化史: ALGOL60——CPL——BCPL——CB语言与C的关系: B是C语言的前一个版本 肯汤姆森用B语言写出了UNIX操作系统 丹尼斯里奇发明C,重写了UNIX操作系统C语言特点: 优:代码量小,速度......
  • Linux用户与用户组管理
    Linux是一个多用户、多任务的分时操作系统,在Linux系统1中,用户的账号等相关信息(密码除外)均放在etc目录下文件所有者:Linux系统中的文件所有者指的是文件的拥有者用户组:Linux系统中的用户大体上可以分为三组:管理员(root)、普通用户和系统用户用户与用户组管理用户管理用户管......
  • 多种方法实现Appium屏幕滑动:让用户仿真动作更简单
    简介在移动端应用中,基于简便的原因,用户通常会倾向于使用滑动操作来达到与应用程序中的控件进行交互的,这使得滑动成为自动化测试中频繁使用的关键动作。在Appium中提供了多种方式来实现模拟用户的滑动屏幕动作。滑动操作的场景移动端应用中的滑动场景,大致有如下几种类型:触......
  • kafka - [01] 概述
    Kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域。 一、什么是KafkaKafka是一个分布式的数据流式传输平台。1、ApacheKafka是一个开源消息系统,由scala写成。是由Apache软件基金会开发的一个开源消息系统项目。2、Kakfa最初是由LinkedIn公司......
  • Azure REST API (0) 概述
    《WindowsAzurePlatform系列文章目录》 1.概述1.我们在使用Azure云服务的时候,可以通过AzurePortal: https://portal.azure.com,输入邮箱地址和密码,然后通过交互式(鼠标点击)的方式创建或者删除微软云的资源2.我们也可以通过API或者SDK的方式进行调用,集......
  • 用户下单+微信支付学习记录
    开始之前补充两个知识点,因为之前写mapper.xml文件中sql语句时,没有提示功能就会很麻烦,补充了此功能:IDEAsql自动补全/sql自动提示/sql列名提示_idea提示sql语句-CSDN博客查看类源码:ctrl+shift+/,查看方法详情:ctrl+mouse1用户下单 接口设计     直接看过了,手动导......
  • ASP.NET Core Web API下基于Keycloak的多租户用户授权的实现
    在上文《Keycloak中授权的实现》中,以一个实际案例介绍了Keycloak中用户授权的设置方法。现在回顾一下这个案例:服务供应商(ServiceProvider)发布/WeatherForecastAPI供外部访问在企业应用(Client)里有三个用户:super,daxnet,nobody在企业应用里有两个用户组:administrators,users在企......
  • 一次奇妙的任意用户登录实战
    刚刚进行了微信sessionkey的学习,正准备实战一下,就发现了这个神奇的网站,预知后事如何。请继续向下看去1.目标2.开局一个登录框3.首先,直接弱口令走起来,万一留有测试的账号呢尝试,1311111111,13333333333.13888888888,未果测试多了还有验证码防止爆破,也就不再继续尝试爆破了......