一场雨

缓存部分小结

接触过的缓存:redis ,memcached ,ehcache,java 内存缓存

这篇笔记以 redis为主,总结了自己项目中的代码,以及其他人博客中的实例,以及《redis的设计及实现》这本书。喜欢笔记的形式,可以提醒一下自己部分知识点,以及知识体系,这样。

redis

1.启动及检测及部分客户端命令

redis 启动: ./redis-server  ../redis.conf (修改一下配置文件)
客户端: ./redis-cli
是否启动: ps -ef |grep redis
连接测试: ping
服务器统计信息: info
性能测试: redis-benchmark -n 1000  1000个请求

String : SET keyname "hello world"  GET keyname
HASH: HMSET keyname propOne "value one" propTwo 200
LIST: LPUSH keyList "one" ,LPUSH keyList "two"
SET: SADD keySet "one" , SADD keySet "two"
有序集合: ZADD runoobkey 1 redis
发布,订阅: SUBSCRIBE redisChat ,PUBLISH redisChat "message"

参考:

菜鸟教程 :http://www.runoob.com/redis/redis-java.html

redis命令 :http://www.redis.net.cn/order/

2.java中jedis的使用

地址绑定的问题: bind 127.0.0.1 这边可以注释掉

protected-mode no 将保护关掉

RedisConfig:

/**
 * 初始化Redis连接池
 */
