通知设置 新通知
你想要的老人都会用的电视直播APP,我替你找到了!
工具软件 • zkbhj 发表了文章 • 0 个评论 • 2498 次浏览 • 2024-07-30 15:24
说干就干,经过几天的折腾,终于让家里老人一键就能方便的看电视直播了。没错,我在Github上找到了一个完全无广告、清晰度超高、频道非常丰富的宝藏iptv应用——西柚电视直播,亲测可用,而且质量超高!!!看凤凰卫视、看奥运会体育频道等相当方便了~~~
1、仓库地址:
Github官方仓库:https://github.com/zkbhj/KBTV
而且这个应用竟然还有一个自己的官网,大家可以在官网下载安装包以及查看使用教程、频道信息等,地址是:
https://xy.zkbhj.com?fr=ask
2、推荐这个APP的理由:
频道丰富,有超过1500+的直播频道频道质量高,大家常看的央视、卫视、港澳台和地方等频道基本上都是1080P的完全没有任何广告,也没有一个购物台不需要关注什么公众号,不会打扰用户一键安装,操作简单,老人都会用,我就是给我父母搞了一个,在家看电视特方便
但如果你是想白嫖别人的劳动成果,那就不要看了,要么你自己去折腾自己弄,要么就支持一下别人付出的劳动,毕竟要投入各种成本和精力,还没有任何广告植入,有大家的支持才能继续坚持维护下去。但性价比非常超值,完全值得冲它。小白们的福音,完美解决家里看不了电视的烦恼,尤其是家里有老人的,搞一个这个直接搞定,不再让家里的电视成为摆设。 查看全部

最近家里老人总跟我说现在电视不好用,难操作,想看个电视台,怎么折腾都找不到,作为儿女,老人提需求必须得满足呀,况且咱还是个技术出身的程序员,这点儿事儿按道理应该难不倒俺!
说干就干,经过几天的折腾,终于让家里老人一键就能方便的看电视直播了。没错,我在Github上找到了一个完全无广告、清晰度超高、频道非常丰富的宝藏iptv应用——西柚电视直播,亲测可用,而且质量超高!!!看凤凰卫视、看奥运会体育频道等相当方便了~~~
1、仓库地址:
Github官方仓库:https://github.com/zkbhj/KBTV

而且这个应用竟然还有一个自己的官网,大家可以在官网下载安装包以及查看使用教程、频道信息等,地址是:
https://xy.zkbhj.com?fr=ask
2、推荐这个APP的理由:
- 频道丰富,有超过1500+的直播频道
- 频道质量高,大家常看的央视、卫视、港澳台和地方等频道基本上都是1080P的
- 完全没有任何广告,也没有一个购物台
- 不需要关注什么公众号,不会打扰用户
- 一键安装,操作简单,老人都会用,我就是给我父母搞了一个,在家看电视特方便

