分布式锁实战

2023-11-06

示例代码:

maven引入:

// maven引入
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.6.5</version>
</dependency>

redisson配置:

// redisson配置
@Bean
public Redisson redisson() {
	// 此为单机模式
	Config config = new Config();
	config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
	return (Redisson) Redisson.create(config);
}

模拟减库存代码:

@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate redisTemplate;

@RequestMapping("/deduct_stock")
public String deductStock() {

	int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
	if (stock > 0) {
		int realStock = stock - 1;
		stringRedisTemplate.opsForValue().set("stock", realStock + "");
		System.out.println("扣减成功,剩余库存:" + realStock);
	} else {
		System.out.println("扣减失败,库存不足");
	}

	return "end";
}

1、高并发场景下秒杀抢购超卖Bug实战重现

如果是高并发场景下有多个用户来进行库存扣减,那么在示例代码中就会有多个用户同一时间从redis中读取到同一个库存,那么就会产生超卖的问题,这样商家肯定就会产生重大损失,这肯定是不允许的。

2、秒杀抢购场景下实战JVM级别锁与分布式锁

那要怎么解决超卖这个问题呢?首先想到的肯定是加,不然多个用户同一时间读取库存。

JVM级别锁

synchronized (this) {
	int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
	if (stock > 0) {
		int realStock = stock - 1;
		stringRedisTemplate.opsForValue().set("stock", realStock + "");
		System.out.println("扣减成功,剩余库存:" + realStock);
	} else {
		System.out.println("扣减失败,库存不足");
	}
}

这样我们就加了一个 synchronized ,这样在单机环境下是没问题了。

但是现在很多项目都是集群部署,这样也会产生问题。synchronized 是一个 JVM 级别的锁,如果同一时刻有多个请求过来,nginx 把请求转发到不同的机器上,那么在集群内其他机器是保证不了产生并发问题的。

我们这里搭建好上图所示的集群后,用 jmeter 来测试一下。

jmeter压测

1)在本地启动8080和8090两个进程

2)配置nginx

upstream redislock{
	server 127.0.0.1:8080;
	server 127.0.0.1:8090;
}

server {
	listen       80;
	server_name  localhost;
	
	#反向代理 8080、8090 服务器集群
	location / {
		proxy_pass         http://redislock/;
	}

}

3)配置 jmeter

4)启动 jmeter 开始压测,并查看结果

8080:

8090:

这里加了 synchronized为什么同一个进程下还会有重复的呢?

因为在分布式下高并发会产生各种各样的问题,这里可能就是由于前一个进程读取后,又把redis中的库存给设置回去了,后一个进程又读取到了已经读取过的库存。

从结果图中可以看到在 8080 和 8090 两个进程中都会有库存重复超卖的现象,所以说 JVM 级别的锁是解决不了分布式下的并发问题。

分布式锁

由于很多公司里面都会用到 redis ,那么直接使用 redis 来实现分布式锁肯定是很简单的。

版本一:

用 redis 中的命令 setnx。

可以从命令手册中看到 setnx 这个命令非常适合用于分布式锁。

@RequestMapping("/deduct_stock")
public String deductStock() {

	String lockKey = "lock:product_101";
	Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 10, TimeUnit.SECONDS); //jedis.setnx(k,v)
	if (!result) {
		return "error_code";
	}

	try {
		int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
		if (stock > 0) {
			int realStock = stock - 1;
			stringRedisTemplate.opsForValue().set("stock", realStock + "");
			System.out.println("扣减成功,剩余库存:" + realStock);
		} else {
			System.out.println("扣减失败,库存不足");
		}
	} finally {
		stringRedisTemplate.delete(lockKey);
	}

	return "end";
}

如果是在一些小公司,并发量不太大,可以容忍一些超卖的情况下,这个版本的分布式锁的代码已经可以解决对应的问题了。

缺点

但是在高并发的场景下,这个版本的代码还是会产生超卖的问题。

如图中所示,T1和T2同一时刻进入抢占锁,T1加锁成功,T2加锁失败,但是由于某个原因导致T1加锁到解锁之间需要执行15s,在T1执行到10s的时候,T1的锁已经失效,这时等待的T2也加锁成功,这时候的锁就是锁的T2。继续执行5s,T1开始执行释放锁的逻辑,这时T1释放的就是T2的锁。T3也在这时刻进入,因为T2的锁已经释放,T3也会加锁成功。

