1.Redis简介
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库
2.Redis的数据结构
- string
set mykey somevalue
get mykey
set mykey somevalue nx
set mykey somevalue xx
set mykey hello ex 10 nx
set mykey hello xx 10 nx
del mykey
// 判断key的数据类型
type key
set counter 100
incr counter
incrby counter 10
decr counter
decrby counter 100
exists count
- Lists
rpush mylist A
rpush mylist B
lpush mylist first
rpush mylist A B C D
lrange mylist 0 -1
rpop mylist
lpop mylist
ltrim mylist 0 2
// 和rpop基本一样,但是如果tasks无数据,那么将会等待5s钟
brpop tasks 5
- Hashes
hmset user:1000 username antirez birthyear 1977 verified 1
// 获取指定的key
hget user:1000 username
// 获取全部的key
hgetall user:1000
// 获取多个key
hmget user:1000 username birthyear
// 对birthyear进行加10操作
hincrby user:1000 birthyear 10
- Sets
sadd myset 1 2 3
smembers myset
// 判断某个值是否在key里面
sismember myset 3
sadd news:1000:tags 1 2 5 77
smembers news:1000:tags
- Sorted sets
zadd hackers 1940 "Alan Kay"
zrange hackers 0 -1
zrange hackers 0 -1 withscores
zrangebyscore hackers -inf 1950
zremrangebyscore hackers 1940 1960
zrank hackers "Anita Borg"
3.Redis的使用场景
1、缓存
缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。
2、排行榜
很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。
/**
* 测试商品排行榜
*/
public static void testTop(){
String rankKey = "product_mobile_phone";
Jedis jedis = new Jedis("localhost");
jedis.zadd(rankKey, 100,"iphone_xs");
jedis.zadd(rankKey, 110,"iphone_xs_max");
jedis.zadd(rankKey, 90,"xiaomi_9");
jedis.zadd(rankKey, 120,"huawei_p30");
Set<String> products = jedis.zrevrange(rankKey, 0, -1);
System.out.println(products);
}
3、计数器
什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。
4、分布式会话
集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。
5、分布式锁
在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。
import java.util.Collections;
import redis.clients.jedis.Jedis;
/**
* @Description
* @Author luohong <luohong.lh@alibaba-inc.com>
* @Date 06/08/19
*/
public class RedisClient {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
final Jedis jedis = new Jedis("localhost");
String lockKey = "hello";
String requestId = "world";
try{
if (tryGetLock(jedis, lockKey, requestId, 10)) {
System.out.println("get lock success");
} else {
System.out.println("get lock failed");
}
}finally {
tryUnlock(jedis, lockKey, requestId);
}
}
}).start();
}
}
/**
* 尝试获取分布式锁
* @param jedis
* @param lockKey
* @param requestId
* @param expireTime
*/
public static boolean tryGetLock(Jedis jedis, String lockKey, String requestId, int expireTime){
String result = jedis.set(lockKey, requestId, "NX", "EX", 10);
return "OK".equals(result);
}
/**
* 释放锁
* @param jedis
* @param lockKey
* @param requestId
* @return
*/
public static boolean tryUnlock(Jedis jedis, String lockKey, String requestId){
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return "OK".equals(result);
}
}
get lock failed
get lock failed
get lock failed
get lock failed
get lock failed
get lock failed
get lock failed
get lock success
get lock failed
get lock failed
6、 社交网络
点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。
7、最新列表
Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。
8、消息系统
消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。
4.Redis高性能的原理
- 绝大部分请求是纯粹的内存操作(非常快速)
- 采用单线程,避免了不必要的上下文切换和竞争条件
- 非阻塞IO - IO多路复用
- 简单的数据结构,大部分的读/写操作的性能都是O(n)
由于进程的执行过程是线性的,当我们调用低速系统I/O(read,write,accept等等),进程可能阻塞,此时进程就阻塞在这个调用上,不能执行其他操作,阻塞很正常.
接下来考虑这么一个问题:一个服务器进程和一个客户端进程通信,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将阻塞,直到客户端调用write(sockfd,but,size)发来数据。在一个客户和服务器通信时这没什么问题;当多个客户与服务器通信时当多个客户与服务器通信时,若服务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达套接字sockfd2时,服务器不能处理,仍然阻塞在read(sockfd1...)上;此时问题就出现了,不能及时处理另一个客户的服务,咋么办?
I/O多路复用来解决!I/O多路复用!继续上面的问题,有多个客户连接,sockfd1、sockfd2、sockfd3...sockfdn。同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用read读取收到消息的sockfd,然后又循环回select阻塞;这样就不会因为阻塞在其中一个上而不能处理另一个客户的消息。“I/O多路复用”的英文是“I/O multiplexing”,可以百度一下multiplexing,就能得到这个图: