Linux使用技巧

终端与图形界面的切换

Linux 的7 个运行级别:

0 – halt /所有进程关闭,机器将有序的停止,可以理解为关机/
1– Single user mode / 单用户模式,只有少数进程启动,同时所有服务不启动/
2 – Multiuser, without NFS / 多用户模式,网络文件系统(NFS )服务不启动/
3 – Full Multiuser mode / 多用户模式。允许多用户登录系统,是系统默认的启动级别/
4 – unused / 留给用户自定义的运行级别/
5 – X11 / 多用户模式,系统启动后运行X-Window,图形化的登录窗口/ 6 – Reboot / 所有进程被终止,系统重启/
在终端模式下输入:init n /n=0,1,2,3,4,5,6/

例如:init 0 关机

init 3 终端模式
init 5 图形界面
init 6 重启

锁屏

ctrl + c 锁屏

ctrl + q 解锁

Redis介绍

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务 !
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库!

Redis能干嘛?

1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof) 2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)

基本命令
127.0.0.1:6379> ping	#测试连接
PONG
127.0.0.1:6379> keys *	#查看所有key
1) "msg"
127.0.0.1:6379> flushall	#清除所有key
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name1 rose	#set key value
OK
127.0.0.1:6379> get name1	#get key
"rose"
127.0.0.1:6379> set name2 mary
OK
127.0.0.1:6379> get name2
"mary"
127.0.0.1:6379> keys *
1) "name2"
2) "name1"
127.0.0.1:6379> exists name1
(integer) 1
127.0.0.1:6379> move name1 1
(integer) 1
127.0.0.1:6379> keys *
1) "name2"
127.0.0.1:6379> flushall
OK
## 测试key过期时间的设置
127.0.0.1:6379> set name1 rose
OK
127.0.0.1:6379> set name2 mary
OK
127.0.0.1:6379> expire name1 15	#设置过期时间为15秒
(integer) 1
127.0.0.1:6379> ttl name1	#查看剩余时间
(integer) 11
127.0.0.1:6379> ttl name1
(integer) 4
127.0.0.1:6379> ttl name1	#实验时间为-2表示过期
(integer) -2
127.0.0.1:6379> get name1	#get过期数据为空
(nil)
127.0.0.1:6379> type name1	#type也为none
none
127.0.0.1:6379> type name2
string
127.0.0.1:6379> flushall
OK

Redis五大数据类型

  • String(字符串)
    string 是 redis 最基本的类型,可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
  • Hash(哈希)Redis hash 是一个键值(key=>value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
  • List(列表)Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
  • Set(集合)Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
  • zset(sorted set:有序集合)Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

String

添加、查询、追加、获取长度,判断是否存在的操作
127.0.0.1:6379> set name rose
OK
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> append name mary	#追加
(integer) 8
127.0.0.1:6379> append name1 mary	#追加而key不存在时,相当于set
(integer) 4
127.0.0.1:6379> get name
"rosemary"
127.0.0.1:6379> get name1
"mary"
127.0.0.1:6379> strlen name	#查询长度
(integer) 8
127.0.0.1:6379> set key Hello world!	#有空格时,要用双引号括起来
(error) ERR syntax error
127.0.0.1:6379> set key "Hello world!"
OK
127.0.0.1:6379> get key
"Hello world!"
127.0.0.1:6379> flushall
OK
自增、自减操作
127.0.0.1:6379> set num 0
OK
127.0.0.1:6379> get num
"0"
127.0.0.1:6379> incr num	#i++
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> get num
"2"
127.0.0.1:6379> decr num	#i--
(integer) 1
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> decr num	#可以减到负数
(integer) -1
127.0.0.1:6379> incrby num 10	#i+=10
(integer) 9
127.0.0.1:6379> decrby num 5	#i-=5
(integer) 4
127.0.0.1:6379> flushall
OK
截取、替换字符串操作
127.0.0.1:6379> set num 123456
OK
127.0.0.1:6379> getrange num 0 2	#获取0到2的数据,不改变原数据
"123"
127.0.0.1:6379> get num
"123456"
127.0.0.1:6379> getrange num 0 -1	#同get
"123456"
127.0.0.1:6379> setrange num 0 66666	#从第0位开始替换为66666,替换长度取决于替换数据
(integer) 6
127.0.0.1:6379> get num
"666666"
127.0.0.1:6379> flushall
OK
设置过期时间、不存在设置操作
# setex在set的同时设置过期时间,expier则是对已有的数据设置过期时间
127.0.0.1:6379> setex name 15 rose
OK
127.0.0.1:6379> ttl name
(integer) 12
127.0.0.1:6379> ttl name
(integer) 7
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> setnx name rose	#setnx,key不存在则设置成功,返回1
(integer) 1
127.0.0.1:6379> setnx name mary	#setnx,key存在则设置失败,返回0
(integer) 0
127.0.0.1:6379> get name
"rose"
127.0.0.1:6379> flushall
OK
mset、mget操作
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3	#批量设置
OK
127.0.0.1:6379> mget k1 k2 k3	#批量查询
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k3 v3 k4 v4	#原子性操作,同时成功同时失败
(integer) 0
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> msetnx k4 v4 k5 v5
(integer) 1
127.0.0.1:6379> keys *
1) "k4"
2) "k2"
3) "k1"
4) "k5"
5) "k3"
127.0.0.1:6379> flushall
OK
添加获取对象、getset操作
## 本质还是字符串
127.0.0.1:6379> mset student:1:name rose student:1:age 22
OK
127.0.0.1:6379> mget student:1:age student:1:name
1) "22"
2) "rose"

