百味皆苦 java后端开发攻城狮

Redis从入门到高可用之客户端

2020-03-02
百味皆苦

jedis

  • maven
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>
  • jedis直连

3RDG4S.png

1:生成一个jedis对象,这个对象负责和指定redis节点进行通信
jedis构造函数:Jedis(String host,int port,int connectionTimeout,int soTimeout);
host:Redis节点所在的机器IP
port:端口号
connectionTimeout:客户端连接超时时间
soTimeout:客户端读写超时时间

Jedis jedis = new Jedis("127.0.0.1",6379);

2:jedis执行set操作
jedis.set("hello","world");

3:jedis执行get操作,值为world
String value = jedis.get("hello")

通用操作

方法 描述 返回值 /补充说明
jedis.flush    
jedis.flushDB 清空数据  
boolean jedis.exists(String key) 判断某个键是否存在 true = 存在,false= 不存在
jedis.set(String key,String value) 新增键值对(key,value) 返回String类型的OK代表成功
Set jedis.keys(*) 获取所有key 返回set 无序集合
jedis.del(String key) 删除键为key的数据项  
jedis.expire(String key,int i) 设置键为key的过期时间为i秒  
int jedis.ttl(String key) 获取建委key数据项的剩余时间(秒)  
jedis.persist(String key) 移除键为key属性项的生存时间限制  
jedis.type(String key) 查看键为key所对应value的数据类型  

字符串操作

  • 字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这 便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。 在Redis中字符串类型的Value最多可以容纳的数据长度是512M。
语法 描述
jedis.set(String key,String value) 增加(或覆盖)数据项
jedis.setnx(String key,String value) 不覆盖增加数据项(重复的不插入)
jedis.setex(String ,int t,String value) 增加数据项并设置有效时间
jedis.del(String key) 删除键为key的数据项
jedis.get(String key) 获取键为key对应的value
jedis.append(String key, String s) 在key对应value 后边扩展字符串 s
jedis.mset(String k1,String V1,String K2,String V2,…) 增加多个键值对
String[] jedis.mget(String K1,String K2,…) 获取多个key对应的value
jedis.del(new String) 删除多个key对应的数据项
String jedis.getSet(String key,String value) 获取key对应value并更新value
String jedis.getrang(String key , int i, int j) 获取key对应value第i到j字符 ,从0开始,包头包尾
//String类型

//输出结果:OK
jedis.set("hello","world");

//输出结果:world
jedis.get("hello");

//输出结果:1
jedis.incr("counter");

数字操作

语法 描述
jedis.incr(String key) 将key对应的value 加1
jedis.incrBy(String key,int n) 将key对应的value 加 n
jedis.decr(String key) 将key对应的value 减1
jedis.decrBy(String key , int n) 将key对应的value 减 n

Hash操作

  • Redis中的Hashes类型可以看成具有String Key和String Value的map容器。所以该类型非常适合于存储值对象的信息。如Username、Password和Age等。如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash可以存储4294967295个键值对。
  • 规律: 哈希的 方法 都以 h 开头,含有m字符的一般是多个的, (multiple: 多个的)
语法 描述
jedis.hmset(String key,Map map) 添加一个Hash
jedis.hset(String key , String key, String value) 向Hash中插入一个元素(K-V)
jedis.hgetAll(String key) 获取Hash的所有(K-V) 元素
jedis.hkeys(String key) 获取Hash所有元素的key
jedis.hvals(String key) 获取Hash所有元素 的value
jedis.hincrBy(String key , String k, int i) 把Hash中对应的k元素的值 val+=i
jedis.hdecrBy(String key,String k, int i) 把Hash中对应的k元素的值 val-=i
jedis.hdel(String key , String k1, String k2,…) 从Hash中删除一个或多个元素
jedis.hlen(String key) 获取Hash中元素的个数
jedis.hexists(String key,String K1) 判断Hash中是否存在K1对应的元素
jedis.hmget(String key,String K1,String K2) 获取Hash中一个或多个元素value
//hash类型

jedis.hset("myhash","f1","v1");
jedis.hset("myhash","f2","v2");

//输出结果:{f1=v1,f2=v2}
jedis.hgetAll("myhash");

list操作

  • 在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表 一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是 4294967295。
  • 从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将 会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。
  • list 元素的下标从0开始
