php vs go

php 优点

  • array 结构无所不能,很强大

php 缺点

  • 弱类型语言,同样的逻辑,写法过于多样,导致后期维护成本高。比如参数可定义可不定义,返回值可定义可不定义
  • 语言自身没有很好的支持并行运算

go 优点

  • 强类型语言,大家都遵循一套编程规范,后期维护成本低
  • 语言自带协程,支持异步和并行
  • 跨平台:Go 编译为本地二进制文件。在 Windows 上,您将获得一个 .exe 文件,在 Linux 上,您将获得一个 ELF 二进制文件,依此类推。而且,除非您使用 cgo,否则 Go 程序可以在几乎没有外部依赖的情况下运行。无需安装任何 .dll 或 .so 文件,Go 程序即可直接使用。
  • 多返回值

go 缺点

从这些角度看 GO 是一门糟糕的语言

  • 不支持泛型编程,相同的逻辑,因为数据类型不同,又得再写一套
  • 没有受保护限定词,某些方法某些变量想在同一包中可见,但对外不可见

设计一个商城购物返积分流程

分3个流程,第一是浏览并加入购物车,第二是下单结算,第三是异步判断是否符合购物送积分等情况。

高并发之抢红包案例

  • 抢红包的逻辑是,先查询红包的信息,看其是否拥有存量可以扣减。如果有存量,那么可以扣减它,否则就不扣减。
  • 抢红包案例:可以当做电商抢购去处理。发送红包后,就确认了红包个数和总金额,红包个数类似于抢购商品库存。

高并发之电商抢购/秒杀案例

  • 对于秒杀这样的高并发场景业务,最基本的原则就是将请求拦截在系统上游,降低下游压力。如果不在进入数据库前拦截很可能造成数据库(mysql、oracle等)读写锁冲突,甚至导致死锁,最终还有可能出现雪崩等场景。

  • 抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:

    1 高并发对数据库产生的压力

    2 竞争状态下如何解决库存的正确减少(“超卖"问题)

    对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。

  1. 先将商品库存入队列

     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);
    ?>
    
  2. 开始抢购/秒杀操作

    注意超卖问题、同一用户抢购多次问题、 流量削峰问题

     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);  // 库存队列回滚
    }
    

高并发之流量削峰

参考文章

  1. 怎样来实现流量削峰方案

    削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则。

消息队列(生产者、消费者)

常用消息队列系统:目前在生产环境,使用较多的消息队列有 ActiveMQ、RabbitMQ、 ZeroMQ、Kafka、MetaMQ、RocketMQ 等。

  1. 为什么使用消息队列

    异步, 解耦, 削峰.

    • 异步. 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个请求啊。。。尴尬了,系统会崩掉。。。引入消息队列,把请求数据先存入消息中间件系统中,消费系统慢慢拉取消费.
  2. 消息队列有什么优点和缺点啊?

    • 优点就是异步,解耦 ,削峰(处理高并发)
    • 缺点:系统可用性降低、系统复杂性提高
  3. 如何保证消费的可靠性传输?

    • 其实这个可靠性传输,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据
    • (1)生产者丢数据:开启事务
    • (2)消息队列丢数据:一般是开启持久化磁盘的配置
    • (3)消费者丢数据:消费者丢数据一般是因为采用了自动确认消息模式。至于解决方案,采用手动确认消息即可

mysql之乐观锁、悲观锁、共享锁、排他锁

相关名词

|–表级锁(锁定整个表)

|–页级锁(锁定一页)

|–行级锁(锁定一行)

|–共享锁(S锁,MyISAM 叫做读锁)

|–排他锁(X锁,MyISAM 叫做写锁)

|–间隙锁(NEXT-KEY锁)

|–悲观锁(抽象性,不真实存在这个锁)

|–乐观锁(抽象性,不真实存在这个锁)

  1. 首先说明:数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。

  2. 共享锁(S): select * from table_name where ... lock in share mode;

    共享锁:对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后。

  3. 排他锁(X): select * from table_name where ... for update;

    对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。

  4. 首先说明,乐观锁和悲观锁都是针对读(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,其它有心情再补充。

mq 出现数据堆积,如何处理

参考文章