127.0.0.1:6379> getset key v1	#先get再set
(nil)
127.0.0.1:6379> getset key v2
"v1"
127.0.0.1:6379> get key
"v2"
127.0.0.1:6379> flushall
OK

总结:String是Redis中最常用的一种数据类型,也是Redis中最简单的一种数据类型。首先,表面上它是字符串,但其实他可以灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。

List

lpush(左插入)、lrange(查询集合)、rpush(右插入)操作
127.0.0.1:6379> lpush list v1	#左插入
(integer) 1
127.0.0.1:6379> lpush list v2
(integer) 2
127.0.0.1:6379> lpush list v3
(integer) 3
127.0.0.1:6379> lrange list 0 -1	#查询,跟getrange一样的意思
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> lpush list2 v1 v2 v3 v4	#批量左插入
(integer) 4
127.0.0.1:6379> lrange list2 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> rpush list v0 #右插入
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "v3"
2) "v2"
3) "v1"
4) "v0"
127.0.0.1:6379> flushall
OK
lpop(左移除)、rpop(右移除)操作
127.0.0.1:6379> lpush list 1 2 3 4 5
(integer) 5
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> lpop list
"5"
127.0.0.1:6379> lpop list 2	#pop出多个值
1) "4"
2) "3"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> rpop list
"1"
127.0.0.1:6379> flushall
OK
127.
lindex(查询指定下标元素)、llen(获取集合长度) 操作
127.0.0.1:6379> rpush list 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> lindex list 0	#查询下标为0的函数
"1"
127.0.0.1:6379> llen list	#查询长度
(integer) 5
127.0.0.1:6379> flushall
OK
lrem(根据value移除指定的值)
127.0.0.1:6379> lpush list v1 v2 v3 v4 v4
(integer) 5
127.0.0.1:6379> lrem list 1 v1	#移除list中1个值为v1的元素
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "v4"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> lrem list 2 v4	#移除list中2个值为v4的元素
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "v3"
2) "v2"
127.0.0.1:6379> lrem list 2 v2	#list中没有足够的元素可以移除,不会报错,成功移除了几个就返回几
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "v3"
127.0.0.1:6379> flushall
OK
ltrim(截取元素)、rpoplpush(移除指定集合中最后一个元素到一个新的集合中)操作
127.0.0.1:6379> lpush list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> ltrim list 1 2	#截取元素并保存,list已被更新
OK
127.0.0.1:6379> lrange list 0 -1
1) "v3"
2) "v2"
127.0.0.1:6379> lpush list2 0 0 0
(integer) 3
127.0.0.1:6379> rpoplpush list list2	#移除指定集合中最后一个元素到一个新的集合中
"v2"
127.0.0.1:6379> lrange list 0 -1
1) "v3"
127.0.0.1:6379> lrange list2 0 -1
1) "v2"
2) "0"
3) "0"
4) "0"
127.0.0.1:6379> rpoplpush list uname	#因为这也是push,未定义过的列表也能放进去
"v3"
127.0.0.1:6379> lrange uname 0 -1
1) "v3"
127.0.0.1:6379> flushall
OK
lset(更新)、linsert操作
127.0.0.1:6379> lpush list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> lset list 1 newv3	#设更新下标为1的值为newv3
OK
127.0.0.1:6379> lrange list 0 -1
1) "v4"
2) "newv3"
3) "v2"
4) "v1"
127.0.0.1:6379> linsert list after v2 v1.5	#在v2后插入值v1.5,也可before
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "v4"
2) "newv3"
3) "v2"
4) "v1.5"
5) "v1"
127.0.0.1:6379> flushall
OK

