Go语句基础回顾 | 青训营笔记

163 阅读17分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记,回顾分享之前写的关于Redis笔记

1.基础知识

redis配置说明:https://www.runoob.com/redis/redis-conf.html
​
redis命令:http://www.redis.cn/commands.html#
  • redis默认有16个数据库,默认使用第0个数据库

使用select index切换数据库

127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 0   切换数据库
OK
127.0.0.1:6379> dbsize   查看db大小
(integer) 4
127.0.0.1:6379> keys *    查看所有的key
1) "counter:__rand_int__"
2) "key:__rand_int__"
3) "mylist"
4) "name"
127.0.0.1:6379> get name
"joker"
127.0.0.1:6379> flushdb    清空当前数据库
OK
127.0.0.1:6379> flushall    清空所有数据库
OK

Redis是单线程的 !!!! 但Redis6开始是多线程

Redis是基于内存操作,CPU不是Redis的性能瓶颈,Redis的性能瓶颈是机器内存和网络带宽

为什么Redis是单线程的,还这么快

注意:

  1. 高性能的服务器不一定就是多线程的
  2. 多线程(CPU上下文会切换)不一定比单线程效率高

速度 : CPU>内存>硬盘

核心:redis将所有数据都放在内存中,所以使用多线程操作效率最高(多线程,CPU上下文会切换,耗时操作)用空间换时间

2.数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

2.1 redis-key

127.0.0.1:6379> set mykey myvalue
OK
127.0.0.1:6379> get mykey
"myvalue"
127.0.0.1:6379> EXISTS mykey    判断是否key存在
(integer) 1
127.0.0.1:6379> EXISTS mykey1
(integer) 0
127.0.0.1:6379> move mykey 1    移动key到1号数据库中
(integer) 1
127.0.0.1:6379> 
 
127.0.0.1:6379> del mykey          删除对应key
(integer) 1127.0.0.1:6379> EXPIRE mykey 10     设置key存活时间为10秒
(integer) 1
127.0.0.1:6379> ttl mykey           剩余存在时间
(integer) 5
127.0.0.1:6379> ttl mykey
(integer) 2
127.0.0.1:6379> ttl mykey
(integer) -2127.0.0.1:6379> type mykey      判断key的类型
string

2.2 string

127.0.0.1:6379> clear
127.0.0.1:6379> set mykey myvalue
OK
127.0.0.1:6379> get mykey
"myvalue"
127.0.0.1:6379> 
  • 追加 append ,如果key不存在,相当于set key value
127.0.0.1:6379> get mykey
"101"
127.0.0.1:6379> APPEND mykey 10
(integer) 5
127.0.0.1:6379> get mykey
"10110"
  • 获取字符串长度
127.0.0.1:6379> STRLEN mykey
(integer) 5
  • 虽然是String类型,但如果是数字的字符串,可以用incr强制转换成数字再追加
127.0.0.1:6379> set mykey 100
OK
127.0.0.1:6379> get mykey
"100"
127.0.0.1:6379> incr mykey          加1
(integer) 101
127.0.0.1:6379> incr mykey
(integer) 102
127.0.0.1:6379> incr mykey
(integer) 103
127.0.0.1:6379> DECR mykey          减1
(integer) 10111
127.0.0.1:6379> DECR mykey
(integer) 10110
127.0.0.1:6379> INCRBY mykey 10     加10
(integer) 10120
127.0.0.1:6379> DECRBY mykey 20     减20
(integer) 10100
127.0.0.1:6379> 
  • getrange 获得字符串区间
127.0.0.1:6379> GETRANGE mykey 0 2
"101"
127.0.0.1:6379> GETRANGE mykey 0 -1     0到-1是全取
"10100"
  • 替换 set和setrange
127.0.0.1:6379> SETRANGE mykey 0 xx
(integer) 5
127.0.0.1:6379> get mykey
"xx100"
  • 设置key存活时间 setex (分布式锁中会常使用)
127.0.0.1:6379> setex username 30 "admin"
OK
127.0.0.1:6379> ttl username
(integer) 22
  • 使用setnx 加锁 (分布式锁中会常使用) key不存在就会成功,如果key已经存在了,则失败
