js如何简单实现汉字转成拼音的功能 - 小小邪 - 博客园

mikel阅读(696)

来源: js如何简单实现汉字转成拼音的功能 – 小小邪 – 博客园

  最近项目需要一个功能,实现汉字转拼音功能,具体比如说输入一个“你好”,同时带出对应拼音“NiHao”,在此做一下记录

1、首先引入两个文件

    <script src="jquery.min.js"></script>
    <script src="Convert_Pinyin.js"></script>

2、html设计

复制代码
 <body>
        <div>         
             输入名称:<input type="text" id="chinaName"  onBlur="ConvertName()" />  <br/>  
             全写拼音:<input type="text" id="fullName" /> <br/> 
             简写拼音:<input type="text" id="easyName" /> <br/> 
        </div>    
 </body>
复制代码

3、js方法

复制代码
<script>
       var ConvertName = function(){    
            var chinaName = $('#chinaName').val();    
            //获取全写拼音(调用js中方法)        
            var fullName = pinyin.getFullChars(chinaName);
            //获取简写拼音(调用js中方法)
            var easyName = pinyin.getCamelChars(chinaName);    
            //给两个文本框赋值    
            $('#fullName').val(fullName);
            $('#easyName').val(easyName);
       }
</script>
复制代码

4、实现效果

5、整体demo

复制代码
<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>如何实现汉字转拼音功能</title>
    </head>
    <script src="jquery.min.js"></script>
    <script src="Convert_Pinyin.js"></script>
    <script>            
        var ConvertName = function(){    
            var chinaName = $('#chinaName').val();    
            //获取全写拼音(调用js中方法)        
            var fullName = pinyin.getFullChars(chinaName);
            //获取简写拼音(调用js中方法)
            var easyName = pinyin.getCamelChars(chinaName);    
            //给两个文本框赋值    
            $('#fullName').val(fullName);
            $('#easyName').val(easyName);
        }
        
    </script>
    <body>
        <div>         
             输入名称:<input type="text" id="chinaName"  onBlur="ConvertName()" />  <br/>  
             全写拼音:<input type="text" id="fullName" /> <br/> 
             简写拼音:<input type="text" id="easyName" /> <br/> 
        </div>    
    </body>
</html>
复制代码

6、汉字转拼音js下载路径:

链接:https://pan.baidu.com/s/1NZ4noIgHv2HSzZW6yBRTxA    密码:2kv1

平时多记记,到用时才能看看,记录你的进步,分享你的成果

『JWT』,你必须了解的认证登录方案 - 风的姿态 - 博客园

mikel阅读(609)

来源: 『JWT』,你必须了解的认证登录方案 – 风的姿态 – 博客园

JWT 全称是 JSON Web Token,是目前非常流行的跨域认证解决方案,在单点登录场景中经常使用到。

有些人觉得它非常好用,用了它之后就不用在服务端借助 redis 实现认证过程了,但是,还有一部分人认为它生来就有缺陷,根本不能用。

这是为什么呢?

传统的认证方式

从一个登录场景说起

你平时用过那么多网站和 APP,其中有很多都是需要登录的吧,那咱们就选一个场景出来说说。

以一个电商系统为例,如果你想要下单,首先需要注册一个账号,拥有了账号之后,需要输入用户名(比如手机号或邮箱)、密码完成登录过程。之后你在一段时间内再次进入系统,是不需要输入用户名和密码的,只有在连续长时间不登录的情况下(例如一个月没登录过)访问系统,才需要再次输入用户名和密码。

对于那些使用频率很高的网站或应用,通常是很长时间都不需要输入密码的,以至于你在换了一台电脑或者一部手机之后,一些经常使用的网站或 APP 的密码都不记得了。

早期互联网以 web 为主,客户端是浏览器 ,所以 Cookie-Session 方式是早期最常用的认证方式,直到现在,一些 web 网站依然用这种方式做认证。

认证过程大致如下:

  1. 用户输入用户名、密码或者用短信验证码方式登录系统;
  2. 服务端验证后,创建一个 Session 信息,并且将 SessionID 存到 cookie,发送回浏览器;
  3. 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验;

image-20200706173031724

但是为什么说它是传统的认证方式,因为现在人手一部智能手机,很多人都不用电脑,平时都是使用手机上的各种 APP,比如淘宝、拼多多等。
在这种潮流之下,传统的 Cookie-Session 就遇到了一些问题:
1、首先,Cookie-Session 只能在 web 场景下使用,如果是 APP 呢,APP 可没有地方存 cookie。
现在的产品基本上都同时提供 web 端和 APP 两种使用方式,有点产品甚至只有 APP。

2、退一万步说,你做的产品只支持 web,也要考虑跨域问题, 但Cookie 是不能跨域的。
拿天猫商城来说,当你进入天猫商城后,会看到顶部有天猫超市、天猫国际、天猫会员这些菜单。而点击这些菜单都会进入不同的域名,不同的域名下的 cookie 都是不一样的,你在 A 域名下是没办法拿到 B 域名的 cookie 的,即使是子域也不行。

image-20200706173939291

3、如果是分布式服务,需要考虑 Session 同步问题。
现在的互联网网站和 APP 基本上都是分布式部署,也就是服务端不止一台机器。当某个用户在页面上进行登录操作后,这个登录动作必定是请求到了其中某一台服务器上。你的身份信息得保存下来吧,传统方式就是存 Session。

接下来,问题来了。你访问了几个页面,这时,有个请求经过负载均衡,路由到了另外一台服务器(不是你登录的那台)。当后台接到请求后,要检查用户身份信息和权限,于是接口开始从从 Session 中获取用户信息。但是,这台服务器不是当时登录的那台,并没存你的 Session ,这样后台服务就认为你是一个非登录的用户,也就不能给你返回数据了。

所以,为了避免这种情况的发生,就要做 Session 同步。一台服务器接收到登录请求后,在当前服务器保存 Session 后,也要向其他几个服务器同步。

4、cookie 存在 CSRF(跨站请求伪造)的风险。 跨站请求伪造,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。CSRF 利用的是网站对用户网页浏览器的信任。简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(比如购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户发起的操作。
比如说我是一个黑客,我发现你经常访问的一个技术网站存在 CSRF 漏洞。发布文章支持 html 格式,进而我在 html 中加入一些危险内容,例如

 <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">

假设 src 指向的地址是一个你平时用的购物网站的付款地址(当然只是举例,真正的攻击可没这么简单),如果你之前登录过并且标识你身份信息的 cookie 已经保存下来了。当你刷到我发布的这篇文章的时候,img 标签一加载,这个 CSRF 攻击就会起作用,在你不知情的情况下向这个网站付款了。

由于传统的 Cookie-Session 认证存在诸多问题,那可以把上面的方案改造一下。
1、改造 Cookie 既然 Cookie 不能在 APP 等非浏览器中使用,那就不用 cookie 做客户端存储,改用其他方式。
改成什么呢?
web 中可以使用 local storage,APP 中使用客户端数据库,这样既能这样就实现了跨域,并且避免了 CSRF 。

2、服务端也不存 Session 了,把 Session 信息拿出来存到 Redis 等内存数据库中,这样即提高了速度,又避免了 Session 同步问题;

经过改造之后变成了如下的认证过程:

  1. 用户输入用户名、密码或者用短信验证码方式登录系统;
  2. 服务端经过验证,将认证信息构造好的数据结构存储到 Redis 中,并将 key 值返回给客户端;
  3. 客户端拿到返回的 key,存储到 local storage 或本地数据库;
  4. 下次客户端再次请求,把 key 值附加到 header 或者 请求体中;
  5. 服务端根据获取的 key,到 Redis 中获取认证信息;

下面两张图分别演示了首次登录和非首次登录的过程。

首次登录

非首次登录

经过一顿猛如虎的改造,解决了传统 Cookie-Session 方式存在的问题。这种改造需要开发者在项目中自行完成。改造起来肯定是费时费力的,而且还有可能存在漏洞。

JWT 出场

这时,JWT 就可以上场了,JWT 就是一种Cookie-Session改造版的具体实现,让你省去自己造轮子的时间,JWT 还有个好处,那就是你可以不用在服务端存储认证信息(比如 token),完全由客户端提供,服务端只要根据 JWT 自身提供的解密算法就可以验证用户合法性,而且这个过程是安全的。

如果你是刚接触 JWT,最有疑问的一点可能就是: JWT 为什么可以完全依靠客户端(比如浏览器端)就能实现认证功能,认证信息全都存在客户端,怎么保证安全性?

JWT 数据结构

JWT 最后的形式就是个字符串,它由头部载荷签名这三部分组成,中间以「.」分隔。像下面这样:

997EDE1C-5689-4C3F-98E8-25C25BBEC3FC

头部

头部以 JSON 格式表示,用于指明令牌类型和加密算法。形式如下,表示使用 JWT 格式,加密算法采用 HS256,这是最常用的算法,除此之外还有很多其他的。

{
  "alg": "HS256",
  "typ": "JWT"
}

对应上图的红色 header 部分,需要 Base64 编码。

载荷

用来存储服务器需要的数据,比如用户信息,例如姓名、性别、年龄等,要注意的是重要的机密信息最好不要放到这里,比如密码等。

{
  "name": "古时的风筝",
  "introduce": "英俊潇洒"
}

另外,JWT 还规定了 7 个字段供开发者选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

这部分信息也是要用 Base64 编码的。

签名

签名有一个计算公式。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  Secret
)

