Redis二次小结(2020-11-29)

一、Redis是单线程的(Maven仓库更新慢)

安装

Redis的安装不难,w10下就是下载下来,然后解压,在命令窗口cd到安装路径下,启动: redis-server.exe redis.windows.conf;在环境变量的path下把Redis的安装目录加进去。之后在cmd的命令窗口下启动:redis-server。另开一个命令窗口,链接Redis: redis-cli.exe -h 127.0.0.1 -p6379

键的常用命令

设置一个key值为value                                    set key value                                             
获得key值得value                                           get key
查看所有键                                                      keys * 
判断某key否存在                                             exists key
从当前库移除                                                   move key db                                                        
给key设置过期时间(秒)                                   expire key 
查看还有多少秒过期(-1永不过期,-2已过期)     ttl key
查看key是什么类型                                          type key 

二、五大数据类型

  1. String型:String 是redis中最基本的数据类型,二进制安全的,即它可以包含任何数据,如序列化的对象、jpg图片,大小上限是512M。

  2. Hash型(存储消耗高于字符串):键值对集合,适合存储对象,类似 Java的Map<String,Object>。

  3. List型: 字符串链表,按插入顺序排序,可重复。

  4. Set: Set 无顺序,不能重复。

  5. SortedSet(zset): 不重复,有顺序。适合做排行榜,排序需要一个分数属性。

三、Redis持久化

RDB持久化

将Reids在内存中的数据库记录定时 dump到磁盘上的RDB持久化.在指定的时间间隔内将内存中的数据集快照写入磁盘,是fork一个子进程,先将数据集写入临时文件,写入成功后,替换之前的文件,用二进制压缩存储。
优势
1.整个Redis数据库将只包含一个文件,便于文件备份,易恢复记录。
2.性能最大化。开始持久化时只fork出子进程,由子进程完成这些持久化的工作,避免服务进程执行IO操作。
3.数据集很大RDB的启动效率高。
劣势
1.持久化之前宕机,没写入磁盘的数据会丢失,无法保证数据的高可用性,即最大限度的避免数据丢失。
2.通过fork子进程来协助完成数据持久化工作的,数据集较大,可能会导致整个服务器停止服务几百毫秒,甚至更长时间。

RDB持久化常用配置

通过配置文件修改快照的频率,打开6379.conf文件搜save。

# 时间策略
save 900 1              #在900秒,1个以上key发生变化,则dump内存快照。
save 300 10            #在300秒,10个以上key发生变化,则dump内存快照。
save 60 10000        #在60秒,10000个以上key发生变化,则dump内存快照。
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes

AOF持久化

是将Reids的操作日志以追加的方式写入文件。以日志的形式记录服务器所处理的每一个写、删除操作.查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
优势
1.更高的数据安全性。Redis中提供每秒同步、每修改同步、不同步三种策略。每修改同步,可视为同步持久化。
2.对日志文件的写入操作采用的是append模式,出现宕机,不会破坏日志文件中已经存在的内容。
3.如果日志过大,自启rewrite机制。即Redis以append模式将修改数据写入文件中,同时Redis会建一个新文件记录此间修改命令。进行rewrite切换时可更好的保证数据安全性。
4. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。
劣势
1.AOF占空间大,AOF的恢复速度慢。
2.AOF在运行效率会慢于RDB。

AOF持久化常用配置

# 是否开启aof
appendonly yes
# 文件名称
appendfilename "appendonly.aof"

# 同步方式
appendfsync always      #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec    #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no          #从不同步,高效但是数据不会被持久化。

# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof时如果有错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes

四、Redis事务

事务顺序
1.事务开始
2.命令入队
3.事务执行

事务命令(将多个命令打包, 然后一次性、按顺序地执行)
MULTI、DISCARD、EXEC、WATCH 

测试

> MULTI                 #开始事务
OK

> SET name "Jun_South"  #命令入队
QUEUED

> GET name              #命令入队
QUEUED

> SADD tag "Jun" "South" "Liu"    #命令入队
QUEUED

> SMEMBERS tag          #命令入队
QUEUED

> EXEC                  #执行
1) OK
2) "Jun_South"
3) (integer) 3
4) 1) "Jun"
   2) "South"
   3) "Liu"

说明:

DISCARD取消事务,清空整个事务队列,从事务状态调整回非事务状态,返回OK说明事务已被取消。Redis的事务不可嵌套,当客户端已经处于事务状态,再发送 MULTI 时,服务器返回一个错误,不会造成事务失败,
继续等待其他命令的入队(WATCH与MULTI一样)。
WATCH 在事务开始之前监视任意数量的键:当EXEC命令执行事务时,被监视的键被其他客户端改了,那么整个事务不再执行,返回失败。