127.0.0.1:6379> SETNX mykey1 "Hello"
(integer) 1
127.0.0.1:6379> SETNX mykey1 "World"
(integer) 0
127.0.0.1:6379> get mykey1
"Hello"
127.0.0.1:6379> set mykey1 "World"
OK
127.0.0.1:6379> get mykey1
"World"
  • MSET 会用新的value替换已经存在的value,就像普通的SET命令一样。
127.0.0.1:6379> MSET key1 "Hello1" key2 "Hello2"
OK
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
  • 返回所有指定的key的value。对于每个不对应string或者不存在的key,都返回特殊值nil
127.0.0.1:6379> MGET key1 key2
1) "Hello1"
2) "Hello2"
  • MSETNX是原子的,对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行。 由于这种特性,MSETNX可以实现要么所有的操作都成功,要么一个都不执行
127.0.0.1:6379> MSETNX key1 v1 key3 v2
(integer) 0
  • 存储对象
127.0.0.1:6379> MSET user:1:name "joker" user:1:age 22    user:{id}:{filed}
OK
127.0.0.1:6379> MGET user:1:name user:1:age
1) "joker"
2) "22"127.0.0.1:6379> set user:1 {name:joker,age:22}           设置一个user:1对象  值为json字符来保存对象
OK
127.0.0.1:6379> get user:1
"{name:joker,age:22}"
  • getsetget,再set ,如果不存在,输出nil,再设置新值
127.0.0.1:6379> GETSET user:1 "new"
"{name:joker,age:22}"
127.0.0.1:6379> get user:1
"new"

String使用场景:

  1. 计数器
  2. 统计多单位数量
  3. 对象缓存

2.3 list (所有list命令都是字母L开头的)

  • RPUSH list values LPUSH list valuesLRANGE list index index 设置list值和取值
127.0.0.1:6379> RPUSH list A B C D E    从右边推入 , LPUSH是从左边推入
(integer) 5
127.0.0.1:6379> LRANGE list 0 2        取序号 02
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> LRANGE list 0 -1       取序号 0 到 倒数第一个
1) "A"
2) "B"
3) "C"
4) "D"
5) "E"
127.0.0.1:6379> LRANGE list 0 -2        取序号 0 到 倒数第二个
1) "A"
2) "B"
3) "C"
4) "D"
  • 判断是否存在对应key
127.0.0.1:6379> EXISTS list
(integer) 1
  • 删除值 LPOPRPOP pop,它从list中删除元素并同时返回删除的值。可以在左边或右边操作
127.0.0.1:6379> LRANGE list 0 -1
1) "A"
2) "B"
3) "C"
4) "D"
5) "E"
127.0.0.1:6379> LPOP list           从list的左边删除第一个值
"A"
127.0.0.1:6379> RPOP list           从list的右边删除第二个值
"E"
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "D"
  • 可以使用LTRIM把 list 从左边截取指定长度
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "D"
127.0.0.1:6379> LTRIM list 0 1
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
  • 从list下标取值 LINDEX
127.0.0.1:6379> LINDEX list 0
"B"
  • 返回list长度
127.0.0.1:6379> LLEN list
(integer) 2
  • 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:

count > 0: 从头往尾移除值为 value 的元素。

count < 0: 从尾往头移除值为 value 的元素。

count = 0: 移除所有值为 value 的元素。

比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。

127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "B"
4) "C"
5) "B"
127.0.0.1:6379> LREM list -2 B
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "C"
  • 原子性地返回并移除存储在 原来List 列表的最后一个元素(列表尾部元素), 并把该元素放入存储在 目标列表 的第一个元素位置(列表头部)。
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "C"
127.0.0.1:6379> RPOPLPUSH list childList   移除list中最后一个值,将其移动到childList中的头位置
"C"
127.0.0.1:6379> keys *
1) "list"
2) "childList"
3) "user:1"
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
127.0.0.1:6379> LRANGE childList 0 -1
1) "C"
  • 设置 index 位置的list元素的值为 value LSET
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
127.0.0.1:6379> LSET list 0 "A"
OK
  • 把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。

    当 key 不存在时,这个list会被看作是空list,任何操作都不会发生。

    当 key 存在,但保存的不是一个list的时候,会返回error。

    BEFORE 或者 AFTER

