`
Donald_Draper
  • 浏览: 980180 次
社区版块
存档分类
最新评论

Jedis获取Redis连接详解

阅读更多
Jedis操作Redis :http://donald-draper.iteye.com/blog/2346958
在前面一章中,我们讲了Jedis如何操作redis,今天我们来看一下他如何操作
从下面这几句开始:
  
 private static JedisPoolConfig jConfig = null;
    private static JedisPool pool = null;  
    private static Jedis jedis = null; 
    static {
    	jConfig = new JedisPoolConfig();
    } 
    public static void init() {  
        pool = new JedisPool(jConfig,"192.168.126.128",6379);
        jedis = pool.getResource();  
        jedis.auth("redis");  
        //测试是否连接成功
        System.out.println("Connecting redis......."+jedis.ping());
    }

上面这段话有几个要点第一点:Jedis连接池配置
jConfig = new JedisPoolConfig();

第二点:jedis连接池初始化
pool = new JedisPool(jConfig,"192.168.126.128",6379);

第三点:从连接池获取jedis连接
jedis = pool.getResource();  
jedis.auth("redis");  
//测试是否连接成功
System.out.println("Connecting redis......."+jedis.ping());


我们先来看第一点
jConfig = new JedisPoolConfig();


import org.apache.commons.pool.impl.GenericObjectPool;
public class JedisPoolConfig extends org.apache.commons.pool.impl.GenericObjectPool.Config
{
    public JedisPoolConfig()
    {
        setTestWhileIdle(true);
        setMinEvictableIdleTimeMillis(60000L);
        setTimeBetweenEvictionRunsMillis(30000L);
        setNumTestsPerEvictionRun(-1);
    }
    public int getMaxIdle()
    {
        return maxIdle;
    }
    public void setMaxIdle(int maxIdle)
    {
        this.maxIdle = maxIdle;
    }
     public int getMinIdle()
    {
        return minIdle;
    }

    public void setMinIdle(int minIdle)
    {
        this.minIdle = minIdle;
    }
      public int getMaxActive()
    {
        return maxActive;
    }
    public void setMaxActive(int maxActive)
    {
        this.maxActive = maxActive;
    }
    public long getMaxWait()
    {
        return maxWait;
    }
    public void setMaxWait(long maxWait)
    {
        this.maxWait = maxWait;
    }
...
}

从JedisPoolConfig我们可以看出,JedisPoolConfig的功能主要是配置连接最大空闲
时间,存活数量,及等待时间;
再来看GenericObjectPool.Config
public class GenericObjectPool extends BaseObjectPool
    implements ObjectPool
{
public static class Config
    {

        public int maxIdle;
        public int minIdle;//空闲时间
        public int maxActive;//存活数量
        public long maxWait;//等待时间
        public byte whenExhaustedAction;
        public boolean testOnBorrow;
        public boolean testOnReturn;
        public boolean testWhileIdle;
        public long timeBetweenEvictionRunsMillis;
        public int numTestsPerEvictionRun;
        public long minEvictableIdleTimeMillis;
        public long softMinEvictableIdleTimeMillis;
        public boolean lifo;

        public Config()
        {
            maxIdle = 8;
            minIdle = 0;
            maxActive = 8;
            maxWait = -1L;
            whenExhaustedAction = 1;
            testOnBorrow = false;
            testOnReturn = false;
            testWhileIdle = false;
            timeBetweenEvictionRunsMillis = -1L;
            numTestsPerEvictionRun = 3;
            minEvictableIdleTimeMillis = 1800000L;
            softMinEvictableIdleTimeMillis = -1L;
            lifo = true;
        }
    }
}

从上可以看出JedisPoolConfig的父类Config为GenericObjectPool的静态内部类,与连接池
有关的属性在Config中,而属性的设置在JedisPoolConfig中;
下面我们来看第二点:
pool = new JedisPool(jConfig,"192.168.126.128",6379);

初始化连接池:
package redis.clients.jedis;

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import redis.clients.util.Pool;

// Referenced classes of package redis.clients.jedis:
//            BinaryJedis, Jedis

public class JedisPool extends Pool
{
   public JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port)
    {
        this(poolConfig, host, port, 2000, null, 0);
    }
    //最后几个参数为timeout, password, database,超时时间,密码,数据库
    public JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password, int database)
    {
        super(poolConfig, new JedisFactory(host, port, timeout, password, database));
    }
}

在来看起父类Pool
public abstract class Pool
{
    private final GenericObjectPool internalPool;//连接池
    public Pool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, PoolableObjectFactory factory)
    {
        internalPool = new GenericObjectPool(factory, poolConfig);
    }
}

再来看一下GenericObjectPool
GenericObjectPool在上面已经出现过,Config为其静态内部类
public class GenericObjectPool extends BaseObjectPool
    implements ObjectPool
{
   public static final byte WHEN_EXHAUSTED_FAIL = 0;
    public static final byte WHEN_EXHAUSTED_BLOCK = 1;
    public static final byte WHEN_EXHAUSTED_GROW = 2;
    public static final int DEFAULT_MAX_IDLE = 8;
    public static final int DEFAULT_MIN_IDLE = 0;
    public static final int DEFAULT_MAX_ACTIVE = 8;
    public static final byte DEFAULT_WHEN_EXHAUSTED_ACTION = 1;
    public static final boolean DEFAULT_LIFO = true;
    public static final long DEFAULT_MAX_WAIT = -1L;
    public static final boolean DEFAULT_TEST_ON_BORROW = false;
    public static final boolean DEFAULT_TEST_ON_RETURN = false;
    public static final boolean DEFAULT_TEST_WHILE_IDLE = false;
    public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L;
    public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3;
    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1800000L;
    public static final long DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1L;
    private int _maxIdle;//空闲时间
    private int _minIdle;
    private int _maxActive;//存活数量
    private long _maxWait;//等待时间
    private byte _whenExhaustedAction;
    private volatile boolean _testOnBorrow;
    private volatile boolean _testOnReturn;
    private boolean _testWhileIdle;
    private long _timeBetweenEvictionRunsMillis;
    private int _numTestsPerEvictionRun;
    private long _minEvictableIdleTimeMillis;
    private long _softMinEvictableIdleTimeMillis;
    private boolean _lifo;
    private CursorableLinkedList _pool;//连接池
    private CursorableLinkedList.Cursor _evictionCursor;//候选连接池
    private PoolableObjectFactory _factory;//连接池对象工厂JedisFactory
    private int _numActive;//存活数量
    private Evictor _evictor;//候选连接执行器
    private int _numInternalProcessing;
    private final LinkedList _allocationQueue;//jedis连接获取互斥锁链
    //根据连接池工程和配置初始化GenericObjectPool
 public GenericObjectPool(PoolableObjectFactory factory, Config config)
    {
        this(factory, config.maxActive, config.whenExhaustedAction, config.maxWait, config.maxIdle, config.minIdle, config.testOnBorrow, config.testOnReturn, config.timeBetweenEvictionRunsMillis, config.numTestsPerEvictionRun, config.minEvictableIdleTimeMillis, config.testWhileIdle, config.softMinEvictableIdleTimeMillis, config.lifo);
    }
    public GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, int minIdle, 
            boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, 
            boolean testWhileIdle, long softMinEvictableIdleTimeMillis, boolean lifo)
    {
        _maxIdle = 8;
        _minIdle = 0;
        _maxActive = 8;
        _maxWait = -1L;
        _whenExhaustedAction = 1;
        _testOnBorrow = false;
        _testOnReturn = false;
        _testWhileIdle = false;
        _timeBetweenEvictionRunsMillis = -1L;
        _numTestsPerEvictionRun = 3;
        _minEvictableIdleTimeMillis = 1800000L;
        _softMinEvictableIdleTimeMillis = -1L;
        _lifo = true;
        _pool = null;
        _evictionCursor = null;
        _factory = null;
        _numActive = 0;//存活连接数量
        _evictor = null;
        _numInternalProcessing = 0;
        _allocationQueue = new LinkedList();
        _factory = factory;//连接池对象工厂
        _maxActive = maxActive;//最大存活数量
        _lifo = lifo;
        switch(whenExhaustedAction)
        {
        case 0: // '\0'
        case 1: // '\001'
        case 2: // '\002'
            _whenExhaustedAction = whenExhaustedAction;
            break;

        default:
            throw new IllegalArgumentException((new StringBuilder()).append("whenExhaustedAction ").append(whenExhaustedAction).append(" not recognized.").toString());
        }
        _maxWait = maxWait;
        _maxIdle = maxIdle;
        _minIdle = minIdle;//空闲、等待时间
        _testOnBorrow = testOnBorrow;
        _testOnReturn = testOnReturn;
        _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        _numTestsPerEvictionRun = numTestsPerEvictionRun;
        _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
        _softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
        _testWhileIdle = testWhileIdle;
	//class CursorableLinkedList implements List, Serializable,存放连接
        _pool = new CursorableLinkedList();
	//由于_evictor为空和_timeBetweenEvictionRunsMillis为-1L,这时候startEvictor没做任何事情
        startEvictor(_timeBetweenEvictionRunsMillis);
    }

}

//启动候选连接执行器
 protected synchronized void startEvictor(long delay)
    {
        if(null != _evictor)
        {
            EvictionTimer.cancel(_evictor);
            _evictor = null;
        }
        if(delay > 0L)
        {
            _evictor = new Evictor();
            EvictionTimer.schedule(_evictor, delay, delay);
        }

//GenericObjectPool内部类Evictor
 private class Evictor extends TimerTask
    {
        public void run()
        {
            try
            {
                //从连接池获取,候选连接池,并分配连接句柄
		evict();
            }
            catch(Exception e) { }
            catch(OutOfMemoryError oome)
            {
                oome.printStackTrace(System.err);
            }
            try
            {
                ensureMinIdle();
            }
            catch(Exception e) { }
        }
        final GenericObjectPool this$0;

        private Evictor()
        {
            this$0 = GenericObjectPool.this;
            super();
        }
    }
    public void evict()
        throws Exception
    {
        //如果候选连接为空,则从连接池中获取连接最为候选连接
        if(null == _evictionCursor)
            _evictionCursor = _pool.cursor(_lifo ? _pool.size() : 0);
        
        allocate();
        return;
    }
    //分配连接获取锁
    private synchronized void allocate()
    {
        if(isClosed())
            return;
        while(!_pool.isEmpty() && !_allocationQueue.isEmpty()) 
        {
            Latch latch = (Latch)_allocationQueue.removeFirst();
            latch.setPair((GenericKeyedObjectPool.ObjectTimestampPair)_pool.removeFirst());
            _numInternalProcessing++;
            synchronized(latch)
            {
                latch.notify();
            }
        }
        while(!_allocationQueue.isEmpty() && (_maxActive < 0 || _numActive + _numInternalProcessing < _maxActive)) 
        {
            Latch latch = (Latch)_allocationQueue.removeFirst();
            latch.setMayCreate(true);
            _numInternalProcessing++;
            synchronized(latch)
            {
                latch.notify();
            }
        }
}

从上面可看出,GenericObjectPool初始化,主要是初始化连接池,连接数,空闲时间,等待时间,连接池,候选连接池,初始化候选连接初始化执行器。
我们再看看一下JedisFactory
public class JedisPool extends Pool
{
    private static class JedisFactory extends BasePoolableObjectFactory
    {

        //构造Redis客户连接Jedis
	public Object makeObject()
            throws Exception
        {
	     //构建Jedis
            Jedis jedis = new Jedis(host, port, timeout);
            jedis.connect();
            if(null != password)
	        //密码不为空则,校验
                jedis.auth(password);
            if(database != 0)
	        //选择数据库
                jedis.select(database);
            return jedis;
        }
        //关闭Jedis连接
        public void destroyObject(Object obj)
            throws Exception
        {
            if(obj instanceof Jedis)
            {
                Jedis jedis = (Jedis)obj;
                if(jedis.isConnected())
                    try
                    {
                        try
                        {
                            jedis.quit();
                        }
                        catch(Exception e) { }
                        jedis.disconnect();
                    }
                    catch(Exception e) { }
            }
        }
       //验证Jedis连接
        public boolean validateObject(Object obj)
        {
            Jedis jedis;
            if(!(obj instanceof Jedis))
                break MISSING_BLOCK_LABEL_40;
            jedis = (Jedis)obj;
            return jedis.isConnected() && jedis.ping().equals("PONG");
            Exception e;
            e;
            return false;
            return false;
        }

        private final String host;//ip
        private final int port;//端口号
        private final int timeout;//超时时间
        private final String password;//密码
        private final int database;//数据库

        public JedisFactory(String host, int port, int timeout, String password, int database)
        {
            this.host = host;
            this.port = port;
            this.timeout = timeout;
            this.password = password;
            this.database = database;
        }
    }
}

从上面可以看出JedisFactory工厂为JedisPool的内部类,JedisFactory的属性有host,port,timeout,password和database;JedisFactory的主要功能为管理(创建,关闭,验证)redis连接jedis。
现在我们来看第三点
第三点:从连接池获取jedis连接
jedis = pool.getResource();  
jedis.auth("redis");  
//测试是否连接成功
System.out.println("Connecting redis......."+jedis.ping());


从连接池获取jedis连接资源
jedis = pool.getResource();

此方法在JedisPool在父类Pool中
public abstract class Pool
{

    public Pool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, PoolableObjectFactory factory)
    {
        internalPool = new GenericObjectPool(factory, poolConfig);
    }
    //从连接池GenericObjectPool,获取jedis连接资源
    public Object getResource()
    {
        return internalPool.borrowObject();
    }
}

再来看GenericObjectPool的borrowObject的方法
//GenericObjectPool
public Object borrowObject()
        throws Exception
    {
        long starttime;
        Latch latch;
        byte whenExhaustedAction;
        long maxWait;
        starttime = System.currentTimeMillis();
        latch = new Latch();
        synchronized(this)
        {
            whenExhaustedAction = _whenExhaustedAction;
            maxWait = _maxWait;
	    //将连接获取锁添加到连接分配锁队列
            _allocationQueue.add(latch);
        }
        allocate();
	...
	//关键在这一句从上面,我们看出_factory为JedisFactory,JedisFactory的makeObject返回的是Jedis连接
	Object obj = _factory.makeObject();
        latch.setPair(new ObjectTimestampPair(obj));
	...
	 _factory.activateObject(latch.getPair().value);
	//如果连接获取开关_testOnBorrow打开,则验证连接
	 if(_testOnBorrow && !_factory.validateObject(latch.getPair().value))
            throw new Exception("ValidateObject failed");
	 //返回jedis客户端
	 return latch.getPair().value;
}

再看borrowObject方法的这两句
latch.setPair(new ObjectTimestampPair(obj));

//ObjectTimestampPair,带时间戳的对象
  static class ObjectTimestampPair
        implements Comparable
    {

        public String toString()
        {
            return (new StringBuilder()).append(value).append(";").append(tstamp).toString();
        }

        public int compareTo(Object obj)
        {
            return compareTo((ObjectTimestampPair)obj);
        }

        public int compareTo(ObjectTimestampPair other)
        {
            long tstampdiff = tstamp - other.tstamp;
            if(tstampdiff == 0L)
                return System.identityHashCode(this) - System.identityHashCode(other);
            else
                return (int)Math.min(Math.max(tstampdiff, -2147483648L), 2147483647L);
        }

        public Object getValue()
        {
            return value;
        }

        public long getTstamp()
        {
            return tstamp;
        }

        /**
         * @deprecated Field value is deprecated
         */
        Object value;
        /**
         * @deprecated Field tstamp is deprecated
         */
        long tstamp;

        ObjectTimestampPair(Object val)
        {
            this(val, System.currentTimeMillis());
        }

        ObjectTimestampPair(Object val, long time)
        {
            value = val;
            tstamp = time;
        }
    }
 }
//Latch
public class GenericObjectPool extends BaseObjectPool
    implements ObjectPool
{
    //连接获取锁
    private static final class Latch
    {

        private synchronized GenericKeyedObjectPool.ObjectTimestampPair getPair()
        {
            return _pair;
        }

        private synchronized void setPair(GenericKeyedObjectPool.ObjectTimestampPair pair)
        {
            _pair = pair;
        }

        private synchronized boolean mayCreate()
        {
            return _mayCreate;
        }

        private synchronized void setMayCreate(boolean mayCreate)
        {
            _mayCreate = mayCreate;
        }

        private synchronized void reset()
        {
            _pair = null;
            _mayCreate = false;
        }

        private GenericKeyedObjectPool.ObjectTimestampPair _pair;
        private boolean _mayCreate;
        private Latch()
        {
            _mayCreate = false;
        }

    }
}

从连接池获取jedis连接资源,实际上看是从pool中获取,而pool又委托给JedisFactory,
最后由JedisFactory创建redis连接jedis。
这一节我们就看到这里
总结:
JedisPoolConfig的功能主要是配置连接最大空闲时间,存活数量,及等待时间;
JedisPoolConfig的父类Config为GenericObjectPool的静态内部类,与连接池有关的属性在Config中,而属性的设置在JedisPoolConfig中;JedisPool的初始化主要是GenericObjectPool初始化,主要是初始化连接池,连接数,空闲时间,等待时间,连接池,候选连接池,初始化候选连接初始化执行器,JedisFactory。JedisFactory工厂为JedisPool的内部类,JedisFactory的属性有host,port,timeout,password和database;JedisFactory的主要功能为管理(创建,关闭,验证)redis连接jedis。从连接池获取jedis连接资源,实际上看是从JedisPool的父类pool中获取,而pool又委托给JedisFactory,最后由JedisFactory创建redis连接jedis。
0
0
分享到:
评论

相关推荐

    Jedis操作redis服务实例

    **Jedis操作Redis服务实例详解** Redis是一款高性能的键值对数据库,被广泛应用于缓存、消息队列、数据持久化等多个场景。Jedis是Java语言的Redis客户端,提供了丰富的API来与Redis服务器进行交互。在本文中,我们...

    jedis-2.9.0.jar和commons-pool2-2.6.0.jar下载(jedis连接redis数据库)

    4. **执行命令**: 通过JedisPool获取连接,执行Redis命令,然后返回连接到池中。 ```java Jedis resource = jedisPool.getResource(); String value = resource.get("key"); resource.close(); ``` 5. **事务...

    java redis使用之利用jedis实现redis消息队列.docx

    这部分代码展示了如何通过 Jedis 连接 Redis 服务器,并提供了一些基本的 Redis 操作方法,如获取键值等。 - **单例模式初始化 Jedis 连接池**:通过静态块的方式创建了一个全局唯一的 `JedisPool` 实例,该实例会...

    Redis使用教程,详解

    Jedis 的使用非常简单,首先需要引入相关的 jar 包,然后创建连接实例,最后使用 Jedis 操作 Redis。 Redis 的特点 Redis 的特点包括易于扩展、灵活的数据模型、支持高并发读写、高效率存储和访问海量数据、高可用...

    Redis 、Redis 连接池、JedisPool

    1.全网最强最好用redis 封装连接池,redis 配置详解 2.jar 内置最全 最安全的两种redis 连接池 创建方式(synchronized and look), 3.通过了自己公司生产环境的检测 4.使用方法:只需要将jar 放入项目 lib 下面 ...

    redis使用详解

    使用JedisPool,你需要配置连接池的基本属性,如最大连接数、空闲连接存活时间等,然后从池中获取Jedis实例执行操作,完成后归还连接回池。 Sentinel是Redis的高可用解决方案,它能监控主从节点的状态,并在主节点...

    详解SSH框架和Redis的整合

    1. **相关Jar文件**:为了实现SSH与Redis的整合,需要引入Redis客户端Jedis的jar包(如jedis-2.3.1.jar)以及Spring对Redis支持的相关jar包(如spring-data-redis-1.3.4.RELEASE.jar)。此外,还需要连接池管理的库...

    关于Jedis连接Linux上的redis出现 DENIED Redis is running in protected mode问题的解决方案

    ### 关于Jedis连接Linux上的Redis出现DENIED Redis is running in protected mode问题的解决方案 #### 一、问题背景 在尝试使用Jedis客户端通过网络连接Linux服务器上的Redis时,可能会遇到一个常见的错误提示:...

    Redis的Java客户端Jedis

    **Redis的Java客户端Jedis详解** Redis是一款高性能的键值对数据存储系统,常用于缓存、消息中间件等场景。而Jedis是Redis官方推荐的Java语言客户端,提供了丰富的API来与Redis服务器进行交互。在2018年6月10日,...

    redis常用命令,redis配置文件,redis持久化,redis事务,redis主从复制,jedis的使用

    * Jedis 连接 Redis:使用 Jedis 的连接池来连接 Redis 服务器 * Jedis 执行命令:使用 Jedis 的命令执行器来执行 Redis 命令 CAP 原理 CAP 原理是分布式系统设计中的一个重要概念,指的是在分布式系统中,不可能...

    Jedis源码-供查询redis API源码

    **Jedis源码详解——深度探索Redis Java客户端** 在Java开发中,Jedis是与Redis进行交互的常用客户端库,它提供了丰富的API用于操作Redis的数据结构。本文将深入解析Jedis的源码,帮助开发者更好地理解和使用这个...

    springboot配置redis过程详解

    通过 Jedis,我们可以连接 Redis 数据库,执行各种操作,例如设置值、获取值、删除值等。 SpringBoot 配置 Redis 的优点 使用 SpringBoot 配置 Redis 有很多优点,例如: * 简单易用:SpringBoot 提供了简洁的...

    redis连接工具以及redis的简单配置

    4. **Jedis**:Java开发者的首选,Jedis是Java语言的Redis客户端库,提供了丰富的API供Java应用与Redis进行交互。 5. **ioredis**:在Node.js环境中,ioredis是一个高效的Redis客户端,它全面支持Redis命令并提供了...

    基于Spark的机器学习资料63、后台服务工具redis:详解redis操作命令.pdf

    基于Spark的机器学习资料63、后台服务工具Redis详解Redis操作命令 基于Spark的机器学习资料63、后台服务工具Redis,是一个高性能的基于内存的数据存储系统,支持五种基本数据类型:string(字符串)、hash(哈希)...

    jedis-2.1.0.jar+commons-pool-1.6.jar

    当应用启动时,Jedis会初始化一定数量的Redis连接并放入池中,后续的请求会从池中获取已存在的连接,使用完毕后再放回池中。这种机制使得多个并发请求可以共享这些连接,从而提高系统效率,减少系统资源的浪费。 **...

    jedis-3.3.0.jar文件

    **Jedis详解** Jedis是Java语言编写的Redis客户端,由Xaviers Leroy开发并维护。它提供了全面的Redis命令支持,包括数据类型操作、事务处理、发布订阅等功能,是Java开发者连接Redis的首选工具。Jedis 3.3.0是其较...

    Linux下Redis安装详解

    然而,如果你的项目中使用Java与Redis交互,你需要安装Java开发环境(JDK),并通过相关库如Jedis来实现Java程序与Redis的通信。 至此,你已经在Linux环境中成功安装并配置了Redis。了解这些基础知识后,你可以...

    jedis-2.4.2版本

    如果使用连接池,可以使用`JedisPoolConfig`配置连接池参数,并通过`JedisPool`获取连接。 2. **执行命令**:一旦连接建立,你可以使用Jedis实例上的各种方法执行Redis命令。例如,使用`set(key, value)`存储键值对...

    Spring+redis5.05配置过程.docx

    Spring+Redis 5.05 配置过程详解 ...我们使用了 Spring 的依赖注入机制来配置 Redis 连接信息,并使用 Jedis 客户端来连接 Redis 服务和执行 Redis 命令。这些技术可以帮助您快速构建高性能的缓存和数据存储系统。

Global site tag (gtag.js) - Google Analytics