在 web 开发中,http 是无状态的协议,而为了保持会话状态,就需要维护一个列表,列表的数据格式为 key:value。
http 通过携带 key 进行访问,后台通过 key 获取 value 值,来确定此次 http 请求会话。
早期 该列表的维护保存在应用内存 session 中。在多机冗余部署的时候,会出现 session 共享的问题。业界有很多种解决方案,如 tomcat 配置 session 共享等。
使用 Redis 进行 session 集中管理是比目前较常用的一种方式。使用方式也很简单,使用 redis 中的 string 数据结构,就能够完成。
总结:通过集中存储 用户session 数据到Redis 中,所有需要进行认证的接口,都从该Redis 中获取 session 数据,进行认证判定。
待过的一家创业公司,给出的需求:简单的统计平台用户活跃数据(单位/天)。
设想一:使用Mysql 。
思路:每个用户,每日一条数据
存在问题:
设想一结论:从长远来看,不可取。
设想二:使用Mysql。
思路:所有用户每日只存一条数据。
分析:
假设表结构如下
dateTime datetime ‘时间’
content varchar(m) ‘日活’content 格式如下 0000010101 -> 表示:当前 dateTime 日期中,有3个人活跃。
不过很明显有个致命的问题,一个用户需要一个站位,1个varchar 站位1个字节,多少个用户多少个字节,且 varchar 最多 65532 个字节,明显不行。
假设表结构如下
dateTime datetime ‘时间’
content bigint(m) ‘日活’如果通过数值,然后转换2二进制。如:id = 1 00 00 的用户进入活跃为1,则二进制位在1万位,对应的数字为 2^10000 ,超出想象
设想二结论:思路可以,通过Mysql 实现不靠谱。
设想三:使用文件存储。
思路:与设想二同,使用二进制格式存储。
分析:
1、在Linux 系统中,1000 个用户的偏移大约 1k
-rw-r–r--. 1 root root 1020 5月 12 22:31 20190515
1 0000 个用户大约 10k ,可以得出 100 万用户大约 1M左右。
从数据的存储上来说能够实现。但是存在一个问题,就是统计计算复杂,加上io操作的损耗,同样不可取。
设想三结论:对比设想二,数据存储得到了解决,但是统计变得复杂,不可取。
设想四:使用 Redis 作为存储介质。
思路:与设想二同,采取二进制存储结构。
分析:
我们通过 Redis 中 bitmap 操作的相关说明进行分析。
设值:SETBIT key offset value
官方介绍
When setting the last possible bit (offset equal to 232 -1) and the string value stored at key does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time。
setbit 进行设值的时候,空间是动态分配的。也就是说,日活存量随着用户增加,占用的内存随着增加,无需一次性进行空间分配。这个棒棒哒。
我们来简单的验证一下,官方的说法是否就是正确的。
验证结论:
1、SETBIT的空间分配是,动态分配的。
2、BitMap 空间占用小。
单单从 value 值来说,占用的内存空间 1000 0000 / 8 / 1024 / 1024 = 1.19M。加上key 占用的空间等。当用户达到 1000 0000,一条日活数据,也就占用大约 2.5M 的空间。
且setbit 偏移量最大为 2^32 -1 大约为 42 9496 7296(约42亿) ,value 占用内存空间最大为 512M。
统计:BITCOUNT key
官方介绍
Count the number of set bits (population counting) in a string.
统计指定bitmap 格式的key 中,二进制偏移被设置为1的数量。
也就是,(key -> value)20190515 -> 0010101010101,中1出现的次数。
我们可以通过该方法,统计某个时间点 日活数量。
BITCOUNT 能够直接进行计算,操作简便,那么其性能怎么样呢?
官方给出了,BITCOUNT 时间复杂度 :O(N)。可以理解随着偏移量增多,也就是用户量增多,计算时间会延长。那究竟时间是多长?官方举了个简单的例子进行描述。
456 字节的查询的时间花费和 GET,INCR 命令的 O(1)操作一样。
也就是 用户量 3648 几乎没什么影响。那在增加10 - 20 倍呢,经过简单测试,几乎也没什么影响。足以应对,小公司中的用户增长。
复杂统计:BITOP operation destkey key [key …]
官方介绍
The BITOP command supports four bitwise operations: AND, OR, XOR and NOT, thus the valid forms to call the command are:
BITOP AND destkey srckey1 srckey2 srckey3 … srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 … srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 … srckeyN
BITOP NOT destkey srckeyBITOP 能够对多个 bitmap 进行位运算。通过位运算我们能够计算更复杂的计算。
比如:统计 20190515,20190516,20190517 这3天的日活总数。
20190515 -> 01010100000 3
20190516 -> 10000000000 1
20190517 -> 00000000111 3
----------------------------------
11010100111 7
使用 BITOP OR DELAY_DAY_3 20190515 20190516 20190517 就能够得到 3日日活数据。
总结:使用 Redis 提供的 bitmap 数据结构,加上 BITCOUNT、BITOP 命令进行日活统计。
用户签到活动,每日一签,并计算用户周签到数,月签到数,达到标准进行奖励发放。
设想一:Mysql,每个用户每天一条签到记录。
表结构:
sign_id bigint(10) 主键id
u_id bigint(10) 用户id
sign_time datetime 签到时间
从数据存储来说:1个用户,如果每天签到,一年假设 365 天,占用空间,大约:(8+8+8)* 365 = 8760 b = 0.0083 MB
从数据展示来说:针对没有签到的时间,没有记录,如果需要返回某月的,签到详细记录。没有签到的日期,需要补全,并返回日期数据到前台。
设想二:Redis 每个用户,所有的签到记录,只有1条数据。
使用 bitmap 数据格式保存数据。存入的数据格式为:用户唯一标识 : 签到记录
其中签到记录,起始时间为功能上线那天。之后根据时间天差,作为 offset 偏移量进行设值。
从数据存储来说:1个用户,如果每天签到,一年假设 365天,占用空间,大约:365 bit = 0.0000435 MB。
从数据展示来说:需要遍历某个偏移量之间的所有值获取每个偏移量的值。
结果
选择设想二,无论从空间占用,还是数据展示统计来说,redis 都是更优的选择。
关注点
在实现的过程中,这里计算的月签到数,需要使用到 BITCOUNT 命令。
BITCOUNT 用法:BITCOUNT key 【start】 【end 】
其中 start 和 end 代表的是字节 而不是 bit 。
例如:BITCOUNT SIGN_USER_1 0 1 查询的是 SIGN_USER_! 用户,从签到功能上线起 ,前8天内的签到统计。
所以在设计,数据存储的时候我们需要如下的存储方式:
首先 BITCOUNT 计算 8 个bit 起,一个月最多 31 天,使用 4 个字节 32 个 byte 存储1个月的签到数据,而此时 1个用户每年的数据占用空间变为 32 * 12 = 384 bit,和之前理想化的 365 bit 相比 相差无几。
在后台管理系统中,我们使用的是 Shiro 权限控制框架。而针对权限控制,所需要的就是当前登录用户,所拥有的权限。通常 查询一个用户拥有的权限都是相对耗时的。在用户通过后台的鉴权时,保存用户相关权限到 redis 中,当用户进行后台操作时的验权步骤中,能够较少因为验权查询数据库导致的响应时间过长。
在我们系统中,app 首页 就进行了一系列的汇总统计,如 累计订单汇总,累计交易总数汇总,累计交易金额汇总等之类的复杂汇总查询。
查询sql耗时较长,导致首页数据响应延迟高。使用 Redis 对数据进行缓存,根据可容忍的时效性进行过期控制。提高接口除第一次加载数据到缓存外的响应速度。
Redis实现分布式锁
Redis 的用法除了以上这些自身用过之外,还有很多用途。比如说:
代码地址 redis 分支
更多精彩内容!