文章目录
- 一、浅谈分布式
-
- [1.1 单机架构](#1.1 单机架构)
- [1.2 认识分布式](#1.2 认识分布式)
- [1.3 数据库分离和负载均衡](#1.3 数据库分离和负载均衡)
- [1.4 引入缓存](#1.4 引入缓存)
- [1.5 数据库分库分表](#1.5 数据库分库分表)
- [1.6 微服务](#1.6 微服务)
- 二、认识Redis
-
- [2.1 Redis的特性](#2.1 Redis的特性)
- [2.2 Redis的应用](#2.2 Redis的应用)
- 三.Redis通用指令
一、浅谈分布式
1.1 单机架构
单机架构,即只有一台服务器,这个服务器负责所有的工作;

- 现在公司的绝大多数产品,都是单机架构,实际上,现在硬件水平已经很高了,一台主机的性能也已经很高了;
- 当产品的用户量和数据量进一步增高时,一台主机就可能无法应付,此时就需要引入更多主机,引入分布式;
1.2 认识分布式
分布式,即使用多台服务器,去负责产品的所有工作;
一台主机的硬件资源是有上限的,其核心硬件资源包括但不限于:
- CPU
- 内存+硬盘
- 网络
- 。。。
- 服务器每收到一个请求就会消耗上述的一些资源
- 如果同一时刻的请求突然变多,那么上述的某个/些资源可能就不够用了,就会导致服务器处理请求的时间变长,甚至出错;
如果遇到服务器资源不够的问题,如何处理?
- 开源:硬件资源不够就加大硬件资源,无法加大一台主机的硬件资源了,就增加主机数
- 节流:软件上的优化,通过性能测试找到瓶颈所在,在对症下药
一旦引入了多台主机,那么该系统就可以叫做"分布式系统";分布式系统的引入,绝不是一个非常好的做法,其是万不得已的做法,因为其可能使得系统的复杂度指数提高,导致bug的可能性就更改,加班和带走年终奖的概率也就更高;
1.3 数据库分离和负载均衡
- 一个最简单的分布式系统就是直接将应用服务和数据库进行分类;
- 对于不同的服务器,可以根据服务去特化对应的服务器;

- 但当数据量和请求量进一步增多时,硬件资源仍然可能不够用,此时需要增加更多的硬件资源,就需要引入更多的服务器节点,即集群架构;

该架构中,引入了更多的服务器,每个服务器都能完整的处理请求,同时存在一个负载均衡器/网格服务器,用户的请求会先到该服务器,之后该服务器根据某个算法将请求分配给其他应用服务器执行响应处理;
假设有10000个请求到网关服务器,有N个应用服务器,那么每个应用服务器可能需要处理的请求数可能就只有10000/N个,大大的减小了单台主机的资源消耗;
问题一:如果请求非常多,那么负载均衡服务器能处理的过来吗?
- 负载均衡服务器由于主要是负责分配请求,因此其消耗的资源是比较少的,因此其可以处理的请求量是远高于应用服务器的;
问题二:负载均会服务器是否有可能扛不住?
- 可能,因此可以考虑提高硬件,即引入更多的负载均衡器
现在,应用服务被分为了多个服务器,但是数据库服务器仍只有一个,意味着所有的请求最终都会访问这一个服务器,因此数据库服务器也有可能扛不住压力;
=》开源+节流(门槛高,更复杂) =》引入更多的数据库服务器;=》数据库读写分离

- 实际应用中,读比写要频繁的多。因此,主服务器一般只有一个,从服务器有多个(一主多从)
- 同时从服务器通过负载均衡让应用服务器进行访问;
1.4 引入缓存

数据库天然的问题就是响应速度较慢,因此将数据区分"冷热",将热点数据放入缓存中,这样访问的速度就大大提升了;
=》引入缓存服务器,将热点数据放人到缓存服务器,这也就是Redis应用的场景之一;
=》虽然这样使得访问速度高了,但也带来了一定的问题:
- 缓存一般是用内存的,因此能存储的数据较少
- 面临数据同步的问题(主要)
1.5 数据库分库分表
- 引入分布式系统,不仅是要处理更大的请求量,还是要处理更大的数据量;
- 一个数据库服务器中,一般存在多个数据库,但还是会出现:一台服务器存不下这些数据库;
- 因此,一台服务器装不下,就可以多加几台数据库服务器,每个服务器存储一个或一部分数据库;
- 同理,一个数据库的某张表可能很大(如数据库存视频数据),甚至可能大到一个服务器存不下,因此也可以对表进行拆分;
- 无论是分库还是分表,其均需要结合具体的业务场景;

1.6 微服务
- 对于一个应用服务,一般的做法是一个应用服务器中包含多个业务,但这导致了一个服务器的代码会越来越复杂;
- 为了更方便的维护,就将这一个应用服务器拆分为更多的,功能更单一的,更小的服务器;

问题一:引入微服务的代价?
- 系统性能下降(原本是进程间通信,现在多主机间通过网络通信,但网络一般是慢于硬盘的,因此性能下降)
- 系统复杂程度变高,可用性受到影响;服务器变多,出现问题的概率就变多了;
问题二:微服务的优点?
- 更方便的进行维护和管理
- 更好的实现功能复用(原本的功能代码是整体,现在拆成独立模块,可以直接用该模块)
- 可以给不同的服务进行不同的部署
二、认识Redis

- Redis是一个在内存中存储数据的中间件,作为数据库,作为数据缓存。。。其主要应用在分布式系统中;
- 对于单机系统,使用变量即可以存储内存中的数据,因此在单机程序中,Redis的存储反而不一定优于直接用变量
2.1 Redis的特性
- MySQL主要通过"表"来存储组织数据的,是"关系型数据库";Redis主要通过"键值对"的方式存储组织数据的,是"非关系型数据库";
- Redis是可扩展的; Redis提供了一组API,可以在Redis原有基础上进行功能扩展
- Redis支持持久化; Redis存储的是内存数据,但内存断电易失,因此Redis提供了持久化的特性,即能将数据在磁盘上做备份
- Redis支持集群;由于内存空间有限,因此Redis能存储的数据是有限的,因此Redis可以引入多台主机,部署多个Redis节点,每个Redis存储数据的一部分;
- Redis是高可用的 ; Redis支持"主从"结构;从节点是主节点的备份,主节点出问题时,从节点可以立刻顶替;
Redis的核心特点在于快,其体现在:
- Redis数据在内存中,比访问硬盘的数据库快的多;
- Redis核心功能的逻辑都比较简单;
- 网络角度看,Redis使用了IO多路复用(epoll)(一个线程,管理多个socket)
- Redis使用了单线程模型(虽然更高版本已经引入了多线程),这种单线程模型,减少了不必要的线程间的竞争开销;
2.2 Redis的应用
- 作为缓存:通过缓存,可以极大的缓解数据库的压力;
- 作为会话存储:对于单机架构,session可以存储在应用服务器上,但引入了分布式系统后,就可以将会话数据单独拿出来,放在一组独立的机器上存储(Redis);
- 消息队列:Redis创建时的期望用法,但现在不是作为主要用法;
- 作为数据库
- 排行榜系统
- 社交网络
- 计数器
- 。。。
三.Redis通用指令
3.1 通用指令
1)get和set
Redis按照键值对进行存储的,其中get和set方法是最核心的方法;
bash
set key1 value1
set key2 value2
get key
# 1.Redis命令不区分大小写
# 2.key和value不用加上引号,就表示字符串类型
# 3.get命令得到key对应的value,如果key不存在,返回nil,nil相当于null
2)keys
- Redis是键值对结构,key固定是字符串,value有许多类型(字符串,哈希表,列表,集合,有序集合等);
- keys用来查询当前服务器上匹配的key;可以通过通配符来描述key的样式,能匹配的key就查询出来;
bash
支持如下样式的pattern:
1. ? 匹配任意单个字符: he?lo
2. * 匹配0个或多个任意字符: he*lo
3. [abc] 只能匹配a/b/c字符: he[abc]lo
4. [^ab] 不能匹配a/e字符
5. [a-e] 范围匹配,匹配a-e中任意的一个

注意事项:
keys的时间复杂度为O(n),一般在生产环境中会禁止keys命令,尤其是"keys * " ;
- 生产环境上的key非常多,而Redis是单线程服务器,执行"keys *的时间非常长,就可能使Redis服务器被阻塞,无法给客户端提供服务;
- Redis经常做缓存,其会挡住MySQL之前,如果Redis因为 keys *阻塞,那么其他Redis操作就会超时,从而直接访问MySQL;如果这样的请求过多,那么MySQL就可能来不及处理,容易挂掉;
- 办公环境:公司发的电脑
- 开发环境:有时候开发环境和办公环境一样,有时候开发环境是单独的服务器,做后端,很有可能是单独的服务器;
- 测试环境:测试人员使用的
- 线上环境/生产环境:如上的三个环境是线下环境,是用户无法访问的;线上环境是用户可以访问的;
3)exists
判断某个key是否存在,返回key存在的个数;
bash
exits hello #查询键hello是否存在,查一个
exits hello song key #查询键hello和song和key是否存在,查多个
一次查多个键和多个键查多次的区别:
- 分开查询,会进行更多的网络通信
- 网络通信的开销(比如封装分用的开销)是很多的,因此多次查询的效率更低
- Redis考虑了这样的网络开销问题,因此其大多数命令都能支持一次操作多个key
- Redis虽然快,但也只是和MySQL比较的;
4)del
删除指定的key并返回删掉的key的个数;时间复杂度为O(1);
bash
del hello #删除键hello并返回删除的个数
del hello hello song key #删除键hello,song,key并返回删除的个数
#redis的删除影响并不是很大,因为其一般作为缓存,即使删了,数据库也还有;
5)expire
给指定的key设置过期时间(key存活时间超过该值,就会自动删除,单位是秒);
成功返回1,否则返回0(key不存在);
可能的使用场景:
- 手机验证码的有效时间
- 点外卖的优惠券的有效时间
- redis分布式锁,避免一直死锁,到期进行自动解锁
6)ttl
看key剩余的过期时间;返回值key剩余的过期时间,如果key不存在返回-2,-1表示key未设置过期时间;
bash
ttl key #查看key剩余的过期时间
7)type
返回key对应的value的数据类型;
bash
type key #返回key对应的value的类型
3.2 Redis的key过期策略
一个Redis中可能存在很多的key,这些key中可能有很多是存在过期时间的,此时,Redis服务器就需要知道哪些key已经过期要删除,哪些key还没过期?
惰性删除+定期删除
假设某个key的过期时间已经到了,但不会立马删除它,而是继续保存;
如果在后面的某次访问中正好用的了该key,就让redis服务器触发删除key的操作,同时返回nil;
但如果每次后使用才删除那也是不可靠的,因此通过定期删除,每次抽取一部分key,进行验证过期时间,来保证抽检的过程足够快;
显然,上面的机制很大概率会导致残留许多过期的key,redis为了应对此情况,还提供了一系列内存淘汰策略来保证;
问题一:定期删除为什么是抽取一部分检验,而不是全部?redis是单线程程序;如果画很多时间去扫描key,那么正常的命令就可能被阻塞;
问题二:为什么仅靠惰性删除无法保证可靠性?
- 如果仅靠惰性删除,那么一定Redis中一定存在大量未被删除的过期Key,这些key已经无用但还占据内存,而内存本就小,因此浪费了内存空间;
3.3 定时器的高效实现
有的资料中会说Redis中还引入了定时器,但实际这是错误的,Redis并未引入定时器来保证过期策略;
1)基于优先级队列
- 假设key设置了过期时间,将key放入优先级队列中,指定规则:过期时间早的,先出队列,队首即为最早的要过期的key;
- 分配一个线程,让该线程检查队首元素,看是否过期即可;如果队首元素未过期,则后面的元素一定也没过期;
- 因此,扫描线程不需要遍历所有的key;
- 另外,扫描线程扫描的不能太频繁,因此根据当前时间和队首的过期时间计算一个等待时间,时间到了,就唤醒该线程;来新任务就重新检查并重新计算即可;

2)基于时间轮

- 将时间划分为小段,每个小段都挂着一个链表,链表的每个节点都表示一个要执行的任务;
- 定义一个指针,每隔一个时间段指针就移动到一个格子,之后尝试执行格子上链表的任务;