127.0.0.1:6379> LRANGE list 0 -1
1) "A"
2) "C"
127.0.0.1:6379> LINSERT list before "C" "new"    在原列表中,检索“C”,存在时,在前面添加“new”
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "A"
2) "new"
3) "C"
127.0.0.1:6379> LINSERT list before "M" "new"    在原列表中,检索“M”,不存在时,返回-1
(integer) -1

总结:

  1. list可以从两边插入数据和删除数据,高效
  2. 如果移除所有值时,list也会消失
  3. 实现消息排队,消息队列(LPUSH , RPOP),栈(LPUSH,LPOP)

2.4 set

Redis Set 是 String 的无序排列,不可重复

  • sadd 添加 set值 ,smembers 打印出全部值
127.0.0.1:6379> keys *
1) "list"
2) "user:1"
127.0.0.1:6379> sadd mySet Hello
(integer) 1
127.0.0.1:6379> sadd mySet Set
(integer) 1
127.0.0.1:6379> sadd mySet Set
(integer) 0
127.0.0.1:6379> smembers mySet
1) "Set"
2) "Hello"
  • sismember 判断set中是否存在对应值
127.0.0.1:6379> SISMEMBER mySet "Set"
(integer) 1
127.0.0.1:6379> SISMEMBER mySet "World"
(integer) 0
  • scard key 返回集合存储的key的基数 (集合元素的数量)
127.0.0.1:6379> scard mySet
(integer) 2
  • srem 删除 set 集合中指定值
127.0.0.1:6379> sMembers mySet
1) "C"
2) "B"
3) "A"
4) "Hello"
127.0.0.1:6379> SREM mySet "A" "D"              如果set集合中存在对应值,删除;
(integer) 1
127.0.0.1:6379> sMembers mySet
1) "C"
2) "B"
3) "Hello"
  • srandmember key 随机获取 set 集合中的值
127.0.0.1:6379> SRANDMEMBER mySet
"B"
127.0.0.1:6379> SRANDMEMBER mySet
"C"
127.0.0.1:6379> SRANDMEMBER mySet
"C"
  • SPOP key 随机删除集合中的值
127.0.0.1:6379> SMEMBERS mySet
1) "C"
2) "B"
3) "Hello"
127.0.0.1:6379> SPOP mySet
"C"
127.0.0.1:6379> SMEMBERS mySet
1) "B"
2) "Hello"
  • SMOVE source destination member 将一个集合中的值
127.0.0.1:6379> SMOVE mySet mySet1 "Hello"
(integer) 1
127.0.0.1:6379> Smembers mySet1
1) "Hello"
  • 数学集合类(交集、并集、差集)
127.0.0.1:6379> SMEMBERS mySet
1) "A"
2) "B"
127.0.0.1:6379> SMEMBERS mySet1
1) "A"
2) "Hello"
127.0.0.1:6379> SDIFF mySet mySet1          mySet集合相对于mySet1不同的值 (差集)
1) "B"
127.0.0.1:6379> SINTER mySet mySet1         两个集合的相同的部分(交集)     共同好友的实现
1) "A"
127.0.0.1:6379> SUNION mySet mySet1         两个集合合起来  (并集)
1) "Hello"
2) "B"
3) "A"

2.5 hash

  • hset key field value

    设置 key 指定的哈希集中指定字段的值。

    如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。

    如果字段在哈希集中存在,它将被重写。

    hget key filed

    返回 key 指定的哈希集中该字段所关联的值

127.0.0.1:6379> hset myHash name joker
(integer) 1
127.0.0.1:6379> hget myHash name
"joker"
  • hmset key field value [field value ...]

    设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联

    hmget key field [field ...]

    返回 key 指定的哈希集中指定字段的值。

    对于哈希集中不存在的每个字段,返回 nil 值。因为不存在的keys被认为是一个空的哈希集,对一个不存在的 key 执行 HMGET 将返回一个只含有 nil 值的列表

