nginx配置+redis使用以及同步

交易型系统设计的一些原则:

在设计系统时,应该多思考墨菲定律

1.任何事情都没有表面看起来那么简单;

2.所有的事都会比你预计的时间长;

3.可能出错的事总会出错;

4.如果你担心某种情况发生,那么它就更有可能发生。

 

大流量缓冲

电商大促时,系统流量高于正常流量的几倍甚至几十倍,解决手段很多,一般都是牺牲强一致性,而保证最终一致性。比如扣减库存:

nginx配置+redis使用以及同步_第1张图片

 

数据闭环

因为数据来源太多,影响服务稳定性的因素就非常多,可以把使用到的数据进行异构存储,形成数据闭环,基本步骤:

1.数据异构:通过如MQ机制接收数据变更,然后原子化存储到redis或持久化kv存储

2.数据聚合:异构的目的是把数据从多个数据源拿过来,聚合就是把这些数据做个聚合, 前端一次调用就能拿到所有数据,此步骤一般存储到kv存储中

3.前端展示:前端通过一次或少量几次调用拿到所有数据

这种方式的好处是数据的闭环,任何依赖系统出问题,还是能正常工作,只是更新会有积压,但是不影响前端展示。

eg:一次需要多个数据,考虑使用Hash Tag机制,如获取商品基本信息“p:productId:”和商品规格参数“d:productId:”,可以使用冒号中间的productId作为数据分片key,这样相同productId的商品相关数据就在一个实例。

 

缓存银弹

1:浏览器端缓存:适用于对实时性不太敏感的数据,如商品详情页框架,商家评分,评价,广告词等;但对于价格,库存等实时要求比较高的数据,就不能做浏览器端缓存;

2:APP客户端缓存:大促之前把APP需要访问的一些素材,如:js/css/image提前下发到客户端进行缓存;APP地图也会做离线缓存,在网络异常情况下有托底数据给用户展示;

3:CDN缓存:让用户能在离他最近的节点找到想要的数据(使用CDN时,URL不能有随机数,否则每次都穿透CDN,回源到源服务器)

4:接入层缓存:使用nginx搭建一层接入层

(1)一致性Hash:保证相同数据落到一台服务器上;

(2)nginx配置proxy_cache缓存内容:

https://blog.csdn.net/dengjiexian123/article/details/53386586

(3)nginx配置proxy_cache_lock,使用lock机制,将多个回源合并为一个:

当多个客户端同时请求同一份内容时,如果开启proxy_cache_lock(默认off)则只有一个请求被发送至后端;其他请求将等待该内容返回;当第一个请求返回时,其他请求将从缓存中获取内容返回;当第一个请求超过了proxy_cache_lock_timeout超时时间(默认5s),则其他请求将同时请求到后端来获取响应,且响应不会被缓存;启用proxy_cache_lock可以应对雪崩效应;

5:应用层缓存:local redis cache,在应用所在服务器上部署一组redis,应用直接读本 机redis获取数据;

6:分布式缓存:

(1)接入层(nginx+lua)读取本地proxy cache/local cache

(2)不命中,读取分布式redis集群

(3)还不命中,回源到Tomcat,读取Tomcat应用堆内cache

(4)都没命中,调用依赖业务获取数据,异步化写到redis集群

高可用:

负载均衡需要关心几个方面

1:上游服务器配置:使用upstream server配置上游服务器

upstream backend {

server 192.168.61.1:9080 weight = 1;

server 192.168.61.1:9090 weight = 2;

}

location / {

proxy_pass http://backend;

}

2:负载均衡算法

upstream backend {

round-robin; //轮询

ip_hash; //根据客户端ip负载均衡

hash $request_uri; //根据请求uri进行负载均衡

hash $consistent_key consistent; //一致性hash算法

least_conn; //负载到最少活跃连接的上游服务器

least_time; //最小平均响应时间进行负载均衡(商业版)

server 192.168.61.1:9080 weight = 1;

server 192.168.61.1:9090 weight = 2;

}

3:失败重试机制

upstream backend {

server 192.168.61.1:9080 max_fails=2 fail_timeout=10s weight = 1;

server 192.168.61.1:9090 max_fails=2 fail_timeout=10s weight = 2;

//max_fails:允许请求失败的次数

//fail_timeout:max_fails次失败后,暂停的时间,之后再重试

}