语法 描述
jedis.lpush(String key, String v1, String v2,….) 添加一个List , 注意:如果已经有该List对应的key, 则按顺序在左边追加 一个或多个
jedis.rpush(String key , String vn) key对应list右边插入元素
jedis.lrange(String key,int i,int j) 获取key对应list区间[i,j]的元素,注:从左边0开始,包头包尾
jedis.lrem(String key,int n , String val) 删除list中 n个元素val
jedis.ltrim(String key,int i,int j) 删除list区间[i,j] 之外的元素
jedis.lpop(String key) key对应list ,左弹出栈一个元素
jedis.rpop(String key) key对应list ,右弹出栈一个元素
jedis.lset(String key,int index,String val) 修改key对应的list指定下标index的元素
jedis.llen(String key) 获取key对应list的长度
jedis.lindex(String key,int index) 获取key对应list下标为index的元素
jedis.sort(String key) 把key对应list里边的元素从小到大排序 (后边详细介绍)
//list类型

jedis.rpush("mylist","1");
jedis.rpush("mylist","2");
jedis.rpush("mylist","3");

//输出结果:[1,2,3]
jedis.lrange("mylist",0,-1);

set操作

  • 在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。需要 说明的是,这些操作的时间是常量时间。Set可包含的最大元素数是4294967295。
  • 和List类型不同的是,Set集合中不允许出现重复的元素。和List类型相比,Set类型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Sets之间的聚合计 算操作,如unions、intersections和differences(就是交集并集那些了)。由于这些操作均在服务端完成, 因此效率极高,而且也节省了大量的网络IO开销
  • set 的方法都以s开头
语法 操作
jedis.sadd(String key,String v1,String v2,…) 添加一个set
jedis.smenbers(String key) 获取key对应set的所有元素
jedis.srem(String key,String val) 删除集合key中值为val的元素
jedis.srem(String key, Sting v1, String v2,…) 删除值为v1, v2 , …的元素
jedis.spop(String key) 随机弹出栈set里的一个元素
jedis.scared(String key) 获取set元素个数
jedis.smove(String key1, String key2, String val) 将元素val从集合key1中移到key2中
jedis.sinter(String key1, String key2) 获取集合key1和集合key2的交集
jedis.sunion(String key1, String key2) 获取集合key1和集合key2的并集
jedis.sdiff(String key1, String key2) 获取集合key1和集合key2的差集
//set类型

jedis.sadd("myset","a");
jedis.sadd("myset","b");
jedis.sadd("myset","a");

//输出结果:[b,a]
jedis.smembers("myset");

zset操作

  • Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联
  • Redis正是通过分数来为集合中的成员进行从小到大的排序。然 而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score) 却是可以重复的。
  • 在Sorted-Set中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted-Sets中的成员在集合中的位置是有序的,因此,即便是访问位于集合中部的成员也仍然是非常高效的。
语法 描述
jedis.zadd(String key,Map map) 添加一个ZSet
jedis.hset(String key,int score , int val) 往 ZSet插入一个元素(Score-Val)
jedis.zrange(String key, int i , int j) 获取ZSet 里下表[i,j] 区间元素Val
jedis. zrangeWithScore(String key,int i , int j) 获取ZSet 里下表[i,j] 区间元素Score - Val
jedis.zrangeByScore(String , int i , int j) 获取ZSet里score[i,j]分数区间的元素(Score-Val)
jeids.zscore(String key,String value) 获取ZSet里value元素的Score
jedis.zrank(String key,String value) 获取ZSet里value元素的score的排名
jedis.zrem(String key,String value) 删除ZSet里的value元素
jedis.zcard(String key) 获取ZSet的元素个数
jedis.zcount(String key , int i ,int j) 获取ZSet总score在[i,j]区间的元素个数
jedis.zincrby(String key,int n , String value) 把ZSet中value元素的score+=n
//zset类型

jedis.zadd("myzset",99,"tom");
jedis.zadd("myzset",66,"peter");
jedis.zadd("myzset",33,"james");

//输出结果:[[["james"],33.0],[["peter"],66.0],[["tom"],99.0]]
jedis.zrangeWithScores("myzset",0,-1);

排序操作

  • 使用排序, 首先需要生成一个排序对象

SortingParams sortingParams = new SortingParams();

语法 描述
jedis.sort(String key,sortingParams.alpha()) 队列按首字母a-z 排序
jedis.sort(String key, sortingParams.asc() ) 队列按数字升序排列
jedis.sort(String key , sortingParams.desc()) 队列按数字降序排列
 Jedis jedis = JedisPoolUtils.getJedis();
 SortingParams sortingParams = new SortingParams();
 List<String> sort = jedis.sort("list02", sortingParams.desc());
  • 这里排序指的是返回的sort是有序的,而之前的list02 依然是以前的顺序。