static {
    try {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxWaitMillis(MAX_WAIT);
        config.setMaxTotal(MAX_ACTIVE);
        config.setMaxIdle(MAX_IDLE);
        config.setTestOnBorrow(TEST_ON_BORROW);
        jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 获取Jedis实例
 * @return
 */
public synchronized static Jedis getJedis() {
    try {
        if (jedisPool != null) {
            Jedis resource = jedisPool.getResource();
            return resource;
        } else {
            return null;
        }
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

TestRedis:

public class TestRedis {

private static Jedis jedis;

/**
 * redis存储字符串
 */

public void testString() {
    //-----添加数据----------  
    jedis.set("name","chenmanman");  
    System.out.println(jedis.get("name"));

    jedis.append("name", " is my name"); //拼接
    System.out.println(jedis.get("name"));

    jedis.del("name");  //删除某个键
    System.out.println(jedis.get("name"));
    //设置多个键值对
    jedis.mset("name","chenmanman","age","26","qq","14*******5");
    jedis.incr("age"); //进行加1操作
    System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq"));

}

/**
 * redis操作Map
 */

public void testMap() {
    //-----添加数据----------  
    Map<String, String> map = new HashMap<String, String>();
    map.put("name", "chenmanman");
    map.put("age", "27");
    map.put("qq", "1491563275");
    jedis.hmset("user",map);
    //取出user中的name,执行结果:[minxr]-->注意结果是一个泛型的List  
    //第一个参数是存入redis中map对象的key,后面跟的是放入map中的对象的key,后面的key可以跟多个,是可变参数  
      // List<String> rsmap = jedis.hmget("user", "name", "age", "qq");
    List<String> rsmap = jedis.hmget("user", "name", "age");
    System.out.println(rsmap);  

    //删除map中的某个键值  
    jedis.hdel("user","age");//删除的是age
    System.out.println(jedis.hmget("user", "age")); //因为删除了,所以返回的是null  
    System.out.println(jedis.hlen("user")); //返回key为user的键中存放的值的个数2
    System.out.println(jedis.exists("user"));//是否存在key为user的记录 返回true  
    System.out.println(jedis.hkeys("user"));//返回map对象中的所有key  
    System.out.println(jedis.hvals("user"));//返回map对象中的所有value

    Iterator<String> iter=jedis.hkeys("user").iterator();  
    while (iter.hasNext()){  
        String key = iter.next();  
        System.out.println(key+":"+jedis.hmget("user",key));  
    }  
}

/**
 * jedis操作List
 */  

public void testList(){  
    //开始前,先移除所有的内容  
    jedis.del("java framework");  
    System.out.println(jedis.lrange("java framework",0,-1));  

    //  Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key.
    //  If the key does not exist an empty list is created just before the append operation.
    //  If the key exists but is not a List an error is returned.

    //先向key java framework中存放三条数据  
    jedis.lpush("java framework","spring");  
    jedis.lpush("java framework","struts");  
    jedis.lpush("java framework","hibernate");
    jedis.lpush("java framework","hibernate");  //可以有重复的
    // 再取出所有数据jedis.lrange是按范围取出,  
    // 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有  
    System.out.println(jedis.lrange("java framework",0,-1));  

    jedis.del("java framework");
    jedis.rpush("java framework","spring");  
    jedis.rpush("java framework","struts");  
    jedis.rpush("java framework","hibernate");
    System.out.println(jedis.lrange("java framework",0,-1));
}  

/**
 * jedis操作Set
 */  

public void testSet(){  
    //添加  
    jedis.sadd("user","chenmanman");  
    jedis.sadd("user","chenxiaoman");  
    jedis.sadd("user","kyzeng");  
    jedis.sadd("user","chenxiaoxiao");
    //移除noname  
    jedis.srem("user","chenxiaoxiao");  
    System.out.println(jedis.smembers("user"));//获取所有加入的value  
    System.out.println(jedis.sismember("user", "chenxiaoxiao"));//判断 who 是否是user集合的元素  
    System.out.println(jedis.srandmember("user"));//随机的
    System.out.println(jedis.scard("user"));//返回集合的元素个数  
}  


public void test() throws InterruptedException {  
    //jedis 排序  
    //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)  
    jedis.del("a");//先清除数据,再加入数据进行测试  
    jedis.rpush("a", "1");  
    jedis.lpush("a","6");  
    jedis.lpush("a","3");  
    jedis.lpush("a","9");  
    System.out.println(jedis.lrange("a",0,-1));// [9, 3, 6, 1]  
    System.out.println(jedis.sort("a")); //[1, 3, 6, 9]  //输入排序后结果  
    System.out.println(jedis.lrange("a",0,-1));  
}  

public void testRedisPool() {
    RedisConfig.getJedis().set("newname", "中文测试");
    System.out.println(RedisConfig.getJedis().get("newname"));
}



public static void main(String[] args) {

    //这边需要注意的是 数据的清空,FLUSHALL cli端的操作

    //或者是 jedis.del("a");//先清除数据,再加入数据进行测试  

    RedisConfig rc =new RedisConfig();

    jedis  =  RedisConfig.getJedis();

      // jedis.set("name","chenmanman");

    // System.out.println(jedis.get("name"));//执行结果:xinxin  

    TestRedis tr =new TestRedis();

    // tr.testString();

    // tr.testMap();

    // tr.testList();

    // tr.testSet();

    try {
        tr.test();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
  }
}

参考文章:

Java中使用Jedis操作Redis

jedis 的API:http://tool.oschina.net/uploads/apidocs/

3.redis的设计及实现

1.数据结构部分

a.字符串

free :未使用的空间
len :保存的字符串的长度
buf :字符串

1).常数复杂度获取字符串的长度。 len
2).杜绝缓冲区溢出 检查free区的大小
3).减少修改字符串长度时所需的内存重分配的次数 空间预分配:多分配一部分 惰性空间释放:使用free接收

b.链表

head : 表头指针
tail : 表尾指针
len : 链表长度计数器
dup,free,match 成员则是用于实现多态链表所需的类型特定函数(复制,释放,比较)

c.字典

当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组指定的索引上面。

Alt text

当键被分配到同一个索引上的时候,就是键冲突,使用链表解决。

Alt text

rehash操作:实现哈希表的拓展和收缩

拓展,迁移之后,将ht[1]设置为ht[0],然后为ht[1]分配一个空白哈希表

Alt text

渐进式 rehash

d.跳跃表

header: 指向跳跃表的表头节点

tail: 指向跳跃表的表尾节点

level: 层数最大的节点的层数

length: 包含的节点数量(表头节点不算)

层: 前进指针 和 后退指针 跨度:指向和当前的节点间的距离 计算排位

分值: 节点中保存分值 跳跃表中,节点按照各自所保存的分值从小到大排列

成员对象: 保存的成员对象

Alt text

整数集合 intset

encoding : 底层整数集合的实现类型

length : 长度

contents : 数据从小到大排列

升级的三步骤

1.根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间

2.将底层的所有元素换成与新元素相同的类型,并将转换后的新元素放到正确的位置,保持有序性不变

3.将新元素添加到底层数组里面

压缩列表

Alt text

压缩列表节点的构成 : previous_entry_length (前一个节点的长度,可以根据当前节点计算出前一个节点的起始位置) ,encoding (数据类型及范围长度), content

连锁更新

对象

对象 : type属性, 编码属性, ptr属性

类型 : 对象的类型 value 保存的数据的类型

编码 : 对象使用了什么底层数据结构作为对象的底层实现

Alt text

单机数据库的实现

Alt text

切换数据库: 客户端执行 select 2

数据库数据的添加

Alt text

过期时间

键的生存时间和过期时间 EXPIRE PEXPIRE设置过期时间 TTL PLLT返回剩余时间

redisDb 结构的expires 字典保存了数据库中的所有的过期时间,即过期字典。(key - value结构)

Alt text

PERSIST 解除键和值在过期字典中的关联。

过期键删除策略:定时删除(定时器),惰性删除(取用时检测并删除,不用的过期键依然保存),定期删除

save 和 bgsave 生成rdb文件时只会保存未过期的数据。rdb文件载入时,只载入未过期的(主服务器模式);rdb文件载入时,全部载入(从服务器模式);

AOF文件写入
当服务器以AOF持久化模式运行时,如果数据库的某个键已过期,但是它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。
当过期键被惰性删除或者被定期删除之后,程序会向AOF文件追加一条DEL命令,来显式的记录该键已被删除。

AOF的重写 和生成RDB文件类似,过期不写。

数据库通知:可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库命令的执行情况。

SUBSCRIBE keyevent@0 :del (订阅0号库中所有的del命令)

SUBSCRIBE keyspace@0 :message (订阅0号库中对message的操作)

RDB持久化

RDB持久化:save 和 bgsave

save 命令会阻塞Redis服务进程

bgsave 会派生出一个子进程来负责创建RDB文件 dump.rdb文件

RDB文件的载入工作是在服务器启动时自动执行的,所以redis并没有专门用于载入RDB文件的命令

AOF文件的更新频率通常比RDB文件的更新频率更高,所以:如果服务器开启了AOF功能,那么服务器会优先使用AOF文件来还原数据库状态。只有AOF持久化功能关闭,服务器才会使用RDB文件来还原数据库状态

服务器载入RDB文件时,会一直处于阻塞状态,直到载入工作结束为止。

自动间隔性保存 save 300 10 300秒内对数据库至少进行10次修改,就执行save操作

dirty , lastsave (计数,最新的save的时间)

RDB文件的结构:REDIS(开头),db_version(RDB版本号),databases(0个或多个数据库),EOF(正文部分的结束),check_num(校验位)

od -c dump.rdb 打印RDB文件

dump.rdb 生成文件是在src下,做文件恢复的时候 ,将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。

bgsave 和save 生成的文件在同一个位置,都叫(dump.rdb)

AOF持久化

因为AOF持久化是通过保存被执行的写命令来记录数据库状态的。

AOF文件的写入与同步: 数据先在aof_buf缓冲区,然后再同步到AOF文件中去 (always, everysec, no)

AOF文件的载入与数据的还原: 将之前的命令重新执行了一遍

AOF文件重写: 直接读取数据库状态

子进程处理数据的时候,主线程还在数据处理,会造成数据库状态不一致的情况

在AOF重写期间,服务器进程执行三个操作:执行客户端发过来的操作;将执行后的命令追加到aof缓冲区;将执行过的命令追加到aof重写缓冲区;

重写结束之后,将aof重写缓冲区的内容写入新的aof文件中,用新的aof文件替换老的AOF文件。

命令请求会保存到aof缓存区里面,之后再定期写入并同步到AOF文件中

服务器只要载入并重新执行保存在aof文件中的命令,就可以还原数据库本来的状态

事件

文件事件:服务器对套接字操作的抽象

Alt text

I/O多路复用程序通过队列向文件事件分派器传送套接字,并根据套接字产生的事件的类型,调用相应的事件处理器。

时间事件:服务器对定时操作的抽象

定时事件,周期性事件

当时间事件执行器运行的时候,他必须遍历链表中的所有的时间事件,这样才能确保服务器中所有已到达的时间事件都会被处理。

链表并不是按照时间属性的大小排序的

客户端

Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:

1.首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。

2.然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法

3.然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送

服务器


发布和订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息

SUBSCRIBE testc :订阅testc

PUBLISH testc “message_one” :发布

UNSUBSCRIBE testc : 退订

Alt text

事务

一个事务从开始到执行经历的三个阶段:开始事务,命令入队,执行事务

memcached (后端)

brew install memcached

指定端口启动 memcached -p 8000

后台启动 memcached -d -p 8000

memcache (前端)

java-memcached :jar包的生成 maven 打包

测试代码 :

package testmodel;

import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;

public class MyCache {  

  public static void main(String[] args) {  //这个是memcache的一个测试的demo,之后可以考虑的是用aop去实现它
    MemCachedClient client=new MemCachedClient();  
    String [] addr ={"127.0.0.1:8000"};  
    Integer [] weights = {3};  
    SockIOPool pool = SockIOPool.getInstance();  
    pool.setServers(addr);  
    pool.setWeights(weights);  
    pool.setInitConn(5);  
    pool.setMinConn(5);  
    pool.setMaxConn(200);  
    pool.setMaxIdle(1000*30*30);  
    pool.setMaintSleep(30);  
    pool.setNagle(false);  
    pool.setSocketTO(30);  
    pool.setSocketConnectTO(0);  
    pool.initialize();  

    //String [] s  =pool.getServers();  
    //client.setCompressEnable(true);  
    //client.setCompressThreshold(1000*1024);  

    //将数据放入缓存  
    TestBean bean=new TestBean();  
    bean.setName("name1");  
    bean.setAge(25);  
    client.add("bean1", bean);  

    //获取缓存数据  
    TestBean beanClient=(TestBean)client.get("bean1");  
    System.out.println(beanClient.getName());  
  }  
}

ehcache

ehcache.xml:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd">

      <diskStore path="java.io.tmpdir"/>

      <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
      <cache name="serviceCache"
        maxElementsInMemory="20000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        memoryStoreEvictionPolicy="LRU" />
</ehcache>

ehcache.xsd 配置略

测试代码:用的切面

@Around("@annotation(cacheable)")
public Object cacheable(final ProceedingJoinPoint pjp, final Cacheable cacheable) throws Throwable {

  if(cacheable.cacheOperation() == CacheOperation.NOP) {
    return pjp.proceed();
  }
  String group = cacheable.group().getName(),
      cacheKey = getCacheKey(pjp);
  Cache cache = getCache(group);
  Object result = null;
  if(cacheable.cacheOperation() == CacheOperation.CACHING) {
        Element element = cache.get(cacheKey);
        if(element == null) {
          result = pjp.proceed();
            if(result == null || result instanceof Serializable) {
              element = new Element(cacheKey, (Serializable)result);
              LOG.info("正在缓存{0}对象。", cacheKey);
              cache.put(element);
            }else {
              LOG.warn("{0}指定方法返回对象{1}不能被序列化,不能缓存。", cacheKey, result);
            }
        }else {
          LOG.info("从缓存中获取{0}对象。", cacheKey);
          result = element.getObjectValue();
        }
  }else if(cacheable.cacheOperation() == CacheOperation.FLUSH_CACHE) {
        result = pjp.proceed();
        for(Class<? extends Entity> clazz : cacheable.flushGroup()) {
          String groupName = clazz.getName();
          if(!groupName.equals(group)) {
            LOG.info("删除缓存组{0}所有的缓存对象。", groupName);
            getCache(group).removeAll();
          }

        }
        LOG.info("删除缓存组{0}所有的缓存对象。", group);
        cache.removeAll();
  }
  return result;
}

java 内存缓存 :

测试代码:

private CacheManager cacheManager = MapCacheManager.getInstance();

Cache cache = cacheManager.nativeCache(“failPushContent”);

实现类:

public class MapCacheManager implements CacheManager{

    private ConcurrentHashMap<String, MapCache> cacheMap = new ConcurrentHashMap<String, MapCache>(0);

    private static Logger logger = LoggerFactory.getLogger(MapCacheManager.class);

    private static MapCacheManager manager;

    private static final long STTLONG = 1000L*60*60*24*365;

    private MapCacheManager(){

    }

    public static MapCacheManager getInstance(){
        synchronized (MapCacheManager.class) {
            if(null == manager)
                manager = new MapCacheManager();
            return manager;
        }
    }

    @Override
    public List<String> names() throws CacheException {
        return new ArrayList<String>(cacheMap.keySet());
    }

    @Override
    public Cache nativeCache(String cacheName) throws CacheException {
        return nativeCache(cacheName,STTLONG);
    }

    @Override
    public Cache nativeCache(String cacheName,long expiredTime) throws CacheException {
        synchronized (cacheMap){
            if(!cacheMap.containsKey(cacheName)){
                cacheMap.put(cacheName, new MapCache(cacheName));

                logger.info("********nativeCache******: no containsKey");

            }
            logger.info("********nativeCache******: out");

            MapCache mapCache = cacheMap.get(cacheName);
            mapCache.setExpiredTime(expiredTime);
            return mapCache;
        }
    }

    @Override
    public void clear() throws CacheException {
        cacheMap.clear();
    }

}

MapCache:

public class MapCache implements Cache{

    private static final  long EXPIREDTIME = 1000L*60*60*24*365;

    private static final int param1 = 10;

    private final ConcurrentHashMap<Object, Object> store;

    private long expiredTime;

    public void setExpiredTime(long expiredTime) {
        this.expiredTime = expiredTime;
    }

    private final String name;

    private CacheEntity cacheEntity;

    private String encryptKey;

    /**
     * @param name 缓存名称
     */
    public MapCache(String name){
        this(name,EXPIREDTIME,new ConcurrentHashMap<Object,Object>(param1));
    }

    /**
     * @param name 缓存名称
     * @param expiredTime 缓存过期时间
     */
    public MapCache(String name,long expiredTime){
        this(name,expiredTime,new ConcurrentHashMap<Object,Object>(param1));
    }

    /**
     * @param name 缓存名称
     * @param expiredTime 缓存过期时间
     * @param store 缓存存储器
     */
    public MapCache(String name, long expiredTime, ConcurrentHashMap<Object, Object> store){
        this.store = store;
        this.expiredTime = expiredTime;
        this.name = name;
    }

    /**
     * 返回当前缓存姓名
     */
    public String name(){
        return this.name;
    }

    /**
     * 添加对象到缓存中
     */
    @Override
    public void put(Object value,Object... key) throws CacheException {
        cacheEntity = new CacheEntity(value);
        encryptKey = generateKey(key);
        store.put(encryptKey, cacheEntity);
    }

    /**
     * 获得指定键的缓存
     */
    @Override
    public Object get(Object... key) throws CacheException {
        encryptKey = generateKey(key);
        cacheEntity = (CacheEntity)store.get(encryptKey);

        if(cacheEntity == null){

            return "noValue";

        }

        if(System.currentTimeMillis() - cacheEntity.getTimeInMillIs() > expiredTime){
            store.remove(encryptKey);
            return "noValue";
        }
        return cacheEntity.getCacheObject();
    }

    @Override
    public Object getOut(Object... key) throws CacheException {

        encryptKey = generateKey(key);
        cacheEntity = (CacheEntity)store.get(encryptKey);

        if(cacheEntity == null){

            return "noValue";

        }

        if(System.currentTimeMillis() - cacheEntity.getTimeInMillIs() > expiredTime){
            store.remove(encryptKey);
            return "noValue";
        }

        Object re=    cacheEntity.getCacheObject();

        store.remove(encryptKey);//清空

        return re;
    }

    @Override
    public void remove(Object... key) throws CacheException {
        encryptKey = generateKey(key);
        store.remove(encryptKey);
    }

    @Override
    public boolean containsKey(Object... key) throws CacheException {
        return store.containsKey(generateKey(key));
    }

    /**
     * 生成缓存加密键
     * @param key
     * @return
     */
    private String generateKey(Object... key){
        Assert.isTrue(null != key, "缓存键值不可为空");
        StringBuilder result = new StringBuilder();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(JSON.toJSONString(new KeyEntity(key)).getBytes());
            for (byte b : bytes) {
                String hex = Integer.toHexString(b & 0xFF);
                if (hex.length() == 1)
                    result.append("0");
                result.append(hex);
            }
            return result.toString();

        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
        return result.toString();
    }

    class KeyEntity{

        private List<Object> keys = new ArrayList<Object>();

        KeyEntity(Object... keys){
            this.keys.addAll(Arrays.asList(keys));
        }

        public List<Object> getKeys() {
            return keys;
        }

        public void setKeys(List<Object> keys) {
            this.keys = keys;
        }

    }

}