redis

6 篇文章 0 订阅

redis概述

  • 开源、免费、高性能的key-value数据库,与Memcached类似

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value非关系型数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

  • Windows下安装运行(由MSOpenTech开发维护)
  • 下载:https://github.com/MSOpenTech/redis/releases

redis-server.exe redis.windows.conf
redis-cli.exe -h 127.0.0.1 -p 6379

  • Linux下安装运行
  • 下载:http://download.redis.io/releases/redis-3.2.8.tar.gz

$ tar xzf redis-3.2.8.tar.gz
$ cd redis-3.2.8
$ make
$ src/redis-server
$ src/redis-cli

  • 后台启动
    • nano /usr/local/software/redis-3.2.8/redis.conf
    • 将daemonize修改为yes
      • daemonize yes
    • 启动命令-表示使用redis的配置文件进行启动
      • ./src/redis-server redis.conf
  • 外部程序访问,需要开发6379端口
    • nano /etc/sysconfig/iptables
    • -A INPUT -m state --state NEW -m tcp -p tcp --dport 6379 -j ACCEPT
    • service iptables restart

常用命令

  • 配置命令
    • 语法
      • config get CONFIGSETTINGNAME
      • config set CONFIGSETTINGNAME NEWCONFIGVALUE
    • 示例
      • config get requirepass
      • config set requirepass "123456789"
    • 设置完密码后,查看密码,需要权限,使用auth命令登陆后再去执行
      • auth "123"
  • config set requirepass 123 这种方式只能够在当前会话中设置密码,redis重启之后失效
  • 实际开发中我们在使用redis的时候,需要进行密码验证,那么可以修改redis.conf配置文件,设置对应的redis密码
    • 将#requirepass foobared去掉注释,然后将密码改为自己对应的密码
  • 键命令:用于管理 Redis 的键
  • 字符串命令:用于管理 Redis 字符串类型值
    • 语法
      • command KEY_NAME
    • 示例
      • set name xiaoming
        • 设置一个键值对 key为name,value为小明
      • get name
        • 根据key去查询数据
      • keys *
        • 列举出redis数据库中所有的key
      • expire name 60
        • 设置key为name的数据,有效时间为60秒
      • ttl name
        • 查看key为name的数据剩余的有效时间,-1表示永久有效,-2表示已经不存在
      • exists name
        • 判断key为name的数据是否存在
      • del name
        • 删除一个key为name的数据

java中使用redis

  • Jedis是Redis官方首选的Java客户端开发包
  • pom.xml中加入Jedis依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.1.0</version>
</dependency>
  • 连接jedis
@Test
public void test3() {
    //连接本地的 Redis 服务
    Jedis jedis = new Jedis("192.168.1.6");
    //查看服务是否运行
    System.out.println("Server is running: "+jedis.ping());
    jedis.set("foo", "bar");
    String value = jedis.get("foo");
}
  • 注意:直接运行会报错
    • 需要注释掉redis.conf中bind 127.0.0.1这一行
    • 将保护模式改为no
      • protected-mode no
    • 重启redis服务,kill -9 pid将之前进程直接杀死后再次启动
  • 将redis整合到项目中
  • 在itrip-utils中编写RedisAPI工具类
public class RedisAPI {
   
    public JedisPool jedisPool;
   
    public JedisPool getJedisPool() {
       return jedisPool;
    }
    public void setJedisPool(JedisPool jedisPool) {
       this.jedisPool = jedisPool;
    }
    /**
     * set key and value to redis
     * @param key
     * @param value
     * @return
     */
    public boolean set(String key,String value){
       try{
           Jedis jedis = jedisPool.getResource();
           jedis.set(key, value);
           return true;
       }catch(Exception e){
           e.printStackTrace();
       }
       return false;
    }
   
    /**
     * set key and value to redis
     * @param key
     * @param seconds 有效期
     * @param value
     * @return
     */
    public boolean set(String key,int seconds,String value){
       try{
           Jedis jedis = jedisPool.getResource();
           jedis.setex(key, seconds, value);
           return true;
       }catch(Exception e){
           e.printStackTrace();
       }
       return false;
    }
   