例如

> WATCH name
OK

> MULTI
OK

> SET name peter
QUEUED

> EXEC
(nil)

五、主从复制

开启主从复制

主从复制的开启主节点啥也不做。
从节点开启主从复制,有3种方式
1.配置文件:在从服务器的配置文件中加入: slaveof <masterip> <masterport>(slaveof 主端口号)
2.启动命令:redis-server启动命令后加入 --slaveof <masterip> <masterport>(slaveof 主端口号)
3.客户端命令:Redis服务器启动后,直接通过客户端执行命令:slaveof <masterip> <masterport>(slaveof 主端口号),则该Redis实例成为从节点。
通过info replication 命令可以看到复制的一些信息。

断开主从复制

命令:slaveof no one

主从复制过程

连接建立(即准备)、数据同步、命令传播。

六、Java中的Jedis

引入依赖或jar包

     <!-- 引入Redis -->
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>2.9.0</version>
	</dependency>

JedisPool池使用

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisPool {

    private static JedisPool pool;//jedis连接池

    private static int maxTotal = 30;//最大连接数

    private static int maxIdle = 10;//最大空闲连接数

    private static int minIdle = 5;//最小空闲连接数

    private static boolean testOnBorrow = true;//在取连接时测试连接的可用性

    private static boolean testOnReturn = false;//再还连接时不测试连接的可用性

    static {
        initPool();//初始化连接池
    }

    public static Jedis getJedis(){
        return pool.getResource();
    }

    public static void close(Jedis jedis){
        jedis.close();
    }

    private static void initPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        config.setBlockWhenExhausted(true);
        pool = new JedisPool(config, "127.0.0.1", 6379, 5000);
    }
}

 RedisPoolUtil

import redis.clients.jedis.Jedis;

public class RedisPoolUtil {

    private RedisPoolUtil(){}

    private static RedisPool redisPool;

    public static String get(String key){
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.get(key);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long setnx(String key, String value){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.setnx(key, value);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }
    
    public static String getSet(String key, String value){
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.getSet(key, value);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }
    
    public static Long expire(String key, int seconds){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.expire(key, seconds);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }
    
    public static Long del(String key){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.del(key);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }
    
}

 Redis的延时队列Demo

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Type;
import java.util.Set;
import java.util.UUID;

//延时队列
public class RedisDelayingQueue<T> {

    static class TaskItem<T>{
        public String id;
        public T msg;
    }

    private Type TaskType = new TypeReference<TaskItem<T>>(){}.getType();

    private Jedis jedis;
    private String queueKey;

    public RedisDelayingQueue(Jedis jedis, String queueKey) {
        this.jedis = jedis;
        this.queueKey = queueKey;
    }


    public void delay(T msg){
        TaskItem<T> task = new TaskItem<>();
        //分配id
        task.id = UUID.randomUUID().toString();
        task.msg = msg;
        //fastjson序列化
        String s = JSON.toJSONString(task);
        //塞入延时队列,5秒后再试。
        jedis.zadd(queueKey,System.currentTimeMillis()+5000,s);
    }

    public void loop(){
       while (!Thread.interrupted()){
           //只取一条
           Set<String> values = jedis.zrangeByScore(queueKey,0,System.currentTimeMillis(),0,1);
           if(values.isEmpty()){
              try {
                  //延时0.5秒
                  Thread.sleep(500);
              }catch (InterruptedException e){
                  break;
              }
              continue;
           }
           String s = values.iterator().next();
           //抢到了
           if(jedis.zrem(queueKey,s)>0){
               // fastjson 反序列化
               TaskItem<T> task = JSON.parseObject(s,TaskType);
               this.handleMsg(task.msg);
           }
       }
    }

    public void handleMsg(T msg){
        System.out.println("msg: "+msg);
    }

    public static void main(String[] args) {
        Jedis jedis = RedisPool.getJedis();
        RedisDelayingQueue<String> queue = new RedisDelayingQueue<>(jedis,"South-Demo");
        Thread producer = new Thread(){
            public void run(){
                for(int i=0;i<15;i++){
                    queue.delay("codehole"+i);
                }
            }
        };
        Thread consumer = new Thread(){
            public void run(){
              queue.loop();
            }
        };
        producer.start();
        consumer.start();
        try{
            producer.join();
            Thread.sleep(3000);
            consumer.interrupt();
            consumer.join();
        }catch (InterruptedException e){

        }
    }

}

 简单的Redis限流Demo

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

import java.io.IOException;

//简单的Redis限流
public class SimpleRateLimiter {
    private Jedis jedis;