location /test {

proxy_connect_timeout 5s;

//nginx与upstream server的连接超时时间,不管upstream server有没有足够的线程处理请求,只要建立连接就ok

 

proxy_read_timeout 5s;

//连接成功后,等候后端服务器响应时间

 

proxy_send_timeout 5s;

//后端服务器数据回传时间

 

proxy_next_upstream error timeout;

proxy_next_upstream_timeout 6s;

proxy_next_upstream_tries 2;

//当error timeout时,在6s内允许2次重试下一台上游服务器

 

client_header_timeout 5s;

//读取客户端请求头超时时间

 

client_body_timeout 10s;

//读取客户端内容体超时时间

 

send_timeout 10s;

//nginx向客户端传输数据的超时时间

 

proxy_pass http://backend;

}

4:服务器心跳检查

upstream backend {

server 192.168.61.1:9080 weight = 1;

server 192.168.61.1:9090 weight = 2;

check interval=3000 rise=1 fall=3 timeout=2000 type=tcp;

//nginx使用tcp对上游服务器进行健康检查

//interval:每隔3秒检测一次

//检测失败3次后,上游服务器标识为不存活

//检测成功1次后,上游服务器标识为存活,可以处理请求

//检测请求超时时间配置

}

Http动态负载均衡

Consul+Consul-template

nginx配置+redis使用以及同步_第2张图片

 

1:upstream服务启动,通过Consul管理后台注册服务;

2:Nginx机器上部署并启动Consul-template Agent,通过长轮询监听服务变更;

3:Consul-template监听到变更后,动态修改upstream列表;

4:调用重启Nginx脚本,重启nginx。

Consul+OpenResty

nginx配置+redis使用以及同步_第3张图片

 

1:upstream服务启动,通过Consul管理后台注册服务;

2:nginx启动时,调用init_by_lua,拉取配置,并更新到共享字典来存储upstream列表,然后通过init_worker_by_lua启动定时器,定期去Consul拉取配置并实时更新共享字典;

3:balancer_by_lua使用共享字典存储的upstream列表进行动态负载均衡。

隔离术

1:线程隔离:将请求分类,交给不同的线程池处理;

nginx配置+redis使用以及同步_第4张图片

 

2:进程隔离:将系统拆分为多个子系统,使得某一个子系统出现问题,不会影响其它子系统;

nginx配置+redis使用以及同步_第5张图片

 

3:集群隔离:当秒杀服务被刷会影响其他服务稳定性时,应考虑为秒杀提供单独的服务集群,即服务分组,实现故障隔离;

nginx配置+redis使用以及同步_第6张图片

 

4:机房隔离:一个机房发生问题,可通过DNS/负载均衡将请求全部切到另一个机房;

5:读写分离:通过主从模式,将读和写集群分离;

nginx配置+redis使用以及同步_第7张图片

 

6:动静隔离:JS/CSS/图片等静态资源放在CDN上,防止访问量一大,带宽被打满,出现不可用;

nginx配置+redis使用以及同步_第8张图片nginx配置+redis使用以及同步_第9张图片

 

7:爬虫隔离:通过限流或者将爬虫路由到单独集群,保证正常流量可用;

set $flag 0;

if ($http_user_agent ~* “spider”) {

set $flag = “1”;

}

if ($flag = “0”) {

//代理到正常集群

}

if ($flag = “1”) {

//代理到爬虫集群

}

限流详解

1:数组方式频率控制:获取当前时间对数组求模得到数组下标,对该下标处的请求数进行原子+1操作,来判断是否达到每秒钟最大访问次数;

eg:

 

2:redis+抖动值方式频率控制:利用redis(设置合理失效时间)保存该ip地址上一次访问/controller/action的时间,根据设置的频率控制大小,计算访问一次的时间间隔,得到上次访问之后,下次访问时间应该在多少之后。设置抖动值为2,允许某些接口连续访问两次的情况;

eg:

nginx配置+redis使用以及同步_第10张图片

 

3:TokenBucket算法:频率控制桶按照1/QPS的时间间隔(如果QPS是100,间隔是10ms),向桶中添加令牌。每次访问,若能从桶中拿走一个可用token,则没有超过访问频率;

eg:

nginx配置+redis使用以及同步_第11张图片

 

4:使用redis:服务每秒/每分钟/每天的调用量;

5:漏桶算法:请求先进入漏桶,漏桶以一定速度出水,当请求速度过大导致漏桶溢出,就拒绝请求;

6:限流总并发/连接/请求数:Tomcat设置acceptCount(正在处理量+排队大小),maxConnects(瞬时最大连接数,超过会排队),maxThreads(请求最大线程数);Mysql:max_connections(最大用户连接数);Redis:maxclients,tcp-backlog(进程还没有accept的TCP连接的队列长度);

7:ngx_http_limit_conn_module:根据定义的键(域名,ip等)来限制每个键值的并发连接数;

