首页 文章 精选 留言 我的

精选列表

搜索[编译原理],共10000篇文章
优秀的个人博客,低调大师

深入了解Kafka基本原理

简介 Apache Kafka是分布式发布-订阅消息系统。它最初由LinkedIn公司开发,之后成为Apache项目的一部分。Kafka是一种快速、可扩展的、设计内在就是分布式的,分区的和可复制的提交日志服务。 Kafka架构 它的架构包括以下组件: 话题(Topic):是特定类型的消息流。消息是字节的有效负载(Payload),话题是消息的分类名或种子(Feed)名。 生产者(Producer):是能够发布消息到话题的任何对象。 服务代理(Broker):已发布的消息保存在一组服务器中,它们被称为代理(Broker)或Kafka集群。 消费者(Consumer):可以订阅一个或多个话题,并从Broker拉数据,从而消费这些已发布的消息。 Kafka存储策略 1)kafka以topic来进行消息管理,每个topic包含多个partition,每个partition对应一个逻辑log,有多个segment组成。 2)每个segment中存储多条消息(见下图),消息id由其逻辑位置决定,即从消息id可直接定位到消息的存储位置,避免id到位置的额外映射。 3)每个part在内存中对应一个index,记录每个segment中的第一条消息偏移。 4)发布者发到某个topic的消息会被均匀的分布到多个partition上(或根据用户指定的路由规则进行分布),broker收到发布消息往对应partition的最后一个segment上添加该消息,当某个segment上的消息条数达到配置值或消息发布时间超过阈值时,segment上的消息会被flush到磁盘,只有flush到磁盘上的消息订阅者才能订阅到,segment达到一定的大小后将不会再往该segment写数据,broker会创建新的segment。 Kafka数据保留策略 1)N天前的删除。 2)保留最近的多少Size数据。 Kafka broker 与其它消息系统不同,Kafka broker是无状态的。这意味着消费者必须维护已消费的状态信息。这些信息由消费者自己维护,broker完全不管(有offset managerbroker管理)。 从代理删除消息变得很棘手,因为代理并不知道消费者是否已经使用了该消息。Kafka创新性地解决了这个问题,它将一个简单的基于时间的SLA应用于保留策略。当消息在代理中超过一定时间后,将会被自动删除。 这种创新设计有很大的好处,消费者可以故意倒回到老的偏移量再次消费数据。这违反了队列的常见约定,但被证明是许多消费者的基本特征。 以下摘抄自kafka官方文档: Kafka Design 目标 1) 高吞吐量来支持高容量的事件流处理 2) 支持从离线系统加载数据 3) 低延迟的消息系统 持久化 1) 依赖文件系统,持久化到本地 2) 数据持久化到log 效率 1) 解决”small IO problem“: 使用”messageset“组合消息。server使用”chunksofmessages“写到log。consumer一次获取大的消息块。 2)解决”byte copying“: 在producer、broker和consumer之间使用统一的binary messageformat。使用系统的page cache。使用sendfile传输log,避免拷贝。 端到端的批量压缩(End-to-end Batch Compression) Kafka支持GZIP和Snappy压缩协议。 复制(Replication) 1)一个partition的复制个数(replication factor)包括这个partition的leader本身。 2)所有对partition的读和写都通过leader。 3)Followers通过pull获取leader上log(message和offset) 4)如果一个follower挂掉、卡住或者同步太慢,leader会把这个follower从”in sync replicas“(ISR)列表中删除。 5)当所有的”in sync replicas“的follower把一个消息写入到自己的log中时,这个消息才被认为是”committed“的。 6)如果针对某个partition的所有复制节点都挂了,Kafka默认选择最先复活的那个节点作为leader(这个节点不一定在ISR里)。 Leader选举 Kafka在Zookeeper中为每一个partition动态的维护了一个ISR,这个ISR里的所有replica都跟上了leader,只有ISR里的成员才能有被选为leader的可能(unclean.leader.election.enable=false)。 在这种模式下,对于f+1个副本,一个Kafka topic能在保证不丢失已经commit消息的前提下容忍f个副本的失败,在大多数使用场景下,这种模式是十分有利的。事实上,为了容忍f个副本的失败,“少数服从多数”的方式和ISR在commit前需要等待的副本的数量是一样的,但是ISR需要的总的副本的个数几乎是“少数服从多数”的方式的一半。 The Producer 发送确认 通过request.required.acks来设置,选择是否等待消息commit(是否等待所有的”in sync replicas“都成功复制了数据) Producer可以通过acks参数指定最少需要多少个Replica确认收到该消息才视为该消息发送成功。acks的默认值是1,即Leader收到该消息后立即告诉Producer收到该消息,此时如果在ISR中的消息复制完该消息前Leader宕机,那该条消息会丢失。 推荐的做法是,将acks设置为all或者-1,此时只有ISR中的所有Replica都收到该数据(也即该消息被Commit),Leader才会告诉Producer该消息发送成功,从而保证不会有未知的数据丢失。 负载均衡 1)producer可以自定义发送到哪个partition的路由规则。默认路由规则:hash(key)%numPartitions,如果key为null则随机选择一个partition。 2)自定义路由:如果key是一个user id,可以把同一个user的消息发送到同一个partition,这时consumer就可以从同一个partition读取同一个user的消息。 异步批量发送 批量发送:配置不多于固定消息数目一起发送并且等待时间小于一个固定延迟的数据。 The Consumer consumer控制消息的读取。 Push vs Pull 1) producer push data to broker,consumer pull data from broker 2) consumer pull的优点:consumer自己控制消息的读取速度和数量。 3) consumer pull的缺点:如果broker没有数据,则可能要pull多次忙等待,Kafka可以配置consumer long pull一直等到有数据。 Consumer Position 1) 大部分消息系统由broker记录哪些消息被消费了,但Kafka不是。 2) Kafka由consumer控制消息的消费,consumer甚至可以回到一个old offset的位置再次消费消息。 Consumer group 每一个consumer实例都属于一个consumer group。 每一条消息只会被同一个consumer group里的一个consumer实例消费。 不同consumer group可以同时消费同一条消息。 Consumer Rebalance Kafka consumer high level API: 如果某consumer group中consumer数量少于partition数量,则至少有一个consumer会消费多个partition的数据。 如果consumer的数量与partition数量相同,则正好一个consumer消费一个partition的数据。 如果consumer的数量多于partition的数量时,会有部分consumer无法消费该topic下任何一条消息。 Message Delivery Semantics 三种: At most once—Messages may be lost but are never redelivered. At least once—Messages are never lost but may be redelivered. Exactly once—this is what people actually want, each message is delivered once and only once. Producer:有个”acks“配置可以控制接收的leader的在什么情况下就回应producer消息写入成功。 Consumer: 读取消息,写log,处理消息。如果处理消息失败,log已经写入,则无法再次处理失败的消息,对应”At most once“。 读取消息,处理消息,写log。如果消息处理成功,写log失败,则消息会被处理两次,对应”At least once“。 读取消息,同时处理消息并把result和log同时写入。这样保证result和log同时更新或同时失败,对应”Exactly once“。 Kafka默认保证at-least-once delivery,容许用户实现at-most-once语义,exactly-once的实现取决于目的存储系统,kafka提供了读取offset,实现也没有问题。 Distribution Consumer Offset Tracking 1)High-level consumer记录每个partition所消费的maximum offset,并定期commit到offset manager(broker)。 2)Simple consumer需要手动管理offset。现在的Simple consumer Java API只支持commit offset到zookeeper。 Consumers and Consumer Groups 1)consumer注册到zookeeper 2)属于同一个group的consumer(group id一样)平均分配partition,每个partition只会被一个consumer消费。 3)当broker或同一个group的其他consumer的状态发生变化的时候,consumer rebalance就会发生。 Zookeeper协调控制 1)管理broker与consumer的动态加入与离开。 2)触发负载均衡,当broker或consumer加入或离开时会触发负载均衡算法,使得一个consumer group内的多个consumer的订阅负载平衡。 3)维护消费关系及每个partition的消费信息。 日志压缩(Log Compaction) 1)针对一个topic的partition,压缩使得Kafka至少知道每个key对应的最后一个值。 2)压缩不会重排序消息。 3)消息的offset是不会变的。 4)消息的offset是顺序的。 5)压缩发送和接收能降低网络负载。 6)以压缩后的形式持久化到磁盘。 生产者代码示例: Partitioning Code: 消费者代码示例: 关于Consumer的一个细节说明: topicCountMap.put(topic, new Integer(a_numThreads)); 这里,如果提供的thread数目(a_numThreads)大于这个topic的partition的数目,有些thread会永远读不到消息。 如果如果提供的thread数目(a_numThreads)小于这个topic的partition的数目,有些thread会从多个partition读到消息。 如果一个线程从多个partition读取消息,无法保证的消息的顺序,只能保证从同一个partition读取到的消息是顺序的。 增加更多的进程/线程消费消息,会导致Kafka re-balance,可能会改变Partition和消费Thread的对应关系。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:744677563 本群提供免费的学习指导 架构资料 以及免费的解答 不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