127.0.0.1:6379> hmset myHash age 11 sex man
OK
127.0.0.1:6379> hmget myHash name age sex
1) "joker"
2) "11"
3) "man"
  • hgetall key获取全部数据
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
5) "sex"
6) "man"
  • hdel key field删除指定字段,对应的value值也消失了
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
5) "sex"
6) "man"
127.0.0.1:6379> HDEL myHash sex
(integer) 1
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
  • hlen key获取集合set长度
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
127.0.0.1:6379> HLEN myHash
(integer) 2
  • hexists返回hash里面field是否存在
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
127.0.0.1:6379> HEXISTS myHash age           存在
(integer) 1
127.0.0.1:6379> HEXISTS myHash sex           不存在
(integer) 0
  • 只显示出所有的field或者value ,hkeys 或者 hvals
127.0.0.1:6379> hgetall myHash
1) "name"
2) "joker"
3) "age"
4) "11"
127.0.0.1:6379> HKEYS myHash
1) "name"
2) "age"
127.0.0.1:6379> HVALS myHash
1) "joker"
2) "11"
  • hincrby 自增和自减
127.0.0.1:6379> HINCRBY myHash age 10
(integer) 22
127.0.0.1:6379> HINCRBY myHash age -10
(integer) 12
  • HSETNX key field value 只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。如果字段已存在,该操作无效果。
127.0.0.1:6379> HSETNX myHash age 13              field存在,所以失败
(integer) 0
127.0.0.1:6379> HSETNX myHash sex "man"           field不存在,所以成功
(integer) 1
127.0.0.1:6379> hgetall myHash
1) "name"
2) "joker"
3) "age"
4) "12"
5) "sex"
6) "boy"

使用场景:

  1. 变更的数据 user age name ,尤其是用户信息类的,经常变动的信息。hash更适合对象的存储,string更适合字符串的存储

2.6 zset(有序集合)

  • zadd key score value 添加值
127.0.0.1:6379> zadd myZset 1 first                添加一个值
(integer) 1
127.0.0.1:6379> zadd myZset 2 second 3 third       添加多个值
(integer) 2
127.0.0.1:6379> zrange myZset 0 -1
1) "first"
2) "second"
3) "third"
  • ZRANGEBYSCORE key min max 升序排序(从小到大)
127.0.0.1:6379> ZRANGEBYSCORE myZset -inf +inf      根据score大小排序,范围在负无穷到正无穷
1) "first"
2) "second"
3) "third"
127.0.0.1:6379> ZRANGEBYSCORE myZset -inf +inf withscores   顺便打印出score
1) "first"
2) "1"
3) "second"
4) "2"
5) "third"
6) "3"
  • ZREVRANGEBYSCORE key max min 降序排序(从大到小)
127.0.0.1:6379> ZREVRANGEBYSCORE myZset +inf -inf
1) "third"
2) "second"
3) "first"
127.0.0.1:6379> ZREVRANGEBYSCORE myZset +inf -inf withscores
1) "third"
2) "3"
3) "second"
4) "2"
5) "first"
6) "1"
  • zrem key value 删除指定元素
127.0.0.1:6379> zrange myZset 0 -1
1) "first"
2) "second"
3) "third"
127.0.0.1:6379> zrem myZset first
(integer) 1
127.0.0.1:6379> zrange myZset 0 -1
1) "second"
2) "third"
  • zcard key 获得集合的个数
127.0.0.1:6379> zcard myZset
(integer) 2
  • zcount key min max 获取指定分数范围的元素个数
127.0.0.1:6379> zrange myZset 0 -1 withscores
1) "second"
2) "2"
3) "third"
4) "3"
127.0.0.1:6379> zcount myZset 0 2              获取score为01的个数
(integer) 1
127.0.0.1:6379> zcount myZset 0 3              获取score为02的个数
(integer) 2

使用场景:

  1. set 排序 存储班级成绩 工资排序
  2. 普通消息 重要消息 带权重进行判断
  3. 排行榜