总结:

  • 实际上是一个链表,before Node after , left,right 都可以插入值
  • 如果key 不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在!
  • 在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
  • 消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!

Set

sadd(添加)、smembers(查看所有元素)、sismember(判断是否存在)、scard(查看长度)、srem(移除指定元素)操作
#set中所有的元素都是唯一的不重复的!
127.0.0.1:6379> sadd set un deux trois quatre #添加元素
(integer) 4
127.0.0.1:6379> smembers set	#遍历元素
1) "trois"
2) "deux"
3) "un"
4) "quatre"
127.0.0.1:6379> sismember set quatre	#查询是否存在某元素,存在返回1
(integer) 1
127.0.0.1:6379> sismember set cinq	#不存在返回0
(integer) 0
127.0.0.1:6379> 
127.0.0.1:6379> scard set	#查看集合长度
(integer) 4
127.0.0.1:6379> srem set un	#删除元素
(integer) 1
127.0.0.1:6379> smembers set
1) "trois"
2) "deux"
3) "quatre"
127.0.0.1:6379> flushall 
OK
srandmember(抽随机)操作
127.0.0.1:6379> sadd dice 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> smembers dice
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> SRANDMEMBER myset 1  #随机抽取myset中1个元素返回
1) "4"
127.0.0.1:6379> SRANDMEMBER myset 1  #随机抽取myset中1个元素返回
1) "1"
127.0.0.1:6379> SRANDMEMBER myset 1  #随机抽取myset中1个元素返回
1) "5"
127.0.0.1:6379> SRANDMEMBER myset  #不填后参数,默认抽1个值,但是下面返回不会带序号值
"3"
127.0.0.1:6379> SRANDMEMBER myset 3  #随机抽取myset中3个元素返回
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SRANDMEMBER myset 3  #随机抽取myset中3个元素返回
1) "6"
2) "3"
3) "5"
127.0.0.1:6379> flushall
OK
spop(随机删除元素)、smove(移动指定元素到新的集合中)操作
127.0.0.1:6379> sadd set1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> spop set1 1	#随机删除元素
1) "4"
127.0.0.1:6379> smembers set1
1) "1"
2) "2"
3) "3"
4) "5"
127.0.0.1:6379> spop set1 2
1) "5"
2) "2"
127.0.0.1:6379> smembers set1
1) "1"
2) "3"
127.0.0.1:6379> sadd set2 2 4 6
(integer) 3
127.0.0.1:6379> smove set2 set1 2 #将2从set2移到set1
(integer) 1
127.0.0.1:6379> smembers set1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> smembers set2
1) "4"
2) "6"
127.0.0.1:6379> flushall
OK
sdiff(差集)、sinter(交集)、sunion(并集)操作
127.0.0.1:6379> sadd myset1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> sadd myset2 3 4 5 6 7
(integer) 5
127.0.0.1:6379> SMEMBERS myset1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> SMEMBERS myset2
1) "3"
2) "4"
3) "5"
4) "6"
5) "7"
127.0.0.1:6379> SDIFF myset1 myset2  #查询指定的set之间的差集,可以是多个set
1) "1"
2) "2"
127.0.0.1:6379> SINTER myset1 myset2  #查询指定的set之间的交集,可以是多个set
1) "3"
2) "4"
3) "5"
127.0.0.1:6379> sunion myset1 myset2  #查询指定的set之间的并集,可以是多个set
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"

