Redis SpringBoot项目学习
Redis 是一个高性能的key-value内存数据库。它支持常用的5种数据结构:String字符串、Hash哈希表、List列表、Set集合、Zset有序集合 等数据类型。
Redis它解决了2个问题:
第一个是:性能
通常数据库的读操作,一般都要几十毫秒,而redisd的读操作一般仅需不到1毫秒。通常只要把数据库的数据缓存进redis,就能得到几十倍甚至上百倍的性能提升。
第二个是:并发
在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常,甚至卡死在数据库中。为了解决大并发卡死的问题,一般的做法是采用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
1.String
Redis 中的字符串,直接就是按照字节数组(二进制数据)的方式存储的!不会做任何的编码转换,存的是啥,取出来就还是啥!不仅仅可以存储文本数据,还支持存储整数、文本、JSON、二进制数据等。
使用场景
- 缓存: 存储临时数据,如用户会话、页面缓存。
- 计数器: 用于统计访问量、点赞数等,通过原子操作增加或减少,
字符串类型是Redis最基础的数据类型,关于字符串需要特别注意:
首先Redis 中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他4种数据结构的学习奠定基础。
其次,如下图所示,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似JSON、XML格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、音频、视频等。不过一个字符串的最大值不能超过512 MB。
2.Hash
- Redis的hash数据结构,其实就是string的升级版,它把string 数据结构的key value,中的value类型升级为hash(和java的hash一样的结构)
Map<String, HashMap<String,String>> hash=new HashMap<String,HashMap<String,String>>();
- 每个 hash的存储大小: 可以存储 2的(32 - 1)方的 键值对(40多亿)
使用场景: - 商品详情:存储商品的各个属性,方便快速检索。
注
Redis存储java对象,一般是String 或 Hash 两种,那到底什么时候用String ? 什么时候用hash ?
String的存储通常用在频繁读操作,它的存储格式是json,即把java对象转换为json,然后存入redis.
Hash的存储场景应用在频繁写操作,即,当对象的某个属性频繁修改时,不适用string+json的数据结构,因为不灵活,每次修改都需要把整个对象转换为json存储。
如果采用hash,就可以针对某个属性单独修改,不用序列号去修改整个对象。例如,商品的库存、价格、关注数、评价数经常变动时,就使用存储hash结果。
Redis实现双十一购物车
用户登录状态下添加商品到购物车
未登录用户状态下添加购物车
再次登录账号查看购物车(涉及未登录时的商品合并操作)
redis解决了分布式系统的session一致性问题
Session有什么作用?
Session 是客户端与服务器通讯会话跟踪技术,服务器与客户端保持整个通讯的会话基本信息。
客户端在第一次访问服务端的时候,服务端会响应一个sessionId并且将它存入到本地cookie中
在之后的访问会将cookie中的sessionId放入到请求头中去访问服务器,
如果通过这个sessionid没有找到对应的数据,那么服务器会创建一个新的sessionid并且响应给客户端。
分布式session的问题
在分布式系统中,通常会将 Session 存储在 Redis 中来实现 分布式 Session,这样就可以在多台服务器之间共享 Session 数据。实现分布式Session通常使用Redis的Hash结构:
- 在用户登录成功后,将 Session 数据存储在 Redis 中。
- 将 Redis 中的 Session 数据的 Key 设置为一个全局唯一的 ID,一般使用类似于“session:token”这样的格式,其中 token 是一个随机生成的字符串,用来标识这个 Session 数据。
- 在客户端返回响应的同时,将 Session ID(即 token)以 Cookie 的形式返回给客户端。客户端在后续的请求中都会携带这个 Cookie。
- 在后续的请求中,服务器会从客户端传递过来的 Cookie 中获取 Session ID,然后根据这个 ID 从 Redis 中获取对应的 Session 数据。如果 Redis 中没有找到对应的 Session 数据,那么就表示这个请求无法通过认证。
- 在用户退出登录或 Session 失效时,需要将 Redis 中的对应 Session 数据删除。
3.List
List类型是一个双端链表的结构,容量是2的32次方减1个元素,即40多亿个;
其主要功能有push、pop、获取元素等;一般应用在栈、队列、消息队列等场景。
使用场景:
- 消息队列:用于简单任务调度、消息传递等场景,通过 LPUSH 和 RPOP 操作实现生产者消费者模式
- 历史记录:存储用户操作的历史记录,便于快速访问
常用命令
以头插或尾插方式插入指定key队列中一个或多个元素
[LR]PUSH key value1 [value2 ...]
获取列表指定范围内的元素
LRANGE key start stop
Redis使用List实现商品分页展示
service
public void runProductPage() {while (true){//模拟从数据库读取100件特价商品,用于加载到聚划算的页面中List<Product> list=this.products();//采用redis list数据结构的lpush来实现存储this.redisTemplate.delete(Constants.JHS_KEY);//lpush命令this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY,list);try {//间隔一分钟 执行一遍Thread.sleep(1000*60);} catch (InterruptedException e) {e.printStackTrace();}log.info("runJhs定时刷新..............");}}
controller
/*** 分页查询:在高并发的情况下,只能走redis查询,走db的话必定会把db打垮*/@GetMapping(value = "/find")public List<Product> find(int page, int size) {List<Product> list=null;long start = (page - 1) * size;long end = start + size - 1;try {//采用redis list数据结构的lrange命令实现分页查询list = this.redisTemplate.opsForList().range(Constants.JHS_KEY, start, end);if (CollectionUtils.isEmpty(list)) {//TODO 走DB查询}log.info("查询结果:{}", list);} catch (Exception ex) {//这里的异常,一般是redis瘫痪 ,或 redis网络timeoutlog.error("exception:", ex);//TODO 走DB查询}return list;}
Redis使用List解决缓存击穿问题
采用主从轮询原理解决缓存击穿问题
service
public void runProductPageAB() {while (true){//模拟从数据库读取100件 特价商品,用于加载到聚划算页面List<Product> list=this.products();//先更新Bthis.redisTemplate.delete(Constants.JHS_KEY_B);this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY_B,list);//再更新Athis.redisTemplate.delete(Constants.JHS_KEY_A);this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY_A,list);try {Thread.sleep(1000*60);} catch (InterruptedException e) {e.printStackTrace();}log.info("重新刷新..............");}
}
controller
try {//采用redis,list数据结构的lrange命令实现分页查询。list = this.redisTemplate.opsForList().range(Constants.JHS_KEY_A, start, end);if (CollectionUtils.isEmpty(list)) {//用户先查询缓存A(上面的代码),如果缓存A查询不到(例如,更新缓存的时候删除了),再查询缓存Bthis.redisTemplate.opsForList().range(Constants.JHS_KEY_B, start, end);}log.info("{}", list);}
Redis List实现微信抢红包
抢红包并发场景分析
像微信抢红包的高峰期一般是在年底公司开年会和春节2个时间段,高峰的并发量是在几千万以上
高峰的抢红包有3大特点:
- 包红包的人多:也就是创建红包的任务比较多,即红包系统是以单个红包的任务来区分,特点就是在高峰期红包任务多。
- 抢红包的人更多:当你发红包出去后,是几十甚至几百人来抢你的红包,即单红包的请求并发量大。
- 抢红包体验:当你发现红包时,要越快抢到越开心,所以要求抢红包的响应速度要快,一般1秒内响应。
微信抢红包的技术实现原理
红包系统在现代互联网应用中非常普遍,尤其在电商平台和社交应用中,经常用于促销活动。设计一个能够承载百亿级超大流量的红包系统,需要全面考虑系统架构、数据库设计、缓存策略、负载均衡、安全性和高可用性等多个方面。本文将详细介绍如何设计并实现一个高效、可靠的红包系统。
- 包红包
1.先把金额拆解为小金额的红包,例如 总金额100元,发20个,用户在点保存的时候,就自动拆解为20个随机小红包。
2.这里的存储就是个难题,多个金额(例如20个小金额的红包)如何存储?采用set?
list? hash? - 抢红包
高并发的抢红包时核心的关键技术,就是控制各个小红包的原子性。
例如 20个红包在500人的群里被抢,20个红包被抢走一个的同时要把红包的库存减1,即剩下19个。
在整个过程中抢走一个 和 红包库存减1个 是一个原子操作。
那
list的pop操作弹出一个元素的同时会自动从队列里面剔除该元素,它是一个原子性操作。
4.Set
集合是无序且不重复的字符串集合,使用哈希表实现,支持快速查找和去重操作。
使用场景
- 标签系统:存储用户的兴趣标签,避免重复,
- 唯一用户集合:记录访问过某个页面的唯一用户,方便进行分析。
常用命令
向名称为key的set中添加元素member
sadd key member
删除名称为key的set中的元素member
srem key member
返回名称为key的set的所有元素
smembers key
member是否是名称为key的set的元素
sismember key member
返回名称为key的set的大小
scard key
将member元素从source集合移动到destination集合
smove srckey dstkey member
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u1"
4) "u3"
127.0.0.1:6379> smembers blacklist
(empty list or set)
127.0.0.1:6379> smove users blacklist u1
(integer) 1
127.0.0.1:6379> smembers users
1) "u2"
2) "u4"
3) "u3"
127.0.0.1:6379> smembers blacklist
1) "u1"
随机返回名称为key的set的一个元素
srandmember key
随机返回并删除名称为key的set中一个元素
spop key
求key1,key2,…keyN的集合的交集
sinter key1 key2 keyN
求key1,key2,…keyN的交集并将交集保存到dstkey的集合
sinterstore dstkey key1 key2 keyN
求key1,key2,…keyN的并集并将交集保存到dstkey的集合
sunionstore dstkey key1 key2 keyN
求key1,key2,…keyN的差集并将交集保存到dstkey的集合
sdiffstore dstkey key1 key2 keyN