    /**
     * 判断某个key是否存在
     * @param key
     * @return
     */
    public boolean exist(String key){
       try{
           Jedis jedis = jedisPool.getResource();
           return jedis.exists(key);
       }catch(Exception e){
           e.printStackTrace();
       }
       return false;
    }
   
    /**
     * 返还到连接池
     * @param pool
     * @param redis
     */
    public static void returnResource(JedisPool pool,Jedis redis){
       if(redis != null){
           pool.returnResource(redis);
       }
    }
   
    /**
     * 获取数据
     * @param key
     * @return
     */
    public String get(String key){
       String value = null;
       Jedis jedis = null;
       try{
           jedis = jedisPool.getResource();
           value = jedis.get(key);
       }catch(Exception e){
           e.printStackTrace();
       }finally{
           //返还到连接池
           returnResource(jedisPool, jedis);
       }
      
       return value;
    }
   
    /**
     * 查询key的有效期,当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
     * 注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
     * @param key
     * @return 剩余多少秒
     */
    public Long ttl(String key){
       try{
           Jedis jedis = jedisPool.getResource();
           return jedis.ttl(key);
       }catch(Exception e){
           e.printStackTrace();
       }
       return (long) -2;
    }
   
    /**
     * 删除
     * @param key
     */
    public void delete(String key){
       try{
           Jedis jedis = jedisPool.getResource();
           jedis.del(key);
       }catch(Exception e){
           e.printStackTrace();
       }     
    }
}
  • 编写测试方法

@Test
public void test2() {
    boolean b = redisAPI.set("age", "18");
    assertEquals(true, b);
    boolean b1 = redisAPI.set("sex", "男", 60);
    assertEquals(true, b);
    Long sex = redisAPI.ttl("age");
    assertEquals(-1, sex.longValue());
    String age = redisAPI.get("age");
    assertEquals("18", age);
    boolean exists = redisAPI.exists("age");
    assertEquals(true, exists);
    Long name = redisAPI.del("name");
    assertEquals(1, name.longValue());
}

Redis的5种类型

  • String: 字符串
  • Hash: 散列
  • List: 列表
  • Set: 集合
  • Sorted Set: 有序集合
  • 各个数据类型应用场景:

    类型简介特性场景
    String(字符串)二进制安全可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M---
    Hash(字典)键值对集合,即编程语言中的Map类型适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去)存储、读取、修改用户属性
    List(列表)链表(双向链表)增删快,提供了操作某一段元素的API1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列
    Set(集合)哈希表实现,元素不重复1,添加、删除,查找的复杂度都是O(1) 2,为集合提供了求交集、并集、差集等操作1,共同好友 2,利用唯一性,统计访问网站的所有独立ip 3,好用推荐时,根据tag求交集,大于某个阈值就可以推荐
    Sorted Set(有序集合)将Set中的元素增加一个权重参数score,元素按score有序排列数据插入集合时,已经进行天然排序1,排行榜 2,带权重的消息队列

Redis分布式锁

分布式锁特点

    系统中各个节点共享:系统中的每个节点(应用)都可以去持有该分布式锁,

                       以及判断锁是否被其它节点持有

    单节点操作:当其中一个节点持有锁的时候,其它节点处于等待状态

 

  常见的分布式锁

    Memcached的add、cas命令

    Zookeeper

*Redis的setnx命令:最常用

1.4.1Redis的set与setnx命令

Set命令针对相同的key的设置会覆盖

Setnx只允许针对相同的key设置一次值,如果再次设置会返回0代表设置失败

 

setnx key成功代表持有锁

del key 或者 expire key second 都可以代表释放锁

1.4.2编写RedisUtils类

在RedisUtils中主要是添加lock方法和unlock方法

@Component
public class RedisUtils {

    private Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * set key and value to redis
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(String key, String value) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        ValueOperations<String, Object> vo = redisTemplate.opsForValue();
        vo.set(key, value);
        return true;
    }

    /**
     * set key and value to redis
     *
     * @param key
     * @param seconds 有效期
     * @param value
     * @return
     */
    public boolean set(String key, long seconds, String value) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        ValueOperations<String, Object> vo = redisTemplate.opsForValue();
        vo.set(key, value);
        expire(key, seconds);
        return true;
    }

    /**
     * 判断某个key是否存在
     *
     * @param key
     * @return
     */
    public boolean exist(String key) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        ValueOperations<String, Object> vo = redisTemplate.opsForValue();
        Object value = vo.get(key);
        return EmptyUtils.isEmpty(value) ? false : true;
    }

    public Object get(String key) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        ValueOperations<String, Object> vo = redisTemplate.opsForValue();
        return vo.get(key);
    }

    public void delete(String key) {
        try {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            //设置序列化Value的实例化对象
            redisTemplate.setValueSerializer(new StringRedisSerializer());
            redisTemplate.delete(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public Boolean expire(final String key, final Long expireTime) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                boolean flag = false;
                try {
                    redisTemplate.setKeySerializer(new StringRedisSerializer());
                    //设置序列化Value的实例化对象
                    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
                    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
                    byte keys[] = stringRedisSerializer.serialize(key);
                    flag = redisConnection.expire(keys, expireTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return flag;
            }
        });
    }


    public boolean validate(String token) {
        return exist(token);
    }

    /**
     * 利用redis的setnx命令为某一件的商品库存加锁
     *
     * @return 如果返回true, 代表某一个线程持有锁成功, 如果返回false代表某一个线程持有锁失败
     */
    public boolean lock(String key) {
        //redisTemplate.getConnectionFactory().getConnection().setNX()
        return redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
                byte[] keyBytes = stringRedisSerializer.serialize(key);
                byte[] valueBytes = stringRedisSerializer.serialize("lock");
                Boolean flag = connection.setNX(keyBytes, valueBytes);
                if (flag) {//加了一个保险,防止redis中的key清除不掉
                    connection.expire(keyBytes, Constants.Redis_Expire.DEFAULT_EXPIRE);
                }
                return flag;
            }
        });
    }

    /**
     * 删掉这个key
     * @param key
     */
    public void unlock(String key) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.delete(key);
    }
}

 

1.4.3编写shop-goods-consumber的QgGoodsServiceImpl

public  Dto qgGoods(String token, String goodsId) {

    //1.根据token判断用户是否是登录状态,只有登录状态才能抢购

    try {

        QgUser currentUser = rpcQgUserService.getCurrentUser(token);

        if (EmptyUtils.isEmpty(currentUser)) {

            return DtoUtil.returnFail("用户未登录", Constants.User_AUTH.AUTH_TOKEN_INVALID);

        }

  

        //**********添加Redis锁***************

        //有可能刚开始其它线程持有锁,所以当前线程不能只获取一次锁,可能获取不到就继续向下执行

        //需要控制不断获取锁,直到获取到锁在继续向下执行

        //这里说明设置超时时间的目的

        while(!redisUtils.lock("lock:" + goodsId)) {//获取成功lock()返回true结束while循环

            Thread.sleep(3000);//每隔三秒尝试获取一次锁

        }

  

  

  

        //2.用户已经登录判断库存是否充足

        //如果库存不足直接返回

        //如果库存充足锁定库存

        int stockFlag = rpcQgGoodsService.checkGoodsStock(goodsId);

        if (stockFlag <= 0) {

            return DtoUtil.returnFail("商品库存不足,抢购失败", Constants.MessageStatus.FAIL+"");

        }

  

  

        int lockFlag = rpcQgGoodsService.lockGoodsStock(goodsId,currentUser.getId());

        if (lockFlag<=0) {

            return DtoUtil.returnFail("抢购失败", Constants.MessageStatus.FAIL+"");

        }

  

    } catch (Exception e) {

        e.printStackTrace();

    }finally {

        //**********释放Redis锁***************

        //无论上面代码是否抛出异常都要释放掉锁

        redisUtils.unlock("lock:" + goodsId);

    }

    return DtoUtil.returnSuccess("抢购成功");

}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值