总结:可实现共同好友、共同关注等需求。

Hash

hset(添加hash)、hget(查询)、hgetall(查询所有)、hdel(删除hash中指定的值)、hlen(获取hash的长度)、hexists(判断key是否存在)操作
127.0.0.1:6379> hset hash name rose age 22	#新增
(integer) 2
127.0.0.1:6379> hget hash name	#获得某key的value
"rose"
127.0.0.1:6379> hget hash age
"22"
127.0.0.1:6379> hgetall hash	#遍历所有key及其value
1) "name"
2) "rose"
3) "age"
4) "22"
127.0.0.1:6379> hset hash gender male	#新增
(integer) 1
127.0.0.1:6379> hgetall hash
1) "name"
2) "rose"
3) "age"
4) "22"
5) "gender"
6) "male"
127.0.0.1:6379> hdel hash age gender	#删除key,value会同时被删除
(integer) 2
127.0.0.1:6379> hgetall hash
1) "name"
2) "rose"
127.0.0.1:6379> hlen hash	#查询长度
(integer) 1
127.0.0.1:6379> hexists hash name	#查询是否存在,存在为1,不存在为0
(integer) 1
127.0.0.1:6379> hexists hash age
(integer) 0
127.0.0.1:6379> flushall
OK
hkeys(获取所有key)、hvals(获取所有value)、hincrby(给值加增量)、hsetnx(存在不添加)操作
127.0.0.1:6379> hset hash name rose age 22 height 185
(integer) 3
127.0.0.1:6379> hkeys hash	#获取所有key
1) "name"
2) "age"
3) "height"
127.0.0.1:6379> hvals hash	#获取所有value
1) "rose"
2) "22"
3) "185"
127.0.0.1:6379> hincrby hash age 2	#增加2,要减就用负数
(integer) 24
127.0.0.1:6379> hsetnx hash gender male	#不存在则新增
(integer) 1
127.0.0.1:6379> hsetnx hash name mary
(integer) 0
127.0.0.1:6379> hgetall hash
 1) "name"
 2) "rose"
 3) "age"
 4) "24"
 5) "height"
 6) "185"
 7) "gender"
 8) "male"
127.0.0.1:6379> flushall
OK

总结:比String更加适合存对象

zSet

zadd(添加)、zrange(查询)、zrangebyscore(排序小-大)、zrevrange(排序大-小)、zrangebyscore withscores(查询所有值包含key)操作
127.0.0.1:6379> zadd zset 1 un 2 deux 3 trois	#设置排序与其值
(integer) 3
127.0.0.1:6379> zrange zset 0 -1	#遍历
1) "un"
2) "deux"
3) "trois"
127.0.0.1:6379> zrangebyscore zset -inf +inf	#正序遍历
1) "un"
2) "deux"
3) "trois"
127.0.0.1:6379> zrevrange zset 1 -1	#从下标1开始倒序遍历
1) "deux"
2) "un"
127.0.0.1:6379> zrange zset 0 -1 withscores #遍历并输出序号
1) "un"
2) "1"
3) "deux"
4) "2"
5) "trois"
6) "3"
127.0.0.1:6379> flushall
OK
zrem(移除元素)、zcard(查看元素个数)、zcount(查询指定区间内的元素个数)操作
127.0.0.1:6379> zadd zset 1 v1 2 v2 3 v3 4 v4
(integer) 4
127.0.0.1:6379> zrange zset 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> zrem zset v2 v3	#按value删除
(integer) 2
127.0.0.1:6379> zcard zset	#查询长度
(integer) 2
127.0.0.1:6379> zcount zset 0 2	#查询指定范围内的元素个数
(integer) 1
127.0.0.1:6379> flushall
OK