2.7 geospatial (地理位置)

  • geoadd key 经度 纬度 member 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作

    • 有效的经度从-180度到180度。
    • 有效的纬度从-85.05112878度到85.05112878度。
127.0.0.1:6379> geoadd china:city 116.23 40.22 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 116.41 23.57 jieyang
(integer) 1
127.0.0.1:6379> geoadd china:city 114.02 30.58 wuhan
(integer) 1
  • geopos keykey里返回所有给定位置元素的位置(经度和纬度)。
127.0.0.1:6379> geopos china:city jieyang                 只查询揭阳的经纬度
1) 1) "116.40999823808670044"
   2) "23.57000072404306223"
127.0.0.1:6379> geopos china:city jieyang beijing wuhan   查询揭阳,北京,武汉的经纬度
1) 1) "116.40999823808670044"
   2) "23.57000072404306223"
2) 1) "116.23000055551528931"
   2) "40.2200010338739844"
3) 1) "114.01999980211257935"
   2) "30.58000021509926825"
  • GEODIST key member1 member2 [unit]返回两个给定位置之间的距离。

    如果两个位置之间的其中一个不存在, 那么命令返回空值。

    指定单位的参数 unit 必须是以下单位的其中一个:

    • m 表示单位为米。
    • km 表示单位为千米。
    • mi 表示单位为英里。
    • ft 表示单位为英尺。
127.0.0.1:6379> geodist china:city jieyang beijing km   以千米为单位,查看揭阳到北京的直线距离
"1851.9947"
127.0.0.1:6379> geodist china:city jieyang beijing m    以米为单位,查看揭阳到北京的直线距离
"1851994.7423"
  • GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

    给定经纬度,组成一个中心点,再给定半径,找到在这个区域中存在的值

    范围可以使用以下其中一个单位:

    • m 表示单位为米。
    • km 表示单位为千米。
    • mi 表示单位为英里。
    • ft 表示单位为英尺。

    在给定以下可选项时, 命令会返回额外的信息:

    • WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
    • WITHCOORD: 将位置元素的经度和维度也一并返回。
    • WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

    命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:

    • ASC: 根据中心的位置, 按照从近到远的方式返回位置元素。
    • DESC: 根据中心的位置, 按照从远到近的方式返回位置元素。

    附近的人,(获取所有人的地址),通过半径来查询

127.0.0.1:6379> GEORADIUS china:city 116 23 100 km
1) "jieyang"
  • GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

    这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点

127.0.0.1:6379> GEORADIUSBYMEMBER china:city jieyang 500 km      以揭阳的经纬度为中心,半径为500km的圆
1) "jieyang"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city jieyang 1000 km     以揭阳的经纬度为中心,半径为1000km的圆
1) "jieyang"
2) "wuhan"
  • GEOHASH key member [member ...] 该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。
# 将二维的经纬度转换成一维的字符串,如果两个字符串越接近,两个距离越近
127.0.0.1:6379> GEOHASH china:city jieyang wuhan
1) "ws4y0fz1jw0"
2) "wt3jfwfv6t0"
127.0.0.1:6379> GEOHASH china:city jieyang beijing
1) "ws4y0fz1jw0"
2) "wx4sucu47r0"

GEO底层的实现原理其实就是Zset,我们可以使用Zset命令来调用它

127.0.0.1:6379> zrange china:city 0 -1
1) "jieyang"
2) "wuhan"
3) "beijing"

应用:

  1. 朋友的定位,附近的人,打车距离计算

2.8 hyperloglogs(基数)

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8} , 那么这个数据集的基数集为 {1, 3, 5 ,7, 8} , 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数

传统统计网页访问量,set保存用户的id,然后统计set元素数量作为标准判断。这种做法大量保存用户id,会比较麻烦,我们的目的只是计数,而不是保存用户id。

使用redis hyperloglogs,一个人访问一个网站多次,还是算作一个人。优点:占用的内存是固定的,2^64不同的元素技术,只需要废12KB内存,如果从内存角度来比较,Hyperloglogs比较好

  • PFADD key element [element ...] 将除了第一个参数以外的参数存储到以第一个参数为变量名的HyperLogLog结构中