优秀的个人博客,低调大师

Kotlin里的Extension Functions实现原理分析

Kotlin里的Extension Functions Kotlin里有所谓的扩展函数(Extension Functions),支持给现有的java类增加函数。 https://kotlinlang.org/docs/reference/extensions.html 比如给String增加一个hello函数,可以这样子写: fun String.hello(world : String) : String { return "hello " + world + this.length; } fun main(args: Array<String>) { System.out.println("abc".hello("world")); } 可以看到在main函数里,直接可以在String上调用hello函数。

优秀的个人博客,低调大师

Java Token的原理和生成使用机制

在此之前我们先了解一下什么是Cookie、Session、Token 1、什么是Cookie? cookie指的就是浏览器里面能永久存储数据的一种数据存储功能。cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。 Cookie有什么功能特点呢?在同一个页面中设置 Cookie,实际上是按从后往前的顺序进行的。如果要先删除一个 Cookie,再写入一个 Cookie,则必须先写写入语句,再写删除语句,否则会出现错误 。 Cookie是面向路径的。缺省路径 (path) 属性时,Web 服务器页会自动传递当前路径给浏览器,指定路径强制服务器使用设置的路径。在一个目录页面里设置的 Cookie 在另一个目录的页面里是看不到的 。 Cookie 必须在 HTML 文件的内容输出之前设置;不同的浏览器 (Netscape Navigator、Internet Explorer) 对 Cookie 的处理不一致,使用时一定要考虑;客户端用户如果设置禁止 Cookie,则 Cookie 不能建立。 并且在客户端,一个浏览器能创建的 Cookie 数量最多为 300 个,并且每个不能超过 4KB,每个 Web 站点能设置的 Cookie 总数不能超过 20 个 。 Cookie的生命周期呢?:Cookie可以保持登录信息到用户下次与服务器的会话,换句话说,下次访问同一网站时,用户会发现不必输入用户名和密码就已经登录了(当然,不排除用户手工删除Cookie)。而还有一些Cookie在用户退出会话的时候就被删除了,这样可以有效保护个人隐私。Cookie在生成时就会被指定一个Expire值,这就是Cookie的生存周期,在这个周期内Cookie有效,超出周期Cookie就会被清除。有些页面将Cookie的生存周期设置为“0”或负值,这样在关闭浏览器时,就马上清除Cookie,不会记录用户信息,更加安全。 2、什么是session? session就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。 session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识sessionID”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识sessionID”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识sessionID”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie 的方式。 服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。 JSP使用一个叫HttpSession的对象实现同样的功能。HTTPSession 是一个建立在cookies 和URL-rewriting上的高质量的界面。Session的信息保存在服务器端, Session的id保存在客户机的cookie中。事实上,在许多服务器上,如果浏览器支持的话它们就使用cookies,但是如果不支持或废除了的话就自动转化为URL-rewriting, session自动为每个流程提供了方便地存储信息的方法。 3、Httpsession具有如下API: getId 此方法返回唯一的标识,这些标识为每个session而产生。当只有一个单一的值与一个session联合时,或当日志信息与先前的sessions有关时,它被当作键名用。 GetCreationTime 返回session被创建的时间。最小单位为千分之一秒。为得到一个对打印输出很有用的值,可将此值传给Date constructor 或者GregorianCalendar的方法setTimeInMillis. GetLastAccessedTime 返回session最后被客户发送的时间。最小单位为千分之一秒。 GetMaxInactiveInterval 返回总时间(秒),负值表示session永远不会超时。 getAttribute 取一个session相联系的信息。(在jsp1.0中为 getValue) Integer item = (Integer) session.getAttribute("item") //检索出session的值并转化为整型 setAttribute 提供一个关键词和一个值。会替换掉任何以前的值。(在jsp1.0中为putValue) session.setAttribute("ItemValue", itemName); // ItemValue 必须不是must简单类型 在应用中使用最多的是getAttribute和setAttribute. 4、传统身份验证(session) HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。 解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID 号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里, 下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie ,这样服务端会验证一个这个 Cookie 里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。 上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 5、什么是Token? Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。 基于 Token 的身份验证 使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。流程是这样的: 客户端使用用户名跟密码请求登录 服务端收到请求,去验证用户名与密码 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据 APP登录的时候发送加密的用户名和密码到服务器,服务器验证用户名和密码,如果成功,以某种方式比如随机生成32位的字符串作为token,存储到服务器中,并返回token到APP,以后APP请求时, 凡是需要验证的地方都要带上该token,然后服务器端验证token,成功返回所需要的结果,失败返回错误信息,让他重新登录。其中服务器上token设置一个有效期,每次APP请求的时候都验证token和有效期。 6、由此我们可以扩展一个问题: 服务器上的token存储到数据库中,每次查询会不会很费时。如果不存储到数据库,应该存储到哪里呢。 客户端得到的token肯定要加密存储的,发送token的时候再解密。存储到数据库还是配置文件好呢 token是个易失数据,丢了无非让用户重新登录一下,可以放到 MSSQL/MySQL 的内存表里(不过据说mysql的内存表性能提升有限),可以放到 Memcache里,可以放到redis里,放到 OpenResty 的变量字典里(只要你有信心不爆内存)。 你认为用数据库来保持token查询时间太长,会成为你系统的瓶颈或者隐患,可以放在内存当中。 比如memcached、redis,KV方式很适合你对token查询的需求。 这个不会太占内存,比如你的token是32位字符串,要是你的用户量在百万级或者千万级,那才多少内存。 要是数据量真的大到单机内存扛不住,或者觉得一宕机全丢风险大,只要这个token生成是足够均匀的,高低位切一下分到不同机器上就行,内存绝对不会是问题。 7、Token安全问题 在存储的时候把token进行对称加密存储,用时解开。 将请求URL、时间戳、token三者进行合并加盐签名,服务端校验有效性。 这两种办法的出发点都是:窃取你存储的数据较为容易,而反汇编你的程序hack你的加密解密和签名算法是比较难的。然而其实说难也不难,所以终究是防君子不防小人的做法。话说加密存储一个你要是被人扒开客户端看也不会被喷明文存储…… 方法1它拿到存储的密文解不开、方法2它不知道你的签名算法和盐,两者可以结合食用。 但是如果token被人拷走,他自然也能植入到自己的手机里面,那到时候他的手机也可以以你的身份来用着,这你就瞎了。 于是可以提供一个让用户可以主动expire一个过去的token类似的机制,在被盗的时候能远程止损。 在网络层面上token明文传输的话会非常的危险,所以建议一定要使用HTTPS,并且把token放在post body里 8、基于JWT的Token认证机制实现 JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其JWT的组成一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。 载荷(Payload) { "iss": "Online JWT Builder", "iat": 1416797419, "exp": 1448333419, "aud": "www.example.com", "sub": "jrocket@example.com", "GivenName": "Johnny", "Surname": "Rocket", "Email": "jrocket@example.com", "Role": [ "Manager", "Project Administrator" ] } iss: 该JWT的签发者,是否使用是可选的; sub: 该JWT所面向的用户,是否使用是可选的; aud: 接收该JWT的一方,是否使用是可选的; exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的; iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的; 其他还有:nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的; 将上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。 eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9 小知识:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和解码 头部(Header) JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。 { "typ": "JWT", "alg": "HS256" } 在头部指明了签名算法是HS256算法。当然头部也要进行BASE64编码,编码后的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 签名(Signature) 将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0 最后,我们将上面拼接完的字符串用HS256算法进行加密。 在加密的时候,我们还需要提供一个密钥(secret)。如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容:rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM 最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM 在我们的请求URL中会带上这串JWT字符串: https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM 9、认证过程 下面我们从一个实例来看如何运用JWT机制实现认证: 登录 第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交后到服务器的登录处理的Action层(Login Action); Login Action调用认证服务进行用户名密码认证,如果认证通过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息); 返回用户信息后,Login Action从配置文件中获取Token签名生成的秘钥信息,进行Token的生成; 生成Token的过程中可以调用第三方的JWT Lib生成签名后的JWT数据; 完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登录过程; 请求认证 基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中; 客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API); 认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找; 如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码; 完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证; 全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断; 如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401; 对Token认证的五点认识直接注意的地方: 一个Token就是一些信息的集合; 在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率; 服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查; 基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端; 因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的 10、JWT的JAVA实现 Java中对JWT的支持可以考虑使用JJWT开源库;JJWT实现了JWT, JWS, JWE 和 JWA RFC规范;下面将简单举例说明其使用: 生成Token import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; import io.jsonwebtoken.*; import java.util.Date; //Sample method to construct a JWT private String createJWT(String id, String issuer, String subject, long ttlMillis) { //The JWT signature algorithm we will be using to sign the token SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //We will sign our JWT with our ApiKey secret byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret()); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //Let's set the JWT Claims JwtBuilder builder = Jwts.builder().setId(id) .setIssuedAt(now) .setSubject(subject) .setIssuer(issuer) .signWith(signatureAlgorithm, signingKey); //if it has been specified, let's add the expiration if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } //Builds the JWT and serializes it to a compact, URL-safe string return builder.compact(); } 解码和验证Token码 import javax.xml.bind.DatatypeConverter; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Claims; //Sample method to validate and read the JWT private void parseJWT(String jwt) { //This line will throw an exception if it is not a signed JWS (as expected) Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret())) .parseClaimsJws(jwt).getBody(); System.out.println("ID: " + claims.getId()); System.out.println("Subject: " + claims.getSubject()); System.out.println("Issuer: " + claims.getIssuer()); System.out.println("Expiration: " + claims.getExpiration()); } 基于JWT的Token认证的安全问题 如何保证用户名/密码验证过程的安全性;因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。 11、Spring实现Token拦截机制 1、JWT 简介 组成部分: Header 头部:一般为token类型和加密算法 Payload 负载:一些用户信息和额外的声明数据 Signature 签名:签名需要使用编码后的header和payload以及一个秘钥 (很安全),前两段的结合加密 2:jar包依赖 <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency> 3:jwt加密和解密的工具类 package com.alienlab.news.utils; import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.util.Date; /** * Created by Msater Zg on 2017/3/13. jwt实现方式 */ public class JwtUtils { public static Claims parseJWT(String jsonWebToken, String base64Security) { try { Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security)) .parseClaimsJws(jsonWebToken).getBody(); return claims; } catch (Exception ex) { return null; } } 前三个参数为自己用户token的一些信息比如id,权限,名称等。不要将隐私信息放入(大家都可以获取到) public static String createJWT(String name, String userId, String role, String audience, String issuer, long TTLMillis, String base64Security) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //生成签名密钥 就是一个base64加密后的字符串? byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary (base64Security); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm. getJcaName()); JSONObject jsonObject = new JSONObject(); jsonObject.put("userName", name); jsonObject.put("userLoginName", userId); //添加构成JWT的参数 JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT") .setIssuedAt(now) //创建时间 .setSubject(jsonObject.toString()) //主题,也差不多是个人的一些信息 .setIssuer(issuer) //发送谁 .setAudience(audience) //个人签名 .signWith(signatureAlgorithm, signingKey); //估计是第三段密钥 //添加Token过期时间 if (TTLMillis >= 0) { //过期时间 long expMillis = nowMillis + TTLMillis; //现在是什么时间 Date exp = new Date(expMillis); //系统时间之前的token都是不可以被承认的 builder.setExpiration(exp).setNotBefore(now); } //生成JWT return builder.compact(); } } 4:使用的条件(该接口允许跨域 cors来配置跨域) 1:cors配置允许跨域 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.ArrayList; import java.util.List; /** * Created by Msater Zg on 2017/4/3. */ @Configuration public class CorsConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "DELETE", "PUT") .maxAge(3600); } private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); List<String> list = new ArrayList<>(); list.add("*"); corsConfiguration.setAllowedOrigins(list); corsConfiguration.addAllowedOrigin("*"); // 1 corsConfiguration.addAllowedHeader("*"); // 2 corsConfiguration.addAllowedMethod("*"); // 3 return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); } } 2:拦截器拦截方法获取token 1:拦截器配置 import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by Msater Zg on 2017/4/5. *拦截器 */ public class ApiInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截了"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截了"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截了"); } } 2: 拦截器管理工具 import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * Created by Msater Zg on 2017/4/5. *拦截器管理工具 */ @Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { //多个拦截器组成一个拦截器链 // addPathPatterns用于添加拦截规则 // excludePathPatterns用户排除拦截 registry.addInterceptor(new ApiInterceptor()).addPathPatterns("/**"); //对来自/user/** 这个链接来的请求进行拦截 super.addInterceptors(registry); } } 5:token的发送与获取 ajax为例子:beforeSend:function(request) { // token,为登陆时获取到 request.setRequestHeader("token",token); }, 后台获取:request.getHeader("token"); 6:token验证机制 1:通过token解密是否成功可以判断token是否正确或者是否过期 2:解密完成,可以对比用户属性或者用户的固定token(缓存中或者放入数据库) 12、生成Token app登陆验证不能使用session来判断了这里,我定义了一个token类来存储token。 就是一个字符串+创建的时间戳。然后定义一个管理类来维护token。简单的实现了,但还有很多问题。比如,我对session的理解(是否可以放session,放session之后什么状态), 比如这定义的这个类在调用的时候加载,在不用的时间结束,而我希望一直存在,这个维护类怎么确保存在,这是类的声明周期问题,比如加载到内存和缓存的实现,缓存用的太少。 Token.java package com.tixa.wedding.util; import java.io.Serializable; public class Token implements Serializable { /** * @Fields serialVersionUID : TODO */ private static final long serialVersionUID = -754659525548951914L; private String signature; private long timestamp; public Token(String signature, long timestamp) { if (signature == null) throw new IllegalArgumentException("signature can not be null"); this.timestamp = timestamp; this.signature = signature; } public Token(String signature) { if (signature == null) throw new IllegalArgumentException("signature can not be null"); this.signature = signature; } /** * Returns a string containing the unique signatureentifier assigned to this token. */ public String getSignature() { return signature; } public long getTimestamp() { return timestamp; } /** * timestamp 不予考虑, 因为就算 timestamp 不同也认为是相同的 token. */ public int hashCode() { return signature.hashCode(); } public boolean equals(Object object) { if (object instanceof Token) return ((Token)object).signature.equals(this.signature); return false; } @Override public String toString() { return "Token [signature=" + signature + ", timestamp=" + timestamp + "]"; } } TokenUtil.java package com.tixa.wedding.util; import java.security.MessageDigest; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; public class TokenUtil { private static final int INTERVAL = 7;// token过期时间间隔 天 private static final String YAN = "testMRf1$789787aadfjkds//*-+'[]jfeu;384785*^*&%^%$%";// 加盐 private static final int HOUR = 3;// 检查token过期线程执行时间 时 private static Logger logger = Logger.getLogger("visit"); private static Map<Integer, Token> tokenMap = new HashMap<Integer, Token>(); private static TokenUtil tokenUtil = null; static ScheduledExecutorService scheduler =Executors.newSingleThreadScheduledExecutor(); static { logger.info("\n===============进入TokenUtil静态代码块=================="); listenTask(); } public static TokenUtil getTokenUtil() { if (tokenUtil == null) { synInit(); } return tokenUtil; } private static synchronized void synInit() { if (tokenUtil == null) { tokenUtil = new TokenUtil(); } } public TokenUtil() { } public static Map<Integer, Token> getTokenMap() { return tokenMap; } /** * 产生一个token */ public static Token generateToken(String uniq,int id) { Token token = new Token(MD5(System.currentTimeMillis()+YAN+uniq+id), System.currentTimeMillis()); synchronized (tokenMap) { tokenMap.put(id, token); } return token; } /** * @Title: removeToken * @Description: 去除token * @param @param nonce * @param @return 参数 * @return boolean 返回类型 */ public static boolean removeToken(int id) { synchronized (tokenMap) { tokenMap.remove(id); logger.info(tokenMap.get(id) == null ? "\n=========已注销========": "\n++++++++注销失败+++++++++++++++"); } return true; } /** * @Title: volidateToken * @Description: 校验token * @param @param signature * @param @param nonce * @param @return 参数 * @return boolean 返回类型 */ public static boolean volidateToken(String signature, int id) { boolean flag = false; Token token = (Token) tokenMap.get(id); if (token != null && token.getSignature().equals(signature)) { logger.info("\n=====已在线======="); flag = true; } return flag; } /** * * @Title: MD5 * @Description: 加密 * @param @param s * @param @return 参数 * @return String 返回类型 */ public final static String MD5(String s) { try { byte[] btInput = s.getBytes(); // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 mdInst.update(btInput); // 获得密文 return byte2hex(mdInst.digest()); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将字节数组转换成16进制字符串 * @param b * @return */ private static String byte2hex(byte[] b) { StringBuilder sbDes = new StringBuilder(); String tmp = null; for (int i = 0; i < b.length; i++) { tmp = (Integer.toHexString(b[i] & 0xFF)); if (tmp.length() == 1) { sbDes.append("0"); } sbDes.append(tmp); } return sbDes.toString(); } /** * @Title: listenTask * @Description: 定时执行token过期清除任务 * @param 参数 * @return void 返回类型 */ public static void listenTask(){ Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); //定制每天的HOUR点,从明天开始 calendar.set(year, month, day+1, HOUR, 0, 0); // calendar.set(year, month, day, 17, 11, 40); Date date = calendar.getTime(); scheduler.scheduleAtFixedRate( new ListenToken(), (date.getTime()-System.currentTimeMillis())/1000, 60*60*24, TimeUnit.SECONDS); } /** * @ClassName: ListenToken * @Description: 监听token过期线程runnable实现 * @author mrf * @date 2015-10-21 下午02:22:24 * */ static class ListenToken implements Runnable { public ListenToken() { super(); } public void run() { logger.info("\n**************************执行监听token列表****************************"); try { synchronized (tokenMap) { for (int i = 0; i < 5; i++) { if (tokenMap != null && !tokenMap.isEmpty()) { for (Entry<Integer, Token> entry : tokenMap.entrySet()) { Token token = (Token) entry.getValue(); logger.info("\n==============已登录用户有:"+entry + "====================="); // try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} int interval = (int) ((System.currentTimeMillis() - token.getTimestamp()) / 1000 / 60 / 60 / 24); if (interval > INTERVAL) { tokenMap.remove(entry.getKey()); logger.info("\n==============移除token:" + entry+ "====================="); } } } } } } catch (Exception e) { logger.error("token监听线程错误:"+e.getMessage()); e.printStackTrace(); } } } public static void main(String[] args) { System.out.println(generateToken( "s",1)); System.out.println(generateToken( "q",1)); System.out.println(generateToken( "s3",2)); System.out.println(generateToken( "s4",3)); System.out.println(removeToken(3)); System.out.println(getTokenMap()); } }

