java 缓存

Java缓存

所谓缓存,就是将程序或系统经常使用的对象存在内存中或在磁盘中创建缓存数据文件,以便再次使用时可以快速调用,有效的减少了再次从数据库中获取数据的开销,从而提高运行效率,减少等待时间。按照存储方式分为内存或磁盘。


1、常用内存缓存

实现简单,通常使用static关键字实现一个静态的Map,将从数据库中查询出的值放入缓存对象内,当再次需要从数据库中取出相同的值时直接从此Map中取出数据即可,避免了再次与数据库的交互而花费的时间。

需要注意的问题:

  • 缓存使用场景,适用范围
    1. 使用频率高,数据量大且在范围内
    2. 操作比较少
    3. 不会被并发更新的数据
  • 并发问题
    当多个程序片段同时对一个缓存对象进行增删改时而造成的数据错误。
    解决方案:使用线程安全的Map或自己实现一个线程安全的缓存类
  • 保证缓存数据与真实数据的同步性
    当真实数据改变后,缓存数据也已经立即更新同步或者删除缓存数据。

示例:网站中的菜单


2、缓存框架

2.1 ehcache

ehcache 是一个轻量级的缓存实现,纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。ehcache可以直接使用。它也是hibernate的默认2级缓存框架。

ehcache 的特点:

  • 快速
  • 简单
  • 内存与磁盘缓存
  • 集群式缓存
  • 具有缓存和缓存管理器的监听接口
  • 支持多缓存管理器实例,以及一个实例的多个缓存区域
  • hibernate的缓存实现

2.1.1 ehcache 核心类

ehcache中3个重要的对象。1个核心配置文件ehcache.xml。

(1) EhcacheManager:操作ehcache的入口。

缓存管理器通常使用单例获得一个实例,创建EhcacheManager:

//根据指定的源创建CacheMangager对象,
1. CacheManager.create(); //根据default config创建一个新的默认配置的单例CacheManager,或者返回一个已经存在的单例。
2. CacheManager.create(Configuration configuration);
3. CacheManager.create(URL configurationFileURL);
4. CacheManager.create(InputStream inputStream);
5. CacheManager.create(String configurationFileName);
//除此之外,还可以通过构造器的方式以及newInstance()的方式,与以上创建CacheManager对象大同小异
(2) Cache:ehcache的核心

由缓存管理器管理的用于缓存元素的对象。类似于下列代码中的userMap

public static Map<String,Integer> userMap = new ConcurrentHashMap<String,Integer>();
(3) Element:元素

正真缓存的对象,观察下列代码

Element element = new Element("zhangsan", 15);
cache.put(element);
↓↓↓↓↓↓↓↓↓↓↓↓↓↓
userMap.put('zhangsan',15)
(4) 核心配置文件