    public SimpleRateLimiter(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean isActionAllowed(String userId,String actionKey,int period,int maxCount)throws IOException{
        String key = String.format("hist:%s:%s",userId,actionKey);
        long nowTs = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();
        pipe.zadd(key,nowTs,""+nowTs);
        pipe.zremrangeByScore(key,0,nowTs-period*1000);
        Response<Long> count = pipe.zcard(key);
        pipe.expire(key,period-1);
        pipe.exec();
        pipe.close();
        return count.get()<=maxCount;
    }

    public static void main(String[] args) throws Exception{

        Jedis jedis = RedisPool.getJedis();
        SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
        for(int i=0;i<20;i++){
            System.out.println(limiter.isActionAllowed("JunSouth","reply",60,5));
        }

    }

}

Redis分布式锁Demo

import redis.clients.jedis.Jedis;
import java.util.Collections;

public class Test01 {

    public static void main(String[] args) {
       /*
        setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。
        get(key):获得key对应的value值,若不存在则返回nil。
        getset(key, value):先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value。
        expire(key, seconds):设置key-value的有效期为seconds秒。
        */

        Jedis jedis = RedisPool.getJedis();
        String lockKey = "myLock";
        String requestId = "myRequestId";

        boolean flag = tryGetDistributedLock(jedis,lockKey,requestId,300);
        System.out.println("flag: "+flag);
        String lockKey2= "myLock2";
        String requestId2 = "myRequestId2";

        boolean flag2 = releaseDistributedLock(jedis,lockKey,requestId);
        System.out.println("flag2: "+flag2);
    }