优秀的个人博客,低调大师

GTS解密--GTS的原理、架构与特点

作者 赵成 日期 2018-01-01 标签 分布式事务 , GTS , Global Transaction Service, 柔性事务, TCC , XA两阶段提交协议 全局事务服务(Global Transaction Service,简称 GTS)是阿里新推出的分布式事务处理方案,对其深入分析的资料相对匮乏。本文的目标是剖析GTS的技术路线,厘清其优势与约束。文章参考了GTS公开的专利、产品文档、相关网页,文章中肯定有不准确的地方,欢迎各位同学拍砖与指正。 一、GTS的目标 GTS是一个面向互联网交易场景的分布式事务解决方案。 制约分布式事务的三个因素 分布式事务是互联网交易场景面临的关键问题之一。不同于搜索、社交、联机分析应用,电子商务、支付是典型的交易场景,数据的错误会带来严重的后果,对数据的一致性与可用性有很高的要求。互联网环境带来了海量的数据容量、连接数与访问量,单一数据库节点无法应对,成为整个系统的瓶颈。为解决单一数据库成为瓶颈的问题,通过数据拆分实现数据库能力的线性扩展。数据拆分是使用分库分表的方式,将数据存储在多个数据库节点,利用分布式数据库平台解决数据库瓶颈的问题。分布式数据库环境中,一个事务会跨越多个数据库,面临分布式事务处理的问题。 分布式事务解决方案面临应用灵活性、数据一致性、性能三者的挑战。目前已有多种成熟方案,每种方案都是对这三个方面做出的取舍。 相互制约的三个因素为: 应用灵活性:应用访问数据的方式是否需要修改,以及修改的程度。 一致性:数据是强一致,还是最终一致的(允许中间不一致的状态)。 系统性能:分布式事务对整体性能的影响。 现有分布式处理方案 现有成熟的分布式解决方案包括XA两阶段提交、可靠消息与TCC模式等类型。XA两阶段提交属于强一致事务,可靠消息与TCC模式属于柔性事务。 XA两阶段提交 XA 是指由 X/Open 组织提出的分布式事务处理的规范。XA规范主要定义了Transaction Manager(TM)和Resource Manager(RM)之间的接口,结构如下图所示。 XA协议的流程可大致分为三个步骤: 步骤1:APP向TM创建全局事务,TM向APP返回全局事务号。 步骤2:APP使用全局事务号,访问RM的资源(当RM为数据库时,资源访问就是SQL操作)。当RM第一次收到访问时,使用该全局事务号向TM注册,TM返回事务分支事务号。 步骤3:APP向TM发出全局事务提交请求,TM与参与事务的RM通信,进行提交处理,全部完成后,向APP返回结果。 TM与RM之间的提交处理,采用两阶段提交协议。TM在第一阶段对所有的参与事务的RM请求“预备”操作,达成关于分布式事务一致性的共识。事务参与者必须完成所有的约束检查,并且确保后续提交或放弃时所需要的数据已持久化。在第二阶段,根据之前达到的提交或放弃的共识,请求所有参事务的RM完成相应的操作。 提交事务的过程中需要在多个资源节点之间进行协调,而各节点对锁资源的释放必须等到事务最终提交时,所以两阶段提交在执行同样的事务时会比一阶段提交消耗更多的时间。当事务并发量达到一定数量时,就会出现大量事务积压甚至出现死锁,系统性能和处理吞吐量就会严重下滑。 可靠消息 可靠消息的一种可能实现的结构如下图。 说明: 业务处理服务在业务事务提交前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送。 业务处理服务在业务事务提交后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送消息。 业务处理服务在业务事务回滚后,向实时消息服务取消发送。 消息状态确认系统定期找到未确认发送或回滚发送的消息,向业务处理服务询问消息状态,业务处理服务根据消息ID或消息内容确定该消息是否有效。 通过消息进行事务异步的方式,可以保证业务数据操作和消息的发送同时执行成功或失败,保持了事务的最终一致性。 采用可靠消息的方式,在两个事务间实现分布式事务时,可以很好地满足事务最终一致性以及事务的回滚,但如果一个事务上下文中超过两个事务操作后,需要开发人员实现整个事务流程的操作日志的记录、每个事务分支的回滚以及整个流程的准确调度。 TCC模式 TCC模式为全局事务执行提供了一个框架,开发人员只需要实现每个事务分支的回滚,不需要记录整个事务流程的操作日志。TCC模式结构如下图。 说明: 一个完整的业务活动由一个主业务服务与若干从业务服务组成。 主业务服务负责发起并完成整个业务活动。 从业务服务提供TCC型业务操作。 业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在业务活动提交时确认所有的TCC型操作的confirm操作,在业务活动取消时调用所有TCC型操作的cancel操作。 TCC业务包括两个阶段完成: 第一阶段:主业务服务分别调用所有从业务的 try 操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的 try 操作都调用成功或者某个从业务服务的 try 操作失败,进入第二阶段。 第二阶段:活动管理器根据第一阶段的执行结果来执行 confirm 或 cancel 操作。如果第一阶段所有 try 操作都成功,则活动管理器调用所有从业务活动的 confirm操作。否则调用所有从业务服务的 cancel 操作。 小结 可靠消息与TCC模式通过避免XA两阶段提交对数据资源的长期锁定提升了性能,通过在数据库外部实现事务机制达到了最终一致性,但牺牲了应用灵活性,需要开发人员实现事务检查与回滚的细节,面临着花费大量精力保证应用正确性的问题。 GTS目标是在性能开销可接受的情况下,由GTS统一处理全局事务的故障恢复与并发控制,对应用开发屏蔽事务处理的细节,从而提升应用的灵活性与数据的一致性。 二、GTS的技术路线 GTS采用基于XA架构优化的技术路线,在保留XA架构灵活性的优点下,通过将XA提交中的第一阶段与第二阶段解耦,将提交过程转换为第一阶段本地事务提交+第二阶段异步清理的方式,从而提供提升系统性能,同时通过在GTS内部维护应用级别的日志与锁信息,实现了全局事务的回滚与并发控制。 GTS方案认为XA性能低效的根本原因是采用了阻塞协议。在分布式事务提交的第一阶段等待最慢的一个事务分支完成,即使在不存在锁冲突的情况下,各事务分支的数据库连接依然会被挂起所占用的资源都不能够释放,以防止全局事务提交前释放资源所造成的数据不一致。对于业务流量极高的大规模互联网企业,难以接受 XA 两阶段提交协议所带来的巨大性能开销。 GTS架构包含的组件与XA完全相同,示意架构如下图。 GTS全局事务处理流程与XA一致,也包括全局事务注册、数据访问与全局事务提交三个步骤,但在第二步与第三步的内部处理上与XA不同: 第二步数据访问中,各事务分支完成数据操作的同时,会将全局事务信息(锁与日志信息)存储在当前数据库的表中。 第三步全局事务提交中,采用一阶段本地事务提交+二阶段异步清理的方式。首先对各数据库做本地事务的提交,并释放数据库连接等系统资源,然后,向TM发出全局事务提交请求,TM收到请求后,立即返回成功,TM后续实际工作是对各个数据库使用全局事务标识符进行全局事务信息的清理。 GTS与XA在全局事务的故障恢复处理与并发控制采用了不同的实现机制: XA两阶段协议是基于数据库内核的日志与锁信息实现全局事务的回滚与并发控制。由于GTS一阶段本地事务提交中,会直接提交本地事务并释放连接,此时数据库内核的日志与锁表对全局事务不再有效。在第二步中,GTS会将日志和锁信息存储在表中,当事务本地提交后,日志和锁信息被持久化保存,用于实现全局事务的并发控制与故障恢复。 GTS的故障恢复只有UNDO操作没有REDO操作,日志表中存储了UNDO需要的信息,包括行记录标识、全局事务号、镜像查询语句、操作的前像与操作的后像。当发生故障时,对于已经本地提交的数据库,从UNDO表中找到修改的记录,记录的操作前像和操作后像,使用镜像查询语句从数据库中读取该记录的当前值。如果当前值与记录操作后像相同,则直接使用操作前像进行恢复,否则报警,进行人工处理。 GTS的全局锁表中存储了记录的加锁信息。封锁的粒度是行(记录),锁的类型包括共享锁和互斥锁,对于同一个记录,加锁的规则是共享锁与共享锁不冲突,共享锁与互斥锁冲突、互斥锁与互斥锁冲突。对插入(INSERT)、修改(UPDATE)、删除(DELETE)、更新模式的锁定查询(SELECT… FOR UPDATE) 操作加互斥锁。对于共享模式的锁定查询 (SELECT…LOCK IN SHARE MODE) 操作加共享锁。若没有锁冲突,在GTS锁表中,增加一行记录,表示加锁成功。 GTS的默认隔离级别为读未提交(脏数据),使用SELECT… FOR UPDATE和SELECT…LOCK IN SHARE MODE,可使查询隔离级别提升至读已提交。 三、GTS的架构与处理流程 架构 下图描述了GTS一种可能的实现架构。 与XA架构相同,GTS架构由应用、事务管理器、资源管理器三个部分组成。资源管理器由事务分支处理模块、镜像查询构造模块、并发控制模块、恢复控制模块,以及存储在数据库中的GTS事务信息(GTS锁表与GTS日志表)等组成。 事务分支处理模块:是资源管理器的外部接口,并完成内部各模块的调用。 镜像查询构造模块:从Insert、Update、Delete语句,生成该操作对应记录集的镜像查询语句。例如table_name表包含两个字段column1和column2,column1为主键,则镜像查询语句为select column1, column2 from table_name where column1=v1。 并发控制模块:基于GTS事务锁表,维护读写并发控制。锁表定义如下: 字段名 字段类型 字段描述 ID 整数 自增主键 TABLE_NAME 字符串 表名 KEY_VALUE 整数 数据行ID XID 字符串 全局事务标识 XLOCK 整数 互斥锁标记 SLOCK 整数 共享锁标记 BRANCH_ID 整数 事务分支标识 恢复控制模块:基于GTS日志表,进行故障恢复。 日志表定义如下: 字段名 字段类型 字段描述 ID 整数 自增主键 GMT_CREATE 时间 创建时间 GMT_MODIFIED datetime 修改时间 XID 整数 全局事务ID BRANCH_ID 整数 分支事务ID ROLLBACK_INFO longblob 查询语句、前像与后像 STATUS 整数 状态 SERVER 字符串 分支所在DB IP 主要流程序列图 分别描述了insert/delete/update操作、读已提交操作、提交操作和回滚操作等四个操作的序列图(一种可能的实现方式)。 insert/delete/update操作流程序列图 读已提交操作流程序列图 提交操作流程序列图 回滚操作流程序列图 阿里官方案例 GTS产品网站给出了一个交易类事务中最典型的转账案例 A和B两个用户的数据分别位于一个DRDS实例的两个不同分库中,用50个进程并发进行 A转账给3,每个进程转账10次,每次转账金额在1到10之间随机生成,转账过程中模拟了3%的网络异常,使用GTS事务保证了A和B钱的总数不变。 从代码上可看出,只需增加一条开启GTS的sql语句,就将单机事务应用提升至分布式事务,体现出很好的应用灵活性。测试中转账事务执行500次,成功490次,失败10次。转账结束10秒后,查询账户金额总数正确。 2017云栖大会 GTS产品介绍中,给出了使用GTS与不使用事务(1PC)测试对比。下图,GTS比1PC的性能损耗在10%,远远小于2PC方式,表现出优异的性能。 四、GTS的优势与约束 与基于消息队列与TCC补偿模式的分布式事务相比,在性能满足的情况下,GTS更好的应用灵活性与数据一致性: 灵活性:数据库应用基本实现零修改,同时,基于XA模型,可方便的支持消息队列数据库等多种RM。 数据一致性:GTS 的缺省事务隔离级别为读未提交,该模式下可以达到分布式事务的最大性能,但可能会读到脏数据。对于一致性要求高的应用,在性能允许的情况下,可以采用已提交读语句(for update、lock in share mode)将隔离级别提升至读已提交。 根据GTS实现机制的特点,其应用场景上有以下约束:加锁操作记录数量不能太大,操作冲突不能太多,加锁时间不能太长。违法以上约束时,GTS内部会占用过多资源、锁冲突和回滚增加,导致性能的下降。电商、物流、金融、零售行业中的核心交易场景有着高并发,高性能,单次操作数据集小,事务响应时间敏感的特点,GTS类方案在此类场景中有着广泛和良好的应用前景。 五、参考 书 《企业IT架构转型之道》第六章 专利 CN201510111872-一种分布式事务处理方法及系统 CN201510111986-一种分布式事务处理方法及系统 网页 破解世界性技术难题! GTS让分布式事务简单高效 分布式事务云市场分析 分布式事务中间件GTS(TXC) GTS for DRDS分布式事务的实现理解 GTS全局事务测试-单DRDS跨库事务 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