jedis连接池

3RDfD1.png

3Rr9PS.png

  • 使用连接池
//初始化jedis连接池,通常来说,jedisPool是单例的
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379);

Jedis jedis = null;
try{
  //1.从连接池获取jedis对象
  jedis = jedisPool.getResource();
  //2.执行操作
  jedis.set("hello","world");
  
}catch(Exception e){
  e.printStackTrance();
}finally{
  if(jedis != null){
    //如果使用jedisPool,close操作不是关闭连接,而是代表归还给连接池
    jedis.close();
  }
}

连接池配置优化

3R6BM4.png

3R6cIx.png

  • 适合的maxTotal
1.命令平均执行时间:0.1ms = 0.001s
2.业务需要50000QPS
3.maxTotal = 0.001*50000 = 50。实际值要偏大一些

线程安全问题

  • 直接在多线程环境下复用一个连接会产生什么问题,以及如何用最佳实践来修复这个问题。

  • @RestController
    @RequestMapping("jedismisreuse")
    @Slf4j
    public class JedisMisreuseController {
    
        private static JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
    
        //向 Redis 初始化 2 组数据,Key=a、Value=1,Key=b、Value=2
        @PostConstruct
        public void init() {
            try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
                Assert.isTrue("OK".equals(jedis.set("a", "1")), "set a = 1 return OK");
                Assert.isTrue("OK".equals(jedis.set("b", "2")), "set b = 2 return OK");
            }
            //我们最好通过 shutdownhook,在程序退出之前关闭 JedisPool
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                jedisPool.close();
            }));
        }
    
      /*
      启动两个线程,共享操作同一个 Jedis 实例,每一个线程循环 1000 次,分别读取
      Key 为 a 和 b 的 Value,判断是否分别为 1 和 2
      */
        @GetMapping("/wrong")
        public void wrong() throws InterruptedException {
            Jedis jedis = new Jedis("127.0.0.1", 6379);
            new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    String result = jedis.get("a");
                    if (!"1".equals(result)) {
                        log.warn("Expect a to be 1 but found {}", result);
                        return;
                    }
                }
            }).start();
            new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    String result = jedis.get("b");
                    if (!"2".equals(result)) {
                        log.warn("Expect b to be 2 but found {}", result);
                        return;
                    }
                }
            }).start();
            TimeUnit.SECONDS.sleep(5);
        }
        /*
        执行程序多次,可以看到日志中出现了各种奇怪的异常信息,有的是读取 Key 为 b 的
    Value 读取到了 1,有的是流非正常结束,还有的是连接关闭异常
    Jedis 继承了 BinaryJedis,BinaryJedis 中保存了单个 Client 的实例,Client
    最终继承了 Connection,Connection 中保存了单个 Socket 的实例,和 Socket 对应的
    两个读写流。因此,一个 Jedis 对应一个 Socket 连接。
    Jedis 继承了 BinaryJedis,BinaryJedis 中保存了单个 Client 的实例,Client
    最终继承了 Connection,Connection 中保存了单个 Socket 的实例,和 Socket 对应的
    两个读写流。因此,一个 Jedis 对应一个 Socket 连接。
    我们在多线程环境下复用 Jedis 对象,其实就是在复用 RedisOutputStream。如果多个线
    程在执行操作,那么既无法确保整条命令以一个原子操作写入 Socket,也无法确保写入
    后、读取前没有其他数据写到远端
    我们在多线程环境下复用 Jedis 对象,其实就是在复用 RedisOutputStream。如果多个线
    程在执行操作,那么既无法确保整条命令以一个原子操作写入 Socket,也无法确保写入
    后、读取前没有其他数据写到远端
        */
    
        
        /*
        使用 Jedis 提供的另一个线程安全的类 JedisPool 来获得 Jedis 的实例。
    JedisPool 可以声明为 static 在多个线程之间共享,扮演连接池的角色。使用时,按需使用
    try-with-resources 模式从 JedisPool 获得和归还 Jedis 实例。
        */
        @GetMapping("/right")
        public void right() throws InterruptedException {
    
            new Thread(() -> {
                try (Jedis jedis = jedisPool.getResource()) {
                    for (int i = 0; i < 1000; i++) {
                        String result = jedis.get("a");
                        if (!"1".equals(result)) {
                            log.warn("Expect a to be 1 but found {}", result);
                            return;
                        }
                    }
                }
            }).start();
            new Thread(() -> {
                try (Jedis jedis = jedisPool.getResource()) {
                    for (int i = 0; i < 1000; i++) {
                        String result = jedis.get("b");
                        if (!"2".equals(result)) {
                            log.warn("Expect b to be 2 but found {}", result);
                            return;
                        }
                    }
                }
            }).start();
            TimeUnit.SECONDS.sleep(5);
    
        }
    
        @GetMapping("timeout")
        public String timeout(@RequestParam("waittimeout") int waittimeout,
                              @RequestParam("conntimeout") int conntimeout) {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(1);
            config.setMaxWaitMillis(waittimeout);
            try (JedisPool jedisPool = new JedisPool(config, "127.0.0.1", 6379, conntimeout);
                 Jedis jedis = jedisPool.getResource()) {
                return jedis.set("test", "test");
            }
        }
    }
    
  • Jedis 的 API 实现是我们说的三种类型中的第一种,也就是连接池和连接分离的 API,JedisPool 是线程安全的连接池,Jedis 是非线程安全的单一连接。

