前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >解读JVM级别本地缓存Caffeine青出于蓝的要诀3 —— 讲透Caffeine的数据驱逐淘汰机制与用法

解读JVM级别本地缓存Caffeine青出于蓝的要诀3 —— 讲透Caffeine的数据驱逐淘汰机制与用法

原创
作者头像
是Vzn呀
发布于 2022-12-23 03:42:38
发布于 2022-12-23 03:42:38
2.2K0
举报
文章被收录于专栏:架构悟道架构悟道

大家好,又见面了。


本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。


上一篇文章中,我们聊了下Caffeine的同步、异步的数据回源方式。本篇文章我们再一起研讨下Caffeine的多种不同的数据淘汰驱逐机制,以及对应的实际使用。

Caffeine的异步淘汰清理机制

在惰性删除实现机制这边,Caffeine做了一些改进优化以提升在并发场景下的性能表现。我们可以和Guava Cache的基于容量大小的淘汰处理做个对比。

当限制了Guava Cache最大容量之后,有新的记录写入超过了总大小,会理解触发数据淘汰策略,然后腾出空间给新的记录写入。比如下面这段逻辑:

代码语言:java
AI代码解释
复制
public static void main(String[] args) {
    Cache<String, String> cache = CacheBuilder.newBuilder()
            .maximumSize(1)
            .removalListener(notification -> System.out.println(notification.getKey() + "被移除,原因:" + notification.getCause()))
            .build();
    cache.put("key1", "value1");
    System.out.println("key1写入后,当前缓存内的keys:" + cache.asMap().keySet());
    cache.put("key2", "value1");
    System.out.println("key2写入后,当前缓存内的keys:" + cache.asMap().keySet());
}

其运行后的结果显示如下,可以很明显的看出,超出容量之后继续写入,会在写入前先执行缓存移除操作。

代码语言:txt
AI代码解释
复制
key1写入后,当前缓存内的keys:[key1]
key1被移除,原因:SIZE
key2写入后,当前缓存内的keys:[key2]

同样地,我们看下使用Caffeine实现一个限制容量大小的缓存对象的处理表现,代码如下:

代码语言:java
AI代码解释
复制
public static void main(String[] args) {
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(1)
            .removalListener((key, value, cause) -> System.out.println(key + "被移除,原因:" + cause))
            .build();
    cache.put("key1", "value1");
    System.out.println("key1写入后,当前缓存内的keys:" + cache.asMap().keySet());
    cache.put("key2", "value1");
    System.out.println("key2写入后,当前缓存内的keys:" + cache.asMap().keySet());
}

运行这段代码,会发现Caffeine的容量限制功能似乎“失灵”了!从输出结果看并没有限制住

代码语言:txt
AI代码解释
复制
key1写入后,当前缓存内的keys:[key1]
key2写入后,当前缓存内的keys:[key1, key2]

什么原因呢?

Caffeine为了提升读写操作的并发效率而将数据淘汰清理操作改为了异步处理,而异步处理时会有微小的延时,由此导致了上述看到的容量控制“失灵”现象。为了证实这一点,我们对上述的测试代码稍作修改,打印下调用线程与数据淘汰清理线程的线程ID,并且最后添加一个sleep等待操作:

代码语言:java
AI代码解释
复制
public static void main(String[] args) throws Exception {
    System.out.println("当前主线程:" + Thread.currentThread().getId());
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(1)
            .removalListener((key, value, cause) ->
                    System.out.println("数据淘汰执行线程:" + Thread.currentThread().getId()
                            + "," + key + "被移除,原因:" + cause))
            .build();
    cache.put("key1", "value1");
    System.out.println("key1写入后,当前缓存内的keys:" + cache.asMap().keySe());
    cache.put("key2", "value1");
    Thread.sleep(1000L); // 等待一段时间时间,等待异步清理操作完成
    System.out.println("key2写入后,当前缓存内的keys:" + cache.asMap().keySet());
}