优秀的个人博客,低调大师

Docker入门1:什么是Docker与原理

1.1Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化 开源项目,诞生2013年,给予GO语言实现,后加入LINUX基金会遵循的Apache2.0协议。 红帽在6.5中集中支持docker.组成架构:c/s 基本概念: 镜像Image Docker镜像就是一个只读的模板 例如:一个镜像可以包含一个完整的redflag操作系统环境,里面仅安装了Apache或用户需要的其它应用程序. 镜像可以用来创建Docker容器.Docker提供了一个很简单的机制来创建镜像或者更新现有的镜像, 用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用 容器Container Docker利用容器运行应用 容器是从镜像创建的运行实例,它可以被启动、开始、停止、删除。每个容器都是相互隔离的,保证安装的平台 可以把容器看做是一个简易版的Linux环境,(包括root用户权限、进程空间、用户空间和网络空间等) 和运行在其中的应用程序 注:镜像是只读的,容器在启动的时候创建一层可写层作为最上层 仓库Repository 仓库是集中存放镜像文件的场所,有时候会把仓库和仓库注册服务器混为一谈,并不严格区分。 实际上,仓库注册服务器上往往存放着多个仓库,每个仓库中又凶信了多个镜像,每个镜像有不同的标签 仓库分为公开仓库(Public)和私有仓库(Private)两种形式 用户可以在本地网络内创建一个私有仓库 当用户创建了自己的镜像之后就可以使用push命令将它上传到公有或者私有仓库, 这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上pull下来就可以了 1.2 DOCKER如何实现资源隔离 LXC 通过LXCLinux的容器技术,具体使用的是内核Kernel中的namespace PID namespace PID NET namespace 网络 IPC namespace IPC MNT namespace 挂载 UTS namespace hostname User namespace 用户 1.3资源限制 cgroup CPU+内存 docker run -it --rm -c 512 stress --cpu 1 默认1024权重调成512 占用比66%+33%=1 docker run -it --rm -cpuset-cpus=0 stress--cpu 1 使用第0颗CPU docker run -it --rm -m 128m stress --cpu 1--vm-bytes 128m --vm-hang 0 设置内存为128M运行 1.4网 络模式 1桥接br brctl show 默认 容器IP被分配成172.16.1.0段的网络环境,按着启动顺序分配 2host 3container 4none 2与KVM和XEN对比: Docker虚拟化方案: 服务器--系统--容器--命令库--APP KVM和XEN传统虚拟化方案:服务器-系统--管理KVM或XEN--系统--命令库--APP 2与openstack对比: 类别 Docker openstatck 安装 非常简单 组件多,部署复杂 启动速度 秒级别 分钟级别 性能 和物理系统几乎一样 vm会占用一些资源 镜像体积 MB GB 隔离性 隔离性高 彻底隔离 可管理性 单进程,不建议启动SSH 完整的系统管理 网络连接 比较弱 借助NEUTRON 3为什么要用Docker 应用场景: 1 更快速的交付和部署,一次创建或配置,到处都能运行,简化配置,提高开发效率,快速部署 2 更高效的虚拟化,内核级别的虚拟化 3 更轻松的迁移和扩展,兼容平台多,方便移植 4 更简单的管理,只要做小小的修改就能,替代以往大量的更新工作,修改作为增量被分发和更新 5服务器隔离,组合服务,多租户,代码流水线管理 4Docker运行环境: Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上,需要内核版本是 2.6.32-431 或者更高版本 查看内核版本:uname -r 本文转自 OpenStack2015 博客,原文链接: http://blog.51cto.com/andyliu/1857653 如需转载请自行联系原作者

