redis 相关内容

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhoucs86/article/details/89763792

Redis相关的应用有分布式锁,发布订阅,限流等

先看看相关代码

悲观锁

package com.redis.distroLock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by zhou1 on 2019/4/30.
 */
public class distriLock implements Runnable {

    public static int globalId = 0;
    public int instanceId = globalId++;

    public static int globalCount = 0;
    public int instCount = 0;

    public static void main(String[] args){
        JedisCommands jedisCli =
                new Jedis("192.168.13.128",6379,10000,10000);
        jedisCli.del("*");
        ExecutorService execService = Executors.newCachedThreadPool();
        for(int i=0;i<3;i++){
            execService.submit(new distriLock());
        }
    }

    @Override
    public void run() {
        String threadName = String.format("thread_%d",instanceId);
        Thread.currentThread().setName(String.format("thread_%d",instanceId));
        JedisCommands jedisCli =
                new Jedis("192.168.13.128",6379,10000,10000);
        while (true){
            //抢不到锁的时候返回的是null
            String getLockResult = jedisCli.set("lock","true",new SetParams().ex(2).nx());
            if(getLockResult!=null){
                try{
                    Thread.sleep(10L);
                    globalCount++;
                    instCount++;
                }catch (Exception e) {

                }
                System.err.printf("%s got the key,total %d, local %d \n"
                        ,threadName,globalCount,instCount);
                jedisCli.del("lock");
            }
            try{
                Thread.sleep(5L);
            }catch (Exception e) {

            }

            if (globalCount>=1000){
                System.err.printf("%s end,total %d, local %d \n"
                        ,threadName,globalCount,instCount);
                break;
            }
        }
    }



}

乐观锁

package com.redis.optiLock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.commands.JedisCommands;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by zhou1 on 2019/5/2.
 */
public class OptiLock implements Runnable  {

    public static int globalId = 0;
    public int instanceId = globalId++;

    public static int globalCount = 0;
    public int instCount = 0;

    public static String userId = "abc";

    public static void main(String[] args){
        JedisCommands jedisCli =
                new Jedis("192.168.13.128",6379,10000,10000);
        jedisCli.del(userId);
        jedisCli.set(userId,"0");
        ExecutorService execService = Executors.newCachedThreadPool();
        for(int i=0;i<3;i++){
            execService.submit(new OptiLock());
        }
    }

    @Override
    public void run() {
        String threadName = String.format("thread_%d",instanceId);
        Thread.currentThread().setName(String.format("thread_%d",instanceId));
        Jedis jedisCli =
                new Jedis("192.168.13.128",6379,10000,10000);

        while (true){
            jedisCli.watch(userId);
            int value = Integer.parseInt(jedisCli.get(userId));
            value++;
            Transaction tx = jedisCli.multi();
            tx.set(userId, String.valueOf(value));
            List<Object> res = tx.exec();
            if (res != null) {
                instCount++;
                System.err.printf("%s set the key,total %d, local %d \n"
                        ,threadName,value,instCount);
            }
            jedisCli.unwatch();


            if (value>=1000){
                System.err.printf("%s end,total %d, local %d \n"
                        ,threadName,value,instCount);
                break;
            }
        }
    }
}

限流


package com.redis.rateLimit;

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

/**
 * Created by zhou1 on 2019/5/1.
 */
public class SimpleRateLimit {

    private Jedis jedis;

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

    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.currentTimeMillis();

        //pipeline就是将操作排队起来
        Pipeline pipe = jedis.pipelined();
        //开始一个事务
        pipe.multi();
        pipe.zadd(key, nowTs, "" + nowTs);
        pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
        //统计zset当中的个数
        Response<Long> count = pipe.zcard(key);
        //就是将这个key超时删除
        pipe.expire(key, period + 1);
        pipe.exec();
        pipe.close();
        return count.get() <= maxCount;
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.13.128",6379,10000,10000);
        jedis.del("hist:laoqian:reply");
        SimpleRateLimit limiter = new SimpleRateLimit(jedis);
        for(int i=0;i<20;i++) {
            try {
                Thread.sleep(50); // 歇会继续
            } catch (InterruptedException e) {
                break;
            }
            System.out.println(limiter.isActionAllowed("laoqian", "reply", 60, 5));
        }
    }


    //redis主备同步是将主的状态同步到父节点,
    //当没同步的被覆盖了就需要去进行快照同步
    //Sentinel,是通过实现一个DNS server类似的功能来检测节点活性的
    //

}
package com.redis.queue;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by zhou1 on 2019/4/30.
 */
public class messageQueueSubscriber implements Runnable{

    public static int globalId = 0;
    public int instanceId = globalId++;



    public static void main(String[] args){
        JedisCommands jedisCli =
                new Jedis("192.168.13.128",6379,10000,10000);
        jedisCli.del("*");
        ExecutorService execService = Executors.newCachedThreadPool();
        for(int i=0;i<3;i++){
            execService.submit(new messageQueueSubscriber());
        }
    }