使用HMACSHA256算法计算得出,这个方法有两个参数,前一个参数是 (base64 编码的头部 + base64 编码的载荷)用点号相连,后一个参数是自定义的字符串密钥,密钥不要暴露在客户端,近应该服务器知道。

使用方式

了解了 JWT 的结构和算法后,那怎么使用呢?假设我这儿有个网站。

1、在用户登录网站的时候,需要输入用户名、密码或者短信验证的方式登录,登录请求到达服务端的时候,服务端对账号、密码进行验证,然后计算出 JWT 字符串,返回给客户端。

2、客户端拿到这个 JWT 字符串后,存储到 cookie 或者 浏览器的 LocalStorage 中。

3、再次发送请求,比如请求用户设置页面的时候,在 HTTP 请求头中加入 JWT 字符串,或者直接放到请求主体中。

4、服务端拿到这串 JWT 字符串后,使用 base64的头部和 base64 的载荷部分,通过HMACSHA256算法计算签名部分,比较计算结果和传来的签名部分是否一致,如果一致,说明此次请求没有问题,如果不一致,说明请求过期或者是非法请求。

怎么保证安全性的

保证安全性的关键就是 HMACSHA256 或者与它同类型的加密算法,因为加密过程是不可逆的,所以不能根据传到前端的 JWT 传反解到密钥信息。

另外,不同的头部和载荷加密之后得到的签名都是不同的,所以,如果有人改了载荷部分的信息,那最后加密出的结果肯定就和改之前的不一样的,所以,最后验证的结果就是不合法的请求。

别人拿到完整 JWT 还安全吗

假设载荷部分存储了权限级别相关的字段,强盗拿到 JWT 串后想要修改为更高权限的级别,上面刚说了,这种情况下是肯定不会得逞的,因为加密出来的签名会不一样,服务器可能很容易的判别出来。

那如果强盗拿到后不做更改,直接用呢,那就没有办法了,为了更大程度上防止被强盗盗取,应该使用 HTTPS 协议而不是 HTTP 协议,这样可以有效的防止一些中间劫持攻击行为。

有同学就要说了,这一点也不安全啊,拿到 JWT 串就可以轻松模拟请求了。确实是这样,但是前提是你怎么样能拿到,除了上面说的中间劫持外,还有什么办法吗?

除非强盗直接拿了你的电脑,那这样的话,对不起,不光 JWT 不安全了,其他任何网站,任何认证方式都不安全。

虽然这样的情况很少,但是在使用 JWT 的时候仍然要注意合理的设置过期时间,不要太长。

一个问题

JWT 有个问题,导致很多开发团队放弃使用它,那就是一旦颁发一个 JWT 令牌,服务端就没办法废弃掉它,除非等到它自身过期。有很多应用默认只允许最新登录的一个客户端正常使用,不允许多端登录,JWT 就没办法做到,因为颁发了新令牌,但是老的令牌在过期前仍然可用。这种情况下,就需要服务端增加相应的逻辑。

常用的 JWT 库

JWT 官网列出了各种语言对应的库,其中 Java 的如下几个。

image-20200817112359199

以 java-jwt为例。

1、引入对应的 Maven 包。

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

2、在登录时,调用 create 方法得到一个令牌,并返回给前端。

public static String create(){
  try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    String token = JWT.create()
      .withIssuer("auth0")
      .withSubject("subject")
      .withClaim("name","古时的风筝")
      .withClaim("introduce","英俊潇洒")
      .sign(algorithm);
    System.out.println(token);
    return token;
  } catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
    throw exception;
  }
}

3、登录成功后,再次发起请求的时候将 token 放到 header 或者请求体中,服务端对 token 进行验证。

public static Boolean verify(String token){
  try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    JWTVerifier verifier = JWT.require(algorithm)
      .withIssuer("auth0")
      .build(); //Reusable verifier instance
    DecodedJWT jwt = verifier.verify(token);
    String payload = jwt.getPayload();
    String name = jwt.getClaim("name").asString();
    String introduce = jwt.getClaim("introduce").asString();
    System.out.println(payload);
    System.out.println(name);
    System.out.println(introduce);
    return true;
  } catch (JWTVerificationException exception){
    //Invalid signature/claims
    return false;
  }
}

4、用 create 方法生成 token,并用 verify 方法验证一下。

public static void main(String[] args){
  String token = create();
  Boolean result = verify(token);
  System.out.println(result);
}

得到下面的结果

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0.ooQ1K_XyljjHf34Nv5iJvg1MQgVe6jlphxv4eeFt8pA
eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0
古时的风筝
英俊潇洒
true

使用 create 方法创建的 JWT 串可以通过验证。

而如果我将 JWT 串中的载荷部分,两个点号中间的部分修改一下,然后再调用 verify 方法验证,会出现 JWTVerificationException 异常,不能通过验证。


这位英俊潇洒的少年,如果觉得还不错的话,给个推荐可好!

公众号「古时的风筝」,Java 开发者,全栈工程师,bug 杀手,擅长解决问题。
一个兼具深度与广度的程序员鼓励师,本打算写诗却写起了代码的田园码农!坚持原创干货输出,你可选择现在就关注我,或者看看历史文章再关注也不迟。长按二维码关注,跟我一起变优秀!

阿里的秒杀系统是怎么设计的? - 敖丙 - 博客园

mikel阅读(578)

来源: 阿里的秒杀系统是怎么设计的? – 敖丙 – 博客园

点赞再看,养成习惯,微信搜一搜【三太子敖丙】关注这个互联网苟且偷生的工具人。

本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

背景

我之前写过一个秒杀系统的文章不过有些许瑕疵,所以我准备在之前的基础上进行二次创作,不过让我决心二创秒杀系统的原因是我最近面试了很多读者,动不动就是秒杀系统把我整蒙蔽了,我懵的主要是秒杀系统的细节大家都不知道,甚至不知道电商公司一个秒杀系统的组成部分。

我之前在某电商公司就是做电商活动的,所以这样的场景和很多解决方案我是比较清楚的,那我就从我自身去带着大家看看一个秒杀的设计细节以及中间各种解决方案的利弊,以下就是我设计的秒杀系统,几乎涵盖了市面上所有秒杀的实现细节:

正文

首先设计一个系统之前,我们需要先确认我们的业务场景是怎么样子的,我就带着大家一起假设一个场景好吧。

我们现场要卖1000件下面这个婴儿纸尿裤,然后我们根据以往这样秒杀活动的数据经验来看,目测来抢这100件纸尿裤的人足足有10万人。(南极人打钱!)

你一听,完了呀,这我们的服务器哪里顶得住啊!说真的直接打DB肯定挂,但是别急嘛,有暖男敖丙在,任何系统我们开始设计之前我们都应该去思考会出现哪些问题?这里我罗列了几个非常经典的问题:

问题

高并发:

是的高并发这个是我们想都不用想的一个点,一瞬间这么多人进来这不是高并发什么时候是呢?

是吧,秒杀的特点就是这样时间极短、 瞬间用户量大

正常的店铺营销都是用极低的价格配合上短信、APP的精准推送,吸引特别多的用户来参与这场秒杀,爽了商家苦了开发呀

