经典案例
文章目录
php vs go
php 优点
- array 结构无所不能,很强大
php 缺点
- 弱类型语言,同样的逻辑,写法过于多样,导致后期维护成本高。比如参数可定义可不定义,返回值可定义可不定义
- 语言自身没有很好的支持并行运算
go 优点
- 强类型语言,大家都遵循一套编程规范,后期维护成本低
- 语言自带协程,支持异步和并行
- 跨平台:Go 编译为本地二进制文件。在 Windows 上,您将获得一个 .exe 文件,在 Linux 上,您将获得一个 ELF 二进制文件,依此类推。而且,除非您使用 cgo,否则 Go 程序可以在几乎没有外部依赖的情况下运行。无需安装任何 .dll 或 .so 文件,Go 程序即可直接使用。
- 多返回值
go 缺点
- 不支持泛型编程,相同的逻辑,因为数据类型不同,又得再写一套
- 没有受保护限定词,某些方法某些变量想在同一包中可见,但对外不可见
设计一个商城购物返积分流程
分3个流程,第一是浏览并加入购物车,第二是下单结算,第三是异步判断是否符合购物送积分等情况。
高并发之抢红包案例
- 抢红包的逻辑是,先查询红包的信息,看其是否拥有存量可以扣减。如果有存量,那么可以扣减它,否则就不扣减。
- 抢红包案例:可以当做电商抢购去处理。发送红包后,就确认了红包个数和总金额,红包个数类似于抢购商品库存。
高并发之电商抢购/秒杀案例
-
对于秒杀这样的高并发场景业务,最基本的原则就是将请求拦截在系统上游,降低下游压力。如果不在进入数据库前拦截很可能造成数据库(mysql、oracle等)读写锁冲突,甚至导致死锁,最终还有可能出现雪崩等场景。
-
抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:
1 高并发对数据库产生的压力
2 竞争状态下如何解决库存的正确减少(“超卖"问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。
-
先将商品库存入队列
1 2 3 4 5 6 7 8 9 10 11
<?php $store=1000; $goods_id = '1'; // 商品 id $redis_goods_store_name = 'goods_store_' . $goods_id; // 商品库存 redis key 值 $redis=new Redis(); $result=$redis->connect('127.0.0.1',6379); for($i=0;$i<store;$i++){ $redis->lpush($redis_goods_store_name,1); } echo $redis->llen($redis_goods_store_name); ?>
-
开始抢购/秒杀操作
注意超卖问题、同一用户抢购多次问题、 流量削峰问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
<?php $conn=mysql_connect("localhost","big","123456"); if(!$conn){ echo "connect failed"; exit; } mysql_select_db("big",$conn); mysql_query("set names utf8"); $user_id = $_POST['user_id']; // 抢购用户 id $goods_id = $_POST['goods_id']; // 商品 id $redis_goods_store_name = 'goods_store_' . $goods_id; // 商品库存 redis key 值 $price=10; $user_id=1; $goods_id=1; $sku_id=11; $number=1; // 生成唯一订单号 function build_order_no(){ return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); } // 记录日志 function insertLog($event,$type=0){ global $conn; $sql="insert into ih_log(event,type) values('$event','$type')"; mysql_query($sql,$conn); } // 模拟下单操作 $redis=new Redis(); $result=$redis->connect('127.0.0.1',6379); // 防止同一个用户抢多次的情况 $user_list = $redis->get('user_list'); $user_list = emplode(',', $user_list); if (in_array($user_id, $user_list) { insertLog('error: 您已抢购成功,请勿重复抢购!'); return; } // 下单前判断 redis 队列库存量 $count=$redis->lpop($redis_goods_store_name); if(!$count){ insertLog('error:no store redis'); return; } // 抢购结果队列 $user_list[] = $user_id; $redis->set('user_list', $user_list); mysql_query("BEGIN"); //开始事务 // 生成订单 $order_sn=build_order_no(); $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; $order_rs=mysql_query($sql,$conn); // 库存减少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'"; $store_rs=mysql_query($sql,$conn); if(mysql_affected_rows()){ insertLog('库存减少成功'); mysql_query("COMMIT"); // 事务提交即解锁 }else{ insertLog('库存减少失败'); mysql_query("ROLLBACK"); $redis->lpush($redis_goods_store_name,1); // 库存队列回滚 }
高并发之流量削峰
-
怎样来实现流量削峰方案
削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则。
消息队列(生产者、消费者)
常用消息队列系统:目前在生产环境,使用较多的消息队列有 ActiveMQ、RabbitMQ、 ZeroMQ、Kafka、MetaMQ、RocketMQ 等。
-
为什么使用消息队列
异步, 解耦, 削峰.
- 异步. A系统需要发送个请求给B系统处理,由于B系统需要查询更新数据库花费时间较长,以至于A系统要等待B系统处理完毕后再发送下个请求,造成A系统资源浪费.使用消息队列后,A系统生产完消息后直接丢进消息消息队列,就完成一次请求,继续处理下个请求.
- 解耦. A系统发送个数据到BCD三个系统,接口调用发送,那如果E系统也要这个数据呢?那如果C系统现在不需要了呢?现在A系统又要发送第二种数据了呢?A系统负责人濒临崩溃中。。。再来点更加崩溃的事儿,A系统要时时刻刻考虑BCDE四个系统如果挂了咋办?我要不要重发?我要不要把消息存起来?使用消息队列就能解决这个问题,A系统只负责生产数据,不需要考虑消息被哪个系统来消费.
- 削峰(如秒杀场景). A系统调用B系统处理数据,每天0点到11点,A系统风平浪静,每秒并发请求数量就100个。结果每次一到11点~1点,每秒并发请求数量突然会暴增到1万条。但是B系统最大的处理能力就只能是每秒钟处理1000个请求啊。。。尴尬了,系统会崩掉。。。引入消息队列,把请求数据先存入消息中间件系统中,消费系统慢慢拉取消费.
-
消息队列有什么优点和缺点啊?
- 优点就是异步,解耦 ,削峰(处理高并发)
- 缺点:系统可用性降低、系统复杂性提高
-
如何保证消费的可靠性传输?
- 其实这个可靠性传输,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据
- (1)生产者丢数据:开启事务
- (2)消息队列丢数据:一般是开启持久化磁盘的配置
- (3)消费者丢数据:消费者丢数据一般是因为采用了自动确认消息模式。至于解决方案,采用手动确认消息即可
mysql之乐观锁、悲观锁、共享锁、排他锁
相关名词
|–表级锁(锁定整个表)
|–页级锁(锁定一页)
|–行级锁(锁定一行)
|–共享锁(S锁,MyISAM 叫做读锁)
|–排他锁(X锁,MyISAM 叫做写锁)
|–间隙锁(NEXT-KEY锁)
|–悲观锁(抽象性,不真实存在这个锁)
|–乐观锁(抽象性,不真实存在这个锁)
-
首先说明:数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。
-
共享锁(S):
select * from table_name where ... lock in share mode;
共享锁:对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后。
-
排他锁(X):
select * from table_name where ... for update;
对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。
-
首先说明,乐观锁和悲观锁都是针对读(select)来说的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
案例: 某商品,用户购买后库存数应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为“n”,之后进行了一些操作,最后将均执行update table set 库存数=n-1,那么,很显然这是错误的。 解决: 使用悲观锁(其实说白了也就是排他锁) |-- 程序A在查询库存数时使用排他锁(select * from table where id=10 for update) |-- 然后进行后续的操作,包括更新库存数,最后提交事务。 |-- 程序B在查询库存数时,如果A还未释放排他锁,它将等待…… |-- 程序C同B…… 使用乐观锁(靠表设计和代码来实现) |-- 一般是在该商品表添加version版本字段或者timestamp时间戳字段 |-- 程序A查询后,执行更新变成了: update table set num=num-1 where id=10 and version=23 这样,保证了修改的数据是和它查询出来的数据是一致的(其他执行程序肯定未进行修改)。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。 总结:对于以上,可以看得出来乐观锁和悲观锁的区别: 悲观锁实际使用了排他锁来实现(select **** for update)。文章开头说到,innodb加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。 因此,悲观锁在未通过索引条件检索数据时,会锁定整张表。导致其他程序不允许“加锁的查询操作”,影响吞吐。故如果在查询居多的情况下,推荐使用乐观锁。 “加锁的查询操作”:加过排他锁的数据行在其他事务中是不能修改的,也不能通过for update或lock in share mode的加锁方式查询,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。 乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。 也就是一句话:读用乐观锁,写用悲观锁。
简单实现一个聊天app, 过程是怎样的
通过 workerman 框架或 golang 可以轻松实现聊天 app,其它有心情再补充。