Redis下载:苍老师网站
Redis就是一个能够将信息或数据保存在内存中的缓存数据库。
Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。目前Redis的开发由Redis Labs赞助。根据月度排行网站DB-Engines.com的数据,Redis是最流行的键值对存储数据库。
Redis是一个开发好的软件,有固定的使用方式。
Redis的特征:
Redis是一个内存(缓存)数据库,因为数据保存在内存中,所以速度快,每秒可执行10万次读写操作。
虽然Redis是一个内存数据库,但是它允许将数据保存在硬盘上,以便出现运行异常时恢复(Redis数据保存到硬盘上的策略有两种:AOF和RDB,可同时开启)
Redis保存数据使用key-value的格式,类似java中的Map类型集合,这样使用key-value保存数据的数据库统称为"非关系型数据库",英文"no-sql"(关系型数据库,sql,通过外键等建立关系)
Redis的key为string类型,value支持各种类型:string、list、set、zset、hash。
Redis支持微服务系统需要的分布式部署,支持master-slave(一主多从)的模式,以达到"高并发、高可用、高性能"的目的
Redis的竞品软件memcached,关于它们的区别可以自学
Redis是一个缓存数据库,以键值对的形式将数据保存到内存中,属于非关系型数据库(nosql),特点是快速响应
内存数据断电消失
Redis将数据保存到硬盘的两种方式:AOF、RDB,可同时进行
有些数据库软件也以key-value形式将数据保存到硬盘上,但是是关系型数据库(sql),如:MongoDB
关系型数据库主要通过外键等建立关系,一般是存在硬盘上,主要用途是用来保存全部数据,如:MySQL、Oracle、MongoDB、ServerSocket
非关系型数据库主要以键值对形式将数据保存到内存中,主要是辅助关系型数据库使用,特点是响应速度快
注意:并非所有的非关系型数据库数据都存在内存中,也有存在硬盘中的
Redis的竞品memcached,就性能而言,memcached性能好,但是redis可以进行备份到硬盘
redis可用于数据进行增减的场合,如商品抢购
redis数据存在内存中,只有重启电脑数据才会清除,一般是1小时无人访问就会自动回收,但是这不是绝对的,一般redis会根据内存分配灵活配置
如下图所示,当faq模块由多台服务器组成时,每个服务器都要缓存一份所有标签,这样会造成缓存冗余,造成内存的浪费。我们可以使用Redis保存所有标签,当任何faq服务器需要时直接从Redis中获取即可,这样节省了内存,提高了服务器性能。
解压运行下载的Redis,文件夹内容如下所示:
双击redis-start.bat文件可以启动redis,出现一个界面,这个界面不能关,一关redis就停止了
redis-cli.exe可以运行操作Redis的客户端
由于每次开机都要启动redis,界面还不能关,不方便。
我们可以实现每次开机自动启动,需要运行下面的文件:
先运行: service-installing.bat---安装Redis服务到操作系统
再运行:service-start.bat---启动Redis服务
如果想停止服务: service-stop.bat
如果想卸载: service-uninstalling.bat
运行上面的步骤1和2即可,正常情况下,每次开机redis都会自动开启了,然后打开redis-cli.exe,启动后输入info,输出下面的信息就表示redis启动成功了,之后可以在此界面进行代码的编写:
Redis支持如下几种数据类型:
我们主要使用string字符串类型,string类型基本操作如下:
127.0.0.1:6379> set mystr "hello world!" //保存字符串类型 127.0.0.1:6379> get mystr //读取字符串类型
除了保存字符串,Redis还特别适合保存频繁变化的数字。因为如果频繁修改硬盘数据库(mysql)中的数字的话,每次都是硬盘操作,效率低;如果修改的是redis中的数据,那么支持的并发会较大。
除此之外,Redis是一个单线程的程序,没有线程安全问题,所有即使是高并发的程序也能够正确响应数字的变化。
下面是对数字增减的专门命令:
127.0.0.1:6379> set mynum "2" //创建数字,也可直接写数字保存:set mynum 2,默认保存数字为字符串类型 OK 127.0.0.1:6379> get mynum //查询数字 "2" 127.0.0.1:6379> incr mynum //数字增加 (integer) 3 127.0.0.1:6379> get mynum //查询数字 "3" 127.0.0.1:6379> decr mynum //数字减少 (integer) 2 127.0.0.1:6379> get mynum //查询数字 "2"
Redis基本命令补充:
List 列表 常用命令: lpush,rpush,lpop,rpop,lrange等 Redis的list在底层实现上并不是数组而是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。 Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 lists的常用操作包括LPUSH、RPUSH、LRANGE、RPOP等。可以用LPUSH在lists的左侧插入一个新元素,用RPUSH在lists的右侧插入一个新元素,用LRANGE命令从lists中指定一个范围来提取元素,RPOP从右侧弹出数据。来看几个例子:: //新建一个list叫做mylist,并在列表头部插入元素"Tom" 127.0.0.1:6379> lpush mylist "Tom" //返回当前mylist中的元素个数 (integer) 1 //在mylist右侧插入元素"Jerry" 127.0.0.1:6379> rpush mylist "Jerry" (integer) 2 //在mylist左侧插入元素"Andy" 127.0.0.1:6379> lpush mylist "Andy" (integer) 3 //列出mylist中从编号0到编号1的元素 127.0.0.1:6379> lrange mylist 0 1 1) "Andy" 2) "Tom" //列出mylist中从编号0到倒数第一个元素 127.0.0.1:6379> lrange mylist 0 -1 1) "Andy" 2) "Tom" 3) "Jerry" //从右侧取出最后一个数据 127.0.0.1:6379> rpop mylist "Jerry" //再次列出mylist中从编号0到倒数第一个元素 127.0.0.1:6379> lrange mylist 0 -1 1) "Andy" 2) "Tom" Set 集合 常用命令: sadd,smembers,sunion 等 set 是无序不重复集合,list是有序可以重复集合,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要功能,这个也是list所不能提供的。 可以基于 set 轻易实现交集、并集、差集的操作。比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能,也就是求交集的过程。set具体命令如下: //向集合myset中加入一个新元素"Tom" 127.0.0.1:6379> sadd myset "Tom" (integer) 1 127.0.0.1:6379> sadd myset "Jerry" (integer) 1 //列出集合myset中的所有元素 127.0.0.1:6379> smembers myset 1) "Jerry" 2) "Tom" //判断元素Tom是否在集合myset中,返回1表示存在 127.0.0.1:6379> sismember myset "Tom" (integer) 1 //判断元素3是否在集合myset中,返回0表示不存在 127.0.0.1:6379> sismember myset "Andy" (integer) 0 //新建一个新的集合yourset 127.0.0.1:6379> sadd yourset "Tom" (integer) 1 127.0.0.1:6379> sadd yourset "John" (integer) 1 127.0.0.1:6379> smembers yourset 1) "Tom" 2) "John" //对两个集合求并集 127.0.0.1:6379> sunion myset yourset 1) "Tom" 2) "Jerry" 3) "John" Sorted Set 有序集合 常用命令: zadd,zrange,zrem,zcard等 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 很多时候,我们都将redis中的有序集合叫做zsets,这是因为在redis中,有序集合相关的操作指令都是以z开头的,比如zrange、zadd、zrevrange、zrangebyscore等等 来看几个生动的例子: //新增一个有序集合hostset,加入一个元素baidu.com,给它赋予score:1 127.0.0.1:6379> zadd hostset 1 baidu.com (integer) 1 //向hostset中新增一个元素bing.com,赋予它的score是30 127.0.0.1:6379> zadd hostset 3 bing.com (integer) 1 //向hostset中新增一个元素google.com,赋予它的score是22 127.0.0.1:6379> zadd hostset 22 google.com (integer) 1 //列出hostset的所有元素,同时列出其score,可以看出myzset已经是有序的了。 127.0.0.1:6379> zrange hostset 0 -1 with scores 1) "baidu.com" 2) "1" 3) "google.com" 4) "22" 5) "bing.com" 6) "30" //只列出hostset的元素 127.0.0.1:6379> zrange hostset 0 -1 1) "baidu.com" 2) "google.com" 3) "bing.com" Hash 常用命令: hget,hset,hgetall 等。 Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: //建立哈希,并赋值 127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34 OK //列出哈希的内容 127.0.0.1:6379> HGETALL user:001 1) "username" 2) "antirez" 3) "password" 4) "P1pp0" 5) "age" 6) "34" //更改哈希中的某一个值 127.0.0.1:6379> HSET user:001 password 12345 (integer) 0 //再次列出哈希的内容 127.0.0.1:6379> HGETALL user:001 1) "username" 2) "antirez" 3) "password" 4) "12345" 5) "age" 6) "34"
添加依赖:像mysql一样,java可以操作mysql数据库,就可以操作Redis。底层我们使用jdbc操作mysql,redis方面底层使用Jedis操作Redis,但是和jdbc操作数据库一样,使用Jedis操作Redis步骤比较繁琐。
我们可以使用Spring Boot Redis操作Redis,这样会很简单,先添加必要依赖,然后才能使用Spring Boot Redis框架,在knows-faq模块的pom.xml文件添加如下:
<!--Spring连接Redis的依赖,上面为底层,下面为封装优化--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency>
application.properties文件中需要配置Redis的ip地址和端口号,就像我们连接数据库也要提供这些资料一样。
# 配置Redis的ip和端口,localhost即127.0.0.1 spring.redis.host=localhost spring.redis.port=6379
我们可以在测试类中编写代码测试是否可以成功操作redis:
//我们添加Spring Redis的依赖就是向Spring容器中添加了一个可以操作Redis的对象 // RedisTemplate<[key的类型],[value的类型]> @Autowired RedisTemplate<String,String> redisTemplate; @Test public void redis(){ // 向Redis中保存(添加)数据 redisTemplate.opsForValue().set("myname","东方不败"); System.out.println("ok"); } @Test public void getValue(){ //读取Redis中的信息 String name=redisTemplate.opsForValue().get("myname"); System.out.println(name); }
输出结果:
ok 东方不败
我们学习了怎么操作Redis,下面我们就将TagServiceImpl实现类中获得所有标签的方法修改为从Redis中获取。
转到knows-faq模块: TagServiceImpl 实现类代码修改如下:
@Service public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService { //RedisTemplate源代码注入只注入了两个类型 //RedisTemplate<String, String> //RedisTemplate<Object, Object> //如果使用@Autowired自动装配,因为泛型类型不匹配,所以会报错 //但是@Resource是id,注入主要参数名称是redisTemplate就可以注入,成功 @Resource private RedisTemplate<String,List<Tag>> redisTemplate; //从Spring容器中取TagMapper @Autowired private TagMapper tagMapper; //全查所有Tags @Override //重写方法 public List<Tag> getTags() { //先从Redis中获得所有标签的集合 List<Tag> tags = redisTemplate.opsForValue().get("tags"); //如果上面的获取失败,说明Redis中没有所要标签 //那么就是第一次请求,需要连接数据库新增 if(tags==null || tags.isEmpty()){ //连接数据库查询所有标签 tags = tagMapper.selectList(null); //将全查出来的标签保存到redis中 redisTemplate.opsForValue().set("tags",tags); System.out.println("Redis已加载所有标签"); } //千万别忘了修改返回值!!!! return tags; } //全查所有Tags放在Map中 @Override public Map<String, Tag> getTagMap() { Map<String,Tag> tagMap = new HashMap<>(); for(Tag t:getTags()){ tagMap.put(t.getName(),t); } //千万别忘了修改返回值!!!! return tagMap; } }
重新启动knows-faq服务,访问学生首页,路径为:http://localhost:8080/index_student.html,输出效果如下:
控制台输出内容如下:
之后刷新或重启服务,不再输出该语句,因为数据已经存到redis缓冲中去了,除非重启电脑,重启后加载一次即可,以后不再需要重复加载,直接使用即可。
注册和查询所有标签我们已经完成了,下面要完成登录功能,登录功能涉及很多知识点和代码,先来学习Ribbon。
Ribbon是SpringCloud提供的一个组件,它能够实现微服务之间的互相调用。它的使用不用添加额外依赖,因为使用的非常频繁,在spring-cloud-starter-alibaba-nacos-discovery这个依赖中已经集成了。
步骤1 : 明确服务的提供者(方法的定义)
服务的提供者也叫生产者,本次调用我们将sys模块作为生成者,需要定义一个方法作为被调用的方法,必须是一个控制器方法才能被Ribbon调用,我们将/v1/auth/demo这个路径的方法作为调用目标。
步骤2 : 在发起调用的一方添加Ribbon的支持
本次调用的发起者是faq模块,在SpringBoot启动类中注入一个能够发起Ribbon请求的对象
@SpringBootApplication @EnableDiscoveryClient @MapperScan("cn.tedu.knows.faq.mapper") public class KnowsFaqApplication { public static void main(String[] args) { SpringApplication.run(KnowsFaqApplication.class, args); } // @Bean表示将下面方法的返回值保存到Spring容器中 @Bean // 保存到Spring容器的对象支持负载均衡的Ribbon调用 @LoadBalanced // 这个方法的返回值是实现Ribbon调用的对象,使用它来发起跨服务器的调用请求 public RestTemplate restTemplate(){ return new RestTemplate(); } }
步骤3 : 发起调用
使用刚刚保存到Spring容器中的RestTemplate对象调用方法,我们先使用测试类来调用。实际开发中经常会在业务逻辑层中发起,测试类代码如下:
@Autowired RestTemplate restTemplate; @Test public void ribbon(){ // 声明要调用的控制器的路径: // sys-service:要调用的微服务注册到Nacos的名称 // /v1/auth/demo:要调用的控制器的访问路径 String url="http://sys-service/v1/auth/demo"; // ribbon调用 // 参数url:是上面定义的字符串 // 参数String.class:定义返回值类型的反射,根据实际情况编写即可 String str=restTemplate.getForObject(url,String.class); System.out.println(str); }
测试结果:
sys:Hello World!!!
关系示意图如下:
我们下面将刚编写的Ribbon升级,添加了参数,返回值变为了User。faq模块还是请求的发起者(消费者),根据Ribbon的规则,我们首先要在sys模块中定义一个根据用户名返回用户对象的控制器方法,没有这个方法就要编写这个方法,从业务逻辑层开始。
转到knows-sys模块:
(1)IUserService接口中添加方法:
// 根据用户名查询用户对象 User getUserByUsername(String username);
UserServiceImpl类中实现业务逻辑层方法:
//根据用户名查找用户对象的逻辑层实现 @Override public User getUserByUsername(String username) { return userMapper.findUserByUsername(username); }
(2)控制层代码:AuthController添加方法
@Resource private IUserService userService; @GetMapping("/user")//测试路径:http://localhost:8002/v1/auth/user public User getUser(String username){ return userService.getUserByUsername(username); }
在faq模块中进行测试,测试执行前保证sys模块重新启动过!
@Test public void getUser(){ // 有参数的Ribbon调用 // url请求的路径写完之后,使用?分割开始编写参数列表 // 参数的值不能写死要用{1},{2}....这种方式占位 String url="http://sys-service/v1/auth/user?username={1}"; // 调用带参数的方法 // 从第三个参数开始,给{1}赋值,第四个参数给{2}赋值,以此类推 User user=restTemplate.getForObject(url, User.class,"st2"); System.out.println(user); }
测试结果:
需要大家下载一个软件:postman(邮递员)
下载地址:https://www.postman.com/downloads/
这个软件用于向服务器发送各种请求,get\post均可,还可以携带参数。
会话就是多个请求和响应的集合,一般来讲,打开浏览器到关闭浏览器就是一次会话。
会话对应java中的HttpSession对象,所谓会话保持,就是多次请求过程中,服务器都可以获得session中的信息。
单体项目中会话保持是依靠session对象的。
因为微服务具有多个项目,每个项目都有自己的session,在一个服务器中登录并不能共享给其它项目,这样单体项目中的会话保持方式就不能使用了。
如下图所示,sys模块登录成功并不能把登录信息发送给faq模块,这样就无法实现会话保持,微服务项目中有专门的会话保持技术称之为"单点登录"。