秒杀大家都知道如果真的营销到位,价格诱人,几十万的流量我觉得完全不是问题,那单机的Redis我感觉3-4W的QPS还是能顶得住的,但是再高了就没办法了,那这个数据随便搞个热销商品的秒杀可能都不止了。

大量的请求进来,我们需要考虑的点就很多了,缓存雪崩缓存击穿缓存穿透这些我之前提到的点都是有可能发生的,出现问题打挂DB那就很难受了,活动失败用户体验差,活动人气没了,最后背锅的还是开发

超卖:

但凡是个秒杀,都怕超卖,我这里举例的只是尿不湿,要是换成100个MacBook Pro,商家的预算经费卖100个可以赚点还可以造势,结果你写错程序多卖出去200个,你不发货用户投诉你,平台封你店,你发货就血亏,你怎么办? (没事看了敖丙的文章直接不怕)

那最后只能杀个开发祭天解气了,秒杀的价格本来就低了,基本上都是不怎么赚钱的,超卖了就恐怖了呀,所以超卖也是很关键的一个点。

恶意请求:

你这么低的价格,假如我抢到了,我转手卖掉我不是血赚?就算我不卖我也不亏啊,那用户知道,你知道,别的别有用心的人(黑客、黄牛…)肯定也知道的。

那简单啊,我知道你什么时候抢,我搞个几十台机器搞点脚本,我也模拟出来十几万个人左右的请求,那我是不是意味着我基本上有80%的成功率了。

真实情况可能远远不止,因为机器请求的速度比人的手速往往快太多了,在贵州的敖丙我每年回家抢高铁票都是秒光的,我也不知道有没有黄牛的功劳,我要Diss你,黄牛。杰伦演唱会门票抢不到,我也Diss你。

Tip:科普下,小道消息了解到的,黄牛的抢票系统,比国内很多小公司的系统还吊很多,架构设计都是顶级的,我用顶配的服务加上顶配的架构设计,你还想看演唱会?还想回家?

不过不用黄牛我回家都难,我们云贵川跟我一样要回家过年的仔太多了555!

链接暴露:

前面几个问题大家可能都很好理解,一看到这个有的小伙伴可能会比较疑惑,啥是链接暴露呀?

相信是个开发同学都对这个画面一点都不陌生吧,懂点行的仔都可以打开谷歌的开发者模式,然后看看你的网页代码,有的就有URL,但是我写VUE的时候是事件触发然后去调用文件里面的接口看源码看不到,但是我可以点击一下查看你的请求地址啊,不过你好像可以对按钮在秒杀前置灰。

不管怎么样子都有危险,撇开外面的所有的东西你都挡住了,你卖这个东西实在便宜得过分,有诱惑力,你能保证开发不动心?开发知道地址,在秒杀的时候自己提前请求。。。(开发:怎么TM又是我)

数据库:

每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单单是做秒杀的还涉及其他的业务,你没做降级、限流、熔断啥的,别的一起挂,小公司的话可能全站崩溃404

反正不管你秒杀怎么挂,你别把别的搞挂了对吧,搞挂了就不是杀一个程序员能搞定的。

程序员:我TM好难啊!

问题都列出来了,那怎么设计,怎么解决这些问题就是接下去要考虑的了,我们对症下药。

我会从我设计的秒杀系统从上到下去给大家介绍我们正常电商秒杀系统在每一层做了些什么,每一层存在的问题,难点等。

我们从前端开始:

前端

秒杀系统普遍都是商城网页、H5、APP、小程序这几项。

在前端这一层其实我们可以做的事情有很多,如果用node去做,甚至能直接处理掉整个秒杀,但是node其实应该属于后端,所以我不讨论node Service了。

资源静态化:

秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。

秒杀链接加盐:

我们上面说了链接要是提前暴露出去可能有人直接访问url就提前秒杀了,那又有小伙伴要说了我做个时间的校验就好了呀,那我告诉你,知道链接的地址比起页面人工点击的还是有很大优势

我知道url了,那我通过程序不断获取最新的北京时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率大太多了,而且我可以一毫秒发送N次请求,搞不好你卖100个产品我全拿了。

那这种情况怎么避免?

简单,把URL动态化,就连写代码的人都不知道,你就通过MD5之类的摘要算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。

这个只能防止一部分没耐心继续破解下去的黑客,有耐心的人研究出来还是能破解,在电商场景存在很多这样的羊毛党,那怎么做呢?

后面我会说。

限流:

限流这里我觉得应该分为前端限流后端限流

物理控制:

大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。

这是因为怕大家在时间快到的最后几秒秒疯狂请求服务器,然后还没到秒杀的时候基本上服务器就挂了。

这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点再给按钮可用状态。

按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。

你敢说你们秒杀的时候不是这样的?

前端限流:这个很简单,一般秒杀不会让你一直点的,一般都是点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。

后端限流:秒杀的时候肯定是涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖光了,return了一个false,前端直接秒杀结束,然后你后端也关闭后续无效请求的介入了。

Tip:真正的限流还会有限流组件的加入例如:阿里的Sentinel、Hystrix等。我这里就不展开了,就说一下物理的限流。

我们卖1000件商品,请求有10W,我们不需要把十万都放进来,你可以放1W请求进来,然后再进行操作,因为秒杀对于用户本身就是黑盒的,所以你怎么做的他们是没感知的,至于为啥放1W进来,而不是刚好1000,是因为会丢掉一些薅羊毛的用户,至于怎么判断,后面的风控阶段我会说。

Nginx:

Nginx大家想必都不陌生了吧,这玩意是高性能的web服务器,并发也随便顶几万不是梦,但是我们的Tomcat只能顶几百的并发呀,那简单呀负载均衡嘛,一台服务几百,那就多搞点,在秒杀的时候多租点流量机

Tip:据我所知国内某大厂就是在去年春节活动期间租光了亚洲所有的服务器,小公司也很喜欢在双十一期间买流量机来顶住压力。

这样一对比是不是觉得你的集群能顶很多了。

恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在网关那一层就得拦截掉了,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用网络带宽或者把服务器打崩、缓存击穿等等。

风控

我可以明确的告诉大家,前面的所有措施还是拦不住很多羊毛党,因为他们是专业的团队,他们可以注册很多账号来薅你的羊毛,而且不用机器请求,就用群控,操作几乎跟真实用户一模一样。

那怎么办,是不是无解了?

这个时候就需要风控同学的介入了,在请求到达后端之前,风控可以根据账号行为分析出这个账号机器人的概率大不大,我现在负责公司的某些特殊系统,每个用户的行为都是会送到我们大数据团队进行分析处理,给你打上对应标签的。

那黑客其实也有办法:养号

他们去黑市买真实用户有过很多记录的账号,买到了还不闲着,帮他们去购物啥的,让系统无法识别他们是黑号还是真实用户的号。

怎么办?

通杀!是的没有办法,只能通杀了,通杀的意思就是,我们通过风管分析出来这个用户是真实用户的概率没有其他用户概率大,那就认为他是机器了,丢弃他的请求。

之前的限流我们放进来10000个请求,但是我们真正的库存只有1000个,那我们就算出最有可能是真实用户的1000人进行秒杀,丢弃其他请求,因为秒杀本来就是黑盒操作的,用户层面是无感知的,这样设计能让真实的用户买到东西,还可以减少自己被薅羊毛的概率。

风控可以说是流量进入的最后一道门槛了,所以很多公司的风控是很强的,蚂蚁金服的风控大家如果了解过就知道了,你的资金在支付宝被盗了,他们是能做到全款补偿是有原因的。

后端

服务单一职责:

设计个能抗住高并发的系统,我觉得还是得单一职责

什么意思呢,大家都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式

也就是我们下单是有个订单服务,用户登录管理等有个用户服务等等,那为啥我们不给秒杀也开个服务,我们把秒杀的代码业务逻辑放一起。

单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(高可用)

Redis集群:

之前不是说单机的Redis顶不住嘛,那简单多找几个兄弟啊,秒杀本来就是读多写少,那你们是不是瞬间想起来我之前跟你们提到过的,Redis集群主从同步读写分离,我们还搞点哨兵,开启持久化直接无敌高可用!

库存预热:

秒杀的本质,就是对库存的抢夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存,撇开性能因数,你不觉得这样好繁琐,对业务开发人员都不友好,而且数据库顶不住啊。