但如果你是想白嫖别人的劳动成果,那就不要看了,要么你自己去折腾自己弄,要么就支持一下别人付出的劳动,毕竟要投入各种成本和精力,还没有任何广告植入,有大家的支持才能继续坚持维护下去。但性价比非常超值,完全值得冲它。小白们的福音,完美解决家里看不了电视的烦恼,尤其是家里有老人的,搞一个这个直接搞定,不再让家里的电视成为摆设。
为了隐藏真正的Mysql数据库,我想通过中间跳板机nginx服务做转发进行数据库连接,要怎么做?
回复数据库 • zkbhj 发起了问题 • 1 人关注 • 0 个回复 • 3353 次浏览 • 2023-07-10 11:23
Nginx+PHP-FPM架构,结合PHP8.1搭建应用服务器时的一些总结和记录
PHP • zkbhj 发表了文章 • 0 个评论 • 4113 次浏览 • 2022-12-08 14:07
一、编译安装PHP8.1:
因为编译过程中基本上遇到问题的概率并不大,我的编译过程还算顺利,因为服务器上保留了以前其他人运行的项目。PHP的版本是7.2.3,所以就编译安装了PHP8.1到一个指定的地方,然后独立安装的PHP-FPM。尽量不影响以前的项目运行。
我的编译配置信息(注意要一行,不能存在换行符):./configure --prefix=/usr/local/php8.1 --with-config-file-path=/usr/local/php8.1/etc --with-curl --with-freetype --enable-gd --with-jpeg --with-gettext --with-kerberos --with-libdir=lib64 --with-libxml --with-mysqli --with-openssl --with-pdo-mysql --with-pdo-sqlite --with-pear --enable-sockets --with-mhash --with-ldap-sasl --with-xsl --with-zlib -with-bz2 --with-iconv --enable-fpm --enable-pdo --enable-bcmath --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-soap --enable-sockets --enable-sysvsem --enable-xml --enable-sysvsem --enable-cli --enable-opcache --enable-intl --enable-calendar --enable-static --enable-mysqlnd最终我把编译好的php-fpm文件复制到了我的PHP8.1目录下,并完成了配置启动。这会儿还一切看似顺利。
编译过程中遇到了一个问题是:
configure: error: iconv does not support errno
优先参考网上的解决方案安装缺失的iconv,如果不行,在运行configure之前先运行:
export LDFLAGS="$LDFLAGS -liconv"参考官网bug讨论 https://bugs.php.net/bug.php?id=80585,在此对歪果友人诚挚的感谢。
二、部署应用:
搭建好之后,顺利启动PHP-FPM服务器,监听端口9000,然后再Nginx配置文件中,将请求信息直接给到9000端口来处理,这里就有一些“东西”要注意了,否则就遇到了下面的问题:
问题1:部署应用之后,请求失败,页面返回“File not Found”,然后查看Nginx的报错日志,显示信息是:2022/12/08 13:15:51 [error] 31547#0: *29582 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 119.116.237.75, server: web.*.com, request: "GET /site/user-agreement HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000", host: "web*.*com"网上说中问题, 比如php-fpm的 运行group和user不对啦,应用目录权限不足啦,但我发现根本原因是在Nginx转发时,少了一个重要的配置信息。
由于我的yii2项目启用了 URLManage功能,为了让链接看起来更漂亮一些,所以需要在Nginx配置文件中加入下面的这个配置:if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
所以,完整的转发配置应该是:location / {
fastcgi_pass 127.0.0.1:9000;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}所以这块,大家应该注意一下,更改完,重新启动Nginx服务之后,项目正常启动了!
问题二、页面图标显示异常:
由于使用了第三方图标工具,打开F12工具之后,发现是静态资源服务器访问字体文件时显示跨域:Access to Font at “xxx” from origin “xxx” has been by CORS plicy:
No ‘Access-Control-Allow-Origin’header is present on the requested resource.
Origin “xxx” is therefore not allowed access.这时候,只需要在静态资源服务器的Nginx配置文件中增加下面的配置项即可:location ~* .(eot|ttf|woff|swoff2|svg|otf)$ {
add_header Access-Control-Allow-Origin [url]http://www.zkbhj.com;[/url] //只允许单域名或者 ‘*' 。不推荐 ‘*' ,会导致安全问题
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
} 查看全部
一、编译安装PHP8.1:
因为编译过程中基本上遇到问题的概率并不大,我的编译过程还算顺利,因为服务器上保留了以前其他人运行的项目。PHP的版本是7.2.3,所以就编译安装了PHP8.1到一个指定的地方,然后独立安装的PHP-FPM。尽量不影响以前的项目运行。
我的编译配置信息(注意要一行,不能存在换行符):
./configure --prefix=/usr/local/php8.1 --with-config-file-path=/usr/local/php8.1/etc --with-curl --with-freetype --enable-gd --with-jpeg --with-gettext --with-kerberos --with-libdir=lib64 --with-libxml --with-mysqli --with-openssl --with-pdo-mysql --with-pdo-sqlite --with-pear --enable-sockets --with-mhash --with-ldap-sasl --with-xsl --with-zlib -with-bz2 --with-iconv --enable-fpm --enable-pdo --enable-bcmath --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-soap --enable-sockets --enable-sysvsem --enable-xml --enable-sysvsem --enable-cli --enable-opcache --enable-intl --enable-calendar --enable-static --enable-mysqlnd最终我把编译好的php-fpm文件复制到了我的PHP8.1目录下,并完成了配置启动。这会儿还一切看似顺利。
编译过程中遇到了一个问题是:
configure: error: iconv does not support errno
优先参考网上的解决方案安装缺失的iconv,如果不行,在运行configure之前先运行:
export LDFLAGS="$LDFLAGS -liconv"参考官网bug讨论 https://bugs.php.net/bug.php?id=80585,在此对歪果友人诚挚的感谢。
二、部署应用:
搭建好之后,顺利启动PHP-FPM服务器,监听端口9000,然后再Nginx配置文件中,将请求信息直接给到9000端口来处理,这里就有一些“东西”要注意了,否则就遇到了下面的问题:
问题1:部署应用之后,请求失败,页面返回“File not Found”,然后查看Nginx的报错日志,显示信息是:
2022/12/08 13:15:51 [error] 31547#0: *29582 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 119.116.237.75, server: web.*.com, request: "GET /site/user-agreement HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000", host: "web*.*com"网上说中问题, 比如php-fpm的 运行group和user不对啦,应用目录权限不足啦,但我发现根本原因是在Nginx转发时,少了一个重要的配置信息。由于我的yii2项目启用了 URLManage功能,为了让链接看起来更漂亮一些,所以需要在Nginx配置文件中加入下面的这个配置:
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
所以,完整的转发配置应该是:location / {
fastcgi_pass 127.0.0.1:9000;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}所以这块,大家应该注意一下,更改完,重新启动Nginx服务之后,项目正常启动了!问题二、页面图标显示异常:
由于使用了第三方图标工具,打开F12工具之后,发现是静态资源服务器访问字体文件时显示跨域:
Access to Font at “xxx” from origin “xxx” has been by CORS plicy:这时候,只需要在静态资源服务器的Nginx配置文件中增加下面的配置项即可:
No ‘Access-Control-Allow-Origin’header is present on the requested resource.
Origin “xxx” is therefore not allowed access.
location ~* .(eot|ttf|woff|swoff2|svg|otf)$ {
add_header Access-Control-Allow-Origin [url]http://www.zkbhj.com;[/url] //只允许单域名或者 ‘*' 。不推荐 ‘*' ,会导致安全问题
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
} 怎么设计一个抢红包系统?
架构思想 • zkbhj 发表了文章 • 0 个评论 • 4018 次浏览 • 2022-07-03 11:46
活动规模
公司在年底,为员工准备了 25 万元的抢红包活动,在年三十晚 8 点准时开始。
技术方案
首先要理解到抢红包特殊的业务场景,红包抢到了并不等于把钱拿到手了。抢红包其实主要有 3 个核心流程:红包金额拆分->抢红包->打款。
红包金额拆分是指将指定金额拆分为指定数目红包的过程,用来确定每个红包的金额数;抢红包是用户抢红包的这个操作,典型的高并发场景,需要系统扛流量且避免红包超发的情况;打款就是将抢到的红包通过微信/银行打款到用户钱包的过程(真正把红包的钱拿到手了),因为要对接三方支付系统是整个系统比较耗时的操作,一般通过异步任务来实现;
红包金额拆分
可选的方案
拆分方式
1、实时拆分
实时拆分,指的是在抢红包时实时计算每个红包的金额,以实现红包的拆分过程,对系统性能和拆分算法要求较高,例如拆分过程要一直保证后续待拆分红包的金额不能为空,不容易做到拆分的红包金额服从正态分布规律。
2、预先生成
预先生成,指的是在红包开抢之前已经完成了红包的金额拆分,抢红包时只是依次取出拆分好的红包金额,对拆分算法要求较低,可以拆分出随机性很好的红包金额,通常需要结合队列使用。
拆分算法
红包拆分算法拆分的金额要看起来随机,最好能够服从正态分布,可以参考 微信 和 @lcode 提供的红包拆分算法。
微信拆分算法的优点是算法较简单,拆分效率高,同时由于该算法天然的特性,可以保证后续红包金额一定不为空,特别适合实时拆分场景,但缺点是会导致大额红包较大概率地在拆分的最后出现。 @lcode 拆分算法的优点是拆分金额基本符合正态分布,适合随机性要求较高的拆分场景。
我们的方案
我们这次的场景对红包金额的随机性要求不高,但是对系统可靠性要求较高,所以我们选用了预先生成方式,使用 二倍均值法 的算法拆分红包金额。
拆分算法可以描述为:假设剩余拆分金额为 M,剩余待拆分红包个数为 N,红包最小金额为 1 元,那么定义当前红包的金额为:
m=rand(1,floor(M/N∗2))
其中,floor 表示向下取整,rand(min, max) 表示从 [min, max] 区间随机一个值。M/N*2 表示剩余待拆分金额平均金额的 2 倍,因为 N >= 2,所以 M/N*2 <= M,表示一定能保证后续红包能拆分到金额。
代码实现为:for ($i = 0; $i < $N - 1; $i++) {
$max = (int)floor($M / ($N - $i)) * 2;
$m[$i] = $max ? mt_rand(1, $max) : 0;
$M -= $m[$i];
}
$m = $M;值得一提的是,为了保证红包金额差异尽量小,先将总金额平均拆分成 N+1 份,将第 N+1 份红包按照上述的红包拆分算法拆分成 N 份,这 N 份红包加上之前的平均金额才作为最终的红包金额。
抢红包
可选的方案
限流
1、前端限流
前端限制用户在 n 秒之内只能提交一次请求,虽然这种方式只能挡住小白(99% 的用户),所以也必须得做。
2、后端限流
常用的后端限流方法有 漏桶算法 和 令牌桶算法。漏桶算法 主要目的是控制请求数据注入的速率,如果此时漏桶溢出,后续的请求数据会被丢弃。而 令牌桶算法 是以一个恒定的速度往桶里放入令牌,而如果请求数据需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌时,这些请求才被丢弃,令牌桶算法的一个好处是可以方便地改变应用接受请求的速率。
防并发超发红包
1、库存加锁
可以通过加锁的方式解决,但是加锁会增加系统开销,大流量下更容易拖垮系统,也可以尝试一下基于版本号的乐观锁。
2、队列串行化请求
之所会出现超发问题,是因为并发时会出现多个进程同时操作同一资源的现象,如果使用高速队列将并行请求串行化,那么问题就不存在了。高速队列可以使用 Redis 来实现,但是还要必须保证整个流程调用链要短、要快,否则队列会积压严重,甚至会拖垮整个系统。
我们的方案
我们选用队列串行化的方案,抢红包整个过程只会操作 Redis,且都是简单高效的 Pop 和 Push 命令操作。
抢红包流程:先从红包队列中 Pop 占有红包,然后 Push 红包到任务队列(待异步打款处理),并同步告知用户抢到红包的结果,抢红包流程就结束了。
当然,在实际应用中,占有红包过程中还会有一些前置规则校验,比如用户是否已经领取过,领取次数是否已经达到上限等?红包占有流程图如下:
其中,red::list为 List 结构,存放预先拆分好金额的红包;red::task 也为 List 结构,异步打款处理任务队列;red::draw为 Hash 结构,存放红包领取记录,field为用户的 openid,value为序列化的红包信息;red::draw_count:u:openid为 k-v 结构,用户领取红包计数器。
1、怎么保证不超发
从红包占有流程图可看出,这个过程是会操作很多 Key,那怎么保证原子性?我们选用了 Lua 方案,一方面是因为首先要保证性能,没有多次请求的网络开销,另一方面 Lua 脚本执行时本身就是原子性的,满足需求。
红包占有的 Lua 脚本实现如下:-- 领取人的openid为xxxxxxxxxxx
local openid = 'xxxxxxxxxxx'
local isDraw = redis.call('HEXISTS', 'red::draw', openid)
-- 已经领取
if isDraw ~= 0 then
return true
end
-- 领取太多次了
local times = redis.call('INCR', 'red::draw_count:u:'..openid)
if times and tonumber(times) > 9 then
return 0
end
local number = redis.call('RPOP', 'red::list')
-- 没有红包
if not number then
return {}
end
-- 领取人昵称为Fhb,头像为https://xxxxxxx
local red = {money=number,name='Fhb',pic='https://xxxxxxx'}
-- 领取记录
redis.call('HSET', 'red::draw', openid, cjson.encode(red))
-- 处理队列
red['openid'] = openid
redis.call('RPUSH', 'red::task', cjson.encode(red))
return true
需要注意 Lua 脚本执行过程并不是事务的,脚本中的操作命令在执行时是有先后顺序的,当某个操作执行失败时不会回滚已经执行成功的操作。
2、怎么提高系统响应
由于抢红包和打款流程分开,抢红包过程只需操作 Redis,整个操作短且快,故不存在性能问题。
打款
我们的方案
采用 Worker 任务去消费任务队列,调用红包支付 API,以及数据持久化操作(后续对账)。尽管红包发放调用链又长又慢,但是这些 Worker 是 无状态 的,所以可以通过增加 Worker 数量,提高系统的消费处理能力。
1、怎么保证数据一致性
若红包打款失败了,前面已经告知用户抢到红包,但是却木有发,那用户肯定会很愤怒。但是根据 CAP 原理,我们通常只需做到数据最终一致性。
我们在打款流程里面做了重试机制,生成一个全局唯一的外部订单号,当某红包打款失败,就会放回任务队列重试,当然重试时要处理好幂等。
2、怎么保证Worker不异常结束
Worker 的实现如下:$maxTask = 1000;
$sleepTime = 1000;
while (true) {
while ($red = RedLogic::getTask()) {
RedLogic::doTask($red);
//处理多少个任务主动退出
$maxTask--;
if ($maxTask < 0) {
return EXIT_CODE_NORMAL;
}
}
//等待任务
usleep($sleepTime);
}
这里使用 LPOP 命令获取任务,所以使用了 while 结构,并且无任务时需要等待,可以用阻塞命令 BLPOP 来改进。
由于 Worker 需要常驻内存运行,难免会出现异常结束的情况(也有主动 Exit), 所以需要保持 Worker 一直处于运行状态。我们使用进程管理工具 Supervisor 来监控和管理 Worker 任务,当任务队列出现堆积时,增加 Worker 数量即可。Supervisor 的监控后台如下:
保障方案
资源CDN缓存
由于本次活动力度较大,静态页面占流量的很大一部分,所以静态页面在发布时都会放置一份在 CDN 上,这样回源的流量就很小了。
降级措施
尽管做了很多准备,还是无法确保万无一失,我们在每个关键节点都增加了开关,一但出现异常,可以通过配置中心人工介入做降级处理。
原文来自微信公众号:后端搬运工。
原文地址:https://mp.weixin.qq.com/s/VG_Wcxte8avnXzn4bPXiGA
欢迎大家多关注! 查看全部
活动规模
公司在年底,为员工准备了 25 万元的抢红包活动,在年三十晚 8 点准时开始。
技术方案
首先要理解到抢红包特殊的业务场景,红包抢到了并不等于把钱拿到手了。抢红包其实主要有 3 个核心流程:红包金额拆分->抢红包->打款。
- 红包金额拆分是指将指定金额拆分为指定数目红包的过程,用来确定每个红包的金额数;
- 抢红包是用户抢红包的这个操作,典型的高并发场景,需要系统扛流量且避免红包超发的情况;
- 打款就是将抢到的红包通过微信/银行打款到用户钱包的过程(真正把红包的钱拿到手了),因为要对接三方支付系统是整个系统比较耗时的操作,一般通过异步任务来实现;
红包金额拆分
可选的方案
拆分方式
1、实时拆分
实时拆分,指的是在抢红包时实时计算每个红包的金额,以实现红包的拆分过程,对系统性能和拆分算法要求较高,例如拆分过程要一直保证后续待拆分红包的金额不能为空,不容易做到拆分的红包金额服从正态分布规律。
2、预先生成
预先生成,指的是在红包开抢之前已经完成了红包的金额拆分,抢红包时只是依次取出拆分好的红包金额,对拆分算法要求较低,可以拆分出随机性很好的红包金额,通常需要结合队列使用。
拆分算法
红包拆分算法拆分的金额要看起来随机,最好能够服从正态分布,可以参考 微信 和 @lcode 提供的红包拆分算法。
微信拆分算法的优点是算法较简单,拆分效率高,同时由于该算法天然的特性,可以保证后续红包金额一定不为空,特别适合实时拆分场景,但缺点是会导致大额红包较大概率地在拆分的最后出现。 @lcode 拆分算法的优点是拆分金额基本符合正态分布,适合随机性要求较高的拆分场景。
我们的方案
我们这次的场景对红包金额的随机性要求不高,但是对系统可靠性要求较高,所以我们选用了预先生成方式,使用 二倍均值法 的算法拆分红包金额。
拆分算法可以描述为:假设剩余拆分金额为 M,剩余待拆分红包个数为 N,红包最小金额为 1 元,那么定义当前红包的金额为:
m=rand(1,floor(M/N∗2))
其中,floor 表示向下取整,rand(min, max) 表示从 [min, max] 区间随机一个值。M/N*2 表示剩余待拆分金额平均金额的 2 倍,因为 N >= 2,所以 M/N*2 <= M,表示一定能保证后续红包能拆分到金额。
代码实现为:
for ($i = 0; $i < $N - 1; $i++) {
$max = (int)floor($M / ($N - $i)) * 2;
$m[$i] = $max ? mt_rand(1, $max) : 0;
$M -= $m[$i];
}
$m = $M;值得一提的是,为了保证红包金额差异尽量小,先将总金额平均拆分成 N+1 份,将第 N+1 份红包按照上述的红包拆分算法拆分成 N 份,这 N 份红包加上之前的平均金额才作为最终的红包金额。抢红包
可选的方案
限流
1、前端限流
前端限制用户在 n 秒之内只能提交一次请求,虽然这种方式只能挡住小白(99% 的用户),所以也必须得做。
2、后端限流
常用的后端限流方法有 漏桶算法 和 令牌桶算法。漏桶算法 主要目的是控制请求数据注入的速率,如果此时漏桶溢出,后续的请求数据会被丢弃。而 令牌桶算法 是以一个恒定的速度往桶里放入令牌,而如果请求数据需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌时,这些请求才被丢弃,令牌桶算法的一个好处是可以方便地改变应用接受请求的速率。
防并发超发红包
1、库存加锁
可以通过加锁的方式解决,但是加锁会增加系统开销,大流量下更容易拖垮系统,也可以尝试一下基于版本号的乐观锁。
2、队列串行化请求
之所会出现超发问题,是因为并发时会出现多个进程同时操作同一资源的现象,如果使用高速队列将并行请求串行化,那么问题就不存在了。高速队列可以使用 Redis 来实现,但是还要必须保证整个流程调用链要短、要快,否则队列会积压严重,甚至会拖垮整个系统。
我们的方案
我们选用队列串行化的方案,抢红包整个过程只会操作 Redis,且都是简单高效的 Pop 和 Push 命令操作。
抢红包流程:先从红包队列中 Pop 占有红包,然后 Push 红包到任务队列(待异步打款处理),并同步告知用户抢到红包的结果,抢红包流程就结束了。
当然,在实际应用中,占有红包过程中还会有一些前置规则校验,比如用户是否已经领取过,领取次数是否已经达到上限等?红包占有流程图如下:
其中,red::list为 List 结构,存放预先拆分好金额的红包;red::task 也为 List 结构,异步打款处理任务队列;red::draw为 Hash 结构,存放红包领取记录,field为用户的 openid,value为序列化的红包信息;red::draw_count:u:openid为 k-v 结构,用户领取红包计数器。
1、怎么保证不超发
从红包占有流程图可看出,这个过程是会操作很多 Key,那怎么保证原子性?我们选用了 Lua 方案,一方面是因为首先要保证性能,没有多次请求的网络开销,另一方面 Lua 脚本执行时本身就是原子性的,满足需求。
红包占有的 Lua 脚本实现如下:
-- 领取人的openid为xxxxxxxxxxx
local openid = 'xxxxxxxxxxx'
local isDraw = redis.call('HEXISTS', 'red::draw', openid)
-- 已经领取
if isDraw ~= 0 then
return true
end
-- 领取太多次了
local times = redis.call('INCR', 'red::draw_count:u:'..openid)
if times and tonumber(times) > 9 then
return 0
end
local number = redis.call('RPOP', 'red::list')
-- 没有红包
if not number then
return {}
end
-- 领取人昵称为Fhb,头像为https://xxxxxxx
local red = {money=number,name='Fhb',pic='https://xxxxxxx'}
-- 领取记录
redis.call('HSET', 'red::draw', openid, cjson.encode(red))
-- 处理队列
red['openid'] = openid
redis.call('RPUSH', 'red::task', cjson.encode(red))
return true
需要注意 Lua 脚本执行过程并不是事务的,脚本中的操作命令在执行时是有先后顺序的,当某个操作执行失败时不会回滚已经执行成功的操作。
2、怎么提高系统响应
由于抢红包和打款流程分开,抢红包过程只需操作 Redis,整个操作短且快,故不存在性能问题。
打款
我们的方案
采用 Worker 任务去消费任务队列,调用红包支付 API,以及数据持久化操作(后续对账)。尽管红包发放调用链又长又慢,但是这些 Worker 是 无状态 的,所以可以通过增加 Worker 数量,提高系统的消费处理能力。
1、怎么保证数据一致性
若红包打款失败了,前面已经告知用户抢到红包,但是却木有发,那用户肯定会很愤怒。但是根据 CAP 原理,我们通常只需做到数据最终一致性。
我们在打款流程里面做了重试机制,生成一个全局唯一的外部订单号,当某红包打款失败,就会放回任务队列重试,当然重试时要处理好幂等。
2、怎么保证Worker不异常结束
Worker 的实现如下:
$maxTask = 1000;
$sleepTime = 1000;
while (true) {
while ($red = RedLogic::getTask()) {
RedLogic::doTask($red);
//处理多少个任务主动退出
$maxTask--;
if ($maxTask < 0) {
return EXIT_CODE_NORMAL;
}
}
//等待任务
usleep($sleepTime);
}
这里使用 LPOP 命令获取任务,所以使用了 while 结构,并且无任务时需要等待,可以用阻塞命令 BLPOP 来改进。
由于 Worker 需要常驻内存运行,难免会出现异常结束的情况(也有主动 Exit), 所以需要保持 Worker 一直处于运行状态。我们使用进程管理工具 Supervisor 来监控和管理 Worker 任务,当任务队列出现堆积时,增加 Worker 数量即可。Supervisor 的监控后台如下:
保障方案
资源CDN缓存
由于本次活动力度较大,静态页面占流量的很大一部分,所以静态页面在发布时都会放置一份在 CDN 上,这样回源的流量就很小了。
降级措施
尽管做了很多准备,还是无法确保万无一失,我们在每个关键节点都增加了开关,一但出现异常,可以通过配置中心人工介入做降级处理。
原文来自微信公众号:后端搬运工。
原文地址:https://mp.weixin.qq.com/s/VG_Wcxte8avnXzn4bPXiGA
欢迎大家多关注!
跟党相关的英文单词和表述
单词本 • zkbhj 发表了文章 • 0 个评论 • 3816 次浏览 • 2021-07-01 11:16
带大家看一下有关我党的英文表达吧!the Communist Party of China (CPC)
中国共产党
members of the Communist Party of China
党员
Chinese working class
中国工人阶级
Party Constitution
中国共产党章程
the Communist Youth League of China
中国共产主义青年团
vanguard
先锋队
Chinese People's Liberation Army
中国人民解放军
Party discipline
党的纪律
applicant for Party membership
申请入党者
probationary Party member
预备党员
take an admission oath
入党宣誓
The oath reads:
It is my will to join the Communist Party of China, uphold the Party's program, observe the provisions of the Party Constitution, fulfill a Party member's duties, carry out the Party's decisions, strictly observe Party discipline, guard Party secrets, be loyal to the Party, work hard, fight for communism throughout my life, be ready at all times to sacrifice my all for the Party and the people, and never betray the Party.
誓词如下:
我志愿加入中国共产党,拥护党的纲领,遵守党的章程,履行党员义务,执行党的决定,严守党的纪律,保守党的秘密,对党忠诚,积极工作,为共产主义奋斗终身,随时准备为党和人民牺牲一切,永不叛党。
full Party member
正式党员
pay membership
交党费
Party branch
党支部
Party cell
党小组
Party cadres
党员领导干部
democratic meetings
民主生活会
a Party standing of ... years
党龄
Party Emblem and Flag
党徽党旗
The emblem of the Communist Party of China is a design of sickle and hammer.
中国共产党党徽为镰刀和锤头组成的图案。
The flag of the Communist Party of China is a red flag highlighted by a golden Party emblem on it.
中国共产党党旗为旗面缀有金黄色党徽图案的红旗。 查看全部
带大家看一下有关我党的英文表达吧!the Communist Party of China (CPC)
中国共产党
members of the Communist Party of China
党员
Chinese working class
中国工人阶级
Party Constitution
中国共产党章程
the Communist Youth League of China
中国共产主义青年团
vanguard
先锋队
Chinese People's Liberation Army
中国人民解放军
Party discipline
党的纪律
applicant for Party membership
申请入党者
probationary Party member
预备党员
take an admission oath
入党宣誓
The oath reads:
It is my will to join the Communist Party of China, uphold the Party's program, observe the provisions of the Party Constitution, fulfill a Party member's duties, carry out the Party's decisions, strictly observe Party discipline, guard Party secrets, be loyal to the Party, work hard, fight for communism throughout my life, be ready at all times to sacrifice my all for the Party and the people, and never betray the Party.
誓词如下:
我志愿加入中国共产党,拥护党的纲领,遵守党的章程,履行党员义务,执行党的决定,严守党的纪律,保守党的秘密,对党忠诚,积极工作,为共产主义奋斗终身,随时准备为党和人民牺牲一切,永不叛党。
full Party member
正式党员
pay membership
交党费
Party branch
党支部
Party cell
党小组
Party cadres
党员领导干部
democratic meetings
民主生活会
a Party standing of ... years
党龄
Party Emblem and Flag
党徽党旗
The emblem of the Communist Party of China is a design of sickle and hammer.
中国共产党党徽为镰刀和锤头组成的图案。
The flag of the Communist Party of China is a red flag highlighted by a golden Party emblem on it.
中国共产党党旗为旗面缀有金黄色党徽图案的红旗。
什么是RCEP?对我们普通人来讲有什么影响?
专业名词 • zkbhj 发表了文章 • 0 个评论 • 4715 次浏览 • 2020-11-17 16:44
RCEP,英文全称 Regional Comprehensive Economic Partnership ,中文全称 区域全面经济伙伴关系协定。是2012年由东盟发起,历时八年,成员包括中国、日本、韩国、澳大利亚、新西兰和东盟十国共15方而制定的协定。东盟十国分别是:印度尼西亚、马来西亚、菲律宾、泰国、新加坡、文莱、柬埔寨、老挝、缅甸、越南。
2020年11月15日,区域全面经济伙伴关系协定签署仪式以视频方式进行,15个RCEP成员国经贸部长将在仪式上正式签署该协定。标志着当前世界上人口最多、经贸规模最大、最具发展潜力的自由贸易区正式启航。
协议共含20个章节,分为四大板块,包括货物贸易协定、投资协定、21世纪新议题和争端解决机制。
原本应该有16国,包括印度在内,但是最后印度2019年因“有重要问题尚未得到解决”而没有加入协定。
二、意义
在疫情肆虐、世界经济严重衰退、国际贸易投资萎缩、保护主义单边主义加剧的特殊背景下,各方能够就RCEP达成共识,宣告了多边主义和自由贸易的胜利,将有力提振各方对经济增长的信心。将为区域和全球经济增长注入强劲动力。
世界正面临百年未有之大变局,RCEP的达成为亚太自贸区(FTAAP)进程提供了实现路径,进一步提升亚太地区今后在全球发展格局中的分量。
三、全球经济格局现状
在RCEP签署之前,全球从洲际合作角度,最大的三个自贸区为北美自贸区(USMCA),欧盟(EU)和中国-东盟自贸区(CAFTA)。RCEP的诞生意味着全球最大自贸区形成,全球贸易格局正式演化为北美、欧盟、亚洲三足鼎立。
四、对普通人来讲,有哪些影响
RCEP协议的签署,就意味着在15国之内,商品流动、技术流动、服务流动、资本流动,包括人员跨境流动都会更加流畅。将会有超九成商品或纳入零关税范围,会极大地降低各成员国内流通商品的销售价格。当然更顺畅的贸易往来,也会带动更多的就业和创业机会。
但这些影响实际发生也需要等到2年之后,因为协议签订之后2年内,各个国家需要完成批准程序,协议才正式生效。 查看全部
RCEP,英文全称 Regional Comprehensive Economic Partnership ,中文全称 区域全面经济伙伴关系协定。是2012年由东盟发起,历时八年,成员包括中国、日本、韩国、澳大利亚、新西兰和东盟十国共15方而制定的协定。东盟十国分别是:印度尼西亚、马来西亚、菲律宾、泰国、新加坡、文莱、柬埔寨、老挝、缅甸、越南。
2020年11月15日,区域全面经济伙伴关系协定签署仪式以视频方式进行,15个RCEP成员国经贸部长将在仪式上正式签署该协定。标志着当前世界上人口最多、经贸规模最大、最具发展潜力的自由贸易区正式启航。
协议共含20个章节,分为四大板块,包括货物贸易协定、投资协定、21世纪新议题和争端解决机制。
原本应该有16国,包括印度在内,但是最后印度2019年因“有重要问题尚未得到解决”而没有加入协定。
二、意义
在疫情肆虐、世界经济严重衰退、国际贸易投资萎缩、保护主义单边主义加剧的特殊背景下,各方能够就RCEP达成共识,宣告了多边主义和自由贸易的胜利,将有力提振各方对经济增长的信心。将为区域和全球经济增长注入强劲动力。
世界正面临百年未有之大变局,RCEP的达成为亚太自贸区(FTAAP)进程提供了实现路径,进一步提升亚太地区今后在全球发展格局中的分量。
三、全球经济格局现状
在RCEP签署之前,全球从洲际合作角度,最大的三个自贸区为北美自贸区(USMCA),欧盟(EU)和中国-东盟自贸区(CAFTA)。RCEP的诞生意味着全球最大自贸区形成,全球贸易格局正式演化为北美、欧盟、亚洲三足鼎立。
四、对普通人来讲,有哪些影响
RCEP协议的签署,就意味着在15国之内,商品流动、技术流动、服务流动、资本流动,包括人员跨境流动都会更加流畅。将会有超九成商品或纳入零关税范围,会极大地降低各成员国内流通商品的销售价格。当然更顺畅的贸易往来,也会带动更多的就业和创业机会。
但这些影响实际发生也需要等到2年之后,因为协议签订之后2年内,各个国家需要完成批准程序,协议才正式生效。
o'clock是什么的缩写?
常识 • zkbhj 发表了文章 • 0 个评论 • 2955 次浏览 • 2020-08-06 10:40
-It's 7 o'clock.
o'clock 表示点钟,这个小学英语课堂上就学过的词,想必大家再熟悉不过了吧。
但是,问题来了:o 和 clock 之间用了一个缩写符号 ',那么它究竟省略掉了哪些东西?
故事还要从没有 clock 的那个时代说起。
上古时代的人类,依靠观察日夜交替、季节更迭和星辰变幻的规律,作为游牧、耕作和祭祀等活动的时间依据。后来,智慧的人类发明了日晷(sundial)、沙漏(hourglass)和水钟(water clock,请注意不是水表)等工具,用以衡量一定长度的时间。
当然,我们中国还有「一袋烟的工夫」、「一炷香的工夫」这些体现传统浪漫主义情怀、随性而接地气的时间表达方式……
直到 14 世纪,现代意义上的时钟雏形才得以发明,但因并未迅速普及开来,其他的时间计量工具也同样在并行使用。所以,为了避免与其他计时工具混淆,人们需要特别说明,自己报出的时间读数,是来自时钟的:
It's 7 of the clock.
人嘛,都是有惰性的。16、17世纪,随着时钟的应用越来越广泛,人们开始将 of 的 f 和 the 一带而过,简略地读成了 o'clock.
好了,答案来了:o'clock = of the clock.
而到了 18 世纪,o'clock 的说法才真正变得流行起来。当时,of the 两个词略读成 o' (/ə/) 的方式为大众普遍采用,比如万圣节标志性的南瓜灯 Jack-o'-lantern,即是 Jack of the lantern 的缩写形式。
虽然钟表早已普及,甚至现在逐渐被电子设备所取代,但 o'clock 这种说法早已成为了英语中的一种习惯,并一直沿用至今。
作者:笔戈科技
链接:https://www.jianshu.com/p/95e592319750
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 查看全部
-What time is it?
-It's 7 o'clock.
o'clock 表示点钟,这个小学英语课堂上就学过的词,想必大家再熟悉不过了吧。
但是,问题来了:o 和 clock 之间用了一个缩写符号 ',那么它究竟省略掉了哪些东西?
故事还要从没有 clock 的那个时代说起。
上古时代的人类,依靠观察日夜交替、季节更迭和星辰变幻的规律,作为游牧、耕作和祭祀等活动的时间依据。后来,智慧的人类发明了日晷(sundial)、沙漏(hourglass)和水钟(water clock,请注意不是水表)等工具,用以衡量一定长度的时间。
当然,我们中国还有「一袋烟的工夫」、「一炷香的工夫」这些体现传统浪漫主义情怀、随性而接地气的时间表达方式……
直到 14 世纪,现代意义上的时钟雏形才得以发明,但因并未迅速普及开来,其他的时间计量工具也同样在并行使用。所以,为了避免与其他计时工具混淆,人们需要特别说明,自己报出的时间读数,是来自时钟的:
It's 7 of the clock.
人嘛,都是有惰性的。16、17世纪,随着时钟的应用越来越广泛,人们开始将 of 的 f 和 the 一带而过,简略地读成了 o'clock.
好了,答案来了:o'clock = of the clock.
而到了 18 世纪,o'clock 的说法才真正变得流行起来。当时,of the 两个词略读成 o' (/ə/) 的方式为大众普遍采用,比如万圣节标志性的南瓜灯 Jack-o'-lantern,即是 Jack of the lantern 的缩写形式。
虽然钟表早已普及,甚至现在逐渐被电子设备所取代,但 o'clock 这种说法早已成为了英语中的一种习惯,并一直沿用至今。
作者:笔戈科技
链接:https://www.jianshu.com/p/95e592319750
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
什么是变量的深拷贝和浅拷贝?
专业名词 • zkbhj 发表了文章 • 0 个评论 • 3770 次浏览 • 2020-07-24 10:41
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
Go语言中的深浅拷贝
1、深拷贝(Deep Copy):
拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。
2、浅拷贝(Shallow Copy):
拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。
引用类型的数据,默认全部都是浅复制,Slice,Map。
深拷贝示例:
package main
import (
"fmt"
)
// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}
func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)
fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)
}深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360浅拷贝示例2:
package main
import (
"fmt"
)
// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}
func main() {
fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330另外一种浅拷贝方式:&取指针赋值
package main
import (
"fmt"
)
// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}
func main() {
fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
查看全部
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
Go语言中的深浅拷贝
1、深拷贝(Deep Copy):
拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。
2、浅拷贝(Shallow Copy):
拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。
引用类型的数据,默认全部都是浅复制,Slice,Map。
深拷贝示例:
package main
import (
"fmt"
)
// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}
func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)
fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)
}
深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。浅拷贝示例2:
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
package main
import (
"fmt"
)
// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}
func main() {
fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
浅拷贝 使用new方式另外一种浅拷贝方式:&取指针赋值
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
package main
import (
"fmt"
)
// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}
func main() {
fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
什么是RPC?
专业名词 • zkbhj 发表了文章 • 0 个评论 • 3917 次浏览 • 2020-07-23 16:52
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
简单总结,RPC需要以下三个步骤来完成:
1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
总结一下上述过程:
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新
// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client
在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。
由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。
1.2 rpc demo
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
2. gRPC的使用
2.1. gRPC与REST
REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息。gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。
参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
查看全部
- RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
- 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
- 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
简单总结,RPC需要以下三个步骤来完成:
1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
总结一下上述过程:
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新
// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client
在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。
由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。
1.2 rpc demo
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
2. gRPC的使用
2.1. gRPC与REST
- REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。
- 但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。
- 正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息。
- gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。
- 注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。
参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
MySQL中char、varchar和text三种字段类型的区别和选择
数据库 • zkbhj 发表了文章 • 0 个评论 • 3445 次浏览 • 2020-05-26 10:57
它们的存储方式和数据的检索方式也都不一样。
数据的检索效率是:char > varchar > text
具体说明:
char:存储定长数据很方便,CHAR字段上的索引效率级高,必须在括号里定义长度,可以有默认值,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间(自动用空格填充),且在检索的时候后面的空格会隐藏掉,所以检索出来的数据需要记得用什么trim之类的函数去过滤空格。
varchar:存储变长数据,但存储效率没有CHAR高,必须在括号里定义长度,可以有默认值。保存数据的时候,不进行空格自动填充,而且如果数据存在空格时,当值保存和检索时尾部的空格仍会保留。另外,varchar类型的实际长度是它的值的实际长度+1,这一个字节用于保存实际使用了多大的长度。
text:存储可变长度的非Unicode数据,最大长度为2^31-1个字符。text列不能有默认值,存储或检索过程中,不存在大小写转换,后面如果指定长度,不会报错误,但是这个长度是不起作用的,意思就是你插入数据的时候,超过你指定的长度还是可以正常插入。
关于存储空间:
在使用UTF8字符集的时候,MySQL手册上是这样描述的:
基本拉丁字母、数字和标点符号使用一个字节;
大多数的欧洲和中东手写字母适合两个字节序列:扩展的拉丁字母(包括发音符号、长音符号、重音符号、低音符号和其它音符)、西里尔字母、希腊语、亚美尼亚语、希伯来语、阿拉伯语、叙利亚语和其它语言;
韩语、中文和日本象形文字使用三个字节序列。
结论:
1、经常变化的字段用varchar;
2、知道固定长度的用char;
3、超过255字节的只能用varchar或者text;
4、能用varchar的地方不用text;
5、能够用数字类型的字段尽量选择数字类型而不用字符串类型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接回逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了;
6、同一张表出现多个大字段,能合并时尽量合并,不能合并时考虑分表 查看全部
它们的存储方式和数据的检索方式也都不一样。
数据的检索效率是:char > varchar > text
具体说明:
char:存储定长数据很方便,CHAR字段上的索引效率级高,必须在括号里定义长度,可以有默认值,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间(自动用空格填充),且在检索的时候后面的空格会隐藏掉,所以检索出来的数据需要记得用什么trim之类的函数去过滤空格。
varchar:存储变长数据,但存储效率没有CHAR高,必须在括号里定义长度,可以有默认值。保存数据的时候,不进行空格自动填充,而且如果数据存在空格时,当值保存和检索时尾部的空格仍会保留。另外,varchar类型的实际长度是它的值的实际长度+1,这一个字节用于保存实际使用了多大的长度。
text:存储可变长度的非Unicode数据,最大长度为2^31-1个字符。text列不能有默认值,存储或检索过程中,不存在大小写转换,后面如果指定长度,不会报错误,但是这个长度是不起作用的,意思就是你插入数据的时候,超过你指定的长度还是可以正常插入。
关于存储空间:
在使用UTF8字符集的时候,MySQL手册上是这样描述的:
基本拉丁字母、数字和标点符号使用一个字节;
大多数的欧洲和中东手写字母适合两个字节序列:扩展的拉丁字母(包括发音符号、长音符号、重音符号、低音符号和其它音符)、西里尔字母、希腊语、亚美尼亚语、希伯来语、阿拉伯语、叙利亚语和其它语言;
韩语、中文和日本象形文字使用三个字节序列。
结论:
1、经常变化的字段用varchar;
2、知道固定长度的用char;
3、超过255字节的只能用varchar或者text;
4、能用varchar的地方不用text;
5、能够用数字类型的字段尽量选择数字类型而不用字符串类型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接回逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了;
6、同一张表出现多个大字段,能合并时尽量合并,不能合并时考虑分表


















