java中的i++不是原子操作
i = 0 ,两个线程分别对i进行i++ 100次,值为(2~200)
极端情况:线程2在开始取到i值为0,在线程2第一次执行赋值操作期间线程1执行了99次,当线程2赋值后i的值重新变为1;同理,线程1最后一次操作取到i值为1,在线程1执行赋值操作期间,线程2执行了剩下的99次,使得i大于100,但由于线程1第100次操作取到i等于1,并且赋值操作在线程2执行完毕后执行,所以极端情况最小值为2
1、常用命令
- keys * 查看当前数据库全部键
- select 切换数据库
- exists < key > 判断某个key是否存在
- type < key > 查看当前key的类型
- del < key > 删除指定的key数据
- unlink < key > 根据vlaue选择非阻塞删除(异步删除)
- expire < key > < 10 > 10秒钟(设置过期时间)
- ttl < key > 查看key还有多久过期(单位秒,-1永不过期,-2已过期)
- dbsize 查看当前数据库的key数量
- flushdb 清空当前库
- flushall 清空全部库
2、常用数据类型
1、string类型
1、命令
- get < key > 获取key对应的vlaue
- append < key > < value > 追加字符串
- strlen < key > 获取值的长度
- setnx < key> < value > 只有在key不存在时设置key的值
- incr < key > 将key对应的数字value加1 (原子性操作,单线程)
- decr < key > 将key对应的数字value减1
- incrby < key > < 值 > 将key对应的数字value加n
- decrby < key > < 值 > 将key对应的数字value减n
- mset < key > < 值 > < key > < 值 > 一次设置多个key
- mget < key > < key > 一次获取多个key的value
- msetnx < key > < 值 > < key > < 值 > 如果有一个键存在,即全部赋值失败
- getrange < key > 开始下标 结束下标, 获取value的片段(截取字符串)
- setrange < key > 开始下标 < 值 > 将value从下标位置开始替换成输入的值
- setex < key > < 过期时间 > < 值 >
- getset < key > < 值 > 取出旧值并赋上新值
2、数据结构
- string数据结构为动态字符串(可变字符串),内部结构与ArrayList类似,采用预分配冗余空间的方式减少内存的频繁分配
- 分配的空间一般高于实际实际字符串长度,当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会增加1M,字符串最大长度512M
2、List类型
底层是个双向列表,可以对两端进行操作。
1、命令
- lpush / rpush < key > < 值 > < 值 > < 值 > 从左边/右边插入一个或多个值
- lpop / rpop < key > 从左边/右边pop一个值
- rpoplpush < key1 > < key2 > 从key1右边pop一个值放到key2左边中
- lrange < key > 0 -1 查看全部值
- lindex < key > < index> 从key中取出第index个元素
- llen < key > 获得列表长度
- linsert k1 < value(列表中的值) > < newvalue > 在k1列表的value前面添加一个值
- lrem < key > < count> < value > 删除key列表count个value, 从左开始
- lset < key > < index> < value > 将key列表的index位置替换为value
2、数据结构
- list的数据结构为快速列表 quicklist
- 在列表元素较少的情况下会使用一块连续的内存存储,这个结构叫ziplist(压缩列表)
- 元素数量较多时会改成quicklist
- 普通链表需要附加指针空间,比较浪费空间
- 满足快速插入删除性能,还不会出现太大的空间冗余
3、Set类型
- 自动去重,是string类型的无序集合。
- 底层是一个value为null的hash表
- 添加、删除、查找时间复杂度为O(1)
1、命令
- sadd < key > < value > < value > 添加一个set集合
- smembers < key > 取出key的set集合
- sismember < key > value 判断key集合是否含有某个值
- scard < key > 查看key集合元素的个数
- srem < key > value 删除集合一个或多个元素
- spop < key > 随机pop一个值
- srandmember < key > < count > 随机取出count个元素
- smove < source > < destination > < value > 把集合中的一个值从该集合移动到另一个集合
- sinter < key > < key > 返回两个集合的交集元素
- sunion < key > < key > 返回两个集合的并集元素
- sdiff < key1 > < key2 > 返回key1集合中key2不存在的元素(类似k1 - k2)
2、数据结构
- set集合数据结构是dict字典
- 字典用哈希表实现
- 与java的hashSet结构一样,内部使用hash结构,所有value都指向同一个内部值
4、Hash类型
- 适合存储对象
1、命令
- hset < key > < field > < value > 给集合的field赋值
- hget < key > < field > 向集合取出field 的值
- hmset < key > < field > < value > < field > < value > < field > < value > 批量设置hash的值
- hexists < key > < field > 查看哈希表key中是否存在field
- hkeys < key > 查看哈希表所有的field
- hvals < key > 查看哈希表所有的value
- hincrby < key > < field > < increment > 给field增加或减少一个数
- hsernx < key > < field > < value > field 不存在时可添加
2、数据结构
- Hash类型对应的数据结构时两种(压缩列表):hashtable(哈希表)
- 当field - value 长度较短且个数较少时,使用ziplist,否则使用hashtable
5、Zset类型
- 有序集合
- 没有重复元素
- score可重复但value唯一
1、命令
- zadd < key > < score > < value > < score > < value > 将一个或多个元素及其score值添加到zset中
- zrange < key > < start > < stop > [WITHSCORES] 返回zset集合, 带WITHSCORES会将score值
- zrangebyscore < key > < min > < max > [WITHSCORES] 查看score范围的元素(从小到大)
- zrevrangebyscore < key > < max > < min > [WITHSCORES] 查看score范围的元素(从大到小)
- zincrby < key > < number > < value > 给value元素 添加 number
- zrem < key > < value > 删除value 元素及其score值
- zcount < key > < min > < max > 统计区间元素个数
- zrank < key > < value > 返回该值在集合的排名,从0开始
2、数据结构
- 类似与Java的map集合,zset可以给value赋予一个权重score
- 又类似与java的TreeSet,元素会按权重进行排序,可以得到每个元素的名次,还能通过score的范围获取元素列表
- hash,关联元素vlaue和权重score,保障元素vlaue的唯一性,可通过vlaue找到对应的score值
- 跳跃表,跳跃表的目的在于给元素vlaue进行排序,根据score的范围获取元素列表
3、配置文件
4、发布和订阅
- 是一种消息通信模式
- 发送者发送消息,订阅者接受消息
- redis客户端可以订阅任意数量的频道
1、发布订阅命令
- subscribe < channel > 订阅频道
- publish < channe > message 向频道发布消息
5、新数据类型
1、Bitmaps类型
- 如果Bitmaps存储的大多数都为0比较不合适
1、命令
- setbit < key > < offset > < value > 设置key的第offset 个位的值(从0开始)
- getbit < key > < offset > 获取key偏移量的值
- bitcount < key > 统计key被设置为1的bit数
- bitcount < key > < start > < end > 统计key从start 到end 被设置为1的bit数,可以是负数,表示倒数
- bitop and < key12 >(user:< and / or / not / xor > :20220101_02) < key1 > < key2 > 计算两个键offset 都为1的个数
2、HyperLogLog类型
1、介绍
用来做基数统计,优点:
- 输入元素的数量或体积非常非常大时,计算技术所属的空间时固定的,并且是很小的
- 每个键只需花费12KB内存就可以计算接近2^64个不同元素的基数
- 只根据输入元素来计算基数,而不会存储输入元素本身,不能返回输入的各个元素
解决基数问题方案:
- 数据存储在MySQL表中,使用distinct count 计算不重复个数
- 使用redis提供的hash、set、bitmaps等数据结构处理
- 数据不断增加导致占用空间越来越大,对于非常大的数据集不合适
2、命令
- pfadd < key > < value …> 添加指定元素
- pfcount < key > 计算近似基数
- pfmerge < destkey > < sourcekey… > 将一个或多个合并后的结果存到destkey中(如:每月用户活跃个数可以使用每天的活跃用户合并计算得到)
3、Geospatial类型
1、介绍
- 元素为二维坐标,在地图上就是经纬度
- 提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等操作
- 经度范围-180 ~ 180
- 纬度-85.05112878 ~ 85.05112878
2、命令
- geoadd < key > < x > < y > < nember > 添加地理位置(坐标和对应的名称)
- geopos < key > < nember > 获取指定地区的坐标
- geodist < key > < nember1 > < nember2 > [ m | km | ft | mi ] 获取两个位置之间的直线距离
- georadius < key > < x > < y > radius [ m | km | ft | mi ] 以给定的经纬为中心,找出某一半径内的元素
6、事务
Redis事务是一个单独的隔离操作
- 事务中所有命令都会被序列化、按顺序执行
- 事务执行过程中,不会被其他客户端发送来的命令请求所打断
- 主要作用是串联多个命令防止别的命令插队
7、乐观锁、悲观锁
- 乐观锁通过一个版本好控制,防止超卖问题
- 乐观锁能解决超卖但是会出现库存遗留问题,即同一批请求进来时,拿到的版本号一致,由于有一个指令更新了数据,其他请求版本号不对应导致更新失败。所以会出现数据遗留问题。
- 乐观锁还会出现连接超时,使用连接池可以解决
8、LUA脚本
- 类似redis事务,有一定原子性,不会被其他命令插队,可以完成一些redis事务性操作
- redis2.6版本才能使用
- 利用lua脚本淘汰用户,解决超卖问题。
- redis利用单线程的特性,用队列的方式解决多任务并发问题
9、redis持久化
1、RDB
在指定时间间隔内将内存中的数据集快照写入磁盘中
1、备份如何执行
- redis会单独创建(fork)一个子进程来进行持久化,会将数据写到一个老师文件,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
- 主进程不进行RDB的IO操作,确保redis的高性能
- 若需进行大规模数据恢复且对数据恢复的完整性不是非常敏感,RDB方式要比AOF方式更加高效
2、执行流程
3、优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
4、劣势
- 最后一次持久化后的数据可能会丢失
- Fork的时候,内存的数据被克隆了一份,大致2倍的膨胀性需要考虑
- redis再fork时使用了写时拷贝技术,但是如果数据量庞大时还是比较消耗性能
- 备份周期在一定时间间隔做一次备份,如果redis意外down掉的话,就会丢失最后一次快照后所有修改
如:10分钟内至少100个key发生改变,执行Fork
完成一次Fork,但下一次Fork条件还未满足(即10分钟内key发生改变的个数未达到100个),期间redis发生改变的数据将丢失