开发:你tm总算为我着想一次了。

那怎么办?

我们都知道数据库顶不住但是他的兄弟非关系型的数据库Redis能顶啊!

那不简单了,我们要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介绍了,再异步的去修改库存就好了。

但是用了Redis就有一个问题了,我们上面说了我们采用主从,就是我们会去读取库存然后再判断然后有库存才去减库存,正常情况没问题,但是高并发的情况问题就很大了。

**多品几遍!!!**就比如现在库存只剩下1个了,我们高并发嘛,4个服务器一起查询了发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,那结果就变成了-3,是的只有一个是真的抢到了,别的都是超卖的。咋办?

事务:

Redis本身是支持事务的,而且他有很多原子命令的,大家也可以用LUA,还可以用他的管道,乐观锁他也知支持。

限流&降级&熔断&隔离:

这个为啥要做呢,不怕一万就怕万一,万一你真的顶不住了,限流,顶不住就挡一部分出去但是不能说不行,降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就独立的,但是你会调用其他的系统嘛,你快不行了你别拖累兄弟们啊。

消息队列(削峰填谷):

一说到这个名词,很多小伙伴就知道了,对的MQ,你买东西少了你直接100个请求改库我觉得没问题,但是万一秒杀一万个,10万个呢?服务器挂了,程序员又要背锅的

秒杀就是这种瞬间流量很高,但是平时又没有流量的场景,那消息队列完全契合这样的场景了呀,削峰填谷。

Tip:可能小伙伴说我们业务达不到这个量级,没必要。但是我想说我们写代码,就不应该写出有逻辑漏洞的代码,至少以后公司体量上去了,别人一看居然不用改代码,一看代码作者是敖丙?有点东西!

你可以把它放消息队列,然后一点点消费去改库存就好了嘛,不过单个商品其实一次修改就够了,我这里说的是某个点多个商品一起秒杀的场景,像极了双十一零点。

数据库

数据库用MySQL只要连接池设置合理一般问题是不大的,不过一般大公司不缺钱而且秒杀这样的活动十分频繁,我之前所在的公司就是这样秒杀特卖这样的场景一直都是不间断的。

单独给秒杀建立一个数据库,为秒杀服务,表的设计也是竟可能的简单点,现在的互联网架构部署都是分库的。

至于表就看大家怎么设计了,该设置索引的地方还是要设置索引的,建完后记得用explain看看SQL的执行计划。(不了解的小伙伴也没事,MySQL章节去康康)

分布式事务

这为啥我不放在后端而放到最后来讲呢?

因为上面的任何一步都是可能出错的,而且我们是在不同的服务里面出错的,那就涉及分布式事务了,但是分布式事务大家想的是一定要成功什么的那就不对了,还是那句话,几个请求丢了就丢了,要保证时效和服务的可用可靠。

所以TCC最终一致性其实不是很适合,TCC开发成本很大,所有接口都要写三次,因为涉及TCC的三个阶段。

最终一致性基本上都是靠轮训的操作去保证一个操作一定成功,那时效性就大打折扣了。

大家觉得不那么可靠的**两段式(2PC)三段式(3PC)**就派上用场了,他们不一定能保证数据最终一致,但是效率上还算ok。

总结

到这里我想我已经基本上把该考虑的点还有对应的解决方案也都说了一下,不知道还有没有没考虑到的,但是就算没考虑到我想我这个设计,应该也能撑住一个完整的秒杀流程。

最后大家再看看这个秒杀系统或许会有新的感悟,是不是一个系统真的没有大家想的那么简单,而且我还是有漏掉的细节,这是一定的。

秒杀这章我脑细胞死了很多,考虑了很多个点,最后还是出来了,忍不住给自己点赞

总结

我们玩归玩,闹归闹,别拿面试开玩笑。

秒杀不一定是每个同学都会问到的,至少肯定没Redis基础那样常问,但是一旦问到,大家一定要回答到点上。

至少你得说出可能出现的情况需要注意的情况,以及对于的解决思路和方案,因为这才是一个coder的基本素养,这些你不考虑你也很难去进步。

最后就是需要对整个链路比较熟悉,注意是一个完整的链路,前端怎么设计的呀,网关的作用呀,怎么解决Redis的并发竞争啊,数据的同步方式呀,MQ的作用啊等等,相信你会有不错的收获。

不知道这是一次成功还是失败的二创,我里面所有提到的技术细节我都写了对应的文章,大家可以关注我去历史文章看看,天色已晚,我溜了。

絮叨

另外,敖丙把自己的面试文章整理成了一本电子书,共 1630页!目录如下,还有我复习时总结的面试题以及简历模板

jquery的$.each如何退出循环和退出本次循环 - 咖啡无眠 - 博客园

mikel阅读(644)

return false

来源: jquery的$.each如何退出循环和退出本次循环 – 咖啡无眠 – 博客园

JQuery中each类似于JavaScript的for循环
但不同于for循环的是在each里面不能使用break结束循环,也不能使用continue来结束本次循环,想要实现类似的功能就只能用return,
break           用return false
continue      用return true

当前上下文中不存在名称"__o" - XOYOZO

mikel阅读(1016)

来源: 当前上下文中不存在名称”__o” – XOYOZO

经常会看到这样似错非错的提示:

当前上下文中不存在名称”__o”

The name ‘__o’ does not exist in the current context

image.png

实际上,我没有定义任何名为 __o 的变量。

发生这种情况的原因可能是使用了类似如下的代码:

1
2
3
4
<% if(true) { %>
<%= 1 %>
<% } %>
<%= 2 %>