ehcache的Cache是配置在配置文件中的,当然也可以通过程序手动创建。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <!-- 当内存缓存中对象数量超过类设置内存缓存数量时,将缓存对象写到硬盘,path=”java.io.tmpdir”表示把数据写到这个目录下。Java.io.tmpdir目录在运行时会根据相对路径生成 -->
    <!-- 
        java.io.tmpdir:默认的临时文件存放路径。
        user.home:用户的主目录。
        user.dir:用户的当前工作目录,即当前程序所对应的工作路径。
        也可以自定义路径,如“D:\\abc ……”,它可以根据需要自由指定路径
     -->
    <diskStore path="java.io.tmpdir" />

    <!-- 必须配置 -->
   <defaultCache  
        maxEntriesLocalHeap="100000"  
        maxEntriesLocalDisk="10485760"
        eternal="false"  
        timeToIdleSeconds="86400"  
        timeToLiveSeconds="604800"  
        diskSpoolBufferSizeMB="30"  
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">  
        <persistence strategy="localTempSwap"/>
    </defaultCache>


    <!-- 
    ☆为必填属性

    ☆ name:指定cache的名称。
       maxEntriesLocalDisk:指定允许在硬盘上存放元素的最大数量,0表示不限制。这个属性我们也可以在运行期通过CacheConfiguration来更改。
    ☆ maxEntriesLocalHeap(等同于旧元素maxElementsInMemory):指定允许在内存中存放元素的最大数量,0表示不限制。这个属性也可以在运行期动态修改。
        1、若overflowToDisk(新属性为persistence)的属性值为true,会将cache中多出的元素放入磁盘文件中。
        2、若overflowToDisk(新属性为persistence)的属性值为false,会根据memoryStoreEvictionPolicy的策略替换cache中原有的元素
      maxEntriesInCache:指定缓存中允许存放元素的最大数量。这个属性也可以在运行期动态修改。但是这个属性只对Terracotta分布式缓存有用。
      maxBytesLocalDisk:指定当前缓存能够使用的硬盘的最大字节数,其值可以是数字加单位,单位可以是K、M或者G,不区分大小写,如:30G。当在CacheManager级别指定了该属性后,Cache级别也可以用百分比来表示,如:60%,表示最多使用CacheManager级别指定硬盘容量的60%。该属性也可以在运行期指定。当指定了该属性后会隐式的使当前Cache的overflowToDisk为true。
      maxBytesLocalHeap:指定当前缓存能够使用的堆内存的最大字节数,其值的设置规则跟maxBytesLocalDisk是一样的。
      maxBytesLocalOffHeap:指定当前Cache允许使用的非堆内存的最大字节数。当指定了该属性后,会使当前Cache的overflowToOffHeap的值变为true,如果我们需要关闭overflowToOffHeap,那么我们需要显示的指定overflowToOffHeap的值为false。
      overflowToDisk(新元素persistence):boolean类型,默认为false。当内存里面的缓存已经达到预设的上限时是否允许将按驱除策略驱除的元素保存在硬盘上,默认是LRU(最近最少使用)。当指定为false的时候表示缓存信息不会保存到磁盘上,只会保存在内存中。该属性现在已经废弃,推荐使用cache元素的子元素persistence来代替,如:<persistence strategy="localTempSwap"/>。
      strategy:localRestartable:仅在使用Ehcache企业版时有效。启用RestartStore,拷贝所有的缓存项(包含堆和非堆中的)到磁盘中,此选项提供了缓存快速重启能力以及对磁盘上缓存的容错能力。 
      localTempSwap:当缓存容量达到上限时,将缓存对象 (包含堆和非堆中的)交换到磁盘中。"localTempSwap" 并不持久化缓存内容。  none:不持久化缓存内容。 
      distributed:按照<terracotta>标签配置的持久化方式执行。非分布式部署时,此选项不可用。
      synchronousWrites:此属性仅在strategy="localRestartable"时有意义。默认false。设置为true,缓存写入方法在缓存项成功写入磁盘前不会返回     
      diskSpoolBufferSizeMB:当往磁盘上写入缓存信息时缓冲区的大小,单位是MB,默认是30。
      overflowToOffHeap:boolean类型,默认为false。表示是否允许Cache使用非堆内存进行存储,非堆内存是不受Java GC影响的。该属性只对企业版Ehcache有用。
      copyOnRead:当指定该属性为true时,我们在从Cache中读数据时取到的是Cache中对应元素的一个copy副本,而不是对应的一个引用。默认为false。
      copyOnWrite:当指定该属性为true时,我们在往Cache中写入数据时用的是原对象的一个copy副本,而不是对应的一个引用。默认为false。
      timeToIdleSeconds:单位是秒,表示一个元素所允许闲置的最大时间,也就是说一个元素在不被请求的情况下允许在缓存中待的最大时间。默认是0,表示不限制。
      timeToLiveSeconds:单位是秒,表示无论一个元素闲置与否,其允许在Cache中存在的最大时间。默认是0,表示不限制。
   ☆ eternal:boolean类型,表示是否永恒,默认为false。如果设为true,将忽略timeToIdleSeconds和timeToLiveSeconds,Cache内的元素永远都不会过期,也就不会因为元素的过期而被清除了。
      diskExpiryThreadIntervalSeconds :单位是秒,表示多久检查元素是否过期的线程多久运行一次,默认是120秒。
      clearOnFlush:boolean类型。表示在调用Cache的flush方法时是否要清空MemoryStore。默认为true。
      memoryStoreEvictionPolicy:当内存里面的元素数量或大小达到指定的限制后将采用的驱除策略。默认是LRU(最近最少使用),可选值还有LFU(最不常使用)和FIFO(先进先出)。
      diskPersistent设定在虚拟机重启时是否进行磁盘存储,默认为false
      transactionalMode="off" 使ehcache作为JTA事务的参与者,只需要如下属性
    -->

    <cache name="simpleCache" eternal="false" maxElementsInMemory="30" >
        <persistence strategy="localTempSwap"/>
    </cache>

    <cache name="userListCache"  
           maxEntriesLocalHeap="10"
           maxEntriesLocalDisk="1048576"
           eternal="false"
           timeToIdleSeconds="30"
           timeToLiveSeconds="180"
           memoryStoreEvictionPolicy="LFU">  
        <persistence strategy="localTempSwap"/>
    </cache>

    <cache name="cache"  
           maxEntriesLocalHeap="1"
           maxEntriesLocalDisk="1048576"
           eternal="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="20"
           memoryStoreEvictionPolicy="LFU">  
        <persistence strategy="localTempSwap"/>
    </cache>  

    <cache name="cache1"
           maxEntriesLocalHeap="100000"  
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="500"
           />

    <cache name="cache2"
           maxEntriesLocalHeap="100000"  
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="500"
           />
</ehcache>

2.1.1 单独使用ehcache

在程序中单独使用ehcache,需要导入的jar包:ehcache-2.8.3.jar,slf4j-api-1.6.6.jar,步骤如下:

1、配置文件ehcache.xml配置需要缓存的cache

示例配置如下:

<cache name="userListCache"  
       maxEntriesLocalHeap="10"
       maxEntriesLocalDisk="1048576"
       eternal="false"
       timeToIdleSeconds="30"
       timeToLiveSeconds="180"
       memoryStoreEvictionPolicy="LFU">  
    <persistence strategy="localTempSwap"/>
</cache>
2、可自定义一个ehcacheUtil工具类,方便进行缓存的操作。

代码可以参考com.chavin.cache.EhcacheUtil。

3、将需要缓存的数据置于cache中。

示例代码如下:

if(!userList.isEmpty()){
    EhcacheUtil uEhcacheUtil = EhcacheUtil.getInstance();
    uEhcacheUtil.removeAllElement("userListCache");
    uEhcacheUtil.putElement("userListCache", "userList", userList);
}
4、读取缓存中的数据。

示例代码如下:

List<User> userList = (List<User>) uEhcacheUtil.getElement("userListCache", "userList");
String cacheInfo = "";
if(userList == null || userList.size() == 0){
    cacheInfo = "缓存中无值,本数据是从数据库中获取";
    UserDao dao = new UserDao();
    userList = dao.getUserList();
}else{
    cacheInfo = "缓存中有值,从缓存userListCache中直接获取本次数据";
}
5、当缓存数据所保存的数据发生改动时,应当对缓存中的数据进行相应的修改

示例代码如下:

EhcacheUtil uEhcacheUtil = EhcacheUtil.getInstance();
uEhcacheUtil.removeAllElement("userListCache");
uEhcacheUtil.putElement("userListCache", "userList", userList);

2.1.2 ehcache与spring整合使用

ehcache与spring整合使用与单独使用ehcache相差无几。可以通过注解或者aop的方式实现,ehcache.xml的配置文件与单独使用ehcache的配置一样,在spring中添加如下代码:

<cache:annotation-driven cache-manager="cacheManager"/> 