这个其实是一种小概率时间,这样无限循环下去其实就跟没加锁的效果是一样的了,根本解决不了超卖的问题。

版本二:

其实版本一的根本性问题是:自己加的锁被别的线程给释放掉了。

这里只需要在释放锁的时候判断一下,只有自己才能释放自己加的锁。

@RequestMapping("/deduct_stock")
public String deductStock() {

	String lockKey = "lock:product_101";
	String clientId = UUID.randomUUID().toString();
	Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "clientId", 30, TimeUnit.SECONDS); //jedis.setnx(k,v)
	if (!result) {
		return "error_code";
	}

	try {
		int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
		if (stock > 0) {
			int realStock = stock - 1;
			stringRedisTemplate.opsForValue().set("stock", realStock + "");
			System.out.println("扣减成功,剩余库存:" + realStock);
		} else {
			System.out.println("扣减失败,库存不足");
		}
	} finally {
		if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
			stringRedisTemplate.delete(lockKey);
		}
	}

	return "end";
}

缺点

if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
	stringRedisTemplate.delete(lockKey);
}

高并发场景下在这段代码里面也会产生版本一的问题,比如在判断clientId成功后,释放锁之前产生卡顿,这时候锁过期,这时有其他的线程抢占锁成功,接着当前线程释放锁,这时也是释放的其他的线程锁,又出现了版本一的缺点。

这里根本性的原因是:判断释放锁这里不是原子性的。

版本三

其实这里锁的过期时间的设定是不好判断的,代码执行可能由于各种的原因导致卡顿的情况,导致锁失效。

如果要把分布式锁实现的比较完美的话有一个锁续命的方案。

当有一个主线程抢到锁后,然后有一个分线程搞一个定时任务每过一段时间(要小于锁的过期时间)执行判断主线程是否已经结束,如果没有结束,把这个锁的超时时间重新设置过过期时间。

这里我们直接就引入 Redisson 框架进行实现分布式锁。

3、分布式锁 Redisson 框架实战

Redisson中文文档:目录 · redisson/redisson Wiki · GitHub

他的引入已经在最上面讲到了,这里就不过多的赘述。

@RequestMapping("/deduct_stock")
public String deductStock() {

	String lockKey = "lock:product_101";
	//获取锁对象
	RLock redissonLock = redisson.getLock(lockKey);
	//加分布式锁
	redissonLock.lock();  //  .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); {
	try {
		int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
		if (stock > 0) {
			int realStock = stock - 1;
			stringRedisTemplate.opsForValue().set("stock", realStock + "");
			System.out.println("扣减成功,剩余库存:" + realStock);
		} else {
			System.out.println("扣减失败,库存不足");
		}
	} finally {
	//解锁
	redissonLock.unlock();
	}

	return "end";
}

redisson用起来就是如此的简单。

4、Redisson 分布式锁源码剖析

加锁

redissonLock.lock() --> lockInterruptibly() --> lockInterruptibly(-1, null) --> tryAcquire --> tryAcquireAsync --> tryLockInnerAsync

这里的调用链路就行清晰,进到 tryLockInnerAsync 内部其实就是执行的一段 lua (redis是单线程执行命令,会把lua脚本里面的语句看成一条命令来执行,不会被打断)脚本就行加锁。

internalLockLeaseTime这里过期时间默认的是30s

其实这个看门狗(过期时间)时间是可以修改的:

@Bean
public Redisson redisson() {
	// 此为单机模式
	Config config = new Config();
	config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
//        config.setLockWatchdogTimeout(10000); // 设置分布式锁 watch dog 超时时间
	return (Redisson) Redisson.create(config);
}

看门狗(锁续命)

ttlRemainingFuture.addListener会监听 tryLockInnerAsync执行的返回结果,然后就行执行 operationComplete方法,如果加锁失败直接返回,如果加锁成功就会继续执行后续的方法。

future.getNow()这段代码会拿到 lua 脚本里面返回的值,如果加锁成功这里就会返回 null(lua 脚本中的 nil 就对应 java 中的 null)。

接着会执行 scheduleExpirationRenewal

根据 internalLockLeaseTime / 3 算出来的时间,会在进入这个方法 10s 后执行 run() 方法。