优秀的个人博客,低调大师

LAMP架构(apache安装,apache工作原理介绍)

一、apache介绍。 apache是一个基金会的名字,httpd才是我们要安装的软件包,早起叫apache,从2.0版本开始叫apache。 apache官网:www.apache.org 下载三个软件 http://mirrors.cnnic.cn/apache/ apr和apr-util是一个通用的函数库,可以让HTTPd不关心底层的操作系统,很方便的移植,比如从Linux到windows http://mirrors.cnnic.cn/apache/apr/ 二、apache安装 分别解压三个软件 tar zxvf apr-***.tar.gz tar zxvf apr-util-***.tar.gz tar zxvf httpd-***.tar.gz 先安装apr cd apr-***.tar.gz ./configure --prefix=/usr/local/apr make && make install 在安装apr-util-***.tar.gz ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr (指定apr,依赖apr) make && make install 这里可能会出现一个问题,提示缺少expat的开发库,安装一下就好 yum install expat-devel -y 最后安装httpd-***.tar.gz ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr (指定apr,依赖apr) --with-apr-util=/usr/local/apr-util (指定apr-util,依赖apr-util) --enable-so (支持动态加载模块,例如php) --enable-mods-shared=most (指定加载哪些模块,most表示绝大多数) make && make install 这里会提示缺少pcre,pcre是个Perl库.包括perl兼容的正则表达式库 进入/usr/local/apache2.4中 bin下是可执行文件 conf 是配置文件所在目录 htdocs 存放了一个访问页,默认的访问网页 logs 日志相关的目录 man 帮助文档 modules 存放了扩展模块 /usr/local/apache2.4/bin/httpd -M /usr/local/apache2.4/bin/apachectl -M 都是查看apache加载了哪些模块 static 静态模块 和bin/httpd是绑定在一起的文件 shared 动态模块 是一个.so文件,是在modules目录下 /usr/local/apache2.4/bin/apachectl start 启动apache netstat -lntp 查看默认监听80端口,mysql默认监听3306端口,ssh默认监听22端口 apache的三种工作模式: (1)prefork:prefork模式算是很古老但是非常稳定的apache模式。apache在启动之初,就先fork一些子进程,然后等待请求进来。之所以这样做,是为了减少频繁创建和销毁进程的开销。每个子进程只有一个线程,在一个时间点内,只能处理一个请求 优点:成熟稳定,兼容所有新老模块。同时,不需要担心线程安全问题 缺点:一个进程相对占用更多的系统资源,消耗更多的内存。而且,他并不擅长处理高并发请求,在这种场景下,它会将请求放进队列里,一直等到有可用进程,请求才会被处理 (2)worker:和prefork模式相比,worker使用了多线程和多进程的混合模式,worker模式也同样会先预派生一些子进程,然后每个子进程创建一些线程,同时包括一个监控线程,每个请求过来会被分配到一个线程来服务。线程比进程更轻量,因为线程是通过共享父进程的内存空间,因此,内存的占用会减少一些,在高并发的场景下会比prefork有更多可用的线程,表现更优秀些 优点:占用更少的内存,高并发下表现优秀 缺点:必须考虑线程安全的问题,因为多个子线程是共享父进程的内存地址的。如果使用keep-alive长连接的方式,某个线程会一直被占据,也许中间几乎没有请求,需要一直等待到超时才会被释放。如果太多的线程,被这样占据,也会导致在高并发场景下的无服务线程可用 (3)event:它和worker模式很像,不同的是它解决了keep-alive长连接的时候占用线程资源被浪费的问题,在event模式中,会有一些专门的线程来管理这些keep-alive类型的线程,当有真实请求过来的时候,将请求传递给服务器的线程,执行完毕后,又允许它释放,增强了在高并发场景下的请求处理 本文转自 小新锐 51CTO博客,原文链接:http://blog.51cto.com/13407306/2051401,如需转载请自行联系原作者

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册