<!--  缓存属性-->;
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> 
    <property name="configLocation"  value="classpath:ehcache.xml"/> 
</bean> 

<!-- 默认是cacheManager -->;
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> 
    <property name="cacheManager"  ref="cacheManagerFactory"/> 
</bean>;

然后在service或dao层使用注解的方式表示是否需要缓存或是清空缓存,示例代码如下:

//@Cacheable({ "serviceCache" })指定缓存名为serviceCache,如果后面有参数可以指定key=#p0作为参数
@Cacheable({ "serviceCache" })
public List<User> getAll() {
    System.out.println(sdf.format(new Date()) + ": 本次查询getAll()数据从数据库中获取");
    List users = this.userDao.findAll();
    return users;
}

@CacheEvict(value = { "serviceCache" }, allEntries = true)
public int updateUser(int deleted, String sql) {
    System.out.println("执行updateUser方法,缓存已清空");
    return this.userDao.updateUser(deleted, sql);
}

//@CacheEvict用于清除缓存,beforeInvocation为在方法执行前清除
@CacheEvict(value = { "serviceCache" }, allEntries = true, beforeInvocation = true)
public void removeAll() {
    System.out.println("清除所有缓存");
}

推荐博文,spring使用cache


2.1.3 ehcache分布式缓存/集群缓存

ehcache支持的集群方案:

  • RMI (Remote Method Invocation) 远程方法调用
    是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机上的对象可以调用另外一台计算机上的对象来获取远程数据。

  • JMS (Java Messaging Service) Java消息服务
    基本概念:JMS是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。

  • JGroups

    JGroups is a toolkit for reliable messaging. It can be used to create clusters whose nodes can send messages to each other.
    JGroups是可靠的消息传递的工具包, 它通常被用来创建集群,它的节点间可以互相发送消息。

  • Terracotta 集群开源框架

这里介绍一篇写的好而且非常详细的文章,深入探讨在集群环境中使用ehcache缓存系统

其他关于集群相关内容,还请自行查阅相关资料学习。


2.2 OSCache

OSCache标记库由OpenSymphony设计,它是一种开创性的缓存方案,它 提供了在现有 JSP 页面之内实现内存缓存的功能。OSCache是个一个被广泛采用的高性能的J2EE缓存框架,OSCache还能应用于任何Java应用程序的普通的缓存解决方案。
OSCache的特点:

  • 多种缓存策略
  • 编程式控制cache
  • 支持集群缓存
  • 缓存过期
2.2.1 OSCache核心

OSCache与ehcache类似,OSCache也有有3个重要的类和一个配置文件,大致与ehcache中的类似,不做过多的介绍。

  • AbstractCacheAdministrator
  • Cache
  • CacheEntry 具体的缓存对象
  • oscache.properties

OSCache中可以缓存对象,也可以缓存页面:

  • 缓存对象 以键值对的形式进行缓存
  • 缓存页面
  • 局部页面缓存
  • 全部页面缓存
2.2.2 OSCache使用方法

OSCache主要在页面缓存中使用比较多,所以本文中主要介绍OSCache的基本用法。

(1) 基本配置和使用
1、WEB.XML中如下配置:
<jsp-config>
    <taglib>
        <taglib-uri>oscache&lt;/taglib-uri>
        <taglib-location>/WEB-INF/classes/oscache.tld&lt;/taglib-location>
    </taglib>
</jsp-config>

2、JSP页面中配置:
<%@ taglib uri="oscache" prefix="cache" %>

注:1、2两步等同于JSTL中在JSP页面中的一句话:
<@ taglib uri="http://www.opensymphony.com/oscache" prefix="oscache" %>

3、页面中使用标签,示例:
<!-- 当session关闭后,缓存内容将无效 -->
<cache:cache key="name" scope="session">  
    name=${param.name}
</cache:cache>

<!-- 设置时间为10秒钟过期 -->
<cache:cache key="name" time="10">  
    name=${param.name}  
</cache:cache>  

<!-- 清除application范围内的缓存  -->
<cache:flush key="cache" scope="application"/>

<!-- 清除session范围内的缓存  -->
<cache:flush scope="session" /> 

<!-- 清除application范围内组名为currencvData的所有缓存 -->
<cache:flush scope="application" group="currencvData"/> 
1、cache标签

OSCache附带一个JSP标记库控制所有的主要功能。

cache标签是OSCache主要的标签。根据指定的属性,标签的体将被缓存。以后每次运行标签,它会检查,看看缓存的内容是否过期,如果下列条件成立的话,标签体的内容将被认为是过时的:
   1:标签体的内容超过了指定的缓存时间;默认为一小时,指定时以s为单位。
   2:超多了cron属性指定的日期或时间;
   3:通过flush标签,清除了指定的scope作用域的缓存。
如果缓存的主体内容是过期的,标签会再次执行和缓存新的主体内容。

