欢迎来访小酒关于Redis的解读
是时候花一些时间整理整理了,我们好像从来都没有时间,好像时间一直都在那……
目录
下面给大家介绍下redis,并带大家深入浅出redis的特性,特别是应用场景,也就是我们为什么要用redis?怎么用?什么时候用?
Redis简介
先了解下Redis的一些基础知识和特性,Redis是一个开源的,基于内存的高性能键值数据库。它支持多种数据类型,如字符串、列表、集合、散列、有序集合等等。Redis具有以下特点:
- 高性能:Redis可以处理每秒数十万次的读写操作,因为它将所有数据存储在内存中,避免了磁盘I/O的开销。
- 持久化:Redis可以将内存中的数据定期保存到磁盘中,或者将每次修改操作追加到日志文件中,以实现数据的持久化。这样,即使发生系统崩溃或重启,也可以恢复数据。
- 主从复制:Redis支持主从复制模式,即一个主节点可以有多个从节点,从节点可以接收主节点的数据更新,并提供读服务。这样可以提高数据的可用性和扩展性。
- 分布式:Redis支持分布式环境下的数据分片和负载均衡,通过一致性哈希算法将数据分配到不同的节点上,以实现水平扩展。
- 事务:Redis支持事务功能,即可以将多个命令打包成一个原子操作,要么全部执行成功,要么全部失败。Redis的事务不支持回滚,也不保证隔离性。
- 发布订阅:Redis支持发布订阅模式,即一个发布者可以向一个或多个频道发送消息,一个或多个订阅者可以接收特定频道的消息。这样可以实现消息的异步通信和广播。
redis性能极高 – Redis 能读的速度大概是 110000 次/s,写的速度大概是 81000 次/s
Redis版本特性
- Redis 4.0:支持模块化扩展,增加了LFU缓存策略,改进了AOF重写机制,增加了混合持久化模式等。
- Redis 5.0:支持Stream数据类型,增加了集群管理工具,改进了RDB和AOF的兼容性,增加了客户端缓存等。
- Redis 6.0:支持多线程IO,增加了ACL访问控制,支持SSL/TLS加密,增加了客户端跟踪等。
Redis常用命令
Redis提供了丰富的命令来操作不同类型的数据。以下是一些常用命令的示例:
- 字符串:SET key value 设置一个键值对;
- GET key 获取一个键对应的值;
- DEL key 删除一个键;
- INCR key 将一个键对应的值增加1;
- APPEND key value 将一个值追加到一个键对应的值后面。
- 列表:LPUSH key value 将一个值插入到一个键对应的列表头部;
- RPUSH key value 将一个值插入到一个键对应的列表尾部;
- LPOP key 弹出并返回一个键对应的列表头部的值;
- RPOP key 弹出并返回一个键对应的列表尾部的值;
- LRANGE key start stop 返回一个键对应的列表中指定范围内的元素。
- 集合:SADD key value 将一个值添加到一个键对应的集合中;
- SREM key value 将一个值从一个键对应的集合中移除;
- SISMEMBER key value 判断一个值是否属于一个键对应的集合;
- SMEMBERS key 返回一个键对应的集合中所有元素;
- SINTER key1 key2 ... 返回多个键对应的集合的交集。
- 散列:HSET key field value 将一个字段和值设置到一个键对应的散列中;
- HGET key field 获取一个键对应的散列中指定字段的值;
- HDEL key field 删除一个键对应的散列中指定字段;
- HGETALL key 返回一个键对应的散列中所有字段和值;
- HINCRBY key field increment 将一个键对应的散列中指定字段的值增加指定数值。
- 有序集合:ZADD key score value 将一个分数和值添加到一个键对应的有序集合中;
- ZREM key value 将一个值从一个键对应的有序集合中移除;
- ZRANK key value 返回一个值在一个键对应的有序集合中的排名(从小到大);
- ZREVRANK key value 返回一个值在一个键对应的有序集合中的排名(从大到小);
- ZRANGE key start stop 返回一个键对应的有序集合中指定范围内的元素(按分数从小到大排序);
- ZREVRANGE key start stop 返回一个键对应的有序集合中指定范围内的元素(按分数从大到小排序)。
其他常用命令
dbsize 返回当前数据库 key 的数量。
# info 返回当前 redis 服务器状态和一些统计信息。
# monitor 实时监听并返回redis服务器接收到的所有请求信息。
# shutdown 把数据同步保存到磁盘上,并关闭redis服务。
# config get parameter 获取一个 redis 配置参数信息。(个别参数可能无法获取)
# config set parameter value 设置一个 redis 配置参数信息。(个别参数可能无法获取)
# config resetstat 重置 info 命令的统计信息。(重置包括:keyspace 命中数、 # keyspace 错误数、 处理命令数,接收连接数、过期 key 数)
# debug object keyg获取一个 key 的调试信息。
# debug segfault 制造一次服务器当机。
# flushdb 删除当前数据库中所有 key,此方法不会失败。小心慎用
# flushall 删除全部数据库中所有 key,此方法不会失败。小心慎用
工具命令
#redis-server:Redis 服务器的 daemon 启动程序
#redis-cli:Redis 命令行操作工具。当然,你也可以用 telnet 根据其纯文本协议来操作 #redis-benchmark:Redis 性能测试工具,测试 Redis 在你的系统及你的配置下的读写性能 $redis-benchmark -n 100000 –c 50 #模拟同时由 50 个客户端发送 100000 个 SETs/GETs 查询
#redis-check-aof:更新日志检查
#redis-check-dump:本地数据库检查
安装命令
redis下载地址:https://github.com/MicrosoftArchive/redis/releases
到redis目录下执行:redis-server.exe --service-install redis.windows.conf --loglevel verbose
启动服务:redis-server.exe --service-start
关闭服务:redis-server.exe --service-stop
登录:redis-cli.exe -h 127.0.0.1 -p 6379
查看密码:config get requirepass
设置密码:config set requirepass xxxx
生效密码:auth xxxx
查看信息:info
查看memory信息:info memory
永久生效,设置到文件中,config rewrite
具体栗子等会去应用场景看~~
三个框架
Jedis:是Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
Redisson:实现了分布式和可扩展的Java数据结构。
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
各自的优点:
Jedis:比较全面的提供了Redis的操作特性,使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列;它是基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
Lettuce:主要在一些分布式缓存框架上使用比较多;基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
建议使用:Jedis + Redisson
Redis和Memcached的区别
Redis和Memcached都是基于内存的高性能键值数据库,但是它们也有一些区别,主要体现在以下方面:
- 数据类型:Redis支持多种数据类型,如字符串、列表、集合、散列、有序集合等,而Memcached只支持字符串类型
- 持久化:Redis可以将内存中的数据定期保存到磁盘中,或者将每次修改操作追加到日志文件中,以实现数据的持久化。而Memcached不支持持久化,一旦服务器重启或崩溃,所有数据都会丢失
- 主从复制:Redis支持主从复制模式,即一个主节点可以有多个从节点,从节点可以接收主节点的数据更新,并提供读服务。而Memcached不支持主从复制,每个节点都是独立的,没有数据同步机制
- 分布式:Redis支持分布式环境下的数据分片和负载均衡,通过一致性哈希算法将数据分配到不同的节点上,以实现水平扩展。而Memcached也支持分布式环境下的数据分片,但是需要客户端来实现一致性哈希算法,并且没有负载均衡机制
- 事务:Redis支持事务功能,即可以将多个命令打包成一个原子操作,要么全部执行成功,要么全部失败。而Memcached不支持事务功能,每个命令都是独立的,并且没有回滚机制
- 发布订阅:Redis支持发布订阅模式,即一个发布者可以向一个或多个频道发送消息,一个或多个订阅者可以接收特定频道的消息。而Memcached不支持发布订阅模式,没有消息通信机制
Redis使用场景(用在哪合适?)
基于上面这些特点,它可以应用于很多场景,例如:
- 缓存:Redis可以作为缓存层来提高系统的响应速度和吞吐量,减少数据库的压力。例如可以将热点数据、静态资源、计算结果等缓存在Redis中,以提供快速访问、发短信1分钟内不可重复发送限制
- 分布式锁,分布式锁可以用setNx来实现,注意分布式锁的写法要有过期时间,并在finally中判断if(true)再删除锁,最好用Redission的看门狗
- 排行榜:Redis可以利用有序集合类型来实现排行榜功能,例如可以根据用户的积分、点击量、销量等指标来排序,并实时更新排名。举个简单的栗子
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class RankingService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 更新排行榜
* @param userNames 要更新的用户名列表
* @param score 要增加的分数
*/
public void updateRanking(List<String> userNames, int score) {
Set<String> keys = userNames.stream().map(userName -> "ranking:" + userName).collect(Collectors.toSet());
redisTemplate.opsForZSet().add(keys, score, System.currentTimeMillis() + 1000); // 设置过期时间,避免内存泄漏
}
}
@Autowired
private RankingService rankingService;
public void updateScore() {
List<String> userNames = Arrays.asList("user1", "user2", "user3"); // 要更新的用户名列表
int score = 100; // 要增加的分数
rankingService.updateRanking(userNames, score); // 更新排行榜
}
- 计数器:Redis可以利用字符串类型或散列类型来实现计数器功能,例如,可以记录用户的登录次数、文章的浏览量、商品的库存量等等,并提供原子操作和过期机制。举个简单的栗子
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class Counter {
@Autowired
private RedisTemplate<String, Long> redisTemplate;
// 设置一个计数器,键为user_id,初始值为0
public void set(String key, long value) {
redisTemplate.opsForValue().set(key, value);
}
// 增加计数器的值
public void incr(String key) {
redisTemplate.opsForValue().increment(key);
}
// 获取计数器的值
public long get(String key) {
return (long)redisTemplate.opsForValue().get(key);
}
}
public static void main(String[] args) throws Exception {
Counter counter = new Counter();
// 设置一个计数器,键为user_id,初始值为0
counter.set("user:1", 0);
// 增加用户1的访问量
counter.incr("user:1");
// 获取用户1的访问量
System.out.println(counter.get("user:1")); // 输出:1
}
- 队列:Redis可以利用列表类型来实现队列功能,例如,可以将任务、消息、事件等放入队列中,然后由消费者从队列中取出并处理。Redis还支持阻塞式的队列操作,即如果队列为空,消费者可以等待直到有元素可取
- 社交网络:Redis可以利用其集合类型和有序集合类型来实现社交网络功能,例如,可以存储用户的好友关系、粉丝关系、兴趣标签等,并进行交集、并集、差集等操作。Redis还可以利用其发布订阅模式来实现消息的推送和广播。举个简单的栗子
user_friends: {user1: [user2, user3], user4: [user5]}
user_followers: {user1: [user6, user7], user8: [user9]}
// 添加好友
public void addFriend(String userId, String friendId) {
redisTemplate.opsForSet().add(USER_FRIENDS, userId, friendId);
}
// 取消关注
public void unfollow(String userId, String followerId) {
redisTemplate.opsForSet().remove(USER_FOLLOWERS, userId, followerId);
}
// 获取好友列表
public Set<String> getFriends(String userId) {
return redisTemplate.opsForSet().members(USER_FRIENDS, userId);
}
// 获取某个用户的所有粉丝和所有好友的交集
public Set<String> getIntersection(String userId) {
Set<String> friends = getFriends(userId);
Set<String> followers = getFollowers(userId);
return friends.stream().filter(followers::contains).collect(Collectors.toSet());
}
// 获取某个用户的所有粉丝和所有好友的并集
public Set<String> getUnion(String userId) {
Set<String> friends = getFriends(userId);
Set<String> followers = getFollowers(userId);
return Stream.concat(friends.stream(), followers.stream()).collect(Collectors.toSet());
}
// 获取某个用户的所有粉丝和所有好友的差集
public Set<String> getDifference(String userId) {
Set<String> friends = getFriends(userId);
Set<String> followers = getFollowers(userId);
return Stream.concat(friends.stream(), followers.stream()).filter((id) -> (!friends.contains(id))).collect(Collectors.toSet());
}
- 地理位置:Redis可以利用其地理位置类型来实现地理位置功能,例如,可以存储用户的经纬度信息,并进行距离计算、范围查询、附近查询等操作。
- 会话管理:Redis可以利用其字符串类型或散列类型来实现会话管理功能,例如,可以将用户的登录状态、权限信息、购物车信息等存储在Redis中,并设置过期时间。这样可以提高系统的安全性和一致性。
还有其他的一些应用场景,具体问题具体分析吧,现在各个企业越来越强依赖redis了,原来我同事去的一家打车公司更是这样,某块业务所有东西都放到redis中,只为高速实时,但是这带来的挑战是另一个层面了,我们后续再说
Redis数据类型
Redis支持多种数据类型,每种数据类型都有其特点和使用场景。下面是Redis数据类型的介绍:
- 字符串String:字符串是Redis最基本的数据类型,它可以存储任何形式的数据,如文本、数字、二进制等。字符串的最大长度为512MB。字符串可以用于缓存、计数器、会话管理等场景。
- 列表List:(双向链表)列表是一个有序的字符串序列,它可以在头部或尾部操作元素。列表可以用于队列、栈、最新消息等场景。
- 集合Set:(底层实现是一个 value 永远为 null 的 HashMap)集合是一个无序的字符串集合,它可以添加或删除元素,也可以判断元素是否存在。集合可以用于标签、好友关系、去重等场景。
- 散列Hash:散列是一个由字段和值组成的映射表,它可以操作字段对应的值。散列可以用于存储对象、计数器等场景。这个结构比较省空间,如果是那种大数据量的尽量选择使用散列
-
优点:
1.可以实现二分查找
2.减少网络消耗
3.维护集合时,查询比List更高效
-
- 有序集合Sorted Set:(内部是HashMap 和跳跃表(SkipList)来保证数据的存储和有序)有序集合是一个按分数排序的字符串集合,可以添加删除元素,也可以根据分数或排名范围查询元素。可以用于排行榜、延时任务等场景。
- 地理位置:地理位置是一个由经纬度坐标和名称组成的地理信息表,它可以添加或删除地理位置信息,也可以根据距离或范围查询附近的地理位置信息。可以用于地图服务、附近搜索等场景。
Redis线程模型
内部使用文件事件处理器file event handler,其是单线程的,大家所说的单线程其实是这部分,它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理
文件处理器结构:
- 多个socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器
一次通信多个socket可能产生不同的操作,每个操作对应不同的文件事件,IO多路复用程序会监听多个socket,将其产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,交给对应的事件处理器进行处理,命令请求处理器读取key-value并在自己内存中完成key-value的设置,操作完成后将命令回复处理器关联
多线程工作模型
Redis 的多线程工作模型是指 Redis 采用多个 IO 线程来处理网络请求,提高网络请求处理的并行度。需要注意的是,Redis 多 IO 线程模型只用来处理网络读写请求,对于 Redis 的读写命令,依然是单线程处理。
这种架构的优点在于可以将网络通信和命令执行分离开来,从而提高了 Redis 的并发性能和可靠性
单线程模型为什么效率会高?
- 简单:单线程模型避免了多线程之间的竞争和同步问题,使得代码更简洁易懂,也更容易维护和调试。
- 快速:单线程模型避免了多线程之间的上下文切换和资源消耗,使得执行效率更高,也更容易实现高性能。
- 一致:单线程模型保证了命令的顺序执行和原子性,使得数据的一致性和完整性更容易保证。
当然,单线程模型也有一些缺点,主要是:
- 无法利用多核CPU的优势,只能使用一个核心的计算能力。
- 无法处理阻塞式的操作,如磁盘I/O、网络I/O等,会导致线程挂起,影响服务质量。
为了解决这些问题,Redis采用了以下策略:
- 将所有数据存储在内存中,避免磁盘I/O的开销。
- 使用多路复用技术,如epoll、kqueue等,来同时监听多个客户端的连接和请求,避免网络I/O的阻塞。
- 使用多进程或多线程来进行持久化操作,如RDB、AOF等,避免影响主线程的执行。
- 使用多进程或多线程来进行主从复制操作,避免影响主线程的执行。
通过这些策略,Redis可以充分发挥单线程模型的优势,同时规避其缺点,从而实现高性能和高可用的服务。
感谢观看,欢迎研讨,下面是小酒其他的几篇关于redis的文章,欢迎指导👇🏻