总结:成绩表排序,工资表排序,年龄排序等需求可以用zset来实现

Redis的事务与乐观锁

介绍

事务

  • 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

在Redis事务没有没有隔离级别的概念!
在Redis单条命令是保证原子性的,但是事务不保证原子性!

乐观锁

  • 当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
  • 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
    在Redis是可以实现乐观锁的!

事务的实现

正常执行事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name rose
QUEUED
127.0.0.1:6379(TX)> set age 22
QUEUED
127.0.0.1:6379(TX)> set height 185
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> get name
"rose"
127.0.0.1:6379> get age
"22"
127.0.0.1:6379> get height
"185"
127.0.0.1:6379> flushall
OK
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name mary
QUEUED
127.0.0.1:6379(TX)> set age 18
QUEUED
127.0.0.1:6379(TX)> discard	#放弃事务
OK
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> flushall
OK
编译时异常:代码或命令有问题,所有的命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name rose
QUEUED
127.0.0.1:6379(TX)> set age	#错误的命令,这是已经报错了,但还是会加入到队列当中
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> set height 185
QUEUED
127.0.0.1:6379(TX)> exec	#有一个命令错误,全部不执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name	#获取名字为空
(nil)
127.0.0.1:6379> flushall
OK
运行时异常:除了错误的语句不会被执行且抛出异常后,其他的正确命令可以正常执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name rose
QUEUED
127.0.0.1:6379(TX)> incr name	#对字符串自增,但此时没有报错
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> exec	#运行时,对字符串自增的语句报错,其他语句正常执行
1) OK
2) (error) ERR value is not an integer or out of range
3) "rose"
127.0.0.1:6379> flushall
OK

总结:由以上可以得出结论,Redis是支持单条命令事务的,但是事务并不能保证原子性!

乐观锁的实现

watch监视
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set cost 0
OK
127.0.0.1:6379> watch money	#监视金钱
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby cost 30
QUEUED
127.0.0.1:6379(TX)> decrby money 30
QUEUED
127.0.0.1:6379(TX)> exec	#此时,数据没有发生变动才可以成功
1) (integer) 30
2) (integer) 70
127.0.0.1:6379> mget money cost
1) "70"
2) "30"
127.0.0.1:6379> flushall
OK
多线程watch测试

新打开一个窗口作为线程二,在执行线程一的过程中某点,执行线程二。

线程一:

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set cost 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby cost 20
QUEUED
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> exec	#先不执行事务,而是执行线程二,则数据变动,事务执行失败
(nil)
127.0.0.1:6379> get money
"120"
127.0.0.1:6379> flushall
OK

线程二:

127.0.0.1:6379> incrby money 20
(integer) 120

Springboot整合Redis

SpringBoot应该不用过多介绍了吧!是Spring当前最火的一个框架,既然学习了Redis,我们肯定是要在实际项目中使用,那么肯定首选整合SpringBoot啦!

简单介绍下SpringBoot对Jedis的支持吧,在1.×版本的时候,SpringBoot的底层还是使用Jedis来连接Redis的,但是在2.×版本后,就换成了Lettuce。两者的区别如下:
Jedis: 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式!
Lettuce: 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式!

引入依赖:

<dependencies>
        <!--集成redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.7.RELEASE</version>
        </dependency>
        <!--序列化-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
            <scope>compile</scope>
        </dependency>
        <!--lombok,自动生成set、get等方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>compile</scope>
        </dependency>

yml配置:

server:
  port: 9988

spring:
  redis:
    host: 192.168.233.131 #虚拟机的ip地址
    port: 6379
    timeout: 500

连接测试类:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
public class testRedis {