属性:
① Key:范围内唯一的。
② scope:缓存存放的作用域,默认为application。可选的值为session和application。
③ time:指定缓存存放的时间,以秒为单位,默认为3600s,即一个小时。如果为负值表示永远不过期。
④ duration:这是和time属性二选一的。Duration可以通过simple date format指定。
⑤ cron:cron表达式确定缓存的内容什么时候过期,它允许缓存的内容在特殊的日期或时间过期。
    cron表达式:
  (1Minute:指定缓存的内容在小时的第几分钟过期,取值范围是0-59;
  (2Hour:指定缓存的内容在一天的第几个小时将过期,这是指定使用24小时制,因此取值范围是:0(午夜)-23(上午11点)。
  (3)DOM:一个月的第几天。这是一个从131的数,它表示缓存的内容什么日子过期,如:在每月的10号过期,应该把值设为10。
  (4Month:一年的第几月使内容过期,它可以通过数字1-12或者月的英文名字(eg 'January')指定。月的名称对大小写是不敏感的,只考虑前三个字符,其他部分忽略。5)DOW:一周的第几天使缓存的内容过期,它可以是数字(0-6,0=星期天,6=星期六)或者是英文的星期名称。和月份一样大小写是不敏感的,只考虑前三个字符,其他部分忽略。
  当你不想给某个字段给定特定的值,你可以使用"*"代替。也可以是一个时间段或者实际范围,具体可查看cron的表达式。
2、usecached

usecached 这个标签内嵌在一个标签中。

告诉所在的标签是否使用已经缓存的内容(缺省为true,使用缓存的内容)。可以使用这个标签来控制缓存。比如使用标签刷新某个key的缓存,但可以在必要的地方即使这样的强制刷新也仍然使用缓存内容而不刷新。

3、flush

刷新缓存。

<cache:flush scope="session" key="foobar" />
<cache:flush scope="application" group="currencyData" />
  • Scope:这决定哪些范围将被刷新。有效值是“application”,“session”和null。空范围将刷新所有缓存,不论其范围。默认为all。
  • Key:当同时指定了key和scope,表示一个缓存条目被标记为flush。当他下次被访问的时候缓存将被refresh。当仅指定了key而没有指定scope这是无效的。
  • group :指定一组将导致组中的所有缓存项被flush,如果仅仅指定group没有指定scope这是无效的。
4、addgroup

此标签必须嵌套在<cache:cache></cache:cache>中,它允许一个组名,动态地添加到缓存的块中.

<cache:cache key="test1">
     <cache:addgroup group="group1" />
     ... some jsp content ...
     <cache:addgroup group="group2" />
     ... some more jsp content ...
</cache:cache>
5、mode

如果不希望被缓存的内容增加到给用户的响应中,可以设置mode属性为”silent”。此时被缓存的部分不在页面上显示,而其它任意的mode属性值都会将缓存的部分显示到页面上。

(2)页面缓存

示例:以下配置将缓存所有jsp页面10分钟,有效期为session关闭。

<filter>
    <filter-name>CacheFilter&lt;/filter-name>
    <filter-class>com.opensymphony.oscache.web.filter.CacheFilter&lt;/filter-class>
    <init-param>
        <param-name>time&lt;/param-name>
        <param-value>600&lt;/param-value>
    </init-param>
    <init-param>
        <param-name>scope&lt;/param-name>
        <param-value>session&lt;/param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CacheFilter&lt;/filter-name>
    <url-pattern>*.jsp&lt;/url-pattern>
</filter-mapping>

注意, CacheFilter 只捕获 Http 头为 200 的页面请求,即只对无错误请求作缓存,而不对其他请求(如 500,404,400 )作缓存处理
(3) 配置文件

oscache.properties的配置详情可查看官网提供的配置文件,里面有很详尽的注释。以下列出一些配置示例:

1、cache.memory
<!--是否使用内存缓存;值为:true或false。默认为true;如设置为false,那cache只能缓存到数据库或硬盘中。

2、cache.capacity
<!--缓存的最大数量。默认是不限制,cache不会移走任何缓存内容。负数被视不限制。-->

3、cache.algorithm
<!--运算规则。为了使用规则,cache的size必须是指定的。

<!--如果cache的size不指定的话, 将不会限制缓存对象的大小。如果指定了cache的size,但不指定algorithm,那它会默认使用:com.opensymphony.oscache.base.algorithm.LRUCache-->

<!--有下面三种规则:-->
com.opensymphony.oscache.base.algorithm.LRUCache: last in first <!--out(最后插入的最先调用)。默认选项。-->
com.opensymphony.oscache.base.algorithm.FIFOCache: first int first <!--out(最先插入的最先调用)。-->
com.opensymphony.oscache.base.algorithm.UnlimitedCache: <!--cache中的内容将永远不会被丢弃。如果cache.capacity不指定值的话,它将被设为默认选项。-->

4、cache.blocking
<!--是否同步。true或者false。一般设为true,避免读取脏数据。-->

5、cache.unlimited.disk
<!--指定硬盘缓存是否要作限制。默认值为false。false的状况下,disk cache capacity和cache.capacity的值相同。-->

6、cache.persistence.class
<!--指定类是被持久化缓存的类。class必须实现PersistenceListener接口。
作为硬盘持久,可以实现com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener接口。
它把class的toString()输出的hash值作为文件的名称。如果你要想文件名易读些(自己设定),DiskPersistenceListener的父类也能使用,但其可能有非法字符或者过长的名字。
注意:HashDiskPersistenceListener和DiskPersistenceListener需要设定硬盘路径:cache.path-->

7、cache.path
<!--指定硬盘缓存的路径。目录如果不存在将被建立。同时注意oscache应该要有权限写文件系统。
例:-->
cache.path=c:\\myapp\\cache
cache.path=/opt/myapp/cache

8、cache.persistence.overflow.only (NEW! Since 2.1)
<!--指定是否只有在内存不足的情况下才使用硬盘缓存。
默认值false。但推荐是true如果内存cache被允许的话。这个属性彻底的改变了cache的行为,使得persisted cache和memory是完全不同。

9、cache.event.listeners
<!--class名列表(用逗号隔开)。每个class必须实现以下接口中的一个或者几个。
CacheEntryEventListener:接收cache add/update/flush and remove事件
CacheMapAccessEventListener :接收cache访问事件。这个可以让你跟踪cache怎么工作。
默认是不配置任何class的。当然你可以使用一下class:
com.opensymphony.oscache.plugins.clustersupport.BroadcastingCacheEventListener: 分布式的监听器。可以广播到局域网内的其他cache实例。
com.opensymphony.oscache.extra.CacheEntryEventListenerImpl:一个简单的监听器。在cache的生命周期中记录所有entry的事件。
com.opensymphony.oscache.extra.CacheMapAccessEventListenerImpl: 记录count of cache map events(cache hits,misses and state hits).-->   

10、cache.key
<!--在application和session的作用域时,用于标识cache对象的,用于ServletCacheAdministrator;此属性不是指定为"__oscache_cache"格式时为默认值, 如果代码中需要用到默认值时可以通使用com.opensymphony.oscache.base.Const.DEFAULT_CACHE_KEY来取得;-->

11、cache.use.host.domain.in.key
<!--当配置多个服务器时,想通过服备器名称自动生成cache key时,可将此属性设为true. 默认值为false;-->

12、Additional Properties
<!--在以上基础选项之上可以加入一些额外的属性到此文件中.
例: JavaGroupsBroadcastingListener便是额外的.-->

13、cache.cluster.multicast.ip
<!--用于缓存集群. 默认为231.12.21.132-->

14、cache.cluster.properties
<!--指集群中的额外配置项.-->

参考文档地址:


3、memcached

memcached是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。Memcached是以守护程序(监听)方式运行于一个或多个服务器中,随时会接收客户端的连接和操作。

3.1 安装说明
  • 先下载memcached for win32,解压至文件夹
  • 运行cmd并进入文件夹
  • 安装命令:
1. memcached -d install
2. memcached -d start 或 net start memcached Server
3. memcached -d stop 或 net stop memcached
4. 修改最大内存使用为100MB
memcached -d runservice -m 512  
其他命令如下:
   -p 监听的端口,默认为11211
   -l 连接的IP地址, 默认是本机 
   -d start 启动memcached服务 
   -d restart 重起memcached服务 
   -d stop|shutdown 关闭正在运行的memcached服务 
   -d install 安装memcached服务 
   -d uninstall 卸载memcached服务 
   -u 以的身份运行 (仅在以root运行的时候有效) 
   -m 最大内存使用,单位MB。默认64MB 
   -M 内存耗尽时返回错误,而不是删除项 
   -c 最大同时连接数,默认是1024 
   -f 块大小增长因子,默认是1.25 
   -n 最小分配空间,key+value+flags默认是48 
   -h 显示帮助
  • 使用telnet查看memcached的缓存信息
- telnet 127.0.0.1 11211    
- ctrl + ] 
- stats
以此执行即可查看  

time:  1255537291             服务器当前的unix时间戳 
total_items:  54              从服务器启动以后存储的items总数量 
connection_structures:  19    服务器分配的连接构造数 
version:    1.2.6             memcache版本
limit_maxbytes:    67108864   分配给memcache的内存大小(字节) 
cmd_get:    1645              get命令(获取)总请求次数 
evictions:0                   为获取空闲内存而删除的items数(分配给memcache的空间用满后需要删除旧的items来得到空间分配给新的items) 
total_connections:    19      从服务器启动以后曾经打开过的连接数 
bytes:    248723              当前服务器存储items占用的字节数 
threads:    1                 当前线程数 
get_misses:    82             总未命中次数 
pointer_size:    32           当前操作系统的指针大小(32位系统一般是32bit) 
bytes_read:    490982         总读取字节数(请求字节数) 
uptime:    161                服务器已经运行的秒数 
curr_connections:    18       当前打开着的连接数 
pid:    2816                  memcache服务器的进程ID 
bytes_written:    16517259    总发送字节数(结果字节数) 
get_hits:    1563             总命中次数 
cmd_set:    54                set命令(保存)总请求次数 
curr_items:    28             服务器当前存储的items数量 

具体内容,查看操作演示;


3.2 Memcached API
3.2.1 MemcachedClient

Memcached的客户端有多种,包括C,java,php,.net等。
+ memcached客户端,它的构造函数有如下几个:

public MemcachedClient(InetSocketAddress[] ia) throws IOException;  
public MemcachedClient(List<InetSocketAddress> addrs) throws IOException;
public MemcachedClient(ConnectionFactory cf, List<InetSocketAddress> addrs) throws IOException;

如示例中使用的就是第一种构造函数。
MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211));    
  • 常用方法
    一般缓存数据的常用操作有:set、get、replace、add