先会判断主线程中加的锁是否还存在,还存在的话就会把主线程的超时时间重新设置为 30s ,接着又会调用下面 listener 的回调方法 operationComplete ,如果续命成功接着就会循环执行 scheduleExpirationRenewal

加锁失败

还是加锁时的那段 lua 脚本,另外的线程加锁失败的话就会返回当前加锁成功线程的锁剩余超时时间。

然后返回主线逻辑中,接着就会执行下面的逻辑,因为加锁失败返回的 ttl 肯定是不为 null 的,往下就会执行 while 中的逻辑。

第一步:ttl = tryAcquire(leaseTime, unit, threadId);这一步是刷新 ttl 剩余时间;

第二步:getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);这里就会阻塞 ttl 这么长的时间,然后再回到 while 循环中再次尝试加锁(这里的阻塞不会占用CPU,会释放CPU,不会消耗性能)。

这里会有一个问题就是:如果在其余线程等待 ttl 时间内,锁已经释放了,不可能让这些线程傻傻的在这里一直等待把。

这里有等待,那么肯定还有其它地方要唤醒。

唤醒抢锁实败阻塞的线程

抢锁失败的线程在这里会订阅 prefixName("redisson_lock__channel", getName())这个channel。

唤醒的逻辑肯定在解锁的时候进行唤醒了。

redissonLock.unlock() --> unlockInnerAsync

然后里面还是一段 lua 脚本。

redis.call('publish', KEYS[2], ARGV[1]); lua 脚本这段就是往 prefixName("redisson_lock__channel", getName()) 里发布一个消息,说明锁已经释放。

再来看到加锁逻辑里面的 LockPubSub#onMessage,就是对上面订阅的channel,有消息来的时候进行执行的方法。

释放的核心逻辑就是 value.getLatch().release();Semaphore 进行释放。

5、Redis 主从架构锁失效问题解析

在企业中一般 redis 都是主从、哨兵或者集群架构的嘛,以防止单点故障问题。master 和 slave 之间同步和 redisson到mater lock之间,这两个步骤之间是异步的。如果在线程从master获取到锁后,接着master 和 slave 之间数据同步的时候,master 突然宕机了,这时候数据还没有同步完成,slave中是没有线程的锁的,这时候slave变成了master 节点,新来的线程也能加锁成功了。这就会造成redis主从架构锁失效。

用redis实现的分布式锁在解决这个问题上并没有那么容易。

6、从 CAP 角度剖析 Redis 与 Zookeeper 分布式锁区别

从CAP角度来说,redis的集群架构师满足AP,可用性这块是满足的多一点。而ZK是满足CP的,一致性这块满足的多一点。

因为在ZK中,也是leader节点进行写数据,它并不会在leader节点写数据成功后马上告诉加锁的线程获取锁成功了,而是先会把数据给集群中的follower节点进行同步,follower节点同步成功过后会把同步成功的结果返回给leader节点,leader节点会计算同步成功到follower节点个数大于等于过半节点个数后,才会返回给客户端说明本次加锁成功。如果这个时候leader挂了,那么ZK会把同步最多数据的那个follower节点选举成为master节点,这样就不会丢失数据(这是由于ZK内部的ZAB机制实现的)。

在性能方面:ZK的性能肯定是不如redis的。

从分布式锁设计语义角度来说:ZK可能更加合适。

7、Redlock 分布式锁原理与存在问题分析

在网上很多文章说用 redlock(红锁) 能解决redis主从切换过程中锁失效的问题。我们就具体分析一下,redlock到底能不能解决这个问题呢?

其实redlock和ZK的写入方式很像,都是要半数加锁成功以后才返回客户端加锁成功。

redlock原理:

如图所示,有三个redis节点都是互相独立的,这时来了一个线程1,他要在redis1,redis2,redis3其中两个节点加锁成功,才会返回给客户端加锁成功。这里比如就在redis1,redis2上加锁成功了。此时线程2也来加锁,线程2只能在redis3上才能加锁成功,因为不足半数2个节点,那么线程2是加锁失败的。

问题1:

