redis中数据类型的使用,并发问题,list重复插入问题,redis使用实例-简单消息队列和排名统计

redis 5种数据类型的正确使用

redis支持5中数据类型,即string,list,hash,set,sortedset。但是什么时候应该用哪种数据类型呢?以string和list的为例来说明

其实并不是简单的java中的list对应redis中的list,java中的string对应redis的string
list可以存到string中,取出来后可以强转为list
redis中list数据的本质,是你需要通过redis来直接维护list的数据,如操作某个下标对应的值或者直接对该list插入和删除数据,所以如果你需要的仅仅是对整个list的操作,不需要对list中的数据直接做操作,那么就不应该使用list类型,而应该是字符串类型。

下面的代码用于说明:string中保存list的实例,ListOperations中leftPush和leftPushAll对list处理的区别

@RestController
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/")
    public String index() {

        // 字符串操作类
        ValueOperations valueOperations = redisTemplate.opsForValue();
        valueOperations.set("d","dvalue");
        // 结果 key:d,value:dvalue
        System.out.println("key:d,value:"+valueOperations.get("d"));
        ArrayList<String> strings1 = new ArrayList<>();
        strings1.add("a");
        strings1.add("b");
        // 将list作为string直接存入和读取
        valueOperations.set("e",strings1);
        if(valueOperations.get("e") instanceof List){
            List<String> e = (List<String>) valueOperations.get("e");
            // 结果:e.size2
            // 说明list存入字符串类型没问题
            System.out.println("e.size"+e.size());
        }

        // list操作类
        ListOperations listOperations = redisTemplate.opsForList();
        ArrayList<String> strings = new ArrayList<>();
        strings.add("a");
        strings.add("b");
        strings.add("c");
        // 此时strings被当做一个整体,即strings作为list中一个下标对应的value
        listOperations.leftPush("list",strings);
        // 此时strings被当做一个list,即strings中每一个元素作为list中一个下标对应的value
        listOperations.leftPushAll("list1",strings);
        // leftPush方法,list.size=1
        // leftPushAll方法,list1.size=3
        System.out.println("leftPush方法,list.size="+listOperations.size("list"));
        System.out.println("leftPushAll方法,list1.size="+listOperations.size("list1"));

        redisTemplate.delete("list");
        redisTemplate.delete("list1");

        return "Greetings from Spring Boot!";
    }
}

问题:什么时候适合使用list?是代码中数据集合是list的时候吗?
其实并不是java代码中使用的是list,对应到redis中的数据类型就是list;而是你java代码对一组数据的操作是符合list的操作逻辑的时候,即需要直接操作该list中具体的数据,可以对该list中的数据不断的写入和读出,你可以直接使用redis对该list数据移除和插入,注意不是操作整个list。如果是整个操作list应该是直接作为字符串类型存入,如上面的实例。

redis的lis并发插入导致的重复问题

需要记录付费用户的id,正常逻辑先整体删除并整体存入
高并发时,删除后可能存在重复存入,即list的内容被追加了,导致存在重复的数据
解决方案一:
redisTemplate.delete返回boolean,只有key存在并且确实删除了该key对应的内容时,才会返回true表示成功删除,所以可以先判断delete是否成功再进行存入,如果删除失败则不再插入,可以避免重复插入

解决方案二:list转化为json,采用字符串模式,key相同可以直接替换,不会存在重复的问题
因为虽然我存的数据是list,但是实际上是个对redies来时是字符串

解决方案三:
list的存储类型直接修改为字符串类型,存入的时候直接存入list;代码实例如下
        ValueOperations valueOperations = redisTemplate.opsForValue();
        valueOperations.setIfPresent("d","dvalue");
        ArrayList<String> strings1 = new ArrayList<>();
        strings1.add("a");
        strings1.add("b");
        valueOperations.set("e",strings1);
        List<String> e = (List<String>) valueOperations.get("e");
解决方案四:采用java的锁,对delete()和push操作加synchronized锁,两个操作作为原子性的操作

方案五:redis事务解决,在同一个session中采用redisOperations.multi()和redisOperations.exec()
原本事务是这样写的
                   redisOperations.multi();
                    ArrayList<String> strings = new ArrayList<>();
                    strings.add("abc");
                    redisTemplate.delete("strings");
                    redisOperations.exec();