1、public Future set(String key, int exp, Object o)
将数据保存到cache服务器,如果保存成功则返回true;如果cache服务器存在同样的key,则替换之;
参数:
    ① 键(key) 
    ② 过期时间(秒)
    ③ 要缓存的对象(如果缓存中有则修改,没有则插入)

2、public Object get(String key)
使用get方法从cache服务器获取一个数据;如果写入时是压缩的或序列化的,则get的返回会自动解压缩及反序列化;
参数:
    ① 键(key)

3、public Future replace(String key, int exp, Object o)  
将数据替换cache服务器中相同的key,如果保存成功则返回true;如果cache服务器不存在同样key,则返回false;
参数:
    ① 键(key)
    ② 过期时间(秒)
    ③ 要缓存的对象

4、public Future add(String key, int exp, Object o)   
将数据添加到cache服务器,如果保存成功则返回true;如果cache服务器存在同样key,则返回false;
参数:
    ① 键(key)
    ② 过期时间(秒)
    ③ 要缓存的对象
3.2.2 SockIOPool

这个类用来创建管理客户端和服务器通讯连接池,客户端主要的工作包括数据通讯、服务器定位、hash码生成等都是由这个类完成的。

  • SockIOPool
//获得连接池的单态方法。这个方法有一个重载方法getInstance( String poolName ),每个poolName只构造一个SockIOPool实例。缺省构造的poolName是default
//如果在客户端配置多个memcached服务,一定要显式声明poolName。
public static SockIOPool getInstance()

//设置连接池可用的cache服务器列表,server的构成形式是IP:PORT(如:127.0.0.1:11211)
public void setServers( String[] servers )

//设置连接池可用cache服务器的权重,和server数组的位置一一对应
//其实现方法是通过根据每个权重在连接池的bucket中放置同样数目的server(如下代码所示),因此所有权重的最大公约数应该是1,不然会引起bucket资源的浪费。 
public void setWeights( Integer[] weights )