因为在我们持久化redis的时候一般不会每一条命令都持久化一次,一般会设置1s持久化一次。如果redis1加锁成功,redis2锁刚刚加成功,这时返回给客户端加锁成功了,但是这个时候redis2加锁成功还不足1s,这时redis2挂了,这时redis2刚刚加的锁没有持久化到磁盘,重启过后这个锁肯定丢失了。这时来一个线程2,在redis2和redis3都能加锁成功,还是会造成上述问题。

问题2:

如果在redis1,redis2,redis3后面都挂一个slave节点,还是和上面一样redis1,redis2已经加锁成功,返回给客户端加锁完成,但是redis2还没有同步到slave2是就挂了,主从切换的时候slave2编程redis2,这时是没有刚刚加锁成功的key的。线程2再次来加锁的话,还是能在redis2和redis3上加锁成功,这时就又出现问题了。

8、大促场景下如何将分布式锁性能提升100倍

分布式锁的设计语义其实是:把多个并行执行的请求串行化了。

它其实是和高并发相违背的,如果遇到特别高并发场景下是需要大于分布式锁进行优化的。

  1. 锁粒度越小越好(加锁的代码越小越好);
  2. 分段锁;

分段锁:

比如有一个product_1这个产品库存有1000个,就会把这个产品进行拆分为product_1_100,product_1_200,.....,product_1_1000,用100个库存来拆分存储到不同的redi节点,到时候请求就根据相应的算法分配到不同的redis节点中,那么分布式锁的性能又有了提升。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式锁实战 的相关文章

  • 序列的排列?

    我有具体数量的数字 现在我想以某种方式显示这个序列的所有可能的排列 例如 如果数字数量为3 我想显示 0 0 0 0 0 1 0 0 2 0 1 0 0 1 1 0 1 2 0 2 0 0 2 1 0 2 2 1 0 0 1 0 1 1 0
  • 如何循环遍历所有组合,例如48 选择 5 [重复]

    这个问题在这里已经有答案了 可能的重复 如何在java中从大小为n的集合中迭代生成k个元素子集 https stackoverflow com questions 4504974 how to iteratively generate k
  • .properties 中的通配符

    是否存在任何方法 我可以将通配符添加到属性文件中 并且具有所有含义 例如a b c d lalalala 或为所有以结尾的内容设置一个正则表达式a b c anything 普通的 Java 属性文件无法处理这个问题 不 请记住 它实际上是
  • Spring AspectJ 在双代理接口时失败:无法生成类的 CGLIB 子类

    我正在使用Spring的
  • jQuery AJAX 调用 Java 方法

    使用 jQuery AJAX 我们可以调用特定的 JAVA 方法 例如从 Action 类 该 Java 方法返回的数据将用于填充一些 HTML 代码 请告诉我是否可以使用 jQuery 轻松完成此操作 就像在 DWR 中一样 此外 对于
  • Java 公历日历更改时区

    我正在尝试设置 HOUR OF DAY 字段并更改 GregorianCalendar 日期对象的时区 GregorianCalendar date new GregorianCalendar TimeZone getTimeZone GM
  • 从最终实体获取根证书和中间证书

    作为密码学的菜鸟 我每天都会偶然发现一些简单的事情 今天只是那些日子之一 我想用 bouncy castle 库验证 java 中的 smime 消息 我想我几乎已经弄清楚了 但此时的问题是 PKIXparameters 对象的构建 假设我
  • 没有 Spring 的自定义 Prometheus 指标

    我需要为 Web 应用程序提供自定义指标 问题是我不能使用 Spring 但我必须使用 jax rs 端点 要求非常简单 想象一下 您有一个包含键值对的映射 其中键是指标名称 值是一个简单的整数 它是一个计数器 代码会是这样的 public
  • 检测并缩短字符串中的所有网址

    假设我有一条字符串消息 您应该将 file zip 上传到http google com extremelylonglink zip http google com extremelylonglink zip not https stack
  • 帮助将图像从 Servlet 获取到 JSP 页面 [重复]

    这个问题在这里已经有答案了 我目前必须生成一个显示字符串文本的图像 我需要在 Servlet 上制作此图像 然后以某种方式将图像传递到 JSP 页面 以便它可以显示它 我试图避免保存图像 而是以某种方式将图像流式传输到 JSP 自从我开始寻
  • 如何对不同的参数类型使用相同的java方法?

    我的问题 我有 2 个已定义的记录 创建对象请求 更新对象请求 必须通过实用方法进行验证 由于这两个对象具有相同的字段 因此可以对这两种类型应用相同的验证方法 现在我只是使用两种方法进行重载 但它很冗长 public record Crea
  • 在我的 Spring Boot 示例中无法打开版本 3 中的 Swagger UI

    我在 Spring Boot 示例中打开 swagger ui 时遇到问题 当我访问 localhost 8080 swagger ui 或 localhost 8080 root api name swagger ui 时出现这种错误 S
  • 尝试将 Web 服务部署到 TomEE 时出现“找不到...的 appInfo”

    我有一个非常简单的项目 用于培训目的 它是一个 RESTful Web 服务 我使用 js css 和 html 创建了一个客户端 我正在尝试将该服务部署到 TomEE 这是我尝试部署时遇到的错误 我在这里做错了什么 刚刚遇到这个问题 我曾
  • 为什么 Java 8 不允许非公共默认方法?

    让我们举个例子 public interface Testerface default public String example return Hello public class Tester implements Testerface
  • 不接受任何内容也不返回任何内容的函数接口[重复]

    这个问题在这里已经有答案了 JDK中是否有一个标准的函数式接口 不接受也不返回任何内容 我找不到一个 像下面这样 FunctionalInterface interface Action void execute 可运行怎么样 Functi
  • 关键字“table”附近的语法不正确,无法提取结果集

    我使用 SQL Server 创建了一个项目 其中包含以下文件 UserDAO java public class UserDAO private static SessionFactory sessionFactory static se
  • java.io.Serialized 在 C/C++ 中的等价物是什么?

    C C 的等价物是什么java io Serialized https docs oracle com javase 7 docs api java io Serializable html 有对序列化库的引用 用 C 序列化数据结构 ht
  • 专门针对 JSP 的测试驱动开发

    在理解 TDD 到底是什么之前 我就已经开始编写测试驱动的代码了 在没有实现的情况下调用函数和类可以帮助我以更快 更有效的方式理解和构建我的应用程序 所以我非常习惯编写代码 gt 编译它 gt 看到它失败 gt 通过构建其实现来修复它的过程
  • Cucumber 0.4.3 (cuke4duke) 与 java + maven gem 问题

    我最近开始为 Cucumber 安装一个示例项目 并尝试使用 maven java 运行它 我遵循了这个指南 http www goodercode com wp using cucumber tests with maven and ja
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn

随机推荐

  • clion-debug调试步骤

    文章目录 clion debug调试方法 先来一道水题 方便大家理解 操作细节 1 打断点 2 点击debug 3 输入数据 4 下一条指令 clion debug调试方法 脱坑神器 先来一道水题 方便大家理解 题目要求 获取两个输入a b
  • Springboot 配置类( @Configuration) 不能使用@Value注解从application.yml中加载值

    问题 在Springboot应用中 通过Spring context 版本4 3 6 的 Configuration注解配置类 使用 Value注解从application yml配置文件中加载属性 但是总是报找不到 设置缺省值 则获取到的
  • Linux安装安全狗

    一 说明 1 服务器运维 用的阿里云服务器 没用阿里云的安全服务 2 系统 CentOS7 系统 CentOS8 均可安装 3 安装用的是安全狗的免费版 4 安装前 先注册安全狗的云账户 安装好后需要登录此用户 二 下载与安装 1 下载地址
  • 思林杰科技通过注册:应收账款余额1.87亿 占营收比例160%

    雷递网 雷建平 2月18日报道 广州思林杰科技股份有限公司 简称 思林杰科技 日前通过注册 准备在科创板上市 计划募资5 57亿元 其中 2 67亿元用于嵌入式智能仪器模块扩产建设项目 1 6亿元用于研发中心建设项目 1 3亿元用于补充运营
  • 在Windows下安装GmSSL

    本文属于 GmSSL国密加密算法库使用系列教程 之一 欢迎查看其它文章 在Windows下安装GmSSL 一 关于GmSSL 二 编译工具准备 1 安装VS2017 2 安装ActivePerl 3 安装NASM 三 GmSSL源码准备 四
  • python:最小二乘法拟合原理及代码实现

    这里写目录标题 原理 代码实现 原理 最小二乘法适用于对处理的一堆数据 不必精确的经过每一点 而是根据图像到每个数据点的距离和最小确定函数 需要注意的是 最小二乘是对全局进行拟合优化 对噪声比较敏感 所以如果有噪声比较大的观测值会影响拟合结
  • 2023-02-01 读书笔记:《有趣的统计》-1-基础知识

    2023 02 01 读书笔记 有趣的统计 1 基础知识 75招学会数据分析 2014 Doctor Bruce Frey 序 统计学 最初 用于确定某些事情发生的可能性 不断发展 根据样本数据准确推断总体数据特征的方法 推论统计学 Hac
  • springBoot中使用rabbitMQ以及消息丢失问题

    一 rabbitMQ中常用的交换机 图源自官网 https www rabbitmq com getstarted html Direct exchange 直连交换机 一个生产者 一个交换机 两个队列 两个消费者 根据消息发送时携带的路由
  • Docker构建Springboot项目,并发布测试

    把SpringBoot项目打包成Docker镜像有两种方案 全自动化 先打好docker镜像仓库 然后在项目的maven配置中配置好仓库的地址 在项目里配置好Dockerfile文件 这样可以直接在idea中打包好后自动上传到镜像仓库 然后
  • 第一章 单机应用到分布式架构演进

    1 传统单机 分布式架构演进历史 单机架构 优点 易于测试 便于集成 小型项目友好 缺点 开发速度慢 启动时间长 依赖庞大 分布式架构 SOA Service Oriented Architecture 面向服务的架构 其中包含多个服务 服
  • Vue 自定义tree组件

    这是一个递归组件 首先组件是可以在模板里通过 export default 的 name 来调用本身 只是一个思路 写的比较简单 直接上代码
  • tensorflow学习笔记4(神经网络八股)

    2 train test 告知要喂入网络的训练集和测试集是什么 3 在Sequential中搭建神经网络结构 逐层描述每层网络 相当于走了一边前向传播 4 在compile配置训练方法 选择何种优化器 更新网络参数使用 何种损失函数 何种评
  • fcm算法的MATLAB实现,FCM算法的matlab程序(初步)

    FCM算法的matlab程序 1 采用iris数据库 iris data txt 5 1 3 5 1 4 0 2 4 9 3 1 4 0 2 4 7 3 2 1 3 0 2 4 6 3 1 1 5 0 2 5 3 6 1 4 0 2 5 4
  • 第十届蓝桥杯(省赛)之Fibonacci数列和黄金分割

    一 version1 遇到的问题 F数组存储的数据超过long表示范围 import java math BigDecimal import java math BigInteger import java util Scanner pub
  • 批量关闭公众号推送_新功能!微信可以批量“取关”公众号啦!

    微信内测新功能 一如既往地引起了热议 有人说 批量关闭接收推送等同于批量取关 有人说 好内容的阅读要涨了 谁说对了 今天下午 有多位读者向WHO哥爆料称 收到 微信公众平台 的通知消息 微信自动检测出 你有多个关注的订阅号长时间未读 可以选
  • 微服务为什么一定要选spring cloud?

    作者 董添 李秉谦 网易乐得技术团队 来自 http tech lede com 现如今微服务架构十分流行 而采用微服务构建系统也会带来更清晰的业务划分和可扩展性 同时 支持微服务的技术栈也是多种多样的 本系列文章主要介绍这些技术中的翘楚
  • 微信公众号网页分享,脱坑思路

    1 绑定域名 首先确定使用的域名是否和微信公众号接口权限 网页服务 网页授权配置的js域名是否一致 2 引入JS文件 在需要调用JS接口的页面引入如下JS文件 支持https http res wx qq com open js jweix
  • 位置式PID控制算法

    刚好前不久搞过PID 部分程序如下 仅供参考 在使用单片机作为控制cpu时 请稍作简化 具体的PID参数必须由具体对象通过实验确定 由于单片机的处理速度和ram资源的限制 一般不采用浮点数运算 而将所有参数全部用整数 运算到最后再除以一个2
  • 【爬坑之路一】windows系统下更新升级node版本【亲测有效】

    前言 一定要看到最后 项目开发中 需要升级 node 版本 本着不想卸载 node 再重新安装的原则 因为node 的环境配置以及各种相关配置有些繁琐 所以就想着使用 命令的方式进行升级 在网上找了一些升级 node 的命令 最常见的是安装
  • 分布式锁实战

    示例代码 maven引入 maven引入