127.0.0.1:6379> pfadd key1 a b c d e
(integer) 1
  • pfcount key 返回key中的个数
127.0.0.1:6379> pfadd key1 a b c d e
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 5
127.0.0.1:6379> pfadd key1 a b c d e
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 5
127.0.0.1:6379> pfadd key2 a b c d e f
(integer) 1
127.0.0.1:6379> PFMERGE key1 key2       将key2中的值添加到key1中,如果有重复的,则不添加
OK
127.0.0.1:6379> pfcount key1            原来长度为5,添加key2中的f,长度变成6
(integer) 6

如果允许容错,那么一定可以使用Hyperloglogs !

如果不允许容错,就使用set或者自己写的数据类型!

2.9 Bitmaps

统计用户信息,活跃,不活跃;登录,未登录;打卡,365打卡

Bitmaps位图,数据结构,都是操作二进制位来进行记录,就只有0和1两个状态

365天=365bit 1字节=8bit 46个字节左右

使用bitmap 来记录 周一到周日的打卡

周一:1 周二:0 周三:0 周四:0 周五:0 周六:0 周日:0

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0

查看某一天是否打卡

127.0.0.1:6379> getbit sign 2
(integer) 0
127.0.0.1:6379> getbit sign 0
(integer) 1

统计打卡数 -- BITCOUNT key [start end]

127.0.0.1:6379> bitcount sign 0 6
(integer) 1

3.事务

原子性:要么都成功,要么都失败

Redis单条命令是保证原子性的,但事务不保证原子性

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,事务在执行中会按顺序执行。(一次性、顺序性、排他性)

队列 : set set set get  按顺序执行

Redis事务没有隔离级别的概念

所有命令在事务中,并没有直接被执行!只有发起执行命令时才会执行 EXEC

redis的事务:

  • 开启事务(MULTI)
  • 命令入队(……)
  • 执行事务(EXEC)

正常执行事务

127.0.0.1:6379> multi             #开启事务
OK
# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec          # 执行事务
1) OK
2) OK
3) "v1"
4) OK

放弃事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> discard        # 取消事务,队列会消失
OK

编译型异常(代码有问题!命令有错误!),事务中所有命令都不会执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> sett k1                   #错误的命令              
(error) ERR unknown command `sett`, with args beginning with: `k1`, 
127.0.0.1:6379(TX)> get k1                    #执行不了
QUEUED
127.0.0.1:6379(TX)> exec                      #事务也是报错的
(error) EXECABORT Transaction discarded because of previous errors.

运行时异常( 1/0 ),如果队列事务中存在语法性错误,在执行命令时,其他命令可以正常执行,错误命令抛出异常

127.0.0.1:6379> set k1 "v1"             #设置其值为字符串
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1             #字符串无法自增1,所以在运行时会报错
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range   #除了这个有错误,其他还能正常执行
2) OK
3) "v1"
4) "v2"

这也验证了redis单条命令有原子性,事务无法保证原子性

4.监控(Watch)(乐观锁)

  • 悲观锁:无论做什么都加锁

  • 乐观锁:做什么事都不加锁

    • 获取version
    • 更新时比较version

正常运行成功

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money        #监视 money 对象
OK
127.0.0.1:6379> multi              #事务正常结束,数据期间没有发生变动,这个时候就正常执行
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis乐观锁操作

线程A:

127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
# 执行到此处时先不运行事务,先到线程B中修改money的值,
# 在执行之前,另外一个线程修改了我们监视的 money 的值,这时候会导致我们执行事务失败
127.0.0.1:6379(TX)> exec
(nil)

线程B:

127.0.0.1:6379> get money
"180"
# 修改money的值
127.0.0.1:6379> set money 300
OK

错误后,如果想修改(如果失败,获取最新值就好)

127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money
OK
•~~~~ 
重新开启事务,添加命令,执行事务

5. 订阅和发布

  • 打开一个客户端订阅channel

subscribe channel

  • 打开另外一个客户端,给 channel发布消息 hello

publish channel value

\