Redis序列化对象

  • 采用性能最好的protostuff来自定义序列化对象,使对象占用最少的空间
<!--配置RedisDao,使用构造器注入-->
<bean id="redisDao" class="com.qinfen.dao.cache.RedisDao">
  <constructor-arg index="0" value="localhost"/>
  <constructor-arg index="1" value="6379"/>
</bean>
  • RedisDao
package com.qinfen.dao.cache;

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.qinfen.entity.Seckill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * Redis缓存
 *
 * @author QinFen
 * @date 2019/11/6 0006 10:00
 */
public class RedisDao {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final JedisPool jedisPool;
    private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);

    public RedisDao(String ip, int port) {
        jedisPool = new JedisPool(ip, port);
    }

    /**
     * 从Redis中取出一个秒杀商品对象
     *
     * @param seckillId
     * @return
     */
    public Seckill getSeckill(long seckillId) {
        //Redis操作逻辑
        try {
            //获取jedis
            Jedis jedis = jedisPool.getResource();
            try {
                String key = "seckill:" + seckillId;
                //Redis没有实现内部序列化操作
                // get -> byte[] -> 反序列化 -> Object(Seckill)
                //采用性能最好的protostuff来自定义序列化,比用jdk的序列化好
                byte[] bytes = jedis.get(key.getBytes());
                //缓存重新获取到
                if (bytes != null) {
                    //空对象
                    Seckill seckill = schema.newMessage();
                    ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
                    //seckill 被反序列化,属性填充完成
                    return seckill;
                }
            } finally {
                jedis.close();
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 向Redis中缓存一个秒杀商品对象
     *
     * @param seckill
     * @return
     */
    public String putSeckill(Seckill seckill) {
        // set Object(Seckill) -> 序列化 -> byte[]
        try {
            Jedis jedis = jedisPool.getResource();
            try {
                String key = "seckill:" + seckill.getSeckillId();
                byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
                        LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                //超时缓存
                int timeout = 60 * 60;//一小时
                String result = jedis.setex(key.getBytes(), timeout, bytes);
                return result;
            } finally {
                jedis.close();
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
}

Redis常用操作封装

  • 配置
package com.imooc.miaosha.redis;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * Redis配置类,从配置文件中读取以redis为前缀的数据
 *
 * @author Administrator
 */
@Component
@ConfigurationProperties(prefix = "redis")
public class RedisConfig {
    /**
     * 主机号
     */
    private String host;
    /**
     * 端口号
     */
    private int port;
    /**
     * 过期时间,秒
     */
    private int timeout;
    /**
     * 密码
     */
    private String password;
    private int poolMaxTotal;
    private int poolMaxIdle;
    private int poolMaxWait;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getPoolMaxTotal() {
        return poolMaxTotal;
    }

    public void setPoolMaxTotal(int poolMaxTotal) {
        this.poolMaxTotal = poolMaxTotal;
    }

    public int getPoolMaxIdle() {
        return poolMaxIdle;
    }

    public void setPoolMaxIdle(int poolMaxIdle) {
        this.poolMaxIdle = poolMaxIdle;
    }

    public int getPoolMaxWait() {
        return poolMaxWait;
    }

    public void setPoolMaxWait(int poolMaxWait) {
        this.poolMaxWait = poolMaxWait;
    }
}
package com.imooc.miaosha.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;

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

/**
 * Redis连接工厂
 *
 * @author Administrator
 */
@Service
public class RedisPoolFactory {

    @Autowired
    RedisConfig redisConfig;

    /**
     * 向spring容器中注入jedis连接池
     *
     * @return
     */
    @Bean
    public JedisPool JedisPoolFactory() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
        poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
        poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
        JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
                redisConfig.getTimeout() * 1000, redisConfig.getPassword(), 0);
        return jp;
    }

}
package com.imooc.miaosha.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;

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

/**
 * Redis常用操作封装
 *
 * @author Administrator
 */
@Service
public class RedisService {

    @Autowired
    JedisPool jedisPool;

    /**
     * 获取当个对象
     */
    public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            String str = jedis.get(realKey);
            T t = stringToBean(str, clazz);
            return t;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 设置对象
     */
    public <T> boolean set(KeyPrefix prefix, String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String str = beanToString(value);
            if (str == null || str.length() <= 0) {
                return false;
            }
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            //过期时间
            int seconds = prefix.expireSeconds();
            if (seconds <= 0) {
                jedis.set(realKey, str);
            } else {
                jedis.setex(realKey, seconds, str);
            }
            return true;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 判断key是否存在
     */
    public <T> boolean exists(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.exists(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 删除
     */
    public boolean delete(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            long ret = jedis.del(key);
            return ret > 0;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 增加值
     */
    public <T> Long incr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.incr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 减少值
     */
    public <T> Long decr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.decr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 把对象转换为字符串
     *
     * @param value
     * @param <T>
     * @return
     */
    public static <T> String beanToString(T value) {
        if (value == null) {
            return null;
        }
        Class<?> clazz = value.getClass();
        if (clazz == int.class || clazz == Integer.class) {
            return "" + value;
        } else if (clazz == String.class) {
            return (String) value;
        } else if (clazz == long.class || clazz == Long.class) {
            return "" + value;
        } else {
            return JSON.toJSONString(value);
        }
    }

    /**
     * 字符串转换为对象
     *
     * @param str
     * @param clazz
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T stringToBean(String str, Class<T> clazz) {
        if (str == null || str.length() <= 0 || clazz == null) {
            return null;
        }
        if (clazz == int.class || clazz == Integer.class) {
            return (T) Integer.valueOf(str);
        } else if (clazz == String.class) {
            return (T) str;
        } else if (clazz == long.class || clazz == Long.class) {
            return (T) Long.valueOf(str);
        } else {
            return JSON.toJavaObject(JSON.parseObject(str), clazz);
        }
    }

    /**
     * 把Redis连接返回到连接池中
     *
     * @param jedis
     */
    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

}

Redis慢查询

生命周期

34rNz8.png

两个配置

34sX90.png

慢查询命令

  • slowlog get [n]:获取慢查询队列
  • slowlog len :获取慢查询队列长度
  • slowlog reset :清空慢查询队列

运维经验

  • slowlog-max-len 不要设置过大,默认10ms,通常设置1ms
  • slowlog-log-slower-than 不要设置过小,通常设置1000左右
  • 理解命令生命周期
  • 定期持久化慢查询

流水线

346aQK.png

  • 流水线作用:

34c9YR.png

  • 在jedis中的使用
Jedis jedis = new Jedis("127.0.0.1",6379);
for(int i =0;i<100;i++){
  Pipeline pipeline = jedis.pipelined();
  for(int j=i*100;j<(i+1)*100;j++){
    pipeline.hset("hashkey"+j,"field"+j,"value"+j);
  }
  pipeline.syncAndReturnAll();
}
  • 使用建议:
    • 注意每次pipeline携带数据量
    • pipeline只能作用在一个Redis节点上
    • M操作(原子操作)和pipeline(非原子操作)区别

发布订阅

  • 模型

34R01I.png

API

  • publish发布命令

34RouV.png

  • subcribe订阅命令

34RqN4.png

  • unsubcribe取消订阅

34WF4H.png

  • 其他

位图

34fRln.png

API

  • setbit

34fvm6.png

34h9te.png

  • getbit

34hVnP.png

  • bitcount

34hUNF.png

  • bitop

34h6HK.png

  • bitpos

34h5jI.png

HyperLogLog

  • 基于HyperLogLog算法,极小空间完成独立数量统计
  • 本质是字符串
  • 三个命令
    • pfakeydd key element [element] :向hyperloglog中添加元素
    • pfcount key [key…]:计算hyperloglog的独立总数
    • pfmerge destkey sourcekey [sourcekey…]:合并多个hyperloglog

GEO

  • GEO(地理信息定位):存储经纬度,计算两地距离,范围计算等
  • geoadd

34ozJU.png

  • geopos

34TiLR.png

  • geodist

34TeJO.png


Comments

Content