//设置开始时每个cache服务器的可用连接数
public void setInitConn( int initConn )

//设置每个服务器最少可用连接数
public void setMinConn( int minConn )

//设置每个服务器最大可用连接数
public void setMaxConn( int maxConn )

//设置可用连接池的最长等待时间
public void setMaxIdle( long maxIdle )

//省略
...

//设置完pool参数后最后调用该方法,启动pool。
public void initialize()


代码示例:
// 创建全局的唯一实例
protected static MemCachedClient client = new MemCachedClient();
protected static MemCached memCached = new MemCached();
// 设置与缓存服务器的连接池
static {
    // 服务器列表和其权重
    String[] servers = { "127.0.0.1:11211" };
    Integer[] weights = { 3 };
    // 获取socke连接池的实例对象
    // 这个类用来创建管理客户端和服务器通讯连接池,
    // 客户端主要的工作(包括数据通讯、服务器定位、hash码生成等)都是由这个类完成的。
    SockIOPool pool = SockIOPool.getInstance();
    // 设置服务器信息
    pool.setServers(servers);

    // 设置Server权重
    pool.setWeights(weights);
    // 设置初始连接数、最小和最大连接数以及最大处理时间
    pool.setInitConn(5);
    pool.setMinConn(5);
    pool.setMaxConn(250);
    pool.setMaxIdle(1000 * 60 * 60 * 6);
    // 设置主线程的睡眠时间
    pool.setMaintSleep(30);
    // 设置连接心跳监测开关
    // true:每次通信都要进行连接是否有效的监测,造成通信次数倍增,加大网络负载,
    // 因此在对HighAvailability要求比较高的场合应该设为true
    // 默认状态是false,建议保持默认。
    pool.setAliveCheck(false);
    // 设置连接失败恢复开关
    // 设置为true,当宕机的服务器启动或中断的网络连接后,这个socket连接还可继续使用,否则将不再使用.
    // 默认状态是true,建议保持默认。
    pool.setFailback(true);
    // 设置容错开关
    // true:当当前socket不可用时,程序会自动查找可用连接并返回,否则返回NULL
    // 默认状态是true,建议保持默认。
    pool.setFailover(true);
    // 设置hash算法
    // alg=0 使用String.hashCode()获得hash code,该方法依赖JDK,可能和其他客户端不兼容,建议不使用
    // alg=1 使用original 兼容hash算法,兼容其他客户端
    // alg=2 使用CRC32兼容hash算法,兼容其他客户端,性能优于original算法
    // alg=3 使用MD5 hash算法
    // 采用前三种hash算法的时候,查找cache服务器使用余数方法。采用最后一种hash算法查找cache服务时使用consistent方法。
    // 默认值为0
    pool.setHashingAlg(0);
    // 设置是否使用Nagle算法,因为我们的通讯数据量通常都比较大(相对TCP控制数据)而且要求响应及时,
    // 因此该值需要设置为false(默认是true)
    pool.setNagle(false);

    // 设置socket的读取等待超时值
    pool.setSocketTO(3000);

    // 设置socket的连接等待超时值
    pool.setSocketConnectTO(0);
    // 初始化连接池
    pool.initialize();
    // 压缩设置,超过指定大小(单位为K)的数据都会被压缩
    // client.setCompressEnable(true); //UnsupportedOperation
    // client.setCompressThreshold(64 * 1024);
}
3.2.2 其他介绍
  • 内置存储方式
    为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中,于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。 另外,内容容量达到指定值之后,就基于LRU(Least Recently Used,最近最少使用)算法自动删除不使用的缓存。
  • 删除
    Memcached在数据删除方面有效里利用资源。Memcached删除数据时数据不会真正从memcached中消失。Memcached不会释放已分配的内存。记录超时后,客户端就无法再看见该记录(invisible 透明),其存储空间即可重复使用。
  • server互不通信的方式
    memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现,客户端在进行存取缓存数据时,通过特定的算法从某一个server中保存或者获取数据。

memcached基础的一篇文章
memcached中使用到的一致性hash算法
日本翻译过来的一篇介绍memcached的文章
memcached比较全面的文档大全
分享封装好的面向JAVA的memcached客户端操作类


4、 Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。ps:摘录自中文社区官网redis简介。

4.1 安装,启动和关闭
4.1.1 redis安装

下载源码包,下载地址:http://www.redis.cn/,并解压

 tar -zxvf redis-3.0.7.tar.gz
 cd redis-3.0.7
 make #编译,从Makefile中读取指令,然后编译
 make install   #安装,从Makefile中读取指令,安装到指定的位置
 make clear #删除临时文件
4.1.2 启动redis和关闭redis

检测redis是否启动:

redis-cli ping  #如果出现PONG则表示连接成功
ps -aux | grep redis  #查看后台进程是否存在
netstat -lntp | grep 6379   #查看默认端口6379是否在监听

redis启动方式:

  • 直接启动
cd redis-3.0.7
redis-server &  #启动redis,&表示后台执行
  • 通过配置文件redis.conf启动,推荐使用此方式启动
    在redis跟目录下找到redis.conf文件
#修改daemonize为yes,即默认以后台程序方式运行(还记得前面手动使用&号强制后台运行吗)。
daemonize no
#可修改默认监听端口
port 6379
#修改生成默认日志文件位置
logfile "/home/futeng/logs/redis.log"
#配置持久化文件存放位置
dir /home/futeng/data/redisData

启动时指定配置文件:

redis-server redis.conf  #指定配置文件目录
#如果修改了端口,使用redis-cli客户端连接时也要指定相应的端口
redis-cli -p 6380

redis关闭

redis-cli shutdown 
kill -9 PID #找到redi进程执行kill
4.2 redis5种数据类型