    // 错误用法
    public static boolean lock(String lockName){//lockName可以为共享变量名,也可以为方法名,主要是用于模拟锁信息
        System.out.println(Thread.currentThread() + "开始尝试加锁!");
        Long result = RedisPoolUtil.setnx(lockName, String.valueOf(System.currentTimeMillis() + 5000)); //加锁
        if (result != null && result.intValue() == 1){
            System.out.println(Thread.currentThread() + "加锁成功!");
            RedisPoolUtil.expire(lockName, 5); //设置执行时间
            System.out.println(""+Thread.currentThread() + "执行业务逻辑!");
            RedisPoolUtil.del(lockName);
            return true;
        } else {
            String lockValueA = RedisPoolUtil.get(lockName);
            if (lockValueA != null && Long.parseLong(lockValueA) >= System.currentTimeMillis()){
                String lockValueB = RedisPoolUtil.getSet(lockName, String.valueOf(System.currentTimeMillis() + 5000)); //加锁
                if (lockValueB == null || lockValueB.equals(lockValueA)){
                    System.out.println(Thread.currentThread() + "加锁成功!");
                    RedisPoolUtil.expire(lockName, 5); //设置执行时间
                    System.out.println(Thread.currentThread() + "执行业务逻辑!");
                    RedisPoolUtil.del(lockName);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }


    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis,String lockKey,String requestId,int expireTime) {
        String result = jedis.set(lockKey,requestId,SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME,expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

}

 Redisq其它一些重要的方法

import redis.clients.jedis.Jedis;

public class Test02 {


    public static void main(String[] args) {
    }

    //位图(适用于签到)
    public static void setAndGet(){
       /*
        setbit
        设置特定key对应的比特位的值。
        getbit
        获取特定key对应的比特位的值。
        bitcount
        统计给定key对应的字符串比特位为1的数量。
        */
        Jedis jedis = RedisPool.getJedis();
        System.out.println(jedis.setbit("Jun",1,true));
        System.out.println(jedis.setbit("Jun",2,true));
        System.out.println(jedis.getbit("Jun",1));
        System.out.println(jedis.getbit("Jun",2));
        System.out.println(jedis.getbit("Jun",3));
        //统计
        System.out.println(jedis.bitpos("Jun",true));
        System.out.println(jedis.bitcount("Jun"));
        jedis.bitfield("","","");
    }

    //HyperLogLog(适于统计UV)
    public static void test_HyperLogLog(){
        Jedis jedis = RedisPool.getJedis();
        for(int i=0;i<1000;i++){
            jedis.pfadd("pf_jun","Jun"+i);//内容去重
        }
        System.out.println(jedis.pfcount("pf_jun"));
    }

    // 布隆(4.0开始提供,强大去重功能,适用于过虑用户看过数据。)
    public static void test_Bloom(){
        /**
         得下插件,有点麻烦,没有搞的欲望了!
         */
    }

}

主从复制

//主
Jedis jedis_M = new Jedis("127.0.0.1",6379);
//从
Jedis jedis_S = new Jedis("127.0.0.1",6380);
//主从关联
jedis_S.slaveof("127.0.0.1",6379);
//读写分离
jedis_M.set("name","JunSouth");
String result = jedis_S.get("name");

七、Redis常见问题

1、缓存雪崩

1.设置缓存时用了相同的过期时间,导致某一时刻同时失效,请求全部转发到DB,DB崩了。
2.缓存雪崩问题排查
    在一个较短的时间内,缓存中较多的key集中过期
    此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
    数据库同时接收到大量的请求无法及时处理
    Redis大量请求被积压,开始出现超时现象
    数据库流量激增,数据库崩溃
    重启后仍然面对缓存中无数据可用
    Redis服务器资源被严重占用,Redis服务器崩溃
    Redis集群呈现崩塌,集群瓦解
    应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
    应用服务器,redis,数据库全部重启,效果不理想
3.解决方案
更多的页面静态化处理
构建多级缓存架构 Nginx缓存+redis缓存+ehcache缓存
检测Mysql严重耗时业务进行优化 对数据库的瓶颈排查: 例如超时查询、耗时较高事务等
灾难预警机制
    监控redis服务器性能指标
    CPU占用、CPU使用率
    内存容量
    查询平均响应时间
    线程数
限流、降级 短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问。
数据有效期策略调整
    根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟
    过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
超热数据使用永久key
定期维护(自动+人工) 对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时。

2、缓存预热

1.系统上线初期,将相关的缓存数据直接加载到缓存系统,可避免大量用户直接查询数据库。
2.问题排查
请求数量较高
主从之间数据吞吐量较大,数据同步操作频度较高
3.解决方案
前置准备工作:
日常例行统计数据访问记录,统计访问频度较高的热点数据
利用LRU数据删除策略,构建数据留存队列。如:storm与kafka配合

3、缓存穿透

1.用户查询数据,库中没有,缓存中也没有,导致每次请求直接查库。
2.解决方案
(1).缓存空值:把数据库返回的空值进行缓存,设置较短的过期时间。
(2).采用布隆过滤器BloomFilter: 在缓存之前在加一层BloomFilter,在查询时先查BloomFilter的key是否存在,如果不存在就直接返回,存在再去查询缓存,缓存中没有再去查询数据库.

4、缓存降级

缓存失效或缓存服务挂掉的情况下,也不去访问数据库,直接访问内存部分数据缓存或直接返回默认数据。
这是有损的操作,尽量减少降级对于业务的影响程度。

5、缓存击穿

1、大量的请求同时查一个key时,此时这个key失效,导致大量的请求都打到数据库上面去。
2.问题排查
 Redis中某大访问量的key过期.
 多个数据请求压倒服务器上,均未命中.
 Redis短时间内向数据库发起大大量同一访问.
3.解决方案
(1). 使用互斥锁(mutex key)
只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据。单机可用synchronized或者lock来处理,分布式环境可用分布式锁就(memcache的add,redis的setnx,zookeeper)。
(2).提前使用互斥锁(mutex key)
在value内部设置超时值timeout1,timeout1比实际的redis timeout(timeout2)小。从cache读取到timeout1发现它过期时,延长timeout1并重新设置到cache,再从数据库加载数据并设置到cache中。
(3).永远不过期
热点key不设置过期时间。把过期时间存在key对应的value里,发现过期,通过一个后台的异步线程进行缓存的构建。
(4).缓存屏障

class MyCache{
    private ConcurrentHashMap<String, String> map;
    private CountDownLatch countDownLatch;
    private AtomicInteger atomicInteger;
    public MyCache(ConcurrentHashMap<String, String> map, CountDownLatch countDownLatch,
                   AtomicInteger atomicInteger) {
        this.map = map;
        this.countDownLatch = countDownLatch;
        this.atomicInteger = atomicInteger;
    }

    public String get(String key){
        String value = map.get(key);
        if (value != null){
            System.out.println(Thread.currentThread().getName()+"\t 线程获取value值 value="+value);
            return value;
        }
        // 如果没获取到值
        // 首先尝试获取token,然后去查询db,初始化化缓存;
        // 如果没有获取到token,超时等待
        if (atomicInteger.compareAndSet(0,1)){
            System.out.println(Thread.currentThread().getName()+"\t 线程获取token");
            return null;
        }
        // 其他线程超时等待
        try {
            System.out.println(Thread.currentThread().getName()+"\t 线程没有获取token,等待中。。。");
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 初始化缓存成功,等待线程被唤醒
        // 等待线程等待超时,自动唤醒
        System.out.println(Thread.currentThread().getName()+"\t 线程被唤醒,获取value ="+map.get("key"));
        return map.get(key);
    }

    public void put(String key, String value){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        // 更新状态
        atomicInteger.compareAndSet(1, 2);
        // 通知其他线程
        countDownLatch.countDown();
        System.out.println();
        System.out.println(Thread.currentThread().getName()+"\t 线程初始化缓存成功!value ="+map.get("key"));
    }
}

class MyThread implements Runnable{
    private MyCache myCache;
    public MyThread(MyCache myCache) {
        this.myCache = myCache;
    }
    @Override
    public void run() {
        String value = myCache.get("key");
        if (value == null){
            myCache.put("key","value");
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0));
        MyThread myThread = new MyThread(myCache);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(myThread);
        }
    }
}

八、Redis内存满了

1、修改配置

1.Redis安装目录下的redis.conf配置文件中添加以下配置设置内存大小
//设置Redis最大占用内存大小为100M
maxmemory 100mb
2.通过命令动态修改内存大小
//设置Redis最大占用内存大小为100M
127.0.0.1:6379> config set maxmemory 100mb
//获取设置的Redis能使用的最大内存大小(64位操作系统下不限制内存大小)
127.0.0.1:6379> config get maxmemory

2、内存淘汰

Redis的淘汰策略
noeviction(默认策略): 对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外).
allkeys-lru:          所有key中使用LRU算法进行淘汰.
volatile-lru:         过期时间的key中使用LRU算法进行淘汰.
allkeys-random:       从所有key中随机淘汰数据.
volatile-random:      过期时间的key中随机淘汰.
volatile-ttl:         过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰.

获取当前内存淘汰策略:  127.0.0.1:6379> config get maxmemory-polic
通过配置文件设置淘汰策略(修改redis.conf文件): maxmemory-policy allkeys-lru
通过命令修改淘汰策略: 127.0.0.1:6379> config set maxmemory-policy allkeys-lru

3、LFU算法(Redis4.0)

淘汰不常用的。LFU算法能表示一个key被访问的热度。
LFU一共有两种策略:
volatile-lfu:   设置了过期时间的key中使用LFU算法淘汰key。
allkeys-lfu:    所有的key中使用LFU算法淘汰数据。

4、LRU算法

淘汰最近没用到的

public class LRUCache<k, v> {
    //容量
    private int capacity;
    //当前有多少节点的统计
    private int count;
    //缓存节点
    private Map<k, Node<k, v>> nodeMap;
    private Node<k, v> head;
    private Node<k, v> tail;
    public LRUCache(int capacity) {
        if (capacity < 1) {
            throw new IllegalArgumentException(String.valueOf(capacity));
        }
        this.capacity = capacity;
        this.nodeMap = new HashMap<>();
        //初始化头节点和尾节点,用哨兵模式减少判断头结点和尾节点为空的代码
        Node headNode = new Node(null, null);
        Node tailNode = new Node(null, null);
        headNode.next = tailNode;
        tailNode.pre = headNode;
        this.head = headNode;
        this.tail = tailNode;
    }

    public void put(k key, v value) {
        Node<k, v> node = nodeMap.get(key);
        if (node == null) {
            if (count >= capacity) {
                //先移除一个节点
                removeNode();
            }
            node = new Node<>(key, value);
            //添加节点
            addNode(node);
        } else {
            //移动节点到头节点
            moveNodeToHead(node);
        }
    }

    public Node<k, v> get(k key) {
        Node<k, v> node = nodeMap.get(key);
        if (node != null) {
            moveNodeToHead(node);
        }
        return node;
    }

    private void removeNode() {
        Node node = tail.pre;
        //从链表里面移除
        removeFromList(node);
        nodeMap.remove(node.key);
        count--;
    }

    private void removeFromList(Node<k, v> node) {
        Node pre = node.pre;
        Node next = node.next;

        pre.next = next;
        next.pre = pre;

        node.next = null;
        node.pre = null;
    }

    private void addNode(Node<k, v> node) {
        //添加节点到头部
        addToHead(node);
        nodeMap.put(node.key, node);
        count++;
    }

    private void addToHead(Node<k, v> node) {
        Node next = head.next;
        next.pre = node;
        node.next = next;
        node.pre = head;
        head.next = node;
    }

    public void moveNodeToHead(Node<k, v> node) {
        //从链表里面移除
        removeFromList(node);
        //添加节点到头部
        addToHead(node);
    }
    class Node<k, v> {
        k key;
        v value;
        Node pre;
        Node next;
        public Node(k key, v value) {
            this.key = key;
            this.value = value;
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值