再次执行上述测试代码,发现结果变的符合预期了,也可以看出Caffeine的确是另起了独立线程去执行数据淘汰操作的。

代码语言:txt
AI代码解释
复制
当前主线程:1
key1写入后,当前缓存内的keys:[key1]
数据淘汰执行线程:13,key1被移除,原因:SIZE
key2写入后,当前缓存内的keys:[key2]

深扒一下源码的实现,可以发现Caffeine在读写操作时会使用独立线程池执行对应的清理任务,如下图中的调用链执行链路 —— 这也证实了上面我们的分析。

所以,严格意义来说,Caffeine的大小容量限制并不能够保证完全精准的小于设定的值,会存在短暂的误差,但是作为一个以高并发吞吐量为优先考量点的组件而言,这一点点的误差也是可以接受的。关于这一点,如果阅读源码仔细点的小伙伴其实也可以发现在很多场景的注释中,Caffeine也都会有明确的说明。比如看下面这段从源码中摘抄的描述,就清晰的写着“如果有同步执行的插入或者移除操作,实际的元素数量可能会出现差异”。

代码语言:java
AI代码解释
复制
public interface Cache<K, V> {
    /**
     * Returns the approximate number of entries in this cache. The value returned is an estimate; the
     * actual count may differ if there are concurrent insertions or removals, or if some entries are
     * pending removal due to expiration or weak/soft reference collection. In the case of stale
     * entries this inaccuracy can be mitigated by performing a {@link #cleanUp()} first.
     *
     * @return the estimated number of mappings
     */
    @NonNegative
    long estimatedSize();

  // 省略其余内容...
}

同样道理,不管是基于大小、还是基于过期时间或基于引用的数据淘汰策略,由于数据淘汰处理是异步进行的,都会存在短暂不够精确的情况。

多种淘汰机制

上面提到并演示了Caffeine基于整体容量进行的数据驱逐策略。除了基于容量大小之外,Caffeine还支持基于时间与基于引用等方式来进行数据驱逐处理。

基于时间

Caffine支持基于时间进行数据的淘汰驱逐处理。这部分的能力与Guava Cache相同,支持根据记录创建时间以及访问时间两个维度进行处理。

数据的过期时间在创建缓存对象的时候进行指定,Caffeine在创建缓存对象的时候提供了3种设定过期策略的方法。

方式

具体说明

expireAfterWrite

基于创建时间进行过期处理

expireAfterAccess

基于最后访问时间进行过期处理

expireAfter