为了在设计界面的 <%= %> 代码块中提供智能感知,ASP.NET(VB 或 C#)会自动生成一个名为“__o”的临时变量,这在页面编译器看到第一个 <%= %> 块时就完成了。但是在这里,<%= %> 块在 if 中出现,所以当关闭 if 后再使用 <%= %> 时,变量超出了定义的范围。

1
2
3
4
5
6
if (true)
{
    object @__o;
    @__o = 1;
}
@__o = 2;

解决方法:在页面的早期添加一个虚表达式。例如:<%= “” %>。这将不会呈现任何内容,并且它将确保在任何潜在的 if(或其他范围界定)语句之前,在 Render 方法中将 __o 声明为顶级。

当然还有一种治标不治本的方法就是隐藏这些错误提示(这并不影响程序正常运行):

点击错误列表面板左上角的过滤器按钮,CS0103,其中包含错误代码:当前上下文中不存在名称”__o”,这些错误将不再显示,您仍然可以有其他 IntelliSense 错误和警告。

c# – 无法将文件或程序集Microsoft.Owin 3.0.0加载到3.0.1 - 程序园

mikel阅读(791)

有趣的依赖问题…… VS2015.3,.NET45将Microsoft.Owin从3.0.0更新到3.0.1(跟踪此更新是问题 – 想要安装https://www.nuget.org/packages/IdentityServer3.AccessTokenValidation/) 它在IIS Express上本地工作 但发布到Azure网站:(我直接从VS删除目标上的其他文件).重新启动并删除了a

来源: c# – 无法将文件或程序集Microsoft.Owin 3.0.0加载到3.0.1 – 程序园

有趣的依赖问题……VS2015.3,.NET45将Microsoft.Owin从3.0.0更新到3.0.1(跟踪此更新是问题 – 想要安装https://www.nuget.org/packages/IdentityServer3.AccessTokenValidation/)

enter image description here
它在IIS Express上本地工作

但发布到Azure网站:(我直接从VS删除目标上的其他文件).重新启动并删除了azure webserver中的所有文件.

enter image description here

web.config翻译看起来没问题

<dependentAssembly>
   <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
   <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />   
</dependentAssembly>

编辑
Bruce Chen的回答指出了我的方向Kuud(Azure – 开发工具,高级工具)

enter image description here

我下载了Owin dll,这是错误的版本,即3.0.0

为了解决这个问题,我清除了解决方案中的所有包工件

git clean -xfd(小心)

然后重建

Could not load file or assembly ‘Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference.

通常,这意味着.NET程序集加载程序无法找到具有引用的特定版本的程序集.请确保您的Azure网站中可以找到版本3.0.1.0的程序集,并且该版本通过KUDU或FTP客户端与web.config中的定义匹配.

正如您所说,它在IIS Express上本地工作,请尝试通过KUDU或FTP客户端手动将您的网站内容部署到Azure,并找出它是否有效.

SQL Server 取日期时间 - 一技赢 - 博客园

mikel阅读(572)

来源: SQL Server 取日期时间 – 一技赢 – 博客园

在本文中,GetDate()获得的日期由两部分组成,分别是今天的日期和当时的时间: Select GetDate()
用DateName()就可以获得相应的年、月、日,然后再把它们连接起来就可以了:

Select Datename(year,GetDate())+’-‘+Datename
(month,GetDate())+’-‘+Datename(day,GetDate())

另外,DateName()还可以获得到小时、时间、秒、星期几、第几周,分别如下:

Select Datename(hour,GetDate())
Select Datename(minute,GetDate())
Select Datename(second,GetDate())
Select Datename(weekDay,GetDate())
Select Datename(week,GetDate())

SQL中的日期类型DateTime的默认格式就是yyyy-mm-dd hh:mi:ss: mmm,可大多数的情况我们只想得到他的日期部分,而不许要后面的时间。上一篇中提到用Datename()函数来截取拼接出不包含时间部分的日期,现在再说一种方法,更加简单的获取到不包含时间的日期!

使用Convert()函数:

select convert(char(10),GetDate(),120) as Date

* 第3个参数就是用来设置日期类型数据的显示样式的,下面介绍几种样式的参数:

100   mm dd yyyy

101   mm/dd/yyyy

102   yyyy.mm.dd

103   dd/mm/yyyy

106   dd mm yyyy

108   hh:mi:ss(时间)

111   yyyy/mm/dd

112   yyyymmdd

120   yyyy-mm-dd

 

复制代码
 1 SELECT CONVERT(varchar(100), GETDATE(), 0) 05  9 2011  9:12AM
 2  SELECT CONVERT(varchar(100), GETDATE(), 1) 05/09/11
 3 SELECT CONVERT(varchar(100), GETDATE(), 2) 11.05.09
 4 SELECT CONVERT(varchar(100), GETDATE(), 3) 09/05/11
 5 SELECT CONVERT(varchar(100), GETDATE(), 4) 09.05.11
 6 SELECT CONVERT(varchar(100), GETDATE(), 5) 09-05-11
 7 SELECT CONVERT(varchar(100), GETDATE(), 6) 09 05 11
 8 SELECT CONVERT(varchar(100), GETDATE(), 7) 05 09, 11
 9 SELECT CONVERT(varchar(100), GETDATE(), 8) 09:13:14
10 SELECT CONVERT(varchar(100), GETDATE(), 9) 05  9 2011  9:13:14:670AM
11 SELECT CONVERT(varchar(100), GETDATE(), 10) 05-09-11
12 SELECT CONVERT(varchar(100), GETDATE(), 11) 11/05/09
13 SELECT CONVERT(varchar(100), GETDATE(), 12) 110509
14 SELECT CONVERT(varchar(100), GETDATE(), 13) 09 05 2011 09:13:14:670
15 SELECT CONVERT(varchar(100), GETDATE(), 14) 09:13:14:670
16 SELECT CONVERT(varchar(100), GETDATE(), 20) 2011-05-09 09:13:14
17 SELECT CONVERT(varchar(100), GETDATE(), 21) 2011-05-09 09:13:14.670
18 SELECT CONVERT(varchar(100), GETDATE(), 22) 05/09/11  9:15:33 AM
19 SELECT CONVERT(varchar(100), GETDATE(), 23) 2011-05-09
20 SELECT CONVERT(varchar(100), GETDATE(), 24) 09:15:33
21 SELECT CONVERT(varchar(100), GETDATE(), 25) 2011-05-09 09:15:33.140
22 SELECT CONVERT(varchar(100), GETDATE(), 100) 05  9 2011  9:15AM
23 SELECT CONVERT(varchar(100), GETDATE(), 101) 05/09/2011
24 SELECT CONVERT(varchar(100), GETDATE(), 102) 2011.05.09
25 SELECT CONVERT(varchar(100), GETDATE(), 103) 09/05/2011
26 SELECT CONVERT(varchar(100), GETDATE(), 104) 09.05.2011
27 SELECT CONVERT(varchar(100), GETDATE(), 105) 09-05-2011
28 SELECT CONVERT(varchar(100), GETDATE(), 106) 09 05 2011
29 SELECT CONVERT(varchar(100), GETDATE(), 107) 05 09, 2011
30 SELECT CONVERT(varchar(100), GETDATE(), 108) 09:16:38
31 SELECT CONVERT(varchar(100), GETDATE(), 109) 05  9 2011  9:16:38:543AM
32 SELECT CONVERT(varchar(100), GETDATE(), 110) 05-09-2011
33 SELECT CONVERT(varchar(100), GETDATE(), 111) 2011/05/09
34 SELECT CONVERT(varchar(100), GETDATE(), 112) 20110509
35 SELECT CONVERT(varchar(100), GETDATE(), 113) 09 05 2011 09:17:19:857
36 SELECT CONVERT(varchar(100), GETDATE(), 114) 09:17:19:857
37 SELECT CONVERT(varchar(100), GETDATE(), 120) 2011-05-09 09:17:19
38 SELECT CONVERT(varchar(100), GETDATE(), 121) 2011-05-09 09:17:19.857
39 SELECT CONVERT(varchar(100), GETDATE(), 126) 2011-05-09T09:17:19.857
40 SELECT CONVERT(varchar(100), GETDATE(), 130)  6 ????? ??????? 1432  9:17:19:857AM
41 SELECT CONVERT(varchar(100), GETDATE(), 131)  6/06/1432  9:17:19:857AM
复制代码

 

用Python爬取股票数据,绘制K线和均线并用机器学习预测股价(来自我出的书) - hsm_computer - 博客园

mikel阅读(810)

来源: 用Python爬取股票数据,绘制K线和均线并用机器学习预测股价(来自我出的书) – hsm_computer – 博客园

最近我出了一本书,《基于股票大数据分析的Python入门实战 视频教学版》,京东链接:https://item.jd.com/69241653952.html,在其中用股票范例讲述Python爬虫、数据分析和机器学习的技术,大家看了我的书,不仅能很快用比较热门的案例学好Python,更能了解些股票知识,不至于一入市就拍脑袋买卖。

在本文里,将给出若干精彩范例,包括用爬虫获取股市数据,用matplotlib可视化控件绘制K线和均线,以及用sklean库里的方法,通过机器学习预测股价的走势。

1 通过pandas_datareader库的方法爬取股市数据

pandas_datareader是一个能读取各种金融数据的库,在下面的getDataByPandasDatareader.py范例程序中演示了通过这个库获取股市数据的常规方法。

复制代码
1    # coding=utf-8
2    from pandas_datareader import data as pdr
3    import yfinance as yf
4    yf.pdr_override()
5    code='600895.ss'
6    stock = pdr.get_data_yahoo(code,'2019-01-02','2019-02-01')
7    print(stock)    # 输出内容
8    # 保存为excel和csv文件
9    stock.to_excel('D:\\stockData\\ch5\\'+code+'.xlsx')
10    stock.to_csv('D:\\stockData\ch5\\'+code+'.csv')
复制代码

从这个范例程序的代码上来看,不算复杂,从中没有见到爬取网站之类的代码。关键的是第6行,通过调用pdr.get_data_yahoo方法从雅虎网站获取数据,这个方法的参数分别是股票代码,开始日期和结束日期。第4行使用yf.pdr_override方法是为了防止雅虎网站修改获取历史数据的API接口而导致get_data_yahoo方法不可用。

在这个范例程序中获取了600895(张江高科)2019-01-02到2019-01-31的数据,可以看出,获取的数据并不包括结束日期参数当天的数据。

在第7行和第8行分别调用了to_excel和to_csv方法,把结果存入了指定目录下的文件中。这个范例程序运行后,我们首先能在控制台中看到输出,其次会在D:\stockData\ch5\目录中,看到600895.ss.xlsx和600895.ss.csv这两个保存股票数据的文件。打开600895.ss.xlsx文件,能看到如图5-4所示的数据内容,其实在控制台中和另一个csv文件中,可以看到一样的数据。

在上述范例程序中,在调用get_data_yahoo方法时,传入的股票代码带有.ss的后缀,这表示该代码是沪股的。此外,还能通过.sz的后缀来表示深股,通过.hk的后缀表示港股。如果要获取美股的数据,则直接用美股的股票代码即可。在下面的printDataByPandasDatareader.py范例程序中演示了获取美股,港股和深股相关数据的方式。

复制代码
1    # coding=utf-8
2    from pandas_datareader import data as pdr
3    import yfinance as yf
4    yf.pdr_override()
5    stockCodeList = []
6    stockCodeList.append('600007.ss')      # 沪股“中国国贸”
7    stockCodeList.append('000001.sz')     # 深股“平安银行”
8    stockCodeList.append('2318.hk')       # 港股“中国平安”
9    stockCodeList.append('IBM')         # 美股,IBM,直接输入股票代码不带后缀
10    for code in stockCodeList:
11        # 为了演示,只取一天(2019-01-02)的交易数据
12        stock = pandas_datareader.get_data_yahoo(code,'2019-01-02','2019-01-03')
13        print(stock)
复制代码

这个范例程序运行后,就能从控制台中看到输出的4个股票在指定日期内的交易情况,由于数据量比较多,本书就不罗列具体的数据了。

2 用matplotlib绘制k线和均线

K线是由开盘价、收盘价、最高价和最低价这四个要素构成。在得到上述四个值之后,首先用开盘价和收盘价绘制成一个长方形实体。随后根据最高价和最低价,把它们垂直地同长方形实体连成一条直线,这条直线就叫影线。如果再细分一下,长方形实体上方的就叫上影线,下方的就叫下影线。通过K线可以形象地记录价格变动的情况,常用的有日K线,周K线和月K线。

均线也叫移动平均线(Moving Average,简称MA),是指某段时间内的平均股价(或指数)连成的曲线,均线一般分为三类:短期、中期和长期。通常把5日和10日移动平均线称为短期均线,一般把20日、30日和60日移动平均线作为中期均线,一般120日和250日(甚至更长)移动平均线称为长期均线。

在如下的drawKAndMAMore.py范例程序中,将用到上文提到的爬取股票数据的代码,从网络接口里获取股票数据,并绘制k线和均线,请大家不仅注意k线和均线的含义,还要重视matplotlib库里绘制图形、图例和坐标轴的做法,在这本书里,对应的知识点都有详细的说明。

复制代码
1    # !/usr/bin/env python
2    # coding=utf-8
3    from pandas_datareader import data as pdr
4    import pandas as pd
5    import matplotlib.pyplot as plt
6    from mpl_finance import candlestick2_ochl
7    from matplotlib.ticker import MultipleLocator
8    import yfinance as yf
9    yf.pdr_override()
10    # 根据指定代码和时间范围获取股票数据
11    code='600895.ss'
12    stock.drop(stock.index[len(stock)-1],inplace=True)
13    # 保存在本地
14    stock.to_csv('D:\\stockData\ch7\\600895.csv')
15    df = pd.read_csv('D:/stockData/ch7/600895.csv',encoding='gbk',index_col=0)
16    # 设置窗口大小
17    fig, ax = plt.subplots(figsize=(10, 8))
18    xmajorLocator   = MultipleLocator(5)     # 将x轴主刻度设置为5的倍数
19    ax.xaxis.set_major_locator(xmajorLocator)
20    # 调用方法绘制K线图 
21    candlestick2_ochl(ax = ax, opens=df["Open"].values,closes=df["Close"].values, highs=df["High"].values, lows=df["Low"].values,width=0.75, colorup='red', colordown='green')
22    # 如下是绘制3种均线
23    df['Close'].rolling(window=3).mean().plot(color="red",label='3日均线')
24    df['Close'].rolling(window=5).mean().plot(color="blue",label='5日均线')
25    df['Close'].rolling(window=10).mean().plot(color="green",label='10日均线')
26    plt.legend(loc='best')     # 绘制图例
27    ax.grid(True)     # 带网格线
28    plt.title("600895张江高科的K线图")
29    plt.rcParams['font.sans-serif']=['SimHei']
30    plt.setp(plt.gca().get_xticklabels(), rotation=30) 
31    plt.show()
复制代码

第一,从第9行到第15行通过调用之前介绍过的get_data_yahoo方法,传入股票代码、开始时间和结束时间这三个参数,从雅虎网站中获得股票交易的数据。

第二,在第17行中调用figsize方法设置了窗口的大小。

第三,第18行和第19行的程序代码设置了主刻度是5的倍数。之所以设置成5的倍数,是因为一般一周的交易日是5天。但这里不能简单地把主刻度设置成每周一,因为某些周一有可能是股市休市的法定假日。

第四,由于无需在x轴上设置每天的日期,因此这里无需再调用plt.xticks方法,但是要调用如第30行所示的代码,设置x轴刻度的旋转角度,否则x轴显示的时间依然有可能会相互重叠。

至于绘制K线的candlestick2_ochl方法和绘制均线的rolling方法与之前drawKAndMA.py范例程序中的代码是完全一致的。

这个范例程序的运行结果如图7-5所示,从中可以看到改进后的效果。由于本次显示的股票时间段变长了(是3个月),因此与drawKAndMA.py范例程序相比,这个范例程序均线的效果更为明显,尤其是3日均线,几乎贯穿于整个时间段的各个交易日。

另外,由于在第26行通过调用plt.legend(loc=’best’)方法指定了图例将“显示在合适的位置”,因此这里的图例显示在效果更加合适的左上方,而不是drawKAndMA.py范例程序中的右上方。

3 用sklearn库的机器学习方法预测股票后市价格

在下面的predictStockByLR.py范例程序中,根据股票历史的开盘价、收盘价和成交量等特征值,从数学角度来预测股票未来的收盘价。

复制代码
1    # !/usr/bin/env python
2    # coding=utf-8
3    import pandas as pd
4    import numpy as np
5    import math
6    import matplotlib.pyplot as plt
7    from sklearn.linear_model import LinearRegression
8    from sklearn.model_selection import train_test_split
9    # 从文件中获取数据
10    origDf = pd.read_csv('D:/stockData/ch13/6035052018-09-012019-06-01.csv',encoding='gbk')
11    df = origDf[['Close', 'High', 'Low','Open' ,'Volume']]
12    featureData = df[['Open', 'High', 'Volume','Low']]
13    # 划分特征值和目标值
14    feature = featureData.values
15    target = np.array(df['Close'])
复制代码

第10行的程序语句从包含股票信息的csv文件中读取数据,在第14行设置了特征值是开盘价、最高价、最低价和成交量,同时在第15行设置了要预测的目标列是收盘价。在后续的代码中,需要将计算出开盘价、最高价、最低价和成交量这四个特征值和收盘价的线性关系,并在此基础上预测收盘价。

复制代码
16    # 划分训练集,测试集
17    feature_train, feature_test, target_train ,target_test = train_test_split(feature,target,test_size=0.05)
18    pridectedDays = int(math.ceil(0.05 * len(origDf)))     # 预测天数
19    lrTool = LinearRegression()
20    lrTool.fit(feature_train,target_train)     # 训练
21    # 用测试集预测结果
22    predictByTest = lrTool.predict(feature_test)
复制代码

第17行的程序语句通过调用train_test_split方法把包含在csv文件中的股票数据分成训练集和测试集,这个方法前两个参数分别是特征列和目标列,而第三个参数0.05则表示测试集的大小是总量的0.05。该方法返回的四个参数分别是特征值的训练集、特征值的测试集、要预测目标列的训练集和目标列的测试集。

第18行的程序语句计算了要预测的交易日数,在第19行中构建了一个线性回归预测的对象,在第20行是调用fit方法训练特征值和目标值的线性关系,请注意这里的训练是针对训练集的,在第22行中,则是用特征值的测试集来预测目标值(即收盘价)。也就是说,是用多个交易日的股价来训练lrTool对象,并在此基础上预测后续交易日的收盘价。至此,上面的程序代码完成了相关的计算工作。

复制代码
23    # 组装数据
24    index=0
25    # 在前95%的交易日中,设置预测结果和收盘价一致
26    while index < len(origDf) - pridectedDays:
27        df.ix[index,'predictedVal']=origDf.ix[index,'Close']
28        df.ix[index,'Date']=origDf.ix[index,'Date']
29        index = index+1
30    predictedCnt=0
31    # 在后5%的交易日中,用测试集推算预测股价
32    while predictedCnt<pridectedDays:
33        df.ix[index,'predictedVal']=predictByTest[predictedCnt]
34        df.ix[index,'Date']=origDf.ix[index,'Date']
35        predictedCnt=predictedCnt+1
36        index=index+1
复制代码

在第26行到第29行的while循环中,在第27行把训练集部分的预测股价设置成收盘价,并在第28行设置了训练集部分的日期。

在第32行到第36行的while循环中,遍历了测试集,在第33行的程序语句把df中表示测试结果的predictedVal列设置成相应的预测结果,同时也在第34行的程序语句逐行设置了每条记录中的日期。

复制代码
37    plt.figure()
38    df['predictedVal'].plot(color="red",label='predicted Data')
39    df['Close'].plot(color="blue",label='Real Data')
40    plt.legend(loc='best')     # 绘制图例
41    # 设置x坐标的标签
42    major_index=df.index[df.index%10==0]
43    major_xtics=df['Date'][df.index%10==0]
44    plt.xticks(major_index,major_xtics)
45    plt.setp(plt.gca().get_xticklabels(), rotation=30)
46    # 带网格线,且设置了网格样式
47    plt.grid(linestyle='-.')
48    plt.show()
复制代码

在完成数据计算和数据组装的工作后,从第37行到第48行程序代码的最后,实现了可视化。

第38行和第39行的程序代码分别绘制了预测股价和真实收盘价,在绘制的时候设置了不同的颜色,也设置了不同的label标签值,在第40行通过调用legend方法,根据收盘价和预测股价的标签值,绘制了相应的图例。

从第42行到第45行设置了x轴显示的标签文字是日期,为了不让标签文字显示过密,设置了“每10个日期里只显示1个”的显示方式,并且在第47行设置了网格线的效果,最后在第48行通过调用show方法绘制出整个图形。运行本范例程序,即可看到如图13-7所示的结果。

 

可以看出,蓝线表示真实的收盘价(图中完整的线),红线表示预测股价(图中靠右边的线。因为本书黑白印刷的原因,在书中读者看不到蓝色和红色,请读者在自己的计算机上运行这个范例程序即可看到红蓝两色的线)。虽然预测股价和真实价之间有差距,但涨跌的趋势大致相同。而且在预测时没有考虑到涨跌停的因素,所以预测结果的涨跌幅度比真实数据要大。

股票价格不仅由技术面决定,还受政策面、资金量以及消息面等诸多因素的影响,这也能解释预测结果和真实结果间有差异的原因。

4 对书的介绍和版权说明

本文给出的范例,仅是《基于股票大数据分析的Python入门实战 视频教学版》一书里的部分案例,该书京东链接:https://item.jd.com/69241653952.html

这本书包括如下的内容,是本入门python的工具书。

1 Python基本语法,集合,面向对象语法,异常处理,读写文件技能。

2 Python操作数据库的技能。

3 通过爬虫从网络接口爬取股票数据的技能。

4 基于Numpy+Pandas+Matplotlib进行数据分析的技能

5 基于TKinter的GUI编程技能+ 发送邮件的技能

6 Django框架的用法

7 线性回归+SVM的机器学习技能

这本书里,像本文那样花花绿绿能吸引人的图真不少,而且还是通过python绘制出来的,用这类比较能吸引人的案例来入门python,一定非常高效。

    本文可以转载,转载时请全文转载,别有删节,并用链接的形式给出原文链接。否则的话,可能会遇到出版社的维权。

文本相关链接:

.Net微服务实战之Kubernetes的搭建与使用 - 陈珙 - 博客园

mikel阅读(806)

来源: .Net微服务实战之Kubernetes的搭建与使用 – 陈珙 – 博客园

系列文章

前言

说到微服务就得扯到自动化运维,然后别人就不得不问你用没用上K8S。无论是概念上还是在实施搭建时,K8S的门槛比Docker Compose、Docker Swarm高了不少。我自己也经过了多次的实践,整理出一套顺利部署的流程。

我这次搭建花了一共整整4个工作实践与一个工作日写博客,中间有一个网络问题导致reset了集群重新搭了一次,完成后结合了Jenkins使用,还是成就感满满的。如果对大家有用,还请点个推荐于关注。

基本概念

Kubectl

kubectl用于运行Kubernetes集群命令的管理工具,Kubernetes kubectl 与 Docker 命令关系可以查看这里

http://docs.kubernetes.org.cn/70.html

Kubeadm

kubeadm 是 kubernetes 的集群安装工具,能够快速安装 kubernetes 集群,相关命令有以下:

kubeadm init

kubeadm join

Kubelet

kubelet是主要的节点代理,它会监视已分配给节点的pod,具体功能:

  • 安装Pod所需的volume。
  • 下载Pod的Secrets。
  • Pod中运行的 docker(或experimentally,rkt)容器。
  • 定期执行容器健康检查。

Pod

Pod是Kubernetes创建或部署的最小(最简单)的基本单位,一个Pod代表集群上正在运行的一个进程,它可能由单个容器或多个容器共享组成的资源。

一个Pod封装一个应用容器(也可以有多个容器),存储资源、一个独立的网络IP以及管理控制容器运行方式的策略选项。

Pods提供两种共享资源:网络和存储。

网络

每个Pod被分配一个独立的IP地址,Pod中的每个容器共享网络命名空间,包括IP地址和网络端口。Pod内的容器可以使用localhost相互通信。当Pod中的容器与Pod 外部通信时,他们必须协调如何使用共享网络资源(如端口)。

存储

Pod可以指定一组共享存储volumes。Pod中的所有容器都可以访问共享volumes,允许这些容器共享数据。volumes 还用于Pod中的数据持久化,以防其中一个容器需要重新启动而丢失数据。

Service

一个应用服务在Kubernetes中可能会有一个或多个Pod,每个Pod的IP地址由网络组件动态随机分配(Pod重启后IP地址会改变)。为屏蔽这些后端实例的动态变化和对多实例的负载均衡,引入了Service这个资源对象。

Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。

Type 的取值以及行为如下:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
  • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

其他详细的概念请移步到 http://docs.kubernetes.org.cn/227.html

物理部署图

 

Docker-ce 1.19安装

在所有需要用到kubernetes服务器上安装docker-ce

卸载旧版本 docker

yum remove docker docker-common docker-selinux dockesr-engine -y
升级系统软件
yum upgrade -y
安装必要的一些系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
添加docker-ce软件源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
更新并安装 docker-ce
yum makecache fast
yum install docker-ce-19.03.12 -y
添加docker国内镜像源
vim /etc/docker/daemon.json
 
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors" : [
    "http://ovfftd6p.mirror.aliyuncs.com",
    "http://registry.docker-cn.com",
    "http://docker.mirrors.ustc.edu.cn",
    "http://hub-mirror.c.163.com"
  ],
  "insecure-registries" : [
    "registry.docker-cn.com",
    "docker.mirrors.ustc.edu.cn"
  ],
  "debug" : true,
  "experimental" : true
}

启动服务

systemctl start docker
systemctl enable docker

安装kubernetes-1.18.3

所有需要用到kubernetes的服务器都执行以下指令。

添加阿里kubernetes源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
安装并启动
yum install kubeadm-1.18.3 kubectl-1.18.3 kubelet-1.18.3

启动kubelet

systemctl enable kubelet
systemctl start kubelet

在Master设置环境变量,在/etc/profile中配置
vim /etc/profile
在最后添加如下配置
export KUBECONFIG=/etc/kubernetes/admin.conf

执行命令使其起效

source /etc/profile

初始化k8s集群

在master节点(server-a)进行初始化集群

开放端口

firewall-cmd --permanent --zone=public --add-port=6443/tcp
firewall-cmd --permanent --zone=public --add-port=10250/tcp
firewall-cmd --reload
关闭swap
vim /etc/fstab
#注释swap那行
 
swapoff -a

设置iptables规则

echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

初始化

kubeadm init --kubernetes-version=1.18.3  --apiserver-advertise-address=192.168.88.138   --image-repository registry.aliyuncs.com/google_containers  --service-cidr=10.10.0.0/16 --pod-network-cidr=10.122.0.0/16 --ignore-preflight-errors=Swap

pod-network-cidr参数的为pod网段:,apiserver-advertise-address参数为本机IP。

  如果中途执行有异常可以通过 kubeadm reset 后重新init。
初始化成功执行下面指令
 mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config
查看node和pod信息
kubectl get node
kubectl get pod --all-namespaces

安装flannel组件

在master节点(server-a)安装flannel组件

找个梯子下载kube-flannel.yml文件

https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

下载不了也没关系,我复制给到大家:

---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp.flannel.unprivileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
    seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
    apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
    apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
  privileged: false
  volumes:
    - configMap
    - secret
    - emptyDir
    - hostPath
  allowedHostPaths:
    - pathPrefix: "/etc/cni/net.d"
    - pathPrefix: "/etc/kube-flannel"
    - pathPrefix: "/run/flannel"
  readOnlyRootFilesystem: false
  # Users and groups
  runAsUser:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  # Privilege Escalation
  allowPrivilegeEscalation: false
  defaultAllowPrivilegeEscalation: false
  # Capabilities
  allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
  defaultAddCapabilities: []
  requiredDropCapabilities: []
  # Host namespaces
  hostPID: false
  hostIPC: false
  hostNetwork: true
  hostPorts:
  - min: 0
    max: 65535
  # SELinux
  seLinux:
    # SELinux is unused in CaaSP
    rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
rules:
  - apiGroups: ['extensions']
    resources: ['podsecuritypolicies']
    verbs: ['use']
    resourceNames: ['psp.flannel.unprivileged']
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flannel
  namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-amd64
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - amd64
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm64
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - arm64
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - arm
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-ppc64le
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - ppc64le
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-s390x
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - s390x
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg

先拉取依赖镜像

 docker pull  quay.io/coreos/flannel:v0.12.0-amd64

把上面文件保存到服务器然后执行下面命令

kubectl apply -f kube-flannel.yml

安装dashboard

在master节点(server-a)安装dashboard组件

继续用梯子下载recommended.yml文件

https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.3/aio/deploy/recommended.yaml

没梯子的可以复制下方原文件

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: Namespace
metadata:
  name: kubernetes-dashboard

---

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-csrf
  namespace: kubernetes-dashboard
type: Opaque
data:
  csrf: ""

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-key-holder
  namespace: kubernetes-dashboard
type: Opaque

---

kind: ConfigMap
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-settings
  namespace: kubernetes-dashboard

---

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
rules:
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
    verbs: ["get", "update", "delete"]
    # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["kubernetes-dashboard-settings"]
    verbs: ["get", "update"]
    # Allow Dashboard to get metrics.
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["heapster", "dashboard-metrics-scraper"]
    verbs: ["proxy"]
  - apiGroups: [""]
    resources: ["services/proxy"]
    resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
    verbs: ["get"]

---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
rules:
  # Allow Metrics Scraper to get metrics from the Metrics server
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list", "watch"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
        - name: kubernetes-dashboard
          image: kubernetesui/dashboard:v2.0.3
          imagePullPolicy: Always
          ports:
            - containerPort: 8443
              protocol: TCP
          args:
            - --auto-generate-certificates
            - --namespace=kubernetes-dashboard
            # Uncomment the following line to manually specify Kubernetes API server Host
            # If not specified, Dashboard will attempt to auto discover the API server and connect
            # to it. Uncomment only if the default does not work.
            # - --apiserver-host=http://my-address:port
          volumeMounts:
            - name: kubernetes-dashboard-certs
              mountPath: /certs
              # Create on-disk volume to store exec logs
            - mountPath: /tmp
              name: tmp-volume
          livenessProbe:
            httpGet:
              scheme: HTTPS
              path: /
              port: 8443
            initialDelaySeconds: 30
            timeoutSeconds: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      volumes:
        - name: kubernetes-dashboard-certs
          secret:
            secretName: kubernetes-dashboard-certs
        - name: tmp-volume
          emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 8000
      targetPort: 8000
  selector:
    k8s-app: dashboard-metrics-scraper

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: dashboard-metrics-scraper
  template:
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
      annotations:
        seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
    spec:
      containers:
        - name: dashboard-metrics-scraper
          image: kubernetesui/metrics-scraper:v1.0.4
          ports:
            - containerPort: 8000
              protocol: TCP
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 8000
            initialDelaySeconds: 30
            timeoutSeconds: 30
          volumeMounts:
          - mountPath: /tmp
            name: tmp-volume
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      volumes:
        - name: tmp-volume
          emptyDir: {}
第39行修改,端口范围30000-32767
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 30221
  selector:
    k8s-app: kubernetes-dashboard

第137行开始,修改账户权限,主要三个参数,kind: ClusterRoleBinding,roleRef-kind: ClusterRole,roleRef-name: cluster-admin

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

保存到服务器后执行以下命令

kubectl apply -f recommended.yaml

等待一段时间启动成功后,https://ip+nodePort,查看UI

Token通过下面指令获取

kubectl -n kubernetes-dashboard get secret

kubectl describe secrets -n kubernetes-dashboard kubernetes-dashboard-token-kfcp2  | grep token | awk 'NR==3{print $2}'

加入Worker节点

在server-b与server-c执行下面操作

把上面init后的那句join拷贝过来,如果忘记了可以在master节点执行下面指令:

kubeadm token list

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

通过返回的数据拼装成下面指令

kubeadm join 192.168.88.138:6443 --token 2zebwy.1549suwrkkven7ow  --discovery-token-ca-cert-hash sha256:c61af74d6e4ba1871eceaef4e769d14a20a86c9276ac0899f8ec6b08b89f532b

查看节点信息

kubectl get node

部署Web应用

在master节点(sever-a)执行下面操作

部署应用前建议有需要的朋友到【.Net微服务实战之CI/CD】看看如何搭建docker私有仓库,后面需要用到,搭建后私有库后执行下面指令

kubectl create secret docker-registry docker-registry-secret --docker-server=192.168.88.141:6000 --docker-username=admin --docker-password=123456789

docker-server就是docker私有仓库的地址

下面是yaml模板,注意imagePullSecrets-name与上面的命名的一致,其余的可以查看yaml里的注释

apiVersion: apps/v1
kind: Deployment # Deployment为多个Pod副本
metadata:
  name: testdockerswarm-deployment
  labels:
    app: testdockerswarm-deployment
spec:
  replicas: 2 # 实例数量
  selector:
    matchLabels: # 定义该部署匹配哪些Pod
      app: testdockerswarm
  minReadySeconds: 3 # 可选,指定Pod可以变成可用状态的最小秒数,默认是0
  strategy:
    type: RollingUpdate # 部署策略类型,使用RollingUpdate可以保证部署期间服务不间断
    rollingUpdate:
      maxUnavailable: 1 # 部署时最大允许停止的Pod数量
      maxSurge: 1 # 部署时最大允许创建的Pod数量
  template: # 用来指定Pod的模板,与Pod的定义类似
    metadata:
      labels: # Pod标签,与上面matchLabels对应
        app: testdockerswarm
    spec:
      imagePullSecrets:
        - name: docker-registry-secret
      containers:
        - name: testdockerswarm
          image: 192.168.88.141:6000/testdockerswarm
          imagePullPolicy: Always # Always每次拉去新镜像
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: testdockerswarm-service
  labels:  
    name: testdockerswarm-service
spec:
  selector:
    app: testdockerswarm #与template-labels参数pod标签一致
  ports:
    - protocol: TCP
      port: 80 #clusterIP开放的端口
      targetPort: 80 #container开放的端口,与containerPort一致
      nodePort: 31221 # 所有的节点都会开放此端口,此端口供外部调用。
  type: NodePort

把yaml文件保存到服务器后执行下面命令

kubectl create -f testdockerswarm.yml

整个搭建部署的过程基本上到这里结束了。

访问

可以通过指令kubectl get service得到ClusterIP,分别在server-c和sever-b执行curl 10.10.184.184

也可以通过执行kubectl get pods -o wide得到pod ip,在server-c执行curl 10.122.2.5 和 server-b执行curl 10.122.1.7

也可以在外部访问 server-c和server-b的 ip + 31221

如果节点有异常可以通过下面指令排查

journalctl -f -u kubelet.service | grep -i error -C 500

如果Pod无法正常running可以通过下面指令查看

kubectl describe pod testdockerswarm-deployment-7bc647d87d-qwvzm