redis中的数据类型都不能再嵌套其他的数据类型,字段值只能是字符串。redis命令不区分大小小。

4.2.1 字符串(strings)

字符串类型是最基本的数据类型。下列是基本的常用命令。

① keys,exist,del,type,flushdb,flushall
keys pattern 列出符合条件的key,pattern支持glob风格通配符格式:
符号 含义
? 匹配一个字符
* 匹配任意个字符
[] 匹配括号中的任一字符,可以使用-表示范围
\x 匹配字符x,用于转义符号,如”\?”匹配?
keys需要遍历redis中所有的key,如果key非常多使用keys会影响性能。
exist key判断一个键是否存在,存在返回1,否则返回0
del key [key ...]删除一个或多个键,返回值是删除的键的个数
type key获取键值的数据类型
flushdb删除本库中所有的key
flushall删除所有库中的key

② set,get:设置key值和获取key值

#设置键值
127.0.0.1:6379> set key value
#获取键的值
127.0.0.1:6379> get key

③ 键值增减命令incr,decr
如果存储的字符串类型是整数形式,则通过incr命令可以使键值递增。利用incr命令可以得到类似于数据库中自动递增的唯一主键的值。decr与incr用法相同,是让键值自减。

127.0.0.1:6379> incr num   #如果键不存在,则自动创建,值默认为0,自增后为1
127.0.0.1:6379> decr num #如果键不存在,则自动创建,值默认为0,自减后为-1

类似命令扩展:
incrby key increment 递增一个给定的整数
decrby key decrement 递减一个给定的整数
incrbyfloat key increment 递增一个给定的浮点数
hincrby key field increment 将hash中指定域的值增加给定的整数
hincrbyfloat key field increment 将hash中指定域的值增加给定的浮点数
注:没有decrbyfloat这样的命令

④ 向尾部追加值append
append key value,向键值的末尾追加value,如果key不存在,则创建:set key value,返回值是追加后字符串的总长度。

127.0.0.1:6379> set key hello
127.0.0.1:6379> append key ' world!'

⑤获取字符串长度strlen
strlen key,返回value的长度,如果key不存在则返回0。

127.0.0.1:6379> set word '你好' #redis接收到UTF-8编码的中文,所以存储的值为"\xe4\xbd\xa0\xe5\xa5\xbd",获取长度时会返回6
OK
127.0.0.1:6379> strlen word
(integer) 6

⑥同时设置/获得多个键值
mget key [key ...]
mset key value [key value ...]
gget/gset和get/set相似,mget/mset可以同时获取和设置多个键的键值

127.0.0.1:6379> mset key1 val1 key2 val2 key3 val3
OK
127.0.0.1:6379> gget key1 key3
1) "val1"
2) "val3"

⑦位操作

setbit key offset #设置指定offset位的bit值
getbit key offset #获取指定offset位的bit值
bitcount key [start] [end] #获取bit值为1的个数
bitop operation destkey key [key ...] #对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键中
#offset从0开始算

如下例子中,bar的3个字母对应的ascii码分别为98、97和114,转换为二进制后分别为1100010、1100001和1110010,一个字节由8个二进制位组成,所以bar的二进制存储结构为:011000100110000101110010

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> getbit foo 2
(integer) 1
127.0.0.1:6379> getbit foo 0
(integer) 0
127.0.0.1:6379> bigcount foo 0 1 #统计前两个字节即ba的bit值为1的个数
(integer) 6
127.0.0.1:6379> setbit foo 6 0
(integer) 1
127.0.0.1:6379> setbit foo 7 1
(integer) 0
127.0.0.1:6379> getfoo
"aar"
4.2.2 散列(hashes)

散列类型相当于java中的map,它保存了多对key-value,而每对key和value都是字符串,一个散列类型最多可以存放 232 -1个字段。常用的命令如下:
① 创建字段和获取值
hset key field value 设置字段
hget key field 获取字段值
hmset key field value [field value ...] 一次性多次设置字段
hmget key field [field ...] 一次性多次获取字段值
hgetall key 获取key所有字段和值
hvals获取key所有的字段值,不含字段

127.0.0.1:6379> hset user:1 name 'chavin'
(integer) 1
127.0.0.1:6379> hmset user:1 pwd '123456' age 23
OK
127.0.0.1:6379> hmget user:1 name pwd
1) "chavin"
2) "123456"
127.0.0.1:6379> hgetall user:1
1) "name"
2) "chavin"
3) "pwd"
4) "123456"
5) "age"
6) "23"
127.0.0.1:6379> hvals user:1
1) "chavin"
2) "123456"
3) "23"

② 查看哈希表 key 中,给定域 field 是否存在。
hexists key field

127.0.0.1:6379> hexists user:1 sex
(integer) 0
127.0.0.1:6379> hexists user:1 pwd
(integer) 
4.2.3 列表(lists)

列表类型(list)用于存储一个有序的字符串列表,列表内部使用的是双向链表实现的,所以向列表两端添加元素的时间复杂度是O(1),获取越接近列表两端的元素的速度越快。一个列表最多可以存放 232 -1个字段,元素可以重复。由于列表的特性,它的应用场景如新鲜事。
① 添加元素
lpush key value [value ...] 向列表添加首元素
rpush key value [value ...] 向列表添加尾元素

127.0.0.1:6379> lpush listkey a
(integer) 1
127.0.0.1:6379> lpush listkey b c d
(integer) 4

② 获取元素值
lpop key移除并返回列表 key 的头元素。
rpop key移除并返回列表 key 的尾元素。
blpop key [key ...] timeoutlpop的阻塞版本,timeout为等待超时时间
brpop key [key ...] timeout