http {

limit_conn_zone $binary_remote_addr zone=addr:10m;

//$binary_remote_addr:限流的key

//addr:限流的共享内存区域,以及大小

 

limit_conn_log_level error;

//被限流后的日志级别,默认error

 

limit_conn_status 503;

//被限流后返回的状态码

 

server {

location /limit {

limit_conn addr 1;

//nginx限制addr中的每个key,同一时刻最大处理1个连接

}

}

}

8:ngx_http_limit_req_module:根据定义的键(域名,ip等)来限制每个键值的请求速率(漏桶算法实现);

http {

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

//$binary_remote_addr:限速的key

//one:限速的共享内存区域,以及大小

//rate:每个key的请求速率

 

limit_req_log_level error;

//超速后的日志级别,默认error

 

limit_req_status 503;

//超速后返回状态码

 

server {

location /limit {

limit_req zone=one burst=5 nodelay;

//one:限速区域;burst:桶容量;nodelay:超速后是否延迟处理

//burst>0 && nodelay:表示允许突发处理请求

}

}

}

9:throttleFirst/throttleLast:在一个时间窗口内,只处理第一个或最后一个事件(eg:网页中的resize,scroll,mousemove事件);

注:

使用redis进行控制,需要调用redis同步接口,可以理解为对多台机器整体进行频率控制。

多级缓存:支撑海量读服务:http://www.importnew.com/18983.html

nginx配置+redis使用以及同步_第12张图片

 

1:接入Nginx将请求负载均衡到应用Nginx(多个机房)

轮询:使服务器请求更加均衡

一致性哈希:提高应用Nginx缓存命中率

2:应用Nginx读取本地缓存(降低后端压力,命中则直接返回)

Lua Shared Dict / Nginx Proxy Cache / Local Redis(本机的Redis集群)

3:读取相应分布式缓存(命中则直接返回,并回写Nginx本地缓存)

4:回源到Tomcat集群,读取本地堆缓存(轮询/一致性哈希)

5:可选:4未命中,尝试一次读主redis集群(防止从redis集群有问题的流量冲击)

6:查询DB或相关服务获取相关数据

7:步骤6返回数据异步写到主Redis集群

存在问题:多个Tomcat实例同时写主Redis集群,造成数据错乱

解决思路:(1)更新数据时使用时间戳/版本对比 + Redis单线程原子操作

          (2)将更新请求按规则分散到多个队列,每个对列使用单线程拉最新数据

  (3)分布式锁:

a)Mysql:数据库排他锁,基于Mysql的InnoDB引擎

注:在查询语句后加for update,使用行级锁需建唯一索引

b)Redis:http://ifeve.com/redis-lock/

思路1:在Redis单实例里创建一个带有过期时间的键值

缺点:master在向slave同步之前宕机,会出现两客户端持相同锁情况

思路2:使用Redis SET req_key req_id NX PX 3000指令(和思路1相比:增加req_id)

NX:key不存在才会设置

PX:key超时时间

req_id:用来判断这把锁是哪个请求加的(释放锁时使用防止错误删除锁)

思路3:RedLock算法(假设N个Redis master节点)

a)客户端获取当前时间

b)轮流用相同req_key req_id在N个节点上请求锁

c)当客户端在N/2+1个master节点获取锁成功,并且总耗时没有超过锁失效时间,认为该锁获取成功

d)获取成功锁不超过N/2+1或着消耗时间超过锁释放时间,客户端需要到每个master节点去释放锁

eg:

上图可以去掉主Redis集群,将Local Cache和从Redis集群,改成独立的Redis集群进行管理,分别作为一级缓存,二级缓存。

 

Nginx本地缓存:解决热点缓存问题

分布式缓存:减少回源率

Tomcat堆缓存:应对上一层缓存失效/崩溃后的冲击

 

如何缓存数据

不过期缓存思路一:

nginx配置+redis使用以及同步_第13张图片

 

存在问题:(1)事务提交失败,缓存不回滚,造成不一致

  (2)并发写缓存出现脏数据

不过期缓存思路二(改进):

nginx配置+redis使用以及同步_第14张图片

 

改进点:(1)写缓存改成写消息

(2)同步缓存订阅消息,根据消息更新缓存

(3)数据一致性:

a) 消息体只传id拉最近数据更新缓存(不担心回滚)

b) 时间戳和内容摘要机制(MD5)进行缓存更新(时间戳控制mq版本)

池化技术:通过复用,提升性能

对象池:复用对象从而减少创建对象,垃圾回收开销;

数据库连接池,Redis连接池,HTTP连接池:复用TCP连接减少创建和释放连接;

线程池:减少频繁创建和销毁线程的过程;

eg:golang sql连接池

nginx配置+redis使用以及同步_第15张图片

你可能感兴趣的:(nginx,redis,同步,配置)