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地址,暂时解决。