基于个性化定制的逻辑来实现过期处理(可以定制基于新增读取更新等场景的过期策略,甚至支持为不同记录指定不同过期时间

下面逐个看下。

expireAfterWrite

expireAfterWrite用于指定数据创建之后多久会过期,使用方式举例如下:

代码语言:java
AI代码解释
复制
Cache<String, User> userCache = 
    Caffeine.newBuilder()
        .expireAfterWrite(1, TimeUnit.SECONDS)
        .build();
userCache.put("123", new User("123", "张三"))

当记录被写入缓存之后达到指定的时间之后,就会被过期淘汰(惰性删除,并不会立即从内存中移除,而是在下一次操作的时候触发清理操作)。

expireAfterAccess

expireAfterAccess用于指定缓存记录多久没有被访问之后就会过期。使用方式与expireAfterWrite类似:

代码语言:java
AI代码解释
复制
Cache<String, User> userCache = 
    Caffeine.newBuilder()
        .expireAfterAccess(1, TimeUnit.SECONDS)
        .build();
    userCache.get("123", s -> userDao.getUser(s));

这种是基于最后一次访问时间来计算数据是否过期,如果一个数据一直被访问,则其就不会过期。比较适用于热点数据的存储场景,可以保证较高的缓存命中率。同样地,数据过期时也不会被立即从内存中移除,而是基于惰性删除机制进行处理。

expireAfter

上面两种设定过期时间的策略与Guava Cache是相似的。为了提供更为灵活的过期时间设定能力,Caffeine提供了一种全新的的过期时间设定方式,也即这里要介绍的expireAfter方法。其支持传入一个自定义的Expiry对象,自行实现数据的过期策略,甚至是针对不同的记录来定制不同的过期时间。

先看下Expiry接口中需要实现的三个方法:

方法名称

含义说明

expireAfterCreate

指定一个过期时间,从记录创建的时候开始计时,超过指定的时间之后就过期淘汰,效果类似expireAfterWrite,但是支持更灵活的定制逻辑。

expireAfterUpdate

指定一个过期时间,从记录最后一次被更新的时候开始计时,超过指定的时间之后就过期。每次执行更新操作之后,都会重新计算过期时间。

expireAfterRead

指定一个过期时间,从记录最后一次被访问的时候开始计时,超过指定时间之后就过期。效果类似expireAfterAccess,但是支持更高级的定制逻辑。

比如下面的代码中,定制了expireAfterCreate方法的逻辑,根据缓存key来决定过期时间,如果key以字母A开头则设定1s过期,否则设定2s过期:

代码语言:java
AI代码解释
复制
public static void main(String[] args) {
    try {
        LoadingCache<String, User> userCache = Caffeine.newBuilder()
                .removalListener((key, value, cause) -> {
                    System.out.println(key + "移除,原因:" + cause);
                })
                .expireAfter(new Expiry<String, User>() {
                    @Override
                    public long expireAfterCreate(@NonNull String key, @NonNullUser value, long currentTime) {
                        if (key.startsWith("A")) {
                            return TimeUnit.SECONDS.toNanos(1);
                        } else {
                            return TimeUnit.SECONDS.toNanos(2);
                        }
                    }
                    @Override
                    public long expireAfterUpdate(@NonNull String key, @NonNullUser value, long currentTime,
                                                  @NonNegative longcurrentDuration) {
                        return Long.MAX_VALUE;
                    }
                    @Override
                    public long expireAfterRead(@NonNull String key, @NonNull Uservalue, long currentTime,
                                                @NonNegative long currentDuration){
                        return Long.MAX_VALUE;
                    }
                })
                .build(key -> userDao.getUser(key));
        userCache.put("123", new User("123", "123"));
        userCache.put("A123", new User("A123", "A123"));
        Thread.sleep(1100L);
        System.out.println(userCache.get("123"));
        System.out.println(userCache.get("A123"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

执行代码进行测试,可以发现,不同的key拥有了不同的过期时间

代码语言:txt
AI代码解释
复制
User(userName=123, userId=123, departmentId=null)
A123移除,原因:EXPIRED
User(userName=A123, userId=A123, departmentId=null)

除了根据key来定制不同的过期时间,也可以根据value的内容来指定不同的过期时间策略。也可以同时定制上述三个方法,搭配来实现更复杂的过期策略。

按照这种方式来定时过期时间的时候需要注意一点,如果不需要设定某一维度的过期策略的时候,需要将对应实现方法的返回值设置为一个非常大的数值,比如可以像上述示例代码中一样,指定为Long.MAX_VALUE值。

基于大小

除了前面提到的基于访问时间或者创建时间来执行数据过期淘汰的方式之外,Caffeine还支持针对缓存总体容量大小进行限制,如果容量满的时候,基于W-TinyLFU算法,淘汰最不常被使用的数据,腾出空间给新的记录写入。

Caffeine支持按照Size(记录条数)或者按照Weighter(记录权重)值进行总体容量的限制。关于Size和Weighter的区别,之前的文章中有介绍过,如果不清楚的小伙伴们可以查看下《重新认识下JVM级别的本地缓存框架Guava Cache(2)——深入解读其容量限制与数据淘汰策略》。

maximumSize

在创建Caffeine缓存对象的时候,可以通过maximumSize来指定允许缓存的最大条数。

比如下面这段代码:

代码语言:java
AI代码解释
复制
Cache<Integer, String> cache = Caffeine.newBuilder()
        .maximumSize(1000L) // 限制最大缓存条数
        .build();
maximumWeight

在创建Caffeine缓存对象的时候,可以通过maximumWeightweighter组合的方式,指定按照权重进行限制缓存总容量。比如一个字符串value值的缓存场景下,我们可以根据字符串的长度来计算权重值,最后根据总权重大小来限制容量。

代码示意如下:

代码语言:java
AI代码解释
复制
Cache<Integer, String> cache = Caffeine.newBuilder()
        .maximumWeight(1000L) // 限制最大权重值
        .weigher((key, value) -> (String.valueOf(value).length() / 1000) + 1)
        .build();
使用注意点

需要注意一点:如果创建的时候指定了weighter,则必须同时指定maximumWeight值,如果不指定、或者指定了maximumSize,会报错(这一点与Guava Cache一致):

代码语言:txt
AI代码解释
复制
java.lang.IllegalStateException: weigher requires maximumWeight
	at com.github.benmanes.caffeine.cache.Caffeine.requireState(Caffeine.java:201)
	at com.github.benmanes.caffeine.cache.Caffeine.requireWeightWithWeigher(Caffeine.java:1215)
	at com.github.benmanes.caffeine.cache.Caffeine.build(Caffeine.java:1099)
	at com.veezean.skills.cache.caffeine.CaffeineCacheService.main(CaffeineCacheService.java:254)

基于引用

基于引用回收的策略,核心是利用JVM虚拟机GC机制来达到数据清理的目的。当一个对象不再被引用的时候,JVM会选择在适当的时候将其回收。Caffeine支持三种不同的基于引用的回收方法:

方法

具体说明

weakKeys

采用弱引用方式存储key值内容,当key对象不再被引用的时候,由GC进行回收

weakValues

采用弱引用方式存储value值内容,当value对象不再被引用的时候,由GC进行回收

softValues

采用软引用方式存储value值内容,当内存容量满时基于LRU策略进行回收

下面逐个介绍下。

weakKeys

默认情况下,我们创建出一个Caffeine缓存对象并写入key-value映射数据时,key和value都是以强引用的方式存储的。而使用weakKeys可以指定将缓存中的key值以弱引用(WeakReference)的方式进行存储,这样一来,如果程序运行时没有其它地方使用或者依赖此缓存值的时候,该条记录就可能会被GC回收掉。

代码语言:java
AI代码解释
复制
 LoadingCache<String,  User> loadingCache = Caffeine.newBuilder()
                .weakKeys()
                .build(key -> userDao.getUser(key));

小伙伴们应该都有个基本的认知,就是两个对象进行比较是否相等的时候,要使用equals方法而非==。而且很多时候我们会主动去覆写hashCode方法与equals方法来指定两个对象的相等判断逻辑。但是基于引用的数据淘汰策略,关注的是引用地址值而非实际内容值,也即一旦使用weakKeys指定了基于引用方式回收,那么查询的时候将只能是使用同一个key对象(内存地址相同)才能够查询到数据,因为这种情况下查询的时候,使用的是==判断是否为同一个key。

看下面的例子:

代码语言:java
AI代码解释
复制
public static void main(String[] args) {
    Cache<String, String> cache = Caffeine.newBuilder()
            .weakKeys()
            .build();
    String key1 = "123";
    cache.put(key1, "value1");
    System.out.println(cache.getIfPresent(key1));
    String key2 = new String("123");
    System.out.println("key1.equals(key2) : " + key1.equals(key2));
    System.out.println("key1==key2 : " + (key1==key2));
    System.out.println(cache.getIfPresent(key2));
}

执行之后,会发现使用存入时的key1进行查询的时候是可以查询到数据的,而使用key2去查询的时候并没有查询到记录,虽然key1与key2的值都是字符串123!

代码语言:txt
AI代码解释
复制
value1
key1.equals(key2) : true
key1==key2 : false
null

在实际使用的时候,这一点务必需要注意,对于新手而言,很容易踩进坑里

weakValues

与weakKeys类似,我们可以在创建缓存对象的时候使用weakValues指定将value值以弱引用的方式存储到缓存中。这样当这条缓存记录的对象不再被引用依赖的时候,就会被JVM在适当的时候回收释放掉。

代码语言:java
AI代码解释
复制
 LoadingCache<String,  User> loadingCache = Caffeine.newBuilder()
                .weakValues()
                .build(key -> userDao.getUser(key));

实际使用的时候需要注意weakValues不支持AsyncLoadingCache中使用。比如下面的代码:

代码语言:java
AI代码解释
复制
public static void main(String[] args) {
    AsyncLoadingCache<String, User> cache = Caffeine.newBuilder()
            .weakValues()
            .buildAsync(key -> userDao.getUser(key));
}

启动运行的时候,就会报错:

代码语言:txt
AI代码解释
复制
Exception in thread "main" java.lang.IllegalStateException: Weak or soft values cannot be combined with AsyncLoadingCache
	at com.github.benmanes.caffeine.cache.Caffeine.requireState(Caffeine.java:201)
	at com.github.benmanes.caffeine.cache.Caffeine.buildAsync(Caffeine.java:1192)
	at com.github.benmanes.caffeine.cache.Caffeine.buildAsync(Caffeine.java:1167)
	at com.veezean.skills.cache.caffeine.CaffeineCacheService.main(CaffeineCacheService.java:297)

当然咯,很多时候也可以将weakKeysweakValues组合起来使用,这样可以获得到两种能力的综合加成。

代码语言:java
AI代码解释
复制
 LoadingCache<String,  User> loadingCache = Caffeine.newBuilder()
                .weakKeys()
                .weakValues()
                .build(key -> userDao.getUser(key));
softValues

softValues是指将缓存内容值以软引用的方式存储在缓存容器中,当内存容量满的时候Caffeine会以LRU(least-recently-used,最近最少使用)顺序进行数据淘汰回收。对比下其与weakValues的差异:

方式

具体描述

weakValues

弱引用方式存储,一旦不再被引用,则会被GC回收

softValues

软引用方式存储,不会被GC回收,但是在内存容量满的时候,会基于LRU策略数据回收

具体使用的时候,可以在创建缓存对象的时候进行指定基于软引用方式数据淘汰:

代码语言:java
AI代码解释
复制
 LoadingCache<String,  User> loadingCache = Caffeine.newBuilder()
                .softValues()
                .build(key -> userDao.getUser(key));

与weakValues一样,需要注意softValues不支持AsyncLoadingCache中使用。此外,还需要注意softValuesweakValues两者也不可以一起使用。

代码语言:java
AI代码解释
复制
public static void main(String[] args) {
    LoadingCache<String, User> cache = Caffeine.newBuilder()
            .weakKeys()
            .weakValues()
            .softValues()
            .build(key -> userDao.getUser(key));
}

启动运行的时候,也会报错:

代码语言:java
AI代码解释
复制
Exception in thread "main" java.lang.IllegalStateException: Value strength was already set to WEAK
	at com.github.benmanes.caffeine.cache.Caffeine.requireState(Caffeine.java:201)
	at com.github.benmanes.caffeine.cache.Caffeine.softValues(Caffeine.java:572)
	at com.veezean.skills.cache.caffeine.CaffeineCacheService.main(CaffeineCacheService.java:297)

小结回顾

好啦,关于Caffeine Cache数据淘汰驱逐策略的实现原理与使用方式的阐述,就介绍到这里了。至此呢,关于Caffeine相关的内容就全部结束了,通过与Caffeine相关的这三篇文章,我们介绍完了Caffeine的整体情况、与Guava Cache相比的改进点、Caffeine的项目中使用,以及Caffeine在数据回源、数据驱逐等方面的展开探讨。关于Caffeine Cache,你是否有自己的一些想法与见解呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。

说起JAVA的本地缓存,除了此前提及的Guava Cache和这里介绍的Caffeine,还有一个同样无法被忽视的存在 —— Ehcache!作为被Hibernate选中的默认缓存实现框架,它究竟有什么魅力?它与Caffeine又有啥区别呢?接下来的文章中,我们就一起来认识下Ehcache,尝试找寻出答案。

📣 补充说明1

本文属于《深入理解缓存原理与实战设计》系列专栏的内容之一。该专栏围绕缓存这个宏大命题进行展开阐述,全方位、系统性地深度剖析各种缓存实现策略与原理、以及缓存的各种用法、各种问题应对策略,并一起探讨下缓存设计的哲学。如果有兴趣,也欢迎关注此专栏。

📣 补充说明2

我是悟道,聊技术、又不仅仅聊技术~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
干货:微信域名链接被微信封杀拦截屏蔽解决方案
想通过活动营销来推广产品的用户,肯定经常遇到域名被微信封杀拦截屏蔽的情况,想申请恢复访问过程复杂,而且由个人或小团队经营的网站基本只能放弃,花钱解封价格昂贵,遇到恶意举报还是一样被封,如此循环带来的损失无疑是很大的,都是在做推广,为何有人推广起来畅通无阻,而有人的微信域名频繁被封,其实微信被拦截被封无法访问很常见,经过测试以后还是可以防止微信域名被封的,并且很多团队也是通过微信域名防封的技术让域名存活的时间更长。那我们要怎样如何快速知道域名已经被微信封杀并恢复访问呢?我们从以下几方面来了解:
qq_1401806571
2020/02/26
10.5K0
干货:微信域名链接被微信封杀拦截屏蔽解决方案
实时微信域名检测API接口的实现方式
最近我们刚成立了一个项目,在微信推广中,域名时不时的就被微信拦截,这使我们都非常头大,这时我翻阅互联网上所有的资料,知道有微信域名检测这样一个API接口,问了身边做技术的朋友,朋友也说需要一这微信域名检测这样的API接口,实时检测域名在微信里是否可以打开,如果被微信拦截,则需要进行下一步操作,所以需要判断域名的状态,但是微信官方并没有提供相关查询的方法,最后在网上找到了这个接口地址,分享给有需要的朋友。
qq_1401806571
2020/03/16
3.1K0
最新最稳定的腾讯短网址(URL短链接)API接口分享
去年年底 ,因为一个客户委托我们开发了一个腾讯短网址API接口!原本这个腾讯的短链接接口一直是我们自己和委托开发的客户在使用!
用户7007940
2020/03/25
7.4K0
最新最稳定的腾讯短网址(URL短链接)API接口分享
微信域名检测查询-域名是否被拦截工具
公司的广告业务量很大,因为微信对域名链接限制是非常严格,这就致使了外部链接域名什么的很容易在微信中跑着跑着就会被屏蔽了,但是他把你屏蔽了又不会跟你通知,这就导致经常跑了很长的时间,发现域名早就已经被屏蔽了,公司利益得到损失。 真的是挺烦的,但是域名太多了,根本没有办法一个个手动检查。
用户6534591
2019/10/23
18.2K0
微信域名检测API接口,快速实时查询域名是否被微信拦截
信域名检测技术主要源于域名经常被微信拦截,哪里能实时检测出来微信域名被封的情况呢?例如,各种版本的微信客户端、微信公众号后台绑定域名、小程序后台绑定域名等等。所以,哪里能有结果,哪里就是利用目标,说到这里应该懂了吧!
用户7007940
2020/03/18
7.7K0
微信域名检测API接口,快速实时查询域名是否被微信拦截
微信域名检测PHP源码
$url = "http://api.new.urlzt.com/api/vx"; $params = array( 'appkey' =>'appkey',//您申请的APPKEY 'url' =>'www.urlzt.com',//您需要检测的域名 );   $paramstring = http_build_query($params); $content = monkeyCurl($url, $paramstring); $result = json_decode($content, true);
晶天
2021/02/23
7.7K0
微信域名检测PHP源码
微信小程序 接口调用讲解 (AccessToken、小程序码登录)
♘ 通过微信小程序实现扫码登录 ♘【网页版】使用小程序码登录 ♘ 通过扫小程序码实现网站登录功能
泥豆芽儿 MT
2022/05/10
19.2K0
微信小程序 接口调用讲解 (AccessToken、小程序码登录)
微信JSAPI支付
1.和H5、Native扫码支付略微有点不同,JSAPI主要适用于微信内支付的场景,就是在微信内置浏览器中实现的H5支付
安德玛
2022/03/05
2.3K0
自动获取ICP备案号接口源码
每一个备案的网站在建立后都要在网站底部放上自己的ICP备案号,并链接到 ICP/IP地址/域名信息备案管理系统 官网。
岳泽以
2022/10/26
4.9K0
自动获取ICP备案号接口源码
〔支付接入〕微信的 h5 支付和 jsapi 支付
江户川码农
2023/08/10
2.1K0
〔支付接入〕微信的 h5 支付和 jsapi 支付
Yii2.0实现微信公众号后台开发
1.用户授权接口:获取access_token、openid等;获取并保存用户资料到数据库
botkenni
2019/09/02
9830
微信域名检测API接口PHP源码
从这里可以看出,检测域名是否被微信屏蔽,是这里的核心。但是在网上搜索和查看微信的文档,微信官方没有提供相关的查询方法。分享一个接口地址,分享给有需要的朋友。
墨渊
2019/09/18
5.6K0
微信支付基于PHP
//微信JSAPI支付 前端页面wx.html <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>微信支付样例-支付</title> <script src="https://static.oschina.net/new
OwenZhang
2021/12/08
3.5K0
微信支付基于PHP
微信API接口(全) - 微信支付/微信红包/微信卡券/微信小店/JSAPI
微信入口绑定,微信事件处理,微信API全部操作包含在这些文件中。 微信支付、微信红包、微信卡券、微信小店。
程序猿的栖息地
2022/04/29
17.7K0
微信API接口(全) - 微信支付/微信红包/微信卡券/微信小店/JSAPI
微信公众号发布提醒(微信公众号模板消息接口)
灵感来源于学校的 每日健康日报,要求使用微信小程序进行每日健康打卡。所以此项目的功能类似于 QQ群机器人,或者是 每日闹钟。
全栈程序员站长
2022/07/26
13.1K0
微信公众号发布提醒(微信公众号模板消息接口)
PHP解决跨域问题常用的方法
比如a.test.com/a.html需要调用b.test.com/index.php,我们可以这样做,写一个接口a.test.com/index.php,由这个接口在后端去调用b.test.com/index.php并拿到返回值,然后再返回给a.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题。
友儿
2022/09/11
1K0
微信JSAPI支付PHP源码
2、公众平台,用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。
用户6493868
2022/03/09
6K0
【Web Function】实战使用:PHP Web函数搭建推送服务,轻松推送消息至个人微信
Web Function,实质上就是Serverless服务的一种,可以让用户在不需要服务器情况下,使用本来需要服务器才能使用的函数、功能(如:Nginx、PHP、Node等)。
Mintimate
2021/07/07
2.9K0
【Web Function】实战使用:PHP Web函数搭建推送服务,轻松推送消息至个人微信
推荐阅读
相关推荐
干货:微信域名链接被微信封杀拦截屏蔽解决方案
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • Caffeine的异步淘汰清理机制
  • 多种淘汰机制
    • 基于时间
      • expireAfterWrite
      • expireAfterAccess
      • expireAfter
    • 基于大小
      • maximumSize
      • maximumWeight
      • 使用注意点
    • 基于引用
      • weakKeys
      • weakValues
      • softValues
  • 小结回顾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档