但是执行的时候,redis事务报错No ongoing transaction. Did you forget to call multi?
原因:发现上面代码中mutil()方法和exec()方法都会从新建立新的连接,导致数据丢失
解决方法:采用RedisTemplate的SesionCallback实现在同一个Connection中
解决方案借鉴
https://www.cnblogs.com/dujiudizhimo/p/9051596.html

方案六
delete后,采用setnx方法,如果对应的key已经存在则什么都不做并返回0,否则设置key的值

推荐方案三和方案一,没有任何阻塞,效率最高。上面涉及到的代码在这里给出

@RestController
public class RedisTransaction {

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/trac")
    public String index() {
        // 每次调用该接口,启动多线程模拟并发场景
        for(int i=0;i<5;i++){
            new Thread(new Task(redisTemplate)).start();
        }
        return "Greetings from Spring Boot!";
    }
}
public class Task implements Runnable {
    public static final String lock = "lock";

    private RedisTemplate redisTemplate;

    public Task(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void run() {
        for(int i=0;i<10000;i++){
            ListOperations listOperations = redisTemplate.opsForList();

            //  有问题代码,会有线程安全问题 即strings对应的值可能会被重复插入
            ArrayList<String> strings = new ArrayList<>();
            strings.add("abc");
            Boolean delete = redisTemplate.delete("strings");
            listOperations.leftPushAll("strings",strings);

            // 正确的方案五 采用redis的事务
           /* SessionCallback sessionCallback = new SessionCallback() {
                @Override
                public Object execute(RedisOperations redisOperations) throws DataAccessException {
                    redisOperations.multi();
                    ArrayList<String> strings = new ArrayList<>();
                    strings.add("abc");
                    redisTemplate.delete("strings");
                    listOperations.leftPushAll("strings",strings);
                    return redisOperations.exec();
                }
            };
            redisTemplate.execute(sessionCallback);*/

           // 错误的方案五 采用redis的事务,报错:No ongoing transaction. Did you forget to call multi?
            // 错误原因是原因:发现上面代码中mutil()方法和exec()方法都会从新建立新的连接,导致数据丢失
            //解决方法:采用RedisTemplate的SesionCallback实现在同一个Connection中
           /* redisOperations.multi();
              ArrayList<String> strings = new ArrayList<>();
              strings.add("abc");
              redisTemplate.delete("strings");
              redisOperations.exec();*/

            // 方案一 delete成功再插入
            /*ArrayList<String> strings = new ArrayList<>();
            strings.add("abc");
            Boolean delete = redisTemplate.delete("strings");
            if(delete){
                listOperations.leftPushAll("strings",strings);
            }*/

            //方案四 java的事务实现
            /*synchronized(lock){
                ArrayList<String> strings = new ArrayList<>();
                strings.add("abc");
                Boolean delete = redisTemplate.delete("strings");
                listOperations.leftPushAll("strings",strings);
            }*/

            // 只要出现了大于1的情况,就说明重复插入了
            if(listOperations.size("strings") >1){
                System.out.println("con error"+listOperations.size("strings"));
            }
        }
    }
}

redis的并发竞争问题如何解决?

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
   1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
   2.服务器角度,利用setnx实现锁。
   注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。

redis使用实例-简单消息队列和排名统计

@RestController
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * desc:  redis使用场景-简单的消息队列
     * @author mazhen
     * @date 2019/4/10 下午5:51
     */
    @RequestMapping("/produce")
    public void produce(String message) {
        ListOperations listOperations = redisTemplate.opsForList();
        listOperations.leftPush("mq",message);
    }
    @RequestMapping("/consume")
    public String consume() {
        ListOperations listOperations = redisTemplate.opsForList();
        Object mq = listOperations.rightPop("mq");
        return mq+"";
    }


    /**
     * desc:  redis使用场景-简单排名统计
     * @author mazhen
     * @date 2019/4/10 下午5:51
     */
    @RequestMapping("/score")
    public String score() {
        redisTemplate.delete("score");
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        zSetOperations.add("score","b",99);
        zSetOperations.add("score","a",100);
        zSetOperations.add("score","c",98);
        zSetOperations.add("score","d",97);

        // 倒序取2条数据,即分数最高的是第一名,这里只要第一和第二名
        int topNum = 2;
        Set<Object> score1 = redisTemplate.opsForZSet().reverseRange("score", 0, topNum - 1);
        String names = "前两名:";
        for(Object name1:score1){
            names += name1+",";
        }
        // 结果 前两名:a,b,
        System.out.println(names);
        return names;
    }
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值