黑名单一词来源于世界著名的英国的牛津和剑桥等大学。在中世纪初这些学校规定对于犯有不端行为的学生,将其姓名、行为列案记录在黑皮书上,谁的名字上了黑皮书,即使不是终生臭名昭著,也会使人在相当时间内名誉扫地。学生们对学校的这一规定十分害怕,常常小心谨慎,严防越轨行为的发生。在网络SEO优化当中,搜索引擎或者义务用户收集的搜索引擎垃圾制造者列表,可以用于从搜索引擎封杀这些垃圾制造者,或者抵制他们。黑名单启用后,被列入到黑名单的用户(或IP地址、IP包、邮件、病毒等)不能通过。白名单的概念与“黑名单”相对应,如果设立了白名单,则在白名单中的用户(或IP地址、IP包、邮件等)会优先通过,不会被当成垃圾邮件拒收,安全性和快捷性都大大提高。将其含义扩展一步,那么凡有黑名单功能的应用,就会有白名单功能与其对应。
访问白名单
一、准备工作
- 添加
echo-nginx-module
模块git clone https://github.com/openresty/echo-nginx-module.git /usr/local/echo-nginx-module
- 切换到nginx源码目录
cd /usr/local/src/nginx
- 重新执行
./configure --prefix=/usr/local/nginx --add-module=/usr/local/echo-nginx-module
- 若提示错误少什么依赖就安装相应的依赖,无提示则执行
make && make install
网上还有另一种方法,同理可以添加别的模块
1 | git clone https://github.com/nginx/nginx.git |
安装ngx_devel_kit(NDK)模块
cd /usr/local
git clone https://github.com/simpl/ngx_devel_kit.git
添加
lua-nginx-module
模块cd /usr/local
git clone https://github.com/chaoslawful/lua-nginx-module.git
重新编译nginx
1
2
3
4
5
6
7./configure --prefix=/usr/local/nginx
--with-ld-opt="-Wl,-rpath,$LUAJIT_LIB"
--add-module=/usr/local/ngx_devel_kit
--add-module=/usr/local/echo-nginx-module
--add-module=/usr/local/lua-nginx-module
make -j2
make install重启nginx服务器
测试Lua,在nginx.conf配置文件server代码块中加入以下代码:
1
2
3
4
5
6
7
8location /echo {
default_type 'text/plain';
echo 'install echo module success!';
}
location /lua {
default_type 'text/plain';
content_by_lua 'ngx.say("install lua module success!")';
}curl http://localhost/echo
- 正常的话输出
install echo module success!
- 正常的话输出
curl http://localhost/lua
- 正常的话输出
install lua module success!
- 正常的话输出
二、实现
实现原理:通过在nginx上进行访问限制,通过lua来灵活实现业务需求,redis用于存储黑名单列表
具体过程
- step1:lua代码(post请求,ip地址黑名单,请求参数中imsi,tel值和黑名单)
1 | [root@git-server ~]# vim /usr/local/nginx/conf/lua/ipblacklist.lua |
step2:修改nginx.conf
1
2
3
4
5
6
7location / {
root html;
index index.html index.htm;
access_by_lua_file /usr/local/nginx/conf/lua/ipblacklist.lua;
proxy_pass http://127.0.0.1:8080;
client_max_body_size 1m;
}step3:添加黑名单规则数据
1
2
3redis-cli sadd black.ip '192.160.10.10'
redis-cli sadd black.imsi '460123456789'
redis-cli sadd black.tel '15888888888'step4:验证结果
curl -d "imsi=460123456789&tel=15800000000" "http://yourdomain/index.php"
三、参考
缓存和数据库数据一致性问题
一、渊源
随着计算机技术的飞速发展,web应用由最初的单一节点到多节点负载均衡再到如今的分布式,每一次发展都会伴随着技术上的革新,同时也会带来新的问题。如现在随随便便一个应用设计都需要考虑的“三高一低”的问题,而要满足这样的要求不可避免的会用到缓存、消息队列等技术,这其中就涉及到缓存和数据库数据一致性的问题。
二、解决方案
方案一:缓存设置过期时间,最终一致:对写入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。如果数据库写成功,缓存更新失败,那么只要到达过期时间缓存会自动删除,后面的读请求自然会从数据库中读取新值然后回填缓存。
- 存在问题
- 短时间内为旧数据
- 过期时间如何设置(设置不当会出现雪崩)
- 适用场景
- 对数据实时性要求不高
- 存在问题
方案二:先更新数据库,再更新缓存(很少使用)
存在问题
- 资源浪费(频繁更新冷数据,或写多读少的情景,缓存命中率低)
- 存在脏数据,由于网络原因,可能会出现以下情形,从而导致A数据覆盖了B数据的情况:
1
2
3
4请求A更新了数据库
请求B更新了数据库
请求B更新了缓存
请求A更新了缓存- 请求响应时间延长(每次更新缓存值都需要经过复杂的计算)
适用场景
- 读请求占据网站的大部分流量
- 网站数据量不大(几十万的文章数据)
- 很少会去更新数据(一般文章写好后,不会去更新)
方案三:先更新数据库,再删除缓存(Cache Aside Pattern)
存在问题
- 存在脏数据,由于网络原因,可能会出现以下情形,从而导致A数据覆盖了B数据的情况:
1
2
3
4
5
6用户1请求数据A
数据A缓存失效
用户1从数据库中得到旧数据数据A
用户2更新了数据A(新数据)
用户2删除了缓存
用户1将查到旧数据写入了缓存- 缓存删除失败,由于某种原因导致缓存删除失败
1
2
3用户A更新了数据A
用户A删除数据A的缓存失败
用户B读到数据A缓存的旧数据解决问题
- 设置缓存有效时间
- 引入消息队列
- 更新数据库
- 删除缓存失败
- 将需要删除的Key发送到消息队列
- 隔断时间从消息队列中拉取要删除的key
- 继续删除,直至成功为止
方案四:先删除缓存,再更新数据库
存在问题
- 存在脏数据
1
2
3
4
5
6
7
8
9
10用户A删除缓存失败
用户A成功更新了数据
或者
用户A删除了缓存;
用户B读取缓存,缓存不存在;
用户B从数据库拿到旧数据;
用户B更新了缓存;
用户A更新了数据。解决问题
- 设置缓存有效时间
- 引入消息队列
1
2
3
4
5先淘汰缓存;
更新数据库;
将需要淘汰的缓存Key发送到消息队列;
另起一程序拉取消息队列的数据;
对需要删除的key进行删除,直至删除为止。
方案五:引入重试机制
- 更新数据库数据;
- 缓存因为种种问题删除失败
- 将需要删除的key发送至消息队列
- 自己消费消息,获得需要删除的key
- 继续重试删除操作,直到成功
方案六:重试+binlog
- 更新数据库数据
- 数据库会将操作信息写入binlog日志当中
- 订阅程序提取出所需要的数据以及key
- 另起一段非业务代码,获得该信息
- 尝试删除缓存操作,发现删除失败
- 将这些信息发送至消息队列
- 重新从消息队列中获得该数据,重试操作。
三、参考文献
秒杀系统
一、概念
- 秒杀系统:同一时刻有大量请求争抢购买同一商品,如抢红包、热卖商品、12306购票等。
- 两个问题
- 高并发对数据库产生的压力
- 竞争状态下如何解决库存的正确减少(”超卖”问题)
- 常见解决方案
- 方案一:将库存字段number字段设为unsigned,当库存为0时返回false
- 方案二:使用MySQL的事务,锁住操作的行(select … for update)
- 方案三:使用文件排他锁
- 方案四:使用redis队列(pop操作是原子的)
二、优化
优化方向
- 将请求拦截在系统上游
- 充分利用缓存
- 异步处理请求
优化细节
- 浏览器层请求拦截
- 产品层面,用户点击“查询”或者“购票”后按钮置灰,禁止用户重复提交请求
- 页面层面,限制用户在x秒之内只能提交一次请求(JS控制)
- 站点层请求拦截与页面缓存
- 同一个uid限制访问频度,做页面缓存,x秒内到达站点层的请求均返回同一页面
- 同一个item的查询,例如手机车次,做页面缓存,x秒内到达站点层的请求均返回同一页面
- 数据缓存
- 读请求读缓存(redis、memcache)
- 过滤相同请求(如redis的set数据类型存储请求ID,做判重处理)
- 异步处理
- 写请求入队列依次处理
- db层批量处理数据
- 浏览器层请求拦截
三、方案
服务单一职责,微服务设计思想,再用分布式的部署方式
给秒单独的服务,单独的库,就算挂了, 也不会影响其他服务
秒杀连接加盐,把url动态化,就连写代码的人都不知道,通过md5之类的加密算法随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。
使用redis集群
使用nginx服务器:高性能的web服务器,并发也是随便几万不是梦,但是tomcat只能顶几百的并发啊。那简单啊负载均衡嘛,一台服务器几百,那就多搞点,在秒杀的时候多租点流量机
- 恶意请求拦截也需要用它,一般单个用户请求次数太夸张,不像真人的请求在网关那一层就得拦截掉了
资源静态化:秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,所有页面一般是不会经过后端的,但是前段也要有自己的服务器啊,那就把能提前放到cdn服务器的东西都放进去,反正把能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力
按钮控制:秒杀前按钮置灰,到点了才能点。这是防止在快到秒杀前的时间疯狂请求服务器,这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点了再给按钮可以用,点击一次之后也得置灰几秒,防止一直点。
限流
- 前端限流:跟按钮控制类似,防止一直点
- 后端限流:秒杀的时候肯定是涉及到了后续的订单生成和支付操作,一旦秒杀产品卖完了,return一个false,前端直接秒杀结束
- 真正的限流还会有限流组件,比如阿里的Sentinel、Hystrix等
库存预热:秒杀的本质,就是对库存的争夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存,对开发很不友好,而且数据库顶不住啊
提前把商品的库存加载到redis中去,让整个流程都在redis里做,然后等秒杀结束了,再异步的去修改库存就好了Lua:lua脚本类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作,一个脚本=查库存+扣减库存,如果是0 了直接false
削峰填谷:说到这里就知道是说mq了,卖100个东西直接100个请求,我觉得没问题,但万一秒杀一万个,10万个呢,服务器挂了,把他放消息队列,然后一点点消费去改库存不就好了嘛
异步:一个下单流程:本来需要100ms,后来产品说要加上积分,流程中加上积分扣减,200ms了
秒杀系统怎么才能抢购两次
四、简单DEMO
- 初始化库存
1 | $store = 1000; //商品库存 |
- 用户下单
1 | $redis = new Redis(); |
五、参考
消息队列的几种方式
一、概念
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。它是一种先进先出的数据结构,即操作队列元素时有顺序性。
消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ,以及Redis。
二、实现方式
- 基于List
- 非阻塞命令LPUSH/RPOP
- 阻塞命令BRPOP
此处的阻塞与非阻塞理解:基于List实现的消息队列,消费者端一般写个死循环(
while(true)
)不断的从队列中拉取消息;当队列为空时,消费者端使用RPOP时仍然会不断拉取消息,进而造成CPU空跑浪费资源;BRPOP则会阻塞,当队列不为空时会重新拉取(在设置的超时时间范围内)。
1 | # 客户端1 |
- 基于Pub/Sub
1 | # 客户端1 |
基于Zset
- 根据score值的有序性可实现顺序消费的消息队列
基于Stream(5.0+版本支持)
1 | # 客户端1 |
三、参考
亿级别活跃度以及登录次数统计
一、bitmaps
- 定义
redis提供的bitmaps这个“数据结构”可以实现对位的操作,其本身不是一种数据类型而是字符串,但它可以对字符串的位进行操作。
- 可以把bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量。
- 单个bitmaps的最大长度是512MB,即2^32个比特位。
命令
- 设置值
setbit key offset value
- 获取值
getbit key offset
- 统计bitmaps值为1的个数
bitcount key [start end]
- 计算交集
bitop and destkey key[key…]
- 计算并集
bitop or destkey key[key…]
- 计算差集
bitop xor destkey key[key..]
注:
setbit/getbit
命令中offset
和bitcount
命令中start
和end
有个对应关系,offset*8=start/end
- 设置值
二、实现
三、参考
分布式锁
一、命令
旧版本
- setnx my_key myvalue
- expire my_key 5
- del my_key
新版本
set key value EX 60 NX
二、DEMO
- 基于setnx版本(多个命令无法保证事务性操作)
1 | $redis = new Redis(); |
基于lua版本
基于set版本