127.0.0.1:6379> del job command request  #确定没有这些key
(integer) 0
127.0.0.1:6379> lpush comand 'cmd' #向command中添加一个元素
(integer) 1
127.0.0.1:6379> lpush request 'req' #向request 中添加一个元素
(integer) 1
127.0.0.1:6379> blpop job command request 1 # job 列表为空,被跳过,紧接着 command 列表的第一个元素被弹出。
1) "request"
2) "req"
阻塞行为:
如果所有给定 key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接,直到等待超时,或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。
超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。

③ lset
lset key index value 将列表key下标为index的元素的值设置为value
④ 获取范围内元素
lrange key start stop

127.0.0.1:6379> lrange listkey 0 1
1) "b"
2) "a"
4.2.4 集合(sets)

Redis 集合(Set)是一个无序的字符串集合. 你可以以O(1)的时间复杂度 (无论集合中有多少元素时间复杂度都是常量)完成添加,删除,以及测试元素是否存在。 Redis 集合拥有令人满意的不允许包含相同成员的属性。多次添加相同的元素,最终在集合里只会有一个元素。 实际上说这些就是意味着在添加元素的时候无须检测元素是否存在。 一个Redis集合的非常有趣的事情是他支持一些服务端的命令从现有的集合出发去进行集合运算,因此你可以在非常短的时间内进行合并(unions), 求交集(intersections),找出不同的元素(differences of sets)。 一个集合最多可以包含 232 - 1 个元素(4294967295, 每个集合超过40一个元素).
① 添加集合元素
sadd key member [member ...]将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。假如 key 不存在,则创建一个只包含 member 元素作成员的集合。

② scard
scard key 返回集合中元素的数量

127.0.0.1:6379> sadd tool pc printer phone
(integer) 3
127.0.0.1:6379> scard tool
(integer) 3

③ sdiff
sdiff key [key ...]返回一个集合的全部成员,该集合是所有给定集合之间的差集

redis> SMEMBERS peter's_movies
1) "bet man"
2) "start war"
3) "2012"

redis> SMEMBERS joe's_movies
1) "hi, lady"
2) "Fast Five"
3) "2012"

redis> SDIFF peter's_movies joe's_movies
1) "bet man"
2) "start war"

④ sdiffstore
sdiffstore destination key [key ...]这个命令的作用和 SDIFF 类似,但它将结果保存到 destination 集合,而不是简单地返回结果集。
⑤ sinter
sinter key [key ...]返回一个集合的全部成员,该集合是所有给定集合的交集。

redis> SMEMBERS group_1
1) "LI LEI"
2) "TOM"
3) "JACK"

redis> SMEMBERS group_2
1) "HAN MEIMEI"
2) "JACK"

redis> SINTER group_1 group_2
1) "JACK"

⑥ sismember
sismember key member 判断 member 元素是否集合 key 的成员。

redis> SMEMBERS joe's_movies
1) "hi, lady"
2) "Fast Five"
3) "2012"

redis> SISMEMBER joe's_movies "bet man"
(integer) 0

redis> SISMEMBER joe's_movies "Fast Five"
(integer) 1

⑦ smember
smembers key 返回集合key中的所有成员


redis> SADD language Ruby Python Clojure
(integer) 3

redis> SMEMBERS language
1) "Python"
2) "Ruby"
3) "Clojure"

⑧smove
smove source dest 将 member 元素从 source 集合移动到 destination 集合。smove 是原子性操作。

redis> SMEMBERS songs
1) "Billie Jean"
2) "Believe Me"

redis> SMEMBERS my_songs
(empty list or set)

redis> SMOVE songs my_songs "Believe Me"
(integer) 1

redis> SMEMBERS songs
1) "Billie Jean"

redis> SMEMBERS my_songs
1) "Believe Me"
4.2.5 有序集合(sorted sets)

有序集合类型与集合类型的区别就是他是有序的。有序集合是在集合的基础上为每一个元素关联一个分数,这就让有序集合不仅支持插入,删除,判断元素是否存在等操作外,还支持获取分数最高/最低的前N个元素。有序集合中的每个元素是不同的,但是分数却可以相同。有序集合使用散列表和跳跃表实现,即使读取位于中间部分的数据也很快,时间复杂度为O(log(N)),有序集合比列表更费内存。
命令与set集合类似,参考set集合命令。

4.3 事务

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令原子性地执行。

redis> MULTI            # 标记事务开始
OK

redis> INCR user_id     # 多条命令按顺序入队
QUEUED

redis> INCR user_id
QUEUED

redis> INCR user_id
QUEUED

redis> PING
QUEUED

redis> EXEC             # 执行
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) PONG

Redis还提供了一个Watch功能,你可以对一个key进行Watch,然后再执行Transactions,在这过程中,如果这个Watched的值进行了修改,那么这个Transactions会发现并拒绝执行。
Session 1
(1)第1步

    redis 127.0.0.1:6379> get age  
    "10"  
    redis 127.0.0.1:6379> watch age  
    OK  
    redis 127.0.0.1:6379> multi  
    OK  

Session 2
(2)第2步

    redis 127.0.0.1:6379> set age 30  
    OK  
    redis 127.0.0.1:6379> get age  
    "30"  

Session 1
(3)第3步

    redis 127.0.0.1:6379> set age 20  
    QUEUED  
    redis 127.0.0.1:6379> exec  
    (nil)  
    redis 127.0.0.1:6379> get age  
    "30"  

第一步,Session 1 还没有来得及对age的值进行修改
第二步,Session 2 已经将age的值设为30
第三步,Session 1 希望将age的值设为20,但结果一执行返回是nil,说明执行失败,之后我们再取一下age的值是30,这是由于Session 1中对age加了乐观锁导致的。

参考:
redis详细命令
redis中文社区官网
Redis入门指南.pdf


缓存也没有实际应用,都是自己学习的,如有错误处,还请指出,一起讨论。
另附上所有的相关资料。
源代码下载地址:http://download.csdn.net/detail/zwy0123/9448230
,共有4个项目。
文档类下载地址:http://download.csdn.net/detail/zwy0123/9448235

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值