    @Override
    public void run() {
        String threadName = String.format("thread_%d",instanceId);
        Thread.currentThread().setName(String.format("thread_%d",instanceId));
        JedisCommands jedisCli =
                new Jedis("192.168.13.128",6379,10000,10000);
        while (true) {

//            List<String> popOutResult = jedisCli.brpop(100,"my:queue");

            //取出从第0到当前时间的所有的元素,数量为1
            Set<String> values = jedisCli.zrangeByScore("my:delayQ", 0, System.currentTimeMillis(), 0, 1);
            if (values.isEmpty()) {
                try {
                    Thread.sleep(50); // 歇会继续
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }
            String s = values.iterator().next();
            //这个是关键,能删除说明是抢到了当前这个任务的线程
            if (jedisCli.zrem("my:delayQ", s) > 0) { // 抢到了
                System.err.printf("%s got the pop result %s\n",threadName,values);
                try {
                    Thread.sleep(50); // 歇会继续
                } catch (InterruptedException e) {
                    break;
                }
            }


            //pfadd ,pfcount 是一个不精确的集合统计,可以近似精确地获取当前集合当中有多少的元素
            //geoadd , geodist, geo   是一个地图的数据类型

//            System.err.printf("%s got the pop result %s\n",threadName,popOutResult);
        }
    }
}


redis集群相关

Sentinel 像是一个dns,所有操作之前先向一个dns server请求一个master节点
在这个状态下要做分布式锁的情况下,需要专门的库redlock

codis,将key hash打散到不同的redis上面去,codis节点之间可以靠zk,etcd来同步

redis集群模式

持久化手段

RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的所有数据进行快照并存储在硬盘上。进行快照的条件可以由用户在配置文件中自定义,由两个参数构成:时间和改动的键的个数。当在指定的时间内被更改的键的个数大于指定的数值时就会进行快照。RDB是Redis默认采用的持久化方式,在配置文件中已经预置了3个条件:
save 900 1
save 300 10
save 60 10000

save参数指定了快照条件,可以存在多个条件,条件之间是“或”的关系。如上所说,save900 1的意思是在15分钟(900秒钟)内有至少一个键被更改则进行快照。如果想要禁用自动快照,只需要将所有的save参数删除即可。
Redis默认会将快照文件存储在当前目录的dump.rdb文件中,可以通过配置dir和dbfilename两个参数分别指定快照文件的存储路径和文件名。
理清Redis实现快照的过程对我们了解快照文件的特性有很大的帮助。快照的过程如下。
(1)Redis使用fork函数复制一份当前进程(父进程)的副本(子进程);
(2)父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中
的临时文件;
(3)当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成。
在执行fork的时候操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时(如执行一个写命令),操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。
通过上述过程可以发现Redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。这使得我们可以通过定时备份RDB文件来实现Redis数据库备份。RDB文件是经过压缩(可以配置rdbcompression参数以禁用压缩节省CPU占用)的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。
除了自动快照,还可以手动发送SAVE或BGSAVE命令让Redis执行快照,两个命令的区别在于,前者是由主进程进行快照操作,会阻塞住其他请求,后者会通过fork子进程进行快照操作。
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将一个记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。

2、AOF方式
默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改:
appendfilename appendonly.aof
下面讲解AOF持久化的具体实现,假设在开启AOF持久化的情况下执行了如下4个命令:
SET foo 1
SET foo 2
SET foo 3
GET foo
Redis会将前3条命令写入AOF文件中,此时AOF文件中的内容如下:
*2
6
SELECT
1
0
*3
3
set
3
foo
1
1
*3
3
set
3
foo
1
2
*3
3
set
3
foo
1
3
可见AOF文件是纯文本文件,其内容正是Redis客户端向Redis发送的原始通信协议的内容(Redis的通信协议会在7.4节中介绍,为了便于阅读,这里将实际的命令部分以粗体显示),从中可见Redis确实只记录了前3条命令。然而这时有一个问题是前2条命令其实都是冗余的,因为这两条的执行结果会被第三条命令覆盖。随着执行的命令越来越多,AOF文件的大小也会越来越大,即使内存中实际的数据可能并没有多少。很自然地,我们希望Redis可以自动优化AOF文件,就上例而言,就是将前两条无用的记录删除,只保留第三条。实际上Redis也正是这样做的,每当达到一定条件时Redis就会自动重写AOF文件,这个条件可以在配置文件中设置:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage参数的意义是当目前的AOF文件大小超过上一次重写时的AOF文件大小的百分之多少时会再次进行重写,如果之前没有重写过,则以启动时的AOF文件大小为依据。auto-aof-rewrite-min-size参数限制了允许重写的最小AOF文件大小,通常在AOF文件很小的情况下即使其中有很多冗余的命令我们也并不太关心。除了让Redis自动执行重写外,我们还可以主动使用BGREWRITEAOF命令手动执行AOF重写。
上例中的AOF文件重写后的内容为:
*2
6
SELECT
1
0
*3
3
SET
3
foo
1
3
可见冗余的命令已经被删除了。重写的过程只和内存中的数据有关,和之前的AOF文件无关,这与RDB很相似,只不过二者的文件格式完全不同。
在启动时Redis会逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,载入的速度相较RDB会慢一些。需要注意的是虽然每次执行更改数据库内容的操作时,AOF都会将命令记录在AOF文件中,但是事实上,由于操作系统的缓存机制,数据并没有真正地写入硬盘,而是进入了系统的硬盘缓存。在默认情况下系统每30秒会执行一次同步操作,以便将硬盘缓存中的内容真正地
写入硬盘,在这30秒的过程中如果系统异常退出则会导致硬盘缓存中的数据丢失。一般来讲启用AOF持久化的应用都无法容忍这样的损失,这就需要Redis在写入AOF文件后主动要求系统将缓存内容同步到硬盘中。在Redis中我们可以通过appendfsync参数设置同步的时机:

appendfsync always

appendfsync everysec

appendfsync no

默认情况下Redis采用everysec 规则,即每秒执行一次同步操作。always表示每次执行写入都会执行同步,这是最安全也是最慢的方式。no表示不主动进行同步操作,而是完全交由操作系统来做(即每30秒一次),这是最快但最不安全的方式。一般情况下使用默认值everysec就足够了,既兼顾了性能又保证了安全。
Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。此时重新启动Redis后Redis会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少。

猜你喜欢

转载自blog.csdn.net/zhoucs86/article/details/89763792