    @Autowired(required = false)
    private RedisTemplate redisTemplate;

    @Test
    void getName(){
        redisTemplate.opsForValue().set("name","rose");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }
}

连接不上。

改redis.conf中的protect mode,以及注释其中的bind 127.0.0.1,还是不行。

修改防火墙(在Linux命令行中操作):

firewall-cmd --zone=public --add-port=6379/tcp --permanent    //开放端口

firewall-cmd --reload   //重新加载

firewall-cmd --zone=public --list-ports    //查看当前开放端口有哪些

仍然不行。。

后来发现,是redis配置文件修改之后,redis未重启的问题。必须进到/usr/local/bin/mtconfig,输入以下命令关闭和重启:

redis-cli -h 127.0.0.1 -p 6379 shutdown	#关闭redis

redis-server redis.conf	#启动redis

连接成功后,返回了rose。

redisTemplate常用方法

// redisTemplate  #操作不同的数据类型,api和我们的指令是一样的 
// opsForValue  #操作字符串 类似String 
// opsForList  #操作List 类似List 
// opsForSet  #操作set
// opsForHash  #操作hash
// opsForZSet  #操作zset
// opsForGeo   #操作geo
// opsForHyperLogLog  #操作HyperLogLog
// 除了进本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的 CRUD 
// 获取redis的连接对象 
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); 
// connection.flushDb(); 
// connection.flushAll();

如果是用基本RedisTemplate类来操作Redis的话,是基本上可以达到所有效果的,具体方法和命令大体一致。

对象的保存和读取(自定义RedisTemplate)

新增一个user类:

import lombok.Data;

@Data
public class User {
    private String name;
    private Integer age;
    private Integer high;
}

测试类:

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
public class testRedis2 {

    @Autowired(required = false)
    private RedisTemplate redisTemplate;

    @Test
    void setObject(){
        User user = new User();
        user.setName("rose");
        user.setAge(22);
        user.setHigh(185);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

}

报错Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.example.demo.User]

这是因为保存对象时我们要序列化。

自定义一个RedisConfig类,在其中自定义RedisTemplate,放在包路径下,就可以自动覆盖旧的:

package com.example.demo;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    // 这是我给大家写好的一个固定模板,大家在企业中,拿去就可以直接使用!
    // 自己定义了一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

再次测试,返回User(name=rose, age=22, high=185),测试成功。

封装RedisUtils类

在main文件夹(而非test)下封装一个RedisUtils类,因为用用RedisTemplate来操作Redis的实在太繁琐:

package com.example.demo;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    public Set<String> keys(String keys){
        try {
            return redisTemplate.keys(keys);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
     /**
     * 普通缓存放入, 不存在放入,存在返回
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean setnx(String key, Object value) {
        try {
            redisTemplate.opsForValue().setIfAbsent(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

  /**
     * 普通缓存放入并设置时间,不存在放入,存在返回
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean setnx(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0){
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================
    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

基本上常用的Redis操作都写在这里了,我们在工作中需要用什么,直接通过RedisUtils来使用即可。

使用RedisUtils

添加控制器:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private RedisUtil redisUtil;

    @GetMapping("/getname")
    public Map<String,Object> getName(@RequestParam Map<String,Object> map){
        Map<String,Object> result = new HashMap<>();
        result.put("name",redisUtil.get(map.get("name").toString()));
        return result;
    }

    @GetMapping("/setname")
    public Object setName(@RequestParam Map<String,Object> map){
        String nameKey = map.get("nameKey").toString();
        String nameValue = map.get("nameValue").toString();
        redisUtil.set(nameKey,nameValue);
        return "加入成功!";
    }

}

测试set:

localhost:9988/test/setname?nameKey=name&nameValue=rose

返回”加入成功!“

测试get:

localhost:9988/test/getname?name=name

返回{“name”:“rose”}

至此测试成功。这里又出现了一个小问题,就是再次提示连接失败,查看虚拟机,发现虚拟机的ip地址变动了。

只好重新修改yml文件中配置的ip地址,暂时解决。