用于Key的命令
SET
: 为Key设置值,实例:SET runoobkey redisEXISTS
: 查询Key是否存在,存在返回1,否则返回0。实例:EXISTS runoob-new-keyPPTL
: 以毫秒为单位返回 key 的剩余过期时间。实例:PTTL KEY_NAMETTL
: 秒为单位返回 key 的剩余过期时间。 实例: TTL KEY_NAME用于字符串的命令
SET
: 设置指定key的值。实例:SET KEY_NAME valueGET
: 获取指定key的值。实例:GET KEY_NAMEINCR
: 把key中存储的数字值增1。实例:INCR KEY_NAMEDECR
: 把key中存储的数字值减1。实例:DECR KEY_NAME用于Hash表的命令
HGET
: 获取hash表中的指定字段的值。实例:HGET KEY_NAME FIELD_NAMEHGETALL
: 获取hash表中所有字段和 对应的值。实例:HGETALL KEY_NAMEHKEYS
: 获取hash表中所有字段。实例:HKEYS KEY_NAME用于List集合的命令
LRANGE
: 获取集合指定范围内的元素。实例:LRANGE key start stop 获取下标从start到stop的元素Scan
1、SCAN命令是增量的循环,每次调用只会返回一小部分的元素。所以不会有KEYS命令的坑。 SCAN命令返回的是一个游标,从0开始遍历,到0结束遍历。scan也有如下一些特设: (1)查询复杂度为O(n),通过游标分步进行,不会阻塞线程 (2)提供limit参数,控制每次返回结果的最大条数。这里值得注意的是,limit只是一个提示,返回的结果可多可少 (3)同keys一样,它也提供模式匹配功能 (4)返回的结果可能会重复,需要客户端去重 (5)遍历过程中,如果有数据修改,改动后的数据不一定能遍历到 (6)单次返回结果是空的并不意味着遍历结束,而是看返回的游标值是否为0
2、使用实例: (1)先插入10个key
(2)然后使用Scan进行扫描:
这时候返回key3、key5,返回的游标为744 (3)使用744游标继续查询:
返回key1、key4,返回游标为268。同理,继续查询可以查询出匹配key*的所有key,直到游标为0为止,结果如下图:
1、概念:
HyperLogLog这种数据结构用于解决去重统计问题,它提供了不精确的去重计数方案,标准误差在0.81%。
2、使用方法:
(1)pfadd:增加计数,pfadd KEY_NAME id
(2)pfcount:获取计数,pfcount KEY_NAME
(3)pfmerge:用于将多个pf计数值累加在一起形成一个新的pf值
3、原理:较为复杂,后续补充
1、概念:指的是客户端允许将多个请求依次发给服务器,过程中而不需要等待请求的回复,在最后再一并读取结果即可。主要作用在于提高吞吐量
上图中可以看出,所有的请求合并为一次IO,除了时延可以降低之外,还能大幅度提升系统吞吐量。 2、实例:
package com.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 管道处理实例
* @author huangy on 2018/8/5
*/
public class Piple {
private static Logger logger = LoggerFactory.getLogger(Piple.class);
private static JedisPool jedisPool;
// 总共有多少个并发任务
private static final int taskCount = 50;
// 管道大小
private static final int batchSize = 10;
// 每个任务处理的命令数
private static final int cmdCount = 1000;
// redis服务地址
private static final String host = "127.0.0.1";
// 端口
private static final int port = 6379;
// 是否使用管道
private static final boolean usePipeline = true;
// 保存执行结果
private static List<Object> results = new ArrayList<>(cmdCount);
private static void init() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(100);
poolConfig.setTestOnBorrow(false);
poolConfig.setTestOnReturn(false);
jedisPool = new JedisPool(poolConfig, host, port);
}
public static void main(String[] args) throws InterruptedException {
// 初始化redis
init();
long beginTime = System.currentTimeMillis();
// 使用多线程执行
ExecutorService executor = Executors.newCachedThreadPool();
// 使用CountDownLatch保证所有任务都执行完
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
executor.submit(new DemoTask(i, latch));
}
latch.await();
executor.shutdownNow();
long endTime = System.currentTimeMillis();
// 耗时
logger.info("execution cost time(s)={}", (endTime - beginTime) / 1000.0);
// 所有结果
logger.info("results, size={}, all={}", results.size(), results);
}
private static Jedis get() {
return jedisPool.getResource();
}
private static class DemoTask implements Runnable {
private int id;
private CountDownLatch latch;
private DemoTask(int id, CountDownLatch latch) {
this.id = id;
this.latch = latch;
}
public void run() {
logger.info("Task[{}] start.", id);
try {
if (usePipeline) {
runWithPipeline();
} else {
runWithNonPipeline();
}
} finally {
latch.countDown();
}
logger.info("Task[{}] end.", id);
}
/**
* 不使用管道处理redis命令
*/
private void runWithNonPipeline() {
// 总共处理cmdCount个
for (int i = 0; i < cmdCount; i++) {
Jedis jedis = get();
try {
jedis.set(key(i), UUID.randomUUID().toString());
} finally {
if (jedis != null) {
jedisPool.returnResource(jedis);
}
}
if (i % batchSize == 0) {
logger.info("Task[{}] process -- {}", id, i);
}
}
}
/**
* 使用管道处理redis命令
* 总共处理cmdCount个redis命令,每次处理batchSize个
*/
private void runWithPipeline() {
for (int i = 0; i < cmdCount;) {
Jedis jedis = get();
try {
Pipeline pipeline = jedis.pipelined();
int j;
for (j = 0; j < batchSize; j++) {
if (i + j < cmdCount) {
pipeline.set(key(i + j), UUID.randomUUID().toString());
} else {
break;
}
}
// pipeline.sync();
// 查看结果,使用syncAndReturnAll
List<Object> tem = pipeline.syncAndReturnAll();
logger.info("Task thread id={} pipeline, cmd end={}, result size={}", id, i + j, tem.size());
results.addAll(tem);
i += j;
} finally {
if (jedis != null) {
jedisPool.returnResource(jedis);
}
}
}
}
private String key(int i) {
return i + "";
}
}
}
上述实例使用50个线程进行处理,每个线程分别处理了1000个redis命令(set),在不使用管道的情况下,耗时大约5秒;使用管道的情况下,耗时大约2秒。
3、缺点: (1)pipeline机制可以优化吞吐量,但无法提供原子性/事务保障,而这个可以通过Redis-Multi等命令实现。 (2)部分读写操作存在相关依赖,无法使用pipeline实现
1、概念:redis实现异步队列,一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。 2、sleep优化: (1)原因:客户端是通过队列的pop操作来获取消息,然后进行处理,处理完了再接着获得消息。可是如果队列空了,客户端就会不停的pop,这样的操作不能获取数据,并且拉高了客户端的CPU,redis的QPS也会拉高,降低redis的查询效率。 (2)使用sleep解决这个问题,让线程睡一会,比如说1s。可以让客户端的CPU降下来。 3、blpop/brpop 优化: (1)原因:sleep的方法可以优化,但是睡眠会导致消息延迟增大。 (2)使用blpop(阻塞读):阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻醒过来。 (3)PS:blpop指队列左边出数据。brpop指队列右边出数据 (4)注意点:当线程一直sleep,服务器一般会断开连接,这时候blpop会抛出异常,所以在编写客户端消费者的时候要小心,注意捕获异常,还要重试。 4、延时队列: (1)延时队列可以通过redis的zset(有序列表)来实现。 (2)简单的代码实例:
/**
* 延时队列案例
* @author huangy on 2018/8/26
*/
@Component
public class QueueDemo {
private static final Logger LOGGER = LoggerFactory.getLogger(QueueDemo.class);
@Resource
private Jedis jedis;
private static final String QUEUE_KEY = "queueKey";
/**
* 将任务扔到延时队列中
* 使用时间作为score
* @param msg 任务(一般使用json格式的字符串)
*/
public void delay(String msg) {
jedis.zadd(QUEUE_KEY, System.currentTimeMillis() + 5000, msg);
}
/**
* 从延时队列中获取任务,利用时间范围作为score的范围
* zrangeByScore :返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员
*/
public void loop() {
while (!Thread.interrupted()) {
Set<String> values = jedis.zrangeByScore(QUEUE_KEY,
0, 577742424, 0, 1);
if (values.isEmpty()) {
try {
Thread.sleep(500);
} catch (Exception e) {
LOGGER.error("loop sleep fail", e);
break;
}
continue;
}
String s = values.iterator().next();
// 利用zrem判断是否拿到任务
if (jedis.zrem(QUEUE_KEY, s) > 0) {
// 拿到这个任务了,进行处理
LOGGER.info("loop msg, msg={}", s);
}
}
}
}
hash(key) % length
,当缓存服务器变化时,length字段变化,导致所有缓存的数据需要重新进行HASH运算,才能使用。而这段时间如果访问量上升了,容易引起服务器雪崩。因此,引入了一致性哈希对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node,这有点儿像浏览器页面的302 redirect跳转。 Redis Cluster是Redis 3.0以后才正式推出,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验。
和MySQL主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,下图为级联结构。
Redis主从复制可以根据是否是全量分为全量同步和增量同步。 1、全量同步 Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下: 1)从服务器连接主服务器,发送SYNC命令; 2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; 3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; 4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; 5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; 6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
Redis全量同步过程 完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。
2、增量同步 Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
3、Redis主从同步策略 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
4、其他:Redis 2.8以后提供了PSYNC优化了断线重连的效率 http://blog.csdn.net/sk199048...
案例情景 如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。并且如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象
解决方法 1、使用互斥锁(这里使用redis的setnx实现分布式锁): 只有获取锁,才能去访问数据库,加载数据,保证同一时刻只有一个请求访问数据库,避免数据库崩溃。但这样子可能造成请求延时比较久的问题。
2、让缓存过期时间不那么集中: 比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
3、做二级缓存 A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2。A1缓存失效时间设置为短期,A2设置为长期。这个在业务追求数据一致性要求不高的情况下,可以使用。
4、缓存永远不过期 这里的“永远不过期”包含两层意思: (1) 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。 (2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期. 从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
案例情景 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透
解决方法 使用Bloom filter(布隆过滤器)。当用户查询某个row时,可以先通过内存中的布隆过滤器过滤掉大量不存在的row请求,然后再到数据库进行查询。
参考: http://www.zsythink.net/archi... http://blog.csdn.net/upxiaofe... http://www.runoob.com/redis/r... https://blog.csdn.net/sk19904... https://www.cnblogs.com/littl... https://blog.csdn.net/fei3342...